Ship Cursor-only MCP admin and modular Admin/Chat UI.

Plugins/Skills install and edit via mcpServers JSON, with clearer row actions, soft reloads, and instant loading placeholders; also drop MCP market and harden related scheduler/logging/exec guards.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-08-12 19:38:26 +08:00
parent 2aff012b48
commit f6f129931f
61 changed files with 18057 additions and 16327 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,24 @@
import messagesZh from "./messages-zh.js";
import messagesEn from "./messages-en.js";
import { state } from "../state.js";
export const I18N = { zh: messagesZh, en: messagesEn };
export const LANG_KEY = "ops_admin_lang";
export function t(key, vars) {
let s = (I18N[state.currentLang] && I18N[state.currentLang][key]) || (I18N.en && I18N.en[key]) || key;
if (vars && typeof s === "string") {
for (const [k, v] of Object.entries(vars)) {
s = s.replace(new RegExp(`\\{${k}\\}`, "g"), String(v));
}
}
return s;
}
export function applyI18nStatic() {
document.documentElement.lang = state.currentLang === "zh" ? "zh-CN" : "en";
document.querySelectorAll("[data-i18n]").forEach((node) => {
const key = node.getAttribute("data-i18n");
node.textContent = t(key);
});
}

View file

@ -0,0 +1,222 @@
export default {
"auth.chatLoginDenied": "This account cannot use chat (contact an admin).",
"auth.consoleAdminOnly": "Only users with the admin role may sign in to the console.",
"auth.disabled": "Account is disabled",
"auth.invalid": "Login failed",
"auth.login": "Login",
"auth.logout": "Logout",
"auth.password": "Password",
"auth.username": "Username",
"boot.loadFailed": "Failed to load chat.js",
"boot.loadFailedHint": "Confirm the gateway is up and /admin/assets/chat.js is reachable.",
"boot.loading": "Loading chat…",
"boot.loadingHint": "If this stays too long, check whether the gateway is running.",
"boot.stillLoading": "Still loading…",
"boot.stillLoadingHint": "If the login form does not appear, refresh after confirming the gateway is up. Desktop: desktop.log / backend.log.",
"chat.activeModelLabel": "Model",
"chat.applyAfterTurn": "applies to history after current turn",
"chat.attach": "Attach",
"chat.attachment.download": "Download",
"chat.attachment.preview": "Preview",
"chat.attachment.previewEmpty": "(empty)",
"chat.attachment.previewError": "Preview failed",
"chat.attachment.previewLoading": "Loading…",
"chat.attachmentAcl": "Attachment ACL",
"chat.attachmentAclBackfill": "Backfill ACL",
"chat.attachmentAclBackfillFail": "Backfill failed: {error}",
"chat.attachmentAclBackfillOk": "Backfill done: inserted {inserted} rows (scanned {scanned_messages} messages)",
"chat.attachmentAclBackfillPrompt": "Backfill attachment_acl by scanning historical message attachments? Recommended during off-peak hours.",
"chat.attachmentLimits": "Attachment limits",
"chat.attachmentLimitsCancel": "Cancel",
"chat.attachmentLimitsCellChars": "Max chars per cell",
"chat.attachmentLimitsCols": "Max columns kept",
"chat.attachmentLimitsDenied": "Not authorized to change attachment limits",
"chat.attachmentLimitsHighPreviewWarn": "Large-table preview rows is above 200, which may significantly increase token usage. Continue?",
"chat.attachmentLimitsInvalidJson": "Invalid attachment limits JSON",
"chat.attachmentLimitsInvalidNumber": "Please enter valid positive integers",
"chat.attachmentLimitsLargePreviewRows": "Large-table preview rows",
"chat.attachmentLimitsMaxSheets": "Max Excel sheets",
"chat.attachmentLimitsPrompt": "Edit attachment limits JSON (empty to restore defaults)",
"chat.attachmentLimitsReset": "Reset defaults",
"chat.attachmentLimitsRows": "Max rows read",
"chat.attachmentLimitsSave": "Save",
"chat.attachmentLimitsSaved": "Attachment limits updated",
"chat.attachmentLimitsSqlTimeoutMs": "SQL timeout (ms)",
"chat.attachmentLimitsTitle": "Attachment limits",
"chat.attachmentLimitsToolEnabled": "Enable large-table tool mode",
"chat.attachmentLimitsToolMaxBytes": "Max file bytes for tool mode",
"chat.attachmentLimitsToolMinRows": "Min rows to trigger tool mode",
"chat.attachmentUploaded": "(attachment uploaded)",
"chat.audit": "Audit & trace",
"chat.backAdmin": "Admin console",
"chat.call": "call",
"chat.callBracket": "[call] {name}",
"chat.compressHistory": "Compress chat",
"chat.compressHistoryFail": "Compaction failed: {error}",
"chat.compressHistoryOk": "Compaction done: scanned {scanned} tool messages, rewritten {rewritten}, oversized compacted {compacted} (skipped {skipped} already compacted).",
"chat.compressHistoryPrompt": "Rewrite this session's historical tool outputs using replay-guard compaction (irreversible). Use only when a session is polluted by huge tool_result.",
"chat.connecting": "Connecting…",
"chat.copy": "Copy",
"chat.copyFail": "Copy failed",
"chat.copyOk": "Copied",
"chat.delete": "Delete",
"chat.deleteConfirm": "Delete this session?",
"chat.deleteMessage": "Delete message",
"chat.deleteMessageConfirm": "Delete this message?",
"chat.dispatchLabelsCancel": "Cancel",
"chat.dispatchLabelsClear": "Clear",
"chat.dispatchLabelsDiffOnly": "Show changed only",
"chat.dispatchLabelsEdit": "Edit reason labels",
"chat.dispatchLabelsEffectivePreview": "Effective preview",
"chat.dispatchLabelsExport": "Export effective",
"chat.dispatchLabelsImport": "Import JSON",
"chat.dispatchLabelsInvalidJson": "Invalid JSON format",
"chat.dispatchLabelsPrompt": "Edit dispatch reason override JSON (empty to clear)",
"chat.dispatchLabelsRestoreDefaults": "Restore defaults template",
"chat.dispatchLabelsSave": "Save",
"chat.dispatchLabelsSaved": "Reason labels updated",
"chat.dispatchLabelsTitle": "Edit reason label overrides",
"chat.dispatchLabelsUnsavedConfirm": "You have unsaved changes. Close anyway?",
"chat.dynamicAllReasons": "View full mix",
"chat.dynamicAllReasonsTitle": "Full reason mix",
"chat.dynamicStats": "Dynamic experts (recent): hit {dynamic}, fallback {fallback}",
"chat.dynamicStatsDetail": "Hit rate {rate}% · Top reason {reason}",
"chat.dynamicTopReasons": "Reason mix {items}",
"chat.empty": "No messages yet",
"chat.encodingAttachments": "Encoding attachments…",
"chat.error": "Request failed",
"chat.execApplied": "Applied: {mode} / {specialist}",
"chat.execModeAgent": "Agent",
"chat.execModeApplied": "Execution: {mode}",
"chat.execModeLabel": "Execution",
"chat.execModePlan": "Plan",
"chat.expandFull": "Load full text (may be slow)",
"chat.exportJson": "Export JSON",
"chat.exportMd": "Export Markdown",
"chat.fork": "Duplicate chat",
"chat.historyComplete": "Reached the start of this chat",
"chat.historyTruncated": "Showing latest {shown}/{total} · scroll up for older",
"chat.imageBlockedHint": "Image blocked from inline display; open original URL",
"chat.imageViewerClose": "Close",
"chat.imageViewerDownload": "Download image",
"chat.imageViewerHint": "Click image to enlarge; click outside or Esc to close",
"chat.jobs": "Background jobs",
"chat.jobsClose": "Close",
"chat.jobsEmpty": "No background jobs",
"chat.jobsHint": "Long agent jobs appear here and may keep running after the chat turn ends. Kill them manually if needed.",
"chat.jobsKill": "Kill",
"chat.jobsKillAll": "Kill all running",
"chat.jobsKillAllConfirm": "Kill all running jobs?",
"chat.jobsKillConfirm": "Kill this job? The process will be force-terminated.",
"chat.jobsPurge": "Purge",
"chat.jobsPurgeConfirm": "Remove local records/logs for this finished job?",
"chat.jobsRefresh": "Refresh",
"chat.jobsRunning": "{n} running",
"chat.jobsStatus.cancelled": "cancelled",
"chat.jobsStatus.failed": "failed",
"chat.jobsStatus.running": "running",
"chat.jobsStatus.succeeded": "succeeded",
"chat.jobsStatus.timeout": "timeout",
"chat.jobsTitle": "Background jobs",
"chat.langSwitchedWhileStreaming": "Language updated. Session is still streaming; refresh the page to reload all labels.",
"chat.liveFull": "[LIVE FULL]",
"chat.loadMore": "Load more",
"chat.loading": "Loading…",
"chat.loadingDots": "Loading...",
"chat.loadingOlder": "Loading older messages…",
"chat.logs": "Logs",
"chat.marker.reclaimed": "Turn markers reclaimed {n}",
"chat.marker.summary": "File markers: pointers {p}, envelope {e} (in-envelope {ep})",
"chat.marker.ttl": "TTL: turn {t} / session {s} / keep {k}",
"chat.memoryApplied": "Memory: {mode}",
"chat.memoryModeDefault": "Default (inject allowed)",
"chat.memoryModeDefaultShort": "default",
"chat.memoryModeLabel": "Memory Mode",
"chat.memoryModeStoreOnly": "Store only (no inject)",
"chat.memoryModeStoreOnlyShort": "store_only",
"chat.modeComprehensive": "Comprehensive",
"chat.modeComprehensiveShort": "Comprehensive",
"chat.modeExpert": "Expert",
"chat.modeExpertShort": "Expert",
"chat.modeLabel": "Mode",
"chat.modeNotPinned": "Mode not pinned: backend returned interaction_mode={mode} specialist={specialist} (want expert/{want})",
"chat.modelProfile": "Model",
"chat.myProfile": "Settings",
"chat.newSession": "New chat",
"chat.noSessions": "No sessions yet. Create one to start.",
"chat.pageTitle": "oliver",
"chat.placeholder": "Message…",
"chat.reason.dynamic_agent_build_failed": "Dynamic agent build failed",
"chat.reason.dynamic_agent_selected": "Dynamic agent selected",
"chat.reason.manager_factory_failed": "Specialist executor factory failed",
"chat.reason.manager_model_missing": "Manager model missing",
"chat.reason.manager_no_specialist_fallback": "No specialist; fallback to generalist",
"chat.reason.manager_route_missing": "Manager route missing",
"chat.reason.manager_select_failed": "Manager selection failed",
"chat.remove": "Remove",
"chat.rename": "Rename",
"chat.saveModeFailed": "Failed to save mode: {error}",
"chat.send": "Send",
"chat.sending": "Sending…",
"chat.sessionMenu": "Session actions",
"chat.sessions": "Sessions",
"chat.specialistGeneralist": "Generalist",
"chat.specialistGeneralistShort": "Generalist",
"chat.specialistImage": "Image",
"chat.specialistImageShort": "Image",
"chat.specialistLabel": "Specialist",
"chat.specialistManage": "Specialist Toggle",
"chat.specialistManageDenied": "Not authorized to change specialist flags",
"chat.specialistManagePromptOff": "Disable memory specialist?",
"chat.specialistManagePromptOn": "Enable memory specialist?",
"chat.specialistMemory": "Memory",
"chat.specialistMemoryShort": "Memory",
"chat.specialistOps": "Ops",
"chat.specialistOpsShort": "Ops",
"chat.specialistVideoShort": "Video",
"chat.stageLocalBoth": " · stage(start→run)={start}ms (run→first)={first}ms",
"chat.stageLocalStart": " · stage(start→run)={start}ms",
"chat.stageSeg": " · stage(A→G:{a2g} / G→M:{g2m} / M→F:{m2f})",
"chat.status.agent": "Agent",
"chat.status.end": "Done",
"chat.status.error": "Error",
"chat.status.oclaw": "Assistant",
"chat.status.plan": "Plan",
"chat.status.running": "Running",
"chat.status.start": "Starting",
"chat.status.tool": "Tool",
"chat.stop": "Stop",
"chat.timeoutRecovered": "Request timed out; recovered this turn from history.",
"chat.toolCall": "Tool call",
"chat.toolResult": "Tool result",
"chat.tools": "Reasoning",
"chat.tools.hidden": "Reasoning hidden",
"chat.tools.off": "Off",
"chat.tools.on": "On",
"chat.tools.visible": "Reasoning visible",
"chat.truncatedNote": "…(truncated {n} chars for UI performance)",
"chat.wikiPreviewClose": "Close",
"chat.wikiPreviewTitle": "Wiki preview: {path}",
"chat.wikiToastFailed": "Wiki capture failed: {error}",
"chat.wikiToastMerged": "Wiki captured: +{merged} (dedup skipped {skipped})",
"chat.wikiViewMerged": "View merged-turns",
"chat.wikiViewTopic": "View topic: {topic}",
"chat.wsInterruptedRecovered": "WS stream interrupted; recovered this turn from history.",
"chat.zoomIn": "Zoom in",
"chat.zoomOut": "Zoom out",
"chat.zoomReset": "Reset zoom",
"common.error": "Error",
"lang.switch": "中文",
"reasoning.processNotes": "Main-channel notes",
"reasoning.summary": "Model reasoning",
"theme.catppuccin": "Catppuccin Mocha",
"theme.deepseek": "DeepSeek",
"theme.dracula": "Dracula",
"theme.forest": "Forest",
"theme.github": "GitHub Dark",
"theme.label": "Color theme",
"theme.light": "Light",
"theme.netx": "NetX Dark",
"theme.nord": "Nord",
"tool.streamTitle": "Tools & progress (this turn)",
};

