diff --git a/apps/server/vitest.config.ts b/apps/server/vitest.config.ts
index 9e551b734..2caa75aa8 100644
--- a/apps/server/vitest.config.ts
+++ b/apps/server/vitest.config.ts
@@ -9,7 +9,11 @@ export default defineConfig({
include: [
'src/**/*.ts',
],
- reporter: ['text', 'json', 'html'],
+ reporter: [
+ 'text',
+ 'json',
+ 'html',
+ ],
thresholds: {
lines: 100,
functions: 100,
diff --git a/apps/ui-server-auth/index.html b/apps/ui-server-auth/index.html
new file mode 100644
index 000000000..ef6ffd4da
--- /dev/null
+++ b/apps/ui-server-auth/index.html
@@ -0,0 +1,57 @@
+
+
+
+
+ AIRI
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/ui-server-auth/package.json b/apps/ui-server-auth/package.json
new file mode 100644
index 000000000..c7bc0114e
--- /dev/null
+++ b/apps/ui-server-auth/package.json
@@ -0,0 +1,83 @@
+{
+ "name": "@proj-airi/ui-server-auth",
+ "type": "module",
+ "private": true,
+ "description": "Auth UI for Project AIRI",
+ "author": {
+ "name": "Moeru AI Project AIRI Team",
+ "email": "airi@moeru.ai",
+ "url": "https://github.com/moeru-ai"
+ },
+ "license": "MIT",
+ "scripts": {
+ "build": "vite build",
+ "dev": "vite --host",
+ "lint": "eslint .",
+ "preview": "vite preview",
+ "typecheck": "vue-tsc --noEmit"
+ },
+ "dependencies": {
+ "@date-fns/utc": "^2.1.1",
+ "@fontsource-variable/nunito": "catalog:",
+ "@formkit/auto-animate": "^0.9.0",
+ "@huggingface/transformers": "^3.8.1",
+ "@moeru/eventa": "catalog:",
+ "@moeru/std": "catalog:",
+ "@proj-airi/font-chillroundm": "workspace:^",
+ "@proj-airi/font-cjkfonts-allseto": "workspace:^",
+ "@proj-airi/font-xiaolai": "workspace:^",
+ "@proj-airi/i18n": "workspace:^",
+ "@proj-airi/stage-layouts": "workspace:^",
+ "@proj-airi/stage-shared": "workspace:^",
+ "@proj-airi/stage-ui": "workspace:^",
+ "@proj-airi/ui": "workspace:^",
+ "@vueuse/core": "^14.2.1",
+ "@vueuse/shared": "^14.2.1",
+ "animejs": "^4.3.6",
+ "colorjs.io": "^0.6.1",
+ "culori": "^4.0.2",
+ "date-fns": "^4.1.0",
+ "nanoid": "^5.1.7",
+ "nprogress": "^0.2.0",
+ "pinia": "^3.0.4",
+ "posthog-js": "catalog:",
+ "uuid": "^13.0.0",
+ "vaul-vue": "^0.4.1",
+ "vue": "catalog:",
+ "vue-i18n": "^11.3.0",
+ "vue-router": "^5.0.4",
+ "vue-sonner": "^2.0.9"
+ },
+ "devDependencies": {
+ "@iconify-json/carbon": "^1.2.19",
+ "@iconify-json/eos-icons": "^1.2.4",
+ "@iconify-json/logos": "catalog:",
+ "@iconify-json/lucide": "^1.2.98",
+ "@iconify-json/mingcute": "^1.2.7",
+ "@iconify-json/ph": "^1.2.2",
+ "@iconify-json/simple-icons": "^1.2.75",
+ "@iconify-json/solar": "^1.2.5",
+ "@iconify-json/svg-spinners": "^1.2.4",
+ "@iconify-json/tabler": "catalog:",
+ "@iconify-json/vscode-icons": "^1.2.45",
+ "@intlify/unplugin-vue-i18n": "^11.0.7",
+ "@proj-airi/iconify-meteocons": "catalog:",
+ "@proj-airi/lobe-icons": "^1.0.18",
+ "@shikijs/markdown-it": "^4.0.2",
+ "@types/culori": "^4.0.1",
+ "@types/nprogress": "^0.2.3",
+ "@types/splitpanes": "catalog:",
+ "@unocss/reset": "^66.6.7",
+ "@vitejs/plugin-vue": "^6.0.5",
+ "@vue-macros/volar": "^3.1.2",
+ "@vueuse/motion": "^3.0.3",
+ "unplugin-info": "^1.2.4",
+ "unplugin-vue-router": "^0.19.2",
+ "unplugin-yaml": "^4.0.1",
+ "vite": "catalog:",
+ "vite-plugin-vue-devtools": "^8.1.1",
+ "vite-plugin-vue-layouts": "^0.11.0",
+ "vue-macros": "^3.1.2",
+ "vue-tsc": "^3.2.6"
+ }
+}
diff --git a/apps/ui-server-auth/public/.assetsignore b/apps/ui-server-auth/public/.assetsignore
new file mode 100644
index 000000000..421c66e16
--- /dev/null
+++ b/apps/ui-server-auth/public/.assetsignore
@@ -0,0 +1,3 @@
+**/node_modules
+**/.DS_Store
+**/.git
diff --git a/apps/ui-server-auth/public/_headers b/apps/ui-server-auth/public/_headers
new file mode 100644
index 000000000..7f40d0894
--- /dev/null
+++ b/apps/ui-server-auth/public/_headers
@@ -0,0 +1,4 @@
+/assets/*
+ cache-control: max-age=31536000
+ cache-control: immutable
+
diff --git a/apps/ui-server-auth/public/apple-touch-icon.png b/apps/ui-server-auth/public/apple-touch-icon.png
new file mode 100644
index 000000000..e3bfed969
Binary files /dev/null and b/apps/ui-server-auth/public/apple-touch-icon.png differ
diff --git a/apps/ui-server-auth/public/favicon.ico b/apps/ui-server-auth/public/favicon.ico
new file mode 100644
index 000000000..a47f8f675
Binary files /dev/null and b/apps/ui-server-auth/public/favicon.ico differ
diff --git a/apps/ui-server-auth/public/favicon.svg b/apps/ui-server-auth/public/favicon.svg
new file mode 100644
index 000000000..eae728aa1
--- /dev/null
+++ b/apps/ui-server-auth/public/favicon.svg
@@ -0,0 +1,61 @@
+
diff --git a/apps/ui-server-auth/public/maskable_icon_x192.png b/apps/ui-server-auth/public/maskable_icon_x192.png
new file mode 100644
index 000000000..9a1728ebd
Binary files /dev/null and b/apps/ui-server-auth/public/maskable_icon_x192.png differ
diff --git a/apps/ui-server-auth/public/maskable_icon_x512.png b/apps/ui-server-auth/public/maskable_icon_x512.png
new file mode 100644
index 000000000..2a56ae126
Binary files /dev/null and b/apps/ui-server-auth/public/maskable_icon_x512.png differ
diff --git a/apps/ui-server-auth/public/open-graph.png b/apps/ui-server-auth/public/open-graph.png
new file mode 100644
index 000000000..a7d26580c
Binary files /dev/null and b/apps/ui-server-auth/public/open-graph.png differ
diff --git a/apps/ui-server-auth/public/web-app-manifest-192x192.png b/apps/ui-server-auth/public/web-app-manifest-192x192.png
new file mode 100644
index 000000000..c046e5331
Binary files /dev/null and b/apps/ui-server-auth/public/web-app-manifest-192x192.png differ
diff --git a/apps/ui-server-auth/public/web-app-manifest-512x512.png b/apps/ui-server-auth/public/web-app-manifest-512x512.png
new file mode 100644
index 000000000..e92eb2fd7
Binary files /dev/null and b/apps/ui-server-auth/public/web-app-manifest-512x512.png differ
diff --git a/apps/ui-server-auth/src/App.vue b/apps/ui-server-auth/src/App.vue
new file mode 100644
index 000000000..9c133e140
--- /dev/null
+++ b/apps/ui-server-auth/src/App.vue
@@ -0,0 +1,34 @@
+
+
+
+
+ toast.dismiss(id)">
+
+
+
+
diff --git a/apps/ui-server-auth/src/main.ts b/apps/ui-server-auth/src/main.ts
new file mode 100644
index 000000000..80c73503d
--- /dev/null
+++ b/apps/ui-server-auth/src/main.ts
@@ -0,0 +1,88 @@
+import type { Plugin } from 'vue'
+import type { Router, RouteRecordRaw } from 'vue-router'
+
+import NProgress from 'nprogress'
+
+import { autoAnimatePlugin } from '@formkit/auto-animate/vue'
+import { isEnvTruthy } from '@proj-airi/stage-shared'
+import { MotionPlugin } from '@vueuse/motion'
+import { createPinia } from 'pinia'
+import { setupLayouts } from 'virtual:generated-layouts'
+import { createApp } from 'vue'
+import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
+import { routes } from 'vue-router/auto-routes'
+
+import App from './App.vue'
+
+import { i18n } from './modules/i18n'
+
+import '@proj-airi/font-cjkfonts-allseto/index.css'
+import '@proj-airi/font-xiaolai/index.css'
+import '@unocss/reset/tailwind.css'
+import 'vue-sonner/style.css'
+import './styles/main.css'
+import 'uno.css'
+
+const pinia = createPinia()
+
+// TODO: vite-plugin-vue-layouts is long deprecated, replace with another layout solution
+const routeRecords = setupLayouts(routes as RouteRecordRaw[])
+
+let router: Router
+if (isEnvTruthy(import.meta.env.VITE_APP_TARGET_HUGGINGFACE_SPACE))
+ router = createRouter({ routes: routeRecords, history: createWebHashHistory() })
+else
+ router = createRouter({ routes: routeRecords, history: createWebHistory() })
+
+router.beforeEach((to, from) => {
+ if (to.path !== from.path)
+ NProgress.start()
+})
+
+router.afterEach(() => {
+ NProgress.done()
+})
+
+createApp(App)
+ .use(MotionPlugin)
+ // TODO: Fix autoAnimatePlugin type error
+ .use(autoAnimatePlugin as unknown as Plugin)
+ .use(router)
+ .use(pinia)
+ .use(i18n)
+ .mount('#app')
+
+if (import.meta.env.DEV && !import.meta.env.SSR) {
+ function captureEvents(el: HTMLElement) {
+ // Force `pointer-events: auto` as DismissableLayer in Reka UI adds
+ // `pointer-events: none` to document body.
+ el.style.pointerEvents = 'auto'
+
+ // We need to capture events inside elements like devtools to prevent them
+ // from leaking to other layers (like DismissableLayer in Reka UI).
+ //
+ // See: https://github.com/unovue/reka-ui/blob/14866201d179b8bae3c8b4346a1ca8eff1c5eaa4/packages/radix-vue/src/DismissableLayer/DismissableLayer.vue#L186-L188
+ el.addEventListener('focus', e => e.stopPropagation(), { capture: true })
+ el.addEventListener('blur', e => e.stopPropagation(), { capture: true })
+ el.addEventListener('pointerdown', e => e.stopPropagation(), { capture: true })
+ }
+
+ const observer = new MutationObserver((mutationsList, observer) => {
+ for (const mutation of mutationsList) {
+ if (mutation.type === 'childList') {
+ const devtoolsContainer = document.getElementById('__vue-devtools-container__')
+
+ if (devtoolsContainer) {
+ captureEvents(devtoolsContainer)
+ observer.disconnect()
+ }
+ }
+ }
+ })
+
+ observer.observe(document.body, { childList: true, subtree: true })
+
+ // Disconnect on timeout in case the MutationObserver is left here forever.
+ // `observer.disconnect()` is idempotent, so it's safe to call it multiple times.
+ setTimeout(() => observer.disconnect(), 15 * 1000)
+}
diff --git a/apps/ui-server-auth/src/modules/i18n.ts b/apps/ui-server-auth/src/modules/i18n.ts
new file mode 100644
index 000000000..438770ebe
--- /dev/null
+++ b/apps/ui-server-auth/src/modules/i18n.ts
@@ -0,0 +1,22 @@
+import messages from '@proj-airi/i18n/locales'
+
+import { resolveSupportedLocale } from '@proj-airi/i18n'
+import { createI18n } from 'vue-i18n'
+
+function getLocale() {
+ let language = localStorage.getItem('settings/language')
+
+ if (!language) {
+ // Fallback to browser language
+ language = navigator.language || 'en'
+ }
+
+ return resolveSupportedLocale(language, Object.keys(messages!))
+}
+
+export const i18n = createI18n({
+ legacy: false,
+ locale: getLocale(),
+ fallbackLocale: 'en',
+ messages,
+})
diff --git a/apps/ui-server-auth/src/pages/electron-callback.vue b/apps/ui-server-auth/src/pages/electron-callback.vue
new file mode 100644
index 000000000..a78fb6fd3
--- /dev/null
+++ b/apps/ui-server-auth/src/pages/electron-callback.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/apps/ui-server-auth/src/pages/index.vue b/apps/ui-server-auth/src/pages/index.vue
new file mode 100644
index 000000000..a78fb6fd3
--- /dev/null
+++ b/apps/ui-server-auth/src/pages/index.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/apps/ui-server-auth/src/pages/sign-in.vue b/apps/ui-server-auth/src/pages/sign-in.vue
new file mode 100644
index 000000000..a78fb6fd3
--- /dev/null
+++ b/apps/ui-server-auth/src/pages/sign-in.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/apps/ui-server-auth/src/styles/main.css b/apps/ui-server-auth/src/styles/main.css
new file mode 100755
index 000000000..6f8e46919
--- /dev/null
+++ b/apps/ui-server-auth/src/styles/main.css
@@ -0,0 +1,65 @@
+@import './transitions.css';
+@import './vue-transitions.css';
+
+:root {
+ --bg-color-light: rgb(255 255 255);
+ --bg-color-dark: rgb(18 18 18);
+ --bg-color: var(--bg-color-light);
+}
+
+/**
+ Disable double-tap "zoom" option in browser on touch devices
+
+ https://stackoverflow.com/a/54207844
+ https://stackoverflow.com/questions/10614481/disable-double-tap-zoom-option-in-browser-on-touch-devices
+ */
+* {
+ touch-action: manipulation;
+}
+
+html,
+body,
+#app {
+ height: 100%;
+ margin: 0;
+ padding: 0;
+ overscroll-behavior: none;
+}
+
+html {
+ background: var(--bg-color);
+ transition: all 0.3s ease-in-out;
+}
+
+html.dark {
+ --bg-color: var(--bg-color-dark);
+ color-scheme: dark;
+}
+
+#nprogress {
+ pointer-events: none;
+}
+
+#nprogress .bar {
+ background: color-mix(in srgb, oklch(95% var(--chromatic-chroma-900) calc(var(--chromatic-hue) + ${0})) 70%, oklch(50% 0 360));
+ opacity: 0.75;
+ position: fixed;
+ z-index: 1031;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 2px;
+}
+
+.dark #nprogress .bar {
+ background: `color-mix(in srgb, oklch(95% var(--chromatic-chroma-900) calc(var(--chromatic-hue) + ${0})) 90%, oklch(90% 0 360))`;
+}
+
+@media (display-mode: standalone) {
+ #nprogress {
+ display: none;
+ }
+ #nprogress .bar {
+ display: none;
+ }
+}
diff --git a/apps/ui-server-auth/src/styles/transitions.css b/apps/ui-server-auth/src/styles/transitions.css
new file mode 100644
index 000000000..82ae842e3
--- /dev/null
+++ b/apps/ui-server-auth/src/styles/transitions.css
@@ -0,0 +1,18 @@
+.slide-away-enter-active,
+.slide-away-leave-active {
+ transition:
+ transform 0.3s ease-in-out,
+ opacity 0.3s ease-in-out;
+}
+
+.slide-away-enter,
+.slide-away-leave-to {
+ transform: translateY(-10px);
+ opacity: 0;
+}
+
+.slide-away-enter-from,
+.slide-away-leave {
+ transform: translateY(10px);
+ opacity: 0;
+}
diff --git a/apps/ui-server-auth/src/styles/vue-transitions.css b/apps/ui-server-auth/src/styles/vue-transitions.css
new file mode 100644
index 000000000..2beab3d5c
--- /dev/null
+++ b/apps/ui-server-auth/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/apps/ui-server-auth/tsconfig.json b/apps/ui-server-auth/tsconfig.json
new file mode 100644
index 000000000..faa0d4ec4
--- /dev/null
+++ b/apps/ui-server-auth/tsconfig.json
@@ -0,0 +1,38 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve",
+ "lib": [
+ "DOM",
+ "ESNext",
+ "DOM.Iterable",
+ "DOM.AsyncIterable"
+ ],
+ "paths": {
+ "@proj-airi/stage-ui/*": [
+ "../../packages/stage-ui/src/*"
+ ],
+ "@proj-airi/stage-layouts/*": [
+ "../../packages/stage-layouts/src/*"
+ ]
+ },
+ "resolveJsonModule": true,
+ "types": [
+ "vitest",
+ "vite/client",
+ "vite-plugin-vue-layouts/client",
+ "vue-macros/macros-global",
+ "unplugin-vue-router/client",
+ "unplugin-info/client"
+ ],
+ "allowJs": true,
+ "strict": true,
+ "skipLibCheck": true
+ },
+ "vueCompilerOptions": {
+ "plugins": [
+ "@vue-macros/volar/define-models",
+ "@vue-macros/volar/define-slots"
+ ]
+ }
+}
diff --git a/apps/ui-server-auth/uno.config.ts b/apps/ui-server-auth/uno.config.ts
new file mode 100644
index 000000000..693fbd0ef
--- /dev/null
+++ b/apps/ui-server-auth/uno.config.ts
@@ -0,0 +1,26 @@
+import { mergeConfigs, presetWebFonts } from 'unocss'
+
+import { presetWebFontsFonts, sharedUnoConfig } from '../../uno.config'
+
+export default mergeConfigs([
+ sharedUnoConfig(),
+ {
+ presets: [
+ presetWebFonts({
+ fonts: {
+ ...presetWebFontsFonts('fontsource'),
+ },
+ timeouts: {
+ warning: 5000,
+ failure: 10000,
+ },
+ }),
+ ],
+ rules: [
+ ['transition-colors-none', {
+ 'transition-property': 'color, background-color, border-color, text-color',
+ 'transition-duration': '0s',
+ }],
+ ],
+ },
+])
diff --git a/apps/ui-server-auth/vite-env.d.ts b/apps/ui-server-auth/vite-env.d.ts
new file mode 100644
index 000000000..af271c835
--- /dev/null
+++ b/apps/ui-server-auth/vite-env.d.ts
@@ -0,0 +1,7 @@
+///
+///
+
+interface ImportMetaEnv {
+ readonly VITE_APP_TARGET_HUGGINGFACE_SPACE: string
+ // more env variables...
+}
diff --git a/apps/ui-server-auth/vite.config.ts b/apps/ui-server-auth/vite.config.ts
new file mode 100644
index 000000000..6f5e00cbe
--- /dev/null
+++ b/apps/ui-server-auth/vite.config.ts
@@ -0,0 +1,105 @@
+import { join, resolve } from 'node:path'
+
+import VueI18n from '@intlify/unplugin-vue-i18n/vite'
+import Vue from '@vitejs/plugin-vue'
+import Unocss from 'unocss/vite'
+import Info from 'unplugin-info/vite'
+import VueRouter from 'unplugin-vue-router/vite'
+import Yaml from 'unplugin-yaml/vite'
+import VueDevTools from 'vite-plugin-vue-devtools'
+import Layouts from 'vite-plugin-vue-layouts'
+import VueMacros from 'vue-macros/vite'
+
+import { defineConfig } from 'vite'
+
+export default defineConfig({
+ optimizeDeps: {
+ exclude: [
+ // Internal Packages
+ '@proj-airi/stage-ui/*',
+ ],
+ },
+
+ resolve: {
+ alias: {
+ '@proj-airi/i18n': resolve(join(import.meta.dirname, '..', '..', 'packages', 'i18n', 'src')),
+ '@proj-airi/stage-ui': resolve(join(import.meta.dirname, '..', '..', 'packages', 'stage-ui', 'src')),
+ '@proj-airi/stage-shared': resolve(join(import.meta.dirname, '..', '..', 'packages', 'stage-shared', 'src')),
+ '@proj-airi/stage-layouts': resolve(join(import.meta.dirname, '..', '..', 'packages', 'stage-layouts', 'src')),
+ },
+ },
+ server: {
+ fs: {
+ // To mute errors like:
+ // The request id ".../node_modules/@fontsource/sniglet/files/sniglet-latin-400-normal.woff" is outside of Vite serving allow list.
+ //
+ // See: https://vite.dev/config/server-options#server-fs-strict
+ strict: false,
+ },
+ warmup: {
+ clientFiles: [
+ `${resolve(join(import.meta.dirname, '..', '..', 'packages', 'stage-ui', 'src'))}/*.vue`,
+ ],
+ },
+ },
+ build: {
+ sourcemap: true,
+ },
+ worker: {
+ format: 'es',
+ rollupOptions: {
+ output: {
+ inlineDynamicImports: false,
+ },
+ },
+ },
+
+ plugins: [
+ Info(),
+
+ Yaml(),
+
+ VueMacros({
+ plugins: {
+ vue: Vue({
+ include: [/\.vue$/, /\.md$/],
+ }),
+ vueJsx: false,
+ },
+ betterDefine: false,
+ }),
+
+ // https://github.com/posva/unplugin-vue-router
+ VueRouter({
+ extensions: ['.vue', '.md'],
+ dts: resolve(import.meta.dirname, 'src/typed-router.d.ts'),
+ importMode: 'async',
+ routesFolder: [
+ resolve(import.meta.dirname, 'src', 'pages'),
+ ],
+ exclude: ['**/components/**'],
+ }),
+
+ // https://github.com/JohnCampionJr/vite-plugin-vue-layouts
+ Layouts({
+ layoutsDirs: [
+ resolve(import.meta.dirname, 'src', 'layouts'),
+ resolve(import.meta.dirname, '..', '..', 'packages', 'stage-layouts', 'src', 'layouts'),
+ ],
+ }),
+
+ // https://github.com/antfu/unocss
+ // see uno.config.ts for config
+ Unocss(),
+
+ // https://github.com/intlify/bundle-tools/tree/main/packages/unplugin-vue-i18n
+ VueI18n({
+ runtimeOnly: true,
+ compositionOnly: true,
+ fullInstall: true,
+ }),
+
+ // https://github.com/webfansplz/vite-plugin-vue-devtools
+ VueDevTools(),
+ ],
+})
diff --git a/package.json b/package.json
index 61f8db771..eb86a78e9 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
"dev:docs": "pnpm -rF @proj-airi/docs run dev",
"dev:ui": "pnpm -rF @proj-airi/stage-ui run story:dev",
"dev:web": "pnpm -rF @proj-airi/stage-web run dev",
+ "dev:server-auth": "pnpm -rF @proj-airi/ui-server-auth run dev",
"dev:pocket:ios": "pnpm -rF @proj-airi/stage-pocket run dev:ios",
"dev:pocket:android": "pnpm -rF @proj-airi/stage-pocket run dev:android",
"dev:server": "pnpm -rF @proj-airi/server-runtime run dev",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5f1936dc8..36fd37bd1 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1876,6 +1876,190 @@ importers:
specifier: ^3.2.6
version: 3.2.6(typescript@5.9.3)
+ apps/ui-server-auth:
+ dependencies:
+ '@date-fns/utc':
+ specifier: ^2.1.1
+ version: 2.1.1
+ '@fontsource-variable/nunito':
+ specifier: 'catalog:'
+ version: 5.2.7
+ '@formkit/auto-animate':
+ specifier: ^0.9.0
+ version: 0.9.0(vue@3.5.30(typescript@5.9.3))
+ '@huggingface/transformers':
+ specifier: ^3.8.1
+ version: 3.8.1
+ '@moeru/eventa':
+ specifier: 'catalog:'
+ version: 1.0.0-beta.3(electron@41.0.3)
+ '@moeru/std':
+ specifier: 'catalog:'
+ version: 0.1.0-beta.17
+ '@proj-airi/font-chillroundm':
+ specifier: workspace:^
+ version: link:../../packages/font-chillroundm
+ '@proj-airi/font-cjkfonts-allseto':
+ specifier: workspace:^
+ version: link:../../packages/font-cjkfonts-allseto
+ '@proj-airi/font-xiaolai':
+ specifier: workspace:^
+ version: link:../../packages/font-xiaolai
+ '@proj-airi/i18n':
+ specifier: workspace:^
+ version: link:../../packages/i18n
+ '@proj-airi/stage-layouts':
+ specifier: workspace:^
+ version: link:../../packages/stage-layouts
+ '@proj-airi/stage-shared':
+ specifier: workspace:^
+ version: link:../../packages/stage-shared
+ '@proj-airi/stage-ui':
+ specifier: workspace:^
+ version: link:../../packages/stage-ui
+ '@proj-airi/ui':
+ specifier: workspace:^
+ version: link:../../packages/ui
+ '@vueuse/core':
+ specifier: ^14.2.1
+ version: 14.2.1(vue@3.5.30(typescript@5.9.3))
+ '@vueuse/shared':
+ specifier: ^14.2.1
+ version: 14.2.1(vue@3.5.30(typescript@5.9.3))
+ animejs:
+ specifier: ^4.3.6
+ version: 4.3.6
+ colorjs.io:
+ specifier: ^0.6.1
+ version: 0.6.1
+ culori:
+ specifier: ^4.0.2
+ version: 4.0.2
+ date-fns:
+ specifier: ^4.1.0
+ version: 4.1.0
+ nanoid:
+ specifier: ^5.1.7
+ version: 5.1.7
+ nprogress:
+ specifier: ^0.2.0
+ version: 0.2.0
+ pinia:
+ specifier: ^3.0.4
+ version: 3.0.4(typescript@5.9.3)(vue@3.5.30(typescript@5.9.3))
+ posthog-js:
+ specifier: 'catalog:'
+ version: 1.306.1
+ uuid:
+ specifier: ^13.0.0
+ version: 13.0.0
+ vaul-vue:
+ specifier: ^0.4.1
+ version: 0.4.1(reka-ui@2.9.2(vue@3.5.30(typescript@5.9.3)))(vue@3.5.30(typescript@5.9.3))
+ vue:
+ specifier: 'catalog:'
+ version: 3.5.30(typescript@5.9.3)
+ vue-i18n:
+ specifier: ^11.3.0
+ version: 11.3.0(vue@3.5.30(typescript@5.9.3))
+ vue-router:
+ specifier: ^5.0.4
+ version: 5.0.4(@vue/compiler-sfc@3.5.30)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.30(typescript@5.9.3)))(vue@3.5.30(typescript@5.9.3))
+ vue-sonner:
+ specifier: ^2.0.9
+ version: 2.0.9(vue@3.5.30(typescript@5.9.3))
+ devDependencies:
+ '@iconify-json/carbon':
+ specifier: ^1.2.19
+ version: 1.2.19
+ '@iconify-json/eos-icons':
+ specifier: ^1.2.4
+ version: 1.2.4
+ '@iconify-json/logos':
+ specifier: 'catalog:'
+ version: 1.2.10
+ '@iconify-json/lucide':
+ specifier: ^1.2.98
+ version: 1.2.98
+ '@iconify-json/mingcute':
+ specifier: ^1.2.7
+ version: 1.2.7
+ '@iconify-json/ph':
+ specifier: ^1.2.2
+ version: 1.2.2
+ '@iconify-json/simple-icons':
+ specifier: ^1.2.75
+ version: 1.2.75
+ '@iconify-json/solar':
+ specifier: ^1.2.5
+ version: 1.2.5
+ '@iconify-json/svg-spinners':
+ specifier: ^1.2.4
+ version: 1.2.4
+ '@iconify-json/tabler':
+ specifier: 'catalog:'
+ version: 1.2.32
+ '@iconify-json/vscode-icons':
+ specifier: ^1.2.45
+ version: 1.2.45
+ '@intlify/unplugin-vue-i18n':
+ specifier: ^11.0.7
+ version: 11.0.7(@vue/compiler-dom@3.5.30)(eslint@10.1.0(jiti@2.6.1))(rollup@4.60.0)(typescript@5.9.3)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-i18n@11.3.0(vue@3.5.30(typescript@5.9.3)))(vue@3.5.30(typescript@5.9.3))
+ '@proj-airi/iconify-meteocons':
+ specifier: 'catalog:'
+ version: 0.1.4
+ '@proj-airi/lobe-icons':
+ specifier: ^1.0.18
+ version: 1.0.18
+ '@shikijs/markdown-it':
+ specifier: ^4.0.2
+ version: 4.0.2
+ '@types/culori':
+ specifier: ^4.0.1
+ version: 4.0.1
+ '@types/nprogress':
+ specifier: ^0.2.3
+ version: 0.2.3
+ '@types/splitpanes':
+ specifier: 'catalog:'
+ version: 2.2.6
+ '@unocss/reset':
+ specifier: ^66.6.7
+ version: 66.6.7
+ '@vitejs/plugin-vue':
+ specifier: ^6.0.5
+ version: 6.0.5(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.30(typescript@5.9.3))
+ '@vue-macros/volar':
+ specifier: ^3.1.2
+ version: 3.1.2(typescript@5.9.3)(vue-tsc@3.2.6(typescript@5.9.3))(vue@3.5.30(typescript@5.9.3))
+ '@vueuse/motion':
+ specifier: ^3.0.3
+ version: 3.0.3(magicast@0.5.2)(vue@3.5.30(typescript@5.9.3))
+ unplugin-info:
+ specifier: ^1.2.4
+ version: 1.2.4(esbuild@0.27.2)(rollup@4.60.0)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
+ unplugin-vue-router:
+ specifier: ^0.19.2
+ version: 0.19.2(@vue/compiler-sfc@3.5.30)(vue-router@5.0.4(@vue/compiler-sfc@3.5.30)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.30(typescript@5.9.3)))(vue@3.5.30(typescript@5.9.3)))(vue@3.5.30(typescript@5.9.3))
+ unplugin-yaml:
+ specifier: ^4.0.1
+ version: 4.0.1(@nuxt/kit@3.20.2(magicast@0.5.2))(esbuild@0.27.2)(rolldown@1.0.0-rc.9)(rollup@4.60.0)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
+ vite:
+ specifier: 'catalog:'
+ version: 8.0.2(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
+ vite-plugin-vue-devtools:
+ specifier: ^8.1.1
+ version: 8.1.1(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.30(typescript@5.9.3))
+ vite-plugin-vue-layouts:
+ specifier: ^0.11.0
+ version: 0.11.0(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-router@5.0.4(@vue/compiler-sfc@3.5.30)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.30(typescript@5.9.3)))(vue@3.5.30(typescript@5.9.3)))(vue@3.5.30(typescript@5.9.3))
+ vue-macros:
+ specifier: ^3.1.2
+ version: 3.1.2(@vueuse/core@14.2.1(vue@3.5.30(typescript@5.9.3)))(esbuild@0.27.2)(rolldown@1.0.0-rc.9)(rollup@4.60.0)(typescript@5.9.3)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-tsc@3.2.6(typescript@5.9.3))(vue@3.5.30(typescript@5.9.3))
+ vue-tsc:
+ specifier: ^3.2.6
+ version: 3.2.6(typescript@5.9.3)
+
docs:
dependencies:
'@fontsource-variable/comfortaa':