feat: AIUI chat mode integration with iframe, context broker, overnight loop
- Chat mode: AIUI loads in sandboxed iframe at /dashboard/chat with transparent bg - Mode switcher: Easy + Pro tabs only, Chat is a launcher button - Keyboard shortcuts: Cmd+1 (Easy), Cmd+2 (Pro), Cmd+3 (Chat), Cmd+M (cycle) - Directional transitions: chat slides from/to left, dashboard from/to right - Context broker: postMessage protocol for quarantined AIUI communication - AI permissions store: user-controlled toggles for data access categories - Settings UI: AI Data Access section with per-category toggles - AIUI container manifest and nginx proxy config for /aiui/ - Deploy script builds AIUI with /aiui/ base path - Overnight loop infrastructure (loop.sh, prepare.sh, plan.md, prompt.md) - Security hooks for autonomous overnight runs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
7b044d22ef
commit
584ce646e1
@@ -34,16 +34,7 @@
|
||||
class="bg-glitch-scan"
|
||||
:class="{ 'glitch-active': isGlitching }"
|
||||
/>
|
||||
<!-- Continuous glitch/flash overlays - same as login, every 5s -->
|
||||
<div
|
||||
class="dashboard-glitch-layer dashboard-glitch-1 bg-fullwidth"
|
||||
:style="{ backgroundImage: `url(/assets/img/${backgroundImage})` }"
|
||||
/>
|
||||
<div
|
||||
class="dashboard-glitch-layer dashboard-glitch-2 bg-fullwidth"
|
||||
:style="{ backgroundImage: `url(/assets/img/${backgroundImage})` }"
|
||||
/>
|
||||
<div class="dashboard-glitch-scan" />
|
||||
<!-- Glitch overlays removed — only intro glitch plays (via isGlitching) -->
|
||||
</div>
|
||||
|
||||
<!-- Oomph accent - brief impact flash when dashboard loads -->
|
||||
@@ -68,6 +59,7 @@
|
||||
|
||||
<!-- Sidebar - Desktop Only, animates in at end with separate parts -->
|
||||
<aside
|
||||
v-show="!chatFullscreen"
|
||||
data-controller-zone="sidebar"
|
||||
class="hidden md:flex w-[256px] flex-shrink-0 relative flex-col z-10"
|
||||
:class="{ 'sidebar-animate': showZoomIn }"
|
||||
@@ -82,11 +74,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-6 pt-4 pb-2 shrink-0">
|
||||
<ModeSwitcher />
|
||||
</div>
|
||||
|
||||
<nav class="sidebar-nav flex-1 min-h-0 space-y-2 p-6 pt-2">
|
||||
<nav class="sidebar-nav flex-1 min-h-0 space-y-2 p-6 pt-4">
|
||||
<RouterLink
|
||||
v-for="(item, idx) in desktopNavItems"
|
||||
:key="item.path"
|
||||
@@ -96,24 +84,30 @@
|
||||
:style="{ '--nav-stagger': idx }"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
v-for="(path, index) in getIconPath(item.icon)"
|
||||
<path
|
||||
v-for="(path, index) in getIconPath(item.icon)"
|
||||
:key="index"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
:d="path"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
:d="path"
|
||||
/>
|
||||
</svg>
|
||||
<span>{{ item.label }}</span>
|
||||
</RouterLink>
|
||||
</nav>
|
||||
|
||||
<div class="sidebar-controller px-6 pb-2 shrink-0">
|
||||
<ControllerIndicator />
|
||||
</div>
|
||||
<!-- Chat launcher button -->
|
||||
<button
|
||||
@click="router.push('/dashboard/chat')"
|
||||
class="chat-launcher-btn w-full flex items-center gap-3 px-4 py-3 rounded-lg transition-all duration-300"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path v-for="(path, index) in getIconPath('chat')" :key="index" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" :d="path" />
|
||||
</svg>
|
||||
<span>Chat</span>
|
||||
</button>
|
||||
|
||||
<div class="sidebar-logout p-6 shrink-0">
|
||||
<!-- Logout - styled as nav item, below Settings -->
|
||||
<button
|
||||
@click="handleLogout"
|
||||
class="sidebar-logout-btn w-full flex items-center gap-3 px-4 py-3 rounded-lg text-white/80 hover:bg-white/10 hover:text-white transition-colors"
|
||||
@@ -123,11 +117,22 @@
|
||||
</svg>
|
||||
<span>Logout</span>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div class="sidebar-controller px-6 pb-2 shrink-0">
|
||||
<ControllerIndicator />
|
||||
</div>
|
||||
|
||||
<!-- Online status pill - bottom of sidebar (desktop only; sidebar is hidden on mobile) -->
|
||||
<!-- Online status -->
|
||||
<div class="px-6 pb-2 shrink-0">
|
||||
<div class="rounded-lg bg-white/5 border border-white/10 px-4 py-2.5">
|
||||
<OnlineStatusPill />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mode switcher -->
|
||||
<div class="px-6 pb-6 shrink-0">
|
||||
<OnlineStatusPill />
|
||||
<ModeSwitcher />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -256,10 +261,17 @@
|
||||
<Transition :name="getTransitionName(route)">
|
||||
<div :key="route.path" class="view-wrapper">
|
||||
<div
|
||||
v-if="route.path === '/dashboard/chat'"
|
||||
class="h-full"
|
||||
>
|
||||
<component :is="Component" />
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
:class="[
|
||||
'px-4 pt-4 pb-4 md:px-8 md:pt-8 md:pb-8 overflow-y-auto h-full',
|
||||
needsMobileBackButtonSpace
|
||||
? 'pb-[calc(var(--mobile-tab-bar-height,_72px)+96px)] md:pb-8'
|
||||
'px-4 pt-4 pb-28 md:px-8 md:pt-8 md:pb-24 overflow-y-auto h-full',
|
||||
needsMobileBackButtonSpace
|
||||
? 'pb-[calc(var(--mobile-tab-bar-height,_72px)+96px)] md:pb-24'
|
||||
: undefined
|
||||
]"
|
||||
>
|
||||
@@ -274,6 +286,7 @@
|
||||
|
||||
<!-- Mobile Bottom Tab Bar - glass piece 5 -->
|
||||
<nav
|
||||
v-show="!chatFullscreen"
|
||||
ref="mobileTabBar"
|
||||
data-mobile-tab-bar
|
||||
class="md:hidden fixed bottom-0 left-0 right-0 border-t border-glass-border shadow-glass z-50 glass-piece"
|
||||
@@ -287,7 +300,7 @@
|
||||
:to="item.path"
|
||||
class="flex items-center justify-center w-full py-3 rounded-lg text-white/70 transition-all duration-300 relative z-10"
|
||||
:class="{
|
||||
'nav-tab-active': item.isCombined
|
||||
'nav-tab-active': item.isCombined
|
||||
? (item.path === '/dashboard/apps'
|
||||
? (route.path.includes('/apps') || route.path.includes('/marketplace'))
|
||||
: (route.path.includes('/cloud') || route.path.includes('/server')))
|
||||
@@ -296,16 +309,25 @@
|
||||
:exact-active-class="item.isCombined ? undefined : 'nav-tab-active'"
|
||||
>
|
||||
<svg class="w-7 h-7 transition-all duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
v-for="(path, index) in getIconPath(item.icon)"
|
||||
<path
|
||||
v-for="(path, index) in getIconPath(item.icon)"
|
||||
:key="index"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
:d="path"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
:d="path"
|
||||
/>
|
||||
</svg>
|
||||
</RouterLink>
|
||||
<!-- Chat launcher -->
|
||||
<button
|
||||
@click="router.push('/dashboard/chat')"
|
||||
class="chat-launcher-btn-mobile flex items-center justify-center w-full py-3 rounded-lg transition-all duration-300 relative z-10"
|
||||
>
|
||||
<svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path v-for="(path, index) in getIconPath('chat')" :key="index" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" :d="path" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
@@ -325,6 +347,10 @@ import { playDashboardLoadOomph } from '@/composables/useLoginSounds'
|
||||
|
||||
const uiMode = useUIModeStore()
|
||||
|
||||
// Chat fullscreen: hide sidebar + mobile nav when on /dashboard/chat (any mode)
|
||||
const chatFullscreen = computed(() => route.path === '/dashboard/chat')
|
||||
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const store = useAppStore()
|
||||
@@ -357,6 +383,7 @@ const ROUTE_BACKGROUNDS: Record<string, string> = {
|
||||
'/dashboard/server': 'bg-network.jpg',
|
||||
'/dashboard/web5': 'bg-web5.jpg',
|
||||
'/dashboard/settings': 'bg-settings.jpg',
|
||||
'/dashboard/chat': 'bg-home.jpg',
|
||||
}
|
||||
|
||||
const backgroundImage = computed(() => {
|
||||
@@ -545,13 +572,12 @@ const gamerDesktopNav: NavItem[] = [
|
||||
|
||||
const easyDesktopNav: NavItem[] = [
|
||||
{ path: '/dashboard', label: 'Home', icon: 'home' },
|
||||
{ path: '/dashboard/apps', label: 'My Services', icon: 'apps' },
|
||||
{ path: '/dashboard/apps', label: 'My Apps', icon: 'apps' },
|
||||
{ path: '/dashboard/settings', label: 'Settings', icon: 'settings' },
|
||||
]
|
||||
|
||||
const chatDesktopNav: NavItem[] = [
|
||||
{ path: '/dashboard', label: 'Home', icon: 'home' },
|
||||
{ path: '/dashboard/chat', label: 'Chat', icon: 'chat' },
|
||||
{ path: '/dashboard/apps', label: 'My Apps', icon: 'apps' },
|
||||
{ path: '/dashboard/settings', label: 'Settings', icon: 'settings' },
|
||||
]
|
||||
@@ -566,19 +592,17 @@ const gamerMobileNav: NavItem[] = [
|
||||
{ path: '/dashboard', label: 'Home', icon: 'home' },
|
||||
{ path: '/dashboard/apps', label: 'Apps', icon: 'apps', isCombined: true },
|
||||
{ path: '/dashboard/cloud', label: 'Network', icon: 'server', isCombined: true },
|
||||
{ path: '/dashboard/web5', label: 'Web5', icon: 'web5' },
|
||||
{ path: '/dashboard/settings', label: 'Settings', icon: 'settings' },
|
||||
]
|
||||
|
||||
const easyMobileNav: NavItem[] = [
|
||||
{ path: '/dashboard', label: 'Home', icon: 'home' },
|
||||
{ path: '/dashboard/apps', label: 'Services', icon: 'apps' },
|
||||
{ path: '/dashboard/apps', label: 'Apps', icon: 'apps' },
|
||||
{ path: '/dashboard/settings', label: 'Settings', icon: 'settings' },
|
||||
]
|
||||
|
||||
const chatMobileNav: NavItem[] = [
|
||||
{ path: '/dashboard', label: 'Home', icon: 'home' },
|
||||
{ path: '/dashboard/chat', label: 'Chat', icon: 'chat' },
|
||||
{ path: '/dashboard/apps', label: 'Apps', icon: 'apps' },
|
||||
{ path: '/dashboard/settings', label: 'Settings', icon: 'settings' },
|
||||
]
|
||||
@@ -628,6 +652,7 @@ const tabOrder = [
|
||||
'/dashboard/cloud',
|
||||
'/dashboard/server',
|
||||
'/dashboard/web5',
|
||||
'/dashboard/chat',
|
||||
'/dashboard/settings'
|
||||
]
|
||||
|
||||
@@ -640,6 +665,18 @@ function getTransitionName(currentRoute: any) {
|
||||
previousPath = currentPath
|
||||
return 'fade'
|
||||
}
|
||||
|
||||
// Chat transitions: directional slide (chat from/to left, dashboard from/to right)
|
||||
const isChat = currentPath === '/dashboard/chat'
|
||||
const wasChat = previousPath === '/dashboard/chat'
|
||||
if (isChat) {
|
||||
previousPath = currentPath
|
||||
return 'chat-open'
|
||||
}
|
||||
if (wasChat) {
|
||||
previousPath = currentPath
|
||||
return 'chat-close'
|
||||
}
|
||||
|
||||
const isAppDetails = currentPath.includes('/apps/') && !currentPath.endsWith('/apps')
|
||||
const isAppsList = currentPath === '/dashboard/apps'
|
||||
@@ -1210,6 +1247,58 @@ aside:not(.sidebar-animate) .sidebar-logout-btn {
|
||||
}
|
||||
}
|
||||
|
||||
/* Chat open transition — chat slides in from left, dashboard slides out to right */
|
||||
.chat-open-enter-active.view-wrapper,
|
||||
.chat-open-leave-active.view-wrapper {
|
||||
transition: opacity 0.5s cubic-bezier(0.22, 1, 0.36, 1), transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.chat-open-enter-from.view-wrapper {
|
||||
opacity: 0;
|
||||
transform: translateX(-60px) scale(0.96);
|
||||
}
|
||||
|
||||
.chat-open-enter-to.view-wrapper {
|
||||
opacity: 1;
|
||||
transform: translateX(0) scale(1);
|
||||
}
|
||||
|
||||
.chat-open-leave-from.view-wrapper {
|
||||
opacity: 1;
|
||||
transform: translateX(0) scale(1);
|
||||
}
|
||||
|
||||
.chat-open-leave-to.view-wrapper {
|
||||
opacity: 0;
|
||||
transform: translateX(60px) scale(0.96);
|
||||
}
|
||||
|
||||
/* Chat close transition — chat slides out to left, dashboard slides in from right */
|
||||
.chat-close-enter-active.view-wrapper,
|
||||
.chat-close-leave-active.view-wrapper {
|
||||
transition: opacity 0.5s cubic-bezier(0.22, 1, 0.36, 1), transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.chat-close-enter-from.view-wrapper {
|
||||
opacity: 0;
|
||||
transform: translateX(60px) scale(0.96);
|
||||
}
|
||||
|
||||
.chat-close-enter-to.view-wrapper {
|
||||
opacity: 1;
|
||||
transform: translateX(0) scale(1);
|
||||
}
|
||||
|
||||
.chat-close-leave-from.view-wrapper {
|
||||
opacity: 1;
|
||||
transform: translateX(0) scale(1);
|
||||
}
|
||||
|
||||
.chat-close-leave-to.view-wrapper {
|
||||
opacity: 0;
|
||||
transform: translateX(-60px) scale(0.96);
|
||||
}
|
||||
|
||||
/* Fade transition for initial loads and default cases */
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
|
||||
Reference in New Issue
Block a user