mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-30 12:50:14 +08:00
feat(hermes): add human delay settings
This commit is contained in:
@@ -63,6 +63,12 @@ const SECURITY_DEFAULTS = {
|
||||
tirithFailOpen: true,
|
||||
}
|
||||
|
||||
const HUMAN_DELAY_DEFAULTS = {
|
||||
humanDelayMode: 'off',
|
||||
humanDelayMinMs: 800,
|
||||
humanDelayMaxMs: 2500,
|
||||
}
|
||||
|
||||
const STREAMING_DEFAULTS = {
|
||||
enabled: false,
|
||||
transport: 'edit',
|
||||
@@ -103,6 +109,7 @@ 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 HUMAN_DELAY_MODES = ['off', 'natural', 'custom']
|
||||
|
||||
export function render() {
|
||||
const el = document.createElement('div')
|
||||
@@ -117,6 +124,7 @@ export function render() {
|
||||
let quickCommandsValues = { ...QUICK_COMMANDS_DEFAULTS }
|
||||
let unauthorizedDmValues = { ...UNAUTHORIZED_DM_DEFAULTS }
|
||||
let securityValues = { ...SECURITY_DEFAULTS }
|
||||
let humanDelayValues = { ...HUMAN_DELAY_DEFAULTS }
|
||||
let streamingValues = { ...STREAMING_DEFAULTS }
|
||||
let executionLimitsValues = { ...EXECUTION_LIMITS_DEFAULTS }
|
||||
let terminalValues = { ...TERMINAL_DEFAULTS }
|
||||
@@ -129,6 +137,7 @@ export function render() {
|
||||
let quickCommandsLoading = true
|
||||
let unauthorizedDmLoading = true
|
||||
let securityLoading = true
|
||||
let humanDelayLoading = true
|
||||
let streamingLoading = true
|
||||
let executionLimitsLoading = true
|
||||
let terminalLoading = true
|
||||
@@ -141,6 +150,7 @@ export function render() {
|
||||
let quickCommandsSaving = false
|
||||
let unauthorizedDmSaving = false
|
||||
let securitySaving = false
|
||||
let humanDelaySaving = false
|
||||
let streamingSaving = false
|
||||
let executionLimitsSaving = false
|
||||
let terminalSaving = false
|
||||
@@ -153,6 +163,7 @@ export function render() {
|
||||
let quickCommandsError = null
|
||||
let unauthorizedDmError = null
|
||||
let securityError = null
|
||||
let humanDelayError = null
|
||||
let streamingError = null
|
||||
let executionLimitsError = null
|
||||
let terminalError = null
|
||||
@@ -166,7 +177,7 @@ export function render() {
|
||||
}
|
||||
|
||||
function isBusy() {
|
||||
return loading || runtimeLoading || compressionLoading || toolGuardrailsLoading || memoryLoading || skillsLoading || quickCommandsLoading || unauthorizedDmLoading || securityLoading || streamingLoading || executionLimitsLoading || terminalLoading || saving || runtimeSaving || compressionSaving || toolGuardrailsSaving || memorySaving || skillsSaving || quickCommandsSaving || unauthorizedDmSaving || securitySaving || streamingSaving || executionLimitsSaving || terminalSaving
|
||||
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
|
||||
}
|
||||
|
||||
function option(labelKey, value, selected) {
|
||||
@@ -522,6 +533,44 @@ export function render() {
|
||||
`
|
||||
}
|
||||
|
||||
function renderHumanDelayConfigPanel() {
|
||||
const disabled = loading || saving || humanDelayLoading || humanDelaySaving || runtimeSaving || compressionSaving || toolGuardrailsSaving || memorySaving || skillsSaving || quickCommandsSaving || unauthorizedDmSaving || securitySaving || streamingSaving || executionLimitsSaving || terminalSaving
|
||||
return `
|
||||
<div class="hm-panel hm-config-runtime-panel hm-config-human-delay-panel">
|
||||
<div class="hm-panel-header">
|
||||
<div>
|
||||
<div class="hm-panel-title">${t('engine.hermesHumanDelayConfigTitle')}</div>
|
||||
<div class="hm-channel-panel-desc">${t('engine.hermesHumanDelayConfigDesc')}</div>
|
||||
</div>
|
||||
<div class="hm-panel-actions">
|
||||
<span class="hm-muted">${humanDelaySaving ? t('engine.hermesConfigStatusSaving') : humanDelayLoading ? t('engine.hermesConfigStatusLoading') : t('engine.hermesHumanDelayConfigStatusReady')}</span>
|
||||
<button class="hm-btn hm-btn--cta hm-btn--sm" id="hm-human-delay-save" ${disabled ? 'disabled' : ''}>${t('engine.hermesHumanDelayConfigSave')}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hm-panel-body">
|
||||
${renderError(humanDelayError)}
|
||||
<div class="hm-config-runtime-grid hm-config-human-delay-grid">
|
||||
<label class="hm-field">
|
||||
<span class="hm-field-label">${t('engine.hermesHumanDelayConfigMode')}</span>
|
||||
<select id="hm-human-delay-mode" class="hm-input" ${disabled ? 'disabled' : ''}>
|
||||
${HUMAN_DELAY_MODES.map(mode => option(`engine.hermesHumanDelayConfigMode_${mode}`, mode, humanDelayValues.humanDelayMode)).join('')}
|
||||
</select>
|
||||
</label>
|
||||
<label class="hm-field">
|
||||
<span class="hm-field-label">${t('engine.hermesHumanDelayConfigMinMs')}</span>
|
||||
<input id="hm-human-delay-min-ms" class="hm-input" type="number" inputmode="numeric" min="0" max="60000" step="100" value="${esc(humanDelayValues.humanDelayMinMs)}" ${disabled ? 'disabled' : ''}>
|
||||
</label>
|
||||
<label class="hm-field">
|
||||
<span class="hm-field-label">${t('engine.hermesHumanDelayConfigMaxMs')}</span>
|
||||
<input id="hm-human-delay-max-ms" class="hm-input" type="number" inputmode="numeric" min="0" max="60000" step="100" value="${esc(humanDelayValues.humanDelayMaxMs)}" ${disabled ? 'disabled' : ''}>
|
||||
</label>
|
||||
</div>
|
||||
<div class="hm-channel-footnote">${t('engine.hermesHumanDelayConfigFootnote')}</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
function renderStreamingPanel() {
|
||||
const disabled = loading || saving || streamingLoading || streamingSaving || runtimeSaving || compressionSaving || toolGuardrailsSaving || memorySaving || skillsSaving || quickCommandsSaving || unauthorizedDmSaving || securitySaving || executionLimitsSaving || terminalSaving
|
||||
return `
|
||||
@@ -742,6 +791,7 @@ export function render() {
|
||||
${renderQuickCommandsConfigPanel()}
|
||||
${renderUnauthorizedDmConfigPanel()}
|
||||
${renderSecurityConfigPanel()}
|
||||
${renderHumanDelayConfigPanel()}
|
||||
|
||||
<div class="hm-panel">
|
||||
<div class="hm-panel-header">
|
||||
@@ -769,6 +819,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-human-delay-save')?.addEventListener('click', saveHumanDelayConfig)
|
||||
el.querySelector('#hm-streaming-save')?.addEventListener('click', saveStreaming)
|
||||
el.querySelector('#hm-execution-limits-save')?.addEventListener('click', saveExecutionLimits)
|
||||
el.querySelector('#hm-terminal-save')?.addEventListener('click', saveTerminal)
|
||||
@@ -819,6 +870,11 @@ export function render() {
|
||||
securityValues = { ...SECURITY_DEFAULTS, ...(data?.values || {}) }
|
||||
}
|
||||
|
||||
async function loadHumanDelayConfig() {
|
||||
const data = await api.hermesHumanDelayConfigRead()
|
||||
humanDelayValues = { ...HUMAN_DELAY_DEFAULTS, ...(data?.values || {}) }
|
||||
}
|
||||
|
||||
async function loadStreaming() {
|
||||
const data = await api.hermesStreamingConfigRead()
|
||||
streamingValues = { ...STREAMING_DEFAULTS, ...(data?.values || {}) }
|
||||
@@ -844,6 +900,7 @@ export function render() {
|
||||
quickCommandsLoading = true
|
||||
unauthorizedDmLoading = true
|
||||
securityLoading = true
|
||||
humanDelayLoading = true
|
||||
streamingLoading = true
|
||||
executionLimitsLoading = true
|
||||
terminalLoading = true
|
||||
@@ -856,6 +913,7 @@ export function render() {
|
||||
quickCommandsError = null
|
||||
unauthorizedDmError = null
|
||||
securityError = null
|
||||
humanDelayError = null
|
||||
streamingError = null
|
||||
executionLimitsError = null
|
||||
terminalError = null
|
||||
@@ -955,6 +1013,14 @@ export function render() {
|
||||
securityLoading = false
|
||||
draw()
|
||||
}
|
||||
try {
|
||||
await loadHumanDelayConfig()
|
||||
} catch (err) {
|
||||
humanDelayError = humanizeError(err, t('engine.hermesHumanDelayConfigLoadFailed') || 'Load human delay config failed')
|
||||
} finally {
|
||||
humanDelayLoading = false
|
||||
draw()
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshRawAfterStructuredSave() {
|
||||
@@ -1000,6 +1066,9 @@ export function render() {
|
||||
try {
|
||||
await loadSecurityConfig()
|
||||
} catch {}
|
||||
try {
|
||||
await loadHumanDelayConfig()
|
||||
} catch {}
|
||||
try {
|
||||
await loadStreaming()
|
||||
} catch {}
|
||||
@@ -1243,6 +1312,33 @@ export function render() {
|
||||
}
|
||||
}
|
||||
|
||||
async function saveHumanDelayConfig() {
|
||||
const form = {
|
||||
humanDelayMode: el.querySelector('#hm-human-delay-mode')?.value || 'off',
|
||||
humanDelayMinMs: el.querySelector('#hm-human-delay-min-ms')?.value || '800',
|
||||
humanDelayMaxMs: el.querySelector('#hm-human-delay-max-ms')?.value || '2500',
|
||||
}
|
||||
humanDelaySaving = true
|
||||
humanDelayError = null
|
||||
draw()
|
||||
try {
|
||||
const result = await api.hermesHumanDelayConfigSave(form)
|
||||
humanDelayValues = { ...HUMAN_DELAY_DEFAULTS, ...(result?.values || form) }
|
||||
await refreshRawAfterStructuredSave()
|
||||
const backup = result?.backup || ''
|
||||
toast({
|
||||
message: t('engine.hermesHumanDelayConfigSaveSuccess'),
|
||||
hint: backup ? t('engine.hermesConfigBackupHint', { path: backup }) : '',
|
||||
}, 'success')
|
||||
} catch (err) {
|
||||
humanDelayError = humanizeError(err, t('engine.hermesHumanDelayConfigSaveFailed') || 'Save human delay config failed')
|
||||
toast(humanDelayError, 'error')
|
||||
} finally {
|
||||
humanDelaySaving = false
|
||||
draw()
|
||||
}
|
||||
}
|
||||
|
||||
async function saveStreaming() {
|
||||
const form = {
|
||||
enabled: !!el.querySelector('#hm-streaming-enabled')?.checked,
|
||||
|
||||
@@ -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 }),
|
||||
hermesHumanDelayConfigRead: () => invoke('hermes_human_delay_config_read'),
|
||||
hermesHumanDelayConfigSave: (form) => invoke('hermes_human_delay_config_save', { form }),
|
||||
hermesStreamingConfigRead: () => invoke('hermes_streaming_config_read'),
|
||||
hermesStreamingConfigSave: (form) => invoke('hermes_streaming_config_save', { form }),
|
||||
hermesExecutionLimitsConfigRead: () => invoke('hermes_execution_limits_config_read'),
|
||||
|
||||
@@ -640,6 +640,20 @@ 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 中單獨設定。'),
|
||||
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', '結構化設定'),
|
||||
hermesHumanDelayConfigSave: _('保存响应节奏', 'Save pacing', '儲存回應節奏'),
|
||||
hermesHumanDelayConfigSaveSuccess: _('响应节奏已保存,建议重启 Hermes Gateway 生效', 'Response pacing saved. Restart Hermes Gateway to take effect.', '回應節奏已儲存,建議重啟 Hermes Gateway 生效'),
|
||||
hermesHumanDelayConfigLoadFailed: _('加载响应节奏失败', 'Load response pacing failed', '載入回應節奏失敗'),
|
||||
hermesHumanDelayConfigSaveFailed: _('保存响应节奏失败', 'Save response pacing failed', '儲存回應節奏失敗'),
|
||||
hermesHumanDelayConfigMode: _('节奏模式', 'Pacing mode', '節奏模式'),
|
||||
hermesHumanDelayConfigMode_off: _('关闭', 'Off', '關閉'),
|
||||
hermesHumanDelayConfigMode_natural: _('自然节奏', 'Natural pacing', '自然節奏'),
|
||||
hermesHumanDelayConfigMode_custom: _('自定义范围', 'Custom range', '自訂範圍'),
|
||||
hermesHumanDelayConfigMinMs: _('最小延迟 ms', 'Minimum delay ms', '最小延遲 ms'),
|
||||
hermesHumanDelayConfigMaxMs: _('最大延迟 ms', 'Maximum delay ms', '最大延遲 ms'),
|
||||
hermesHumanDelayConfigFootnote: _('natural 使用 800-2500ms;custom 使用下方范围。Signal 等平台可能忽略或仅部分支持该设置。', 'natural uses 800-2500ms; custom uses the range below. Platforms such as Signal may ignore or only partially support this setting.', 'natural 使用 800-2500ms;custom 使用下方範圍。Signal 等平台可能忽略或僅部分支援此設定。'),
|
||||
hermesSecurityConfigTitle: _('Tirith 安全扫描', 'Tirith security scanning', 'Tirith 安全掃描'),
|
||||
hermesSecurityConfigDesc: _('控制终端命令执行前的 Tirith 内容扫描,拦截明显的 URL 伪装、管道执行和注入风险。', 'Control Tirith content scanning before terminal commands run to catch obvious URL spoofing, pipe-to-shell, and injection risks.', '控制終端命令執行前的 Tirith 內容掃描,攔截明顯的 URL 偽裝、管道執行和注入風險。'),
|
||||
hermesSecurityConfigStatusReady: _('结构化配置', 'structured settings', '結構化設定'),
|
||||
|
||||
Reference in New Issue
Block a user