View file

@ -0,0 +1,222 @@
export default {
"auth.chatLoginDenied": "当前账号无法使用对话(请联系管理员)。",
"auth.consoleAdminOnly": "仅管理员(admin 角色)可登录管理台。",
"auth.disabled": "账号已被禁用,请联系管理员",
"auth.invalid": "登录失败,请检查凭据",
"auth.login": "登录",
"auth.logout": "退出登录",
"auth.password": "密码",
"auth.username": "用户名",
"boot.loadFailed": "无法加载 chat.js",
"boot.loadFailedHint": "请确认网关已启动且 /admin/assets/chat.js 可访问。",
"boot.loading": "正在加载对话…",
"boot.loadingHint": "若长时间停留在此,请检查网关是否已启动。",
"boot.stillLoading": "加载仍在进行…",
"boot.stillLoadingHint": "若超过数秒仍无登录框,请确认网关已启动后刷新。桌面端可查看 desktop.log / backend.log。",
"chat.activeModelLabel": "模型",
"chat.applyAfterTurn": "本轮结束后应用到历史消息",
"chat.attach": "附件",
"chat.attachment.download": "下载",
"chat.attachment.preview": "预览",
"chat.attachment.previewEmpty": "(空内容)",
"chat.attachment.previewError": "预览失败",
"chat.attachment.previewLoading": "加载中…",
"chat.attachmentAcl": "附件 ACL",
"chat.attachmentAclBackfill": "回填 ACL",
"chat.attachmentAclBackfillFail": "回填失败:{error}",
"chat.attachmentAclBackfillOk": "回填完成:插入 {inserted} 条(扫描 {scanned_messages} 条消息)",
"chat.attachmentAclBackfillPrompt": "回填 attachment_acl(扫描历史消息 attachments)?建议先在低峰期执行。",
"chat.attachmentLimits": "附件阈值",
"chat.attachmentLimitsCancel": "取消",
"chat.attachmentLimitsCellChars": "单元格最大字符数",
"chat.attachmentLimitsCols": "最大保留列数",
"chat.attachmentLimitsDenied": "无权限修改附件阈值",
"chat.attachmentLimitsHighPreviewWarn": "大表摘要预览行数超过 200,可能显著增加 token 消耗。确认继续?",
"chat.attachmentLimitsInvalidJson": "附件阈值 JSON 不合法",
"chat.attachmentLimitsInvalidNumber": "请输入有效正整数",
"chat.attachmentLimitsLargePreviewRows": "大表摘要预览行数",
"chat.attachmentLimitsMaxSheets": "Excel 最大 Sheet 数",
"chat.attachmentLimitsPrompt": "编辑附件阈值 JSON(留空恢复默认)",
"chat.attachmentLimitsReset": "恢复默认",
"chat.attachmentLimitsRows": "最大读取行数",
"chat.attachmentLimitsSave": "保存",
"chat.attachmentLimitsSaved": "附件阈值已更新",
"chat.attachmentLimitsSqlTimeoutMs": "SQL 超时(毫秒)",
"chat.attachmentLimitsTitle": "附件阈值配置",
"chat.attachmentLimitsToolEnabled": "启用大表工具模式",
"chat.attachmentLimitsToolMaxBytes": "工具模式最大文件字节数",
"chat.attachmentLimitsToolMinRows": "触发工具模式最小行数",
"chat.attachmentUploaded": "(已上传附件)",
"chat.audit": "审计与追踪",
"chat.backAdmin": "管理台",
"chat.call": "调用",
"chat.callBracket": "[调用] {name}",
"chat.compressHistory": "压缩对话",
"chat.compressHistoryFail": "压缩失败:{error}",
"chat.compressHistoryOk": "压缩完成:扫描 {scanned} 条 tool 消息,重写 {rewritten} 条,超限压缩 {compacted} 条(已跳过 {skipped} 条已压缩)。",
"chat.compressHistoryPrompt": "将本会话历史工具输出按回放策略写回压缩(不可逆)?建议仅在发现超大 tool_result/导出卡顿时使用。",
"chat.connecting": "连接中…",
"chat.copy": "复制",
"chat.copyFail": "复制失败",
"chat.copyOk": "已复制",
"chat.delete": "删除",
"chat.deleteConfirm": "删除此会话?",
"chat.deleteMessage": "删除消息",
"chat.deleteMessageConfirm": "删除这条消息?",
"chat.dispatchLabelsCancel": "取消",
"chat.dispatchLabelsClear": "清空",
"chat.dispatchLabelsDiffOnly": "仅显示变更项",
"chat.dispatchLabelsEdit": "编辑原因文案",
"chat.dispatchLabelsEffectivePreview": "Effective 预览",
"chat.dispatchLabelsExport": "导出 Effective",
"chat.dispatchLabelsImport": "导入 JSON",
"chat.dispatchLabelsInvalidJson": "JSON 格式不合法",
"chat.dispatchLabelsPrompt": "编辑 dispatch reason 覆盖 JSON(留空清空)",
"chat.dispatchLabelsRestoreDefaults": "恢复默认模板",
"chat.dispatchLabelsSave": "保存",
"chat.dispatchLabelsSaved": "原因文案已更新",
"chat.dispatchLabelsTitle": "编辑原因文案覆盖",
"chat.dispatchLabelsUnsavedConfirm": "当前有未保存变更,确认关闭?",
"chat.dynamicAllReasons": "查看完整分布",
"chat.dynamicAllReasonsTitle": "完整原因分布",
"chat.dynamicStats": "动态专家近窗:命中 {dynamic},回退 {fallback}",
"chat.dynamicStatsDetail": "命中率 {rate}% · Top原因 {reason}",
"chat.dynamicTopReasons": "原因分布 {items}",
"chat.empty": "暂无消息",
"chat.encodingAttachments": "编码附件中…",
"chat.error": "请求失败",
"chat.execApplied": "本轮执行:{mode} / {specialist}",
"chat.execModeAgent": "Agent",
"chat.execModeApplied": "执行态:{mode}",
"chat.execModeLabel": "执行态",
"chat.execModePlan": "Plan",
"chat.expandFull": "展开全文(可能较慢)",
"chat.exportJson": "导出 JSON",
"chat.exportMd": "导出 Markdown",
"chat.fork": "复制会话",
"chat.historyComplete": "已到最早消息",
"chat.historyTruncated": "已显示最近 {shown}/{total} 条 · 上滑加载更早",
"chat.imageBlockedHint": "图片无法嵌入显示(可能被防盗链),点击打开原始链接",
"chat.imageViewerClose": "关闭",
"chat.imageViewerDownload": "下载图片",
"chat.imageViewerHint": "点击查看大图,空白处或 Esc 关闭",
"chat.jobs": "后台任务",
"chat.jobsClose": "关闭",
"chat.jobsEmpty": "暂无后台任务",
"chat.jobsHint": "Agent 启动的长任务会列在这里;对话结束后进程仍可能继续。可手动终止以防风险。",
"chat.jobsKill": "终止",
"chat.jobsKillAll": "全部终止",
"chat.jobsKillAllConfirm": "确认终止全部运行中的任务?",
"chat.jobsKillConfirm": "确认终止该任务?进程将被强制结束。",
"chat.jobsPurge": "清除记录",
"chat.jobsPurgeConfirm": "清除该已结束任务的本地记录与日志?",
"chat.jobsRefresh": "刷新",
"chat.jobsRunning": "运行中 {n}",
"chat.jobsStatus.cancelled": "已取消",
"chat.jobsStatus.failed": "失败",
"chat.jobsStatus.running": "运行中",
"chat.jobsStatus.succeeded": "成功",
"chat.jobsStatus.timeout": "超时",
"chat.jobsTitle": "后台任务",
"chat.langSwitchedWhileStreaming": "语言已切换。当前会话仍在输出,刷新页面可更新全部界面文案。",
"chat.liveFull": "[实时全量]",
"chat.loadMore": "加载更多",
"chat.loading": "加载中…",
"chat.loadingDots": "加载中...",
"chat.loadingOlder": "正在加载更早消息…",
"chat.logs": "日志",
"chat.marker.reclaimed": "本轮回收 turn 标记 {n}",
"chat.marker.summary": "文件标记:指针 {p},封套 {e}(封套内 {ep})",
"chat.marker.ttl": "TTL:turn {t} / session {s} / keep {k}",
"chat.memoryApplied": "记忆:{mode}",
"chat.memoryModeDefault": "默认(可注入)",
"chat.memoryModeDefaultShort": "默认",
"chat.memoryModeLabel": "记忆模式",
"chat.memoryModeStoreOnly": "仅记录不注入",
"chat.memoryModeStoreOnlyShort": "仅记录",
"chat.modeComprehensive": "综合",
"chat.modeComprehensiveShort": "综合",
"chat.modeExpert": "专家",
"chat.modeExpertShort": "专家",
"chat.modeLabel": "模式",
"chat.modeNotPinned": "模式未固化:后端返回 interaction_mode={mode} specialist={specialist}(期望 expert/{want})",
"chat.modelProfile": "模型",
"chat.myProfile": "设置",
"chat.newSession": "开启新对话",
"chat.noSessions": "暂无会话,点击新建开始。",
"chat.pageTitle": "oliver",
"chat.placeholder": "输入消息…",
"chat.reason.dynamic_agent_build_failed": "动态专家构建失败",
"chat.reason.dynamic_agent_selected": "动态专家命中",
"chat.reason.manager_factory_failed": "专家执行器创建失败",
"chat.reason.manager_model_missing": "全能者模型不可用",
"chat.reason.manager_no_specialist_fallback": "无可用专家,回退通用",
"chat.reason.manager_route_missing": "全能者路由缺失",
"chat.reason.manager_select_failed": "全能者决策失败",
"chat.remove": "移除",
"chat.rename": "重命名",
"chat.saveModeFailed": "保存“模式”失败:{error}",
"chat.send": "发送",
"chat.sending": "发送中…",
"chat.sessionMenu": "会话操作",
"chat.sessions": "会话",
"chat.specialistGeneralist": "通用",
"chat.specialistGeneralistShort": "通用",
"chat.specialistImage": "图像",
"chat.specialistImageShort": "图像",
"chat.specialistLabel": "专家",
"chat.specialistManage": "专家开关",
"chat.specialistManageDenied": "无权限修改专家开关",
"chat.specialistManagePromptOff": "是否关闭 memory 专家?",
"chat.specialistManagePromptOn": "是否启用 memory 专家?",
"chat.specialistMemory": "记忆",
"chat.specialistMemoryShort": "记忆",
"chat.specialistOps": "运维",
"chat.specialistOpsShort": "运维",
"chat.specialistVideoShort": "视频",
"chat.stageLocalBoth": " · 阶段耗时(启动→执行)={start}ms (执行→首字)={first}ms",
"chat.stageLocalStart": " · 阶段耗时(启动→执行)={start}ms",
"chat.stageSeg": " · 阶段(A→G:{a2g} / G→M:{g2m} / M→F:{m2f})",
"chat.status.agent": "专家",
"chat.status.end": "完成",
"chat.status.error": "异常",
"chat.status.oclaw": "助手",
"chat.status.plan": "规划",
"chat.status.running": "执行中",
"chat.status.start": "启动",
"chat.status.tool": "工具",
"chat.stop": "停止",
"chat.timeoutRecovered": "请求超时,已从历史补齐本轮结果。",
"chat.toolCall": "工具调用",
"chat.toolResult": "工具结果",
"chat.tools": "推理",
"chat.tools.hidden": "推理已隐藏",
"chat.tools.off": "关",
"chat.tools.on": "开",
"chat.tools.visible": "推理已显示",
"chat.truncatedNote": "…(为保证界面性能,已截断 {n} 字符)",
"chat.wikiPreviewClose": "关闭",
"chat.wikiPreviewTitle": "Wiki 预览:{path}",
"chat.wikiToastFailed": "Wiki 收录失败:{error}",
"chat.wikiToastMerged": "Wiki 已收录:+{merged}(去重跳过 {skipped})",
"chat.wikiViewMerged": "查看 merged-turns",
"chat.wikiViewTopic": "查看 topic:{topic}",
"chat.wsInterruptedRecovered": "WS流式中断,已从历史补齐本轮结果。",
"chat.zoomIn": "放大",
"chat.zoomOut": "缩小",
"chat.zoomReset": "重置缩放",
"common.error": "错误",
"lang.switch": "English",
"reasoning.processNotes": "主通道过程说明",
"reasoning.summary": "模型推理片段",
"theme.catppuccin": "Catppuccin Mocha",
"theme.deepseek": "DeepSeek",
"theme.dracula": "Dracula",
"theme.forest": "森绿",
"theme.github": "GitHub 暗色",
"theme.label": "界面配色",
"theme.light": "浅色",
"theme.netx": "NetX 暗色",
"theme.nord": "Nord",
"tool.streamTitle": "工具与进度(本轮)",
};

