From 8ee4a3cd2f12b6e19588d9a6209328d035e1793c Mon Sep 17 00:00:00 2001 From: Neko Ayaka Date: Sun, 9 Jun 2024 20:37:28 +0800 Subject: [PATCH] refactor: cleanup code Signed-off-by: Neko Ayaka --- components/Live2DViewer.vue | 34 ++- components/MainStage.vue | 260 ++++++++++++++-------- composables/queue.ts | 28 ++- composables/queues.ts | 234 +++++++++++++++++++ constants/emotions.ts | 33 +++ constants/index.ts | 2 + constants/prompts/system-v2.ts | 32 +++ package.json | 1 + pages/test/filter-message.vue | 79 +++++++ pages/test/queues/delays.vue | 72 ++++++ pages/test/queues/emotions.vue | 78 +++++++ pages/test/queues/messages.vue | 67 ++++++ pnpm-lock.yaml | 60 ++++- server/api/v1/llm/voice/text-to-speech.ts | 2 + stores/audio.ts | 6 +- stores/llm.ts | 3 + 16 files changed, 872 insertions(+), 119 deletions(-) create mode 100644 composables/queues.ts create mode 100644 constants/emotions.ts create mode 100644 constants/prompts/system-v2.ts create mode 100644 pages/test/filter-message.vue create mode 100644 pages/test/queues/delays.vue create mode 100644 pages/test/queues/emotions.vue create mode 100644 pages/test/queues/messages.vue diff --git a/components/Live2DViewer.vue b/components/Live2DViewer.vue index 949905762..1e82ca272 100644 --- a/components/Live2DViewer.vue +++ b/components/Live2DViewer.vue @@ -3,10 +3,11 @@ import { onMounted, onUnmounted, ref, watch } from 'vue' import { Application } from '@pixi/app' import { extensions } from '@pixi/extensions' import { Ticker, TickerPlugin } from '@pixi/ticker' -import { Live2DModel } from 'pixi-live2d-display/cubism4' +import { Live2DModel, MotionPreloadStrategy, MotionPriority } from 'pixi-live2d-display/cubism4' import { useElementBounding, useWindowSize } from '@vueuse/core' const props = withDefaults(defineProps<{ + model: string mouthOpenSize?: number }>(), { mouthOpenSize: 0, @@ -22,9 +23,15 @@ const mouthOpenSize = computed(() => { const { width, height } = useWindowSize() const containerElementBounding = useElementBounding(containerRef) +const containerParentElementBounding = useElementBounding(containerRef.value?.parentElement) + +function getCoreModel() { + return model.value!.internalModel.coreModel as any +} async function initLive2DPixiStage(parent: HTMLDivElement) { containerElementBounding.update() + containerParentElementBounding.update() // https://guansss.github.io/pixi-live2d-display/#package-importing Live2DModel.registerTicker(Ticker) @@ -32,21 +39,21 @@ async function initLive2DPixiStage(parent: HTMLDivElement) { pixiApp.value = new Application({ width: containerElementBounding.width.value, - height: 550, + height: Math.max(800, containerParentElementBounding.height.value), backgroundAlpha: 0, }) pixiAppCanvas.value = pixiApp.value.view parent.appendChild(pixiApp.value.view) - model.value = await Live2DModel.from('assets/live2d/models/hiyori_free_zh/runtime/hiyori_free_t08.model3.json') + model.value = await Live2DModel.from(props.model, { motionPreload: MotionPreloadStrategy.ALL }) pixiApp.value.stage.addChild(model.value as any) model.value.x = containerElementBounding.width.value / 2 - model.value.y = 600 + model.value.y = Math.max(1000, containerParentElementBounding.height.value) model.value.rotation = Math.PI model.value.skew.x = Math.PI - model.value.scale.set(0.3, 0.3) + model.value.scale.set(0.5, 0.5) model.value.anchor.set(0.5, 0.5) model.value.on('hit', (hitAreas) => { @@ -58,18 +65,22 @@ async function initLive2DPixiStage(parent: HTMLDivElement) { coreModel.setParameterValueById('ParamMouthOpenY', mouthOpenSize.value) } +async function setMotion(motionName: string) { + await model.value!.motion(motionName, undefined, MotionPriority.FORCE) +} + watch([width, height], () => { if (pixiApp.value) pixiApp.value.renderer.resize((width.value - 16) / 2, 550) if (pixiAppCanvas.value) { pixiAppCanvas.value.width = (width.value - 16) / 2 - pixiAppCanvas.value.height = 550 + pixiAppCanvas.value.height = Math.max(800, containerParentElementBounding.height.value) } if (model.value) { model.value.x = (width.value - 16) / 4 - model.value.y = 600 + model.value.y = Math.max(1000, containerParentElementBounding.height.value) } }) @@ -85,11 +96,14 @@ onUnmounted(() => { }) watch(mouthOpenSize, (value) => { - const coreModel = model.value!.internalModel.coreModel as any - coreModel.setParameterValueById('ParamMouthOpenY', value) + getCoreModel().setParameterValueById('ParamMouthOpenY', value) +}) + +defineExpose({ + setMotion, }) diff --git a/components/MainStage.vue b/components/MainStage.vue index 4dc0e6f14..5e849473c 100644 --- a/components/MainStage.vue +++ b/components/MainStage.vue @@ -1,40 +1,57 @@ + + diff --git a/pages/test/queues/delays.vue b/pages/test/queues/delays.vue new file mode 100644 index 000000000..5544cf91a --- /dev/null +++ b/pages/test/queues/delays.vue @@ -0,0 +1,72 @@ + + + diff --git a/pages/test/queues/emotions.vue b/pages/test/queues/emotions.vue new file mode 100644 index 000000000..cd06a1ec5 --- /dev/null +++ b/pages/test/queues/emotions.vue @@ -0,0 +1,78 @@ + + + diff --git a/pages/test/queues/messages.vue b/pages/test/queues/messages.vue new file mode 100644 index 000000000..dae9a6399 --- /dev/null +++ b/pages/test/queues/messages.vue @@ -0,0 +1,67 @@ + + + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 30bb04e60..bb87a513f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,9 @@ importers: '@pixi/ticker': specifier: '6' version: 6.5.10(@pixi/extensions@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)) + '@vueuse/components': + specifier: ^10.11.0 + version: 10.11.0(vue@3.4.27(typescript@5.4.5)) ai: specifier: ^3.1.30 version: 3.1.30(openai@4.49.0(encoding@0.1.13))(react@18.3.1)(solid-js@1.8.17)(svelte@4.2.18)(vue@3.4.27(typescript@5.4.5))(zod@3.23.8) @@ -1622,8 +1625,11 @@ packages: '@vue/shared@3.4.27': resolution: {integrity: sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==} - '@vueuse/components@10.9.0': - resolution: {integrity: sha512-BHQpA0yIi3y7zKa1gYD0FUzLLkcRTqVhP8smnvsCK6GFpd94Nziq1XVPD7YpFeho0k5BzbBiNZF7V/DpkJ967A==} + '@vueuse/components@10.11.0': + resolution: {integrity: sha512-ZvLZI23d5ZAtva5fGyYh/jQtZO8l+zJ5tAXyYNqHJZkq1o5yWyqZhENvSv5mfDmN5IuAOp4tq02mRmX/ipFGcg==} + + '@vueuse/core@10.11.0': + resolution: {integrity: sha512-x3sD4Mkm7PJ+pcq3HX8PLPBadXCAlSDR/waK87dz0gQE+qJnaaFhc/dZVfJz+IUYzTMVGum2QlR7ImiJQN4s6g==} '@vueuse/core@10.9.0': resolution: {integrity: sha512-/1vjTol8SXnx6xewDEKfS0Ra//ncg4Hb0DaZiwKf7drgfMsKFExQ+FnnENcN6efPen+1kIzhLQoGSy0eDUVOMg==} @@ -1669,6 +1675,9 @@ packages: universal-cookie: optional: true + '@vueuse/metadata@10.11.0': + resolution: {integrity: sha512-kQX7l6l8dVWNqlqyN3ePW3KmjCQO3ZMgXuBMddIu83CmucrsBfXlH+JoviYyRBws/yLTQO8g3Pbw+bdIoVm4oQ==} + '@vueuse/metadata@10.9.0': resolution: {integrity: sha512-iddNbg3yZM0X7qFY2sAotomgdHK7YJ6sKUvQqbvwnf7TmaVPxS4EJydcNsVejNdS8iWCtDk+fYXr7E32nyTnGA==} @@ -1677,6 +1686,9 @@ packages: peerDependencies: nuxt: ^3.0.0 + '@vueuse/shared@10.11.0': + resolution: {integrity: sha512-fyNoIXEq3PfX1L3NkNhtVQUSRtqYwJtJg+Bp9rIzculIZWHTkKSysujrOk2J+NrRulLTQH9+3gGSfYLWSEWU1A==} + '@vueuse/shared@10.9.0': resolution: {integrity: sha512-Uud2IWncmAfJvRaFYzv5OHDli+FbOzxiVEQdLCKQKLyhz94PIyFC3CHcH7EDMwIn8NPtD06+PNbC/PiO0LGLtw==} @@ -5261,6 +5273,17 @@ packages: '@vue/composition-api': optional: true + vue-demi@0.14.8: + resolution: {integrity: sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==} + engines: {node: '>=12'} + hasBin: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + vue-devtools-stub@0.1.0: resolution: {integrity: sha512-RutnB7X8c5hjq39NceArgXg28WZtZpGc3+J16ljMiYnFhKvd8hITxSWQSQ5bvldxMDU6gG5mkxl1MTQLXckVSQ==} @@ -7423,7 +7446,7 @@ snapshots: dependencies: '@unocss/reset': 0.60.0 '@vue/devtools-shared': 7.1.3 - '@vueuse/components': 10.9.0(vue@3.4.27(typescript@5.4.5)) + '@vueuse/components': 10.11.0(vue@3.4.27(typescript@5.4.5)) '@vueuse/core': 10.9.0(vue@3.4.27(typescript@5.4.5)) '@vueuse/integrations': 10.9.0(focus-trap@7.5.4)(vue@3.4.27(typescript@5.4.5)) colord: 2.9.3 @@ -7480,11 +7503,21 @@ snapshots: '@vue/shared@3.4.27': {} - '@vueuse/components@10.9.0(vue@3.4.27(typescript@5.4.5))': + '@vueuse/components@10.11.0(vue@3.4.27(typescript@5.4.5))': dependencies: - '@vueuse/core': 10.9.0(vue@3.4.27(typescript@5.4.5)) - '@vueuse/shared': 10.9.0(vue@3.4.27(typescript@5.4.5)) - vue-demi: 0.14.7(vue@3.4.27(typescript@5.4.5)) + '@vueuse/core': 10.11.0(vue@3.4.27(typescript@5.4.5)) + '@vueuse/shared': 10.11.0(vue@3.4.27(typescript@5.4.5)) + vue-demi: 0.14.8(vue@3.4.27(typescript@5.4.5)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vueuse/core@10.11.0(vue@3.4.27(typescript@5.4.5))': + dependencies: + '@types/web-bluetooth': 0.0.20 + '@vueuse/metadata': 10.11.0 + '@vueuse/shared': 10.11.0(vue@3.4.27(typescript@5.4.5)) + vue-demi: 0.14.8(vue@3.4.27(typescript@5.4.5)) transitivePeerDependencies: - '@vue/composition-api' - vue @@ -7510,6 +7543,8 @@ snapshots: - '@vue/composition-api' - vue + '@vueuse/metadata@10.11.0': {} + '@vueuse/metadata@10.9.0': {} '@vueuse/nuxt@10.9.0(nuxt@3.11.2(@opentelemetry/api@1.8.0)(@parcel/watcher@2.4.1)(@types/node@20.12.11)(@unocss/reset@0.60.0)(encoding@0.1.13)(eslint@8.57.0)(floating-vue@5.2.2(@nuxt/kit@3.11.2(rollup@4.17.2))(vue@3.4.27(typescript@5.4.5)))(ioredis@5.4.1)(optionator@0.9.4)(rollup@4.17.2)(terser@5.31.0)(typescript@5.4.5)(unocss@0.60.0(@unocss/webpack@0.60.0(rollup@4.17.2)(webpack@5.88.2(esbuild@0.20.2)))(postcss@8.4.38)(rollup@4.17.2)(vite@5.2.11(@types/node@20.12.11)(terser@5.31.0)))(vite@5.2.11(@types/node@20.12.11)(terser@5.31.0))(vue-tsc@2.0.17(typescript@5.4.5)))(rollup@4.17.2)(vue@3.4.27(typescript@5.4.5))': @@ -7526,6 +7561,13 @@ snapshots: - supports-color - vue + '@vueuse/shared@10.11.0(vue@3.4.27(typescript@5.4.5))': + dependencies: + vue-demi: 0.14.8(vue@3.4.27(typescript@5.4.5)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + '@vueuse/shared@10.9.0(vue@3.4.27(typescript@5.4.5))': dependencies: vue-demi: 0.14.7(vue@3.4.27(typescript@5.4.5)) @@ -11645,6 +11687,10 @@ snapshots: dependencies: vue: 3.4.27(typescript@5.4.5) + vue-demi@0.14.8(vue@3.4.27(typescript@5.4.5)): + dependencies: + vue: 3.4.27(typescript@5.4.5) + vue-devtools-stub@0.1.0: {} vue-eslint-parser@9.4.2(eslint@8.57.0): diff --git a/server/api/v1/llm/voice/text-to-speech.ts b/server/api/v1/llm/voice/text-to-speech.ts index 90380d06f..1ffaf2c31 100644 --- a/server/api/v1/llm/voice/text-to-speech.ts +++ b/server/api/v1/llm/voice/text-to-speech.ts @@ -7,6 +7,8 @@ export default defineEventHandler(async (event) => { }) const res = await client.generate({ + // voice: 'ShanShan', + // Quite good for English voice: 'Myriam', // Beatrice is not 'childish' like the others // voice: 'Beatrice', diff --git a/stores/audio.ts b/stores/audio.ts index be6246f04..75e178873 100644 --- a/stores/audio.ts +++ b/stores/audio.ts @@ -13,9 +13,11 @@ function calculateVolumeWithLinearNormalize(analyser: AnalyserNode) { // We can apply a power function to amplify the volume, for example // v ** 1.2 will amplify the volume by 1.2 times .map(v => v ** 1.2) + // Scale up the volume values to make them more distinguishable + .map(v => v * 1.2) .reduce((acc, cur) => acc + cur, 0) - console.log('volumeSum linear', volumeSum) + // console.log('volumeSum linear', volumeSum, (volumeSum / dataBuffer.length / 100)) return (volumeSum / dataBuffer.length / 100) } @@ -49,7 +51,7 @@ function calculateVolumeWithMinMaxNormalize(analyser: AnalyserNode) { // Aggregate the volume values const volumeSum = normalizedVolumeVector.reduce((acc, cur) => acc + cur, 0) - console.log('volumeSum minmax', volumeSum) + // console.log('volumeSum minmax', volumeSum) // Average the volume values return volumeSum / dataBuffer.length diff --git a/stores/llm.ts b/stores/llm.ts index 968439626..ed63c2bf8 100644 --- a/stores/llm.ts +++ b/stores/llm.ts @@ -36,6 +36,9 @@ export const useLLM = defineStore('llm', () => { } async function streamSpeech(text: string) { + if (!text || !text.trim()) + throw new Error('Text is required') + return await ofetch('/api/v1/llm/voice/text-to-speech', { body: { text,