feat(audio): new package

This commit is contained in:
Neko Ayaka
2025-07-03 01:57:12 +08:00
parent b8f6b6e638
commit eb0e7a8fe3
12 changed files with 956 additions and 3 deletions
+1
View File
@@ -19,6 +19,7 @@
"@formkit/auto-animate": "^0.8.2", "@formkit/auto-animate": "^0.8.2",
"@huggingface/transformers": "^3.6.0", "@huggingface/transformers": "^3.6.0",
"@moeru/std": "catalog:", "@moeru/std": "catalog:",
"@proj-airi/audio": "workspace:^",
"@proj-airi/ccc": "workspace:^", "@proj-airi/ccc": "workspace:^",
"@proj-airi/drizzle-duckdb-wasm": "catalog:", "@proj-airi/drizzle-duckdb-wasm": "catalog:",
"@proj-airi/i18n": "workspace:*", "@proj-airi/i18n": "workspace:*",
+1
View File
@@ -21,6 +21,7 @@
"@huggingface/transformers": "^3.6.0", "@huggingface/transformers": "^3.6.0",
"@llama-flow/core": "^0.4.4", "@llama-flow/core": "^0.4.4",
"@moeru/std": "catalog:", "@moeru/std": "catalog:",
"@proj-airi/audio": "workspace:^",
"@proj-airi/ccc": "workspace:^", "@proj-airi/ccc": "workspace:^",
"@proj-airi/drizzle-duckdb-wasm": "catalog:", "@proj-airi/drizzle-duckdb-wasm": "catalog:",
"@proj-airi/i18n": "workspace:*", "@proj-airi/i18n": "workspace:*",
+62
View File
@@ -0,0 +1,62 @@
{
"name": "@proj-airi/audio",
"type": "module",
"private": true,
"description": "Audio processing utilities for Project AIRI",
"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/audio"
},
"exports": {
".": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
},
"./audio-context": {
"types": "./dist/audio-context/index.d.mts",
"default": "./dist/audio-context/index.mjs"
},
"./audio-context/processor.worklet": {
"types": "./dist/audio-context/processor.worklet.d.mts",
"default": "./dist/audio-context/processor.worklet.mjs"
},
"./vue": {
"types": "./dist/vue/index.d.mts",
"default": "./dist/vue/index.mjs"
}
},
"main": "./dist/index.mjs",
"types": "./dist/index.d.mts",
"files": [
"README.md",
"dist",
"package.json"
],
"scripts": {
"dev": "pnpm run build",
"build": "tsdown",
"typecheck": "tsc --noEmit"
},
"peerDependencies": {
"vue": ">=3"
},
"peerDependenciesMeta": {
"vue": {
"optional": true
}
},
"dependencies": {
"@alexanderolsen/libsamplerate-js": "^2.1.2"
},
"devDependencies": {
"@types/audioworklet": "^0.0.77",
"vite": "^7.0.0"
}
}
+316
View File
@@ -0,0 +1,316 @@
/// <reference types="vite/client" />
import LibsamplerateWorkletURL from '@alexanderolsen/libsamplerate-js/dist/libsamplerate.worklet.js?worker&url'
import ProcessorWorkletURL from './processor.worklet?worker&url'
let context: AudioContext | undefined
let sampleRate: number = 48000 // High quality base sample rate
let isReady: boolean = false
let error: string = ''
let isInitializing: boolean = false
let workletLoaded: boolean = false
const activeSources = new Set<MediaStreamAudioSourceNode>()
const activeGainNodes = new Set<GainNode>()
const activeAnalyzers = new Set<AnalyserNode>()
const activeWorkletNodes = new Set<AudioWorkletNode>()
const listeners = new Set<(state: State) => void>()
export interface State {
isReady: boolean
sampleRate: number
error: string
isInitializing: boolean
workletLoaded: boolean
currentTime: number
state: AudioContextState
}
export interface WorkletOptions {
inputSampleRate?: number
outputSampleRate?: number
channels?: number
converterType?: number
bufferSize?: number
}
function notifyListeners() {
const state: State = {
isReady,
sampleRate,
error,
isInitializing,
workletLoaded,
currentTime: context?.currentTime ?? 0,
state: context?.state ?? 'closed',
}
listeners.forEach((listener) => {
try {
listener(state)
}
catch (err) {
console.error('AudioContext state listener error:', err)
}
})
}
async function loadWorklets() {
if (!context || workletLoaded)
return
try {
await context.audioWorklet.addModule(ProcessorWorkletURL)
await context.audioWorklet.addModule(LibsamplerateWorkletURL)
workletLoaded = true
}
catch (err) {
console.error('Failed to load AudioWorklets:', err)
throw new Error(`Worklet loading failed: ${err}`)
}
}
export async function initializeAudioContext(requestedSampleRate: number = 48000): Promise<AudioContext> {
// Use high quality base sample rate
const baseSampleRate = Math.max(requestedSampleRate, 48000)
if (context && isReady && sampleRate === baseSampleRate && workletLoaded) {
return context
}
if (isInitializing) {
return new Promise((resolve, reject) => {
const checkReady = () => {
if (!isInitializing) {
if (context && isReady && workletLoaded) {
resolve(context)
}
else {
reject(new Error(error || 'AudioContext initialization failed'))
}
}
else {
setTimeout(checkReady, 10)
}
}
checkReady()
})
}
isInitializing = true
error = ''
notifyListeners()
try {
// Close existing context if sample rate changed
if (context && sampleRate !== baseSampleRate) {
await cleanupAudioContext()
}
// Create new context if needed
if (!context) {
context = new AudioContext({ sampleRate: baseSampleRate })
sampleRate = baseSampleRate
}
// Resume if suspended
if (context.state === 'suspended') {
await context.resume()
}
// Load worklets
await loadWorklets()
isReady = true
notifyListeners()
return context
}
catch (err) {
error = err instanceof Error ? err.message : String(err)
isReady = false
workletLoaded = false
notifyListeners()
console.error('Failed to initialize AudioContext:', err)
throw err
}
finally {
isInitializing = false
notifyListeners()
}
}
export function createAudioSource(mediaStream: MediaStream): MediaStreamAudioSourceNode {
if (!context || !isReady) {
throw new Error('AudioContext not initialized')
}
const source = context.createMediaStreamSource(mediaStream)
activeSources.add(source)
return source
}
export function createAudioAnalyser(options?: Partial<{
fftSize: number
smoothingTimeConstant: number
minDecibels: number
maxDecibels: number
}>): AnalyserNode {
if (!context || !isReady) {
throw new Error('AudioContext not initialized')
}
const analyser = context.createAnalyser()
if (options?.fftSize)
analyser.fftSize = options.fftSize
if (options?.smoothingTimeConstant !== undefined) {
analyser.smoothingTimeConstant = options.smoothingTimeConstant
}
if (options?.minDecibels !== undefined)
analyser.minDecibels = options.minDecibels
if (options?.maxDecibels !== undefined)
analyser.maxDecibels = options.maxDecibels
activeAnalyzers.add(analyser)
return analyser
}
export function createAudioGainNode(initialGain: number = 1): GainNode {
if (!context || !isReady) {
throw new Error('AudioContext not initialized')
}
const gainNode = context.createGain()
gainNode.gain.value = initialGain
activeGainNodes.add(gainNode)
return gainNode
}
export function removeAudioSource(source: MediaStreamAudioSourceNode) {
source.disconnect()
activeSources.delete(source)
}
export function removeAudioGainNode(gainNode: GainNode) {
gainNode.disconnect()
activeGainNodes.delete(gainNode)
}
export function removeAudioAnalyser(analyser: AnalyserNode) {
analyser.disconnect()
activeAnalyzers.delete(analyser)
}
export async function suspendAudioContext() {
if (context && context.state === 'running') {
await context.suspend()
notifyListeners()
}
}
export async function resumeAudioContext() {
if (context && context.state === 'suspended') {
await context.resume()
notifyListeners()
}
}
export function createResamplingWorkletNode(
inputNode: AudioNode,
options: WorkletOptions = {},
): AudioWorkletNode {
if (!context || !isReady || !workletLoaded) {
throw new Error('AudioContext or worklets not ready')
}
const workletOptions = {
inputSampleRate: sampleRate,
outputSampleRate: 16000,
channels: 1,
converterType: 2, // SRC_SINC_MEDIUM_QUALITY
bufferSize: 4096,
...options,
}
const workletNode = new AudioWorkletNode(context, 'resampling-processor', {
numberOfInputs: 1,
numberOfOutputs: 1,
channelCount: workletOptions.channels,
processorOptions: workletOptions,
})
// Connect input to worklet
inputNode.connect(workletNode)
activeWorkletNodes.add(workletNode)
return workletNode
}
export function removeWorkletNode(node: AudioWorkletNode) {
node.disconnect()
activeWorkletNodes.delete(node)
}
export async function cleanupAudioContext() {
// Disconnect all active nodes
activeSources.forEach(source => source.disconnect())
activeGainNodes.forEach(gainNode => gainNode.disconnect())
activeAnalyzers.forEach(analyser => analyser.disconnect())
activeWorkletNodes.forEach(worklet => worklet.disconnect())
// Clear sets
activeSources.clear()
activeGainNodes.clear()
activeAnalyzers.clear()
activeWorkletNodes.clear()
// Close context
if (context && context.state !== 'closed') {
await context.close()
}
context = undefined
isReady = false
workletLoaded = false
error = ''
notifyListeners()
}
export function getAudioContextState(): State {
return {
isReady,
sampleRate,
error,
isInitializing,
workletLoaded,
currentTime: context?.currentTime ?? 0,
state: context?.state ?? 'closed',
}
}
export function getAudioContext(): AudioContext | undefined {
return context
}
export function getCurrentTime(): number {
return context?.currentTime ?? 0
}
export function isAudioContextReady(): boolean {
return isReady
}
// Event subscription
export function subscribeToAudioContext(listener: (state: State) => void): () => void {
listeners.add(listener)
return () => listeners.delete(listener)
}
// Browser cleanup
if ('window' in globalThis && globalThis.window != null) {
globalThis.window.addEventListener('beforeunload', cleanupAudioContext)
globalThis.window.addEventListener('pagehide', cleanupAudioContext)
}
@@ -0,0 +1,160 @@
/// <reference types="@types/audioworklet" />
import type { ConverterTypeValue } from '@alexanderolsen/libsamplerate-js/dist/converter-type'
import { ConverterType, create } from '@alexanderolsen/libsamplerate-js'
interface ProcessorOptions {
inputSampleRate: number
outputSampleRate: number
channels: number
converterType: ConverterTypeValue
bufferSize: number
}
class ResamplingAudioWorkletProcessor extends AudioWorkletProcessor {
private converter: Awaited<ReturnType<typeof create>> | null = null
private isInitialized = false
private options: ProcessorOptions
private inputBuffer: Float32Array[] = []
private outputBuffer: Float32Array[] = []
private bufferSize: number
constructor(options: AudioWorkletNodeOptions) {
super()
this.options = {
inputSampleRate: options.processorOptions?.inputSampleRate || 44100,
outputSampleRate: options.processorOptions?.outputSampleRate || 16000,
channels: options.processorOptions?.channels || 1,
converterType: options.processorOptions?.converterType || ConverterType.SRC_SINC_MEDIUM_QUALITY,
bufferSize: options.processorOptions?.bufferSize || 4096,
}
this.bufferSize = this.options.bufferSize
// Initialize input/output buffers for each channel
for (let i = 0; i < this.options.channels; i++) {
this.inputBuffer[i] = new Float32Array(this.bufferSize)
this.outputBuffer[i] = new Float32Array(0)
}
this.initializeConverter()
// Listen for messages from main thread
this.port.onmessage = (event) => {
if (event.data.type === 'updateOptions') {
this.updateOptions(event.data.options)
}
}
}
private async initializeConverter() {
try {
this.converter = await create(
this.options.channels,
this.options.inputSampleRate,
this.options.outputSampleRate,
{
converterType: this.options.converterType,
},
)
this.isInitialized = true
this.port.postMessage({ type: 'initialized', success: true })
}
catch (error) {
console.error('Failed to initialize sample rate converter:', error)
this.port.postMessage({
type: 'initialized',
success: false,
error: error instanceof Error ? error.message : String(error),
})
}
}
private async updateOptions(newOptions: Partial<ProcessorOptions>) {
const needsReinitialize
= newOptions.inputSampleRate !== this.options.inputSampleRate
|| newOptions.outputSampleRate !== this.options.outputSampleRate
|| newOptions.channels !== this.options.channels
|| newOptions.converterType !== this.options.converterType
Object.assign(this.options, newOptions)
if (needsReinitialize && this.converter) {
this.converter.destroy()
this.converter = null
this.isInitialized = false
await this.initializeConverter()
}
}
process(inputs: Float32Array[][], outputs: Float32Array[][]): boolean {
const input = inputs[0]
const output = outputs[0]
if (!this.isInitialized || !this.converter || !input.length) {
// Pass through if not ready
for (let channel = 0; channel < output.length; channel++) {
if (input[channel]) {
output[channel].set(input[channel])
}
}
return true
}
try {
// Process each channel
for (let channel = 0; channel < Math.min(input.length, this.options.channels); channel++) {
const inputData = input[channel]
if (inputData && inputData.length > 0) {
// Resample the input data
const resampledData = this.converter.simple(inputData)
// Send resampled data to main thread
this.port.postMessage({
type: 'audioData',
channel,
data: resampledData,
originalSampleRate: this.options.inputSampleRate,
outputSampleRate: this.options.outputSampleRate,
timestamp: currentTime,
})
// Copy to output (you might want to buffer this properly for different sample rates)
if (output[channel]) {
const copyLength = Math.min(resampledData.length, output[channel].length)
for (let i = 0; i < copyLength; i++) {
output[channel][i] = resampledData[i]
}
// Zero-pad remaining
for (let i = copyLength; i < output[channel].length; i++) {
output[channel][i] = 0
}
}
}
}
}
catch (error) {
console.error('Resampling error in worklet:', error)
this.port.postMessage({
type: 'error',
error: error instanceof Error ? error.message : String(error),
})
// Pass through original data on error
for (let channel = 0; channel < output.length; channel++) {
if (input[channel]) {
output[channel].set(input[channel])
}
}
}
return true
}
}
registerProcessor('resampling-processor', ResamplingAudioWorkletProcessor)
View File
+94
View File
@@ -0,0 +1,94 @@
import type { MaybeRefOrGetter } from 'vue'
import { computed, onUnmounted, ref, toRef, watch } from 'vue'
import { createAudioGainNode, createAudioSource, getAudioContext, getCurrentTime, initializeAudioContext, removeAudioGainNode, removeAudioSource } from '../audio-context'
export function useAudioPlayback(src: MaybeRefOrGetter<MediaStream | undefined>) {
const audioContext = getAudioContext()
const sourceStream = toRef(src)
const isEnabled = ref(false)
const volume = ref(50) // 0-100
const error = ref('')
// Audio nodes for playback
const source = ref<MediaStreamAudioSourceNode>()
const gainNode = ref<GainNode>()
const actualVolume = computed(() => volume.value / 100)
async function setupPlayback() {
if (!sourceStream.value || !isEnabled.value)
return
try {
error.value = ''
// Ensure audio context is ready
await initializeAudioContext()
// Clean up existing playback
cleanupPlayback()
// Create new nodes
source.value = createAudioSource(sourceStream.value)
gainNode.value = createAudioGainNode(actualVolume.value)
// Connect: source -> gain -> destination
source.value.connect(gainNode.value)
gainNode.value.connect(audioContext!.destination)
}
catch (err) {
error.value = err instanceof Error ? err.message : String(err)
console.error('Failed to setup audio playback:', err)
}
}
function cleanupPlayback() {
if (source.value) {
removeAudioSource(source.value)
source.value = undefined
}
if (gainNode.value) {
removeAudioGainNode(gainNode.value)
gainNode.value = undefined
}
}
// Watch for changes
watch([sourceStream, isEnabled], async () => {
if (isEnabled.value && sourceStream.value) {
await setupPlayback()
}
else {
cleanupPlayback()
}
})
watch(actualVolume, (newVolume) => {
if (gainNode.value) {
// Smooth volume changes to prevent clicks
const currentTime = getCurrentTime()
gainNode.value.gain.cancelScheduledValues(currentTime)
gainNode.value.gain.setValueAtTime(gainNode.value.gain.value, currentTime)
gainNode.value.gain.linearRampToValueAtTime(newVolume, currentTime + 0.1)
}
})
onUnmounted(() => {
cleanupPlayback()
})
return {
// State
isEnabled,
volume,
error,
// Manual control
setupPlayback,
cleanupPlayback,
}
}
+194
View File
@@ -0,0 +1,194 @@
import type { MaybeRefOrGetter } from 'vue'
import { onUnmounted, ref, toRef } from 'vue'
import { createAudioAnalyser, createAudioSource, getAudioContext, getCurrentTime, initializeAudioContext } from '../audio-context'
export interface AudioStreamConfig {
deviceId: string
sampleRate?: number
echoCancellation?: boolean
noiseSuppression?: boolean
autoGainControl?: boolean
}
export interface AudioAnalysisData {
volumeLevel: number
frequencyData: Uint8Array
timeDomainData: Float32Array
timestamp: number
}
export type AudioAnalysisCallback = (data: AudioAnalysisData) => void
export type AudioChunkCallback = (chunk: Float32Array, sampleRate: number) => void
export function useAudioStream(cfg: MaybeRefOrGetter<AudioStreamConfig | undefined>) {
const config = toRef(cfg)
const isActive = ref(false)
const error = ref<string>('')
const isInitializing = ref(false) // Add loading state
// Stream-specific state
const mediaStream = ref<MediaStream>()
const source = ref<MediaStreamAudioSourceNode>()
const analyser = ref<AnalyserNode>()
// Callbacks for different types of analysis
const analysisCallbacks = new Set<AudioAnalysisCallback>()
const chunkCallbacks = new Set<AudioChunkCallback>()
// Audio data arrays
const dataArray = ref<Uint8Array>()
const timeDataArray = ref<Float32Array>()
// Helper function to wait for audio context
async function waitForAudioContext(maxRetries = 10, delay = 100) {
for (let i = 0; i < maxRetries; i++) {
const audioContext = getAudioContext()
if (audioContext && audioContext.state !== 'closed') {
return audioContext
}
await new Promise(resolve => setTimeout(resolve, delay))
}
throw new Error('Audio context failed to initialize within timeout')
}
async function start() {
if (isActive.value || isInitializing.value)
return
try {
isInitializing.value = true
error.value = ''
// Initialize global audio context and wait for it
await initializeAudioContext(config.value?.sampleRate || 16000)
await waitForAudioContext()
// Get user media
mediaStream.value = await navigator.mediaDevices.getUserMedia({
audio: {
deviceId: config.value?.deviceId,
sampleRate: config.value?.sampleRate || 16000,
echoCancellation: config.value?.echoCancellation ?? true,
noiseSuppression: config.value?.noiseSuppression ?? true,
autoGainControl: config.value?.autoGainControl ?? true,
},
})
// Ensure we still have a valid audio context after getUserMedia
const currentAudioContext = getAudioContext()
if (!currentAudioContext) {
throw new Error('Audio context became unavailable')
}
// Create audio nodes using global context
source.value = createAudioSource(mediaStream.value)
analyser.value = createAudioAnalyser({
fftSize: 512,
smoothingTimeConstant: 0.1,
})
// Connect nodes
source.value.connect(analyser.value)
// Setup data arrays
const bufferLength = analyser.value.frequencyBinCount
dataArray.value = new Uint8Array(bufferLength)
timeDataArray.value = new Float32Array(analyser.value.fftSize)
isActive.value = true
startAnalysis()
}
catch (err) {
error.value = err instanceof Error ? err.message : String(err)
console.error('Failed to start audio stream:', err)
}
finally {
isInitializing.value = false
}
}
function startAnalysis() {
const analyze = () => {
const audioContext = getAudioContext()
if (!audioContext || !analyser.value || !dataArray.value || !timeDataArray.value)
return
// Get frequency and time domain data
analyser.value.getByteFrequencyData(dataArray.value)
analyser.value.getFloatTimeDomainData(timeDataArray.value)
// Calculate volume level
let sum = 0
for (let i = 0; i < dataArray.value.length; i++) {
sum += dataArray.value[i] * dataArray.value[i]
}
const rms = Math.sqrt(sum / dataArray.value.length)
const volumeLevel = Math.min(100, (rms / 255) * 100 * 3)
// Create analysis data
const analysisData: AudioAnalysisData = {
volumeLevel,
frequencyData: new Uint8Array(dataArray.value),
timeDomainData: new Float32Array(timeDataArray.value),
timestamp: getCurrentTime(),
}
// Notify analysis callbacks
analysisCallbacks.forEach((callback) => {
try {
callback(analysisData)
}
catch (err) {
console.error('Audio analysis callback error:', err)
}
})
// Notify chunk callbacks with fresh audioContext reference
chunkCallbacks.forEach((callback) => {
try {
callback(
new Float32Array(timeDataArray.value),
audioContext.sampleRate,
)
}
catch (err) {
console.error('Audio chunk callback error:', err)
}
})
}
analyze()
}
// Callback management
function addAnalysisCallback(callback: AudioAnalysisCallback) {
analysisCallbacks.add(callback)
return () => analysisCallbacks.delete(callback)
}
function addChunkCallback(callback: AudioChunkCallback) {
chunkCallbacks.add(callback)
return () => chunkCallbacks.delete(callback)
}
// Cleanup
onUnmounted(() => {
stop()
})
return {
isActive,
error,
mediaStream,
isInitializing,
start,
stop,
addAnalysisCallback,
addChunkCallback,
}
}
+2
View File
@@ -0,0 +1,2 @@
export * from './audio-playback'
export * from './audio-stream'
+22
View File
@@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "ESNext",
"lib": [
"ESNext",
"DOM"
],
"module": "ESNext",
"moduleResolution": "bundler",
"types": [
"@types/audioworklet"
],
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true
},
"include": [
"src/**/*.ts"
]
}
+16
View File
@@ -0,0 +1,16 @@
import { defineConfig } from 'tsdown'
export default defineConfig({
entry: {
'index': 'src/index.ts',
'audio-context/index': 'src/audio-context/index.ts',
'audio-context/processor.worklet': 'src/audio-context/processor.worklet.ts',
'vue/index': 'src/vue/index.ts',
},
unbundle: true,
fixedExtension: true,
external: [
'@alexanderolsen/libsamplerate-js/dist/libsamplerate.worklet.js?worker&url',
'./processor.worklet?worker&url',
],
})
+88 -3
View File
@@ -273,6 +273,9 @@ importers:
'@moeru/std': '@moeru/std':
specifier: 'catalog:' specifier: 'catalog:'
version: 0.1.0-beta.4 version: 0.1.0-beta.4
'@proj-airi/audio':
specifier: workspace:^
version: link:../../packages/audio
'@proj-airi/ccc': '@proj-airi/ccc':
specifier: workspace:^ specifier: workspace:^
version: link:../../packages/ccc version: link:../../packages/ccc
@@ -562,6 +565,9 @@ importers:
'@moeru/std': '@moeru/std':
specifier: 'catalog:' specifier: 'catalog:'
version: 0.1.0-beta.4 version: 0.1.0-beta.4
'@proj-airi/audio':
specifier: workspace:^
version: link:../../packages/audio
'@proj-airi/ccc': '@proj-airi/ccc':
specifier: workspace:^ specifier: workspace:^
version: link:../../packages/ccc version: link:../../packages/ccc
@@ -861,6 +867,22 @@ importers:
specifier: ^3.5.17 specifier: ^3.5.17
version: 3.5.17(typescript@5.8.3) version: 3.5.17(typescript@5.8.3)
packages/audio:
dependencies:
'@alexanderolsen/libsamplerate-js':
specifier: ^2.1.2
version: 2.1.2
vue:
specifier: '>=3'
version: 3.5.17(typescript@5.8.3)
devDependencies:
'@types/audioworklet':
specifier: ^0.0.77
version: 0.0.77
vite:
specifier: ^7.0.0
version: 7.0.0(@types/node@24.0.7)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)
packages/ccc: packages/ccc:
dependencies: dependencies:
meta-png: meta-png:
@@ -877,7 +899,7 @@ importers:
devDependencies: devDependencies:
unplugin-yaml: unplugin-yaml:
specifier: ^3.0.0 specifier: ^3.0.0
version: 3.0.0(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.44.1))(@nuxt/schema@3.14.1592(magicast@0.3.5)(rollup@4.44.1))(astro@5.10.1(@types/node@24.0.7)(encoding@0.1.13)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(rollup@4.44.1)(terser@5.43.1)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0))(esbuild@0.25.5)(rolldown@1.0.0-beta.20)(rollup@4.44.1)(vite@6.3.5(@types/node@24.0.7)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)) version: 3.0.0(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.44.1))(@nuxt/schema@3.14.1592(magicast@0.3.5)(rollup@4.44.1))(astro@5.10.1(@types/node@24.0.7)(encoding@0.1.13)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(rollup@4.44.1)(terser@5.43.1)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0))(esbuild@0.25.5)(rolldown@1.0.0-beta.20)(rollup@4.44.1)(vite@7.0.0(@types/node@24.0.7)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0))
packages/memory-pgvector: packages/memory-pgvector:
dependencies: dependencies:
@@ -1653,6 +1675,9 @@ packages:
resolution: {integrity: sha512-nznEC1ZA/m3hQDEnrGQ4c5gkaa9pcaVnw4LFJyzBAaR7E3nfiAPEHS3otnSafpZouVnoKeITl5D+2LsnwlnK8g==} resolution: {integrity: sha512-nznEC1ZA/m3hQDEnrGQ4c5gkaa9pcaVnw4LFJyzBAaR7E3nfiAPEHS3otnSafpZouVnoKeITl5D+2LsnwlnK8g==}
engines: {node: '>=14.0.0'} engines: {node: '>=14.0.0'}
'@alexanderolsen/libsamplerate-js@2.1.2':
resolution: {integrity: sha512-pIXQDX/DZIgz6pKInUddDd6Tnq/s2E9g4ZITkKX60kxM/nAuZcxYa/z0y/jwJbjyp/oKe+8/qHLSqMzQ18xSiQ==}
'@alvarosabu/utils@3.2.0': '@alvarosabu/utils@3.2.0':
resolution: {integrity: sha512-aoGWRfaQjOo9TUwrBA6W0zwTHktgrXy69GIFNILT4gHsqscw6+X8P6uoSlZVQFr887SPm8x3aDin5EBVq8y4pw==} resolution: {integrity: sha512-aoGWRfaQjOo9TUwrBA6W0zwTHktgrXy69GIFNILT4gHsqscw6+X8P6uoSlZVQFr887SPm8x3aDin5EBVq8y4pw==}
@@ -11501,6 +11526,46 @@ packages:
yaml: yaml:
optional: true optional: true
vite@7.0.0:
resolution: {integrity: sha512-ixXJB1YRgDIw2OszKQS9WxGHKwLdCsbQNkpJN171udl6szi/rIySHL6/Os3s2+oE4P/FLD4dxg4mD7Wust+u5g==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
'@types/node': ^20.19.0 || >=22.12.0
jiti: '>=1.21.0'
less: ^4.0.0
lightningcss: ^1.21.0
sass: ^1.70.0
sass-embedded: ^1.70.0
stylus: '>=0.54.8'
sugarss: ^5.0.0
terser: ^5.16.0
tsx: ^4.8.1
yaml: ^2.4.2
peerDependenciesMeta:
'@types/node':
optional: true
jiti:
optional: true
less:
optional: true
lightningcss:
optional: true
sass:
optional: true
sass-embedded:
optional: true
stylus:
optional: true
sugarss:
optional: true
terser:
optional: true
tsx:
optional: true
yaml:
optional: true
vitefu@1.0.7: vitefu@1.0.7:
resolution: {integrity: sha512-eRWXLBbJjW3X5z5P5IHcSm2yYbYRPb2kQuc+oqsbAl99WB5kVsPbiiox+cymo8twTzifA6itvhr2CmjnaZZp0Q==} resolution: {integrity: sha512-eRWXLBbJjW3X5z5P5IHcSm2yYbYRPb2kQuc+oqsbAl99WB5kVsPbiiox+cymo8twTzifA6itvhr2CmjnaZZp0Q==}
peerDependencies: peerDependencies:
@@ -12049,6 +12114,8 @@ snapshots:
'@akryum/tinypool@0.3.1': {} '@akryum/tinypool@0.3.1': {}
'@alexanderolsen/libsamplerate-js@2.1.2': {}
'@alvarosabu/utils@3.2.0': {} '@alvarosabu/utils@3.2.0': {}
'@ampproject/remapping@2.3.0': '@ampproject/remapping@2.3.0':
@@ -23982,7 +24049,7 @@ snapshots:
rollup: 4.44.1 rollup: 4.44.1
vite: rolldown-vite@6.3.21(@types/node@24.0.7)(esbuild@0.25.5)(jiti@2.4.2)(less@4.3.0)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0) vite: rolldown-vite@6.3.21(@types/node@24.0.7)(esbuild@0.25.5)(jiti@2.4.2)(less@4.3.0)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)
unplugin-yaml@3.0.0(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.44.1))(@nuxt/schema@3.14.1592(magicast@0.3.5)(rollup@4.44.1))(astro@5.10.1(@types/node@24.0.7)(encoding@0.1.13)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(rollup@4.44.1)(terser@5.43.1)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0))(esbuild@0.25.5)(rolldown@1.0.0-beta.20)(rollup@4.44.1)(vite@6.3.5(@types/node@24.0.7)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)): unplugin-yaml@3.0.0(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.44.1))(@nuxt/schema@3.14.1592(magicast@0.3.5)(rollup@4.44.1))(astro@5.10.1(@types/node@24.0.7)(encoding@0.1.13)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(rollup@4.44.1)(terser@5.43.1)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0))(esbuild@0.25.5)(rolldown@1.0.0-beta.20)(rollup@4.44.1)(vite@7.0.0(@types/node@24.0.7)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)):
dependencies: dependencies:
'@rollup/pluginutils': 5.2.0(rollup@4.44.1) '@rollup/pluginutils': 5.2.0(rollup@4.44.1)
unplugin: 2.3.5 unplugin: 2.3.5
@@ -23994,7 +24061,7 @@ snapshots:
esbuild: 0.25.5 esbuild: 0.25.5
rolldown: 1.0.0-beta.20 rolldown: 1.0.0-beta.20
rollup: 4.44.1 rollup: 4.44.1
vite: 6.3.5(@types/node@24.0.7)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0) vite: 7.0.0(@types/node@24.0.7)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)
unplugin@1.16.1: unplugin@1.16.1:
dependencies: dependencies:
@@ -24349,6 +24416,24 @@ snapshots:
tsx: 4.20.3 tsx: 4.20.3
yaml: 2.8.0 yaml: 2.8.0
vite@7.0.0(@types/node@24.0.7)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0):
dependencies:
esbuild: 0.25.5
fdir: 6.4.6(picomatch@4.0.2)
picomatch: 4.0.2
postcss: 8.5.6
rollup: 4.44.1
tinyglobby: 0.2.14
optionalDependencies:
'@types/node': 24.0.7
fsevents: 2.3.3
jiti: 2.4.2
less: 4.3.0
lightningcss: 1.30.1
terser: 5.43.1
tsx: 4.20.3
yaml: 2.8.0
vitefu@1.0.7(vite@6.3.5(@types/node@24.0.7)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)): vitefu@1.0.7(vite@6.3.5(@types/node@24.0.7)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)):
optionalDependencies: optionalDependencies:
vite: 6.3.5(@types/node@24.0.7)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0) vite: 6.3.5(@types/node@24.0.7)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)