feat: profile-aware routes, provider sync, channel settings improvements
- Add hermes-profile.ts for dynamic profile path resolution (all backend routes now read from active profile directory instead of hardcoded ~/.hermes/) - Add profile switcher dropdown in sidebar, reload page on switch - Sync PROVIDER_PRESETS with Hermes CLI (fix keys: kimi-coding→kimi-for-coding, kilocode→kilo, ai-gateway→vercel, opencode-zen→opencode; remove moonshot) - Sync PROVIDER_ENV_MAP with Hermes models.dev + overlays (correct env var names) - Add gateway restart after adding model provider - Don't write GLM_BASE_URL/KIMI_BASE_URL for zai/kimi (let Hermes auto-detect) - Write API keys to .env and credential_pool for all providers - Built-in providers skip custom_providers in config.yaml - Add debounce + per-field loading state for channel settings inputs - Run hermes setup --reset for profiles without config.yaml - Create empty .env for new profiles (not copied from default) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,12 +2,15 @@
|
||||
import { computed, ref, onMounted, onUnmounted } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useMessage } from "naive-ui";
|
||||
import { useAppStore } from "@/stores/hermes/app";
|
||||
import ModelSelector from "./ModelSelector.vue";
|
||||
import ProfileSelector from "./ProfileSelector.vue";
|
||||
import LanguageSwitch from "./LanguageSwitch.vue";
|
||||
import danceVideo from "@/assets/dance.mp4";
|
||||
|
||||
const { t } = useI18n();
|
||||
const message = useMessage();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const appStore = useAppStore();
|
||||
@@ -63,6 +66,15 @@ onMounted(() => {
|
||||
function handleNav(key: string) {
|
||||
router.push({ name: key });
|
||||
}
|
||||
|
||||
async function handleUpdate() {
|
||||
const ok = await appStore.doUpdate();
|
||||
if (ok) {
|
||||
message.success(t('sidebar.updateSuccess'), { duration: 5000 });
|
||||
} else {
|
||||
message.error(t('sidebar.updateFailed'));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -147,27 +159,6 @@ function handleNav(key: string) {
|
||||
<span>{{ t("sidebar.models") }}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="nav-item"
|
||||
:class="{ active: selectedKey === 'hermes.profiles' }"
|
||||
@click="handleNav('hermes.profiles')"
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
|
||||
<circle cx="12" cy="7" r="4" />
|
||||
</svg>
|
||||
<span>{{ t("sidebar.profiles") }}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="nav-item"
|
||||
:class="{ active: selectedKey === 'hermes.channels' }"
|
||||
@@ -280,6 +271,27 @@ function handleNav(key: string) {
|
||||
<span>{{ t("sidebar.usage") }}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="nav-item"
|
||||
:class="{ active: selectedKey === 'hermes.profiles' }"
|
||||
@click="handleNav('hermes.profiles')"
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
|
||||
<circle cx="12" cy="7" r="4" />
|
||||
</svg>
|
||||
<span>{{ t("sidebar.profiles") }}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="nav-item"
|
||||
:class="{ active: selectedKey === 'hermes.terminal' }"
|
||||
@@ -325,6 +337,7 @@ function handleNav(key: string) {
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<ProfileSelector />
|
||||
<ModelSelector />
|
||||
|
||||
<div class="sidebar-footer">
|
||||
@@ -346,7 +359,10 @@ function handleNav(key: string) {
|
||||
<LanguageSwitch />
|
||||
</div>
|
||||
<div class="version-info">
|
||||
Hermes {{ appStore.serverVersion || "v0.1.0" }}
|
||||
<span>Hermes Web UI v{{ appStore.serverVersion || "0.1.0" }}</span>
|
||||
<a v-if="appStore.updateAvailable" class="update-hint" :class="{ loading: appStore.updating }" @click="handleUpdate">
|
||||
{{ appStore.updating ? t('sidebar.updating') : t('sidebar.updateVersion', { version: appStore.latestVersion }) }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
@@ -483,6 +499,31 @@ function handleNav(key: string) {
|
||||
padding: 2px 12px 8px;
|
||||
font-size: 11px;
|
||||
color: $text-muted;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.update-hint {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
padding: 5px 10px;
|
||||
border-radius: $radius-sm;
|
||||
background: #333333;
|
||||
color: rgba(#fff, 0.7);
|
||||
font-size: 11px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: background $transition-fast;
|
||||
|
||||
&:hover {
|
||||
background: #3d3d3d;
|
||||
}
|
||||
|
||||
&.loading {
|
||||
pointer-events: none;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-mobile) {
|
||||
|
||||
Reference in New Issue
Block a user