fix(stage-*): model not loading when entering main stage
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { Screen } from '@proj-airi/stage-ui/components'
|
||||
import { Live2DCanvas, Live2DModel } from '@proj-airi/stage-ui/components/scenes'
|
||||
import { useSettings } from '@proj-airi/stage-ui/stores/settings'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
const live2dCanvasRef = ref<InstanceType<typeof Live2DCanvas>>()
|
||||
const live2dModelRef = ref<InstanceType<typeof Live2DModel>>()
|
||||
|
||||
const settingsStore = useSettings()
|
||||
const { stageModelSelectedUrl } = storeToRefs(settingsStore)
|
||||
const motion = ref<string>('idle')
|
||||
const motionGroupsList = ref<{
|
||||
motionName: string
|
||||
@@ -61,6 +65,7 @@ function handleShot() {
|
||||
>
|
||||
<Live2DModel
|
||||
ref="live2dModelRef"
|
||||
:model-src="stageModelSelectedUrl"
|
||||
:app="app"
|
||||
:width="width"
|
||||
:height="height"
|
||||
|
||||
@@ -13,7 +13,10 @@ import ModelManagerDialog from '../../Dialogs/model-selector/model-selector-dial
|
||||
import Live2D from './Live2D.vue'
|
||||
import VRM from './VRM.vue'
|
||||
|
||||
import { DisplayModelFormat } from '../../../../stores/display-models'
|
||||
import { useLive2d } from '../../../../stores/live2d'
|
||||
import { useSettings } from '../../../../stores/settings'
|
||||
import { useVRM } from '../../../../stores/vrm'
|
||||
|
||||
const props = defineProps<{
|
||||
palette: string[]
|
||||
@@ -33,9 +36,20 @@ const positionCursor = useMouse()
|
||||
const settingsStore = useSettings()
|
||||
const { live2dDisableFocus, stageModelSelectedUrl, stageModelSelected, stageModelRenderer } = storeToRefs(settingsStore)
|
||||
|
||||
watch(selectedModel, () => {
|
||||
watch(selectedModel, async () => {
|
||||
stageModelSelected.value = selectedModel.value?.id
|
||||
settingsStore.updateStageModel()
|
||||
await settingsStore.updateStageModel()
|
||||
|
||||
if (selectedModel.value) {
|
||||
switch (selectedModel.value.format) {
|
||||
case DisplayModelFormat.Live2dZip:
|
||||
useLive2d().shouldUpdateView()
|
||||
break
|
||||
case DisplayModelFormat.VRM:
|
||||
useVRM().shouldUpdateView()
|
||||
break
|
||||
}
|
||||
}
|
||||
}, { deep: true })
|
||||
</script>
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import { useLive2d } from '../../stores/live2d'
|
||||
import { useSpeechStore } from '../../stores/modules/speech'
|
||||
import { useProvidersStore } from '../../stores/providers'
|
||||
import { useSettings } from '../../stores/settings'
|
||||
import { useVRM } from '../../stores/vrm'
|
||||
|
||||
withDefaults(defineProps<{
|
||||
paused?: boolean
|
||||
@@ -41,12 +42,34 @@ const db = ref<DuckDBWasmDrizzleDatabase>()
|
||||
|
||||
const vrmViewerRef = ref<{ setExpression: (expression: string) => void }>()
|
||||
|
||||
const { stageModelRenderer, stageViewControlsEnabled, live2dDisableFocus, stageModelSelectedUrl } = storeToRefs(useSettings())
|
||||
const settingsStore = useSettings()
|
||||
const { stageModelRenderer, stageViewControlsEnabled, live2dDisableFocus, stageModelSelectedUrl } = storeToRefs(settingsStore)
|
||||
const { mouthOpenSize } = storeToRefs(useSpeakingStore())
|
||||
const { audioContext, calculateVolume } = useAudioContext()
|
||||
const { onBeforeMessageComposed, onBeforeSend, onTokenLiteral, onTokenSpecial, onStreamEnd, onAssistantResponseEnd } = useChatStore()
|
||||
const providersStore = useProvidersStore()
|
||||
|
||||
const live2dStore = useLive2d()
|
||||
const vrmStore = useVRM()
|
||||
|
||||
const showStage = ref(true)
|
||||
|
||||
live2dStore.onShouldUpdateView(async () => {
|
||||
showStage.value = false
|
||||
await settingsStore.updateStageModel()
|
||||
setTimeout(() => {
|
||||
showStage.value = true
|
||||
}, 100)
|
||||
})
|
||||
|
||||
vrmStore.onShouldUpdateView(async () => {
|
||||
showStage.value = false
|
||||
await settingsStore.updateStageModel()
|
||||
setTimeout(() => {
|
||||
showStage.value = true
|
||||
}, 100)
|
||||
})
|
||||
|
||||
const audioAnalyser = ref<AnalyserNode>()
|
||||
const nowSpeaking = ref(false)
|
||||
const lipSyncStarted = ref(false)
|
||||
@@ -229,7 +252,7 @@ onMounted(async () => {
|
||||
<div relative>
|
||||
<div h-full w-full>
|
||||
<Live2DScene
|
||||
v-if="stageModelRenderer === 'live2d'"
|
||||
v-if="stageModelRenderer === 'live2d' && showStage"
|
||||
min-w="50% <lg:full" min-h="100 sm:100" h-full w-full flex-1
|
||||
:model-src="stageModelSelectedUrl"
|
||||
:focus-at="focusAt"
|
||||
@@ -241,7 +264,7 @@ onMounted(async () => {
|
||||
:disable-focus-at="live2dDisableFocus"
|
||||
/>
|
||||
<VRMScene
|
||||
v-else-if="stageModelRenderer === 'vrm'"
|
||||
v-if="stageModelRenderer === 'vrm' && showStage"
|
||||
ref="vrmViewerRef"
|
||||
:model-src="stageModelSelectedUrl"
|
||||
idle-animation="/assets/vrm/animations/idle_loop.vrma"
|
||||
|
||||
@@ -1,252 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { FieldRange } from '@proj-airi/ui'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import PoppinMarkdown from './PoppinMarkdown.web.vue'
|
||||
|
||||
import {
|
||||
createCutePopupAnimator,
|
||||
createFadeAnimator,
|
||||
createFloatAnimator,
|
||||
createPopupAnimator,
|
||||
createStackAnimator,
|
||||
} from './animators'
|
||||
|
||||
const duration = ref(750)
|
||||
|
||||
// Sample markdown content
|
||||
const markdownText = ref(`# Hello World
|
||||
|
||||
This is **bold text** and *italic text*.
|
||||
|
||||
Here's a list:
|
||||
- Item 1
|
||||
- Item 2
|
||||
- Item 3
|
||||
|
||||
\`\`\`javascript
|
||||
console.log('Hello, world!');
|
||||
\`\`\`
|
||||
|
||||
> This is a blockquote with some **bold** content.
|
||||
|
||||
And here's a [link](https://example.com) and some inline \`code\`.`)
|
||||
|
||||
const chatMarkdown = ref(`Hi! I can help you with **markdown**. Here are some examples:
|
||||
|
||||
- *Italics* and **bold** text
|
||||
- \`inline code\` snippets
|
||||
- Even [links](https://example.com)
|
||||
|
||||
\`\`\`typescript
|
||||
function greet(name: string) {
|
||||
console.log(\`Hello, \${name}!\`);
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
What would you like to know?`)
|
||||
|
||||
function createMarkdownStream(text: string) {
|
||||
return new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
const bytes = new TextEncoder().encode(text)
|
||||
let index = 0
|
||||
const interval = setInterval(() => {
|
||||
if (index < bytes.length) {
|
||||
controller.enqueue(bytes.subarray(index, index + 1))
|
||||
index++
|
||||
}
|
||||
else {
|
||||
clearInterval(interval)
|
||||
controller.close()
|
||||
}
|
||||
}, 30 + 50 * Math.random()) // Faster for better demo
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const streamingMarkdown = createMarkdownStream(`# Streaming Markdown Demo
|
||||
|
||||
This text is being **streamed** character by character!
|
||||
|
||||
## Features
|
||||
|
||||
- Real-time *markdown* rendering
|
||||
- **Character-by-character** animation
|
||||
- Support for \`code\` and other elements
|
||||
|
||||
\`\`\`javascript
|
||||
// Code blocks appear as complete units
|
||||
const message = "Hello from streaming markdown!";
|
||||
console.log(message);
|
||||
\`\`\`
|
||||
|
||||
> Blockquotes work too with **mixed** formatting.
|
||||
|
||||
Pretty cool, right?`)
|
||||
|
||||
const chatStreamingMarkdown = createMarkdownStream(`🤖 **AI Assistant**: Hello! I can help you with various tasks.
|
||||
|
||||
Here's what I can do:
|
||||
- Answer questions about **programming**
|
||||
- Help with \`code debugging\`
|
||||
- Explain *technical concepts*
|
||||
|
||||
\`\`\`python
|
||||
# Example: Python function
|
||||
def greet_user(name):
|
||||
return f"Hello, {name}! How can I help?"
|
||||
|
||||
print(greet_user("Developer"))
|
||||
\`\`\`
|
||||
|
||||
> 💡 **Tip**: I stream my responses in real-time for a better experience!
|
||||
|
||||
What would you like to work on today?`)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Story
|
||||
title="PoppinMarkdown"
|
||||
group="widgets"
|
||||
:layout="{ type: 'grid', width: '100%' }"
|
||||
>
|
||||
<template #controls>
|
||||
<div px-2>
|
||||
<FieldRange
|
||||
v-model.number="duration"
|
||||
:min="50"
|
||||
:max="3000"
|
||||
:step="50"
|
||||
label="Animation Duration"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<Variant
|
||||
id="static-markdown"
|
||||
title="Static Markdown"
|
||||
>
|
||||
<div h-auto max-w-2xl w-full p-4>
|
||||
<PoppinMarkdown
|
||||
:text="markdownText"
|
||||
:animator="createFadeAnimator({ duration })"
|
||||
class="prose-sm prose dark:prose-invert"
|
||||
/>
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant
|
||||
id="streaming-markdown"
|
||||
title="Streaming Markdown"
|
||||
>
|
||||
<div h-auto max-w-2xl w-full p-4>
|
||||
<PoppinMarkdown
|
||||
:text="streamingMarkdown"
|
||||
:animator="createFadeAnimator({ duration: 100 })"
|
||||
class="prose-sm prose dark:prose-invert"
|
||||
/>
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant
|
||||
id="chat-style"
|
||||
title="Chat Style"
|
||||
>
|
||||
<div h-auto max-w-2xl w-full p-4>
|
||||
<div
|
||||
class="border-2 border-primary-200/50 rounded-lg border-solid bg-primary-50/50 p-4 dark:border-primary-500/50 dark:bg-primary-950/70"
|
||||
>
|
||||
<PoppinMarkdown
|
||||
:text="chatMarkdown"
|
||||
:animator="createFadeAnimator({ duration: 150 })"
|
||||
class="prose-sm prose prose-primary dark:prose-invert"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant
|
||||
id="streaming-chat"
|
||||
title="Streaming Chat Response"
|
||||
>
|
||||
<div h-auto max-w-2xl w-full p-4>
|
||||
<div
|
||||
class="border-2 border-primary-200/50 rounded-lg border-solid bg-primary-50/50 p-4 dark:border-primary-500/50 dark:bg-primary-950/70"
|
||||
>
|
||||
<PoppinMarkdown
|
||||
:text="chatStreamingMarkdown"
|
||||
:animator="createFadeAnimator({ duration: 80 })"
|
||||
class="prose-sm prose prose-primary dark:prose-invert"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant
|
||||
id="popup-animation"
|
||||
title="Popup Animation"
|
||||
>
|
||||
<div h-auto max-w-2xl w-full p-4>
|
||||
<PoppinMarkdown
|
||||
:text="markdownText"
|
||||
:animator="createPopupAnimator({ duration })"
|
||||
class="prose-sm prose dark:prose-invert"
|
||||
/>
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant
|
||||
id="float-animation"
|
||||
title="Float Animation"
|
||||
>
|
||||
<div h-auto max-w-2xl w-full p-4>
|
||||
<PoppinMarkdown
|
||||
:text="markdownText"
|
||||
:animator="createFloatAnimator({ duration })"
|
||||
class="prose-sm prose dark:prose-invert"
|
||||
/>
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant
|
||||
id="cute-popup"
|
||||
title="Cute Popup"
|
||||
>
|
||||
<div h-auto max-w-2xl w-full p-4>
|
||||
<PoppinMarkdown
|
||||
:text="chatMarkdown"
|
||||
:animator="createCutePopupAnimator({ duration })"
|
||||
class="prose-sm prose dark:prose-invert"
|
||||
/>
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant
|
||||
id="stack-animation"
|
||||
title="Stack Animation"
|
||||
>
|
||||
<div h-auto max-w-2xl w-full p-4>
|
||||
<PoppinMarkdown
|
||||
:text="markdownText"
|
||||
:animator="createStackAnimator({ duration })"
|
||||
class="prose-sm prose dark:prose-invert"
|
||||
/>
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant
|
||||
id="no-markdown"
|
||||
title="Plain Text Mode"
|
||||
>
|
||||
<div h-auto max-w-2xl w-full p-4>
|
||||
<PoppinMarkdown
|
||||
:text="markdownText"
|
||||
:markdown="false"
|
||||
:animator="createFadeAnimator({ duration })"
|
||||
class="text-sm font-mono"
|
||||
/>
|
||||
</div>
|
||||
</Variant>
|
||||
</Story>
|
||||
</template>
|
||||
@@ -1,212 +0,0 @@
|
||||
<!-- PoppinMarkdown - PoppinText with markdown support -->
|
||||
<script setup lang="ts">
|
||||
import type { Element, Root } from 'hast'
|
||||
|
||||
import type { Animator } from './animators'
|
||||
|
||||
import RemarkParse from 'remark-parse'
|
||||
import RemarkRehype from 'remark-rehype'
|
||||
|
||||
import { readGraphemeClusters } from 'clustr'
|
||||
import { unified } from 'unified'
|
||||
import { computed, defineComponent, h, ref, shallowRef, watch } from 'vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
/**
|
||||
* A plain string or a ReadableStream of bytes from text in UTF-8 encoding.
|
||||
* If a stream is provided, the stream **SHOULD NOT** be reused.
|
||||
*/
|
||||
text?: string | ReadableStream<Uint8Array>
|
||||
textClass?: string | string[]
|
||||
animator?: Animator
|
||||
/**
|
||||
* Whether to process the accumulated text as markdown
|
||||
* @default true
|
||||
*/
|
||||
markdown?: boolean
|
||||
}>(), {
|
||||
markdown: true,
|
||||
})
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'textSplit', grapheme: string): void
|
||||
(e: 'markdownUpdated', html: string): void
|
||||
}>()
|
||||
|
||||
const targets = ref<string[]>([])
|
||||
const abortController = shallowRef<AbortController>()
|
||||
const segmenter = new Intl.Segmenter('und', { granularity: 'grapheme' })
|
||||
|
||||
// Accumulate all text content
|
||||
const accumulatedText = ref('')
|
||||
const animatedCharCount = ref(0)
|
||||
|
||||
// Create markdown processor
|
||||
const processor = unified()
|
||||
.use(RemarkParse)
|
||||
.use(RemarkRehype)
|
||||
|
||||
// Convert HAST to Vue render function
|
||||
function hastToVNode(node: any, animator?: Animator): any {
|
||||
if (node.type === 'text') {
|
||||
// Text node - split into animated characters
|
||||
const textContent = node.value.trim()
|
||||
if (!textContent)
|
||||
return null
|
||||
|
||||
const characters = [...segmenter.segment(textContent)].map(seg => seg.segment)
|
||||
const currentAnimatedCount = animatedCharCount.value
|
||||
|
||||
return characters.map((char, index) => {
|
||||
const globalIndex = currentAnimatedCount + index
|
||||
|
||||
return h('span', {
|
||||
key: `char-${globalIndex}`,
|
||||
class: 'inline-block poppin-char',
|
||||
onVnodeMounted: (vnode: any) => {
|
||||
if (animator && vnode.el) {
|
||||
setTimeout(() => animator([vnode.el]), 0)
|
||||
}
|
||||
},
|
||||
}, char)
|
||||
})
|
||||
}
|
||||
|
||||
if (node.type === 'element') {
|
||||
const element = node as Element
|
||||
const { tagName, properties = {}, children = [] } = element
|
||||
|
||||
// Check if this is a non-text element that should animate as a unit
|
||||
const isNonTextElement = ['img', 'code', 'pre', 'hr', 'br'].includes(tagName)
|
||||
|| (properties.className
|
||||
&& (properties.className as string[]).includes('code-block'))
|
||||
|
||||
if (isNonTextElement) {
|
||||
return h(tagName, {
|
||||
...properties,
|
||||
key: `element-${tagName}-${animatedCharCount.value}`,
|
||||
class: [properties.className, 'poppin-element'],
|
||||
onVnodeMounted: (vnode: any) => {
|
||||
if (animator && vnode.el) {
|
||||
setTimeout(() => animator([vnode.el]), 0)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Regular element - recursively process children
|
||||
const childVNodes = children
|
||||
.map((child: any) => hastToVNode(child, animator))
|
||||
.flat()
|
||||
.filter(Boolean)
|
||||
|
||||
return h(tagName, properties, childVNodes)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// Parse markdown to HAST and create Vue component
|
||||
const MarkdownComponent = computed(() => {
|
||||
if (!props.markdown || !accumulatedText.value) {
|
||||
// Fallback to character animation
|
||||
// eslint-disable-next-line vue/one-component-per-file
|
||||
return defineComponent({
|
||||
setup() {
|
||||
return () => targets.value.map((char, index) =>
|
||||
h('span', {
|
||||
key: `fallback-${index}`,
|
||||
class: 'inline-block color-primary-400 dark:color-primary-100',
|
||||
onVnodeMounted: (vnode: any) => {
|
||||
if (props.animator && vnode.el) {
|
||||
setTimeout(() => props.animator?.([vnode.el]), 0)
|
||||
}
|
||||
},
|
||||
}, char),
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const ast = processor.parse(accumulatedText.value)
|
||||
const hast = processor.runSync(ast) as Root
|
||||
|
||||
// eslint-disable-next-line vue/one-component-per-file
|
||||
return defineComponent({
|
||||
setup() {
|
||||
return () => h('div', {
|
||||
class: ['markdown-content', props.textClass].flat().filter(Boolean),
|
||||
}, hast.children.map((child: any) => hastToVNode(child, props.animator)))
|
||||
},
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
console.warn('Markdown parsing failed:', error)
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
// Update character count when streaming
|
||||
watch(accumulatedText, (newText, oldText) => {
|
||||
if (newText.length > (oldText?.length || 0)) {
|
||||
// Only update count when new characters are added
|
||||
const addedChars = newText.slice(oldText?.length || 0)
|
||||
const addedCharCount = [...segmenter.segment(addedChars)].length
|
||||
animatedCharCount.value += addedCharCount
|
||||
}
|
||||
else if (newText.length < (oldText?.length || 0)) {
|
||||
// Text was reset, reset count
|
||||
animatedCharCount.value = 0
|
||||
}
|
||||
|
||||
emits('markdownUpdated', newText)
|
||||
})
|
||||
|
||||
// Reset animation count when text prop changes
|
||||
watch(() => props.text, () => {
|
||||
animatedCharCount.value = 0
|
||||
}, { flush: 'pre' })
|
||||
|
||||
watch(() => props.text, async (text) => {
|
||||
if (!text) {
|
||||
accumulatedText.value = ''
|
||||
targets.value = []
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof text === 'string') {
|
||||
accumulatedText.value = text
|
||||
targets.value = [...segmenter.segment(text)].map(seg => seg.segment)
|
||||
}
|
||||
else {
|
||||
abortController.value?.abort()
|
||||
abortController.value = new AbortController()
|
||||
try {
|
||||
targets.value = []
|
||||
accumulatedText.value = ''
|
||||
|
||||
for await (const cluster of readGraphemeClusters(text.getReader(), { signal: abortController.value.signal })) {
|
||||
accumulatedText.value += cluster
|
||||
targets.value.push(cluster)
|
||||
emits('textSplit', cluster)
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error && error.message === 'Aborted') {
|
||||
console.warn('Text reading aborted')
|
||||
}
|
||||
else {
|
||||
console.error('Error reading text:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, { immediate: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component
|
||||
:is="MarkdownComponent"
|
||||
:key="accumulatedText.length"
|
||||
/>
|
||||
</template>
|
||||
@@ -39,8 +39,8 @@ export interface DisplayModelURL {
|
||||
}
|
||||
|
||||
const displayModelsPresets: DisplayModel[] = [
|
||||
{ id: 'preset-live2d-1', format: DisplayModelFormat.Live2dZip, type: 'url', url: '/assets/live2d/models/hiyori_pro_zh.zip', name: 'Hiyori (Pro)', previewImage: '/assets/live2d/models/hiyori_free_zh/avatar.png', importedAt: 1733113886840 },
|
||||
{ id: 'preset-live2d-2', format: DisplayModelFormat.Live2dZip, type: 'url', url: '/assets/live2d/models/hiyori_free_zh.zip', name: 'Hiyori (Free)', previewImage: '/assets/live2d/models/hiyori_free_zh/avatar.png', importedAt: 1733113886840 },
|
||||
{ id: 'preset-live2d-1', format: DisplayModelFormat.Live2dZip, type: 'url', url: '/assets/live2d/models/hiyori_pro_zh.zip', name: 'Hiyori (Pro)', previewImage: '/assets/live2d/models/hiyori_pro_zh/avatar.png', importedAt: 1733113886840 },
|
||||
{ id: 'preset-live2d-2', format: DisplayModelFormat.Live2dZip, type: 'url', url: '/assets/live2d/models/hiyori_free_zh.zip', name: 'Hiyori (Free)', previewImage: '/assets/live2d/models/hiyori_pro_zh/avatar.png', importedAt: 1733113886840 },
|
||||
]
|
||||
|
||||
export const useDisplayModelsStore = defineStore('display-models', () => {
|
||||
|
||||
@@ -35,7 +35,7 @@ export const useSettings = defineStore('settings', () => {
|
||||
|
||||
const language = useLocalStorage('settings/language', '')
|
||||
|
||||
const stageModelSelected = useLocalStorage<string | undefined>('settings/stage/model', undefined)
|
||||
const stageModelSelected = useLocalStorage<string | undefined>('settings/stage/model', 'preset-live2d-1')
|
||||
const stageModelSelectedDisplayModel = ref<DisplayModel | undefined>()
|
||||
const stageModelSelectedUrl = ref<string>()
|
||||
const stageModelRenderer = ref<'live2d' | 'vrm' | 'disabled'>()
|
||||
@@ -56,17 +56,6 @@ export const useSettings = defineStore('settings', () => {
|
||||
return
|
||||
}
|
||||
|
||||
if (model.type === 'file') {
|
||||
if (stageModelSelectedUrl.value) {
|
||||
URL.revokeObjectURL(stageModelSelectedUrl.value)
|
||||
}
|
||||
|
||||
stageModelSelectedUrl.value = URL.createObjectURL(model.file)
|
||||
}
|
||||
else {
|
||||
stageModelSelectedUrl.value = model.url
|
||||
}
|
||||
|
||||
switch (model.format) {
|
||||
case DisplayModelFormat.Live2dZip:
|
||||
stageModelRenderer.value = 'live2d'
|
||||
@@ -79,6 +68,17 @@ export const useSettings = defineStore('settings', () => {
|
||||
break
|
||||
}
|
||||
|
||||
if (model.type === 'file') {
|
||||
if (stageModelSelectedUrl.value) {
|
||||
URL.revokeObjectURL(stageModelSelectedUrl.value)
|
||||
}
|
||||
|
||||
stageModelSelectedUrl.value = URL.createObjectURL(model.file)
|
||||
}
|
||||
else {
|
||||
stageModelSelectedUrl.value = model.url
|
||||
}
|
||||
|
||||
stageModelSelectedDisplayModel.value = model
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user