mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-07-02 21:21:37 +08:00
feat(hermes): add browser advanced controls
This commit is contained in:
@@ -3325,6 +3325,7 @@ const HERMES_STREAMING_TRANSPORTS = new Set(['auto', 'draft', 'edit', 'off'])
|
||||
const HERMES_CODE_EXECUTION_MODES = new Set(['project', 'strict'])
|
||||
const HERMES_TERMINAL_BACKENDS = new Set(['local', 'ssh', 'docker', 'singularity', 'modal', 'daytona', 'vercel_sandbox'])
|
||||
const HERMES_BROWSER_ENGINES = new Set(['auto', 'lightpanda', 'chrome'])
|
||||
const HERMES_BROWSER_DIALOG_POLICIES = new Set(['must_respond', 'auto_dismiss', 'auto_accept'])
|
||||
const HERMES_STT_PROVIDERS = new Set(['auto', 'local', 'groq', 'openai', 'mistral'])
|
||||
const HERMES_STT_LOCAL_MODELS = new Set(['tiny', 'base', 'small', 'medium', 'large-v3', 'turbo'])
|
||||
const HERMES_STT_OPENAI_MODELS = new Set(['whisper-1', 'gpt-4o-mini-transcribe', 'gpt-4o-transcribe'])
|
||||
@@ -3462,6 +3463,13 @@ function normalizeHermesBrowserEngine(value, strict = false) {
|
||||
return 'auto'
|
||||
}
|
||||
|
||||
function normalizeHermesBrowserDialogPolicy(value, strict = false) {
|
||||
const policy = String(value ?? '').trim().toLowerCase() || 'must_respond'
|
||||
if (HERMES_BROWSER_DIALOG_POLICIES.has(policy)) return policy
|
||||
if (strict) throw new Error('browser.dialog_policy 必须是 must_respond、auto_dismiss 或 auto_accept')
|
||||
return 'must_respond'
|
||||
}
|
||||
|
||||
function normalizeHermesSttProvider(value, strict = false) {
|
||||
const provider = String(value ?? '').trim().toLowerCase() || 'auto'
|
||||
if (HERMES_STT_PROVIDERS.has(provider)) return provider
|
||||
@@ -5293,6 +5301,11 @@ export function buildHermesBrowserConfigValues(config = {}) {
|
||||
browserCommandTimeout: parseHermesInteger(browser.command_timeout, 'browser.command_timeout', 30, 5, 3600, false),
|
||||
browserRecordSessions: readHermesBool(browser.record_sessions, false),
|
||||
browserEngine: normalizeHermesBrowserEngine(browser.engine, false),
|
||||
browserAllowPrivateUrls: readHermesBool(browser.allow_private_urls, false),
|
||||
browserAutoLocalForPrivateUrls: readHermesBool(browser.auto_local_for_private_urls, true),
|
||||
browserCdpUrl: normalizeHermesOptionalString(browser.cdp_url, 'browser.cdp_url'),
|
||||
browserDialogPolicy: normalizeHermesBrowserDialogPolicy(browser.dialog_policy, false),
|
||||
browserDialogTimeout: parseHermesInteger(browser.dialog_timeout_s, 'browser.dialog_timeout_s', 300, 1, 86400, false),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5306,6 +5319,13 @@ export function mergeHermesBrowserConfig(config = {}, form = {}) {
|
||||
browser.command_timeout = parseHermesInteger(Object.hasOwn(form, 'browserCommandTimeout') ? form.browserCommandTimeout : currentValues.browserCommandTimeout, 'browser.command_timeout', 30, 5, 3600, true)
|
||||
browser.record_sessions = formHermesBool(form, 'browserRecordSessions', currentValues.browserRecordSessions)
|
||||
browser.engine = normalizeHermesBrowserEngine(Object.hasOwn(form, 'browserEngine') ? form.browserEngine : currentValues.browserEngine, true)
|
||||
browser.allow_private_urls = formHermesBool(form, 'browserAllowPrivateUrls', currentValues.browserAllowPrivateUrls)
|
||||
browser.auto_local_for_private_urls = formHermesBool(form, 'browserAutoLocalForPrivateUrls', currentValues.browserAutoLocalForPrivateUrls)
|
||||
const cdpUrl = normalizeHermesOptionalString(Object.hasOwn(form, 'browserCdpUrl') ? form.browserCdpUrl : currentValues.browserCdpUrl, 'browser.cdp_url')
|
||||
if (cdpUrl) browser.cdp_url = cdpUrl
|
||||
else delete browser.cdp_url
|
||||
browser.dialog_policy = normalizeHermesBrowserDialogPolicy(Object.hasOwn(form, 'browserDialogPolicy') ? form.browserDialogPolicy : currentValues.browserDialogPolicy, true)
|
||||
browser.dialog_timeout_s = parseHermesInteger(Object.hasOwn(form, 'browserDialogTimeout') ? form.browserDialogTimeout : currentValues.browserDialogTimeout, 'browser.dialog_timeout_s', 300, 1, 86400, true)
|
||||
next.browser = browser
|
||||
return next
|
||||
}
|
||||
|
||||
@@ -6892,6 +6892,29 @@ fn normalize_hermes_browser_engine(value: Option<String>, strict: bool) -> Resul
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_hermes_browser_dialog_policy(
|
||||
value: Option<String>,
|
||||
strict: bool,
|
||||
) -> Result<String, String> {
|
||||
let policy = value.unwrap_or_default().trim().to_ascii_lowercase();
|
||||
let policy = if policy.is_empty() {
|
||||
"must_respond".to_string()
|
||||
} else {
|
||||
policy
|
||||
};
|
||||
if matches!(
|
||||
policy.as_str(),
|
||||
"must_respond" | "auto_dismiss" | "auto_accept"
|
||||
) {
|
||||
return Ok(policy);
|
||||
}
|
||||
if strict {
|
||||
Err("browser.dialog_policy 必须是 must_respond、auto_dismiss 或 auto_accept".to_string())
|
||||
} else {
|
||||
Ok("must_respond".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_hermes_stt_provider(value: Option<String>, strict: bool) -> Result<String, String> {
|
||||
let provider = value.unwrap_or_default().trim().to_ascii_lowercase();
|
||||
let provider = if provider.is_empty() {
|
||||
@@ -7785,12 +7808,34 @@ fn build_hermes_browser_config_values(config: &serde_yaml::Value) -> Value {
|
||||
false,
|
||||
)
|
||||
.unwrap_or_else(|_| "auto".to_string());
|
||||
let browser_allow_private_urls = browser
|
||||
.and_then(|map| yaml_bool_field(map, "allow_private_urls"))
|
||||
.unwrap_or(false);
|
||||
let browser_auto_local_for_private_urls = browser
|
||||
.and_then(|map| yaml_bool_field(map, "auto_local_for_private_urls"))
|
||||
.unwrap_or(true);
|
||||
let browser_cdp_url = browser
|
||||
.and_then(|map| yaml_string_field(map, "cdp_url"))
|
||||
.unwrap_or_default();
|
||||
let browser_dialog_policy = normalize_hermes_browser_dialog_policy(
|
||||
browser.and_then(|map| yaml_string_field(map, "dialog_policy")),
|
||||
false,
|
||||
)
|
||||
.unwrap_or_else(|_| "must_respond".to_string());
|
||||
let browser_dialog_timeout = browser
|
||||
.map(|map| bounded_hermes_i64(yaml_i64_field(map, "dialog_timeout_s"), 300, 1, 86400))
|
||||
.unwrap_or(300);
|
||||
|
||||
serde_json::json!({
|
||||
"browserInactivityTimeout": browser_inactivity_timeout,
|
||||
"browserCommandTimeout": browser_command_timeout,
|
||||
"browserRecordSessions": browser_record_sessions,
|
||||
"browserEngine": browser_engine,
|
||||
"browserAllowPrivateUrls": browser_allow_private_urls,
|
||||
"browserAutoLocalForPrivateUrls": browser_auto_local_for_private_urls,
|
||||
"browserCdpUrl": browser_cdp_url,
|
||||
"browserDialogPolicy": browser_dialog_policy,
|
||||
"browserDialogTimeout": browser_dialog_timeout,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7828,6 +7873,51 @@ fn merge_hermes_browser_config(config: &mut serde_yaml::Value, form: &Value) ->
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
let browser_allow_private_urls =
|
||||
form_bool(form, "browserAllowPrivateUrls").unwrap_or_else(|| {
|
||||
current["browserAllowPrivateUrls"]
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
});
|
||||
let browser_auto_local_for_private_urls = form_bool(form, "browserAutoLocalForPrivateUrls")
|
||||
.unwrap_or_else(|| {
|
||||
current["browserAutoLocalForPrivateUrls"]
|
||||
.as_bool()
|
||||
.unwrap_or(true)
|
||||
});
|
||||
let browser_cdp_url = if form.get("browserCdpUrl").is_some() {
|
||||
form_string(form, "browserCdpUrl")
|
||||
.ok_or_else(|| "browser.cdp_url 必须是字符串".to_string())?
|
||||
.trim()
|
||||
.to_string()
|
||||
} else {
|
||||
current["browserCdpUrl"]
|
||||
.as_str()
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.to_string()
|
||||
};
|
||||
let browser_dialog_policy = normalize_hermes_browser_dialog_policy(
|
||||
if form.get("browserDialogPolicy").is_some() {
|
||||
form_string(form, "browserDialogPolicy")
|
||||
} else {
|
||||
current["browserDialogPolicy"]
|
||||
.as_str()
|
||||
.map(ToString::to_string)
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
let browser_dialog_timeout = validate_hermes_i64(
|
||||
if form.get("browserDialogTimeout").is_some() {
|
||||
form_i64(form, "browserDialogTimeout")
|
||||
} else {
|
||||
Some(current["browserDialogTimeout"].as_i64().unwrap_or(300))
|
||||
},
|
||||
"browser.dialog_timeout_s",
|
||||
300,
|
||||
1,
|
||||
86400,
|
||||
)?;
|
||||
|
||||
let root = ensure_yaml_object(config)?;
|
||||
let browser = yaml_child_object(root, "browser")?;
|
||||
@@ -7847,6 +7937,23 @@ fn merge_hermes_browser_config(config: &mut serde_yaml::Value, form: &Value) ->
|
||||
yaml_key("engine"),
|
||||
serde_yaml::Value::String(browser_engine),
|
||||
);
|
||||
browser.insert(
|
||||
yaml_key("allow_private_urls"),
|
||||
serde_yaml::Value::Bool(browser_allow_private_urls),
|
||||
);
|
||||
browser.insert(
|
||||
yaml_key("auto_local_for_private_urls"),
|
||||
serde_yaml::Value::Bool(browser_auto_local_for_private_urls),
|
||||
);
|
||||
set_optional_yaml_string(browser, "cdp_url", browser_cdp_url);
|
||||
browser.insert(
|
||||
yaml_key("dialog_policy"),
|
||||
serde_yaml::Value::String(browser_dialog_policy),
|
||||
);
|
||||
browser.insert(
|
||||
yaml_key("dialog_timeout_s"),
|
||||
serde_yaml::Value::Number(browser_dialog_timeout.into()),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -16370,6 +16477,11 @@ mod hermes_browser_config_tests {
|
||||
assert_eq!(values["browserCommandTimeout"], 30);
|
||||
assert_eq!(values["browserRecordSessions"], false);
|
||||
assert_eq!(values["browserEngine"], "auto");
|
||||
assert_eq!(values["browserAllowPrivateUrls"], false);
|
||||
assert_eq!(values["browserAutoLocalForPrivateUrls"], true);
|
||||
assert_eq!(values["browserCdpUrl"], "");
|
||||
assert_eq!(values["browserDialogPolicy"], "must_respond");
|
||||
assert_eq!(values["browserDialogTimeout"], 300);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -16381,6 +16493,11 @@ browser:
|
||||
command_timeout: 45
|
||||
record_sessions: true
|
||||
engine: lightpanda
|
||||
allow_private_urls: true
|
||||
auto_local_for_private_urls: false
|
||||
cdp_url: ws://127.0.0.1:9222/devtools/browser/demo
|
||||
dialog_policy: auto_accept
|
||||
dialog_timeout_s: 120
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -16389,6 +16506,14 @@ browser:
|
||||
assert_eq!(values["browserCommandTimeout"], 45);
|
||||
assert_eq!(values["browserRecordSessions"], true);
|
||||
assert_eq!(values["browserEngine"], "lightpanda");
|
||||
assert_eq!(values["browserAllowPrivateUrls"], true);
|
||||
assert_eq!(values["browserAutoLocalForPrivateUrls"], false);
|
||||
assert_eq!(
|
||||
values["browserCdpUrl"],
|
||||
"ws://127.0.0.1:9222/devtools/browser/demo"
|
||||
);
|
||||
assert_eq!(values["browserDialogPolicy"], "auto_accept");
|
||||
assert_eq!(values["browserDialogTimeout"], 120);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -16419,6 +16544,11 @@ streaming:
|
||||
"browserCommandTimeout": "60",
|
||||
"browserRecordSessions": true,
|
||||
"browserEngine": "chrome",
|
||||
"browserAllowPrivateUrls": true,
|
||||
"browserAutoLocalForPrivateUrls": false,
|
||||
"browserCdpUrl": "http://127.0.0.1:9222",
|
||||
"browserDialogPolicy": "auto_dismiss",
|
||||
"browserDialogTimeout": "45",
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
@@ -16430,9 +16560,22 @@ streaming:
|
||||
assert_eq!(config["browser"]["record_sessions"].as_bool(), Some(true));
|
||||
assert_eq!(config["browser"]["engine"].as_str(), Some("chrome"));
|
||||
assert_eq!(
|
||||
config["browser"]["cdp_url"].as_str(),
|
||||
Some("ws://127.0.0.1:9222/devtools/browser/demo")
|
||||
config["browser"]["allow_private_urls"].as_bool(),
|
||||
Some(true)
|
||||
);
|
||||
assert_eq!(
|
||||
config["browser"]["auto_local_for_private_urls"].as_bool(),
|
||||
Some(false)
|
||||
);
|
||||
assert_eq!(
|
||||
config["browser"]["cdp_url"].as_str(),
|
||||
Some("http://127.0.0.1:9222")
|
||||
);
|
||||
assert_eq!(
|
||||
config["browser"]["dialog_policy"].as_str(),
|
||||
Some("auto_dismiss")
|
||||
);
|
||||
assert_eq!(config["browser"]["dialog_timeout_s"].as_i64(), Some(45));
|
||||
assert_eq!(
|
||||
config["browser"]["camofox"]["managed_persistence"].as_bool(),
|
||||
Some(true)
|
||||
@@ -16443,6 +16586,26 @@ streaming:
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_browser_config_removes_empty_cdp_url() {
|
||||
let mut config: serde_yaml::Value = serde_yaml::from_str(
|
||||
r#"
|
||||
browser:
|
||||
cdp_url: ws://127.0.0.1:9222/devtools/browser/demo
|
||||
custom_flag: keep-browser
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
merge_hermes_browser_config(&mut config, &json!({ "browserCdpUrl": " " })).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
config["browser"]["custom_flag"].as_str(),
|
||||
Some("keep-browser")
|
||||
);
|
||||
assert!(config["browser"]["cdp_url"].is_null());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_browser_config_rejects_invalid_values() {
|
||||
let mut config = serde_yaml::Value::Mapping(serde_yaml::Mapping::new());
|
||||
@@ -16456,6 +16619,16 @@ streaming:
|
||||
let err = merge_hermes_browser_config(&mut config, &json!({ "browserCommandTimeout": 4 }))
|
||||
.unwrap_err();
|
||||
assert!(err.contains("browser.command_timeout"));
|
||||
let err =
|
||||
merge_hermes_browser_config(&mut config, &json!({ "browserDialogPolicy": "ignore" }))
|
||||
.unwrap_err();
|
||||
assert!(err.contains("browser.dialog_policy"));
|
||||
let err = merge_hermes_browser_config(&mut config, &json!({ "browserDialogTimeout": 0 }))
|
||||
.unwrap_err();
|
||||
assert!(err.contains("browser.dialog_timeout_s"));
|
||||
let err =
|
||||
merge_hermes_browser_config(&mut config, &json!({ "browserCdpUrl": 123 })).unwrap_err();
|
||||
assert!(err.contains("browser.cdp_url"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -260,6 +260,11 @@ const BROWSER_DEFAULTS = {
|
||||
browserCommandTimeout: 30,
|
||||
browserRecordSessions: false,
|
||||
browserEngine: 'auto',
|
||||
browserAllowPrivateUrls: false,
|
||||
browserAutoLocalForPrivateUrls: true,
|
||||
browserCdpUrl: '',
|
||||
browserDialogPolicy: 'must_respond',
|
||||
browserDialogTimeout: 300,
|
||||
}
|
||||
|
||||
const STT_DEFAULTS = {
|
||||
@@ -298,6 +303,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 BROWSER_ENGINES = ['auto', 'lightpanda', 'chrome']
|
||||
const BROWSER_DIALOG_POLICIES = ['must_respond', 'auto_dismiss', 'auto_accept']
|
||||
const STT_PROVIDERS = ['auto', 'local', 'groq', 'openai', 'mistral']
|
||||
const STT_LOCAL_MODELS = ['tiny', 'base', 'small', 'medium', 'large-v3', 'turbo']
|
||||
const STT_OPENAI_MODELS = ['whisper-1', 'gpt-4o-mini-transcribe', 'gpt-4o-transcribe']
|
||||
@@ -1977,12 +1983,34 @@ export function render() {
|
||||
<span class="hm-field-label">${t('engine.hermesBrowserConfigCommandTimeout')}</span>
|
||||
<input id="hm-browser-command-timeout" class="hm-input" type="number" inputmode="numeric" min="5" max="3600" step="1" value="${esc(browserValues.browserCommandTimeout)}" ${disabled ? 'disabled' : ''}>
|
||||
</label>
|
||||
<label class="hm-field">
|
||||
<span class="hm-field-label">${t('engine.hermesBrowserConfigCdpUrl')}</span>
|
||||
<input id="hm-browser-cdp-url" class="hm-input" type="text" value="${esc(browserValues.browserCdpUrl)}" placeholder="${t('engine.hermesBrowserConfigCdpUrlPlaceholder')}" ${disabled ? 'disabled' : ''}>
|
||||
</label>
|
||||
<label class="hm-field">
|
||||
<span class="hm-field-label">${t('engine.hermesBrowserConfigDialogPolicy')}</span>
|
||||
<select id="hm-browser-dialog-policy" class="hm-input" ${disabled ? 'disabled' : ''}>
|
||||
${BROWSER_DIALOG_POLICIES.map(policy => option(`engine.hermesBrowserConfigDialogPolicy_${policy}`, policy, browserValues.browserDialogPolicy)).join('')}
|
||||
</select>
|
||||
</label>
|
||||
<label class="hm-field">
|
||||
<span class="hm-field-label">${t('engine.hermesBrowserConfigDialogTimeout')}</span>
|
||||
<input id="hm-browser-dialog-timeout" class="hm-input" type="number" inputmode="numeric" min="1" max="86400" step="1" value="${esc(browserValues.browserDialogTimeout)}" ${disabled ? 'disabled' : ''}>
|
||||
</label>
|
||||
</div>
|
||||
<div class="hm-config-check-grid">
|
||||
<label class="hm-channel-check">
|
||||
<input id="hm-browser-record-sessions" type="checkbox" ${browserValues.browserRecordSessions ? 'checked' : ''} ${disabled ? 'disabled' : ''}>
|
||||
<span>${t('engine.hermesBrowserConfigRecordSessions')}</span>
|
||||
</label>
|
||||
<label class="hm-channel-check">
|
||||
<input id="hm-browser-allow-private-urls" type="checkbox" ${browserValues.browserAllowPrivateUrls ? 'checked' : ''} ${disabled ? 'disabled' : ''}>
|
||||
<span>${t('engine.hermesBrowserConfigAllowPrivateUrls')}</span>
|
||||
</label>
|
||||
<label class="hm-channel-check">
|
||||
<input id="hm-browser-auto-local-for-private-urls" type="checkbox" ${browserValues.browserAutoLocalForPrivateUrls ? 'checked' : ''} ${disabled ? 'disabled' : ''}>
|
||||
<span>${t('engine.hermesBrowserConfigAutoLocalForPrivateUrls')}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="hm-channel-footnote">${t('engine.hermesBrowserConfigFootnote')}</div>
|
||||
</div>
|
||||
@@ -3818,6 +3846,11 @@ export function render() {
|
||||
browserCommandTimeout: el.querySelector('#hm-browser-command-timeout')?.value || '30',
|
||||
browserRecordSessions: !!el.querySelector('#hm-browser-record-sessions')?.checked,
|
||||
browserEngine: el.querySelector('#hm-browser-engine')?.value || 'auto',
|
||||
browserAllowPrivateUrls: !!el.querySelector('#hm-browser-allow-private-urls')?.checked,
|
||||
browserAutoLocalForPrivateUrls: !!el.querySelector('#hm-browser-auto-local-for-private-urls')?.checked,
|
||||
browserCdpUrl: el.querySelector('#hm-browser-cdp-url')?.value || '',
|
||||
browserDialogPolicy: el.querySelector('#hm-browser-dialog-policy')?.value || 'must_respond',
|
||||
browserDialogTimeout: el.querySelector('#hm-browser-dialog-timeout')?.value || '300',
|
||||
}
|
||||
browserSaving = true
|
||||
browserError = null
|
||||
|
||||
@@ -672,7 +672,16 @@ export default {
|
||||
hermesBrowserConfigEngine_auto: _('自动选择', 'Auto select', '自動選擇'),
|
||||
hermesBrowserConfigEngine_lightpanda: _('Lightpanda 快速导航', 'Lightpanda fast navigation', 'Lightpanda 快速導覽'),
|
||||
hermesBrowserConfigEngine_chrome: _('Chrome 完整浏览器', 'Chrome full browser', 'Chrome 完整瀏覽器'),
|
||||
hermesBrowserConfigFootnote: _('Lightpanda 导航更快但不支持截图;录制会把 WebM 写入 Hermes browser_recordings 目录,请只在需要审计时开启。CDP、Dialog 和 Camofox 高级字段会保留在 raw YAML 中。', 'Lightpanda navigates faster but does not support screenshots. Recording writes WebM files into the Hermes browser_recordings directory, so enable it only for audits. Advanced CDP, Dialog, and Camofox fields stay in raw YAML.', 'Lightpanda 導覽更快但不支援截圖;錄製會把 WebM 寫入 Hermes browser_recordings 目錄,請只在需要稽核時開啟。CDP、Dialog 和 Camofox 進階欄位會保留在 raw YAML 中。'),
|
||||
hermesBrowserConfigAllowPrivateUrls: _('允许访问私网和本机地址', 'Allow private and localhost URLs', '允許存取私網和本機位址'),
|
||||
hermesBrowserConfigAutoLocalForPrivateUrls: _('私网地址自动切到本地浏览器', 'Use local browser automatically for private URLs', '私網位址自動切到本機瀏覽器'),
|
||||
hermesBrowserConfigCdpUrl: _('CDP 连接地址', 'CDP endpoint URL', 'CDP 連線位址'),
|
||||
hermesBrowserConfigCdpUrlPlaceholder: _('留空则自动创建浏览器', 'Leave empty to launch browser automatically', '留空則自動建立瀏覽器'),
|
||||
hermesBrowserConfigDialogPolicy: _('弹窗处理策略', 'Dialog handling policy', '彈窗處理策略'),
|
||||
hermesBrowserConfigDialogPolicy_must_respond: _('等待用户处理', 'Wait for user response', '等待使用者處理'),
|
||||
hermesBrowserConfigDialogPolicy_auto_dismiss: _('自动取消弹窗', 'Auto dismiss dialogs', '自動取消彈窗'),
|
||||
hermesBrowserConfigDialogPolicy_auto_accept: _('自动确认弹窗', 'Auto accept dialogs', '自動確認彈窗'),
|
||||
hermesBrowserConfigDialogTimeout: _('弹窗等待超时秒数', 'Dialog wait timeout seconds', '彈窗等待逾時秒數'),
|
||||
hermesBrowserConfigFootnote: _('Lightpanda 导航更快但不支持截图;录制会把 WebM 写入 Hermes browser_recordings 目录,请只在需要审计时开启。允许私网地址会放开 localhost / 192.168 等目标;CDP 地址留空表示自动创建浏览器;Camofox 嵌套高级字段会保留在 raw YAML 中。', 'Lightpanda navigates faster but does not support screenshots. Recording writes WebM files into the Hermes browser_recordings directory, so enable it only for audits. Allowing private URLs opens localhost / 192.168-style targets. Empty CDP URL means Hermes launches a browser automatically. Nested Camofox advanced fields stay in raw YAML.', 'Lightpanda 導覽更快但不支援截圖;錄製會把 WebM 寫入 Hermes browser_recordings 目錄,請只在需要稽核時開啟。允許私網位址會放開 localhost / 192.168 等目標;CDP 位址留空表示自動建立瀏覽器;Camofox 巢狀進階欄位會保留在 raw YAML 中。'),
|
||||
hermesSttConfigTitle: _('语音转写', 'Speech transcription', '語音轉寫'),
|
||||
hermesSttConfigDesc: _('控制消息平台语音消息是否自动转写,以及本地、OpenAI 和 Mistral 转写模型。适合需要处理语音反馈的渠道。', 'Control automatic voice-message transcription for messaging platforms, plus local, OpenAI, and Mistral transcription models. Useful for channels that receive voice feedback.', '控制訊息平台語音訊息是否自動轉寫,以及本機、OpenAI 和 Mistral 轉寫模型。適合需要處理語音回饋的渠道。'),
|
||||
hermesSttConfigStatusReady: _('结构化配置', 'structured settings', '結構化設定'),
|
||||
|
||||
@@ -14,6 +14,11 @@ test('Hermes 浏览器配置读取会提供上游默认值', () => {
|
||||
browserCommandTimeout: 30,
|
||||
browserRecordSessions: false,
|
||||
browserEngine: 'auto',
|
||||
browserAllowPrivateUrls: false,
|
||||
browserAutoLocalForPrivateUrls: true,
|
||||
browserCdpUrl: '',
|
||||
browserDialogPolicy: 'must_respond',
|
||||
browserDialogTimeout: 300,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -24,6 +29,11 @@ test('Hermes 浏览器配置读取会回显 YAML 字段', () => {
|
||||
command_timeout: 45,
|
||||
record_sessions: true,
|
||||
engine: 'lightpanda',
|
||||
allow_private_urls: true,
|
||||
auto_local_for_private_urls: false,
|
||||
cdp_url: 'ws://127.0.0.1:9222/devtools/browser/demo',
|
||||
dialog_policy: 'auto_accept',
|
||||
dialog_timeout_s: 120,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -31,6 +41,11 @@ test('Hermes 浏览器配置读取会回显 YAML 字段', () => {
|
||||
assert.equal(values.browserCommandTimeout, 45)
|
||||
assert.equal(values.browserRecordSessions, true)
|
||||
assert.equal(values.browserEngine, 'lightpanda')
|
||||
assert.equal(values.browserAllowPrivateUrls, true)
|
||||
assert.equal(values.browserAutoLocalForPrivateUrls, false)
|
||||
assert.equal(values.browserCdpUrl, 'ws://127.0.0.1:9222/devtools/browser/demo')
|
||||
assert.equal(values.browserDialogPolicy, 'auto_accept')
|
||||
assert.equal(values.browserDialogTimeout, 120)
|
||||
})
|
||||
|
||||
test('Hermes 浏览器配置保存会保留未知字段并写入上游结构', () => {
|
||||
@@ -51,6 +66,11 @@ test('Hermes 浏览器配置保存会保留未知字段并写入上游结构', (
|
||||
browserCommandTimeout: '60',
|
||||
browserRecordSessions: true,
|
||||
browserEngine: 'chrome',
|
||||
browserAllowPrivateUrls: true,
|
||||
browserAutoLocalForPrivateUrls: false,
|
||||
browserCdpUrl: 'http://127.0.0.1:9222',
|
||||
browserDialogPolicy: 'auto_dismiss',
|
||||
browserDialogTimeout: '45',
|
||||
})
|
||||
|
||||
assert.deepEqual(next.model, { provider: 'anthropic' })
|
||||
@@ -59,11 +79,29 @@ test('Hermes 浏览器配置保存会保留未知字段并写入上游结构', (
|
||||
assert.equal(next.browser.command_timeout, 60)
|
||||
assert.equal(next.browser.record_sessions, true)
|
||||
assert.equal(next.browser.engine, 'chrome')
|
||||
assert.equal(next.browser.cdp_url, 'ws://127.0.0.1:9222/devtools/browser/demo')
|
||||
assert.equal(next.browser.allow_private_urls, true)
|
||||
assert.equal(next.browser.auto_local_for_private_urls, false)
|
||||
assert.equal(next.browser.cdp_url, 'http://127.0.0.1:9222')
|
||||
assert.equal(next.browser.dialog_policy, 'auto_dismiss')
|
||||
assert.equal(next.browser.dialog_timeout_s, 45)
|
||||
assert.deepEqual(next.browser.camofox, { managed_persistence: true })
|
||||
assert.equal(next.browser.custom_flag, 'keep-browser')
|
||||
})
|
||||
|
||||
test('Hermes 浏览器配置保存空 CDP URL 会移除可选字段', () => {
|
||||
const next = mergeHermesBrowserConfig({
|
||||
browser: {
|
||||
cdp_url: 'ws://127.0.0.1:9222/devtools/browser/demo',
|
||||
custom_flag: 'keep-browser',
|
||||
},
|
||||
}, {
|
||||
browserCdpUrl: ' ',
|
||||
})
|
||||
|
||||
assert.equal(next.browser.custom_flag, 'keep-browser')
|
||||
assert.equal(Object.hasOwn(next.browser, 'cdp_url'), false)
|
||||
})
|
||||
|
||||
test('Hermes 浏览器配置保存会拒绝非法引擎和越界值', () => {
|
||||
assert.throws(
|
||||
() => mergeHermesBrowserConfig({}, { browserEngine: 'firefox' }),
|
||||
@@ -77,4 +115,16 @@ test('Hermes 浏览器配置保存会拒绝非法引擎和越界值', () => {
|
||||
() => mergeHermesBrowserConfig({}, { browserCommandTimeout: '4' }),
|
||||
/browser\.command_timeout/,
|
||||
)
|
||||
assert.throws(
|
||||
() => mergeHermesBrowserConfig({}, { browserDialogPolicy: 'ignore' }),
|
||||
/browser\.dialog_policy/,
|
||||
)
|
||||
assert.throws(
|
||||
() => mergeHermesBrowserConfig({}, { browserDialogTimeout: '0' }),
|
||||
/browser\.dialog_timeout_s/,
|
||||
)
|
||||
assert.throws(
|
||||
() => mergeHermesBrowserConfig({}, { browserCdpUrl: 123 }),
|
||||
/browser\.cdp_url/,
|
||||
)
|
||||
})
|
||||
|
||||
@@ -379,6 +379,11 @@ test('Hermes 配置页会暴露浏览器基础结构化配置字段', () => {
|
||||
'hm-browser-command-timeout',
|
||||
'hm-browser-record-sessions',
|
||||
'hm-browser-engine',
|
||||
'hm-browser-allow-private-urls',
|
||||
'hm-browser-auto-local-for-private-urls',
|
||||
'hm-browser-cdp-url',
|
||||
'hm-browser-dialog-policy',
|
||||
'hm-browser-dialog-timeout',
|
||||
]) {
|
||||
assert.match(source, new RegExp(`id="${id}"`), `缺少 ${id}`)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user