diff --git a/apps/stage-tamagotchi/package.json b/apps/stage-tamagotchi/package.json index 6a0a721a5..d03d20230 100644 --- a/apps/stage-tamagotchi/package.json +++ b/apps/stage-tamagotchi/package.json @@ -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", diff --git a/apps/stage-tamagotchi/src/renderer/pages/index.vue b/apps/stage-tamagotchi/src/renderer/pages/index.vue index 0ca8240f6..720098c88 100644 --- a/apps/stage-tamagotchi/src/renderer/pages/index.vue +++ b/apps/stage-tamagotchi/src/renderer/pages/index.vue @@ -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 }) diff --git a/apps/stage-tamagotchi/src/renderer/pages/settings/system/developer.vue b/apps/stage-tamagotchi/src/renderer/pages/settings/system/developer.vue index 4d8ae2b45..e9a0e872b 100644 --- a/apps/stage-tamagotchi/src/renderer/pages/settings/system/developer.vue +++ b/apps/stage-tamagotchi/src/renderer/pages/settings/system/developer.vue @@ -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) diff --git a/apps/stage-web/package.json b/apps/stage-web/package.json index 1543f231f..b7afdc4a8 100644 --- a/apps/stage-web/package.json +++ b/apps/stage-web/package.json @@ -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", diff --git a/apps/stage-web/src/pages/settings/system/developer.vue b/apps/stage-web/src/pages/settings/system/developer.vue index d01d2e9b4..aa5628de6 100644 --- a/apps/stage-web/src/pages/settings/system/developer.vue +++ b/apps/stage-web/src/pages/settings/system/developer.vue @@ -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', + }, ]) diff --git a/cspell.config.yaml b/cspell.config.yaml index 3aa44536b..3fcb2e75d 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -11,6 +11,7 @@ words: - alexanderolsen - alibabacloud - aliyun + - aliyuncs - allseto - animejs - APNG @@ -238,6 +239,7 @@ words: - sensenova - serde - Shadcn + - shenzhen - shiki - shikijs - silero diff --git a/packages/stage-pages/src/pages/devtools/providers-transcription-realtime-aliyun-nls.vue b/packages/stage-pages/src/pages/devtools/providers-transcription-realtime-aliyun-nls.vue new file mode 100644 index 000000000..96b453c1b --- /dev/null +++ b/packages/stage-pages/src/pages/devtools/providers-transcription-realtime-aliyun-nls.vue @@ -0,0 +1,542 @@ + + + + + + + Aliyun NLS Realtime Transcription + + + Access Key ID and Secret with SpeechTranscriber permissions are required. + + + + + + + + + + + + + + + + + Status: + + {{ statusLabel }} + + + Recording + + + + + + Connect + + + + Listen + + + + Stop + + + + Disconnect + + + + + + + + Transcripts + + + + + Partial + + + {{ currentPartial }} + + + + Waiting for server... + + + + + #{{ sentence.index }} + + + + {{ sentence.text }} + + + Waiting for final result... + + + + + + + + + + Logs + + + + {{ entry.text }} + + + + + + + +meta: + layout: settings + diff --git a/packages/stage-ui/.env b/packages/stage-ui/.env new file mode 100644 index 000000000..1bccbae84 --- /dev/null +++ b/packages/stage-ui/.env @@ -0,0 +1,2 @@ +ALIYUN_AK_ID= +ALIYUN_AK_SECRET= diff --git a/packages/stage-ui/package.json b/packages/stage-ui/package.json index 9f7e715a6..3d20a928e 100644 --- a/packages/stage-ui/package.json +++ b/packages/stage-ui/package.json @@ -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", diff --git a/packages/stage-ui/src/stores/providers/aliyun/index.ts b/packages/stage-ui/src/stores/providers/aliyun/index.ts new file mode 100644 index 000000000..23407bed8 --- /dev/null +++ b/packages/stage-ui/src/stores/providers/aliyun/index.ts @@ -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 { + appkey: string + message_id: string + task_id: string + namespace: 'SpeechTranscriber' + name: N + + status?: 20000000 + status_message?: 'GATEWAY' | 'SUCCESS' | 'Success' +} + +interface BaseEvent { + header: BaseEventHeader + 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; +}[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; +}[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, + } +} diff --git a/packages/stage-ui/src/stores/providers/aliyun/token.test.ts b/packages/stage-ui/src/stores/providers/aliyun/token.test.ts new file mode 100644 index 000000000..067fdffe7 --- /dev/null +++ b/packages/stage-ui/src/stores/providers/aliyun/token.test.ts @@ -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) + }) +}) diff --git a/packages/stage-ui/src/stores/providers/aliyun/token.ts b/packages/stage-ui/src/stores/providers/aliyun/token.ts new file mode 100644 index 000000000..c803df75d --- /dev/null +++ b/packages/stage-ui/src/stores/providers/aliyun/token.ts @@ -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 + +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 { + 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, + algorithm, + false, + ['sign'], + ) + + const signDataEncoder = new TextEncoder() + const data = signDataEncoder.encode(stringToSign) + const signatureBuffer = await subtle.sign('HMAC', cryptoKey, data as Uint8Array) + return encodeBase64(signatureBuffer) +} + +export async function buildCreateTokenRequest(accessKeyId: string, accessKeySecret: string, options?: CreateTokenOptions): Promise { + 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'}`) +} diff --git a/packages/stage-ui/src/stores/providers/aliyun/utils.ts b/packages/stage-ui/src/stores/providers/aliyun/utils.ts new file mode 100644 index 000000000..bf208acf9 --- /dev/null +++ b/packages/stage-ui/src/stores/providers/aliyun/utils.ts @@ -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 +} diff --git a/packages/stage-ui/vitest.config.ts b/packages/stage-ui/vitest.config.ts index 647f39364..26253de4d 100644 --- a/packages/stage-ui/vitest.config.ts +++ b/packages/stage-ui/vitest.config.ts @@ -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'), ''), + }, + }) }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index de2aaeb85..c8ee4e0c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: diff --git a/vitest.config.ts b/vitest.config.ts index 7f40d85e6..da11910bc 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -4,6 +4,7 @@ export default defineConfig({ test: { projects: [ 'packages/injecta', + 'packages/stage-ui', ], }, })
+ Access Key ID and Secret with SpeechTranscriber permissions are required. +