- 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.
164 lines
6.2 KiB
Vue
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>
|