Files
hermes-web/packages/client/src/components/hermes/settings/DisplaySettings.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

164 lines
6.2 KiB
Vue

<script setup lang="ts">
import { computed } from 'vue'
import { NButton, NSwitch, NSelect, NInputNumber } from 'naive-ui'
import { useMessage } from '@/composables/useMessage'
import { useI18n } from 'vue-i18n'
import { useSettingsStore } from '@/stores/hermes/settings'
import { useTheme, type BrightnessMode } from '@/composables/useTheme'
import { requestCompletionNotificationPermission, showCompletionNotification, type CompletionNotificationPermissionResult } from '@/utils/completion-notification'
import { clampChatInputHeight, MAX_CHAT_INPUT_HEIGHT, MIN_CHAT_INPUT_HEIGHT } from '@/utils/chat-input-height'
import SettingRow from './SettingRow.vue'
const settingsStore = useSettingsStore()
const message = useMessage()
const { t } = useI18n()
const { brightness, setBrightness } = useTheme()
const themeOptions = [
{ label: t('settings.display.themeLight'), value: 'light' },
{ label: t('settings.display.themeDark'), value: 'dark' },
{ label: t('settings.display.themeSystem'), value: 'system' },
]
const chatInputHeight = computed(() => clampChatInputHeight(settingsStore.display.chat_input_height))
async function save(values: Record<string, any>) {
try {
await settingsStore.saveSection('display', values)
message.success(t('settings.saved'))
} catch (err: any) {
message.error(t('settings.saveFailed'))
}
}
function handleThemeChange(val: string) {
const m = val as BrightnessMode
setBrightness(m)
save({ skin: m })
}
function handleChatInputHeightChange(value: number | null) {
return save({ chat_input_height: clampChatInputHeight(value) })
}
function resetChatInputHeight() {
return save({ chat_input_height: null })
}
function notificationPermissionErrorKey(result: CompletionNotificationPermissionResult): string {
if (result.reason === 'insecure') return 'settings.display.notifyOnCompleteInsecure'
if (result.reason === 'unsupported') return 'settings.display.notifyOnCompleteUnsupported'
return 'settings.display.notifyOnCompleteDenied'
}
async function handleNotifyOnCompleteChange(value: boolean) {
if (value) {
const result = await requestCompletionNotificationPermission()
if (!result.granted) {
message.error(t(notificationPermissionErrorKey(result)))
return
}
}
await save({ notify_on_complete: value })
if (value) {
void showCompletionNotification({
title: 'Hermes',
body: t('settings.display.notifyOnCompleteTest'),
icon: '/coding-agents/hermes.png',
tag: `hermes-complete-test-${Date.now()}`,
})
}
}
async function testCompletionNotification() {
const result = await requestCompletionNotificationPermission()
if (!result.granted) {
message.error(t(notificationPermissionErrorKey(result)))
return
}
const shown = await showCompletionNotification({
title: 'Hermes',
body: t('settings.display.notifyOnCompleteTest'),
icon: '/coding-agents/hermes.png',
tag: `hermes-complete-test-${Date.now()}`,
})
if (!shown) {
message.error(t('settings.display.notifyOnCompleteTestFailed'))
return
}
message.success(t('settings.display.notifyOnCompleteTestSent'))
}
</script>
<template>
<section class="settings-section">
<SettingRow :label="t('settings.display.theme')" :hint="t('settings.display.themeHint')">
<NSelect :value="brightness" :options="themeOptions" size="small" :consistent-menu-width="false" class="input-sm" @update:value="handleThemeChange" />
</SettingRow>
<SettingRow :label="t('settings.display.streaming')" :hint="t('settings.display.streamingHint')">
<NSwitch :value="settingsStore.display.streaming" @update:value="v => save({ streaming: v })" />
</SettingRow>
<SettingRow :label="t('settings.display.compact')" :hint="t('settings.display.compactHint')">
<NSwitch :value="settingsStore.display.compact" @update:value="v => save({ compact: v })" />
</SettingRow>
<SettingRow :label="t('settings.display.showReasoning')" :hint="t('settings.display.showReasoningHint')">
<NSwitch :value="settingsStore.display.show_reasoning" @update:value="v => save({ show_reasoning: v })" />
</SettingRow>
<SettingRow :label="t('settings.display.showCost')" :hint="t('settings.display.showCostHint')">
<NSwitch :value="settingsStore.display.show_cost" @update:value="v => save({ show_cost: v })" />
</SettingRow>
<SettingRow :label="t('settings.display.inlineDiffs')" :hint="t('settings.display.inlineDiffsHint')">
<NSwitch :value="settingsStore.display.inline_diffs" @update:value="v => save({ inline_diffs: v })" />
</SettingRow>
<SettingRow :label="t('settings.display.bellOnComplete')" :hint="t('settings.display.bellOnCompleteHint')">
<NSwitch :value="settingsStore.display.bell_on_complete" @update:value="v => save({ bell_on_complete: v })" />
</SettingRow>
<SettingRow :label="t('settings.display.notifyOnComplete')" :hint="`${t('settings.display.notifyOnCompleteHint')} ${t('settings.display.notifyOnCompleteMacHint')}`">
<div class="notify-controls">
<NSwitch :value="settingsStore.display.notify_on_complete" @update:value="handleNotifyOnCompleteChange" />
<NButton size="tiny" secondary @click="testCompletionNotification">
{{ t('settings.display.notifyOnCompleteTestButton') }}
</NButton>
</div>
</SettingRow>
<SettingRow :label="t('settings.display.chatInputHeight')" :hint="t('settings.display.chatInputHeightHint')">
<div class="chat-input-height-controls">
<NInputNumber
:value="chatInputHeight"
:min="MIN_CHAT_INPUT_HEIGHT"
:max="MAX_CHAT_INPUT_HEIGHT"
:step="8"
:show-button="false"
size="small"
class="input-sm"
@update:value="handleChatInputHeightChange"
>
<template #suffix>px</template>
</NInputNumber>
<NButton size="tiny" secondary @click="resetChatInputHeight">
{{ t('common.reset') }}
</NButton>
</div>
</SettingRow>
</section>
</template>
<style scoped lang="scss">
@use '@/styles/variables' as *;
.settings-section {
margin-top: 16px;
}
.notify-controls {
display: inline-flex;
align-items: center;
gap: 10px;
}
.chat-input-height-controls {
display: inline-flex;
align-items: center;
gap: 10px;
}
</style>