feat(i18n): localize planner and authority diagnostics

This commit is contained in:
youzini
2026-06-05 11:43:02 +00:00
parent 5d082f3618
commit dffc145ada
9 changed files with 825 additions and 137 deletions

View File

@@ -9,6 +9,7 @@
* BME theming automatically.
*/
import { t } from '../i18n/index.js';
import {
isSameLlmConfigSnapshot,
resolveDedicatedLlmProviderConfig,
@@ -248,7 +249,7 @@ function populatePlannerLlmPresetSelect(selectedPreset = resolvePlannerLlmSelect
if (!select) return;
if (select.options.length > 0) {
select.options[0].textContent = '-- 跟随全局(当前 BME API --';
select.options[0].textContent = t('planner.llmPreset.global');
}
while (select.options.length > 1) {
@@ -268,7 +269,7 @@ function populatePlannerLlmPresetSelect(selectedPreset = resolvePlannerLlmSelect
if (selectedPreset === LEGACY_PLANNER_LLM_OPTION) {
const legacyOption = document.createElement('option');
legacyOption.value = LEGACY_PLANNER_LLM_OPTION;
legacyOption.textContent = '旧 ENA 独立连接(兼容)';
legacyOption.textContent = t('planner.llmPreset.legacy');
select.appendChild(legacyOption);
}
@@ -294,7 +295,7 @@ function createPromptBlockElement(block, idx, total) {
const nameInput = document.createElement('input');
nameInput.type = 'text';
nameInput.className = 'bme-config-input';
nameInput.placeholder = '块名称';
nameInput.placeholder = t('planner.promptBlock.namePlaceholder');
nameInput.value = block.name || '';
nameInput.addEventListener('change', () => {
block.name = nameInput.value;
@@ -324,7 +325,7 @@ function createPromptBlockElement(block, idx, total) {
upBtn.type = 'button';
upBtn.className = 'bme-config-secondary-btn bme-planner-icon-btn';
upBtn.innerHTML = '<i class="fa-solid fa-chevron-up"></i>';
upBtn.title = '上移';
upBtn.title = t('planner.promptBlock.moveUp');
upBtn.disabled = idx === 0;
upBtn.addEventListener('click', (ev) => {
ev.preventDefault();
@@ -340,7 +341,7 @@ function createPromptBlockElement(block, idx, total) {
downBtn.type = 'button';
downBtn.className = 'bme-config-secondary-btn bme-planner-icon-btn';
downBtn.innerHTML = '<i class="fa-solid fa-chevron-down"></i>';
downBtn.title = '下移';
downBtn.title = t('planner.promptBlock.moveDown');
downBtn.disabled = idx === total - 1;
downBtn.addEventListener('click', (ev) => {
ev.preventDefault();
@@ -356,7 +357,7 @@ function createPromptBlockElement(block, idx, total) {
delBtn.type = 'button';
delBtn.className = 'bme-config-secondary-btn bme-config-danger-btn bme-planner-icon-btn';
delBtn.innerHTML = '<i class="fa-solid fa-trash-can"></i>';
delBtn.title = '删除块';
delBtn.title = t('planner.promptBlock.deleteBlock');
delBtn.addEventListener('click', (ev) => {
ev.preventDefault();
ev.stopPropagation();
@@ -370,7 +371,7 @@ function createPromptBlockElement(block, idx, total) {
const content = document.createElement('textarea');
content.className = 'bme-config-input bme-planner-textarea';
content.placeholder = '提示词内容...';
content.placeholder = t('planner.promptBlock.contentPlaceholder');
content.rows = 4;
content.value = block.content || '';
content.addEventListener('change', () => {
@@ -402,7 +403,7 @@ function renderPromptList() {
function renderTemplateSelect(selected = '') {
const sel = $('bme-planner-tpl-select');
if (!sel) return;
sel.innerHTML = '<option value="">-- 选择模板 --</option>';
sel.innerHTML = `<option value="">${t('planner.template.selectPlaceholder')}</option>`;
const names = Object.keys(cfgCache?.promptTemplates || {});
const selectedName = names.includes(selected) ? selected : '';
for (const name of names) {
@@ -445,14 +446,14 @@ function renderLogs() {
if (!body) return;
const list = Array.isArray(logsCache) ? logsCache : [];
if (!list.length) {
body.innerHTML = '<div class="bme-planner-log-empty">暂无日志</div>';
body.innerHTML = `<div class="bme-planner-log-empty">${t('planner.log.noLogs')}</div>`;
return;
}
body.innerHTML = list
.map((item) => {
const time = item.time ? new Date(item.time).toLocaleString() : '-';
const cls = item.ok ? 'success' : 'error';
const label = item.ok ? '成功' : '失败';
const label = item.ok ? t('planner.log.success') : t('planner.log.failure');
let msgHtml = '';
if (Array.isArray(item.requestMessages) && item.requestMessages.length) {
msgHtml = item.requestMessages
@@ -472,7 +473,7 @@ function renderLogs() {
})
.join('');
} else {
msgHtml = '<div class="bme-planner-log-empty">无消息</div>';
msgHtml = `<div class="bme-planner-log-empty">${t('planner.log.noMessages')}</div>`;
}
return `
<div class="bme-planner-log-item">
@@ -481,13 +482,13 @@ function renderLogs() {
<span>${escapeHtml(item.model || '-')}</span>
</div>
${item.error ? `<div class="bme-planner-log-error">${escapeHtml(item.error)}</div>` : ''}
<details><summary>请求消息 (${(item.requestMessages || []).length})</summary>
<details><summary>${t('planner.log.requestMessages', { count: (item.requestMessages || []).length })}</summary>
<div class="bme-planner-msg-list">${msgHtml}</div>
</details>
<details><summary>原始回复</summary>
<details><summary>${t('planner.log.rawReply')}</summary>
<pre class="bme-planner-log-pre">${escapeHtml(item.rawReply || '')}</pre>
</details>
<details open><summary>过滤后回复</summary>
<details open><summary>${t('planner.log.filteredReply')}</summary>
<pre class="bme-planner-log-pre">${escapeHtml(item.filteredReply || '')}</pre>
</details>
</div>`;
@@ -540,16 +541,16 @@ function applyConfigToFields(cfg) {
setStatusChip(
'bme-planner-state-chip',
toBool(cfgCache.enabled, false) ? '已启用' : '未启用',
toBool(cfgCache.enabled, false) ? t('planner.status.enabled') : t('planner.status.disabled'),
toBool(cfgCache.enabled, false) ? 'active' : 'idle',
);
updatePrefixModeUI();
syncPlannerLlmPresetSelect();
const llmSelectState = resolvePlannerLlmSelectState(cfgCache);
if (llmSelectState.mode === 'legacy') {
setLocalStatus('bme-planner-api-status', '当前仍在使用旧版 ENA 独立连接;切换为全局或预设后将不再保留这套隐藏配置。', '');
setLocalStatus('bme-planner-api-status', t('planner.llmPreset.legacyWarning'), '');
} else if (llmSelectState.missingPresetName) {
setLocalStatus('bme-planner-api-status', `已回退为跟随全局:缺少预设 ${llmSelectState.missingPresetName}`, 'error');
setLocalStatus('bme-planner-api-status', t('planner.llmPreset.missingPresetFallback', { name: llmSelectState.missingPresetName }), 'error');
} else {
setLocalStatus('bme-planner-api-status', '', '');
}
@@ -605,7 +606,7 @@ function updatePrefixModeUI() {
function resetPlannerSaveStatusIfReady() {
if (autosaveInProgress) return;
setStatusChip('bme-planner-save-chip', '就绪', 'idle');
setStatusChip('bme-planner-save-chip', t('planner.status.ready'), 'idle');
}
/* ── Save flow ──────────────────────────────────────────────────────────── */
@@ -629,24 +630,24 @@ async function doSave() {
if (autosaveInProgress) return;
const api = getPlannerApi();
if (!api?.patchConfig) {
setStatusChip('bme-planner-save-chip', 'API 未就绪', 'error');
setStatusChip('bme-planner-save-chip', t('planner.status.apiNotReady'), 'error');
return;
}
autosaveInProgress = true;
setStatusChip('bme-planner-save-chip', '保存中…', 'loading');
setStatusChip('bme-planner-save-chip', t('planner.status.saving'), 'loading');
try {
const patch = pendingSavePatch || collectPatch();
const res = await api.patchConfig(patch);
if (res?.ok) {
pendingSavePatch = null;
setStatusChip('bme-planner-save-chip', '已保存', 'success');
setStatusChip('bme-planner-save-chip', t('planner.status.saved'), 'success');
setTimeout(() => {
if ($('bme-planner-save-chip')?.dataset?.tone === 'success') {
setStatusChip('bme-planner-save-chip', '就绪', 'idle');
setStatusChip('bme-planner-save-chip', t('planner.status.ready'), 'idle');
}
}, 2000);
} else {
setStatusChip('bme-planner-save-chip', res?.error || '保存失败', 'error');
setStatusChip('bme-planner-save-chip', res?.error || t('planner.status.saveFailed'), 'error');
}
} catch (err) {
setStatusChip('bme-planner-save-chip', String(err?.message ?? err), 'error');
@@ -674,7 +675,7 @@ function bindOnce(section) {
$('bme-planner-enabled')?.addEventListener('change', () => {
setStatusChip(
'bme-planner-state-chip',
toBool($('bme-planner-enabled').value, false) ? '已启用' : '未启用',
toBool($('bme-planner-enabled').value, false) ? t('planner.status.enabled') : t('planner.status.disabled'),
toBool($('bme-planner-enabled').value, false) ? 'active' : 'idle',
);
flushSave();
@@ -687,10 +688,10 @@ function bindOnce(section) {
$('bme-planner-run-test')?.addEventListener('click', async () => {
const textEl = $('bme-planner-test-input');
const text = (textEl?.value || '').trim();
setLocalStatus('bme-planner-test-status', '测试中…', 'loading');
setLocalStatus('bme-planner-test-status', t('planner.status.testing'), 'loading');
const res = await api?.runTest?.(text);
if (res?.ok) setLocalStatus('bme-planner-test-status', '规划测试完成', 'success');
else setLocalStatus('bme-planner-test-status', res?.error || '规划测试失败', 'error');
if (res?.ok) setLocalStatus('bme-planner-test-status', t('planner.status.testComplete'), 'success');
else setLocalStatus('bme-planner-test-status', res?.error || t('planner.status.testFailed'), 'error');
});
/* API connection */
@@ -700,10 +701,10 @@ function bindOnce(section) {
if (!input || !btn) return;
if (input.type === 'password') {
input.type = 'text';
btn.querySelector('span').textContent = '隐藏';
btn.querySelector('span').textContent = t('planner.apiKey.hide');
} else {
input.type = 'password';
btn.querySelector('span').textContent = '显示';
btn.querySelector('span').textContent = t('planner.apiKey.show');
}
});
@@ -713,16 +714,16 @@ function bindOnce(section) {
setLocalStatus('bme-planner-api-status', statusText, 'loading');
const res = await api?.fetchModels?.();
if (!res) {
setLocalStatus('bme-planner-api-status', 'API 未就绪', 'error');
setLocalStatus('bme-planner-api-status', t('planner.status.apiNotReady'), 'error');
return;
}
if (!res.ok) {
setLocalStatus('bme-planner-api-status', res.error || '拉取失败', 'error');
setLocalStatus('bme-planner-api-status', res.error || t('planner.status.fetchModelsFailed'), 'error');
return;
}
const models = Array.isArray(res.models) ? res.models : [];
if (!models.length) {
setLocalStatus('bme-planner-api-status', '未获取到模型', 'error');
setLocalStatus('bme-planner-api-status', t('planner.status.noModelsFetched'), 'error');
const sel = $('bme-planner-model-select');
if (sel) sel.style.display = 'none';
return;
@@ -730,7 +731,7 @@ function bindOnce(section) {
fetchedModels = models;
const sel = $('bme-planner-model-select');
if (sel) {
sel.innerHTML = '<option value="">-- 从列表选择 --</option>';
sel.innerHTML = `<option value="">${t('planner.model.selectFromList')}</option>`;
const cur = ($('bme-planner-model')?.value || '').trim();
for (const m of models) {
const opt = document.createElement('option');
@@ -741,11 +742,11 @@ function bindOnce(section) {
}
sel.style.display = '';
}
setLocalStatus('bme-planner-api-status', `获取到 ${models.length} 个模型`, 'success');
setLocalStatus('bme-planner-api-status', t('planner.status.modelsFetched', { count: models.length }), 'success');
};
$('bme-planner-fetch-models')?.addEventListener('click', () => handleFetchModels('拉取中…'));
$('bme-planner-test-conn')?.addEventListener('click', () => handleFetchModels('测试中…'));
$('bme-planner-fetch-models')?.addEventListener('click', () => handleFetchModels(t('planner.status.fetchingModels')));
$('bme-planner-test-conn')?.addEventListener('click', () => handleFetchModels(t('planner.status.testing')));
$('bme-planner-model-select')?.addEventListener('change', () => {
const sel = $('bme-planner-model-select');
@@ -771,13 +772,13 @@ function bindOnce(section) {
cfgCache.api.apiKey = '';
cfgCache.api.model = '';
syncPlannerLlmPresetSelect();
setLocalStatus('bme-planner-api-status', '已改为跟随全局 BME API', 'success');
setLocalStatus('bme-planner-api-status', t('planner.llmPreset.switchedToGlobal'), 'success');
scheduleSave();
return;
}
if (selectedName === LEGACY_PLANNER_LLM_OPTION) {
syncPlannerLlmPresetSelect();
setLocalStatus('bme-planner-api-status', '继续保留旧版 ENA 独立连接', '');
setLocalStatus('bme-planner-api-status', t('planner.llmPreset.keepingLegacy'), '');
scheduleSave();
return;
}
@@ -791,7 +792,7 @@ function bindOnce(section) {
cfgCache.api.apiKey = '';
cfgCache.api.model = '';
syncPlannerLlmPresetSelect();
setLocalStatus('bme-planner-api-status', '选中的 API 预设不存在,已回退为跟随全局', 'error');
setLocalStatus('bme-planner-api-status', t('planner.llmPreset.presetNotFound'), 'error');
scheduleSave();
return;
}
@@ -803,17 +804,17 @@ function bindOnce(section) {
cfgCache.api.apiKey = '';
cfgCache.api.model = '';
syncPlannerLlmPresetSelect();
setLocalStatus('bme-planner-api-status', `已切换为 API 预设:${selectedName}`, 'success');
setLocalStatus('bme-planner-api-status', t('planner.llmPreset.switchedToPreset', { name: selectedName }), 'success');
scheduleSave();
});
$('bme-planner-open-task-presets')?.addEventListener('click', () => {
const opened = openPlannerTaskPresetWorkspace();
if (!opened) {
setLocalStatus('bme-planner-api-status', '未找到任务预设工作区,请手动切到“任务 -> 规划”', 'error');
setLocalStatus('bme-planner-api-status', t('planner.taskPreset.workspaceNotFound'), 'error');
return;
}
setLocalStatus('bme-planner-api-status', '已切换到“任务 -> 规划”预设编辑器', 'success');
setLocalStatus('bme-planner-api-status', t('planner.taskPreset.workspaceSwitched'), 'success');
});
/* Prompts + templates */
@@ -822,20 +823,20 @@ function bindOnce(section) {
$('bme-planner-add-prompt')?.addEventListener('click', () => {
cfgCache = cfgCache || {};
cfgCache.promptBlocks = cfgCache.promptBlocks || [];
cfgCache.promptBlocks.push({ id: genId(), role: 'system', name: '新块', content: '' });
cfgCache.promptBlocks.push({ id: genId(), role: 'system', name: t('planner.promptBlock.newBlock'), content: '' });
renderPromptList();
scheduleSave();
});
$('bme-planner-reset-prompt')?.addEventListener('click', async () => {
if (!confirm('确定恢复默认提示词块?当前提示词块将被覆盖。')) return;
setStatusChip('bme-planner-save-chip', '重置中…', 'loading');
if (!confirm(t('planner.promptBlock.confirmReset'))) return;
setStatusChip('bme-planner-save-chip', t('planner.status.resetting'), 'loading');
const res = await api?.resetPromptToDefault?.();
if (res?.ok && res.config) {
applyConfigToFields(res.config);
setStatusChip('bme-planner-save-chip', '已恢复默认', 'success');
setStatusChip('bme-planner-save-chip', t('planner.status.resetToDefault'), 'success');
} else {
setStatusChip('bme-planner-save-chip', res?.error || '重置失败', 'error');
setStatusChip('bme-planner-save-chip', res?.error || t('planner.status.resetFailed'), 'error');
}
});
@@ -854,7 +855,7 @@ function bindOnce(section) {
$('bme-planner-tpl-save')?.addEventListener('click', () => {
const name = $('bme-planner-tpl-select').value;
if (!name) {
setStatusChip('bme-planner-save-chip', '请先选择或新建模板', 'error');
setStatusChip('bme-planner-save-chip', t('planner.template.selectOrCreateFirst'), 'error');
return;
}
cfgCache.promptTemplates = cfgCache.promptTemplates || {};
@@ -865,7 +866,7 @@ function bindOnce(section) {
});
$('bme-planner-tpl-saveas')?.addEventListener('click', () => {
const name = prompt('新模板名称');
const name = prompt(t('planner.template.newTemplateName'));
if (!name) return;
cfgCache.promptTemplates = cfgCache.promptTemplates || {};
cfgCache.promptTemplates[name] = structuredClone(cfgCache.promptBlocks || []);
@@ -901,20 +902,20 @@ function bindOnce(section) {
const out = $('bme-planner-debug-output');
if (out) {
setHidden(out, false);
out.textContent = '诊断中…';
out.textContent = t('planner.debug.diagnosing');
}
const res = await api?.debugWorldbook?.();
if (out) out.textContent = res?.output ?? '诊断失败';
if (out) out.textContent = res?.output ?? t('planner.debug.failed');
});
$('bme-planner-debug-char')?.addEventListener('click', async () => {
const out = $('bme-planner-debug-output');
if (out) {
setHidden(out, false);
out.textContent = '诊断中…';
out.textContent = t('planner.debug.diagnosing');
}
const res = await api?.debugChar?.();
if (out) out.textContent = res?.output ?? '诊断失败';
if (out) out.textContent = res?.output ?? t('planner.debug.failed');
});
/* Logs */
@@ -925,7 +926,7 @@ function bindOnce(section) {
});
$('bme-planner-logs-clear')?.addEventListener('click', async () => {
if (!confirm('确定清空所有日志?')) return;
if (!confirm(t('planner.log.confirmClear'))) return;
const res = await api?.clearLogs?.();
if (res?.ok !== false) {
logsCache = [];
@@ -975,8 +976,8 @@ export function initPlannerSections(rootEl, options = {}) {
const api = getPlannerApi();
if (!api) {
setStatusChip('bme-planner-state-chip', '模块未加载', 'error');
setStatusChip('bme-planner-save-chip', '不可用', 'error');
setStatusChip('bme-planner-state-chip', t('planner.status.moduleNotLoaded'), 'error');
setStatusChip('bme-planner-save-chip', t('planner.status.unavailable'), 'error');
return;
}
@@ -1007,7 +1008,7 @@ export function refreshPlannerSections(options = {}) {
}
const api = getPlannerApi();
if (!api) {
setStatusChip('bme-planner-state-chip', '模块未加载', 'error');
setStatusChip('bme-planner-state-chip', t('planner.status.moduleNotLoaded'), 'error');
return;
}
if (typeof api.getConfig === 'function') applyConfigToFields(api.getConfig());

View File

@@ -64,6 +64,7 @@ import {
normalizeMaintenanceExecutionMode,
} from "../runtime/concurrency.js";
import {
formatI18nValue,
formatUiStatusMeta,
formatUiStatusText,
hydrateI18n,
@@ -3329,7 +3330,30 @@ function _refreshTaskPersistence() {
? "Authority 审计中"
: "等待审计",
detail: String(ps.authorityConsistencyError || "尚未运行一致性审计"),
labelKey:
ps.authorityConsistencyState === "success"
? "authority.summary.aligned"
: ps.authorityConsistencyState === "warning"
? "authority.summary.driftPending"
: ps.authorityConsistencyState === "error"
? "error.auditFailed"
: ps.authorityConsistencyState === "running"
? "authority.summary.auditRunning"
: "authority.summary.waitingForAudit",
labelParams: {},
detailKey: ps.authorityConsistencyError ? "" : "authority.summary.notYetAudited",
detailParams: {},
};
const authorityAuditSummaryLabel = formatI18nValue(authorityAuditSummary, {
keyField: "labelKey",
paramsField: "labelParams",
fallbackField: "label",
});
const authorityAuditSummaryDetail = formatI18nValue(authorityAuditSummary, {
keyField: "detailKey",
paramsField: "detailParams",
fallbackField: "detail",
});
const authorityAuditSqlRevision = Number.isFinite(Number(authorityAudit?.sql?.revision))
? String(Number(authorityAudit.sql.revision))
: "—";
@@ -3345,14 +3369,21 @@ function _refreshTaskPersistence() {
authorityAudit?.blob?.path || ps.authorityBlobCheckpointPath || "",
).trim() || "—";
const authorityAuditIssuesLabel = Array.isArray(authorityAudit?.issues) && authorityAudit.issues.length
? authorityAudit.issues.map((issue) => issue.message).filter(Boolean).join(" / ")
: authorityAuditSummary.detail || "—";
? authorityAudit.issues
.map((issue) => formatI18nValue(issue, {
keyField: "messageKey",
paramsField: "messageParams",
fallbackField: "message",
}))
.filter(Boolean)
.join(" / ")
: authorityAuditSummaryDetail || "—";
const authorityAuditActionsLabel = Array.isArray(authorityAudit?.actions) && authorityAudit.actions.length
? authorityAudit.actions.map((action) => ({
"write-authority-checkpoint": "同步备份 Checkpoint",
"rebuild-authority-trivium": "同步向量/Trivium 副本",
"run-authority-consistency-audit": "重新审计",
"restore-from-authority-blob-checkpoint": "灾难恢复:从 Checkpoint 覆盖 SQL",
"write-authority-checkpoint": t("authority.action.syncCheckpoint"),
"rebuild-authority-trivium": t("authority.action.syncTrivium"),
"run-authority-consistency-audit": t("authority.action.reaudit"),
"restore-from-authority-blob-checkpoint": t("authority.action.disasterRecovery"),
}[action] || action)).join(" · ")
: "—";
const authorityAuditUpdatedLabel = ps.authorityConsistencyUpdatedAt
@@ -3367,12 +3398,12 @@ function _refreshTaskPersistence() {
const authorityRestoreState = String(ps.authorityCheckpointRestoreState || "idle").trim();
const authorityRestoreLabel =
authorityRestoreState === "success"
? "已恢复"
? t("authority.restore.success")
: authorityRestoreState === "error"
? "恢复失败"
? t("authority.restore.error")
: authorityRestoreState === "running"
? "恢复中"
: "未执行";
? t("authority.restore.running")
: t("authority.restore.idle");
const authorityRestoreUpdatedLabel = ps.authorityCheckpointRestoreUpdatedAt
? _formatTaskProfileTime(ps.authorityCheckpointRestoreUpdatedAt)
: "—";
@@ -3389,28 +3420,37 @@ function _refreshTaskPersistence() {
).trim();
const authorityRepairLabel =
authorityRepairState === "success"
? "同步完成"
? t("authority.repair.status.success")
: authorityRepairState === "error"
? "同步失败"
? t("authority.repair.status.error")
: authorityRepairState === "warning"
? "部分同步失败"
? t("authority.repair.status.warning")
: authorityRepairState === "running"
? authorityRepairResult?.handoffRequired
? "等待 Job 交接"
: "同步中"
: "未执行";
? t("authority.repair.status.handoff")
: t("authority.repair.status.running")
: t("authority.repair.status.idle");
const authorityRepairUpdatedLabel = ps.authorityRepairUpdatedAt
? _formatTaskProfileTime(ps.authorityRepairUpdatedAt)
: "—";
const authorityRepairPlanLabel = authorityRepairPlan.ok
? authorityRepairPlan.steps.map((step) => step.label).join(" → ")
: authorityRepairPlan.summary.label || "当前无需编排同步";
? authorityRepairPlan.steps.map((step) => formatI18nValue(step, {
keyField: "labelKey",
paramsField: "labelParams",
fallbackField: "label",
})).join(" → ")
: formatI18nValue(authorityRepairPlan.summary, {
keyField: "labelKey",
paramsField: "labelParams",
fallbackField: "label",
fallback: t("authority.repair.none"),
});
const authorityRepairResultLabel = authorityRepairResult?.steps?.length
? `${Number(authorityRepairResult.steps.length || 0)}${
? `${t("authority.repair.resultSteps", { count: Number(authorityRepairResult.steps.length || 0) })}${
authorityRepairResult?.handoffRequired
? authorityRepairHandoffJobId
? ` · job ${authorityRepairHandoffJobId}`
: " · 已交接异步 Job"
: ` · ${t("authority.repair.resultHandoff")}`
: ""
}`
: "—";
@@ -3479,6 +3519,11 @@ function _refreshTaskPersistence() {
const authorityArtifactPruneLabel = ps.authorityDiagnosticsLastPrunedAt
? `${Number(ps.authorityDiagnosticsLastPrunedCount || 0)} 条 · ${_formatTaskProfileTime(ps.authorityDiagnosticsLastPrunedAt)}`
: "未触发";
const authorityRepairPlanSummaryDetail = formatI18nValue(authorityRepairPlan.summary, {
keyField: "detailKey",
paramsField: "detailParams",
fallbackField: "detail",
});
const activeRegionLabel = String(
historyState?.activeRegion ||
historyState?.lastExtractedRegion ||
@@ -3576,7 +3621,7 @@ function _refreshTaskPersistence() {
..._buildPersistDeltaDiagnosticRows(persistDeltaDiagnostics),
);
const authorityRows = [
["审计状态", authorityAuditSummary.label],
["审计状态", authorityAuditSummaryLabel],
["SQL rev", authorityAuditSqlRevision],
["Trivium rev", authorityAuditTriviumRevision],
["Blob rev", authorityAuditBlobRevision],
@@ -3634,28 +3679,28 @@ function _refreshTaskPersistence() {
);
const authorityActionButtons = [
typeof _actionHandlers.runAuthorityConsistencyAudit === "function"
? `<button class="bme-config-secondary-btn" type="button" data-authority-persistence-action="audit">执行 Authority 审计</button>`
? `<button class="bme-config-secondary-btn" type="button" data-authority-persistence-action="audit">${_escHtml(t("authority.button.runAudit"))}</button>`
: "",
showAuthorityRepairAction && typeof _actionHandlers.runAuthorityConsistencyRepairPlan === "function"
? `<button class="bme-config-secondary-btn" type="button" data-authority-persistence-action="repair-plan">执行副本同步</button>`
? `<button class="bme-config-secondary-btn" type="button" data-authority-persistence-action="repair-plan">${_escHtml(t("authority.button.runRepair"))}</button>`
: "",
showAuthorityCheckpointWriteAction && typeof _actionHandlers.writeAuthorityCheckpoint === "function"
? `<button class="bme-config-secondary-btn" type="button" data-authority-persistence-action="checkpoint">同步 Checkpoint</button>`
? `<button class="bme-config-secondary-btn" type="button" data-authority-persistence-action="checkpoint">${_escHtml(t("authority.button.syncCheckpoint"))}</button>`
: "",
showAuthorityRestoreAction && typeof _actionHandlers.restoreAuthorityCheckpoint === "function"
? `<button class="bme-config-secondary-btn" type="button" data-authority-persistence-action="restore">灾难恢复Checkpoint 覆盖 SQL</button>`
? `<button class="bme-config-secondary-btn" type="button" data-authority-persistence-action="restore">${_escHtml(t("authority.button.disasterRecovery"))}</button>`
: "",
showAuthorityTriviumRebuildAction && typeof _actionHandlers.rebuildVectorIndex === "function"
? `<button class="bme-config-secondary-btn" type="button" data-authority-persistence-action="rebuild-trivium">同步 Authority Trivium</button>`
? `<button class="bme-config-secondary-btn" type="button" data-authority-persistence-action="rebuild-trivium">${_escHtml(t("authority.button.syncTrivium"))}</button>`
: "",
typeof _actionHandlers.captureAuthorityPerformanceBaseline === "function"
? `<button class="bme-config-secondary-btn" type="button" data-authority-persistence-action="baseline">捕获 Perf Baseline</button>`
? `<button class="bme-config-secondary-btn" type="button" data-authority-persistence-action="baseline">${_escHtml(t("authority.button.captureBaseline"))}</button>`
: "",
typeof _actionHandlers.exportDiagnosticsBundle === "function"
? `<button class="bme-config-secondary-btn" type="button" data-authority-persistence-action="bundle">导出诊断包</button>`
? `<button class="bme-config-secondary-btn" type="button" data-authority-persistence-action="bundle">${_escHtml(t("authority.button.exportDiagnostics"))}</button>`
: "",
typeof _actionHandlers.refreshAuthorityDiagnosticsArtifacts === "function"
? `<button class="bme-config-secondary-btn" type="button" data-authority-persistence-action="artifacts-refresh">刷新工件列表</button>`
? `<button class="bme-config-secondary-btn" type="button" data-authority-persistence-action="artifacts-refresh">${_escHtml(t("authority.button.refreshArtifacts"))}</button>`
: "",
].filter(Boolean).join("");
const authorityArtifactsHtml = authorityArtifactEntries.length
@@ -3686,10 +3731,10 @@ function _refreshTaskPersistence() {
</div>`
: `<div class="bme-config-help" style="margin-top:12px">${_escHtml(
ps.authorityDiagnosticsArtifactsError
? `工件列表刷新失败:${ps.authorityDiagnosticsArtifactsError}`
? t("authority.diagnostics.artifactsRefreshFailed", { error: ps.authorityDiagnosticsArtifactsError })
: ps.authorityDiagnosticsArtifactsUpdatedAt
? "最近工件列表已刷新,但暂无可用诊断包记录"
: "尚未刷新 diagnostics artifact 列表"
? t("authority.diagnostics.noArtifacts")
: t("authority.diagnostics.notYetRefreshed")
)}</div>`;
el.innerHTML = `
@@ -3727,10 +3772,10 @@ function _refreshTaskPersistence() {
</div>
${authorityActionButtons ? `<div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px">${authorityActionButtons}</div>` : ""}
${renderRowsTwoColumn(authorityRows)}
<div class="bme-config-help" style="margin-top:10px">${_escHtml(authorityAuditSummary.detail || "—")}</div>
<div class="bme-config-help" style="margin-top:10px">${_escHtml(authorityAuditSummaryDetail || "—")}</div>
<div class="bme-config-help" style="margin-top:6px">${_escHtml(authorityAuditIssuesLabel)}</div>
<div class="bme-config-help" style="margin-top:6px">${_escHtml(authorityRepairPlan.summary.detail || "—")}</div>
<div class="bme-config-help" style="margin-top:10px">最近 diagnostics artifacts</div>
<div class="bme-config-help" style="margin-top:6px">${_escHtml(authorityRepairPlanSummaryDetail || "—")}</div>
<div class="bme-config-help" style="margin-top:10px">${_escHtml(t("authority.diagnostics.recentArtifacts"))}</div>
${authorityArtifactsHtml}
${ps.authorityRepairError ? `<div class="bme-config-help" style="margin-top:6px;color:#e74c3c">${_escHtml(ps.authorityRepairError)}</div>` : ""}
${ps.authorityCheckpointRestoreError ? `<div class="bme-config-help" style="margin-top:6px;color:#e74c3c">${_escHtml(ps.authorityCheckpointRestoreError)}</div>` : ""}
@@ -3747,56 +3792,66 @@ function _refreshTaskPersistence() {
try {
if (action === "audit") {
if (typeof _actionHandlers.runAuthorityConsistencyAudit !== "function") return;
toastr.info("Authority 一致性审计中…", "ST-BME", { timeOut: 2000 });
toastr.info(t("authority.toast.auditRunning"), "ST-BME", { timeOut: 2000 });
const result = await _actionHandlers.runAuthorityConsistencyAudit();
if (result?.success) {
toastr.success(result?.audit?.summary?.label || "Authority 审计完成", "ST-BME");
toastr.success(
formatI18nValue(result?.audit?.summary, {
keyField: "labelKey",
paramsField: "labelParams",
fallbackField: "label",
fallback: t("authority.toast.auditCompleted"),
}) || t("authority.toast.auditCompleted"),
"ST-BME",
);
} else {
toastr.warning(`Authority 审计失败:${result?.error || "unknown"}`, "ST-BME");
toastr.warning(t("authority.toast.auditFailed", { error: result?.error || "unknown" }), "ST-BME");
}
} else if (action === "repair-plan") {
if (typeof _actionHandlers.runAuthorityConsistencyRepairPlan !== "function") return;
if (authorityRepairPlan.requiresConfirmation) {
const confirmed = globalThis.confirm?.(
`副本同步计划将按以下顺序执行:\n${authorityRepairPlan.steps.map((step, index) => `${index + 1}. ${step.label}`).join("\n")}\n\n其中包含从 Blob Checkpoint 恢复 SQL。此操作只适合 SQL 缺失、损坏或需要回滚时使用,确定继续?`,
t("authority.confirm.repairPlan", {
steps: authorityRepairPlan.steps.map((step, index) => `${index + 1}. ${formatI18nValue(step, { keyField: "labelKey", paramsField: "labelParams", fallbackField: "label" })}`).join("\n"),
}),
);
if (!confirmed) return;
}
toastr.info("Authority 副本同步执行中…", "ST-BME", { timeOut: 2000 });
toastr.info(t("authority.toast.repairRunning"), "ST-BME", { timeOut: 2000 });
const result = await _actionHandlers.runAuthorityConsistencyRepairPlan();
if (result?.success) {
const stepCount = Number(result?.repairResult?.steps?.length || result?.results?.length || 0);
if (result?.partialFailure || result?.repairResult?.partialFailure || result?.outcome === "warning" || result?.repairResult?.outcome === "warning") {
toastr.warning(`Authority 副本部分同步失败;记忆图谱不受影响${stepCount > 0 ? `${stepCount} 步)` : ""}`, "ST-BME");
toastr.warning(t("authority.toast.repairPartialFailure", { count: stepCount }), "ST-BME");
} else if (result?.handoffRequired || result?.repairResult?.handoffRequired) {
toastr.success(`Authority 副本同步已交接异步 Job${stepCount > 0 ? `${stepCount} 步)` : ""}`, "ST-BME");
toastr.success(t("authority.toast.repairHandedOff", { count: stepCount }), "ST-BME");
} else {
toastr.success(`Authority 副本同步已完成${stepCount > 0 ? `${stepCount} 步)` : ""}`, "ST-BME");
toastr.success(t("authority.toast.repairCompleted", { count: stepCount }), "ST-BME");
}
} else {
toastr.warning(`Authority 副本同步失败:${result?.error || "unknown"}`, "ST-BME");
toastr.warning(t("authority.toast.repairFailed", { error: result?.error || "unknown" }), "ST-BME");
}
} else if (action === "checkpoint") {
if (typeof _actionHandlers.writeAuthorityCheckpoint !== "function") return;
toastr.info("Authority Checkpoint 写入中…", "ST-BME", { timeOut: 2000 });
toastr.info(t("authority.toast.checkpointWriting"), "ST-BME", { timeOut: 2000 });
const result = await _actionHandlers.writeAuthorityCheckpoint();
if (result?.success) {
toastr.success(`Authority Checkpoint 已写入rev ${Number(result?.result?.checkpointRevision || result?.result?.revision || 0) || "?"}`, "ST-BME");
toastr.success(t("authority.toast.checkpointWritten", { revision: Number(result?.result?.checkpointRevision || result?.result?.revision || 0) || "?" }), "ST-BME");
} else {
toastr.warning(`Authority Checkpoint 写入失败:${result?.error || "unknown"}`, "ST-BME");
toastr.warning(t("authority.toast.checkpointWriteFailed", { error: result?.error || "unknown" }), "ST-BME");
}
} else if (action === "restore") {
if (typeof _actionHandlers.restoreAuthorityCheckpoint !== "function") return;
const confirmed = globalThis.confirm?.(
`灾难恢复会用 Blob Checkpoint 覆盖 Authority SQL。\n\nSQL rev: ${authorityAuditSqlRevision}\nCheckpoint rev: ${authorityAuditBlobRevision}\n\n只有 SQL 缺失、损坏或明确需要回滚时才继续。确定执行?`,
t("authority.confirm.checkpointRestore", { sqlRevision: authorityAuditSqlRevision, checkpointRevision: authorityAuditBlobRevision }),
);
if (!confirmed) return;
toastr.info("Authority Checkpoint 恢复中…", "ST-BME", { timeOut: 2000 });
toastr.info(t("authority.toast.checkpointRestoring"), "ST-BME", { timeOut: 2000 });
const result = await _actionHandlers.restoreAuthorityCheckpoint();
if (result?.success) {
toastr.success(`Authority Checkpoint 已恢复rev ${Number(result?.result?.revision || 0) || "?"}`, "ST-BME");
toastr.success(t("authority.toast.checkpointRestored", { revision: Number(result?.result?.revision || 0) || "?" }), "ST-BME");
} else {
toastr.warning(`Authority Checkpoint 恢复失败:${result?.error || "unknown"}`, "ST-BME");
toastr.warning(t("authority.toast.checkpointRestoreFailed", { error: result?.error || "unknown" }), "ST-BME");
}
} else if (action === "rebuild-trivium") {
if (typeof _actionHandlers.rebuildVectorIndex !== "function") return;
@@ -3806,9 +3861,9 @@ function _refreshTaskPersistence() {
if (typeof _actionHandlers.captureAuthorityPerformanceBaseline !== "function") return;
const result = await _actionHandlers.captureAuthorityPerformanceBaseline();
if (result?.ok) {
toastr.success("Authority Perf Baseline 已捕获", "ST-BME");
toastr.success(t("authority.toast.baselineCaptured"), "ST-BME");
} else {
toastr.warning(`Authority Perf Baseline 捕获失败:${result?.error || "unknown"}`, "ST-BME");
toastr.warning(t("authority.toast.baselineCaptureFailed", { error: result?.error || "unknown" }), "ST-BME");
}
} else if (action === "bundle") {
if (typeof _actionHandlers.exportDiagnosticsBundle !== "function") return;
@@ -3820,26 +3875,26 @@ function _refreshTaskPersistence() {
if (typeof _actionHandlers.refreshAuthorityDiagnosticsArtifacts !== "function") return;
const result = await _actionHandlers.refreshAuthorityDiagnosticsArtifacts();
if (result?.ok) {
toastr.success(`已刷新 diagnostics artifact 列表(${Number(result?.entries?.length || 0)} 条)`, "ST-BME");
toastr.success(t("authority.toast.artifactsRefreshed", { count: Number(result?.entries?.length || 0) }), "ST-BME");
} else {
toastr.warning(`diagnostics artifact 列表刷新失败:${result?.error || "unknown"}`, "ST-BME");
toastr.warning(t("authority.toast.artifactsRefreshFailed", { error: result?.error || "unknown" }), "ST-BME");
}
}
} catch (error) {
toastr.error(
action === "restore"
? `Authority Checkpoint 恢复失败: ${error?.message || error}`
? t("authority.toast.checkpointRestoreFailed", { error: error?.message || error })
: action === "repair-plan"
? `Authority 副本同步失败: ${error?.message || error}`
? t("authority.toast.repairFailed", { error: error?.message || error })
: action === "checkpoint"
? `Authority Checkpoint 写入失败: ${error?.message || error}`
? t("authority.toast.checkpointWriteFailed", { error: error?.message || error })
: action === "rebuild-trivium"
? `Authority Trivium 重建失败: ${error?.message || error}`
? t("authority.toast.triviumRebuildFailed", { error: error?.message || error })
: action === "baseline"
? `Authority Perf Baseline 捕获失败: ${error?.message || error}`
? t("authority.toast.baselineCaptureFailed", { error: error?.message || error })
: action === "artifacts-refresh"
? `diagnostics artifact 列表刷新失败: ${error?.message || error}`
: `Authority 审计失败: ${error?.message || error}`,
? t("authority.toast.artifactsRefreshFailed", { error: error?.message || error })
: t("authority.toast.auditFailed", { error: error?.message || error }),
"ST-BME",
);
} finally {
@@ -3859,35 +3914,35 @@ function _refreshTaskPersistence() {
try {
if (action === "copy-path") {
await _copyTextToClipboard(artifactPath);
toastr.success("诊断包路径已复制", "ST-BME");
toastr.success(t("authority.toast.diagnosticPathCopied"), "ST-BME");
} else if (action === "download") {
if (typeof _actionHandlers.readAuthorityDiagnosticsArtifact !== "function") return;
const result = await _actionHandlers.readAuthorityDiagnosticsArtifact(artifactPath);
if (!result?.ok || !result?.payload) {
toastr.warning(`诊断包读取失败:${result?.error || "unknown"}`, "ST-BME");
toastr.warning(t("authority.toast.diagnosticReadFailed", { error: result?.error || "unknown" }), "ST-BME");
return;
}
const fileName = String(artifactPath.split("/").pop() || `st-bme-diagnostics-${artifactReason}.json`);
_downloadJsonFile(result.payload, fileName);
toastr.success("诊断包已下载", "ST-BME");
toastr.success(t("authority.toast.diagnosticDownloaded"), "ST-BME");
} else if (action === "delete") {
if (typeof _actionHandlers.deleteAuthorityDiagnosticsArtifact !== "function") return;
const confirmed = globalThis.confirm?.(`确定删除该 diagnostics artifact\n${artifactPath}`);
const confirmed = globalThis.confirm?.(t("authority.confirm.deleteArtifact", { path: artifactPath }));
if (!confirmed) return;
const result = await _actionHandlers.deleteAuthorityDiagnosticsArtifact(artifactPath);
if (result?.ok) {
toastr.success("诊断包已删除", "ST-BME");
toastr.success(t("authority.toast.diagnosticDeleted"), "ST-BME");
} else {
toastr.warning(`诊断包删除失败:${result?.error || "unknown"}`, "ST-BME");
toastr.warning(t("authority.toast.diagnosticDeleteFailed", { error: result?.error || "unknown" }), "ST-BME");
}
}
} catch (error) {
toastr.error(
action === "copy-path"
? `复制路径失败: ${error?.message || error}`
? t("authority.toast.copyPathFailed", { error: error?.message || error })
: action === "download"
? `下载诊断包失败: ${error?.message || error}`
: `删除诊断包失败: ${error?.message || error}`,
? t("authority.toast.diagnosticDownloadFailed", { error: error?.message || error })
: t("authority.toast.diagnosticDeleteFailed", { error: error?.message || error }),
"ST-BME",
);
} finally {