Files
hermes-web/packages/client/src/App.vue
T
toor 4baccd5d88 Phase 005: vendor ArkScope, land the Arco style baseline, and fix bridge interpreter resolution
- Vendor ArkScope decorative layers (ScrambleTitle / IsometricWave) with provenance notes; decorative-only per AGENTS.md red lines
- Migrate theme tokens to Arco, register the Arco theme, and add hook adapter composables (useMessage / useDialog / useNotification) with matching signatures
- Sample pages: LoginView and AppSidebar on the new baseline; docs/arco-mapping.md and docs/ui-baseline.md land
- agent-bridge: prefer the PM-selected venv from facts.json over legacy in-tree venvs (legacy lacks the mcp extra, so every MCP server showed 0 tools / not connected)
- agent-bridge python (vendored, deliberate deviation): MCP discovery import fallback and pyyaml -> hermes_yaml so plugins and MCP work under the PM env; install the mcp extra via `hermes pm install --extra mcp`
- Chat-chain fragment: docs/chat-chain-changes/2026-10-17-phase005-arkscope-baseline.md

Verified: harness:check, test (2020 passed), test:e2e (30 passed), build all green; manual: plugins 100/0 warnings, MCP 6/6 servers connected with 104 tools.
2026-10-17 09:45:30 +08:00

131 lines
3.5 KiB
Vue

<script setup lang="ts">
import { onUnmounted, computed, watch } from 'vue'
import { useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useTheme } from '@/composables/useTheme'
import AppSidebar from '@/components/layout/AppSidebar.vue'
import { useKeyboard } from '@/composables/useKeyboard'
import { useAppStore } from '@/stores/hermes/app'
import SessionSearchModal from '@/components/hermes/chat/SessionSearchModal.vue'
import AuthEventListener from '@/components/auth/AuthEventListener.vue'
import DefaultCredentialPrompt from '@/components/auth/DefaultCredentialPrompt.vue'
const { isDark } = useTheme()
const { t } = useI18n()
const appStore = useAppStore()
const route = useRoute()
const isLoginPage = computed(() => route.name === 'login')
const usesPageSidebar = computed(() =>
['hermes.chat', 'hermes.session', 'hermes.history', 'hermes.historySession', 'hermes.globalAgent', 'hermes.globalAgentSession', 'hermes.groupChat', 'hermes.groupChatRoom', 'hermes.workflow'].includes(route.name as string),
)
const showAppSidebar = computed(() => !isLoginPage.value && !usesPageSidebar.value)
const showMobileMenuButton = computed(() => !isLoginPage.value && (showAppSidebar.value || usesPageSidebar.value))
const nodeVersionLow = computed(() => {
const v = appStore.nodeVersion
const major = parseInt(v.split('.')[0], 10)
return !isNaN(major) && major < 23
})
function handleMobileMenuClick() {
if (usesPageSidebar.value) {
window.dispatchEvent(new CustomEvent('hermes:open-page-sidebar'))
return
}
appStore.toggleSidebar()
}
watch(isLoginPage, (loginPage) => {
if (loginPage) {
appStore.stopHealthPolling()
return
}
appStore.loadModels()
appStore.startHealthPolling()
}, {
immediate: true,
})
onUnmounted(() => {
appStore.stopHealthPolling()
})
useKeyboard()
</script>
<template>
<div class="app-root" :class="{ dark: isDark }">
<AuthEventListener />
<div class="app-shell">
<div v-if="nodeVersionLow" class="node-warning-bar">
{{ t('sidebar.nodeVersionWarning', { version: appStore.nodeVersion }) }}
</div>
<div class="app-layout" :class="{ 'no-sidebar': isLoginPage || !showAppSidebar }">
<button v-if="showMobileMenuButton" class="hamburger-btn" @click="handleMobileMenuClick">
<img src="/logo.png" alt="Menu" style="width: 24px; height: 24px;" />
</button>
<div v-if="!isLoginPage && showAppSidebar && appStore.sidebarOpen" class="mobile-backdrop" @click="appStore.closeSidebar" />
<AppSidebar v-if="!isLoginPage && showAppSidebar" />
<main class="app-main">
<router-view />
</main>
</div>
</div>
<SessionSearchModal />
<DefaultCredentialPrompt />
</div>
</template>
<style scoped lang="scss">
@use '@/styles/variables' as *;
.app-shell {
height: calc(100 * var(--vh));
width: 100%;
max-width: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
background-color: $bg-primary;
}
.app-layout {
display: flex;
flex: 1;
min-height: 0;
width: 100%;
max-width: 100%;
overflow: hidden;
&.no-sidebar {
display: block;
}
}
.app-main {
flex: 1;
min-width: 0;
overflow-y: auto;
background-color: $bg-primary;
.no-sidebar & {
height: 100%;
}
}
.node-warning-bar {
flex: 0 0 auto;
width: 100%;
z-index: 100;
padding: 4px 16px;
font-size: 12px;
font-weight: 500;
color: #b45309;
background-color: #fef3c7;
border-bottom: 1px solid #fde68a;
text-align: center;
line-height: 1.4;
}
</style>