style: lint

This commit is contained in:
Neko Ayaka
2026-08-26 19:49:58 +08:00
parent e60a04a4ec
commit 98f40d7d0b
1625 changed files with 75216 additions and 75203 deletions
@@ -38,10 +38,10 @@ describe('performance overlay recording controls', () => {
},
})
await screen.getByRole('button', { name: 'Record', exact: true }).click()
await screen.getByRole('button', { exact: true, name: 'Record' }).click()
expect(store.recording).toBe(true)
await screen.getByRole('button', { name: 'Stop', exact: true }).click()
await screen.getByRole('button', { exact: true, name: 'Stop' }).click()
expect(store.recording).toBe(false)
expect(exportCsv).not.toHaveBeenCalled()
@@ -9,7 +9,7 @@ export function useAudioInput() {
const audioInputs = computed(() => devices.audioInputs.value)
const constraints = ref<MediaStreamConstraints>({ audio: true })
const media = useUserMedia({ constraints, autoSwitch: true, enabled: false })
const media = useUserMedia({ autoSwitch: true, constraints, enabled: false })
async function request() {
if (devices.permissionGranted.value) {
@@ -71,13 +71,13 @@ export function useAudioInput() {
}
return {
selectedAudioInputId,
selectedAudioInput,
audioInputs,
media,
request,
selectedAudioInput,
selectedAudioInputId,
start,
stop,
request,
media,
}
}
@@ -22,8 +22,8 @@ export function useIconAnimation(icon: string) {
})
return {
animationIcon,
iconAnimationStarted,
showIconAnimation,
animationIcon,
}
}
@@ -46,17 +46,17 @@ export function createLagSampler(tracer: PerfTracer) {
const fps = delta > 0 ? 1000 / delta : 0
tracer.emit({
tracerId: 'lag',
name: 'fps',
ts,
duration: fps,
name: 'fps',
tracerId: 'lag',
ts,
})
tracer.emit({
tracerId: 'lag',
name: 'frameDuration',
ts,
duration: delta,
name: 'frameDuration',
tracerId: 'lag',
ts,
})
}
@@ -81,14 +81,14 @@ export function createLagSampler(tracer: PerfTracer) {
longTaskObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
tracer.emit({
tracerId: 'lag',
name: 'longtask',
ts: entry.startTime,
duration: entry.duration,
name: 'longtask',
tracerId: 'lag',
ts: entry.startTime,
})
}
})
longTaskObserver.observe({ type: 'longtask', buffered: true })
longTaskObserver.observe({ buffered: true, type: 'longtask' })
}
catch (error) {
console.warn('[LagSampler] Failed to start longtask observer', error)
@@ -110,10 +110,10 @@ export function createLagSampler(tracer: PerfTracer) {
memoryTimer = setInterval(() => {
tracer.emit({
tracerId: 'lag',
name: 'memory',
ts: performance.now(),
duration: perfWithMemory.memory?.usedJSHeapSize ?? 0,
name: 'memory',
tracerId: 'lag',
ts: performance.now(),
})
}, 1000)
}
@@ -138,8 +138,8 @@ export function createLagSampler(tracer: PerfTracer) {
}
return {
supported,
start,
stop,
supported,
}
}
+2 -2
View File
@@ -47,9 +47,9 @@ const routeRecords = setupLayouts(routes as RouteRecordRaw[])
let router: Router
if (isEnvTruthy(import.meta.env.VITE_APP_TARGET_HUGGINGFACE_SPACE))
router = createRouter({ routes: routeRecords, history: createWebHashHistory() })
router = createRouter({ history: createWebHashHistory(), routes: routeRecords })
else
router = createRouter({ routes: routeRecords, history: createWebHistory() })
router = createRouter({ history: createWebHistory(), routes: routeRecords })
router.beforeEach((to, from) => {
if (to.path !== from.path)
+1 -1
View File
@@ -15,8 +15,8 @@ function getLocale() {
}
export const i18n = createI18n({
fallbackLocale: 'en',
legacy: false,
locale: getLocale(),
fallbackLocale: 'en',
messages,
})
@@ -63,6 +63,18 @@ afterAll(() => {
layoutStyle.remove()
})
async function expectImportedModelPhoto(component: Component, modelId: string) {
const container = await renderPolaroid(component, modelId)
await expect.poll(() => container.querySelector('option'), { timeout: 20_000 }).toBeInstanceOf(HTMLOptionElement)
const canvas = container.querySelector('canvas')
if (!(canvas instanceof HTMLCanvasElement))
throw new TypeError('Polaroid did not render a canvas.')
expect(canvas.toDataURL('image/png')).toMatch(/^data:image\/png;base64,./)
}
async function renderPolaroid(component: Component, modelId: string) {
const pinia = createPinia()
const displayModels = useDisplayModelsStore(pinia)
@@ -82,12 +94,12 @@ async function renderPolaroid(component: Component, modelId: string) {
app.mount(container)
displayModels.displayModels.unshift({
id: modelId,
format: DisplayModelFormat.Live2dZip,
type: 'file',
file,
name: file.name,
format: DisplayModelFormat.Live2dZip,
id: modelId,
importedAt: Date.now(),
name: file.name,
type: 'file',
})
settings.stageModelSelected = modelId
@@ -97,18 +109,6 @@ async function renderPolaroid(component: Component, modelId: string) {
return container
}
async function expectImportedModelPhoto(component: Component, modelId: string) {
const container = await renderPolaroid(component, modelId)
await expect.poll(() => container.querySelector('option'), { timeout: 20_000 }).toBeInstanceOf(HTMLOptionElement)
const canvas = container.querySelector('canvas')
if (!(canvas instanceof HTMLCanvasElement))
throw new TypeError('Polaroid did not render a canvas.')
expect(canvas.toDataURL('image/png')).toMatch(/^data:image\/png;base64,./)
}
describe('polaroid imported Live2D model', () => {
it('loads and captures an imported model on the web page', async () => {
// ROOT CAUSE:
+68 -68
View File
@@ -8,22 +8,68 @@ import { createLagSampler } from '../composables/perf/register-lag-sampler'
export type LagMetric = 'fps' | 'frameDuration' | 'longtask' | 'memory'
interface Sample {
ts: number
value: number
meta?: Record<string, unknown>
interface HistogramBin {
count: number
end: number
start: number
}
interface RecordingSnapshot {
samples: Record<LagMetric, Sample[]>
startedAt: number
stoppedAt: number
samples: Record<LagMetric, Sample[]>
}
interface HistogramBin {
start: number
end: number
count: number
interface Sample {
meta?: Record<string, unknown>
ts: number
value: number
}
function buildHistogram(values: number[], bins = 20): HistogramBin[] {
if (!values.length)
return []
const min = Math.min(...values)
const max = Math.max(...values)
if (min === max) {
return [{
count: values.length,
end: max || min + 1,
start: min,
}]
}
const width = (max - min) / bins
const buckets = Array.from({ length: bins }, (_, idx) => ({
count: 0,
end: min + ((idx + 1) * width),
start: min + (idx * width),
}))
for (const value of values) {
let binIndex = Math.floor((value - min) / width)
if (binIndex >= bins)
binIndex = bins - 1
buckets[binIndex].count += 1
}
return buckets
}
function calcStats(values: number[]) {
if (!values.length)
return { avg: 0, latest: 0, p95: 0 }
const total = values.reduce((acc, n) => acc + n, 0)
const avg = total / values.length
const sorted = [...values].sort((a, b) => a - b)
const idx = Math.max(0, Math.floor(0.95 * (sorted.length - 1)))
const p95 = sorted[idx]
const latest = values.at(-1) ?? 0
return { avg, latest, p95 }
}
function createEmptySamples(): Record<LagMetric, Sample[]> {
@@ -40,52 +86,6 @@ function pruneSamples(buffer: Sample[], cutoffTs: number) {
buffer.shift()
}
function calcStats(values: number[]) {
if (!values.length)
return { avg: 0, p95: 0, latest: 0 }
const total = values.reduce((acc, n) => acc + n, 0)
const avg = total / values.length
const sorted = [...values].sort((a, b) => a - b)
const idx = Math.max(0, Math.floor(0.95 * (sorted.length - 1)))
const p95 = sorted[idx]
const latest = values.at(-1) ?? 0
return { avg, p95, latest }
}
function buildHistogram(values: number[], bins = 20): HistogramBin[] {
if (!values.length)
return []
const min = Math.min(...values)
const max = Math.max(...values)
if (min === max) {
return [{
start: min,
end: max || min + 1,
count: values.length,
}]
}
const width = (max - min) / bins
const buckets = Array.from({ length: bins }, (_, idx) => ({
start: min + (idx * width),
end: min + ((idx + 1) * width),
count: 0,
}))
for (const value of values) {
let binIndex = Math.floor((value - min) / width)
if (binIndex >= bins)
binIndex = bins - 1
buckets[binIndex].count += 1
}
return buckets
}
export const useDevtoolsLagStore = defineStore('devtoolsLag', () => {
const enabled = reactive({
fps: false,
@@ -98,7 +98,7 @@ export const useDevtoolsLagStore = defineStore('devtoolsLag', () => {
const buffers = reactive(createEmptySamples())
const recording = ref(false)
const recordingStartedAt = ref<number | null>(null)
const recordingStartedAt = ref<null | number>(null)
const recordingElapsedMs = ref(0)
const recordingSamples = reactive(createEmptySamples())
const lastRecording = ref<RecordingSnapshot>()
@@ -120,12 +120,12 @@ export const useDevtoolsLagStore = defineStore('devtoolsLag', () => {
const cutoff = ts - windowMs
const buffer = buffers[metric]
buffer.push({ ts, value, meta })
buffer.push({ meta, ts, value })
pruneSamples(buffer, cutoff)
if (recording.value) {
const sampleBuffer = recordingSamples[metric]
sampleBuffer.push({ ts, value, meta })
sampleBuffer.push({ meta, ts, value })
}
}
@@ -165,14 +165,14 @@ export const useDevtoolsLagStore = defineStore('devtoolsLag', () => {
? 0
: stoppedAt - recordingStartedAt.value
const snapshot: RecordingSnapshot = {
startedAt: recordingStartedAt.value ?? stoppedAt,
stoppedAt,
samples: {
fps: [...recordingSamples.fps],
frameDuration: [...recordingSamples.frameDuration],
longtask: [...recordingSamples.longtask],
memory: [...recordingSamples.memory],
},
startedAt: recordingStartedAt.value ?? stoppedAt,
stoppedAt,
}
lastRecording.value = snapshot
@@ -253,7 +253,7 @@ export const useDevtoolsLagStore = defineStore('devtoolsLag', () => {
if (!target)
return
const rows: Array<Array<string | number>> = [['metric', 'ts', 'value', 'meta']]
const rows: Array<Array<number | string>> = [['metric', 'ts', 'value', 'meta']]
for (const metric of Object.keys(target.samples) as LagMetric[]) {
for (const sample of target.samples[metric]) {
rows.push([
@@ -291,18 +291,18 @@ export const useDevtoolsLagStore = defineStore('devtoolsLag', () => {
}
return {
enabled,
buffers,
buildHistogram,
calcStats,
enabled,
exportCsv,
lastRecording,
recording,
recordingElapsedMs,
lastRecording,
supported,
startRecording,
stopRecording,
toggleRecording,
exportCsv,
supported,
toggleAll,
calcStats,
buildHistogram,
toggleRecording,
}
})
+1 -1
View File
@@ -25,8 +25,8 @@ export const usePWAStore = defineStore('pwa', () => {
onNeedRefresh: () => {
const id = nanoid()
toast.custom(markRaw(h(ToasterPWAUpdateReady, { id, onUpdate: () => updateSW() })), {
id,
duration: 30000,
id,
position: isMobile.value ? 'top-center' : 'bottom-right',
})
},
+52 -52
View File
@@ -7,30 +7,30 @@ import { AutoModel, Tensor } from '@huggingface/transformers'
* Voice Activity Detection processor
*/
export class VAD implements BaseVAD {
private config: BaseVADConfig
private model: PreTrainedModel | undefined
private state: Tensor
private sampleRateTensor: Tensor
private buffer: Float32Array
private bufferPointer: number = 0
private config: BaseVADConfig
private eventListeners: Partial<Record<keyof VADEvents, VADEventCallback<any>[]>> = {}
private inferenceChain: Promise<any> = Promise.resolve()
private isReady: boolean = false
private isRecording: boolean = false
private model: PreTrainedModel | undefined
private postSpeechSamples: number = 0
private prevBuffers: Float32Array[] = []
private inferenceChain: Promise<any> = Promise.resolve()
private eventListeners: Partial<Record<keyof VADEvents, VADEventCallback<any>[]>> = {}
private isReady: boolean = false
private sampleRateTensor: Tensor
private state: Tensor
constructor(userConfig: Partial<BaseVADConfig> = {}) {
// Default configuration
const defaultConfig: BaseVADConfig = {
sampleRate: 16000,
speechThreshold: 0.3,
exitThreshold: 0.1,
minSilenceDurationMs: 400,
speechPadMs: 80,
minSpeechDurationMs: 250,
maxBufferDuration: 30,
minSilenceDurationMs: 400,
minSpeechDurationMs: 250,
newBufferSize: 512,
sampleRate: 16000,
speechPadMs: 80,
speechThreshold: 0.3,
}
this.config = { ...defaultConfig, ...userConfig }
@@ -45,7 +45,7 @@ export class VAD implements BaseVAD {
*/
public async initialize(): Promise<void> {
try {
this.emit('status', { type: 'info', message: 'Loading VAD model...' })
this.emit('status', { message: 'Loading VAD model...', type: 'info' })
this.model = await AutoModel.from_pretrained('onnx-community/silero-vad', {
config: { model_type: 'custom' } as any,
@@ -53,24 +53,14 @@ export class VAD implements BaseVAD {
})
this.isReady = true
this.emit('status', { type: 'info', message: 'VAD model loaded successfully' })
this.emit('status', { message: 'VAD model loaded successfully', type: 'info' })
}
catch (error) {
this.emit('status', { type: 'error', message: `Failed to load VAD model: ${error}` })
this.emit('status', { message: `Failed to load VAD model: ${error}`, type: 'error' })
throw error
}
}
/**
* Add event listener
*/
public on<K extends keyof VADEvents>(event: K, callback: VADEventCallback<K>): void {
if (!this.eventListeners[event]) {
this.eventListeners[event] = []
}
this.eventListeners[event]!.push(callback as any)
}
/**
* Remove event listener
*/
@@ -81,14 +71,13 @@ export class VAD implements BaseVAD {
}
/**
* Emit event
* Add event listener
*/
private emit<K extends keyof VADEvents>(event: K, data: VADEvents[K]): void {
if (!this.eventListeners[event])
return
for (const callback of this.eventListeners[event]!) {
callback(data)
public on<K extends keyof VADEvents>(event: K, callback: VADEventCallback<K>): void {
if (!this.eventListeners[event]) {
this.eventListeners[event] = []
}
this.eventListeners[event]!.push(callback as any)
}
/**
@@ -144,7 +133,7 @@ export class VAD implements BaseVAD {
if (!this.isRecording) {
// Speech just started
this.emit('speech-start', undefined)
this.emit('status', { type: 'info', message: 'Speech detected' })
this.emit('status', { message: 'Speech detected', type: 'info' })
}
// Update state
@@ -170,13 +159,31 @@ export class VAD implements BaseVAD {
}
}
/**
* Update configuration
*/
public updateConfig(newConfig: Partial<BaseVADConfig>): void {
this.config = { ...this.config, ...newConfig }
// If buffer size changed, create a new buffer
if (newConfig.maxBufferDuration || newConfig.sampleRate) {
this.buffer = new Float32Array(this.config.maxBufferDuration * this.config.sampleRate)
this.bufferPointer = 0
}
// Update sample rate tensor if needed
if (newConfig.sampleRate) {
this.sampleRateTensor = new Tensor('int64', [this.config.sampleRate], [])
}
}
/**
* Detect speech in an audio buffer
*/
private async detectSpeech(buffer: Float32Array): Promise<boolean> {
const input = new Tensor('float32', buffer, [1, buffer.length])
const { stateN, output } = await (this.inferenceChain = this.inferenceChain.then(() =>
const { output, stateN } = await (this.inferenceChain = this.inferenceChain.then(() =>
this.model?.({
input,
sr: this.sampleRateTensor,
@@ -189,7 +196,7 @@ export class VAD implements BaseVAD {
// Get the speech probability
const speechProb = output.data[0]
this.emit('debug', { message: 'VAD score', data: { probability: speechProb } })
this.emit('debug', { data: { probability: speechProb }, message: 'VAD score' })
// Apply thresholds
return (
@@ -198,6 +205,17 @@ export class VAD implements BaseVAD {
)
}
/**
* Emit event
*/
private emit<K extends keyof VADEvents>(event: K, data: VADEvents[K]): void {
if (!this.eventListeners[event])
return
for (const callback of this.eventListeners[event]!) {
callback(data)
}
}
/**
* Process a complete speech segment
*/
@@ -247,24 +265,6 @@ export class VAD implements BaseVAD {
this.postSpeechSamples = 0
this.prevBuffers = []
}
/**
* Update configuration
*/
public updateConfig(newConfig: Partial<BaseVADConfig>): void {
this.config = { ...this.config, ...newConfig }
// If buffer size changed, create a new buffer
if (newConfig.maxBufferDuration || newConfig.sampleRate) {
this.buffer = new Float32Array(this.config.maxBufferDuration * this.config.sampleRate)
this.bufferPointer = 0
}
// Update sample rate tensor if needed
if (newConfig.sampleRate) {
this.sampleRateTensor = new Tensor('int64', [this.config.sampleRate], [])
}
}
}
/**
+2 -2
View File
@@ -11,15 +11,15 @@ export default mergeConfigs([
...presetWebFontsFonts('fontsource'),
},
timeouts: {
warning: 5000,
failure: 10000,
warning: 5000,
},
}),
],
rules: [
['transition-colors-none', {
'transition-property': 'color, background-color, border-color, text-color',
'transition-duration': '0s',
'transition-property': 'color, background-color, border-color, text-color',
}],
],
},
+80 -80
View File
@@ -38,6 +38,26 @@ function hasFlagEnableMkcert(): boolean {
}
export default defineConfig({
build: {
manifest: true,
rolldownOptions: {
output: {
chunkFileNames: (chunkInfo) => {
const containsAnalyticsModule = chunkInfo.moduleIds.some((moduleId) => {
const normalizedModuleId = moduleId.replaceAll('\\', '/').toLowerCase()
return normalizedModuleId.includes('analytics') || normalizedModuleId.includes('posthog')
})
// Only analytics/provider chunks receive the manual neutral mapping;
// all unrelated chunks retain Vite's readable default naming.
return containsAnalyticsModule
? 'assets/auxiliary-[hash].js'
: 'assets/[name]-[hash].js'
},
},
},
sourcemap: true,
},
optimizeDeps: {
exclude: [
// Internal Packages
@@ -65,60 +85,6 @@ export default defineConfig({
'@framework/model/cubismmoc',
],
},
resolve: {
alias: {
'@proj-airi/server-sdk': resolve(join(import.meta.dirname, '..', '..', 'packages', 'server-sdk', 'src')),
'@proj-airi/i18n': resolve(join(import.meta.dirname, '..', '..', 'packages', 'i18n', 'src')),
'@proj-airi/stage-ui': resolve(join(import.meta.dirname, '..', '..', 'packages', 'stage-ui', 'src')),
'@proj-airi/stage-pages': resolve(join(import.meta.dirname, '..', '..', 'packages', 'stage-pages', 'src')),
'@proj-airi/stage-shared': resolve(join(import.meta.dirname, '..', '..', 'packages', 'stage-shared', 'src')),
'@proj-airi/stage-layouts': resolve(join(import.meta.dirname, '..', '..', 'packages', 'stage-layouts', 'src')),
},
},
server: {
fs: {
// To mute errors like:
// The request id ".../node_modules/@fontsource/sniglet/files/sniglet-latin-400-normal.woff" is outside of Vite serving allow list.
//
// See: https://vite.dev/config/server-options#server-fs-strict
strict: false,
},
warmup: {
clientFiles: [
`${resolve(join(import.meta.dirname, '..', '..', 'packages', 'stage-ui', 'src'))}/*.vue`,
`${resolve(join(import.meta.dirname, '..', '..', 'packages', 'stage-pages', 'src'))}/*.vue`,
],
},
},
build: {
manifest: true,
rolldownOptions: {
output: {
chunkFileNames: (chunkInfo) => {
const containsAnalyticsModule = chunkInfo.moduleIds.some((moduleId) => {
const normalizedModuleId = moduleId.replaceAll('\\', '/').toLowerCase()
return normalizedModuleId.includes('analytics') || normalizedModuleId.includes('posthog')
})
// Only analytics/provider chunks receive the manual neutral mapping;
// all unrelated chunks retain Vite's readable default naming.
return containsAnalyticsModule
? 'assets/auxiliary-[hash].js'
: 'assets/[name]-[hash].js'
},
},
},
sourcemap: true,
},
worker: {
format: 'es',
rollupOptions: {
output: {
inlineDynamicImports: false,
},
},
},
plugins: [
...(
hasFlagEnableMkcert()
@@ -137,6 +103,7 @@ export default defineConfig({
Yaml(),
VueMacros({
betterDefine: false,
plugins: {
vue: Vue({
include: [/\.vue$/, /\.md$/],
@@ -144,18 +111,17 @@ export default defineConfig({
}),
vueJsx: false,
},
betterDefine: false,
}),
VueRouter({
extensions: ['.vue', '.md'],
dts: resolve(import.meta.dirname, 'src/typed-router.d.ts'),
exclude: ['**/components/**'],
extensions: ['.vue', '.md'],
importMode: 'async',
routesFolder: [
resolve(import.meta.dirname, 'src', 'pages'),
resolve(import.meta.dirname, '..', '..', 'packages', 'stage-pages', 'src', 'pages'),
],
exclude: ['**/components/**'],
}),
// https://github.com/JohnCampionJr/vite-plugin-vue-layouts
@@ -186,20 +152,17 @@ export default defineConfig({
...(env.TARGET_HUGGINGFACE_SPACE
? []
: [VitePWA({
registerType: 'prompt',
includeAssets: ['favicon.svg', 'apple-touch-icon.png'],
manifest: {
name: 'AIRI',
short_name: 'AIRI',
icons: [
{
src: '/web-app-manifest-192x192.png',
sizes: '192x192',
src: '/web-app-manifest-192x192.png',
type: 'image/png',
},
{
src: '/web-app-manifest-512x512.png',
sizes: '512x512',
src: '/web-app-manifest-512x512.png',
type: 'image/png',
},
{
@@ -215,7 +178,10 @@ export default defineConfig({
type: 'image/png',
},
],
name: 'AIRI',
short_name: 'AIRI',
},
registerType: 'prompt',
workbox: {
maximumFileSizeToCacheInBytes: 64 * 1024 * 1024,
navigateFallbackDenylist: [
@@ -229,22 +195,22 @@ export default defineConfig({
// https://github.com/intlify/bundle-tools/tree/main/packages/unplugin-vue-i18n
VueI18n({
runtimeOnly: true,
compositionOnly: true,
fullInstall: true,
runtimeOnly: true,
}),
// https://github.com/webfansplz/vite-plugin-vue-devtools
VueDevTools(),
DownloadLive2DSDK(),
Download('https://dist.ayaka.moe/live2d-models/hiyori_free_zh.zip', 'hiyori_free_zh.zip', 'live2d/models', { parentDir: stageUIAssetsRoot, cacheDir: sharedCacheDir }),
Download('https://dist.ayaka.moe/live2d-models/hiyori_pro_zh.zip', 'hiyori_pro_zh.zip', 'live2d/models', { parentDir: stageUIAssetsRoot, cacheDir: sharedCacheDir }),
Download('https://dist.ayaka.moe/vrm-models/VRoid-Hub/AvatarSample-A/AvatarSample_A.vrm', 'AvatarSample_A.vrm', 'vrm/models/AvatarSample-A', { parentDir: stageUIAssetsRoot, cacheDir: sharedCacheDir }),
Download('https://dist.ayaka.moe/vrm-models/VRoid-Hub/AvatarSample-B/AvatarSample_B.vrm', 'AvatarSample_B.vrm', 'vrm/models/AvatarSample-B', { parentDir: stageUIAssetsRoot, cacheDir: sharedCacheDir }),
Download('https://dist.ayaka.moe/live2d-models/hiyori_free_zh.zip', 'hiyori_free_zh.zip', 'live2d/models', { cacheDir: sharedCacheDir, parentDir: stageUIAssetsRoot }),
Download('https://dist.ayaka.moe/live2d-models/hiyori_pro_zh.zip', 'hiyori_pro_zh.zip', 'live2d/models', { cacheDir: sharedCacheDir, parentDir: stageUIAssetsRoot }),
Download('https://dist.ayaka.moe/vrm-models/VRoid-Hub/AvatarSample-A/AvatarSample_A.vrm', 'AvatarSample_A.vrm', 'vrm/models/AvatarSample-A', { cacheDir: sharedCacheDir, parentDir: stageUIAssetsRoot }),
Download('https://dist.ayaka.moe/vrm-models/VRoid-Hub/AvatarSample-B/AvatarSample_B.vrm', 'AvatarSample_B.vrm', 'vrm/models/AvatarSample-B', { cacheDir: sharedCacheDir, parentDir: stageUIAssetsRoot }),
// HuggingFace Spaces
LFS({ root: cwd(), extraGlobs: [
LFS({ extraGlobs: [
// Scene & Models
'*.vrm',
'*.vrma',
@@ -261,21 +227,21 @@ export default defineConfig({
'*.avif',
// Tensorflow / MediaPipe task
'*.task',
] }),
], root: cwd() }),
SpaceCard({
root: cwd(),
title: 'AIRI: Virtual Companion',
emoji: '🧸',
colorFrom: 'pink',
colorTo: 'pink',
sdk: 'static',
pinned: false,
emoji: '🧸',
license: 'mit',
models: [
'onnx-community/whisper-base',
'onnx-community/silero-vad',
],
pinned: false,
root: cwd(),
sdk: 'static',
short_description: 'AI driven VTuber & Companion, supports Live2D and VRM.',
title: 'AIRI: Virtual Companion',
}),
// For the following example assets:
@@ -293,9 +259,6 @@ export default defineConfig({
? []
: [
Basemove({
prefix: env.STAGE_WEB_WARP_DRIVE_PREFIX || 'proj-airi/stage-web/main/',
include: [/\.wasm$/i, /\.ttf$/i, /\.vrm$/i, /\.zip$/i], // in existing assets, wasm, ttf, vrm files are the largest ones
manifest: true,
clean: false,
contentTypeBy: (filename: string) => {
if (filename.endsWith('.wasm')) {
@@ -311,14 +274,51 @@ export default defineConfig({
return 'application/zip'
}
},
include: [/\.wasm$/i, /\.ttf$/i, /\.vrm$/i, /\.zip$/i], // in existing assets, wasm, ttf, vrm files are the largest ones
manifest: true,
prefix: env.STAGE_WEB_WARP_DRIVE_PREFIX || 'proj-airi/stage-web/main/',
provider: createS3Provider({
endpoint: env.S3_ENDPOINT,
accessKeyId: env.S3_ACCESS_KEY_ID,
secretAccessKey: env.S3_SECRET_ACCESS_KEY,
region: env.S3_REGION,
endpoint: env.S3_ENDPOINT,
publicBaseUrl: env.WARP_DRIVE_PUBLIC_BASE ?? env.S3_ENDPOINT,
region: env.S3_REGION,
secretAccessKey: env.S3_SECRET_ACCESS_KEY,
}),
}),
]),
],
resolve: {
alias: {
'@proj-airi/i18n': resolve(join(import.meta.dirname, '..', '..', 'packages', 'i18n', 'src')),
'@proj-airi/server-sdk': resolve(join(import.meta.dirname, '..', '..', 'packages', 'server-sdk', 'src')),
'@proj-airi/stage-layouts': resolve(join(import.meta.dirname, '..', '..', 'packages', 'stage-layouts', 'src')),
'@proj-airi/stage-pages': resolve(join(import.meta.dirname, '..', '..', 'packages', 'stage-pages', 'src')),
'@proj-airi/stage-shared': resolve(join(import.meta.dirname, '..', '..', 'packages', 'stage-shared', 'src')),
'@proj-airi/stage-ui': resolve(join(import.meta.dirname, '..', '..', 'packages', 'stage-ui', 'src')),
},
},
server: {
fs: {
// To mute errors like:
// The request id ".../node_modules/@fontsource/sniglet/files/sniglet-latin-400-normal.woff" is outside of Vite serving allow list.
//
// See: https://vite.dev/config/server-options#server-fs-strict
strict: false,
},
warmup: {
clientFiles: [
`${resolve(join(import.meta.dirname, '..', '..', 'packages', 'stage-ui', 'src'))}/*.vue`,
`${resolve(join(import.meta.dirname, '..', '..', 'packages', 'stage-pages', 'src'))}/*.vue`,
],
},
},
worker: {
format: 'es',
rollupOptions: {
output: {
inlineDynamicImports: false,
},
},
},
})
+6 -6
View File
@@ -12,25 +12,25 @@ export default defineConfig({
{
extends: true,
test: {
name: 'unit',
environment: 'jsdom',
include: ['src/**/*.test.ts'],
exclude: ['src/**/*.browser.test.ts'],
include: ['src/**/*.test.ts'],
name: 'unit',
},
},
mergeConfig(stageWebConfig, defineConfig({
test: {
name: 'browser',
include: ['src/**/*.browser.test.ts'],
setupFiles: ['./src/test/setup-live2d.browser.ts'],
browser: {
enabled: true,
headless: true,
provider: playwright(),
instances: [
{ browser: 'chromium' },
],
provider: playwright(),
},
include: ['src/**/*.browser.test.ts'],
name: 'browser',
setupFiles: ['./src/test/setup-live2d.browser.ts'],
},
})),
],