chore: prune unused apps and services for the moeka fork
CI / Lint (push) Canceled after 0s
CI / Build Test (stage-web) (push) Canceled after 0s
CI / Build Test (ui-loading-screens) (push) Canceled after 0s
CI / Build Test (ui-transitions) (push) Canceled after 0s
CI / Unit Test (push) Canceled after 0s
CI / Type Check (push) Canceled after 0s
CI / Check Provenance (push) Canceled after 0s
Sync Labels / sync-labels (push) Successful in 1m43s

- Remove stage-tamagotchi, stage-pocket, server, and engine workspaces with their workflows, addons, and docs
- Switch the toolchain from pnpm to bun, replace lockfiles with bun.lock
- Rewrite remaining product references to Moeka
This commit is contained in:
2026-09-14 16:40:49 +07:00
parent 00c6867b7f
commit dc26878386
1981 changed files with 10974 additions and 260677 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
"private": true,
"description": "Shared Layouts",
"author": {
"name": "Moeru AI Project AIRI Team",
"name": "Moeka Team",
"email": "airi@moeru.ai",
"url": "https://github.com/moeru-ai"
},
@@ -102,7 +102,7 @@ describe('adaptive input geometry', () => {
// ROOT CAUSE:
//
// Safari keeps reporting the keyboard-sized Visual Viewport for part of its close animation.
// If AIRI uses that stale measurement after blur, the input region stays compressed and leaves
// If Moeka uses that stale measurement after blur, the input region stays compressed and leaves
// a visible gap above the keyboard.
//
// Before the fix, blur disabled keyboardVisible but kept height and visibleBottom at 404px.
@@ -1,11 +1,12 @@
<script setup lang="ts">
import { ProfileSwitcherPopover } from '@proj-airi/stage-ui/components'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { RouterLink, useRouter } from 'vue-router'
import HeaderAvatar from './HeaderAvatar.vue'
import HeaderLink from './HeaderLink.vue'
import ActionAbout from './InteractiveArea/Actions/About.vue'
const { t } = useI18n()
const router = useRouter()
function handleNavigation() {
@@ -19,7 +20,15 @@ function handleNavigation() {
<div flex items-center gap-2>
<ActionAbout />
<ProfileSwitcherPopover @manage="handleNavigation" />
<HeaderAvatar />
<RouterLink
border="2 solid neutral-100/60 dark:neutral-800/30"
bg="neutral-50/70 dark:neutral-800/70"
w-fit flex items-center justify-center rounded-xl p-2 backdrop-blur-md
:title="t('settings.title')"
to="/settings"
>
<div i-solar:settings-minimalistic-bold-duotone size-5 text="neutral-500 dark:neutral-400" />
</RouterLink>
</div>
</header>
</template>
@@ -1,200 +0,0 @@
<script setup lang="ts">
import { signOut } from '@proj-airi/stage-ui/libs/auth'
import { useAuthStore } from '@proj-airi/stage-ui/stores/auth'
import { AnimatedContent, Avatar } from '@proj-airi/ui'
import { storeToRefs } from 'pinia'
import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuPortal,
DropdownMenuRoot,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from 'reka-ui'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { RouterLink } from 'vue-router'
defineProps<{
/** Shows only the account control on the mobile Stage. @default false */
compact?: boolean
}>()
const authStore = useAuthStore()
const { isAuthenticated, user, credits } = storeToRefs(authStore)
const { t } = useI18n()
const userName = computed(() => user.value?.name)
const userAvatar = computed(() => user.value?.image)
const formattedCredits = computed(() => credits.value.toLocaleString())
</script>
<template>
<div flex items-center gap-2>
<!-- Non-authenticated: Settings & Sign in -->
<!-- NOTICE: The avatar is stored in the localstorage, it will be shown at the first time of the page load, so we do not need the skeleton loading here -->
<template v-if="!isAuthenticated">
<RouterLink
v-if="!compact"
border="2 solid neutral-100/60 dark:neutral-800/30"
bg="neutral-50/70 dark:neutral-800/70"
w-fit flex items-center justify-center rounded-xl p-2 backdrop-blur-md
title="Settings"
to="/settings"
>
<div i-solar:settings-minimalistic-bold-duotone size-5 text="neutral-500 dark:neutral-400" />
</RouterLink>
<button
:class="compact ? ['min-h-11 min-w-11 focus-visible:outline-2 focus-visible:outline-primary-500'] : undefined"
border="2 solid neutral-100/60 dark:neutral-800/30"
bg="neutral-50/70 dark:neutral-800/70"
w-fit flex items-center justify-center rounded-xl p-2 backdrop-blur-md
title="Sign in"
type="button"
@click="authStore.needsLogin = true"
>
<div i-solar:user-bold-duotone />
</button>
</template>
<!-- Authenticated: Avatar Dropdown -->
<DropdownMenuRoot v-else>
<DropdownMenuTrigger as-child>
<button
type="button"
:aria-label="userName || t('settings.pages.account.title')"
:class="[
'group flex items-center gap-2 rounded-full border-2 p-1 outline-none backdrop-blur-md',
compact ? 'size-11 justify-center focus-visible:ring-2 focus-visible:ring-primary-500' : 'pl-1 pr-3',
'border-neutral-100/60 bg-neutral-50/70 dark:border-neutral-800/30 dark:bg-neutral-800/70',
'hover:bg-neutral-100 data-[state=open]:bg-neutral-100',
'dark:hover:bg-neutral-800 dark:data-[state=open]:bg-neutral-800',
'data-[state=open]:ring-2 data-[state=open]:ring-primary-500/20',
'transition-colors duration-200 ease-in-out',
]"
>
<Avatar
:src="userAvatar"
:class="[
'h-7 w-7 rounded-full',
'bg-neutral-200 text-neutral-500 dark:bg-neutral-700 dark:text-neutral-400',
]"
/>
<span
v-if="userName && !compact"
:class="[
'max-w-[100px] hidden truncate text-sm font-medium sm:block',
'text-neutral-700 dark:text-neutral-200',
]"
>
{{ userName }}
</span>
<div
v-if="!compact"
:class="[
'i-solar:alt-arrow-down-linear text-neutral-400',
'transition-transform duration-200',
'group-data-[state=open]:rotate-180',
]"
/>
</button>
</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent
as-child
align="end"
side="bottom"
:side-offset="6"
:class="[
'z-10000 w-60 rounded-xl border p-1 shadow-lg outline-none backdrop-blur-md',
'border-neutral-100/80 bg-neutral-100/80 text-neutral-700',
'dark:border-neutral-800/60 dark:bg-neutral-800/80 dark:text-neutral-100',
]"
>
<AnimatedContent>
<div class="px-3 py-2">
<p class="text-xs text-neutral-500 dark:text-neutral-400">
Signed in as
</p>
<p class="truncate text-sm text-neutral-900 font-medium dark:text-white">
{{ userName }}
</p>
<div class="mt-1 flex items-center gap-1.5 text-xs text-primary-600 font-medium dark:text-primary-400">
<div class="i-solar:battery-charge-bold-duotone text-sm" />
<span>{{ formattedCredits }} Flux</span>
</div>
</div>
<DropdownMenuSeparator :class="['mx-2 my-1 h-px bg-neutral-200/80 dark:bg-neutral-700/80']" />
<DropdownMenuItem as-child>
<RouterLink
to="/settings/account"
:class="[
'group w-full flex cursor-pointer select-none items-center gap-3 rounded-lg px-3 py-2',
'text-sm leading-none outline-none text-neutral-700 dark:text-neutral-200',
'data-[highlighted]:bg-primary-100/80 dark:data-[highlighted]:bg-primary-900/40',
'transition-colors duration-150 ease-in-out',
]"
>
<div class="i-solar:user-id-bold-duotone text-lg text-neutral-400 transition group-hover:text-primary-500" />
Profile
</RouterLink>
</DropdownMenuItem>
<DropdownMenuItem as-child>
<RouterLink
to="/settings/flux"
:class="[
'group w-full flex cursor-pointer select-none items-center gap-3 rounded-lg px-3 py-2',
'text-sm leading-none outline-none text-neutral-700 dark:text-neutral-200',
'data-[highlighted]:bg-primary-100/80 dark:data-[highlighted]:bg-primary-900/40',
'transition-colors duration-150 ease-in-out',
]"
>
<div class="i-solar:battery-charge-bold-duotone text-lg text-neutral-400 transition group-hover:text-primary-500" />
Flux
</RouterLink>
</DropdownMenuItem>
<DropdownMenuItem as-child>
<RouterLink
to="/settings"
:class="[
'group w-full flex cursor-pointer select-none items-center gap-3 rounded-lg px-3 py-2',
'text-sm leading-none outline-none text-neutral-700 dark:text-neutral-200',
'data-[highlighted]:bg-primary-100/80 dark:data-[highlighted]:bg-primary-900/40',
'transition-colors duration-150 ease-in-out',
]"
>
<div class="i-solar:settings-minimalistic-bold-duotone text-lg text-neutral-400 transition group-hover:text-primary-500" />
Settings
</RouterLink>
</DropdownMenuItem>
<DropdownMenuSeparator :class="['mx-2 my-1 h-px bg-neutral-200/80 dark:bg-neutral-700/80']" />
<DropdownMenuItem as-child>
<button
:class="[
'group w-full flex cursor-pointer select-none items-center gap-3 rounded-lg px-3 py-2',
'text-sm leading-none outline-none text-red-600 dark:text-red-400',
'data-[highlighted]:bg-red-50 dark:data-[highlighted]:bg-red-900/20',
'transition-colors duration-150 ease-in-out',
]"
@click="signOut"
>
<div class="i-solar:logout-3-bold-duotone text-lg transition group-hover:text-red-600 dark:group-hover:text-red-400" />
Sign out
</button>
</DropdownMenuItem>
</AnimatedContent>
</DropdownMenuContent>
</DropdownMenuPortal>
</DropdownMenuRoot>
</div>
</template>
@@ -20,7 +20,7 @@ const { isDark: dark } = useTheme()
<img :src="Logo" h-8 w-8 class="theme-colored">
</template>
<div translate-y="[2px]" font-quicksand font-semibold>
<span>AIRI</span>
<span>Moeka</span>
</div>
</RouterLink>
</template>
@@ -1,12 +1,10 @@
<script setup lang="ts">
import { HearingConfig } from '@proj-airi/stage-ui/components'
import { useAuthStore } from '@proj-airi/stage-ui/stores/auth'
import { useSettingsAudioDevice } from '@proj-airi/stage-ui/stores/settings'
import { Avatar, BasicButton, BottomDrawer, Checkbox, GhostButton, useTheme } from '@proj-airi/ui'
import { storeToRefs } from 'pinia'
import { BasicButton, BottomDrawer, Checkbox, GhostButton, useTheme } from '@proj-airi/ui'
import { shallowRef, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { RouterLink, useRouter } from 'vue-router'
import { RouterLink } from 'vue-router'
import ActionAbout from './InteractiveArea/Actions/About.vue'
@@ -21,18 +19,15 @@ const emit = defineEmits<{
const characterVoiceEnabled = defineModel<boolean>('characterVoiceEnabled', { required: true })
const { t } = useI18n()
const { isDark } = useTheme()
const authStore = useAuthStore()
const { isAuthenticated, user } = storeToRefs(authStore)
const router = useRouter()
const settingsAudioDevice = useSettingsAudioDevice()
const hearingOpen = shallowRef(false)
const backgroundDialogOpen = shallowRef(false)
const settingsOpen = shallowRef(false)
const aboutOpen = shallowRef(false)
// Finish closing settings before opening a sibling modal, so focus and scroll locks have one owner.
const nextPanel = shallowRef<'background' | 'about' | 'account' | 'hearing' | 'view'>()
const nextPanel = shallowRef<'background' | 'about' | 'hearing' | 'view'>()
function openPanel(panel: 'background' | 'about' | 'account' | 'hearing' | 'view') {
function openPanel(panel: 'background' | 'about' | 'hearing' | 'view') {
nextPanel.value = panel
settingsOpen.value = false
}
@@ -47,12 +42,6 @@ function finishSettingsClose() {
else if (nextPanel.value === 'hearing') {
hearingOpen.value = true
}
else if (nextPanel.value === 'account') {
if (isAuthenticated.value)
void router.push('/settings/account')
else
authStore.needsLogin = true
}
else if (nextPanel.value === 'view') {
emit('openViewControls')
}
@@ -83,22 +72,6 @@ watch(hearingOpen, async (open) => {
<span aria-hidden="true" :class="['i-solar:settings-outline size-6']" />
</BasicButton>
</template>
<GhostButton
block size="unset"
:class="[
'mobile-tool-row rounded-2xl',
'[&_.basic-button-content]:w-full [&_.basic-button-content]:gap-3 [&_[aria-hidden]]:shrink-0',
isAuthenticated ? 'mobile-tool-row-authenticated mb-4 min-h-16' : 'mobile-tool-row-anonymous mb-3 min-h-14',
]"
@click="openPanel('account')"
>
<Avatar v-if="isAuthenticated" :src="user?.image" :class="['size-12 shrink-0 rounded-full bg-neutral-200 text-neutral-500 dark:bg-neutral-700']" />
<span :class="['min-w-0 flex-1 text-left']">
<span :class="['block truncate text-base font-semibold']">{{ isAuthenticated ? user?.name : t('stage.mobile-tools.sign-in') }}</span>
<span :class="['block text-xs text-neutral-500 dark:text-neutral-400']">{{ t('stage.mobile-tools.account-description') }}</span>
</span>
<span aria-hidden="true" :class="['i-solar:alt-arrow-right-outline size-4 shrink-0 text-neutral-400']" />
</GhostButton>
<section :class="['mb-4']">
<h3 :class="['mb-2 px-1 text-xs font-semibold text-neutral-500 dark:text-neutral-400']">
{{ t('stage.mobile-tools.appearance') }}
@@ -201,12 +174,4 @@ watch(hearingOpen, async (open) => {
.mobile-tool-row :deep([aria-hidden]) {
flex-shrink: 0;
}
.mobile-tool-row.mobile-tool-row-authenticated {
padding: 0.75rem 1rem !important;
}
.mobile-tool-row.mobile-tool-row-anonymous {
padding: 0.5rem 0 !important;
}
</style>
@@ -5,7 +5,7 @@ import { computed, nextTick, onMounted, onScopeDispose, shallowRef, toValue, wat
import { useAdaptiveInput } from './use-adaptive-input'
/** The AIRI elements and feature policy used to lay out the mobile chat surface. */
/** The Moeka elements and feature policy used to lay out the mobile chat surface. */
export interface UseMobileInteractiveAreaLayoutOptions {
/** The region that contains the message composer and its related controls. */
area: Readonly<Ref<HTMLElement | null>>
@@ -26,9 +26,9 @@ export interface UseMobileInteractiveAreaLayoutOptions {
}
/**
* Applies AIRI's mobile chat proportions and transition policy to adaptive input values.
* Applies Moeka's mobile chat proportions and transition policy to adaptive input values.
*
* This composable owns only AIRI presentation decisions. Browser keyboard measurement remains
* This composable owns only Moeka presentation decisions. Browser keyboard measurement remains
* in {@link useAdaptiveInput}, so another framework or layout can consume the same geometry.
*/
export function useMobileInteractiveAreaLayout(options: UseMobileInteractiveAreaLayoutOptions) {