style: lint
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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',
|
||||
})
|
||||
},
|
||||
|
||||
@@ -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], [])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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',
|
||||
}],
|
||||
],
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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'],
|
||||
},
|
||||
})),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user