192 lines
4.8 KiB
Vue
192 lines
4.8 KiB
Vue
<script setup lang="ts">
|
|
import { computed, onMounted, ref } from "vue";
|
|
import { useRouter } from "vue-router";
|
|
import { useI18n } from "vue-i18n";
|
|
import { getHermesCliStatus, resetHermesCliProbe, type HermesCliStatus } from "@/api/hermes/system";
|
|
|
|
const { t } = useI18n();
|
|
const router = useRouter();
|
|
const rechecking = ref(false);
|
|
const status = ref<HermesCliStatus | null>(null);
|
|
const issue = computed(() => status.value?.reason === 'permission_denied' ? 'permission' : status.value?.reason === 'failed' ? 'failed' : 'missing');
|
|
const title = computed(() => t(issue.value === 'missing' ? 'hermesMissing.title' : `hermesMissing.${issue.value}Title`));
|
|
const intro = computed(() => t(issue.value === 'missing' ? 'hermesMissing.intro' : `hermesMissing.${issue.value}Intro`));
|
|
onMounted(async () => { status.value = await getHermesCliStatus(); });
|
|
|
|
async function handleRetry() {
|
|
rechecking.value = true;
|
|
try {
|
|
resetHermesCliProbe();
|
|
status.value = await getHermesCliStatus();
|
|
if (status.value.available || status.value.reason === 'timeout') {
|
|
router.replace("/hermes/chat");
|
|
}
|
|
} finally {
|
|
rechecking.value = false;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="hermes-missing-view">
|
|
<div class="hermes-missing-card">
|
|
<header class="hermes-missing-hd">
|
|
<div class="hermes-missing-mark" aria-hidden="true">⚠</div>
|
|
<h1 class="hermes-missing-title">{{ title }}</h1>
|
|
<p class="hermes-missing-desc">{{ intro }}</p>
|
|
</header>
|
|
|
|
<div class="hermes-missing-bd">
|
|
<pre v-if="issue === 'missing'" class="hermes-missing-term"><code>{{ t("hermesMissing.installCmd") }}</code></pre>
|
|
<pre v-else-if="status?.bin" class="hermes-missing-term"><code>{{ status.bin }}</code></pre>
|
|
|
|
<p class="hermes-missing-desc">{{ t("hermesMissing.envIntro") }}</p>
|
|
<pre class="hermes-missing-term"><code>{{ t("hermesMissing.envCmd") }}</code></pre>
|
|
|
|
<p class="hermes-missing-hint">{{ t(issue === 'missing' ? "hermesMissing.restartHint" : "hermesMissing.repairHint") }}</p>
|
|
|
|
<button class="hermes-missing-btn" :disabled="rechecking" @click="handleRetry">
|
|
{{ rechecking ? "..." : t("hermesMissing.retry") }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped lang="scss">
|
|
@use "@/styles/variables" as *;
|
|
|
|
.hermes-missing-view {
|
|
height: calc(100 * var(--vh));
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: $bg-primary;
|
|
padding: 20px;
|
|
}
|
|
|
|
.hermes-missing-card {
|
|
width: 560px;
|
|
max-width: calc(100vw - 32px);
|
|
border: 1px solid $border-color;
|
|
border-radius: $radius-lg;
|
|
background: $bg-card;
|
|
box-shadow: $shadow-lg;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.hermes-missing-hd {
|
|
padding: 26px 26px 18px;
|
|
text-align: center;
|
|
}
|
|
|
|
// 缺失态用琥珀橙:需要人介入装 CLI,不是系统自己在动
|
|
.hermes-missing-mark {
|
|
width: 46px;
|
|
height: 46px;
|
|
margin: 0 auto 14px;
|
|
border: 1px solid $warning;
|
|
border-radius: $radius-lg;
|
|
display: grid;
|
|
place-items: center;
|
|
color: $warning;
|
|
font-family: $font-code;
|
|
font-size: 20px;
|
|
line-height: 1;
|
|
box-shadow:
|
|
0 0 0 1px rgba(var(--warning-rgb), 0.2),
|
|
0 0 26px rgba(var(--warning-rgb), 0.16) inset;
|
|
}
|
|
|
|
.hermes-missing-title {
|
|
margin: 0;
|
|
font-family: $font-code;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
line-height: 1.3;
|
|
color: $text-primary;
|
|
}
|
|
|
|
.hermes-missing-desc {
|
|
margin: 9px 0 0;
|
|
font-size: 12.5px;
|
|
line-height: 1.6;
|
|
color: $text-muted;
|
|
}
|
|
|
|
.hermes-missing-bd {
|
|
padding: 0 30px 28px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
|
|
.hermes-missing-desc {
|
|
margin: 0;
|
|
}
|
|
}
|
|
|
|
// 终端块:原型 .term —— 深底 + 等宽,命令直接照抄
|
|
.hermes-missing-term {
|
|
margin: 0;
|
|
padding: 12px 14px;
|
|
border: 1px solid $border-color;
|
|
border-radius: $radius-sm;
|
|
background: $code-bg;
|
|
font-family: $font-code;
|
|
font-size: 12px;
|
|
line-height: 1.65;
|
|
color: $text-secondary;
|
|
white-space: pre-wrap;
|
|
word-break: break-all;
|
|
overflow: auto;
|
|
|
|
code {
|
|
font-family: inherit;
|
|
color: $accent-primary;
|
|
}
|
|
}
|
|
|
|
.hermes-missing-hint {
|
|
margin: 0;
|
|
font-size: 12.5px;
|
|
line-height: 1.6;
|
|
color: $text-secondary;
|
|
}
|
|
|
|
// 主按钮:原型 .btn.pri
|
|
.hermes-missing-btn {
|
|
width: 100%;
|
|
height: 34px;
|
|
padding: 0 12px;
|
|
border: 1px solid $accent-primary;
|
|
border-radius: $radius-sm;
|
|
background: $accent-primary;
|
|
color: var(--text-on-accent);
|
|
font-family: $font-code;
|
|
font-size: 12.5px;
|
|
font-weight: 600;
|
|
letter-spacing: 0.04em;
|
|
cursor: pointer;
|
|
transition: box-shadow $transition-fast, opacity $transition-fast;
|
|
|
|
&:hover:not(:disabled) {
|
|
box-shadow: 0 0 12px rgba(var(--accent-primary-rgb), 0.4);
|
|
}
|
|
|
|
&:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
}
|
|
|
|
@media (max-width: $breakpoint-mobile) {
|
|
.hermes-missing-hd {
|
|
padding: 24px 22px 16px;
|
|
}
|
|
|
|
.hermes-missing-bd {
|
|
padding: 0 22px 22px;
|
|
}
|
|
}
|
|
</style>
|