ui: clarify ambiguous SPA config copy (readback, save feedback, LLM toggle)

Four copy fixes on the SPA configuration page (web/index.html +
web/config.js). All string-only except one 1-line status override
in the runtime save handler.

1. Callout: "Business configuration is not read from the server" ->
   "Business configuration is encrypted in Redis". The old text
   contradicted GET /api/business-config, which reads non-secret
   fields back. New copy states secrets are not echoed but non-secret
   fields are read back; the first save must include all required
   secrets.

2. Save feedback: after saving runtime parameters the status line
   reused runtime.loaded ("read from Redis"), which is wrong for a
   save. Added runtime.savedState ("Saved and applied successfully.")
   and set it in the save handler. Dropped the stale "business
   config still needs manual filling" sentence from runtime.readToast,
   which also contradicted readback.

3. LLM toggle: "Allow network calls" -> "Enable LLM outbound
   requests"; the hint now states no LLM client is initialized and
   the bot calls no external model service when off.

4. All fixes applied to both zh and en dictionaries and to the HTML
   English fallback defaults for consistency.

GATE-DOCS 6/6 PASS. No src/, cloudflare-worker/, or docs/ changes.
This commit is contained in:
anonsaber 2026-10-10 17:02:34 +08:00
parent 0b58d81193
commit da46a8fd4c
2 changed files with 16 additions and 13 deletions

View File

