mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-11 07:00:48 +08:00
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:
parent
2aff012b48
commit
f6f129931f
61 changed files with 18057 additions and 16327 deletions
3108
interfaces/admin/static/js/chat/core.js
Normal file
3108
interfaces/admin/static/js/chat/core.js
Normal file
File diff suppressed because it is too large
Load diff
24
interfaces/admin/static/js/chat/i18n/index.js
Normal file
24
interfaces/admin/static/js/chat/i18n/index.js
Normal 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);
|
||||
});
|
||||
}
|
||||
222
interfaces/admin/static/js/chat/i18n/messages-en.js
Normal file
222
interfaces/admin/static/js/chat/i18n/messages-en.js
Normal 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)",
|
||||
};
|
||||
222
interfaces/admin/static/js/chat/i18n/messages-zh.js
Normal file
222
interfaces/admin/static/js/chat/i18n/messages-zh.js
Normal 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": "工具与进度(本轮)",
|
||||
};
|
||||
128
interfaces/admin/static/js/chat/main.js
Normal file
128
interfaces/admin/static/js/chat/main.js
Normal 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) }),
|
||||
]),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
25
interfaces/admin/static/js/chat/state.js
Normal file
25
interfaces/admin/static/js/chat/state.js
Normal 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,
|
||||
};
|
||||
2655
interfaces/admin/static/js/chat/ui.js
Normal file
2655
interfaces/admin/static/js/chat/ui.js
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue