diff --git a/packages/stage-ui/src/components/Scenes/Live2D/Model.vue b/packages/stage-ui/src/components/Scenes/Live2D/Model.vue
index a93aa2792..3537590fb 100644
--- a/packages/stage-ui/src/components/Scenes/Live2D/Model.vue
+++ b/packages/stage-ui/src/components/Scenes/Live2D/Model.vue
@@ -14,7 +14,7 @@ import { Live2DFactory, Live2DModel, MotionPriority } from 'pixi-live2d-display/
import { computed, onMounted, onUnmounted, ref, shallowRef, toRef, watch } from 'vue'
import { useLive2DIdleEyeFocus } from '../../../composables/live2d'
-import { useSettings } from '../../../stores'
+import { useLive2d, useSettings } from '../../../stores'
const props = withDefaults(defineProps<{
app?: Application
@@ -101,16 +101,20 @@ function setScaleAndPosition() {
}
const {
- live2dModelFile,
- loadingLive2dModel,
- live2dCurrentMotion,
- availableLive2dMotions,
- live2dLoadSource,
- live2dModelUrl,
+ modelFile,
+ loadingModel,
+ currentMotion,
+ availableMotions,
+ loadSource,
+ modelUrl,
+} = storeToRefs(useLive2d())
+
+const {
themeColorsHue,
themeColorsHueDynamic,
} = storeToRefs(useSettings())
-const currentMotion = ref<{ group: string, index: number }>({ group: 'Idle', index: 0 })
+
+const localCurrentMotion = ref<{ group: string, index: number }>({ group: 'Idle', index: 0 })
async function loadModel() {
if (!pixiApp.value)
@@ -124,11 +128,11 @@ async function loadModel() {
const modelInstance = new Live2DModel()
- if (live2dLoadSource.value === 'file') {
- await Live2DFactory.setupLive2DModel(modelInstance, [live2dModelFile.value], { autoInteract: false })
+ if (loadSource.value === 'file') {
+ await Live2DFactory.setupLive2DModel(modelInstance, [modelFile.value], { autoInteract: false })
}
- else if (live2dLoadSource.value === 'url') {
- await Live2DFactory.setupLive2DModel(modelInstance, live2dModelUrl.value, { autoInteract: false })
+ else if (loadSource.value === 'url') {
+ await Live2DFactory.setupLive2DModel(modelInstance, modelUrl.value, { autoInteract: false })
}
model.value = modelInstance
@@ -149,7 +153,7 @@ async function loadModel() {
const motionManager = internalModel.motionManager
coreModel.setParameterValueById('ParamMouthOpenY', mouthOpenSize.value)
- availableLive2dMotions.value = Object.entries(motionManager.definitions).flatMap(([motionName, definition]) => {
+ availableMotions.value = Object.entries(motionManager.definitions).flatMap(([motionName, definition]) => {
if (!definition)
return []
@@ -187,16 +191,16 @@ async function loadModel() {
}
motionManager.on('motionStart', (group, index) => {
- currentMotion.value = { group, index }
+ localCurrentMotion.value = { group, index }
})
// save to indexdb
- if (live2dModelFile.value) {
- await localforage.setItem('live2dModel', live2dModelFile.value)
+ if (modelFile.value) {
+ await localforage.setItem('live2dModel', modelFile.value)
}
emits('modelLoaded')
- loadingLive2dModel.value = false
+ loadingModel.value = false
}
async function initLive2DPixiStage() {
@@ -211,19 +215,19 @@ async function initLive2DPixiStage() {
// load indexdb model first
const live2dModelFromIndexedDB = await localforage.getItem
('live2dModel')
if (live2dModelFromIndexedDB) {
- live2dModelFile.value = live2dModelFromIndexedDB
- live2dLoadSource.value = 'file'
- loadingLive2dModel.value = true
+ modelFile.value = live2dModelFromIndexedDB
+ loadSource.value = 'file'
+ loadingModel.value = true
return
}
- if (live2dModelUrl.value) {
- live2dLoadSource.value = 'url'
- loadingLive2dModel.value = true
+ if (modelUrl.value) {
+ loadSource.value = 'url'
+ loadingModel.value = true
return
}
- loadingLive2dModel.value = false
+ loadingModel.value = false
}
async function setMotion(motionName: string, index?: number) {
@@ -268,7 +272,7 @@ watch(themeColorsHueDynamic, () => {
watch(mouthOpenSize, value => getCoreModel().setParameterValueById('ParamMouthOpenY', value))
watch(pixiApp, initLive2DPixiStage)
-watch(live2dCurrentMotion, value => setMotion(value.group, value.index))
+watch(currentMotion, value => setMotion(value.group, value.index))
watch(paused, value => value ? pixiApp.value?.stop() : pixiApp.value?.start())
watch(focusAt, (value) => {
@@ -280,7 +284,7 @@ watch(focusAt, (value) => {
model.value.focus(value.x, value.y)
})
-watchDebounced(loadingLive2dModel, (value) => {
+watchDebounced(loadingModel, (value) => {
if (!value)
return
@@ -294,7 +298,7 @@ onUnmounted(() => {
})
function listMotionGroups() {
- return availableLive2dMotions.value
+ return availableMotions.value
}
defineExpose({
diff --git a/packages/stage-ui/src/components/Scenes/Stage.vue b/packages/stage-ui/src/components/Scenes/Stage.vue
index 0d5db0c3a..6a91224c5 100644
--- a/packages/stage-ui/src/components/Scenes/Stage.vue
+++ b/packages/stage-ui/src/components/Scenes/Stage.vue
@@ -21,6 +21,7 @@ import { useQueue } from '../../composables/queue'
import { useDelayMessageQueue, useEmotionsMessageQueue, useMessageContentQueue } from '../../composables/queues'
import { llmInferenceEndToken } from '../../constants'
import { EMOTION_EmotionMotionName_value, EMOTION_VRMExpressionName_value, EmotionThinkMotionName } from '../../constants/emotions'
+import { useLive2d } from '../../stores'
import { useAudioContext, useSpeakingStore } from '../../stores/audio'
import { useChatStore } from '../../stores/chat'
import { useSpeechStore } from '../../stores/modules/speech'
@@ -131,7 +132,7 @@ ttsQueue.on('add', (content) => {
const messageContentQueue = useMessageContentQueue(ttsQueue)
-const { live2dCurrentMotion } = storeToRefs(useSettings())
+const { currentMotion } = storeToRefs(useLive2d())
const emotionsQueue = useQueue({
handlers: [
@@ -144,7 +145,7 @@ const emotionsQueue = useQueue({
await vrmViewerRef.value!.setExpression(value)
}
else if (stageView.value === '2d') {
- live2dCurrentMotion.value = { group: EMOTION_EmotionMotionName_value[ctx.data] }
+ currentMotion.value = { group: EMOTION_EmotionMotionName_value[ctx.data] }
}
},
],
@@ -189,7 +190,7 @@ onBeforeMessageComposed(async () => {
})
onBeforeSend(async () => {
- live2dCurrentMotion.value = { group: EmotionThinkMotionName }
+ currentMotion.value = { group: EmotionThinkMotionName }
})
onTokenLiteral(async (literal) => {
diff --git a/apps/stage-tamagotchi/src/components/Settings/ColorPalette.vue b/packages/stage-ui/src/components/Widgets/Settings/ColorPalette.vue
similarity index 100%
rename from apps/stage-tamagotchi/src/components/Settings/ColorPalette.vue
rename to packages/stage-ui/src/components/Widgets/Settings/ColorPalette.vue
diff --git a/apps/stage-tamagotchi/src/components/Widgets/Live2DSettings.vue b/packages/stage-ui/src/components/Widgets/Settings/Live2DSettings.vue
similarity index 59%
rename from apps/stage-tamagotchi/src/components/Widgets/Live2DSettings.vue
rename to packages/stage-ui/src/components/Widgets/Settings/Live2DSettings.vue
index e871497ed..7a46eb082 100644
--- a/apps/stage-tamagotchi/src/components/Widgets/Live2DSettings.vue
+++ b/packages/stage-ui/src/components/Widgets/Settings/Live2DSettings.vue
@@ -1,19 +1,18 @@
-
@@ -116,61 +125,62 @@ const exportObjectUrl = useObjectUrl(live2dModelFile)
-
-
+
{{ t('settings.live2d.change-model.from-file') }}...
-
+
Extract colors from model
diff --git a/packages/stage-ui/src/components/Widgets/Settings/index.ts b/packages/stage-ui/src/components/Widgets/Settings/index.ts
new file mode 100644
index 000000000..3b876eb53
--- /dev/null
+++ b/packages/stage-ui/src/components/Widgets/Settings/index.ts
@@ -0,0 +1,2 @@
+export { default as ColorPalette } from './ColorPalette.vue'
+export { default as Live2DSettings } from './Live2DSettings.vue'
diff --git a/packages/stage-ui/src/stores/index.ts b/packages/stage-ui/src/stores/index.ts
index bdf4d48ec..6fd0e3dd7 100644
--- a/packages/stage-ui/src/stores/index.ts
+++ b/packages/stage-ui/src/stores/index.ts
@@ -1,5 +1,6 @@
export * from './audio'
export * from './chat'
+export * from './live2d'
export * from './llm'
export * from './mcp'
export * from './modules'
diff --git a/packages/stage-ui/src/stores/live2d.ts b/packages/stage-ui/src/stores/live2d.ts
new file mode 100644
index 000000000..8c69978da
--- /dev/null
+++ b/packages/stage-ui/src/stores/live2d.ts
@@ -0,0 +1,32 @@
+import { useLocalStorage } from '@vueuse/core'
+import { defineStore } from 'pinia'
+import { computed, ref } from 'vue'
+
+export const useLive2d = defineStore('live2d', () => {
+ const modelFile = ref()
+ const modelUrl = ref('/assets/live2d/models/hiyori_pro_zh.zip')
+ const loadSource = ref<'file' | 'url'>('url')
+ const loadingModel = ref(false) // if set to true, the model will be loaded
+ const position = useLocalStorage('settings/live2d/position', { x: 0, y: 0 }) // position is relative to the center of the screen, units are %
+ const positionInPercentageString = computed(() => ({
+ x: `${position.value.x}%`,
+ y: `${position.value.y}%`,
+ }))
+ const currentMotion = ref<{ group: string, index?: number }>({ group: 'Idle', index: 0 })
+ const availableMotions = ref<{ motionName: string, motionIndex: number, fileName: string }[]>([])
+ const motionMap = useLocalStorage>('settings/live2d/motion-map', {})
+ const scale = useLocalStorage('settings/live2d/scale', 1)
+
+ return {
+ modelFile,
+ modelUrl,
+ loadSource,
+ loadingModel,
+ position,
+ positionInPercentageString,
+ currentMotion,
+ availableMotions,
+ motionMap,
+ scale,
+ }
+})
diff --git a/packages/stage-ui/src/stores/settings.ts b/packages/stage-ui/src/stores/settings.ts
index 87f2809bc..880faa69b 100644
--- a/packages/stage-ui/src/stores/settings.ts
+++ b/packages/stage-ui/src/stores/settings.ts
@@ -18,21 +18,6 @@ export const useSettings = defineStore('settings', () => {
const selectedAudioDeviceId = computed(() => selectedAudioDevice.value?.deviceId)
const { audioInputs, ensurePermissions } = useDevicesList({ constraints: { audio: true } })
- // TODO: extract to a separate store, use a single page to do this
- const live2dModelFile = ref()
- const live2dModelUrl = ref('/assets/live2d/models/hiyori_pro_zh.zip')
- const live2dLoadSource = ref<'file' | 'url'>('url')
- const loadingLive2dModel = ref(false) // if set to true, the model will be loaded
- const live2dPosition = useLocalStorage('settings/live2d/position', { x: 0, y: 0 }) // position is relative to the center of the screen, units are %
- const live2dPositionInPercentageString = computed(() => ({
- x: `${live2dPosition.value.x}%`,
- y: `${live2dPosition.value.y}%`,
- }))
- const live2dCurrentMotion = ref<{ group: string, index?: number }>({ group: 'Idle', index: 0 })
- const availableLive2dMotions = ref<{ motionName: string, motionIndex: number, fileName: string }[]>([])
- const live2dMotionMap = useLocalStorage>('settings/live2d/motion-map', {})
- const live2dScale = useLocalStorage('settings/live2d/scale', 1)
-
const disableTransitions = useLocalStorage('settings/disable-transitions', true)
const usePageSpecificTransitions = useLocalStorage('settings/use-page-specific-transitions', true)
@@ -88,16 +73,6 @@ export const useSettings = defineStore('settings', () => {
}, { immediate: true })
return {
- live2dModelFile,
- live2dModelUrl,
- live2dLoadSource,
- live2dCurrentMotion,
- live2dPosition,
- live2dPositionInPercentageString,
- live2dScale,
- availableLive2dMotions,
- live2dMotionMap,
- loadingLive2dModel,
disableTransitions,
usePageSpecificTransitions,
language,