diff --git a/apps/playground-prompt-engineering/src/components/ChatSimulator.vue b/apps/playground-prompt-engineering/src/components/ChatSimulator.vue
index 55184359..5175a362 100644
--- a/apps/playground-prompt-engineering/src/components/ChatSimulator.vue
+++ b/apps/playground-prompt-engineering/src/components/ChatSimulator.vue
@@ -331,6 +331,7 @@ watch(() => messages.value.length, () => {
.line-clamp-2 {
display: -webkit-box;
+ line-clamp: 2;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
diff --git a/apps/stage-web/src/components/Layouts/InteractiveArea.vue b/apps/stage-web/src/components/Layouts/InteractiveArea.vue
index ac805b47..ed4764fb 100644
--- a/apps/stage-web/src/components/Layouts/InteractiveArea.vue
+++ b/apps/stage-web/src/components/Layouts/InteractiveArea.vue
@@ -6,7 +6,7 @@ import WhisperWorker from '@proj-airi/stage-ui/libs/workers/worker?worker&url'
import { toWAVBase64 } from '@proj-airi/audio'
import { useMicVAD, useWhisper } from '@proj-airi/stage-ui/composables'
import { useAudioContext, useChatStore, useConsciousnessStore, useProvidersStore, useSettings } from '@proj-airi/stage-ui/stores'
-import { BasicTextarea, TransitionVertical } from '@proj-airi/ui'
+import { BasicTextarea } from '@proj-airi/ui'
import { useDevicesList } from '@vueuse/core'
import { storeToRefs } from 'pinia'
import { onMounted, ref, watch } from 'vue'
@@ -24,8 +24,8 @@ const providersStore = useProvidersStore()
const { activeProvider, activeModel } = storeToRefs(useConsciousnessStore())
const { themeColorsHueDynamic } = storeToRefs(useSettings())
-const { audioInputs, ensurePermissions } = useDevicesList({ constraints: { audio: true } })
-const { selectedAudioDevice, isAudioInputOn, selectedAudioDeviceId } = storeToRefs(useSettings())
+const { ensurePermissions } = useDevicesList({ constraints: { audio: true } })
+const { isAudioInputOn, selectedAudioDeviceId } = storeToRefs(useSettings())
const { send, onAfterSend, discoverToolsCompatibility } = useChatStore()
const { messages } = storeToRefs(useChatStore())
const { audioContext } = useAudioContext()
@@ -104,17 +104,6 @@ async function handleTranscription(buffer: ArrayBufferLike) {
generate({ type: 'generate', data: { audio: audioBase64, language: 'en' } })
}
-async function handleAudioInputChange(event: Event) {
- const target = event.target as HTMLSelectElement
- const found = audioInputs.value.find(d => d.deviceId === target.value)
- if (!found) {
- selectedAudioDevice.value = undefined
- return
- }
-
- selectedAudioDevice.value = found
-}
-
watch(isAudioInputOn, async (value) => {
if (value === 'false') {
destroy()
@@ -195,46 +184,5 @@ onAfterSend(async () => {
-
-
-
-
-
-
-
-
diff --git a/apps/stage-web/src/components/Layouts/InteractiveArea/Actions/ViewControls.vue b/apps/stage-web/src/components/Layouts/InteractiveArea/Actions/ViewControls.vue
new file mode 100644
index 00000000..a03e2b9b
--- /dev/null
+++ b/apps/stage-web/src/components/Layouts/InteractiveArea/Actions/ViewControls.vue
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/stage-web/src/components/Layouts/MobileHeader.vue b/apps/stage-web/src/components/Layouts/MobileHeader.vue
new file mode 100644
index 00000000..1664c0d7
--- /dev/null
+++ b/apps/stage-web/src/components/Layouts/MobileHeader.vue
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/apps/stage-web/src/components/Layouts/MobileInteractiveArea.vue b/apps/stage-web/src/components/Layouts/MobileInteractiveArea.vue
index b359c11c..f92311cf 100644
--- a/apps/stage-web/src/components/Layouts/MobileInteractiveArea.vue
+++ b/apps/stage-web/src/components/Layouts/MobileInteractiveArea.vue
@@ -4,11 +4,20 @@ import type { ChatProvider } from '@xsai-ext/shared-providers'
import { useMicVAD } from '@proj-airi/stage-ui/composables'
import { useChatStore, useConsciousnessStore, useProvidersStore, useSettings } from '@proj-airi/stage-ui/stores'
import { BasicTextarea } from '@proj-airi/ui'
+import { useDark } from '@vueuse/core'
import { storeToRefs } from 'pinia'
-import { onMounted, ref, watch } from 'vue'
+import { onMounted, ref, useTemplateRef, watch } from 'vue'
import { useI18n } from 'vue-i18n'
+import { RouterLink } from 'vue-router'
import MobileChatHistory from '../Widgets/MobileChatHistory.vue'
+import ActionViewControls from './InteractiveArea/Actions/ViewControls.vue'
+import ViewControlInputs from './ViewControls/Inputs.vue'
+
+const isDark = useDark({ disableTransition: false })
+
+const viewControlsActiveMode = ref<'x' | 'y' | 'z' | 'scale'>('scale')
+const viewControlsInputsRef = useTemplateRef>('viewControlsInputs')
const messageInput = ref('')
const listening = ref(false)
@@ -19,7 +28,7 @@ const { activeProvider, activeModel } = storeToRefs(useConsciousnessStore())
// const { audioInputs } = useDevicesList({ constraints: { audio: true }, requestPermissions: true })
// const { selectedAudioDevice, isAudioInputOn, selectedAudioDeviceId } = storeToRefs(useSettings())
-const { isAudioInputOn, selectedAudioDeviceId, themeColorsHueDynamic } = storeToRefs(useSettings())
+const { isAudioInputOn, selectedAudioDeviceId, themeColorsHueDynamic, stageView, stageViewControlsEnabled } = storeToRefs(useSettings())
const { send, onAfterSend, discoverToolsCompatibility } = useChatStore()
const { messages } = storeToRefs(useChatStore())
const { t } = useI18n()
@@ -100,34 +109,75 @@ onMounted(() => {
-
-
-
-
-
diff --git a/apps/stage-web/src/components/Layouts/ViewControls/Inputs.vue b/apps/stage-web/src/components/Layouts/ViewControls/Inputs.vue
new file mode 100644
index 00000000..89d9fb5a
--- /dev/null
+++ b/apps/stage-web/src/components/Layouts/ViewControls/Inputs.vue
@@ -0,0 +1,203 @@
+
+
+
+
+
+
+
+
+
diff --git a/apps/stage-web/src/components/Widgets/ChatHistory.vue b/apps/stage-web/src/components/Widgets/ChatHistory.vue
index 3c64e5c7..cd370b49 100644
--- a/apps/stage-web/src/components/Widgets/ChatHistory.vue
+++ b/apps/stage-web/src/components/Widgets/ChatHistory.vue
@@ -1,7 +1,6 @@
diff --git a/apps/stage-web/src/components/Widgets/MobileChatHistory.vue b/apps/stage-web/src/components/Widgets/MobileChatHistory.vue
index 96440d85..5e5515c1 100644
--- a/apps/stage-web/src/components/Widgets/MobileChatHistory.vue
+++ b/apps/stage-web/src/components/Widgets/MobileChatHistory.vue
@@ -1,7 +1,6 @@
-
-
-
-
+
+
updateThemeColor())
-
+
updateThemeColor())
-
+
+
@@ -56,8 +58,8 @@ onMounted(() => updateThemeColor())
:y-offset="positionInPercentageString.y"
:scale="scale"
/>
-
-
+
+
diff --git a/apps/stage-web/src/pages/settings/models/index.vue b/apps/stage-web/src/pages/settings/models/index.vue
index fc6e806a..b0bfb6df 100644
--- a/apps/stage-web/src/pages/settings/models/index.vue
+++ b/apps/stage-web/src/pages/settings/models/index.vue
@@ -43,8 +43,14 @@ const {
-
-
+
+
+
import { WidgetStage } from '@proj-airi/stage-ui/components/scenes'
-import { useDark, useMouse } from '@vueuse/core'
-import { ref } from 'vue'
+import { useLive2d } from '@proj-airi/stage-ui/stores'
+import { breakpointsTailwind, useBreakpoints, useDark, useMouse } from '@vueuse/core'
+import { storeToRefs } from 'pinia'
+import { onMounted, ref, watch } from 'vue'
import Cross from '../../components/Backgrounds/Cross.vue'
import Header from '../../components/Layouts/Header.vue'
import InteractiveArea from '../../components/Layouts/InteractiveArea.vue'
+import MobileHeader from '../../components/Layouts/MobileHeader.vue'
import MobileInteractiveArea from '../../components/Layouts/MobileInteractiveArea.vue'
import AnimatedWave from '../../components/Widgets/AnimatedWave.vue'
+import { themeColorFromPropertyOf, useThemeColor } from '../../composables/theme-color'
+
const dark = useDark()
const paused = ref(false)
@@ -17,19 +22,28 @@ function handleSettingsOpen(open: boolean) {
}
const positionCursor = useMouse()
+const { scale, position, positionInPercentageString } = storeToRefs(useLive2d())
+const breakpoints = useBreakpoints(breakpointsTailwind)
+const isMobile = breakpoints.smaller('md')
+
+const { updateThemeColor } = useThemeColor(themeColorFromPropertyOf('.widgets.top-widgets .colored-area', 'background-color'))
+watch(dark, () => updateThemeColor(), { immediate: true })
+onMounted(() => updateThemeColor())
-
-
+
+
+
@@ -40,9 +54,12 @@ const positionCursor = useMouse()
x: positionCursor.x.value,
y: positionCursor.y.value,
}"
+ :x-offset="`${isMobile ? position.x : position.x - 10}%`"
+ :y-offset="positionInPercentageString.y"
+ :scale="scale"
/>
-
-
+
+
@@ -50,7 +67,7 @@ const positionCursor = useMouse()
-name: StageScenePage
+name: IndexScenePage
meta:
layout: stage
stageTransition:
diff --git a/apps/stage-web/src/styles/main.css b/apps/stage-web/src/styles/main.css
index d638b316..843282d0 100755
--- a/apps/stage-web/src/styles/main.css
+++ b/apps/stage-web/src/styles/main.css
@@ -1,4 +1,5 @@
@import './transitions.css';
+@import './vue-transitions.css';
:root {
--bg-color-light: rgb(255 255 255);
diff --git a/apps/stage-web/src/styles/vue-transitions.css b/apps/stage-web/src/styles/vue-transitions.css
new file mode 100644
index 00000000..2beab3d5
--- /dev/null
+++ b/apps/stage-web/src/styles/vue-transitions.css
@@ -0,0 +1,48 @@
+.fade-slide-out-r-to-l-enter-active,
+.fade-slide-out-r-to-l-leave-active {
+ transition: opacity 0.2s ease-in-out, transform 0.2s ease-in-out;
+}
+
+.fade-slide-out-r-to-l-enter-from,
+.fade-slide-out-r-to-l-leave-to {
+ opacity: 0;
+ transform: translateX(10px);
+}
+
+.fade-slide-out-r-to-l-enter-to,
+.fade-slide-out-r-to-l-leave-from {
+ opacity: 1;
+ transform: translateX(0);
+}
+
+.fade-slide-out-l-to-r-enter-active,
+.fade-slide-out-l-to-r-leave-active {
+ transition: opacity 0.2s ease-in-out, transform 0.2s ease-in-out;
+}
+
+.fade-slide-out-l-to-r-enter-from,
+.fade-slide-out-l-to-r-leave-to {
+ opacity: 0;
+ transform: translateX(-10px);
+}
+
+.fade-slide-out-l-to-r-enter-to,
+.fade-slide-out-l-to-r-leave-from {
+ opacity: 1;
+ transform: translateX(0);
+}
+
+.fade-enter-active,
+.fade-leave-active {
+ transition: opacity 0.2s ease-in-out;
+}
+
+.fade-enter-from,
+.fade-leave-to {
+ opacity: 0;
+}
+
+.fade-enter-to,
+.fade-leave-from {
+ opacity: 1;
+}
diff --git a/packages/stage-ui/src/components/Form/Range/RoundRange.story.vue b/packages/stage-ui/src/components/Form/Range/RoundRange.story.vue
new file mode 100644
index 00000000..047ae689
--- /dev/null
+++ b/packages/stage-ui/src/components/Form/Range/RoundRange.story.vue
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/stage-ui/src/components/Layouts/Callout.vue b/packages/stage-ui/src/components/Layouts/Callout.vue
index f0aed0cb..917c67c9 100644
--- a/packages/stage-ui/src/components/Layouts/Callout.vue
+++ b/packages/stage-ui/src/components/Layouts/Callout.vue
@@ -14,7 +14,7 @@ const themeClasses: Record = {
primary: {
container: [
- 'bg-primary-400/10 dark:bg-primary-50/10 text-neutral-900/80 backdrop-blur-md',
+ 'bg-primary-100/60 dark:bg-primary-50/10 text-neutral-900/80 backdrop-blur-md',
`before:bg-primary-400 dark:bg-primary-900/10 dark:text-neutral-100/80 before:content-[''] before:dark:bg-primary-900`,
],
label: [
@@ -23,7 +23,7 @@ const themeClasses: Record(), {
+ toggled: false,
variant: 'primary',
disabled: false,
loading: false,
@@ -38,18 +40,33 @@ const isDisabled = computed(() => props.disabled || props.loading)
// Extract variant styles for better organization
const variantClasses: Record = {
- primary: {
- default: 'bg-primary-500/15 hover:bg-primary-500/20 active:bg-primary-500/30 dark:bg-primary-700/30 dark:hover:bg-primary-700/40 dark:active:bg-primary-700/30 focus:ring-primary-300/60 dark:focus:ring-primary-600/30 border-2 border-solid border-primary-500/5 dark:border-primary-900/40 text-primary-950 dark:text-primary-100',
+ 'primary': {
+ default: {
+ default: 'bg-primary-500/15 hover:bg-primary-500/20 active:bg-primary-500/30 dark:bg-primary-700/30 dark:hover:bg-primary-700/40 dark:active:bg-primary-700/30 focus:ring-primary-300/60 dark:focus:ring-primary-600/30 border-2 border-solid border-primary-500/5 dark:border-primary-900/40 text-primary-950 dark:text-primary-100',
+ },
},
- secondary: {
- default: 'bg-neutral-400/15 hover:bg-neutral-400/20 active:bg-neutral-400/30 dark:bg-neutral-700/60 dark:hover:bg-neutral-700/80 dark:active:bg-neutral-700/60 focus:ring-neutral-300/30 dark:focus:ring-neutral-600/60 dark:focus:ring-neutral-600/30 border-2 border-solid border-neutral-300/30 dark:border-neutral-700/30 text-neutral-950 dark:text-neutral-100',
+ 'secondary': {
+ default: {
+ default: 'bg-neutral-100/55 hover:bg-neutral-400/20 active:bg-neutral-400/30 dark:bg-neutral-700/60 dark:hover:bg-neutral-700/80 dark:active:bg-neutral-700/60 focus:ring-neutral-300/30 dark:focus:ring-neutral-600/60 dark:focus:ring-neutral-600/30 border-2 border-solid border-neutral-300/30 dark:border-neutral-700/30 text-neutral-950 dark:text-neutral-100',
+ },
},
- danger: {
- default: 'bg-red-500/15 hover:bg-red-500/20 active:bg-red-500/30 dark:bg-red-700/30 dark:hover:bg-red-700/40 dark:active:bg-red-700/30 focus:ring-red-300/30 dark:focus:ring-red-600/60 dark:focus:ring-red-600/30 border-2 border-solid border-red-200/30 dark:border-red-900/30 text-red-950 dark:text-red-100',
+ 'secondary-muted': {
+ default: {
+ default: 'hover:bg-neutral-50/50 active:bg-neutral-50/90 hover:dark:bg-neutral-800/50 active:dark:bg-neutral-800/90 border-2 border-solid border-neutral-100/60 dark:border-neutral-800/30 focus:ring-neutral-300/30 dark:focus:ring-neutral-600/60 dark:focus:ring-neutral-600/30',
+ nonToggled: 'bg-neutral-50/70 dark:bg-neutral-800/70 text-neutral-500 dark:text-neutral-400',
+ toggled: 'bg-white/90 dark:bg-neutral-500/70 ring-neutral-300/30 dark:ring-neutral-600/60 ring-2 dark:ring-neutral-600/30 text-primary-500 dark:text-primary-100',
+ },
+ },
+ 'danger': {
+ default: {
+ default: 'bg-red-500/15 hover:bg-red-500/20 active:bg-red-500/30 dark:bg-red-700/30 dark:hover:bg-red-700/40 dark:active:bg-red-700/30 focus:ring-red-300/30 dark:focus:ring-red-600/60 dark:focus:ring-red-600/30 border-2 border-solid border-red-200/30 dark:border-red-900/30 text-red-950 dark:text-red-100',
+ },
},
}
@@ -62,14 +79,16 @@ const sizeClasses: Record = {
// Base classes that are always applied
const baseClasses = computed(() => [
- 'rounded-lg font-medium outline-none',
+ 'rounded-xl font-medium outline-none',
'transition-all duration-200 ease-in-out',
'disabled:cursor-not-allowed disabled:opacity-50',
+ 'backdrop-blur-md',
props.block ? 'w-full' : '',
sizeClasses[props.size],
- variantClasses[props.variant][props.theme],
+ variantClasses[props.variant][props.theme].default,
+ props.toggled ? variantClasses[props.variant][props.theme].toggled || '' : variantClasses[props.variant][props.theme].nonToggled || '',
{ 'opacity-50 cursor-not-allowed': isDisabled.value },
- 'focus:ring-2 focus:ring-offset-2 focus:ring-offset-white dark:focus:ring-offset-neutral-900',
+ 'focus:ring-2',
])
diff --git a/packages/stage-ui/src/components/Scenarios/Settings/ModelSettings/Live2DScene.vue b/packages/stage-ui/src/components/Scenarios/Settings/ModelSettings/Live2DScene.vue
deleted file mode 100644
index 125e5218..00000000
--- a/packages/stage-ui/src/components/Scenarios/Settings/ModelSettings/Live2DScene.vue
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/packages/stage-ui/src/components/Scenarios/Settings/ModelSettings/VRM.vue b/packages/stage-ui/src/components/Scenarios/Settings/ModelSettings/VRM.vue
index e95e7f48..bd0174e2 100644
--- a/packages/stage-ui/src/components/Scenarios/Settings/ModelSettings/VRM.vue
+++ b/packages/stage-ui/src/components/Scenarios/Settings/ModelSettings/VRM.vue
@@ -31,6 +31,7 @@ const {
loadSource,
loadingModel,
modelUrl,
+ scale,
modelSize,
modelOffset,
} = storeToRefs(vrm)
@@ -59,6 +60,122 @@ modelFileDialog.onChange((files) => {
{{ t('settings.vrm.switch-to-vrm.change-to-vrm') }}
+
+
+
+
+
+ {{ t('settings.vrm.scale-and-position.model-info-x') }}
+ {{ modelSize.x.toFixed(4) }}
+
+
+ {{ t('settings.vrm.scale-and-position.model-info-y') }}
+ {{ modelSize.y.toFixed(4) }}
+
+
+ {{ t('settings.vrm.scale-and-position.model-info-z') }}
+ {{ modelSize.z.toFixed(4) }}
+
+
+
+
+
+
+ {{ t('settings.vrm.scale-and-position.tips') }}
+
+
+
+
+
+
+ {{ t('settings.vrm.scale-and-position.scale') }}
+
+
scale = 1">
+
+
+
+
+
+
+
+
+
+ {{ t('settings.vrm.scale-and-position.x') }}
+
+
modelOffset.x = 0">
+
+
+
+
+
+
+
+
+
{{ t('settings.vrm.scale-and-position.y') }}
+
modelOffset.y = 0">
+
+
+
+
+
+
+
+
+
{{ t('settings.vrm.scale-and-position.z') }}
+
modelOffset.z = 0">
+
+
+
+
+
+
{
{{ t('settings.vrm.theme-color-from-model.button-extract.title') }}
-
-
-
-
-
- {{ t('settings.vrm.scale-and-position.model-info-x') }}
- {{ modelSize.x.toFixed(4) }}
-
-
- {{ t('settings.vrm.scale-and-position.model-info-y') }}
- {{ modelSize.y.toFixed(4) }}
-
-
- {{ t('settings.vrm.scale-and-position.model-info-z') }}
- {{ modelSize.z.toFixed(4) }}
-
-
-
-
-
-
- {{ t('settings.vrm.scale-and-position.tips') }}
-
-
- modelOffset.x = val"
- >
-
-
-
- {{ t('settings.vrm.scale-and-position.x') }}
-
-
modelOffset.x = 0">
-
-
-
-
-
- modelOffset.y = val"
- >
-
-
-
{{ t('settings.vrm.scale-and-position.y') }}
-
modelOffset.y = 0">
-
-
-
-
-
- modelOffset.z = val"
- >
-
-
-
{{ t('settings.vrm.scale-and-position.z') }}
-
modelOffset.z = 0">
-
-
-
-
-
-
diff --git a/packages/stage-ui/src/components/Scenarios/Settings/ModelSettings/VRMScene.vue b/packages/stage-ui/src/components/Scenarios/Settings/ModelSettings/VRMScene.vue
deleted file mode 100644
index 7c8570e1..00000000
--- a/packages/stage-ui/src/components/Scenarios/Settings/ModelSettings/VRMScene.vue
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- emit('loadModelProgress', val)"
- @error="(val) => emit('error', val)"
- />
-
-
-
diff --git a/packages/stage-ui/src/components/Scenarios/Settings/ModelSettings/index.ts b/packages/stage-ui/src/components/Scenarios/Settings/ModelSettings/index.ts
index 50bbd7ce..5adacada 100644
--- a/packages/stage-ui/src/components/Scenarios/Settings/ModelSettings/index.ts
+++ b/packages/stage-ui/src/components/Scenarios/Settings/ModelSettings/index.ts
@@ -1,5 +1 @@
export { default as ModelSettings } from './index.vue'
-export { default as ModelSettingsLive2D } from './Live2D.vue'
-export { default as ModelSettingsLive2DScene } from './Live2DScene.vue'
-export { default as ModelSettingsVRM } from './VRM.vue'
-export { default as ModelSettingsVRMScene } from './VRMScene.vue'
diff --git a/packages/stage-ui/src/components/Scenarios/Settings/ModelSettings/index.vue b/packages/stage-ui/src/components/Scenarios/Settings/ModelSettings/index.vue
index 37c8cf7b..244accc6 100644
--- a/packages/stage-ui/src/components/Scenarios/Settings/ModelSettings/index.vue
+++ b/packages/stage-ui/src/components/Scenarios/Settings/ModelSettings/index.vue
@@ -1,10 +1,10 @@
@@ -50,80 +41,5 @@ const { currentMotion } = storeToRefs(useLive2d())
:scale="scale"
/>
-
-
-
-
-
-
- {{ t('stage.viewers.debug-menu.emotions') }}
-
-
-
- {{ t('stage.viewers.debug-menu.emotions-btn.surprised') }}
-
-
- {{ t('stage.viewers.debug-menu.emotions-btn.sad') }}
-
-
- {{ t('stage.viewers.debug-menu.emotions-btn.angry') }}
-
-
- {{ t('stage.viewers.debug-menu.emotions-btn.happy') }}
-
-
- {{ t('stage.viewers.debug-menu.emotions-btn.awkward') }}
-
-
- {{ t('stage.viewers.debug-menu.emotions-btn.question') }}
-
-
- {{ t('stage.viewers.debug-menu.emotions-btn.think') }}
-
-
-
-
-
-
diff --git a/packages/stage-ui/src/components/Scenes/Stage.vue b/packages/stage-ui/src/components/Scenes/Stage.vue
index 6a91224c..aecf2035 100644
--- a/packages/stage-ui/src/components/Scenes/Stage.vue
+++ b/packages/stage-ui/src/components/Scenes/Stage.vue
@@ -41,7 +41,7 @@ const db = ref()
const vrmViewerRef = ref<{ setExpression: (expression: string) => void }>()
-const { stageView } = storeToRefs(useSettings())
+const { stageView, stageViewControlsEnabled } = storeToRefs(useSettings())
const { mouthOpenSize } = storeToRefs(useSpeakingStore())
const { audioContext, calculateVolume } = useAudioContext()
const { onBeforeMessageComposed, onBeforeSend, onTokenLiteral, onTokenSpecial, onStreamEnd, onAssistantResponseEnd } = useChatStore()
@@ -245,6 +245,7 @@ onMounted(async () => {
idle-animation="/assets/vrm/animations/idle_loop.vrma"
min-w="50%
diff --git a/packages/stage-ui/src/components/Scenes/VRM.vue b/packages/stage-ui/src/components/Scenes/VRM.vue
index 49c72451..9dffa057 100644
--- a/packages/stage-ui/src/components/Scenes/VRM.vue
+++ b/packages/stage-ui/src/components/Scenes/VRM.vue
@@ -1,44 +1,35 @@
-
+
-
-
+
+
+
emit('error', val)"
/>
-
-
-
-
-
-
- {{ t('stage.viewers.debug-menu.vrm.model.title') }}
-
-
-
- {{ t('stage.viewers.debug-menu.vrm.camera') }}
-
-
-
- X
-
-
-
- {{ cameraPositionX }}
-
-
-
- Y
-
-
-
- {{ cameraPositionY }}
-
-
-
- Z
-
-
-
- {{ cameraPositionZ }}
-
-
-
-
-
- {{ t('stage.viewers.debug-menu.vrm.model.title') }}
-
-
-
- X
-
-
-
-
- {{ vrmModelPositionX }}
-
-
-
- Y
-
-
-
- {{ vrmModelPositionY }}
-
-
-
- Z
-
-
-
- {{ vrmModelPositionZ }}
-
-
-
-
- {{ t('stage.viewers.debug-menu.emotions') }}
-
-
-
- {{ t('stage.viewers.debug-menu.emotions-btn.neutral') }}
-
-
- {{ t('stage.viewers.debug-menu.emotions-btn.surprised') }}
-
-
- {{ t('stage.viewers.debug-menu.emotions-btn.sad') }}
-
-
- {{ t('stage.viewers.debug-menu.emotions-btn.angry') }}
-
-
- {{ t('stage.viewers.debug-menu.emotions-btn.happy') }}
-
-
-
-
-
-
diff --git a/packages/stage-ui/src/components/Scenes/VRM/Model.vue b/packages/stage-ui/src/components/Scenes/VRM/Model.vue
index a1efe76f..25536e6e 100644
--- a/packages/stage-ui/src/components/Scenes/VRM/Model.vue
+++ b/packages/stage-ui/src/components/Scenes/VRM/Model.vue
@@ -40,15 +40,16 @@ const {
modelOffset,
modelOrigin,
modelSize,
- modelPosition,
+ position,
+ scale,
} = storeToRefs(vrmStore)
watch(modelOffset, () => {
if (vrm.value) {
vrm.value.scene.position.set(
- modelPosition.value.x,
- modelPosition.value.y,
- modelPosition.value.z,
+ position.value.x,
+ position.value.y,
+ position.value.z,
)
}
}, { deep: true })
@@ -69,9 +70,10 @@ onMounted(async () => {
console.warn('No VRM model loaded')
return
}
+
const { _vrm, modelCenter: vrmModelCenter, modelSize: vrmModelSize } = _vrmInfo
- // Set initial postions for model
+ // Set initial positions for model
modelOrigin.value = {
x: vrmModelCenter.x,
y: vrmModelCenter.y,
@@ -83,7 +85,7 @@ onMounted(async () => {
z: vrmModelSize.z,
}
- // Set initial positons for animation
+ // Set initial positions for animation
function removeRootPositionTrack(clip: AnimationClip) {
clip.tracks = clip.tracks.filter((track) => {
return !track.name.endsWith('.position')
@@ -96,6 +98,7 @@ onMounted(async () => {
console.warn('No VRM animation loaded')
return
}
+
removeRootPositionTrack(clip)
// play animation
@@ -105,6 +108,7 @@ onMounted(async () => {
vrmEmote.value = useVRMEmote(_vrm)
vrm.value = _vrm
+ vrm.value.scene.scale.set(scale.value, scale.value, scale.value)
disposeBeforeRenderLoop = onBeforeRender(({ delta }) => {
vrmAnimationMixer.value?.update(delta)
@@ -138,6 +142,12 @@ const { pause, resume } = useLoop()
watch(() => props.paused, (value) => {
value ? pause() : resume()
})
+
+watch(scale, () => {
+ if (vrm.value) {
+ vrm.value.scene.scale.set(scale.value, scale.value, scale.value)
+ }
+}, { immediate: true })
diff --git a/packages/stage-ui/src/stores/settings.ts b/packages/stage-ui/src/stores/settings.ts
index 880faa69..4c87fdf7 100644
--- a/packages/stage-ui/src/stores/settings.ts
+++ b/packages/stage-ui/src/stores/settings.ts
@@ -13,6 +13,7 @@ export const useSettings = defineStore('settings', () => {
const language = useLocalStorage('settings/language', 'en')
const stageView = useLocalStorage('settings/stage/view/model-renderer', '2d')
+ const stageViewControlsEnabled = ref(false)
const isAudioInputOn = useLocalStorage('settings/audio/input', 'false')
const selectedAudioDeviceId = computed(() => selectedAudioDevice.value?.deviceId)
@@ -77,6 +78,7 @@ export const useSettings = defineStore('settings', () => {
usePageSpecificTransitions,
language,
stageView,
+ stageViewControlsEnabled,
themeColorsHue,
themeColorsHueDynamic,
isAudioInputOn,
diff --git a/packages/stage-ui/src/stores/vrm.ts b/packages/stage-ui/src/stores/vrm.ts
index 70c4310e..7a079cbd 100644
--- a/packages/stage-ui/src/stores/vrm.ts
+++ b/packages/stage-ui/src/stores/vrm.ts
@@ -7,18 +7,21 @@ export const useVRM = defineStore('vrm', () => {
const modelUrl = ref('/assets/vrm/models/AvatarSample-B/AvatarSample_B.vrm')
const loadSource = ref<'file' | 'url'>('url')
const loadingModel = ref(false)
+
+ const scale = useLocalStorage('settings/live2d/cameraScale', 1)
const modelSize = useLocalStorage('settings/vrm/modelSize', { x: 0, y: 0, z: 0 })
const modelOrigin = useLocalStorage('settings/vrm/modelOrigin', { x: 0, y: 0, z: 0 })
const modelOffset = useLocalStorage('settings/vrm/modelOffset', { x: 0, y: 0, z: 0 })
- const modelPosition = computed(() => ({
+
+ const position = computed(() => ({
x: modelOrigin.value.x + modelOffset.value.x,
y: modelOrigin.value.y + modelOffset.value.y,
z: modelOrigin.value.z + modelOffset.value.z,
}))
const positionInPercentageString = computed(() => ({
- x: `${modelPosition.value.x}%`,
- y: `${modelPosition.value.y}%`,
- z: `${modelPosition.value.z}%`,
+ x: `${position.value.x}%`,
+ y: `${position.value.y}%`,
+ z: `${position.value.z}%`,
}))
const modelObjectUrl = ref()
@@ -52,9 +55,10 @@ export const useVRM = defineStore('vrm', () => {
loadSource,
loadingModel,
modelSize,
+ scale,
modelOrigin,
modelOffset,
- modelPosition,
+ position,
positionInPercentageString,
selectedModel, // Expose the new computed property
}
diff --git a/packages/ui/src/components/Form/Range/Range.vue b/packages/ui/src/components/Form/Range/Range.vue
index 3a1a3bcd..c2347a1e 100644
--- a/packages/ui/src/components/Form/Range/Range.vue
+++ b/packages/ui/src/components/Form/Range/Range.vue
@@ -153,6 +153,7 @@ https://toughengineer.github.io/demo/slider-styler*/
border: var(--track-border);
border-radius: var(--track-border-radius);
background: var(--track-background);
+ backdrop-filter: blur(8px);
box-shadow: var(--track-box-shadow);
position: relative;
cursor: col-resize;
@@ -225,6 +226,7 @@ https://toughengineer.github.io/demo/slider-styler*/
border: var(--track-border);
border-radius: var(--track-border-radius);
background: var(--track-background);
+ backdrop-filter: blur(8px);
box-shadow: var(--track-box-shadow);
cursor: col-resize;
/* Trim left and right paddings of track */
@@ -307,6 +309,7 @@ https://toughengineer.github.io/demo/slider-styler*/
height: var(--track-height);
border-radius: var(--track-border-radius);
background: var(--track-background);
+ backdrop-filter: blur(8px);
border: var(--track-border);
box-shadow: var(--track-box-shadow);
box-sizing: border-box;
diff --git a/packages/ui/src/components/Form/Range/RoundRange.vue b/packages/ui/src/components/Form/Range/RoundRange.vue
new file mode 100644
index 00000000..90129cfa
--- /dev/null
+++ b/packages/ui/src/components/Form/Range/RoundRange.vue
@@ -0,0 +1,277 @@
+
+
+
+
+
+
+
diff --git a/packages/ui/src/components/Form/Range/index.ts b/packages/ui/src/components/Form/Range/index.ts
index 713d4e53..dd6a78c5 100644
--- a/packages/ui/src/components/Form/Range/index.ts
+++ b/packages/ui/src/components/Form/Range/index.ts
@@ -1,2 +1,3 @@
export { default as ColorHueRange } from './ColorHueRange.vue'
export { default as Range } from './Range.vue'
+export { default as RoundRange } from './RoundRange.vue'