@ -57,8 +57,8 @@
"business.eyebrow": "Full replacement", "business.eyebrow": "Full replacement",
"business.title": "Business connections and auth", "business.title": "Business connections and auth",
"business.persist": "Encrypted in Redis", "business.persist": "Encrypted in Redis",
"callout.title": "Business configuration is not read from the server.", "callout.title": "Business configuration is encrypted in Redis.",
"callout.bodyPre": " To avoid secret echo, the API does not expose a business configuration GET. Every submission must re-fill the complete configuration, including all required secrets; optional LLM fields are sent as ", "callout.bodyPre": " Secret fields (app passwords, tokens) are not echoed and must be re-entered on each full save; non-secret fields are read back from the server. The first save must include all required secrets. Optional LLM fields are sent as ",
"callout.bodyPost": " when left blank. The mailbox primary account is always used.", "callout.bodyPost": " when left blank. The mailbox primary account is always used.",
"legend.business": "Full business configuration", "legend.business": "Full business configuration",
"jmap.title": "JMAP mailbox", "jmap.title": "JMAP mailbox",
@ -80,8 +80,8 @@
"llm.desc": "When enabled, an HTTPS Base URL, model name and API key are required.", "llm.desc": "When enabled, an HTTPS Base URL, model name and API key are required.",
"llm.enableLabel": "Enable LLM", "llm.enableLabel": "Enable LLM",
"llm.enableHint": "Allow the bot to call OpenAI-compatible model services.", "llm.enableHint": "Allow the bot to call OpenAI-compatible model services.",
"llm.netLabel": "Allow network calls", "llm.netLabel": "Enable LLM outbound requests",
"llm.netHint": "When off, no LLM client is created.", "llm.netHint": "When off, no LLM client is initialized and the bot will not call any external model service.",
"llm.urlHint": "HTTPS is required when LLM is enabled.", "llm.urlHint": "HTTPS is required when LLM is enabled.",
"llm.modelLabel": "Model name", "llm.modelLabel": "Model name",
"llm.modelPh": "Leave blank for null", "llm.modelPh": "Leave blank for null",
@ -131,6 +131,7 @@
"runtime.saveStateInitial": "Runtime parameters become editable after a successful read.", "runtime.saveStateInitial": "Runtime parameters become editable after a successful read.",
"runtime.previewState": "Default-value preview; save is disabled until a read succeeds.", "runtime.previewState": "Default-value preview; save is disabled until a read succeeds.",
"runtime.loaded": "Runtime parameters read from Redis.", "runtime.loaded": "Runtime parameters read from Redis.",
"runtime.savedState": "Saved and applied successfully.",
"runtime.readFailState": "Read failed; save is disabled.", "runtime.readFailState": "Read failed; save is disabled.",
"runtime.dirty": "There are unsaved runtime parameter changes.", "runtime.dirty": "There are unsaved runtime parameter changes.",
"runtime.defaultsState": "Defaults restored as preview; they are written to Redis only after saving.", "runtime.defaultsState": "Defaults restored as preview; they are written to Redis only after saving.",
@ -140,7 +141,7 @@
"runtime.saveBusy": "Saving…", "runtime.saveBusy": "Saving…",
"runtime.savingToast": "Saving runtime parameters to Redis…", "runtime.savingToast": "Saving runtime parameters to Redis…",
"runtime.readingToast": "Reading runtime parameters from Redis…", "runtime.readingToast": "Reading runtime parameters from Redis…",
"runtime.readToast": "Runtime parameters read. Business configuration still needs to be filled in manually.", "runtime.readToast": "Runtime parameters read.",
"runtime.savedToast": "Runtime parameters saved to Redis and applied immediately to subsequent requests.", "runtime.savedToast": "Runtime parameters saved to Redis and applied immediately to subsequent requests.",
"runtime.defaultsToast": "Runtime parameters restored to default-value preview. They are written to Redis only after clicking \"Save runtime parameters\".", "runtime.defaultsToast": "Runtime parameters restored to default-value preview. They are written to Redis only after clicking \"Save runtime parameters\".",
"privacy.title": "Secrets are never echoed or persisted to the browser.", "privacy.title": "Secrets are never echoed or persisted to the browser.",
@ -271,8 +272,8 @@
"business.eyebrow": "完整替换", "business.eyebrow": "完整替换",
"business.title": "业务连接与鉴权", "business.title": "业务连接与鉴权",
"business.persist": "Redis 加密保存", "business.persist": "Redis 加密保存",
"callout.title": "业务配置不会从服务器读取。", "callout.title": "业务配置以 Redis 加密保存。",
"callout.bodyPre": " 为避免密钥回显,API 不提供业务配置 GET。每次提交都必须重新填写完整配置,包括所有必填密钥;可选的 LLM 字段留空时会发送为 ", "callout.bodyPre": " 密钥字段(应用密码、Token 等)不会回显,每次完整保存需重新填写;非密钥字段会从服务器读回。首次保存必须包含所有必填密钥。可选的 LLM 字段留空时会发送为 ",
"callout.bodyPost": "。始终使用邮箱主账户。", "callout.bodyPost": "。始终使用邮箱主账户。",
"legend.business": "完整业务配置", "legend.business": "完整业务配置",
"jmap.title": "JMAP 邮箱", "jmap.title": "JMAP 邮箱",
@ -294,8 +295,8 @@
"llm.desc": "启用后要求 HTTPS Base URL、模型名和 API key。", "llm.desc": "启用后要求 HTTPS Base URL、模型名和 API key。",
"llm.enableLabel": "启用 LLM", "llm.enableLabel": "启用 LLM",
"llm.enableHint": "允许机器人调用兼容 OpenAI 的模型服务。", "llm.enableHint": "允许机器人调用兼容 OpenAI 的模型服务。",
"llm.netLabel": "允许网络调用", "llm.netLabel": "启用 LLM 出站请求",
"llm.netHint": "关闭时不会创建 LLM 客户端。", "llm.netHint": "关闭后不会初始化 LLM 客户端,机器人不会调用任何外部模型服务。",
"llm.urlHint": "启用 LLM 时必须使用 HTTPS。", "llm.urlHint": "启用 LLM 时必须使用 HTTPS。",
"llm.modelLabel": "模型名称", "llm.modelLabel": "模型名称",
"llm.modelPh": "留空表示 null", "llm.modelPh": "留空表示 null",
@ -342,6 +343,7 @@
"runtime.saveStateInitial": "读取成功后可编辑运行参数。", "runtime.saveStateInitial": "读取成功后可编辑运行参数。",
"runtime.previewState": "默认值预览;读取后才能保存。", "runtime.previewState": "默认值预览;读取后才能保存。",
"runtime.loaded": "已读取 Redis 中的运行参数。", "runtime.loaded": "已读取 Redis 中的运行参数。",
"runtime.savedState": "已成功保存并应用。",
"runtime.readFailState": "读取未成功,保存已禁用。", "runtime.readFailState": "读取未成功,保存已禁用。",
"runtime.dirty": "有尚未保存的运行参数修改。", "runtime.dirty": "有尚未保存的运行参数修改。",
"runtime.defaultsState": "已恢复默认预览值;保存后才会写入 Redis。", "runtime.defaultsState": "已恢复默认预览值;保存后才会写入 Redis。",
@ -351,7 +353,7 @@
"runtime.saveBusy": "正在保存…", "runtime.saveBusy": "正在保存…",
"runtime.savingToast": "正在保存运行参数到 Redis…", "runtime.savingToast": "正在保存运行参数到 Redis…",
"runtime.readingToast": "正在从 Redis 读取运行参数…", "runtime.readingToast": "正在从 Redis 读取运行参数…",
"runtime.readToast": "运行参数已读取。业务配置仍需手动完整填写。", "runtime.readToast": "运行参数已读取。",
"runtime.savedToast": "运行参数已保存到 Redis,并立即应用于后续请求。", "runtime.savedToast": "运行参数已保存到 Redis,并立即应用于后续请求。",
"runtime.defaultsToast": "运行参数已恢复为默认值预览。点击“保存运行参数”后才会更新 Redis。", "runtime.defaultsToast": "运行参数已恢复为默认值预览。点击“保存运行参数”后才会更新 Redis。",
"privacy.title": "密钥不会显示或保存在浏览器里。", "privacy.title": "密钥不会显示或保存在浏览器里。",
@ -1556,6 +1558,7 @@
try { try {
const saved = normalizeRuntimeConfig(await request("/api/config", "PUT", config)); const saved = normalizeRuntimeConfig(await request("/api/config", "PUT", config));
activateRuntimeConfig(saved); activateRuntimeConfig(saved);
runtimeSaveState.textContent = t("runtime.savedState");
showNotice("success", t("runtime.savedToast")); showNotice("success", t("runtime.savedToast"));
} catch (error) { } catch (error) {
if (error.status === 401) { if (error.status === 401) {

View File

@ -78,8 +78,8 @@
<span class="persist-label"><span class="persist-dot"></span><span data-i18n="business.persist">Encrypted in Redis</span></span> <span class="persist-label"><span class="persist-dot"></span><span data-i18n="business.persist">Encrypted in Redis</span></span>
</div> </div>
<div class="callout callout-warning"> <div class="callout callout-warning">
<strong data-i18n="callout.title">Business configuration is not read from the server.</strong> <strong data-i18n="callout.title">Business configuration is encrypted in Redis.</strong>
<span data-i18n="callout.bodyPre"> To avoid secret echo, the API does not expose a business configuration GET. Every submission must re-fill the complete configuration, including all required secrets; optional LLM fields are sent as </span><code>null</code><span data-i18n="callout.bodyPost"> when left blank. The mailbox primary account is always used.</span> <span data-i18n="callout.bodyPre"> Secret fields (app passwords, tokens) are not echoed and must be re-entered on each full save; non-secret fields are read back from the server. The first save must include all required secrets. Optional LLM fields are sent as </span><code>null</code><span data-i18n="callout.bodyPost"> when left blank. The mailbox primary account is always used.</span>
</div> </div>
<form id="business-form" novalidate> <form id="business-form" novalidate>
@ -111,7 +111,7 @@
<div class="form-section-heading"><span class="section-number">03</span><div><h3 id="llm-title" data-i18n="llm.title">LLM service</h3><p data-i18n="llm.desc">When enabled, an HTTPS Base URL, model name and API key are required.</p></div></div> <div class="form-section-heading"><span class="section-number">03</span><div><h3 id="llm-title" data-i18n="llm.title">LLM service</h3><p data-i18n="llm.desc">When enabled, an HTTPS Base URL, model name and API key are required.</p></div></div>
<div class="toggle-grid"> <div class="toggle-grid">
<label class="toggle-card" for="llm-enabled"><input id="llm-enabled" name="llm_enabled" type="checkbox" /><span><strong data-i18n="llm.enableLabel">Enable LLM</strong><small data-i18n="llm.enableHint">Allow the bot to call OpenAI-compatible model services.</small></span></label> <label class="toggle-card" for="llm-enabled"><input id="llm-enabled" name="llm_enabled" type="checkbox" /><span><strong data-i18n="llm.enableLabel">Enable LLM</strong><small data-i18n="llm.enableHint">Allow the bot to call OpenAI-compatible model services.</small></span></label>
<label class="toggle-card" for="llm-allow-net"><input id="llm-allow-net" name="llm_allow_net" type="checkbox" /><span><strong data-i18n="llm.netLabel">Allow network calls</strong><small data-i18n="llm.netHint">When off, no LLM client is created.</small></span></label> <label class="toggle-card" for="llm-allow-net"><input id="llm-allow-net" name="llm_allow_net" type="checkbox" /><span><strong data-i18n="llm.netLabel">Enable LLM outbound requests</strong><small data-i18n="llm.netHint">When off, no LLM client is initialized and the bot will not call any external model service.</small></span></label>
</div> </div>
<div class="field-grid llm-fields"> <div class="field-grid llm-fields">
<div class="field"><label for="llm-base-url">LLM Base URL <span id="llm-url-required" class="optional-mark" data-i18n="common.optional">Optional</span></label><input id="llm-base-url" name="llm_base_url" type="url" inputmode="url" autocomplete="url" placeholder="https://api.example.com/v1" /><small data-i18n="llm.urlHint">HTTPS is required when LLM is enabled.</small></div> <div class="field"><label for="llm-base-url">LLM Base URL <span id="llm-url-required" class="optional-mark" data-i18n="common.optional">Optional</span></label><input id="llm-base-url" name="llm_base_url" type="url" inputmode="url" autocomplete="url" placeholder="https://api.example.com/v1" /><small data-i18n="llm.urlHint">HTTPS is required when LLM is enabled.</small></div>