fix: use deep merge for config updates and save inputs on blur
- Backend: replace shallow merge with recursive deepMerge in PUT /api/hermes/config to prevent nested config fields from being lost when updating partial values - Frontend: switch all NInput fields to default-value + @change (save on blur) instead of :value + @update:value (save on every keystroke) in both PlatformSettings.vue and SettingsView.vue api_server tab - Remove unused debounce logic and dead changeKey function Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,29 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue'
|
||||
import { NTabs, NTabPane, NSpin, NSwitch, NInput, NInputNumber, useMessage } from 'naive-ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useSettingsStore } from '@/stores/hermes/settings'
|
||||
import DisplaySettings from '@/components/hermes/settings/DisplaySettings.vue'
|
||||
import AgentSettings from '@/components/hermes/settings/AgentSettings.vue'
|
||||
import MemorySettings from '@/components/hermes/settings/MemorySettings.vue'
|
||||
import SessionSettings from '@/components/hermes/settings/SessionSettings.vue'
|
||||
import PrivacySettings from '@/components/hermes/settings/PrivacySettings.vue'
|
||||
import SettingRow from '@/components/hermes/settings/SettingRow.vue'
|
||||
import { onMounted } from "vue";
|
||||
import {
|
||||
NTabs,
|
||||
NTabPane,
|
||||
NSpin,
|
||||
NSwitch,
|
||||
NInput,
|
||||
NInputNumber,
|
||||
useMessage,
|
||||
} from "naive-ui";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useSettingsStore } from "@/stores/hermes/settings";
|
||||
import DisplaySettings from "@/components/hermes/settings/DisplaySettings.vue";
|
||||
import AgentSettings from "@/components/hermes/settings/AgentSettings.vue";
|
||||
import MemorySettings from "@/components/hermes/settings/MemorySettings.vue";
|
||||
import SessionSettings from "@/components/hermes/settings/SessionSettings.vue";
|
||||
import PrivacySettings from "@/components/hermes/settings/PrivacySettings.vue";
|
||||
import SettingRow from "@/components/hermes/settings/SettingRow.vue";
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const message = useMessage()
|
||||
const { t } = useI18n()
|
||||
const settingsStore = useSettingsStore();
|
||||
const message = useMessage();
|
||||
const { t } = useI18n();
|
||||
|
||||
onMounted(() => {
|
||||
settingsStore.fetchSettings()
|
||||
})
|
||||
|
||||
settingsStore.fetchSettings();
|
||||
});
|
||||
async function saveApiServer(values: Record<string, any>) {
|
||||
try {
|
||||
await settingsStore.saveSection('platforms', { api_server: values })
|
||||
message.success(t('settings.saved'))
|
||||
await settingsStore.saveSection("platforms", { api_server: values });
|
||||
message.success(t("settings.saved"));
|
||||
} catch (err: any) {
|
||||
message.error(t('settings.saveFailed'))
|
||||
message.error(t("settings.saveFailed"));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -31,11 +38,15 @@ async function saveApiServer(values: Record<string, any>) {
|
||||
<template>
|
||||
<div class="settings-view">
|
||||
<header class="page-header">
|
||||
<h2 class="header-title">{{ t('settings.title') }}</h2>
|
||||
<h2 class="header-title">{{ t("settings.title") }}</h2>
|
||||
</header>
|
||||
|
||||
<div class="settings-content">
|
||||
<NSpin :show="settingsStore.loading || settingsStore.saving" size="large" :description="t('common.loading')">
|
||||
<NSpin
|
||||
:show="settingsStore.loading || settingsStore.saving"
|
||||
size="large"
|
||||
:description="t('common.loading')"
|
||||
>
|
||||
<NTabs type="line" animated>
|
||||
<NTabPane name="display" :tab="t('settings.tabs.display')">
|
||||
<DisplaySettings />
|
||||
@@ -54,40 +65,66 @@ async function saveApiServer(values: Record<string, any>) {
|
||||
</NTabPane>
|
||||
<NTabPane name="api_server" :tab="t('settings.tabs.apiServer')">
|
||||
<section class="settings-section">
|
||||
<SettingRow :label="t('settings.apiServer.enable')" :hint="t('settings.apiServer.enableHint')">
|
||||
<SettingRow
|
||||
:label="t('settings.apiServer.enable')"
|
||||
:hint="t('settings.apiServer.enableHint')"
|
||||
>
|
||||
<NSwitch
|
||||
:value="settingsStore.platforms?.api_server?.enabled"
|
||||
@update:value="v => saveApiServer({ enabled: v })"
|
||||
@update:value="(v) => saveApiServer({ enabled: v })"
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow :label="t('settings.apiServer.host')" :hint="t('settings.apiServer.hostHint')">
|
||||
<SettingRow
|
||||
:label="t('settings.apiServer.host')"
|
||||
:hint="t('settings.apiServer.hostHint')"
|
||||
>
|
||||
<NInput
|
||||
:value="settingsStore.platforms?.api_server?.host || ''"
|
||||
size="small" class="input-md"
|
||||
@update:value="v => saveApiServer({ host: v })"
|
||||
:default-value="settingsStore.platforms?.api_server?.host || ''"
|
||||
size="small"
|
||||
class="input-md"
|
||||
@change="(v: string) => saveApiServer({ host: v })"
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow :label="t('settings.apiServer.port')" :hint="t('settings.apiServer.portHint')">
|
||||
<SettingRow
|
||||
:label="t('settings.apiServer.port')"
|
||||
:hint="t('settings.apiServer.portHint')"
|
||||
>
|
||||
<NInputNumber
|
||||
:value="settingsStore.platforms?.api_server?.port"
|
||||
:min="1024" :max="65535"
|
||||
size="small" class="input-sm"
|
||||
@update:value="v => v != null && saveApiServer({ port: v })"
|
||||
:default-value="settingsStore.platforms?.api_server?.port"
|
||||
:min="1024"
|
||||
:max="65535"
|
||||
size="small"
|
||||
class="input-sm"
|
||||
@blur="(e: FocusEvent) => {
|
||||
const val = (e.target as HTMLInputElement).value
|
||||
if (val) saveApiServer({ port: Number(val) })
|
||||
}"
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow :label="t('settings.apiServer.key')" :hint="t('settings.apiServer.keyHint')">
|
||||
<SettingRow
|
||||
:label="t('settings.apiServer.key')"
|
||||
:hint="t('settings.apiServer.keyHint')"
|
||||
>
|
||||
<NInput
|
||||
:value="settingsStore.platforms?.api_server?.key || ''"
|
||||
type="password" show-password-on="click"
|
||||
size="small" class="input-md"
|
||||
@update:value="v => saveApiServer({ key: v })"
|
||||
:default-value="settingsStore.platforms?.api_server?.key || ''"
|
||||
type="password"
|
||||
show-password-on="click"
|
||||
size="small"
|
||||
class="input-md"
|
||||
@change="(v: string) => saveApiServer({ key: v })"
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow :label="t('settings.apiServer.cors')" :hint="t('settings.apiServer.corsHint')">
|
||||
<SettingRow
|
||||
:label="t('settings.apiServer.cors')"
|
||||
:hint="t('settings.apiServer.corsHint')"
|
||||
>
|
||||
<NInput
|
||||
:value="settingsStore.platforms?.api_server?.cors_origins || ''"
|
||||
size="small" class="input-md"
|
||||
@update:value="v => saveApiServer({ cors_origins: v })"
|
||||
:default-value="
|
||||
settingsStore.platforms?.api_server?.cors_origins || ''
|
||||
"
|
||||
size="small"
|
||||
class="input-md"
|
||||
@change="(v: string) => saveApiServer({ cors_origins: v })"
|
||||
/>
|
||||
</SettingRow>
|
||||
</section>
|
||||
@@ -99,7 +136,7 @@ async function saveApiServer(values: Record<string, any>) {
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use '@/styles/variables' as *;
|
||||
@use "@/styles/variables" as *;
|
||||
|
||||
.settings-view {
|
||||
height: calc(100 * var(--vh));
|
||||
|
||||
Reference in New Issue
Block a user