mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-29 20:30:00 +08:00
feat(hermes): add display reliability settings
This commit is contained in:
@@ -63,6 +63,17 @@ const SECURITY_DEFAULTS = {
|
||||
tirithFailOpen: true,
|
||||
}
|
||||
|
||||
const DISPLAY_DEFAULTS = {
|
||||
displayToolProgress: 'all',
|
||||
displayToolProgressCommand: false,
|
||||
displayInterimAssistantMessages: true,
|
||||
displayRuntimeFooterEnabled: false,
|
||||
displayRuntimeFooterFields: 'model\ncontext_pct\ncwd',
|
||||
displayFileMutationVerifier: true,
|
||||
displayLanguage: 'en',
|
||||
displayResumeDisplay: 'full',
|
||||
}
|
||||
|
||||
const HUMAN_DELAY_DEFAULTS = {
|
||||
humanDelayMode: 'off',
|
||||
humanDelayMinMs: 800,
|
||||
@@ -109,6 +120,9 @@ const STREAMING_TRANSPORTS = ['edit', 'auto', 'draft', 'off']
|
||||
const CODE_EXECUTION_MODES = ['project', 'strict']
|
||||
const TERMINAL_BACKENDS = ['local', 'ssh', 'docker', 'singularity', 'modal', 'daytona', 'vercel_sandbox']
|
||||
const UNAUTHORIZED_DM_BEHAVIORS = ['pair', 'ignore']
|
||||
const DISPLAY_TOOL_PROGRESS_VALUES = ['off', 'new', 'all', 'verbose']
|
||||
const DISPLAY_LANGUAGE_VALUES = ['en', 'zh', 'zh-hant', 'ja', 'de', 'es', 'fr', 'tr', 'uk', 'af', 'ko', 'it', 'ga', 'pt', 'ru', 'hu']
|
||||
const DISPLAY_RESUME_VALUES = ['full', 'minimal']
|
||||
const HUMAN_DELAY_MODES = ['off', 'natural', 'custom']
|
||||
|
||||
export function render() {
|
||||
@@ -124,6 +138,7 @@ export function render() {
|
||||
let quickCommandsValues = { ...QUICK_COMMANDS_DEFAULTS }
|
||||
let unauthorizedDmValues = { ...UNAUTHORIZED_DM_DEFAULTS }
|
||||
let securityValues = { ...SECURITY_DEFAULTS }
|
||||
let displayValues = { ...DISPLAY_DEFAULTS }
|
||||
let humanDelayValues = { ...HUMAN_DELAY_DEFAULTS }
|
||||
let streamingValues = { ...STREAMING_DEFAULTS }
|
||||
let executionLimitsValues = { ...EXECUTION_LIMITS_DEFAULTS }
|
||||
@@ -137,6 +152,7 @@ export function render() {
|
||||
let quickCommandsLoading = true
|
||||
let unauthorizedDmLoading = true
|
||||
let securityLoading = true
|
||||
let displayLoading = true
|
||||
let humanDelayLoading = true
|
||||
let streamingLoading = true
|
||||
let executionLimitsLoading = true
|
||||
@@ -150,6 +166,7 @@ export function render() {
|
||||
let quickCommandsSaving = false
|
||||
let unauthorizedDmSaving = false
|
||||
let securitySaving = false
|
||||
let displaySaving = false
|
||||
let humanDelaySaving = false
|
||||
let streamingSaving = false
|
||||
let executionLimitsSaving = false
|
||||
@@ -163,6 +180,7 @@ export function render() {
|
||||
let quickCommandsError = null
|
||||
let unauthorizedDmError = null
|
||||
let securityError = null
|
||||
let displayError = null
|
||||
let humanDelayError = null
|
||||
let streamingError = null
|
||||
let executionLimitsError = null
|
||||
@@ -177,7 +195,7 @@ export function render() {
|
||||
}
|
||||
|
||||
function isBusy() {
|
||||
return loading || runtimeLoading || compressionLoading || toolGuardrailsLoading || memoryLoading || skillsLoading || quickCommandsLoading || unauthorizedDmLoading || securityLoading || humanDelayLoading || streamingLoading || executionLimitsLoading || terminalLoading || saving || runtimeSaving || compressionSaving || toolGuardrailsSaving || memorySaving || skillsSaving || quickCommandsSaving || unauthorizedDmSaving || securitySaving || humanDelaySaving || streamingSaving || executionLimitsSaving || terminalSaving
|
||||
return loading || runtimeLoading || compressionLoading || toolGuardrailsLoading || memoryLoading || skillsLoading || quickCommandsLoading || unauthorizedDmLoading || securityLoading || displayLoading || humanDelayLoading || streamingLoading || executionLimitsLoading || terminalLoading || saving || runtimeSaving || compressionSaving || toolGuardrailsSaving || memorySaving || skillsSaving || quickCommandsSaving || unauthorizedDmSaving || securitySaving || displaySaving || humanDelaySaving || streamingSaving || executionLimitsSaving || terminalSaving
|
||||
}
|
||||
|
||||
function option(labelKey, value, selected) {
|
||||
@@ -533,6 +551,70 @@ export function render() {
|
||||
`
|
||||
}
|
||||
|
||||
function renderDisplayConfigPanel() {
|
||||
const disabled = loading || saving || displayLoading || displaySaving || runtimeSaving || compressionSaving || toolGuardrailsSaving || memorySaving || skillsSaving || quickCommandsSaving || unauthorizedDmSaving || securitySaving || humanDelaySaving || streamingSaving || executionLimitsSaving || terminalSaving
|
||||
return `
|
||||
<div class="hm-panel hm-config-runtime-panel hm-config-display-panel">
|
||||
<div class="hm-panel-header">
|
||||
<div>
|
||||
<div class="hm-panel-title">${t('engine.hermesDisplayConfigTitle')}</div>
|
||||
<div class="hm-channel-panel-desc">${t('engine.hermesDisplayConfigDesc')}</div>
|
||||
</div>
|
||||
<div class="hm-panel-actions">
|
||||
<span class="hm-muted">${displaySaving ? t('engine.hermesConfigStatusSaving') : displayLoading ? t('engine.hermesConfigStatusLoading') : t('engine.hermesDisplayConfigStatusReady')}</span>
|
||||
<button class="hm-btn hm-btn--cta hm-btn--sm" id="hm-display-save" ${disabled ? 'disabled' : ''}>${t('engine.hermesDisplayConfigSave')}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hm-panel-body">
|
||||
${renderError(displayError)}
|
||||
<div class="hm-config-runtime-grid hm-config-display-grid">
|
||||
<label class="hm-field">
|
||||
<span class="hm-field-label">${t('engine.hermesDisplayConfigToolProgress')}</span>
|
||||
<select id="hm-display-tool-progress" class="hm-input" ${disabled ? 'disabled' : ''}>
|
||||
${DISPLAY_TOOL_PROGRESS_VALUES.map(mode => option(`engine.hermesDisplayConfigToolProgress_${mode}`, mode, displayValues.displayToolProgress)).join('')}
|
||||
</select>
|
||||
</label>
|
||||
<label class="hm-field">
|
||||
<span class="hm-field-label">${t('engine.hermesDisplayConfigLanguage')}</span>
|
||||
<select id="hm-display-language" class="hm-input" ${disabled ? 'disabled' : ''}>
|
||||
${DISPLAY_LANGUAGE_VALUES.map(mode => option(`engine.hermesDisplayConfigLanguage_${mode}`, mode, displayValues.displayLanguage)).join('')}
|
||||
</select>
|
||||
</label>
|
||||
<label class="hm-field">
|
||||
<span class="hm-field-label">${t('engine.hermesDisplayConfigResumeDisplay')}</span>
|
||||
<select id="hm-display-resume-display" class="hm-input" ${disabled ? 'disabled' : ''}>
|
||||
${DISPLAY_RESUME_VALUES.map(mode => option(`engine.hermesDisplayConfigResumeDisplay_${mode}`, mode, displayValues.displayResumeDisplay)).join('')}
|
||||
</select>
|
||||
</label>
|
||||
<label class="hm-field">
|
||||
<span class="hm-field-label">${t('engine.hermesDisplayConfigRuntimeFooterFields')}</span>
|
||||
<textarea id="hm-display-runtime-footer-fields" class="hm-input" ${disabled ? 'disabled' : ''} style="min-height:96px;resize:vertical">${esc(displayValues.displayRuntimeFooterFields)}</textarea>
|
||||
</label>
|
||||
</div>
|
||||
<div class="hm-config-check-grid">
|
||||
<label class="hm-channel-check">
|
||||
<input id="hm-display-tool-progress-command" type="checkbox" ${displayValues.displayToolProgressCommand ? 'checked' : ''} ${disabled ? 'disabled' : ''}>
|
||||
<span>${t('engine.hermesDisplayConfigToolProgressCommand')}</span>
|
||||
</label>
|
||||
<label class="hm-channel-check">
|
||||
<input id="hm-display-interim-assistant-messages" type="checkbox" ${displayValues.displayInterimAssistantMessages ? 'checked' : ''} ${disabled ? 'disabled' : ''}>
|
||||
<span>${t('engine.hermesDisplayConfigInterimAssistantMessages')}</span>
|
||||
</label>
|
||||
<label class="hm-channel-check">
|
||||
<input id="hm-display-runtime-footer-enabled" type="checkbox" ${displayValues.displayRuntimeFooterEnabled ? 'checked' : ''} ${disabled ? 'disabled' : ''}>
|
||||
<span>${t('engine.hermesDisplayConfigRuntimeFooterEnabled')}</span>
|
||||
</label>
|
||||
<label class="hm-channel-check">
|
||||
<input id="hm-display-file-mutation-verifier" type="checkbox" ${displayValues.displayFileMutationVerifier ? 'checked' : ''} ${disabled ? 'disabled' : ''}>
|
||||
<span>${t('engine.hermesDisplayConfigFileMutationVerifier')}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="hm-channel-footnote">${t('engine.hermesDisplayConfigFootnote')}</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
function renderHumanDelayConfigPanel() {
|
||||
const disabled = loading || saving || humanDelayLoading || humanDelaySaving || runtimeSaving || compressionSaving || toolGuardrailsSaving || memorySaving || skillsSaving || quickCommandsSaving || unauthorizedDmSaving || securitySaving || streamingSaving || executionLimitsSaving || terminalSaving
|
||||
return `
|
||||
@@ -791,6 +873,7 @@ export function render() {
|
||||
${renderQuickCommandsConfigPanel()}
|
||||
${renderUnauthorizedDmConfigPanel()}
|
||||
${renderSecurityConfigPanel()}
|
||||
${renderDisplayConfigPanel()}
|
||||
${renderHumanDelayConfigPanel()}
|
||||
|
||||
<div class="hm-panel">
|
||||
@@ -819,6 +902,7 @@ export function render() {
|
||||
el.querySelector('#hm-quick-commands-save')?.addEventListener('click', saveQuickCommandsConfig)
|
||||
el.querySelector('#hm-unauthorized-dm-save')?.addEventListener('click', saveUnauthorizedDmConfig)
|
||||
el.querySelector('#hm-security-save')?.addEventListener('click', saveSecurityConfig)
|
||||
el.querySelector('#hm-display-save')?.addEventListener('click', saveDisplayConfig)
|
||||
el.querySelector('#hm-human-delay-save')?.addEventListener('click', saveHumanDelayConfig)
|
||||
el.querySelector('#hm-streaming-save')?.addEventListener('click', saveStreaming)
|
||||
el.querySelector('#hm-execution-limits-save')?.addEventListener('click', saveExecutionLimits)
|
||||
@@ -870,6 +954,11 @@ export function render() {
|
||||
securityValues = { ...SECURITY_DEFAULTS, ...(data?.values || {}) }
|
||||
}
|
||||
|
||||
async function loadDisplayConfig() {
|
||||
const data = await api.hermesDisplayConfigRead()
|
||||
displayValues = { ...DISPLAY_DEFAULTS, ...(data?.values || {}) }
|
||||
}
|
||||
|
||||
async function loadHumanDelayConfig() {
|
||||
const data = await api.hermesHumanDelayConfigRead()
|
||||
humanDelayValues = { ...HUMAN_DELAY_DEFAULTS, ...(data?.values || {}) }
|
||||
@@ -900,6 +989,7 @@ export function render() {
|
||||
quickCommandsLoading = true
|
||||
unauthorizedDmLoading = true
|
||||
securityLoading = true
|
||||
displayLoading = true
|
||||
humanDelayLoading = true
|
||||
streamingLoading = true
|
||||
executionLimitsLoading = true
|
||||
@@ -913,6 +1003,7 @@ export function render() {
|
||||
quickCommandsError = null
|
||||
unauthorizedDmError = null
|
||||
securityError = null
|
||||
displayError = null
|
||||
humanDelayError = null
|
||||
streamingError = null
|
||||
executionLimitsError = null
|
||||
@@ -1013,6 +1104,14 @@ export function render() {
|
||||
securityLoading = false
|
||||
draw()
|
||||
}
|
||||
try {
|
||||
await loadDisplayConfig()
|
||||
} catch (err) {
|
||||
displayError = humanizeError(err, t('engine.hermesDisplayConfigLoadFailed') || 'Load display config failed')
|
||||
} finally {
|
||||
displayLoading = false
|
||||
draw()
|
||||
}
|
||||
try {
|
||||
await loadHumanDelayConfig()
|
||||
} catch (err) {
|
||||
@@ -1066,6 +1165,9 @@ export function render() {
|
||||
try {
|
||||
await loadSecurityConfig()
|
||||
} catch {}
|
||||
try {
|
||||
await loadDisplayConfig()
|
||||
} catch {}
|
||||
try {
|
||||
await loadHumanDelayConfig()
|
||||
} catch {}
|
||||
@@ -1312,6 +1414,38 @@ export function render() {
|
||||
}
|
||||
}
|
||||
|
||||
async function saveDisplayConfig() {
|
||||
const form = {
|
||||
displayToolProgress: el.querySelector('#hm-display-tool-progress')?.value || 'all',
|
||||
displayToolProgressCommand: !!el.querySelector('#hm-display-tool-progress-command')?.checked,
|
||||
displayInterimAssistantMessages: !!el.querySelector('#hm-display-interim-assistant-messages')?.checked,
|
||||
displayRuntimeFooterEnabled: !!el.querySelector('#hm-display-runtime-footer-enabled')?.checked,
|
||||
displayRuntimeFooterFields: el.querySelector('#hm-display-runtime-footer-fields')?.value || 'model\ncontext_pct\ncwd',
|
||||
displayFileMutationVerifier: !!el.querySelector('#hm-display-file-mutation-verifier')?.checked,
|
||||
displayLanguage: el.querySelector('#hm-display-language')?.value || 'en',
|
||||
displayResumeDisplay: el.querySelector('#hm-display-resume-display')?.value || 'full',
|
||||
}
|
||||
displaySaving = true
|
||||
displayError = null
|
||||
draw()
|
||||
try {
|
||||
const result = await api.hermesDisplayConfigSave(form)
|
||||
displayValues = { ...DISPLAY_DEFAULTS, ...(result?.values || form) }
|
||||
await refreshRawAfterStructuredSave()
|
||||
const backup = result?.backup || ''
|
||||
toast({
|
||||
message: t('engine.hermesDisplayConfigSaveSuccess'),
|
||||
hint: backup ? t('engine.hermesConfigBackupHint', { path: backup }) : '',
|
||||
}, 'success')
|
||||
} catch (err) {
|
||||
displayError = humanizeError(err, t('engine.hermesDisplayConfigSaveFailed') || 'Save display config failed')
|
||||
toast(displayError, 'error')
|
||||
} finally {
|
||||
displaySaving = false
|
||||
draw()
|
||||
}
|
||||
}
|
||||
|
||||
async function saveHumanDelayConfig() {
|
||||
const form = {
|
||||
humanDelayMode: el.querySelector('#hm-human-delay-mode')?.value || 'off',
|
||||
|
||||
@@ -525,6 +525,8 @@ export const api = {
|
||||
hermesUnauthorizedDmConfigSave: (form) => invoke('hermes_unauthorized_dm_config_save', { form }),
|
||||
hermesSecurityConfigRead: () => invoke('hermes_security_config_read'),
|
||||
hermesSecurityConfigSave: (form) => invoke('hermes_security_config_save', { form }),
|
||||
hermesDisplayConfigRead: () => invoke('hermes_display_config_read'),
|
||||
hermesDisplayConfigSave: (form) => invoke('hermes_display_config_save', { form }),
|
||||
hermesHumanDelayConfigRead: () => invoke('hermes_human_delay_config_read'),
|
||||
hermesHumanDelayConfigSave: (form) => invoke('hermes_human_delay_config_save', { form }),
|
||||
hermesStreamingConfigRead: () => invoke('hermes_streaming_config_read'),
|
||||
|
||||
@@ -640,6 +640,44 @@ export default {
|
||||
hermesUnauthorizedDmConfigBehavior_pair: _('回复配对码', 'Reply with pairing code', '回覆配對碼'),
|
||||
hermesUnauthorizedDmConfigBehavior_ignore: _('静默忽略', 'Silently ignore', '靜默忽略'),
|
||||
hermesUnauthorizedDmConfigFootnote: _('pair 是默认值,会拒绝访问但在私信中回复一次性配对码;ignore 会静默丢弃陌生私信。平台级覆盖仍可在渠道配置或 raw YAML 中单独设置。', 'pair is the default: Hermes denies access but replies with a one-time pairing code in DMs. ignore silently drops unknown DMs. Platform-level overrides can still be set in channel settings or raw YAML.', 'pair 是預設值,會拒絕存取但在私訊中回覆一次性配對碼;ignore 會靜默丟棄陌生私訊。平台級覆蓋仍可在頻道設定或 raw YAML 中單獨設定。'),
|
||||
hermesDisplayConfigTitle: _('全局显示与可靠性', 'Global display and reliability', '全域顯示與可靠性'),
|
||||
hermesDisplayConfigDesc: _('控制消息平台和 CLI 的默认进度展示、静态提示语言、运行信息页脚,以及文件写入失败校验。', 'Control default progress display, static prompt language, runtime footer, and failed file-mutation verification for messaging platforms and CLI.', '控制訊息平台和 CLI 的預設進度顯示、靜態提示語言、執行資訊頁腳,以及檔案寫入失敗校驗。'),
|
||||
hermesDisplayConfigStatusReady: _('结构化配置', 'structured settings', '結構化設定'),
|
||||
hermesDisplayConfigSave: _('保存显示设置', 'Save display settings', '儲存顯示設定'),
|
||||
hermesDisplayConfigSaveSuccess: _('显示与可靠性配置已保存,建议重启 Hermes Gateway 生效', 'Display and reliability settings saved. Restart Hermes Gateway to take effect.', '顯示與可靠性設定已儲存,建議重啟 Hermes Gateway 生效'),
|
||||
hermesDisplayConfigLoadFailed: _('加载显示与可靠性配置失败', 'Load display and reliability settings failed', '載入顯示與可靠性設定失敗'),
|
||||
hermesDisplayConfigSaveFailed: _('保存显示与可靠性配置失败', 'Save display and reliability settings failed', '儲存顯示與可靠性設定失敗'),
|
||||
hermesDisplayConfigToolProgress: _('默认工具进度', 'Default tool progress', '預設工具進度'),
|
||||
hermesDisplayConfigToolProgress_off: _('关闭', 'Off', '關閉'),
|
||||
hermesDisplayConfigToolProgress_new: _('工具变化时显示', 'Only when tool changes', '工具變化時顯示'),
|
||||
hermesDisplayConfigToolProgress_all: _('显示每次工具调用', 'Show every tool call', '顯示每次工具呼叫'),
|
||||
hermesDisplayConfigToolProgress_verbose: _('详细显示参数和结果', 'Verbose args and results', '詳細顯示參數與結果'),
|
||||
hermesDisplayConfigToolProgressCommand: _('在消息平台启用 /verbose 命令', 'Enable /verbose on messaging platforms', '在訊息平台啟用 /verbose 命令'),
|
||||
hermesDisplayConfigInterimAssistantMessages: _('发送中途状态消息', 'Send interim assistant updates', '傳送中途狀態訊息'),
|
||||
hermesDisplayConfigRuntimeFooterEnabled: _('在最终回复追加运行信息', 'Append runtime footer to final replies', '在最終回覆追加執行資訊'),
|
||||
hermesDisplayConfigRuntimeFooterFields: _('运行信息字段(每行一个)', 'Runtime footer fields, one per line', '執行資訊欄位(每行一個)'),
|
||||
hermesDisplayConfigFileMutationVerifier: _('启用文件写入失败校验', 'Enable failed file-mutation verifier', '啟用檔案寫入失敗校驗'),
|
||||
hermesDisplayConfigLanguage: _('静态提示语言', 'Static prompt language', '靜態提示語言'),
|
||||
hermesDisplayConfigLanguage_en: _('英语', 'English', '英語'),
|
||||
hermesDisplayConfigLanguage_zh: _('简体中文', 'Simplified Chinese', '簡體中文'),
|
||||
'hermesDisplayConfigLanguage_zh-hant': _('繁体中文', 'Traditional Chinese', '繁體中文'),
|
||||
hermesDisplayConfigLanguage_ja: _('日语', 'Japanese', '日語'),
|
||||
hermesDisplayConfigLanguage_de: _('德语', 'German', '德語'),
|
||||
hermesDisplayConfigLanguage_es: _('西班牙语', 'Spanish', '西班牙語'),
|
||||
hermesDisplayConfigLanguage_fr: _('法语', 'French', '法語'),
|
||||
hermesDisplayConfigLanguage_tr: _('土耳其语', 'Turkish', '土耳其語'),
|
||||
hermesDisplayConfigLanguage_uk: _('乌克兰语', 'Ukrainian', '烏克蘭語'),
|
||||
hermesDisplayConfigLanguage_af: _('南非荷兰语', 'Afrikaans', '南非荷蘭語'),
|
||||
hermesDisplayConfigLanguage_ko: _('韩语', 'Korean', '韓語'),
|
||||
hermesDisplayConfigLanguage_it: _('意大利语', 'Italian', '義大利語'),
|
||||
hermesDisplayConfigLanguage_ga: _('爱尔兰语', 'Irish', '愛爾蘭語'),
|
||||
hermesDisplayConfigLanguage_pt: _('葡萄牙语', 'Portuguese', '葡萄牙語'),
|
||||
hermesDisplayConfigLanguage_ru: _('俄语', 'Russian', '俄語'),
|
||||
hermesDisplayConfigLanguage_hu: _('匈牙利语', 'Hungarian', '匈牙利語'),
|
||||
hermesDisplayConfigResumeDisplay: _('恢复会话展示', 'Resume display', '恢復會話顯示'),
|
||||
hermesDisplayConfigResumeDisplay_full: _('显示完整上下文', 'Show full context', '顯示完整上下文'),
|
||||
hermesDisplayConfigResumeDisplay_minimal: _('仅显示一行摘要', 'Show one-line summary', '僅顯示一行摘要'),
|
||||
hermesDisplayConfigFootnote: _('这里写入全局 display 配置;平台级覆盖仍在渠道页管理。display.streaming 是 CLI-only,本面板不会把它误写成 Gateway 全局流式设置。运行信息字段支持 model、context_pct、cwd、duration、tokens、cost。', 'This writes global display settings; per-platform overrides remain in channel settings. display.streaming is CLI-only, so this panel does not write it as a global Gateway streaming setting. Runtime footer fields support model, context_pct, cwd, duration, tokens, and cost.', '這裡寫入全域 display 設定;平台級覆蓋仍在頻道頁管理。display.streaming 是 CLI-only,本面板不會把它誤寫成 Gateway 全域串流設定。執行資訊欄位支援 model、context_pct、cwd、duration、tokens、cost。'),
|
||||
hermesHumanDelayConfigTitle: _('响应节奏', 'Response pacing', '回應節奏'),
|
||||
hermesHumanDelayConfigDesc: _('控制消息平台回复分块之间的等待时间,降低刷屏或模拟更自然发送节奏。', 'Control the wait time between reply chunks on messaging platforms to reduce flooding or mimic a more natural sending rhythm.', '控制訊息平台回覆分塊之間的等待時間,降低刷屏或模擬更自然的傳送節奏。'),
|
||||
hermesHumanDelayConfigStatusReady: _('结构化配置', 'structured settings', '結構化設定'),
|
||||
|
||||
Reference in New Issue
Block a user