mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-06-20 15:09:46 +08:00
feat(hermes): add display tool prefix config
This commit is contained in:
@@ -3599,6 +3599,13 @@ function normalizeHermesDisplayToolProgress(value, strict = false, key = 'displa
|
||||
return 'all'
|
||||
}
|
||||
|
||||
function normalizeHermesDisplayToolPrefix(value, strict = false) {
|
||||
const prefix = String(value ?? '').trim() || '┊'
|
||||
if (prefix.length <= 8 && !/[\r\n\t]/.test(prefix)) return prefix
|
||||
if (strict) throw new Error('display.tool_prefix 必须是 1 到 8 个字符,且不能包含换行或制表符')
|
||||
return '┊'
|
||||
}
|
||||
|
||||
function normalizeHermesDisplayStreaming(value, strict = false, key = 'display.streaming') {
|
||||
if (typeof value === 'boolean') return value ? 'true' : 'false'
|
||||
const streaming = String(value ?? '').trim().toLowerCase() || 'inherit'
|
||||
@@ -3687,6 +3694,7 @@ export function buildHermesDisplayConfigValues(config = {}) {
|
||||
return {
|
||||
displayCompact: readHermesBool(display.compact, false),
|
||||
displaySkin: normalizeHermesDisplaySkin(display.skin, false),
|
||||
displayToolPrefix: normalizeHermesDisplayToolPrefix(display.tool_prefix, false),
|
||||
displayToolProgress: normalizeHermesDisplayToolProgress(display.tool_progress, false),
|
||||
displayShowReasoning: readHermesBool(display.show_reasoning, false),
|
||||
displayToolPreviewLength: parseHermesInteger(display.tool_preview_length, 'display.tool_preview_length', 0, 0, 200000, false),
|
||||
@@ -3720,6 +3728,7 @@ export function mergeHermesDisplayConfig(config = {}, form = {}) {
|
||||
|
||||
display.compact = formHermesBool(form, 'displayCompact', currentValues.displayCompact)
|
||||
display.skin = normalizeHermesDisplaySkin(Object.hasOwn(form, 'displaySkin') ? form.displaySkin : currentValues.displaySkin, true)
|
||||
display.tool_prefix = normalizeHermesDisplayToolPrefix(Object.hasOwn(form, 'displayToolPrefix') ? form.displayToolPrefix : currentValues.displayToolPrefix, true)
|
||||
display.tool_progress = normalizeHermesDisplayToolProgress(Object.hasOwn(form, 'displayToolProgress') ? form.displayToolProgress : currentValues.displayToolProgress, true, 'display.tool_progress')
|
||||
display.show_reasoning = formHermesBool(form, 'displayShowReasoning', currentValues.displayShowReasoning)
|
||||
display.tool_preview_length = parseHermesInteger(Object.hasOwn(form, 'displayToolPreviewLength') ? form.displayToolPreviewLength : currentValues.displayToolPreviewLength, 'display.tool_preview_length', 0, 0, 200000, true)
|
||||
|
||||
@@ -2209,6 +2209,25 @@ fn normalize_hermes_display_tool_progress(
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_hermes_display_tool_prefix(
|
||||
value: Option<String>,
|
||||
strict: bool,
|
||||
) -> Result<String, String> {
|
||||
let prefix = value.unwrap_or_default().trim().to_string();
|
||||
let prefix = if prefix.is_empty() {
|
||||
"┊".to_string()
|
||||
} else {
|
||||
prefix
|
||||
};
|
||||
if prefix.chars().count() <= 8 && !prefix.contains(['\r', '\n', '\t']) {
|
||||
Ok(prefix)
|
||||
} else if strict {
|
||||
Err("display.tool_prefix 必须是 1 到 8 个字符,且不能包含换行或制表符".to_string())
|
||||
} else {
|
||||
Ok("┊".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_hermes_display_streaming_text(
|
||||
value: Option<String>,
|
||||
strict: bool,
|
||||
@@ -6232,6 +6251,10 @@ fn build_hermes_display_config_values(config: &serde_yaml::Value) -> Value {
|
||||
display.and_then(|map| yaml_string_field(map, "skin")),
|
||||
false,
|
||||
).unwrap_or_else(|_| "default".to_string()),
|
||||
"displayToolPrefix": normalize_hermes_display_tool_prefix(
|
||||
display.and_then(|map| yaml_string_field(map, "tool_prefix")),
|
||||
false,
|
||||
).unwrap_or_else(|_| "┊".to_string()),
|
||||
"displayToolProgress": normalize_hermes_display_tool_progress(
|
||||
display.and_then(|map| yaml_string_field(map, "tool_progress")),
|
||||
false,
|
||||
@@ -6338,6 +6361,17 @@ fn merge_hermes_display_config(config: &mut serde_yaml::Value, form: &Value) ->
|
||||
true,
|
||||
)?),
|
||||
);
|
||||
display.insert(
|
||||
yaml_key("tool_prefix"),
|
||||
serde_yaml::Value::String(normalize_hermes_display_tool_prefix(
|
||||
form_string(form, "displayToolPrefix").or_else(|| {
|
||||
current["displayToolPrefix"]
|
||||
.as_str()
|
||||
.map(ToString::to_string)
|
||||
}),
|
||||
true,
|
||||
)?),
|
||||
);
|
||||
display.insert(
|
||||
yaml_key("tool_progress"),
|
||||
serde_yaml::Value::String(tool_progress),
|
||||
@@ -18958,6 +18992,7 @@ mod hermes_display_config_tests {
|
||||
assert_eq!(values["displayToolProgress"], "all");
|
||||
assert_eq!(values["displayCompact"], false);
|
||||
assert_eq!(values["displaySkin"], "default");
|
||||
assert_eq!(values["displayToolPrefix"], "┊");
|
||||
assert_eq!(values["displayShowReasoning"], false);
|
||||
assert_eq!(values["displayToolPreviewLength"], 0);
|
||||
assert_eq!(values["displayCleanupProgress"], false);
|
||||
@@ -18988,6 +19023,7 @@ display:
|
||||
tool_progress: VERBOSE
|
||||
compact: true
|
||||
skin: MONO
|
||||
tool_prefix: "╎"
|
||||
show_reasoning: true
|
||||
tool_preview_length: 80
|
||||
cleanup_progress: true
|
||||
@@ -19016,6 +19052,7 @@ display:
|
||||
assert_eq!(values["displayToolProgress"], "verbose");
|
||||
assert_eq!(values["displayCompact"], true);
|
||||
assert_eq!(values["displaySkin"], "mono");
|
||||
assert_eq!(values["displayToolPrefix"], "╎");
|
||||
assert_eq!(values["displayShowReasoning"], true);
|
||||
assert_eq!(values["displayToolPreviewLength"], 80);
|
||||
assert_eq!(values["displayCleanupProgress"], true);
|
||||
@@ -19064,6 +19101,7 @@ memory:
|
||||
"displayToolProgress": "off",
|
||||
"displayCompact": true,
|
||||
"displaySkin": "slate",
|
||||
"displayToolPrefix": "│",
|
||||
"displayShowReasoning": true,
|
||||
"displayToolPreviewLength": 120,
|
||||
"displayCleanupProgress": true,
|
||||
@@ -19089,6 +19127,7 @@ memory:
|
||||
assert_eq!(config["memory"]["memory_enabled"].as_bool(), Some(true));
|
||||
assert_eq!(config["display"]["compact"].as_bool(), Some(true));
|
||||
assert_eq!(config["display"]["skin"].as_str(), Some("slate"));
|
||||
assert_eq!(config["display"]["tool_prefix"].as_str(), Some("│"));
|
||||
assert_eq!(config["display"]["show_reasoning"].as_bool(), Some(true));
|
||||
assert_eq!(config["display"]["tool_preview_length"].as_i64(), Some(120));
|
||||
assert_eq!(config["display"]["cleanup_progress"].as_bool(), Some(true));
|
||||
@@ -19166,6 +19205,13 @@ memory:
|
||||
.unwrap_err();
|
||||
assert!(err.contains("display.skin"));
|
||||
|
||||
let err = merge_hermes_display_config(
|
||||
&mut config,
|
||||
&json!({ "displayToolPrefix": "too-long-prefix" }),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(err.contains("display.tool_prefix"));
|
||||
|
||||
let err =
|
||||
merge_hermes_display_config(&mut config, &json!({ "displayResumeDisplay": "compact" }))
|
||||
.unwrap_err();
|
||||
|
||||
@@ -147,6 +147,7 @@ const DISPLAY_DEFAULTS = {
|
||||
displayToolProgress: 'all',
|
||||
displayCompact: false,
|
||||
displaySkin: 'default',
|
||||
displayToolPrefix: '┊',
|
||||
displayShowReasoning: false,
|
||||
displayToolPreviewLength: 0,
|
||||
displayCleanupProgress: false,
|
||||
@@ -1327,6 +1328,10 @@ export function render() {
|
||||
${DISPLAY_SKINS.map(mode => option(`engine.hermesDisplayConfigSkin_${mode}`, mode, displayValues.displaySkin)).join('')}
|
||||
</select>
|
||||
</label>
|
||||
<label class="hm-field">
|
||||
<span class="hm-field-label">${t('engine.hermesDisplayConfigToolPrefix')}</span>
|
||||
<input id="hm-display-tool-prefix" class="hm-input" maxlength="8" value="${esc(displayValues.displayToolPrefix)}" ${disabled ? 'disabled' : ''}>
|
||||
</label>
|
||||
<label class="hm-field">
|
||||
<span class="hm-field-label">${t('engine.hermesDisplayConfigLanguage')}</span>
|
||||
<select id="hm-display-language" class="hm-input" ${disabled ? 'disabled' : ''}>
|
||||
@@ -3355,6 +3360,7 @@ export function render() {
|
||||
displayToolProgress: el.querySelector('#hm-display-tool-progress')?.value || 'all',
|
||||
displayCompact: !!el.querySelector('#hm-display-compact')?.checked,
|
||||
displaySkin: el.querySelector('#hm-display-skin')?.value || 'default',
|
||||
displayToolPrefix: el.querySelector('#hm-display-tool-prefix')?.value || '┊',
|
||||
displayShowReasoning: !!el.querySelector('#hm-display-show-reasoning')?.checked,
|
||||
displayToolPreviewLength: el.querySelector('#hm-display-tool-preview-length')?.value || '0',
|
||||
displayCleanupProgress: !!el.querySelector('#hm-display-cleanup-progress')?.checked,
|
||||
|
||||
@@ -945,7 +945,7 @@ export default {
|
||||
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 的默认进度展示、工具预览、推理展示、进度清理、横幅紧凑模式、显示皮肤、最终回复 Markdown、时间戳、完成提醒、终端输出恢复、忙时输入、后台进程通知、静态提示语言、运行信息页脚,以及文件写入失败校验。', 'Control default progress display, tool previews, reasoning visibility, progress cleanup, compact banner mode, display skin, final-response Markdown, timestamps, completion bell, terminal output recovery, busy input handling, background process notifications, static prompt language, runtime footer, and failed file-mutation verification for messaging platforms and CLI.', '控制訊息平台和 CLI 的預設進度顯示、工具預覽、推理展示、進度清理、橫幅緊湊模式、顯示皮膚、最終回覆 Markdown、時間戳、完成提醒、終端輸出恢復、忙時輸入、背景程序通知、靜態提示語言、執行資訊頁腳,以及檔案寫入失敗校驗。'),
|
||||
hermesDisplayConfigDesc: _('控制消息平台和 CLI 的默认进度展示、工具预览、工具输出前缀、推理展示、进度清理、横幅紧凑模式、显示皮肤、最终回复 Markdown、时间戳、完成提醒、终端输出恢复、忙时输入、后台进程通知、静态提示语言、运行信息页脚,以及文件写入失败校验。', 'Control default progress display, tool previews, tool output prefix, reasoning visibility, progress cleanup, compact banner mode, display skin, final-response Markdown, timestamps, completion bell, terminal output recovery, busy input handling, background process notifications, static prompt language, runtime footer, and failed file-mutation verification for messaging platforms and CLI.', '控制訊息平台和 CLI 的預設進度顯示、工具預覽、工具輸出前綴、推理展示、進度清理、橫幅緊湊模式、顯示皮膚、最終回覆 Markdown、時間戳、完成提醒、終端輸出恢復、忙時輸入、背景程序通知、靜態提示語言、執行資訊頁腳,以及檔案寫入失敗校驗。'),
|
||||
hermesDisplayConfigStatusReady: _('结构化配置', 'structured settings', '結構化設定'),
|
||||
hermesDisplayConfigSave: _('保存显示设置', 'Save display settings', '儲存顯示設定'),
|
||||
hermesDisplayConfigSaveSuccess: _('显示与可靠性配置已保存,建议重启 Hermes Gateway 生效', 'Display and reliability settings saved. Restart Hermes Gateway to take effect.', '顯示與可靠性設定已儲存,建議重啟 Hermes Gateway 生效'),
|
||||
@@ -958,6 +958,7 @@ export default {
|
||||
hermesDisplayConfigToolProgress_verbose: _('详细显示参数和结果', 'Verbose args and results', '詳細顯示參數與結果'),
|
||||
hermesDisplayConfigCompact: _('使用紧凑启动横幅', 'Use compact startup banner', '使用緊湊啟動橫幅'),
|
||||
hermesDisplayConfigSkin: _('CLI 显示皮肤', 'CLI display skin', 'CLI 顯示皮膚'),
|
||||
hermesDisplayConfigToolPrefix: _('工具输出前缀', 'Tool output prefix', '工具輸出前綴'),
|
||||
hermesDisplayConfigSkin_default: _('默认', 'Default', '預設'),
|
||||
hermesDisplayConfigSkin_ares: _('Ares', 'Ares', 'Ares'),
|
||||
hermesDisplayConfigSkin_mono: _('Mono', 'Mono', 'Mono'),
|
||||
|
||||
@@ -191,6 +191,7 @@ test('Hermes 配置页会暴露全局显示与可靠性结构化配置字段', (
|
||||
'hm-display-tool-progress',
|
||||
'hm-display-compact',
|
||||
'hm-display-skin',
|
||||
'hm-display-tool-prefix',
|
||||
'hm-display-show-reasoning',
|
||||
'hm-display-tool-preview-length',
|
||||
'hm-display-cleanup-progress',
|
||||
|
||||
@@ -13,6 +13,7 @@ test('Hermes 显示配置读取会提供上游默认值', () => {
|
||||
displayToolProgress: 'all',
|
||||
displayCompact: false,
|
||||
displaySkin: 'default',
|
||||
displayToolPrefix: '┊',
|
||||
displayShowReasoning: false,
|
||||
displayToolPreviewLength: 0,
|
||||
displayCleanupProgress: false,
|
||||
@@ -39,6 +40,7 @@ test('Hermes 显示配置读取会规范化已有字段', () => {
|
||||
tool_progress: 'VERBOSE',
|
||||
compact: true,
|
||||
skin: 'MONO',
|
||||
tool_prefix: '╎',
|
||||
show_reasoning: true,
|
||||
tool_preview_length: 80,
|
||||
cleanup_progress: true,
|
||||
@@ -64,6 +66,7 @@ test('Hermes 显示配置读取会规范化已有字段', () => {
|
||||
assert.equal(values.displayToolProgress, 'verbose')
|
||||
assert.equal(values.displayCompact, true)
|
||||
assert.equal(values.displaySkin, 'mono')
|
||||
assert.equal(values.displayToolPrefix, '╎')
|
||||
assert.equal(values.displayShowReasoning, true)
|
||||
assert.equal(values.displayToolPreviewLength, 80)
|
||||
assert.equal(values.displayCleanupProgress, true)
|
||||
@@ -101,6 +104,7 @@ test('Hermes 显示配置保存会保留未知 YAML 并写入 display', () => {
|
||||
displayToolProgress: 'off',
|
||||
displayCompact: true,
|
||||
displaySkin: 'slate',
|
||||
displayToolPrefix: '│',
|
||||
displayShowReasoning: true,
|
||||
displayToolPreviewLength: 120,
|
||||
displayCleanupProgress: true,
|
||||
@@ -124,6 +128,7 @@ test('Hermes 显示配置保存会保留未知 YAML 并写入 display', () => {
|
||||
assert.deepEqual(next.memory, { memory_enabled: true })
|
||||
assert.equal(next.display.compact, true)
|
||||
assert.equal(next.display.skin, 'slate')
|
||||
assert.equal(next.display.tool_prefix, '│')
|
||||
assert.equal(next.display.show_reasoning, true)
|
||||
assert.equal(next.display.tool_preview_length, 120)
|
||||
assert.equal(next.display.cleanup_progress, true)
|
||||
@@ -155,6 +160,10 @@ test('Hermes 显示配置保存会拒绝非法枚举和页脚字段', () => {
|
||||
() => mergeHermesDisplayConfig({}, { displaySkin: 'unknown' }),
|
||||
/display\.skin/,
|
||||
)
|
||||
assert.throws(
|
||||
() => mergeHermesDisplayConfig({}, { displayToolPrefix: 'too-long-prefix' }),
|
||||
/display\.tool_prefix/,
|
||||
)
|
||||
assert.throws(
|
||||
() => mergeHermesDisplayConfig({}, { displayResumeDisplay: 'compact' }),
|
||||
/display\.resume_display/,
|
||||
|
||||
Reference in New Issue
Block a user