mirror of
https://github.com/moeru-ai/airi.git
synced 2026-08-14 08:52:42 +00:00
feat(stage-*): experimental realtime transcription powered by Aliyun NLS
This commit is contained in:
@@ -93,6 +93,7 @@
|
||||
"three": "^0.181.0",
|
||||
"unified": "^11.0.5",
|
||||
"unspeech": "^0.1.7",
|
||||
"uuid": "^13.0.0",
|
||||
"valibot": "1.0.0-beta.9",
|
||||
"vaul-vue": "^0.4.1",
|
||||
"vue": "^3.5.22",
|
||||
|
||||
@@ -13,7 +13,7 @@ import { useConsciousnessStore } from '@proj-airi/stage-ui/stores/modules/consci
|
||||
import { useHearingSpeechInputPipeline } from '@proj-airi/stage-ui/stores/modules/hearing'
|
||||
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
|
||||
import { useSettingsAudioDevice } from '@proj-airi/stage-ui/stores/settings'
|
||||
import { debouncedRef, useBroadcastChannel, watchPausable } from '@vueuse/core'
|
||||
import { refDebounced, useBroadcastChannel } from '@vueuse/core'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, onUnmounted, ref, toRef, watch } from 'vue'
|
||||
|
||||
@@ -43,11 +43,11 @@ const shouldFadeOnCursorWithin = ref(false)
|
||||
|
||||
const { isOutside: isOutsideWindow } = useElectronMouseInWindow()
|
||||
const { isOutside } = useElectronMouseInElement(controlsIslandRef)
|
||||
const isOutsideFor250Ms = debouncedRef(isOutside, 250)
|
||||
const isOutsideFor250Ms = refDebounced(isOutside, 250)
|
||||
const { x: relativeMouseX, y: relativeMouseY } = useElectronRelativeMouse()
|
||||
const isTransparent = useCanvasPixelIsTransparentAtPoint(stageCanvas, relativeMouseX, relativeMouseY)
|
||||
const { isNearAnyBorder: isAroundWindowBorder } = useElectronMouseAroundWindowBorder({ threshold: 30 })
|
||||
const isAroundWindowBorderFor250Ms = debouncedRef(isAroundWindowBorder, 250)
|
||||
const isAroundWindowBorderFor250Ms = refDebounced(isAroundWindowBorder, 250)
|
||||
|
||||
const setIgnoreMouseEvents = useElectronEventaInvoke(electron.window.setIgnoreMouseEvents)
|
||||
|
||||
@@ -56,7 +56,7 @@ const { live2dLookAtX, live2dLookAtY } = storeToRefs(useWindowStore())
|
||||
|
||||
watch(componentStateStage, () => isLoading.value = componentStateStage.value !== 'mounted', { immediate: true })
|
||||
|
||||
const { pause, resume } = watchPausable(isTransparent, (transparent) => {
|
||||
const { pause, resume } = watch(isTransparent, (transparent) => {
|
||||
shouldFadeOnCursorWithin.value = !transparent
|
||||
}, { immediate: true })
|
||||
|
||||
|
||||
@@ -36,6 +36,12 @@ const menu = computed(() => [
|
||||
icon: 'i-solar:sledgehammer-bold-duotone',
|
||||
to: '/devtools/use-electron-relative-mouse',
|
||||
},
|
||||
{
|
||||
title: 'Aliyun Real-time Transcriber',
|
||||
description: 'Stream microphone audio to Aliyun NLS and inspect live transcripts',
|
||||
icon: 'i-solar:sledgehammer-bold-duotone',
|
||||
to: '/devtools/providers-transcription-realtime-aliyun-nls',
|
||||
},
|
||||
])
|
||||
|
||||
const { context } = createContext(window.electron.ipcRenderer)
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
"three": "^0.181.0",
|
||||
"unified": "^11.0.5",
|
||||
"unspeech": "^0.1.7",
|
||||
"uuid": "^13.0.0",
|
||||
"valibot": "1.0.0-beta.9",
|
||||
"vaul-vue": "^0.4.1",
|
||||
"vue": "^3.5.22",
|
||||
|
||||
@@ -62,6 +62,12 @@ const menu = computed(() => [
|
||||
icon: 'i-solar:sledgehammer-bold-duotone',
|
||||
to: '/devtools/vibrant',
|
||||
},
|
||||
{
|
||||
title: 'Aliyun Real-time Transcriber',
|
||||
description: 'Stream microphone audio to Aliyun NLS and inspect live transcripts',
|
||||
icon: 'i-solar:sledgehammer-bold-duotone',
|
||||
to: '/devtools/providers-transcription-realtime-aliyun-nls',
|
||||
},
|
||||
])
|
||||
</script>
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ words:
|
||||
- alexanderolsen
|
||||
- alibabacloud
|
||||
- aliyun
|
||||
- aliyuncs
|
||||
- allseto
|
||||
- animejs
|
||||
- APNG
|
||||
@@ -238,6 +239,7 @@ words:
|
||||
- sensenova
|
||||
- serde
|
||||
- Shadcn
|
||||
- shenzhen
|
||||
- shiki
|
||||
- shikijs
|
||||
- silero
|
||||
|
||||
+542
@@ -0,0 +1,542 @@
|
||||
<script setup lang="ts">
|
||||
import type { ServerEvent, ServerEvents } from '@proj-airi/stage-ui/stores/providers/aliyun'
|
||||
|
||||
import vadWorkletUrl from '@proj-airi/stage-ui/workers/vad/process.worklet?worker&url'
|
||||
|
||||
import { Button } from '@proj-airi/stage-ui/components'
|
||||
import { createAliyunNLSSession } from '@proj-airi/stage-ui/stores/providers/aliyun'
|
||||
import { FieldInput, FieldSelect } from '@proj-airi/ui'
|
||||
import { computed, nextTick, onBeforeUnmount, reactive, ref, shallowRef, watch } from 'vue'
|
||||
|
||||
type ConnectionState = 'idle' | 'connecting' | 'connected' | 'error' | 'closed'
|
||||
type AliyunRegion
|
||||
= | 'cn-shanghai'
|
||||
| 'cn-shanghai-internal'
|
||||
| 'cn-beijing'
|
||||
| 'cn-beijing-internal'
|
||||
| 'cn-shenzhen'
|
||||
| 'cn-shenzhen-internal'
|
||||
|
||||
const credentials = reactive({
|
||||
accessKeyId: '',
|
||||
accessKeySecret: '',
|
||||
appKey: '',
|
||||
region: 'cn-shanghai' as AliyunRegion,
|
||||
})
|
||||
|
||||
const connectionState = ref<ConnectionState>('idle')
|
||||
const transcriptionReady = ref(false)
|
||||
const isRecording = ref(false)
|
||||
const currentPartial = ref<string | undefined>('')
|
||||
const transcripts = ref<Array<{ index: number, text: string, final: boolean }>>([])
|
||||
|
||||
const websocket = shallowRef<WebSocket>()
|
||||
const session = shallowRef<ReturnType<typeof createAliyunNLSSession>>()
|
||||
const sessionId = ref('')
|
||||
|
||||
const audioContext = shallowRef<AudioContext>()
|
||||
const workletNode = shallowRef<AudioWorkletNode>()
|
||||
const mediaStreamSource = shallowRef<MediaStreamAudioSourceNode>()
|
||||
const mediaStream = shallowRef<MediaStream>()
|
||||
|
||||
const logs = ref<Array<{ id: number, level: 'info' | 'error', text: string }>>([])
|
||||
const logsContainer = ref<HTMLDivElement>()
|
||||
|
||||
const regionOptions: { label: string, value: AliyunRegion }[] = [
|
||||
{ label: 'cn-shanghai', value: 'cn-shanghai' },
|
||||
{ label: 'cn-beijing', value: 'cn-beijing' },
|
||||
{ label: 'cn-shenzhen', value: 'cn-shenzhen' },
|
||||
{ label: 'cn-shanghai (internal)', value: 'cn-shanghai-internal' },
|
||||
{ label: 'cn-beijing (internal)', value: 'cn-beijing-internal' },
|
||||
{ label: 'cn-shenzhen (internal)', value: 'cn-shenzhen-internal' },
|
||||
]
|
||||
|
||||
const statusLabel = computed(() => {
|
||||
switch (connectionState.value) {
|
||||
case 'connecting':
|
||||
return 'Connecting'
|
||||
case 'connected':
|
||||
return 'Connected'
|
||||
case 'error':
|
||||
return 'Error'
|
||||
case 'closed':
|
||||
return 'Disconnected'
|
||||
default:
|
||||
return 'Idle'
|
||||
}
|
||||
})
|
||||
|
||||
const statusColor = computed(() => {
|
||||
switch (connectionState.value) {
|
||||
case 'connected':
|
||||
return 'text-green-500'
|
||||
case 'connecting':
|
||||
return 'text-blue-500'
|
||||
case 'error':
|
||||
return 'text-red-500'
|
||||
default:
|
||||
return 'text-neutral-500 dark:text-neutral-400'
|
||||
}
|
||||
})
|
||||
|
||||
const canConnect = computed(() => {
|
||||
return (
|
||||
!!credentials.accessKeyId
|
||||
&& !!credentials.accessKeySecret
|
||||
&& !!credentials.appKey
|
||||
&& connectionState.value !== 'connecting'
|
||||
&& connectionState.value !== 'connected'
|
||||
)
|
||||
})
|
||||
|
||||
const canStartRecording = computed(() => {
|
||||
return connectionState.value === 'connected' && transcriptionReady.value && !isRecording.value
|
||||
})
|
||||
|
||||
const canStopRecording = computed(() => isRecording.value)
|
||||
|
||||
const canDisconnect = computed(() => websocket.value && websocket.value.readyState !== WebSocket.CLOSED)
|
||||
|
||||
let audioChunkCount = 0
|
||||
let lastChunkLogAt = 0
|
||||
|
||||
watch(logs, () => {
|
||||
nextTick(() => {
|
||||
const container = logsContainer.value
|
||||
if (container)
|
||||
container.scrollTop = container.scrollHeight
|
||||
})
|
||||
})
|
||||
|
||||
function appendLog(message: string, level: 'info' | 'error' = 'info') {
|
||||
logs.value.push({
|
||||
id: Date.now() + Math.random(),
|
||||
level,
|
||||
text: `[${new Date().toLocaleTimeString()}] ${message}`,
|
||||
})
|
||||
}
|
||||
|
||||
function float32ToInt16(buffer: Float32Array) {
|
||||
const output = new Int16Array(buffer.length)
|
||||
for (let i = 0; i < buffer.length; i++) {
|
||||
const value = Math.max(-1, Math.min(1, buffer[i]))
|
||||
output[i] = value < 0 ? value * 0x8000 : value * 0x7FFF
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
function ensureWebSocketOpen() {
|
||||
return websocket.value && websocket.value.readyState === WebSocket.OPEN
|
||||
}
|
||||
|
||||
function resetTranscriptionState() {
|
||||
transcriptionReady.value = false
|
||||
currentPartial.value = ''
|
||||
transcripts.value = []
|
||||
audioChunkCount = 0
|
||||
lastChunkLogAt = 0
|
||||
}
|
||||
|
||||
async function initializeAudioGraph(stream: MediaStream) {
|
||||
const context = new AudioContext({
|
||||
sampleRate: 16000,
|
||||
latencyHint: 'interactive',
|
||||
})
|
||||
await context.audioWorklet.addModule(vadWorkletUrl)
|
||||
|
||||
const node = new AudioWorkletNode(context, 'vad-audio-worklet-processor')
|
||||
node.port.onmessage = ({ data }: MessageEvent<{ buffer?: Float32Array }>) => {
|
||||
const buffer = data.buffer
|
||||
if (!buffer || !ensureWebSocketOpen())
|
||||
return
|
||||
|
||||
audioChunkCount += 1
|
||||
if (audioChunkCount === 1 || audioChunkCount - lastChunkLogAt >= 50) {
|
||||
appendLog(`Streaming audio chunk #${audioChunkCount}`)
|
||||
lastChunkLogAt = audioChunkCount
|
||||
}
|
||||
|
||||
const pcm16 = float32ToInt16(buffer)
|
||||
websocket.value?.send(pcm16.buffer)
|
||||
}
|
||||
|
||||
const source = context.createMediaStreamSource(stream)
|
||||
source.connect(node)
|
||||
|
||||
const silentGain = context.createGain()
|
||||
silentGain.gain.value = 0
|
||||
node.connect(silentGain)
|
||||
silentGain.connect(context.destination)
|
||||
|
||||
audioContext.value = context
|
||||
workletNode.value = node
|
||||
mediaStreamSource.value = source
|
||||
}
|
||||
|
||||
async function startRecording() {
|
||||
if (!ensureWebSocketOpen()) {
|
||||
appendLog('WebSocket is not ready. Connect before starting recording.', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
if (isRecording.value)
|
||||
return
|
||||
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: {
|
||||
channelCount: 1,
|
||||
sampleRate: 16000,
|
||||
echoCancellation: true,
|
||||
noiseSuppression: true,
|
||||
autoGainControl: true,
|
||||
},
|
||||
})
|
||||
|
||||
mediaStream.value = stream
|
||||
await initializeAudioGraph(stream)
|
||||
|
||||
if (audioContext.value?.state === 'suspended')
|
||||
await audioContext.value.resume()
|
||||
|
||||
isRecording.value = true
|
||||
appendLog('Recording started')
|
||||
}
|
||||
catch (error) {
|
||||
appendLog(`Failed to start recording: ${error instanceof Error ? error.message : String(error)}`, 'error')
|
||||
await stopRecording()
|
||||
}
|
||||
}
|
||||
|
||||
async function stopRecording() {
|
||||
if (!isRecording.value)
|
||||
return
|
||||
|
||||
try {
|
||||
workletNode.value?.port.postMessage({ type: 'stop' })
|
||||
}
|
||||
catch { /* ignore */ }
|
||||
|
||||
if (mediaStreamSource.value) {
|
||||
mediaStreamSource.value.disconnect()
|
||||
mediaStreamSource.value = undefined
|
||||
}
|
||||
|
||||
if (workletNode.value) {
|
||||
workletNode.value.port.onmessage = null
|
||||
workletNode.value.disconnect()
|
||||
workletNode.value = undefined
|
||||
}
|
||||
|
||||
if (mediaStream.value) {
|
||||
mediaStream.value.getTracks().forEach(track => track.stop())
|
||||
mediaStream.value = undefined
|
||||
}
|
||||
|
||||
if (audioContext.value) {
|
||||
try {
|
||||
await audioContext.value.close()
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to close audio context', error)
|
||||
}
|
||||
audioContext.value = undefined
|
||||
}
|
||||
|
||||
isRecording.value = false
|
||||
appendLog('Recording stopped')
|
||||
}
|
||||
|
||||
function handleServerEvent(event: ServerEvent) {
|
||||
switch (event.header.name) {
|
||||
case 'TranscriptionStarted':
|
||||
{
|
||||
const payload = event.payload as ServerEvents['TranscriptionStarted']
|
||||
transcriptionReady.value = true
|
||||
appendLog(`Transcription started. Session: ${payload.session_id}`)
|
||||
break
|
||||
}
|
||||
case 'TranscriptionResultChanged':
|
||||
{
|
||||
const payload = event.payload as ServerEvents['TranscriptionResultChanged']
|
||||
currentPartial.value = payload.result
|
||||
upsertTranscript(payload.index, payload.result, false)
|
||||
break
|
||||
}
|
||||
case 'SentenceEnd':
|
||||
{
|
||||
const payload = event.payload as ServerEvents['SentenceEnd']
|
||||
currentPartial.value = ''
|
||||
upsertTranscript(payload.index, payload.result, true)
|
||||
appendLog(`Sentence #${payload.index} (${payload.time}ms): ${payload.result}`)
|
||||
break
|
||||
}
|
||||
case 'TranscriptionCompleted':
|
||||
appendLog('Transcription completed')
|
||||
break
|
||||
default:
|
||||
appendLog(`Server event: ${event.header.name}`)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function upsertTranscript(index: number, text: string, final: boolean) {
|
||||
const existingIndex = transcripts.value.findIndex(entry => entry.index === index)
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
const existing = transcripts.value[existingIndex]
|
||||
transcripts.value.splice(existingIndex, 1, {
|
||||
index,
|
||||
text,
|
||||
final: existing.final || final,
|
||||
})
|
||||
}
|
||||
else {
|
||||
transcripts.value.push({ index, text, final })
|
||||
}
|
||||
|
||||
transcripts.value.sort((a, b) => a.index - b.index)
|
||||
}
|
||||
|
||||
async function connectWebSocket() {
|
||||
if (!canConnect.value)
|
||||
return
|
||||
|
||||
resetTranscriptionState()
|
||||
connectionState.value = 'connecting'
|
||||
|
||||
try {
|
||||
session.value = createAliyunNLSSession(
|
||||
credentials.accessKeyId.trim(),
|
||||
credentials.accessKeySecret.trim(),
|
||||
credentials.appKey.trim(),
|
||||
{ region: credentials.region },
|
||||
)
|
||||
sessionId.value = session.value.sessionId
|
||||
|
||||
const url = await session.value.websocketUrl()
|
||||
appendLog(`Connecting to ${url}`)
|
||||
|
||||
const ws = new WebSocket(url)
|
||||
ws.binaryType = 'arraybuffer'
|
||||
|
||||
ws.onopen = () => {
|
||||
connectionState.value = 'connected'
|
||||
appendLog('WebSocket connected')
|
||||
session.value?.start(ws, { enable_intermediate_result: true, enable_punctuation_prediction: true })
|
||||
}
|
||||
|
||||
ws.onerror = (event) => {
|
||||
connectionState.value = 'error'
|
||||
appendLog(`WebSocket error: ${JSON.stringify(event)}`, 'error')
|
||||
}
|
||||
|
||||
ws.onmessage = (messageEvent) => {
|
||||
try {
|
||||
const data = JSON.parse(messageEvent.data)
|
||||
session.value?.onEvent(data, handleServerEvent)
|
||||
}
|
||||
catch {
|
||||
appendLog(`Server message: ${messageEvent.data}`)
|
||||
}
|
||||
}
|
||||
|
||||
ws.onclose = () => {
|
||||
connectionState.value = 'closed'
|
||||
appendLog('WebSocket closed by server')
|
||||
cleanupConnection()
|
||||
}
|
||||
|
||||
websocket.value = ws
|
||||
}
|
||||
catch (error) {
|
||||
connectionState.value = 'error'
|
||||
appendLog(`Failed to connect: ${error instanceof Error ? error.message : String(error)}`, 'error')
|
||||
cleanupConnection()
|
||||
}
|
||||
}
|
||||
|
||||
async function disconnectWebSocket() {
|
||||
await stopRecording()
|
||||
|
||||
if (websocket.value && websocket.value.readyState === WebSocket.OPEN) {
|
||||
try {
|
||||
session.value?.stop(websocket.value)
|
||||
}
|
||||
catch (error) {
|
||||
appendLog(`Failed to send stop event: ${error instanceof Error ? error.message : String(error)}`, 'error')
|
||||
}
|
||||
websocket.value.close()
|
||||
}
|
||||
else {
|
||||
cleanupConnection()
|
||||
connectionState.value = 'closed'
|
||||
}
|
||||
}
|
||||
|
||||
function cleanupConnection() {
|
||||
stopRecording()
|
||||
websocket.value = undefined
|
||||
session.value = undefined
|
||||
resetTranscriptionState()
|
||||
}
|
||||
|
||||
onBeforeUnmount(async () => {
|
||||
await disconnectWebSocket()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold">
|
||||
Aliyun NLS Realtime Transcription
|
||||
</h1>
|
||||
<p class="mt-1 text-sm text-neutral-500 dark:text-neutral-400">
|
||||
Access Key ID and Secret with SpeechTranscriber permissions are required.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section class="space-y-4">
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
<FieldInput
|
||||
v-model="credentials.accessKeyId"
|
||||
label="Access Key ID"
|
||||
description="RAM AccessKey ID with SpeechTranscriber permissions."
|
||||
placeholder="LTAI..."
|
||||
/>
|
||||
|
||||
<FieldInput
|
||||
v-model="credentials.accessKeySecret"
|
||||
label="Access Key Secret"
|
||||
description="Keep this secret safe; it never leaves this page."
|
||||
placeholder="****************"
|
||||
type="password"
|
||||
/>
|
||||
|
||||
<FieldInput
|
||||
v-model="credentials.appKey"
|
||||
label="App Key"
|
||||
description="NLS project AppKey to bind the transcription session."
|
||||
placeholder="请输入 AppKey"
|
||||
/>
|
||||
|
||||
<FieldSelect
|
||||
v-model="credentials.region"
|
||||
label="Region"
|
||||
description="Match the region used when issuing the token."
|
||||
:options="regionOptions"
|
||||
placeholder="cn-shanghai"
|
||||
layout="vertical"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<div class="text-sm">
|
||||
<span class="text-neutral-500 dark:text-neutral-400">Status:</span>
|
||||
<span class="ml-2 font-medium" :class="statusColor">
|
||||
{{ statusLabel }}
|
||||
</span>
|
||||
<span v-if="isRecording" class="ml-2 rounded bg-red-500/10 px-2 py-0.5 text-xs text-red-500">
|
||||
Recording
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<Button
|
||||
:disabled="!canConnect"
|
||||
variant="primary"
|
||||
@click="connectWebSocket"
|
||||
>
|
||||
Connect
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
:disabled="!canStartRecording"
|
||||
variant="primary"
|
||||
@click="startRecording"
|
||||
>
|
||||
Listen
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
:disabled="!canStopRecording"
|
||||
variant="danger"
|
||||
@click="stopRecording"
|
||||
>
|
||||
Stop
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
:disabled="!canDisconnect"
|
||||
variant="secondary"
|
||||
@click="disconnectWebSocket"
|
||||
>
|
||||
Disconnect
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="space-y-3">
|
||||
<h2 class="text-lg font-semibold">
|
||||
Transcripts
|
||||
</h2>
|
||||
<div class="border border-neutral-200/80 rounded bg-neutral-50/60 p-4 text-sm dark:border-neutral-700 dark:bg-neutral-900/50">
|
||||
<div v-if="currentPartial" class="mb-3 text-neutral-500 dark:text-neutral-400">
|
||||
<div class="text-xs text-neutral-400 tracking-wide uppercase dark:text-neutral-500">
|
||||
Partial
|
||||
</div>
|
||||
<div class="mt-1 font-medium">
|
||||
{{ currentPartial }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!transcripts.length && !currentPartial" class="text-neutral-400 dark:text-neutral-600">
|
||||
Waiting for server...
|
||||
</div>
|
||||
<ul class="space-y-2">
|
||||
<li
|
||||
v-for="sentence in transcripts"
|
||||
:key="sentence.index"
|
||||
class="flex items-start gap-2"
|
||||
>
|
||||
<span class="mt-0.5 rounded bg-neutral-200/80 px-2 py-0.5 text-xs text-neutral-700 dark:bg-neutral-800/70 dark:text-neutral-200">
|
||||
#{{ sentence.index }}
|
||||
</span>
|
||||
<div>
|
||||
<div class="font-medium" :class="sentence.final ? '' : 'italic text-neutral-500 dark:text-neutral-400'">
|
||||
{{ sentence.text }}
|
||||
</div>
|
||||
<div v-if="!sentence.final" class="text-xs text-neutral-400">
|
||||
Waiting for final result...
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="space-y-3">
|
||||
<h2 class="text-lg font-semibold">
|
||||
Logs
|
||||
</h2>
|
||||
<div
|
||||
ref="logsContainer"
|
||||
class="h-64 overflow-y-auto border border-neutral-200/80 rounded bg-neutral-50/60 p-3 text-xs leading-5 dark:border-neutral-700 dark:bg-neutral-900/50"
|
||||
>
|
||||
<div
|
||||
v-for="entry in logs"
|
||||
:key="entry.id"
|
||||
:class="entry.level === 'error' ? 'text-red-500' : 'text-neutral-700 dark:text-neutral-200'"
|
||||
>
|
||||
{{ entry.text }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
layout: settings
|
||||
</route>
|
||||
@@ -0,0 +1,2 @@
|
||||
ALIYUN_AK_ID=
|
||||
ALIYUN_AK_SECRET=
|
||||
@@ -26,6 +26,7 @@
|
||||
"./constants": "./src/constants/index.ts",
|
||||
"./libs/*": "./src/libs/*.ts",
|
||||
"./libs": "./src/libs/index.ts",
|
||||
"./stores/providers/aliyun": "./src/stores/providers/aliyun/index.ts",
|
||||
"./stores/*": "./src/stores/*.ts",
|
||||
"./stores": "./src/stores/index.ts",
|
||||
"./workers/vad": "./src/workers/vad/index.ts",
|
||||
@@ -108,6 +109,7 @@
|
||||
"localforage": "^1.10.0",
|
||||
"mediabunny": "^1.24.2",
|
||||
"nanoid": "^5.1.6",
|
||||
"ofetch": "^1.5.0",
|
||||
"pinia": "^3.0.3",
|
||||
"pixi-filters": "4",
|
||||
"pixi-live2d-display": "^0.4.0",
|
||||
@@ -123,6 +125,7 @@
|
||||
"unified": "^11.0.5",
|
||||
"unist-builder": "^4.0.0",
|
||||
"unspeech": "^0.1.7",
|
||||
"uuid": "^13.0.0",
|
||||
"vaul-vue": "^0.4.1",
|
||||
"vue-i18n": "^11.1.12",
|
||||
"vue-router": "^4.6.3",
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
/**
|
||||
* https://help.aliyun.com/zh/isi/developer-reference/websocket?spm=a2c4g.11186623.help-menu-30413.d_3_2_1_8.694e8d64PgomCT
|
||||
*/
|
||||
|
||||
import { utc } from '@date-fns/utc'
|
||||
import { merge } from '@moeru/std'
|
||||
import { isBefore } from 'date-fns'
|
||||
import { customAlphabet } from 'nanoid'
|
||||
|
||||
import { createToken } from './token'
|
||||
import { nlsWebSocketEndpointFromRegion } from './utils'
|
||||
|
||||
// NOTICE: Aliyun NLS requires exact 32 character length in hex for IDs,
|
||||
// so we use a custom nanoid alphabet and length here.
|
||||
const nanoid = customAlphabet('0123456789abcdef', 32)
|
||||
|
||||
interface BaseEventHeader<N> {
|
||||
appkey: string
|
||||
message_id: string
|
||||
task_id: string
|
||||
namespace: 'SpeechTranscriber'
|
||||
name: N
|
||||
|
||||
status?: 20000000
|
||||
status_message?: 'GATEWAY' | 'SUCCESS' | 'Success'
|
||||
}
|
||||
|
||||
interface BaseEvent<N, P> {
|
||||
header: BaseEventHeader<N>
|
||||
payload: P
|
||||
}
|
||||
|
||||
export interface EventStartTranscription extends BaseEvent<'StartTranscription', {
|
||||
/** Audio format, supports PCM, WAV, OPUS, SPEEX, AMR, MP3, AAC. */
|
||||
format?: 'pcm' | 'wav' | 'opus' | 'speex' | 'amr' | 'mp3' | 'aac'
|
||||
/** Audio sample rate in Hz. Default is 16000. Make sure the project is configured for the selected rate. */
|
||||
sample_rate?: 8000 | 16000
|
||||
/** Return interim recognition results. Disabled by default. */
|
||||
enable_intermediate_result?: boolean
|
||||
/** Add punctuation during post-processing. Disabled by default. */
|
||||
enable_punctuation_prediction?: boolean
|
||||
/** Inverse text normalization (ITN). Converts Chinese numerals to Arabic digits when true. Disabled by default. */
|
||||
enable_inverse_text_normalization?: boolean
|
||||
/** Custom model ID. */
|
||||
customization_id?: string
|
||||
/** Custom vocabulary ID. */
|
||||
vocabulary_id?: string
|
||||
/** Silence threshold in milliseconds for sentence segmentation. Range 200–2000 ms, default 800 ms. */
|
||||
max_sentence_silence?: number
|
||||
/** Return word-level timestamps. Disabled by default. */
|
||||
enable_words?: boolean
|
||||
/** Filter filler words (disfluency). Requires version 4.0. Disabled by default. */
|
||||
disfluency?: boolean
|
||||
/**
|
||||
* Noise threshold. Range [-1, 1].
|
||||
* Closer to -1 treats noise as speech more aggressively.
|
||||
* Closer to +1 treats speech as noise more aggressively.
|
||||
* Important: this is an advanced parameter and should be tuned carefully with dedicated testing.
|
||||
*/
|
||||
speech_noise_threshold?: number
|
||||
/** Enable semantic sentence segmentation. Disabled by default. */
|
||||
enable_semantic_sentence_detection?: boolean
|
||||
}> {}
|
||||
|
||||
export interface EventStopTranscription extends BaseEvent<'StopTranscription', undefined> {}
|
||||
|
||||
export interface EventTranscriptionStarted extends BaseEvent<'TranscriptionStarted', {
|
||||
/** Session ID. Returned as-is if provided by the client, otherwise generated by the server. */
|
||||
session_id: string
|
||||
}> {}
|
||||
|
||||
export interface EventSentenceBegin extends BaseEvent<'SentenceBegin', {
|
||||
/** Incremental sentence index starting from 1. */
|
||||
index: number
|
||||
/** Sentence start time relative to the audio stream start, in milliseconds. */
|
||||
time: number
|
||||
}> {}
|
||||
|
||||
export interface EventTranscriptionResultChanged extends BaseEvent<'TranscriptionResultChanged', {
|
||||
/** Incremental sentence index starting from 1. */
|
||||
index: number
|
||||
/** Audio duration processed so far, in milliseconds. */
|
||||
time: number
|
||||
/** Current recognition text. */
|
||||
result: string
|
||||
/** Word-level alignment information. */
|
||||
words?: {
|
||||
text: string
|
||||
startTime: number
|
||||
endTime: number
|
||||
}[]
|
||||
/** Status code returned by the service. */
|
||||
status: number
|
||||
}> {}
|
||||
|
||||
export interface EventSentenceEnd extends BaseEvent<'SentenceEnd', {
|
||||
/** Incremental sentence index starting from 1. */
|
||||
index: number
|
||||
/** Audio duration processed so far, in milliseconds. */
|
||||
time: number
|
||||
/** Start time of the matching `SentenceBegin` event, in milliseconds. */
|
||||
begin_time: number
|
||||
/** Final recognition text for the sentence. */
|
||||
result: string
|
||||
/** Confidence score in the range [0.0, 1.0]. */
|
||||
confidence: number
|
||||
/** Word-level alignment information. */
|
||||
words?: {
|
||||
/** Word text */
|
||||
text: string
|
||||
/** Word start time */
|
||||
startTime: number
|
||||
/** Word end time */
|
||||
endTime: number
|
||||
}[]
|
||||
/** Status code returned by the service. */
|
||||
status: number
|
||||
/** Buffered result when semantic segmentation is enabled; contains the upcoming unfinished sentence. */
|
||||
stash_result: {
|
||||
/** Incremental sentence index starting from 1. */
|
||||
sentenceId: number
|
||||
/** Sentence start time. */
|
||||
beginTime: number
|
||||
/** Recognized text. */
|
||||
text: string
|
||||
/** Current processing time. */
|
||||
currentTime: number
|
||||
}
|
||||
}> {}
|
||||
|
||||
export interface EventTranscriptionCompleted extends BaseEvent<'TranscriptionCompleted', undefined> {}
|
||||
|
||||
export interface ClientEvents {
|
||||
StartTranscription: EventStartTranscription['payload']
|
||||
StopTranscription: EventStopTranscription['payload']
|
||||
}
|
||||
|
||||
export type ClientEvent = {
|
||||
[K in keyof ClientEvents]: BaseEvent<K, ClientEvents[K]>;
|
||||
}[keyof ClientEvents]
|
||||
|
||||
export interface ServerEvents {
|
||||
TranscriptionStarted: EventTranscriptionStarted['payload']
|
||||
SentenceBegin: EventSentenceBegin['payload']
|
||||
TranscriptionResultChanged: EventTranscriptionResultChanged['payload']
|
||||
SentenceEnd: EventSentenceEnd['payload']
|
||||
TranscriptionCompleted: EventTranscriptionCompleted['payload']
|
||||
}
|
||||
|
||||
export type ServerEvent = {
|
||||
[K in keyof ServerEvents]: BaseEvent<K, ServerEvents[K]>;
|
||||
}[keyof ServerEvents]
|
||||
|
||||
export function createAliyunNLSSession(
|
||||
accessKeyId: string,
|
||||
accessKeySecret: string,
|
||||
appKey: string,
|
||||
options?: {
|
||||
region?:
|
||||
| 'cn-shanghai'
|
||||
| 'cn-shanghai-internal'
|
||||
| 'cn-beijing'
|
||||
| 'cn-beijing-internal'
|
||||
| 'cn-shenzhen'
|
||||
| 'cn-shenzhen-internal'
|
||||
},
|
||||
) {
|
||||
const provider = createAliyunNLSProvider(accessKeyId, accessKeySecret, appKey, options)
|
||||
const providerSessionId = nanoid()
|
||||
|
||||
function start(websocketConn: WebSocket, options?: {
|
||||
sessionId?: string
|
||||
} & EventStartTranscription['payload']) {
|
||||
const mergedOptions = merge({ sessionId: providerSessionId }, options)
|
||||
|
||||
websocketConn.send(JSON.stringify({
|
||||
header: {
|
||||
appkey: provider.appKey,
|
||||
message_id: nanoid(),
|
||||
task_id: mergedOptions.sessionId,
|
||||
namespace: 'SpeechTranscriber',
|
||||
name: 'StartTranscription',
|
||||
},
|
||||
payload: {
|
||||
format: 'wav',
|
||||
},
|
||||
} satisfies EventStartTranscription))
|
||||
}
|
||||
|
||||
function stop(websocketConn: WebSocket, options?: {
|
||||
sessionId?: string
|
||||
}) {
|
||||
const mergedOptions = merge({ sessionId: providerSessionId }, options)
|
||||
|
||||
websocketConn.send(JSON.stringify({
|
||||
header: {
|
||||
appkey: provider.appKey,
|
||||
message_id: nanoid(),
|
||||
task_id: mergedOptions.sessionId,
|
||||
namespace: 'SpeechTranscriber',
|
||||
name: 'StopTranscription',
|
||||
},
|
||||
payload: undefined,
|
||||
} satisfies EventStopTranscription))
|
||||
}
|
||||
|
||||
function onEvent(data: unknown, cb: (event: ServerEvent) => void) {
|
||||
const event = data as ServerEvent
|
||||
cb(event)
|
||||
}
|
||||
|
||||
return {
|
||||
...provider,
|
||||
|
||||
sessionId: providerSessionId,
|
||||
start,
|
||||
stop,
|
||||
onEvent,
|
||||
}
|
||||
}
|
||||
|
||||
export function createAliyunNLSProvider(
|
||||
accessKeyId: string,
|
||||
accessKeySecret: string,
|
||||
appKey: string,
|
||||
options?: {
|
||||
region?:
|
||||
| 'cn-shanghai'
|
||||
| 'cn-shanghai-internal'
|
||||
| 'cn-beijing'
|
||||
| 'cn-beijing-internal'
|
||||
| 'cn-shenzhen'
|
||||
| 'cn-shenzhen-internal'
|
||||
},
|
||||
) {
|
||||
let token: string = ''
|
||||
let tokenExpiresAt: number = utc(new Date()).getTime()
|
||||
|
||||
async function websocketUrl() {
|
||||
if (!token || isBefore(new Date(tokenExpiresAt), utc(new Date()))) {
|
||||
const created = await createToken(accessKeyId, accessKeySecret, { regionId: options?.region ?? 'cn-shanghai' })
|
||||
token = created.token
|
||||
tokenExpiresAt = created.expiresAt
|
||||
}
|
||||
|
||||
const url = nlsWebSocketEndpointFromRegion(options?.region ?? 'cn-shanghai')
|
||||
url.searchParams.set('token', token)
|
||||
return url.toString()
|
||||
}
|
||||
|
||||
return {
|
||||
websocketUrl,
|
||||
appKey,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import { env } from 'node:process'
|
||||
|
||||
import { utc } from '@date-fns/utc'
|
||||
import { isAfter, parse } from 'date-fns'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
buildCreateTokenRequest,
|
||||
canonicalizeQuery,
|
||||
createStringToSign,
|
||||
createToken,
|
||||
signStringToBase64,
|
||||
} from './token'
|
||||
|
||||
describe('buildCreateTokenRequest', () => {
|
||||
const testParameters = {
|
||||
AccessKeyId: 'my_access_key_id',
|
||||
Action: 'CreateToken',
|
||||
Format: 'JSON',
|
||||
RegionId: 'cn-shanghai',
|
||||
SignatureMethod: 'HMAC-SHA1',
|
||||
SignatureNonce: 'b924c8c3-6d03-4c5d-ad36-d984d3116788',
|
||||
SignatureVersion: '1.0',
|
||||
Timestamp: '2019-04-18T08:32:31Z',
|
||||
Version: '2019-02-28',
|
||||
}
|
||||
|
||||
const expectedCanonicalQuery = 'AccessKeyId=my_access_key_id&Action=CreateToken&Format=JSON&RegionId=cn-shanghai&SignatureMethod=HMAC-SHA1&SignatureNonce=b924c8c3-6d03-4c5d-ad36-d984d3116788&SignatureVersion=1.0&Timestamp=2019-04-18T08%3A32%3A31Z&Version=2019-02-28'
|
||||
const expectedBuiltQueryString = 'GET&%2F&AccessKeyId%3Dmy_access_key_id%26Action%3DCreateToken%26Format%3DJSON%26RegionId%3Dcn-shanghai%26SignatureMethod%3DHMAC-SHA1%26SignatureNonce%3Db924c8c3-6d03-4c5d-ad36-d984d3116788%26SignatureVersion%3D1.0%26Timestamp%3D2019-04-18T08%253A32%253A31Z%26Version%3D2019-02-28'
|
||||
const expectedSignature = 'hHq4yNsPitlfDJ2L0nQPdugdEzM='
|
||||
const expectedSignatureEncoded = encodeURIComponent(expectedSignature)
|
||||
const expectedSignedQuery = `Signature=${expectedSignatureEncoded}&${expectedCanonicalQuery}`
|
||||
const expectedUrl = `http://nls-meta.cn-shanghai.aliyuncs.com/?${expectedSignedQuery}`
|
||||
|
||||
it('builds canonical query string matching the Java implementation', () => {
|
||||
const canonical = canonicalizeQuery(testParameters)
|
||||
expect(canonical).toBe(expectedCanonicalQuery)
|
||||
})
|
||||
|
||||
it('creates the expected string to sign', () => {
|
||||
const canonical = canonicalizeQuery(testParameters)
|
||||
const stringToSign = createStringToSign('GET', '/', canonical)
|
||||
expect(stringToSign).toBe(expectedBuiltQueryString)
|
||||
})
|
||||
|
||||
it('produces the expected signature', async () => {
|
||||
const signature = await signStringToBase64(expectedBuiltQueryString, 'my_access_key_secret')
|
||||
expect(signature).toBe(expectedSignature)
|
||||
expect(encodeURIComponent(signature)).toBe(expectedSignatureEncoded)
|
||||
})
|
||||
|
||||
it('constructs the full token request data', async () => {
|
||||
const request = await buildCreateTokenRequest(
|
||||
'my_access_key_id',
|
||||
'my_access_key_secret',
|
||||
{
|
||||
timestamp: parse(testParameters.Timestamp, 'yyyy-MM-dd\'T\'HH:mm:ssX', new Date()),
|
||||
signatureNonce: testParameters.SignatureNonce,
|
||||
},
|
||||
)
|
||||
|
||||
expect(request.canonicalQuery).toBe(expectedCanonicalQuery)
|
||||
expect(request.stringToSign).toBe(expectedBuiltQueryString)
|
||||
expect(request.signature).toBe(expectedSignature)
|
||||
expect(request.encodedSignature).toBe(expectedSignatureEncoded)
|
||||
expect(request.signedQuery).toBe(expectedSignedQuery)
|
||||
expect(request.url).toBe(expectedUrl)
|
||||
expect(request.params.Signature).toBe(expectedSignature)
|
||||
})
|
||||
})
|
||||
|
||||
describe('createToken', (test) => {
|
||||
it('successfully fetches a token', async () => {
|
||||
if (!env.ALIYUN_AK_ID || !env.ALIYUN_AK_SECRET) {
|
||||
test.skip('ALIYUN_AK_ID and ALIYUN_AK_SECRET must be set in environment to run this test', () => {})
|
||||
return
|
||||
}
|
||||
|
||||
const { token, expiresAt } = await createToken(env.ALIYUN_AK_ID!, env.ALIYUN_AK_SECRET!)
|
||||
expect(token).toBeDefined()
|
||||
expect(token).toBeTypeOf('string')
|
||||
expect(expiresAt).toBeTypeOf('number')
|
||||
expect(isAfter(new Date(expiresAt), utc(new Date()))).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,134 @@
|
||||
import { utc } from '@date-fns/utc'
|
||||
import { encodeBase64, merge } from '@moeru/std'
|
||||
import { format } from 'date-fns'
|
||||
import { ofetch } from 'ofetch'
|
||||
import { subtle } from 'uncrypto'
|
||||
import { v4 as uuidV4 } from 'uuid'
|
||||
|
||||
import { nlsMetaEndpointFromRegion } from './utils'
|
||||
|
||||
const SIGNING_METHOD = 'HMAC-SHA1'
|
||||
const SIGNATURE_VERSION = '1.0'
|
||||
const API_VERSION = '2019-02-28'
|
||||
|
||||
type AliyunQueryParams = Record<string, string>
|
||||
|
||||
export interface CreateTokenOptions {
|
||||
regionId?: string
|
||||
endpoint?: string
|
||||
timestamp?: Date
|
||||
signatureNonce?: string
|
||||
extraQuery?: AliyunQueryParams
|
||||
}
|
||||
|
||||
export interface CreateTokenRequest {
|
||||
endpoint: string
|
||||
canonicalQuery: string
|
||||
stringToSign: string
|
||||
signature: string
|
||||
encodedSignature: string
|
||||
signedQuery: string
|
||||
params: AliyunQueryParams
|
||||
url: string
|
||||
}
|
||||
|
||||
export function canonicalizeQuery(params: AliyunQueryParams): string {
|
||||
return Object.keys(params)
|
||||
.sort()
|
||||
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
|
||||
.join('&')
|
||||
}
|
||||
|
||||
export function createStringToSign(
|
||||
method: string,
|
||||
path: string,
|
||||
canonicalQuery: string,
|
||||
): string {
|
||||
const encodedPath = encodeURIComponent(path)
|
||||
const encodedQuery = encodeURIComponent(canonicalQuery)
|
||||
return `${method}&${encodedPath}&${encodedQuery}`
|
||||
}
|
||||
|
||||
export async function signStringToBase64(stringToSign: string, accessKeySecret: string): Promise<string> {
|
||||
const encoder = new TextEncoder()
|
||||
const keyData = encoder.encode(`${accessKeySecret}&`)
|
||||
const algorithm: HmacImportParams = {
|
||||
name: 'HMAC',
|
||||
hash: { name: 'SHA-1' },
|
||||
}
|
||||
const cryptoKey = await subtle.importKey(
|
||||
'raw',
|
||||
keyData as Uint8Array<ArrayBuffer>,
|
||||
algorithm,
|
||||
false,
|
||||
['sign'],
|
||||
)
|
||||
|
||||
const signDataEncoder = new TextEncoder()
|
||||
const data = signDataEncoder.encode(stringToSign)
|
||||
const signatureBuffer = await subtle.sign('HMAC', cryptoKey, data as Uint8Array<ArrayBuffer>)
|
||||
return encodeBase64(signatureBuffer)
|
||||
}
|
||||
|
||||
export async function buildCreateTokenRequest(accessKeyId: string, accessKeySecret: string, options?: CreateTokenOptions): Promise<CreateTokenRequest> {
|
||||
const mergedOptions = merge({ timestamp: new Date() }, options)
|
||||
|
||||
// ISO 8601 format: YYYY-MM-DDThh:mm:ssZ
|
||||
const timestamp = format(utc(mergedOptions.timestamp), 'yyyy-MM-dd\'T\'HH:mm:ssXX')
|
||||
const signatureNonce = options?.signatureNonce ?? uuidV4()
|
||||
|
||||
const params: AliyunQueryParams = {
|
||||
AccessKeyId: accessKeyId,
|
||||
Action: 'CreateToken',
|
||||
Format: 'JSON',
|
||||
RegionId: options?.regionId ?? 'cn-shanghai',
|
||||
SignatureMethod: SIGNING_METHOD,
|
||||
SignatureNonce: signatureNonce,
|
||||
SignatureVersion: SIGNATURE_VERSION,
|
||||
Timestamp: timestamp,
|
||||
Version: API_VERSION,
|
||||
...options?.extraQuery,
|
||||
}
|
||||
|
||||
const canonicalQuery = canonicalizeQuery(params)
|
||||
const stringToSign = createStringToSign('POST', '/', canonicalQuery)
|
||||
const signatureBase64 = await signStringToBase64(stringToSign, accessKeySecret)
|
||||
const encodedSignature = encodeURIComponent(signatureBase64)
|
||||
const signedQuery = `Signature=${encodedSignature}&${canonicalQuery}`
|
||||
const endpoint = (options?.endpoint ?? nlsMetaEndpointFromRegion(options?.regionId ?? 'cn-shanghai').toString()).replace(/\/$/, '')
|
||||
const url = `${endpoint}/?${signedQuery}`
|
||||
|
||||
return {
|
||||
endpoint,
|
||||
canonicalQuery,
|
||||
stringToSign,
|
||||
signature: signatureBase64,
|
||||
encodedSignature,
|
||||
signedQuery,
|
||||
params: {
|
||||
Signature: signatureBase64,
|
||||
...params,
|
||||
},
|
||||
url,
|
||||
}
|
||||
}
|
||||
|
||||
export async function createToken(accessKeyId: string, accessKeySecret: string, options?: CreateTokenOptions): Promise<{ token: string, expiresAt: number }> {
|
||||
const request = await buildCreateTokenRequest(accessKeyId, accessKeySecret, options)
|
||||
const response = await ofetch<{
|
||||
NlsRequestId: string
|
||||
RequestId: string
|
||||
ErrMsg: string
|
||||
Token: { ExpireTime: number, Id: string, UserId: string }
|
||||
} | {
|
||||
RequestId: string
|
||||
Message: string
|
||||
Code: string
|
||||
}>(request.url, { method: 'POST' })
|
||||
|
||||
if ('Token' in response && typeof response.Token === 'object' && 'Id' in response.Token) {
|
||||
return { token: response.Token.Id, expiresAt: response.Token.ExpireTime * 1000 }
|
||||
}
|
||||
|
||||
throw new Error(`Failed to create token: ${JSON.stringify(response) || 'Unknown error'}`)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
export function nlsMetaEndpointFromRegion(region: string): URL {
|
||||
return new URL(`http://nls-meta.${region}.aliyuncs.com`)
|
||||
}
|
||||
|
||||
export function nlsWebSocketEndpointFromRegion(region: string = 'cn-shanghai'): URL {
|
||||
const websocketURL = new URL('/ws/v1', 'https://example.com')
|
||||
|
||||
switch (region) {
|
||||
case 'cn-shanghai':
|
||||
case 'cn-beijing':
|
||||
case 'cn-shenzhen':
|
||||
websocketURL.protocol = 'wss:'
|
||||
websocketURL.hostname = `nls-gateway-${region}.aliyuncs.com`
|
||||
break
|
||||
case 'cn-shanghai-internal':
|
||||
case 'cn-beijing-internal':
|
||||
case 'cn-shenzhen-internal':
|
||||
websocketURL.protocol = 'wss:'
|
||||
websocketURL.hostname = `nls-gateway-${region}-internal.aliyuncs.com:80`
|
||||
}
|
||||
|
||||
return websocketURL
|
||||
}
|
||||
@@ -1,7 +1,14 @@
|
||||
import { join } from 'node:path'
|
||||
import { cwd } from 'node:process'
|
||||
|
||||
import { loadEnv } from 'vite'
|
||||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
include: ['src/**/*.test.ts'],
|
||||
},
|
||||
export default defineConfig(({ mode }) => {
|
||||
return ({
|
||||
test: {
|
||||
include: ['src/**/*.test.ts'],
|
||||
env: loadEnv(mode, join(cwd(), 'packages', 'stage-ui'), ''),
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
Generated
+25
-13
@@ -461,6 +461,9 @@ importers:
|
||||
unspeech:
|
||||
specifier: ^0.1.7
|
||||
version: 0.1.7
|
||||
uuid:
|
||||
specifier: ^13.0.0
|
||||
version: 13.0.0
|
||||
valibot:
|
||||
specifier: 1.0.0-beta.9
|
||||
version: 1.0.0-beta.9(typescript@5.9.3)
|
||||
@@ -843,6 +846,9 @@ importers:
|
||||
unspeech:
|
||||
specifier: ^0.1.7
|
||||
version: 0.1.7
|
||||
uuid:
|
||||
specifier: ^13.0.0
|
||||
version: 13.0.0
|
||||
valibot:
|
||||
specifier: 1.0.0-beta.9
|
||||
version: 1.0.0-beta.9(typescript@5.9.3)
|
||||
@@ -1044,7 +1050,7 @@ importers:
|
||||
version: 11.1.12(vue@3.5.22(typescript@5.9.3))
|
||||
vue-sonner:
|
||||
specifier: ^2.0.9
|
||||
version: 2.0.9(@nuxt/kit@4.0.3(magicast@0.3.5))(@nuxt/schema@4.0.3)(nuxt@4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.0)(@types/node@24.9.2)(@vue/compiler-sfc@3.5.22)(bufferutil@4.0.9)(db0@0.3.2(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(eslint@9.39.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.4.2)(lightningcss@1.30.2)(magicast@0.3.5)(optionator@0.9.4)(rolldown@1.0.0-beta.45)(rollup@4.52.5)(terser@5.43.1)(tsx@4.20.6)(typescript@5.9.3)(utf-8-validate@5.0.10)(vite@7.1.12(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1))(vue-tsc@3.1.2(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.1))
|
||||
version: 2.0.9(@nuxt/kit@4.0.3(magicast@0.3.5))(@nuxt/schema@4.0.3)(nuxt@4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.0)(@types/node@24.9.2)(@vue/compiler-sfc@3.5.22)(bufferutil@4.0.9)(db0@0.3.2(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(encoding@0.1.13)(eslint@9.39.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.4.2)(lightningcss@1.30.2)(magicast@0.3.5)(optionator@0.9.4)(rolldown@1.0.0-beta.45)(rollup@4.52.5)(terser@5.43.1)(tsx@4.20.6)(typescript@5.9.3)(utf-8-validate@5.0.10)(vite@7.1.12(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1))(vue-tsc@3.1.2(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.1))
|
||||
devDependencies:
|
||||
'@iconify-json/lucide':
|
||||
specifier: ^1.2.71
|
||||
@@ -1123,7 +1129,7 @@ importers:
|
||||
version: 0.1.3
|
||||
unplugin-yaml:
|
||||
specifier: ^3.0.7
|
||||
version: 3.0.7(@nuxt/kit@4.0.3(magicast@0.3.5))(@nuxt/schema@4.0.3)(astro@5.10.1(@netlify/blobs@9.1.2)(@types/node@24.9.2)(db0@0.3.2(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(ioredis@5.7.0)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(rollup@4.52.5)(terser@5.43.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1))(esbuild@0.25.9)(rolldown@1.0.0-beta.45)(rollup@4.52.5)(vite@7.1.12(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1))
|
||||
version: 3.0.7(@nuxt/kit@4.0.3(magicast@0.3.5))(@nuxt/schema@4.0.3)(astro@5.10.1(@netlify/blobs@9.1.2)(@types/node@24.9.2)(db0@0.3.2(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(encoding@0.1.13)(ioredis@5.7.0)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(rollup@4.52.5)(terser@5.43.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1))(esbuild@0.25.9)(rolldown@1.0.0-beta.45)(rollup@4.52.5)(vite@7.1.12(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1))
|
||||
vitepress:
|
||||
specifier: ^2.0.0-alpha.12
|
||||
version: 2.0.0-alpha.12(@types/node@24.9.2)(change-case@5.4.4)(fuse.js@7.1.0)(jiti@2.5.1)(jwt-decode@4.0.0)(less@4.4.2)(lightningcss@1.30.2)(nprogress@0.2.0)(postcss@8.5.6)(terser@5.43.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)
|
||||
@@ -1175,7 +1181,7 @@ importers:
|
||||
devDependencies:
|
||||
unplugin-yaml:
|
||||
specifier: ^3.0.7
|
||||
version: 3.0.7(@nuxt/kit@4.0.3(magicast@0.3.5))(@nuxt/schema@4.0.3)(astro@5.10.1(@netlify/blobs@9.1.2)(@types/node@24.9.2)(db0@0.3.2(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(ioredis@5.7.0)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(rollup@4.52.5)(terser@5.43.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1))(esbuild@0.25.9)(rolldown@1.0.0-beta.45)(rollup@4.52.5)(vite@7.1.12(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1))
|
||||
version: 3.0.7(@nuxt/kit@4.0.3(magicast@0.3.5))(@nuxt/schema@4.0.3)(astro@5.10.1(@netlify/blobs@9.1.2)(@types/node@24.9.2)(db0@0.3.2(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(encoding@0.1.13)(ioredis@5.7.0)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(rollup@4.52.5)(terser@5.43.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1))(esbuild@0.25.9)(rolldown@1.0.0-beta.45)(rollup@4.52.5)(vite@7.1.12(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1))
|
||||
|
||||
packages/injecta:
|
||||
devDependencies:
|
||||
@@ -1526,6 +1532,9 @@ importers:
|
||||
nanoid:
|
||||
specifier: ^5.1.6
|
||||
version: 5.1.6
|
||||
ofetch:
|
||||
specifier: ^1.5.0
|
||||
version: 1.5.0
|
||||
pinia:
|
||||
specifier: ^3.0.3
|
||||
version: 3.0.3(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3))
|
||||
@@ -1571,6 +1580,9 @@ importers:
|
||||
unspeech:
|
||||
specifier: ^0.1.7
|
||||
version: 0.1.7
|
||||
uuid:
|
||||
specifier: ^13.0.0
|
||||
version: 13.0.0
|
||||
vaul-vue:
|
||||
specifier: ^0.4.1
|
||||
version: 0.4.1(reka-ui@2.6.0(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3))
|
||||
@@ -1582,7 +1594,7 @@ importers:
|
||||
version: 4.6.3(vue@3.5.22(typescript@5.9.3))
|
||||
vue-sonner:
|
||||
specifier: ^2.0.9
|
||||
version: 2.0.9(@nuxt/kit@4.0.3(magicast@0.3.5))(@nuxt/schema@4.0.3)(nuxt@4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.0)(@types/node@24.9.2)(@vue/compiler-sfc@3.5.22)(bufferutil@4.0.9)(db0@0.3.2(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(eslint@9.39.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.4.2)(lightningcss@1.30.2)(magicast@0.3.5)(optionator@0.9.4)(rolldown@1.0.0-beta.45)(rollup@4.52.5)(terser@5.43.1)(tsx@4.20.6)(typescript@5.9.3)(utf-8-validate@5.0.10)(vite@6.4.1(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1))(vue-tsc@3.1.2(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.1))
|
||||
version: 2.0.9(@nuxt/kit@4.0.3(magicast@0.3.5))(@nuxt/schema@4.0.3)(nuxt@4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.0)(@types/node@24.9.2)(@vue/compiler-sfc@3.5.22)(bufferutil@4.0.9)(db0@0.3.2(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(encoding@0.1.13)(eslint@9.39.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.4.2)(lightningcss@1.30.2)(magicast@0.3.5)(optionator@0.9.4)(rolldown@1.0.0-beta.45)(rollup@4.52.5)(terser@5.43.1)(tsx@4.20.6)(typescript@5.9.3)(utf-8-validate@5.0.10)(vite@6.4.1(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1))(vue-tsc@3.1.2(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.1))
|
||||
vue-tsc:
|
||||
specifier: ^3.1.2
|
||||
version: 3.1.2(typescript@5.9.3)
|
||||
@@ -1691,7 +1703,7 @@ importers:
|
||||
version: 1.2.4(@nuxt/kit@4.0.3(magicast@0.3.5))(@nuxt/schema@4.0.3)(esbuild@0.25.9)(rollup@4.52.5)(vite@6.4.1(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1))
|
||||
unplugin-yaml:
|
||||
specifier: ^3.0.7
|
||||
version: 3.0.7(@nuxt/kit@4.0.3(magicast@0.3.5))(@nuxt/schema@4.0.3)(astro@5.10.1(@netlify/blobs@9.1.2)(@types/node@24.9.2)(db0@0.3.2(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(ioredis@5.7.0)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(rollup@4.52.5)(terser@5.43.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1))(esbuild@0.25.9)(rolldown@1.0.0-beta.45)(rollup@4.52.5)(vite@6.4.1(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1))
|
||||
version: 3.0.7(@nuxt/kit@4.0.3(magicast@0.3.5))(@nuxt/schema@4.0.3)(astro@5.10.1(@netlify/blobs@9.1.2)(@types/node@24.9.2)(db0@0.3.2(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(encoding@0.1.13)(ioredis@5.7.0)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(rollup@4.52.5)(terser@5.43.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1))(esbuild@0.25.9)(rolldown@1.0.0-beta.45)(rollup@4.52.5)(vite@6.4.1(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1))
|
||||
vite:
|
||||
specifier: ^6.4.1
|
||||
version: 6.4.1(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1)
|
||||
@@ -29213,7 +29225,7 @@ snapshots:
|
||||
- yaml
|
||||
optional: true
|
||||
|
||||
nuxt@4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.0)(@types/node@24.9.2)(@vue/compiler-sfc@3.5.22)(bufferutil@4.0.9)(db0@0.3.2(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(eslint@9.39.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.4.2)(lightningcss@1.30.2)(magicast@0.3.5)(optionator@0.9.4)(rolldown@1.0.0-beta.45)(rollup@4.52.5)(terser@5.43.1)(tsx@4.20.6)(typescript@5.9.3)(utf-8-validate@5.0.10)(vite@6.4.1(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1))(vue-tsc@3.1.2(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.1):
|
||||
nuxt@4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.0)(@types/node@24.9.2)(@vue/compiler-sfc@3.5.22)(bufferutil@4.0.9)(db0@0.3.2(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(encoding@0.1.13)(eslint@9.39.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.4.2)(lightningcss@1.30.2)(magicast@0.3.5)(optionator@0.9.4)(rolldown@1.0.0-beta.45)(rollup@4.52.5)(terser@5.43.1)(tsx@4.20.6)(typescript@5.9.3)(utf-8-validate@5.0.10)(vite@6.4.1(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1))(vue-tsc@3.1.2(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.1):
|
||||
dependencies:
|
||||
'@nuxt/cli': 3.28.0(magicast@0.3.5)
|
||||
'@nuxt/devalue': 2.0.2
|
||||
@@ -29338,7 +29350,7 @@ snapshots:
|
||||
- yaml
|
||||
optional: true
|
||||
|
||||
nuxt@4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.0)(@types/node@24.9.2)(@vue/compiler-sfc@3.5.22)(bufferutil@4.0.9)(db0@0.3.2(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(eslint@9.39.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.4.2)(lightningcss@1.30.2)(magicast@0.3.5)(optionator@0.9.4)(rolldown@1.0.0-beta.45)(rollup@4.52.5)(terser@5.43.1)(tsx@4.20.6)(typescript@5.9.3)(utf-8-validate@5.0.10)(vite@7.1.12(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1))(vue-tsc@3.1.2(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.1):
|
||||
nuxt@4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.0)(@types/node@24.9.2)(@vue/compiler-sfc@3.5.22)(bufferutil@4.0.9)(db0@0.3.2(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(encoding@0.1.13)(eslint@9.39.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.4.2)(lightningcss@1.30.2)(magicast@0.3.5)(optionator@0.9.4)(rolldown@1.0.0-beta.45)(rollup@4.52.5)(terser@5.43.1)(tsx@4.20.6)(typescript@5.9.3)(utf-8-validate@5.0.10)(vite@7.1.12(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1))(vue-tsc@3.1.2(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.1):
|
||||
dependencies:
|
||||
'@nuxt/cli': 3.28.0(magicast@0.3.5)
|
||||
'@nuxt/devalue': 2.0.2
|
||||
@@ -33032,7 +33044,7 @@ snapshots:
|
||||
rollup: 4.52.5
|
||||
vite: rolldown-vite@7.1.20(@types/node@24.9.2)(esbuild@0.25.9)(jiti@2.5.1)(less@4.4.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1)
|
||||
|
||||
unplugin-yaml@3.0.7(@nuxt/kit@4.0.3(magicast@0.3.5))(@nuxt/schema@4.0.3)(astro@5.10.1(@netlify/blobs@9.1.2)(@types/node@24.9.2)(db0@0.3.2(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(ioredis@5.7.0)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(rollup@4.52.5)(terser@5.43.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1))(esbuild@0.25.9)(rolldown@1.0.0-beta.45)(rollup@4.52.5)(vite@6.4.1(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1)):
|
||||
unplugin-yaml@3.0.7(@nuxt/kit@4.0.3(magicast@0.3.5))(@nuxt/schema@4.0.3)(astro@5.10.1(@netlify/blobs@9.1.2)(@types/node@24.9.2)(db0@0.3.2(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(encoding@0.1.13)(ioredis@5.7.0)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(rollup@4.52.5)(terser@5.43.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1))(esbuild@0.25.9)(rolldown@1.0.0-beta.45)(rollup@4.52.5)(vite@6.4.1(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1)):
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.3.0(rollup@4.52.5)
|
||||
unplugin: 2.3.10
|
||||
@@ -33046,7 +33058,7 @@ snapshots:
|
||||
rollup: 4.52.5
|
||||
vite: 6.4.1(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1)
|
||||
|
||||
unplugin-yaml@3.0.7(@nuxt/kit@4.0.3(magicast@0.3.5))(@nuxt/schema@4.0.3)(astro@5.10.1(@netlify/blobs@9.1.2)(@types/node@24.9.2)(db0@0.3.2(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(ioredis@5.7.0)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(rollup@4.52.5)(terser@5.43.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1))(esbuild@0.25.9)(rolldown@1.0.0-beta.45)(rollup@4.52.5)(vite@7.1.12(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1)):
|
||||
unplugin-yaml@3.0.7(@nuxt/kit@4.0.3(magicast@0.3.5))(@nuxt/schema@4.0.3)(astro@5.10.1(@netlify/blobs@9.1.2)(@types/node@24.9.2)(db0@0.3.2(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(encoding@0.1.13)(ioredis@5.7.0)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(rollup@4.52.5)(terser@5.43.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1))(esbuild@0.25.9)(rolldown@1.0.0-beta.45)(rollup@4.52.5)(vite@7.1.12(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1)):
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.3.0(rollup@4.52.5)
|
||||
unplugin: 2.3.10
|
||||
@@ -33698,17 +33710,17 @@ snapshots:
|
||||
'@nuxt/schema': 4.0.3
|
||||
nuxt: 4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.0)(@types/node@24.9.2)(@vue/compiler-sfc@3.5.22)(bufferutil@4.0.9)(db0@0.3.2(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(encoding@0.1.13)(eslint@9.39.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.4.2)(lightningcss@1.30.2)(magicast@0.3.5)(optionator@0.9.4)(rolldown-vite@7.1.20(@types/node@24.9.2)(esbuild@0.25.9)(jiti@2.5.1)(less@4.4.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1))(rolldown@1.0.0-beta.45)(rollup@2.79.2)(terser@5.43.1)(tsx@4.20.6)(typescript@5.9.3)(utf-8-validate@5.0.10)(vue-tsc@3.1.2(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.1)
|
||||
|
||||
vue-sonner@2.0.9(@nuxt/kit@4.0.3(magicast@0.3.5))(@nuxt/schema@4.0.3)(nuxt@4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.0)(@types/node@24.9.2)(@vue/compiler-sfc@3.5.22)(bufferutil@4.0.9)(db0@0.3.2(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(eslint@9.39.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.4.2)(lightningcss@1.30.2)(magicast@0.3.5)(optionator@0.9.4)(rolldown@1.0.0-beta.45)(rollup@4.52.5)(terser@5.43.1)(tsx@4.20.6)(typescript@5.9.3)(utf-8-validate@5.0.10)(vite@6.4.1(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1))(vue-tsc@3.1.2(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.1)):
|
||||
vue-sonner@2.0.9(@nuxt/kit@4.0.3(magicast@0.3.5))(@nuxt/schema@4.0.3)(nuxt@4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.0)(@types/node@24.9.2)(@vue/compiler-sfc@3.5.22)(bufferutil@4.0.9)(db0@0.3.2(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(encoding@0.1.13)(eslint@9.39.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.4.2)(lightningcss@1.30.2)(magicast@0.3.5)(optionator@0.9.4)(rolldown@1.0.0-beta.45)(rollup@4.52.5)(terser@5.43.1)(tsx@4.20.6)(typescript@5.9.3)(utf-8-validate@5.0.10)(vite@6.4.1(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1))(vue-tsc@3.1.2(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.1)):
|
||||
optionalDependencies:
|
||||
'@nuxt/kit': 4.0.3(magicast@0.3.5)
|
||||
'@nuxt/schema': 4.0.3
|
||||
nuxt: 4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.0)(@types/node@24.9.2)(@vue/compiler-sfc@3.5.22)(bufferutil@4.0.9)(db0@0.3.2(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(eslint@9.39.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.4.2)(lightningcss@1.30.2)(magicast@0.3.5)(optionator@0.9.4)(rolldown@1.0.0-beta.45)(rollup@4.52.5)(terser@5.43.1)(tsx@4.20.6)(typescript@5.9.3)(utf-8-validate@5.0.10)(vite@6.4.1(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1))(vue-tsc@3.1.2(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.1)
|
||||
nuxt: 4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.0)(@types/node@24.9.2)(@vue/compiler-sfc@3.5.22)(bufferutil@4.0.9)(db0@0.3.2(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(encoding@0.1.13)(eslint@9.39.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.4.2)(lightningcss@1.30.2)(magicast@0.3.5)(optionator@0.9.4)(rolldown@1.0.0-beta.45)(rollup@4.52.5)(terser@5.43.1)(tsx@4.20.6)(typescript@5.9.3)(utf-8-validate@5.0.10)(vite@6.4.1(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1))(vue-tsc@3.1.2(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.1)
|
||||
|
||||
vue-sonner@2.0.9(@nuxt/kit@4.0.3(magicast@0.3.5))(@nuxt/schema@4.0.3)(nuxt@4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.0)(@types/node@24.9.2)(@vue/compiler-sfc@3.5.22)(bufferutil@4.0.9)(db0@0.3.2(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(eslint@9.39.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.4.2)(lightningcss@1.30.2)(magicast@0.3.5)(optionator@0.9.4)(rolldown@1.0.0-beta.45)(rollup@4.52.5)(terser@5.43.1)(tsx@4.20.6)(typescript@5.9.3)(utf-8-validate@5.0.10)(vite@7.1.12(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1))(vue-tsc@3.1.2(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.1)):
|
||||
vue-sonner@2.0.9(@nuxt/kit@4.0.3(magicast@0.3.5))(@nuxt/schema@4.0.3)(nuxt@4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.0)(@types/node@24.9.2)(@vue/compiler-sfc@3.5.22)(bufferutil@4.0.9)(db0@0.3.2(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(encoding@0.1.13)(eslint@9.39.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.4.2)(lightningcss@1.30.2)(magicast@0.3.5)(optionator@0.9.4)(rolldown@1.0.0-beta.45)(rollup@4.52.5)(terser@5.43.1)(tsx@4.20.6)(typescript@5.9.3)(utf-8-validate@5.0.10)(vite@7.1.12(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1))(vue-tsc@3.1.2(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.1)):
|
||||
optionalDependencies:
|
||||
'@nuxt/kit': 4.0.3(magicast@0.3.5)
|
||||
'@nuxt/schema': 4.0.3
|
||||
nuxt: 4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.0)(@types/node@24.9.2)(@vue/compiler-sfc@3.5.22)(bufferutil@4.0.9)(db0@0.3.2(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(eslint@9.39.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.4.2)(lightningcss@1.30.2)(magicast@0.3.5)(optionator@0.9.4)(rolldown@1.0.0-beta.45)(rollup@4.52.5)(terser@5.43.1)(tsx@4.20.6)(typescript@5.9.3)(utf-8-validate@5.0.10)(vite@7.1.12(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1))(vue-tsc@3.1.2(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.1)
|
||||
nuxt: 4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.0)(@types/node@24.9.2)(@vue/compiler-sfc@3.5.22)(bufferutil@4.0.9)(db0@0.3.2(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(encoding@0.1.13)(eslint@9.39.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.4.2)(lightningcss@1.30.2)(magicast@0.3.5)(optionator@0.9.4)(rolldown@1.0.0-beta.45)(rollup@4.52.5)(terser@5.43.1)(tsx@4.20.6)(typescript@5.9.3)(utf-8-validate@5.0.10)(vite@7.1.12(@types/node@24.9.2)(jiti@2.5.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.6)(yaml@2.8.1))(vue-tsc@3.1.2(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.1)
|
||||
|
||||
vue-tsc@3.1.2(typescript@5.9.3):
|
||||
dependencies:
|
||||
|
||||
@@ -4,6 +4,7 @@ export default defineConfig({
|
||||
test: {
|
||||
projects: [
|
||||
'packages/injecta',
|
||||
'packages/stage-ui',
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user