View file

@ -0,0 +1,128 @@
import { AUTH_TOKEN_KEY, AUTH_SESSION_KEY, CHAT_URL_SCOPE_KEY, showToast, openBackgroundJobsPanel, forceReloginRequested, clearAuthAndReloginFlagFromUrl, withTimeout, apiGet, apiPost, el, isChatStreaming, clearChatPageBlockers, loadMeProfile, mount, openAdminFromChat, syncAuthUserLabel, renderLogin, state, t, applyI18nStatic, LANG_KEY } from "./core.js";
import { renderChatUi } from "./ui.js";
async function syncLangFromServer() {
try {
const r = await apiGet("/admin/api/chat/settings/ui-lang");
if (r && r.lang && (r.lang === "zh" || r.lang === "en")) {
state.currentLang = r.lang;
localStorage.setItem(LANG_KEY, state.currentLang);
}
} catch (_) {}
}
async function boot() {
clearChatPageBlockers();
applyI18nStatic();
if (forceReloginRequested()) {
clearAuthAndReloginFlagFromUrl();
}
try {
state.authSession = JSON.parse(localStorage.getItem(AUTH_SESSION_KEY) || "null");
} catch (_) {
state.authSession = null;
}
const tok = String(localStorage.getItem(AUTH_TOKEN_KEY) || "").trim();
if (!state.authSession || !tok) {
localStorage.removeItem(AUTH_TOKEN_KEY);
localStorage.removeItem(AUTH_SESSION_KEY);
state.authSession = null;
try {
await withTimeout(apiPost("/admin/api/auth/bootstrap", {}), 2500, "auth_bootstrap_timeout");
} catch (_) {}
mount(await renderLogin());
applyI18nStatic();
syncAuthUserLabel();
return;
}
try {
await withTimeout(syncLangFromServer(), 2500, "ui_lang_timeout");
} catch (_) {}
try {
await withTimeout(loadMeProfile(), 2500, "me_profile_timeout");
} catch (_) {}
try {
mount(await renderChatUi());
} catch (err) {
mount(
el("div", { class: "chat-app--login" }, [
el("div", { class: "card u-modal-card" }, [
el("div", { class: "card__title", text: t("common.error") }),
el("div", { class: "pre", text: String(err) }),
]),
]),
);
}
applyI18nStatic();
syncAuthUserLabel();
}
document.body.addEventListener("click", async (e) => {
const menuBtn = e.target.closest && e.target.closest(".chat-sess-menu-item[data-menu-action]");
if (menuBtn) {
const action = String(menuBtn.getAttribute("data-menu-action") || "");
document.querySelectorAll(".chat-sess-menu-pop").forEach((n) => n.remove());
if (action === "profile") {
openAdminFromChat("profile");
return;
}
if (action === "jobs") {
openBackgroundJobsPanel().catch((err) => showToast(`${t("chat.error")}: ${String(err)}`, { kind: "error" }));
return;
}
if (action === "logout") {
try {
await apiPost("/admin/api/auth/logout", {});
} catch (_) {}
localStorage.removeItem(AUTH_TOKEN_KEY);
localStorage.removeItem(AUTH_SESSION_KEY);
try {
localStorage.removeItem(CHAT_URL_SCOPE_KEY);
} catch (_) {}
state.authSession = null;
await boot();
return;
}
if (action === "lang") {
state.currentLang = state.currentLang === "zh" ? "en" : "zh";
localStorage.setItem(LANG_KEY, state.currentLang);
try {
await apiPost("/admin/api/chat/settings/ui-lang", { lang: state.currentLang });
} catch (_) {}
clearChatPageBlockers();
if (isChatStreaming()) {
applyI18nStatic();
syncAuthUserLabel();
showToast(t("chat.langSwitchedWhileStreaming"), { kind: "info", ttlMs: 6500 });
} else {
await boot();
}
return;
}
}
});
window.addEventListener("popstate", () => {
if (state.authSession) boot().catch(() => {});
});
document.addEventListener("keydown", (ev) => {
if (ev.key !== "Escape" || ev.defaultPrevented) return;
if (!document.querySelector(".chat-confirm-backdrop, .chat-sess-menu-pop, .chat-img-lightbox, .chat-menu-scrim")) {
return;
}
clearChatPageBlockers();
});
boot().catch((err) => {
mount(
el("div", { class: "chat-app--login" }, [
el("div", { class: "card u-modal-card" }, [
el("div", { class: "card__title", text: t("common.error") }),
el("div", { class: "pre", text: String(err) }),
]),
]),
);
});

View file

@ -0,0 +1,25 @@
const LANG_KEY = "ops_admin_lang";
const CHAT_REASONING_TOGGLE_KEY = "ops_chat_reasoning_toggle";
const ADMIN_CHAT_SHOW_TOOL_OUTPUT_DEFAULT = true;
let lang = "zh";
try {
lang = String(localStorage.getItem(LANG_KEY) || "zh").toLowerCase();
} catch (_) {}
if (lang !== "zh" && lang !== "en") lang = "zh";
let showTool = ADMIN_CHAT_SHOW_TOOL_OUTPUT_DEFAULT;
try {
const _rt = String(localStorage.getItem(CHAT_REASONING_TOGGLE_KEY) || "").trim().toLowerCase();
if (_rt) showTool = ["1", "true", "yes", "on"].includes(_rt);
} catch (_) {}
export const state = {
currentLang: lang,
authSession: null,
adminChatShowToolOutput: showTool,
meProfile: null,
statusReasonPairs: [],
jobsBadgeEl: null,
jobsBtnLabelEl: null,
};

File diff suppressed because it is too large Load diff