mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-11 19:59:50 +08:00
feat(vscodex): add remote Codex collaboration module
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,112 @@
|
||||
(function (root, factory) {
|
||||
"use strict";
|
||||
|
||||
const api = factory();
|
||||
if (typeof module === "object" && module.exports) module.exports = api;
|
||||
if (!root || !root.document) return;
|
||||
|
||||
const bridge = api.createAetherEmbedBridge(root);
|
||||
root.AetherVscodexEmbed = bridge;
|
||||
if (bridge.active) bridge.start();
|
||||
})(typeof window === "object" ? window : undefined, function () {
|
||||
"use strict";
|
||||
|
||||
const VERSION = 1;
|
||||
const PREFIX = "aether-vscodex/";
|
||||
const INBOUND_TYPES = new Set(["connect", "context", "disconnect", "error"]);
|
||||
|
||||
function isAetherEmbed(locationLike) {
|
||||
try {
|
||||
return new URLSearchParams(locationLike?.search || "").get("embed") === "aether";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeTheme(value) {
|
||||
const theme = String(value || "").trim().toLowerCase();
|
||||
return theme === "dark" || theme === "light" ? theme : "system";
|
||||
}
|
||||
|
||||
function createAetherEmbedBridge(windowLike) {
|
||||
const active = isAetherEmbed(windowLike.location);
|
||||
const listeners = new Map();
|
||||
const pending = new Map();
|
||||
let started = false;
|
||||
|
||||
const emit = (name, payload) => {
|
||||
for (const listener of listeners.get(name) || []) listener(payload);
|
||||
};
|
||||
|
||||
const post = (type, payload = {}) => {
|
||||
if (!active || windowLike.parent === windowLike) return false;
|
||||
windowLike.parent.postMessage({ v: VERSION, type: `${PREFIX}${type}`, ...payload }, windowLike.location.origin);
|
||||
return true;
|
||||
};
|
||||
|
||||
const applyContext = (payload) => {
|
||||
if (payload.locale && windowLike.VscodexI18n?.setLocale) {
|
||||
windowLike.VscodexI18n.setLocale(payload.locale, { persist: false });
|
||||
}
|
||||
const theme = normalizeTheme(payload.theme);
|
||||
const documentElement = windowLike.document?.documentElement;
|
||||
if (documentElement) {
|
||||
if (theme === "system") delete documentElement.dataset.theme;
|
||||
else documentElement.dataset.theme = theme;
|
||||
documentElement.style.colorScheme = theme === "system" ? "" : theme;
|
||||
}
|
||||
};
|
||||
|
||||
const handleMessage = (event) => {
|
||||
if (!active || event.origin !== windowLike.location.origin || event.source !== windowLike.parent) return;
|
||||
const message = event.data;
|
||||
if (!message || typeof message !== "object" || message.v !== VERSION || typeof message.type !== "string") return;
|
||||
if (!message.type.startsWith(PREFIX)) return;
|
||||
const name = message.type.slice(PREFIX.length);
|
||||
if (!INBOUND_TYPES.has(name)) return;
|
||||
if (name === "connect" || name === "context") applyContext(message);
|
||||
if (!(listeners.get(name)?.size)) pending.set(name, message);
|
||||
emit(name, message);
|
||||
};
|
||||
|
||||
return {
|
||||
active,
|
||||
version: VERSION,
|
||||
start() {
|
||||
if (!active || started) return;
|
||||
started = true;
|
||||
windowLike.document.body?.classList.add("embed-aether");
|
||||
windowLike.addEventListener("message", handleMessage);
|
||||
post("ready");
|
||||
},
|
||||
stop() {
|
||||
if (!started) return;
|
||||
started = false;
|
||||
windowLike.removeEventListener("message", handleMessage);
|
||||
listeners.clear();
|
||||
pending.clear();
|
||||
},
|
||||
on(name, listener) {
|
||||
if (!INBOUND_TYPES.has(name) || typeof listener !== "function") return () => undefined;
|
||||
if (!listeners.has(name)) listeners.set(name, new Set());
|
||||
listeners.get(name).add(listener);
|
||||
if (pending.has(name)) {
|
||||
const message = pending.get(name);
|
||||
pending.delete(name);
|
||||
listener(message);
|
||||
}
|
||||
return () => listeners.get(name)?.delete(listener);
|
||||
},
|
||||
post,
|
||||
requestTicket(payload = {}) {
|
||||
return post("request-ticket", payload);
|
||||
},
|
||||
reportState(state, payload = {}) {
|
||||
return post("state", { state, ...payload });
|
||||
},
|
||||
_handleMessage: handleMessage,
|
||||
};
|
||||
}
|
||||
|
||||
return { createAetherEmbedBridge, isAetherEmbed, normalizeTheme };
|
||||
});
|
||||
@@ -0,0 +1,541 @@
|
||||
(function (root, factory) {
|
||||
"use strict";
|
||||
|
||||
const api = factory(root);
|
||||
if (typeof module === "object" && module.exports) module.exports = api;
|
||||
if (root?.document) root.VscodexI18n = api;
|
||||
})(typeof window === "object" ? window : undefined, function (root) {
|
||||
"use strict";
|
||||
|
||||
const STORAGE_KEY = "aether-vscodex.locale";
|
||||
const SUPPORTED = new Set(["zh-CN", "en-US"]);
|
||||
const EN = Object.freeze({
|
||||
"本地模式": "Local mode",
|
||||
"独立模式": "Standalone mode",
|
||||
"云端模式": "Cloud mode",
|
||||
"控制模式": "Control mode",
|
||||
"同步": "Sync",
|
||||
"异步": "Async",
|
||||
"同步模式跟随 VS Code 当前会话": "Sync mode follows the current VS Code conversation",
|
||||
"异步模式可独立管理会话": "Async mode manages conversations independently",
|
||||
"正在切换控制模式": "Switching control mode",
|
||||
"控制模式已切换": "Control mode switched",
|
||||
"控制模式切换失败": "Unable to switch control mode",
|
||||
"当前任务或请求完成后才能切换控制模式": "The control mode can be changed after the current task or request finishes",
|
||||
"同步模式下会话管理由 VS Code 控制": "VS Code controls conversation navigation in sync mode",
|
||||
"当前模式不支持修改会话设置": "The current mode does not support changing conversation settings",
|
||||
"本机连接(无需 token)": "Local connection (no token required)",
|
||||
"本机模式无需填写;认证模式再填写": "No token is needed locally; enter one only for authenticated mode",
|
||||
"访问 token(认证模式)": "Access token (authenticated mode)",
|
||||
"粘贴 relay 启动时打印的 token": "Paste the token printed when the relay started",
|
||||
"本地连接无需 token": "No token is needed for a local connection",
|
||||
"编辑外部文件和联网时始终询问": "Always ask before editing external files or using the network",
|
||||
"不限制联网或文件访问": "Allow unrestricted network and file access",
|
||||
"查看请求数据": "View request data",
|
||||
"查看上下文用量": "View context usage",
|
||||
"创建新会话": "New conversation",
|
||||
"打开会话历史": "Open conversation history",
|
||||
"待处理的 Codex 请求": "Pending Codex requests",
|
||||
"当前会话": "Current conversation",
|
||||
"当前模型": "Current model",
|
||||
"切换模型": "Change model",
|
||||
"等待 VS Code 主机": "Waiting for VS Code host",
|
||||
"等待连接": "Waiting for connection",
|
||||
"对话内容": "Conversation",
|
||||
"发送 JSON": "Send JSON",
|
||||
"发送后续指令": "Send follow-up",
|
||||
"发送消息": "Send message",
|
||||
"返回会话列表": "Back to conversations",
|
||||
"返回模型强度": "Back to model effort",
|
||||
"高级": "Advanced",
|
||||
"简洁": "Simple",
|
||||
"更多操作": "More actions",
|
||||
"更高效": "More efficient",
|
||||
"更智能": "More capable",
|
||||
"工作目录": "Working directory",
|
||||
"工作区": "Workspace",
|
||||
"工作区写入": "Workspace write",
|
||||
"回到最新消息": "Jump to latest message",
|
||||
"正在工作,回到最新消息": "Working, jump to latest message",
|
||||
"会话历史": "Conversation history",
|
||||
"会话设置": "Conversation settings",
|
||||
"仅本次 turn": "This turn only",
|
||||
"仅查看文件,不修改工作区": "View files without changing the workspace",
|
||||
"仅对可能不安全的操作询问": "Ask only for potentially unsafe actions",
|
||||
"拒绝": "Deny",
|
||||
"可用会话": "Available conversations",
|
||||
"连接设置": "Connection settings",
|
||||
"留空使用默认模型": "Leave empty to use the default model",
|
||||
"模式": "Mode",
|
||||
"模型": "Model",
|
||||
"模型与推理强度": "Model and reasoning effort",
|
||||
"默认": "Default",
|
||||
"启动新 thread": "Start new thread",
|
||||
"强度": "Effort",
|
||||
"切换模型与推理强度": "Change model and reasoning effort",
|
||||
"清除搜索": "Clear search",
|
||||
"清空当前输出": "Clear current output",
|
||||
"清空对话": "Clear conversation",
|
||||
"取消": "Cancel",
|
||||
"权限设置": "Permission settings",
|
||||
"确认": "Confirm",
|
||||
"确认完全访问": "Confirm full access",
|
||||
"沙箱": "Sandbox",
|
||||
"上下文用量": "Context usage",
|
||||
"设置": "Settings",
|
||||
"审批策略": "Approval policy",
|
||||
"使用 config.toml 中的权限": "Use permissions from config.toml",
|
||||
"使用左右方向键调整强度": "Use the left and right arrow keys to adjust effort",
|
||||
"授权范围": "Authorization scope",
|
||||
"授权与输入": "Approvals and input",
|
||||
"刷新会话列表": "Refresh conversations",
|
||||
"搜索最近会话": "Search recent conversations",
|
||||
"提交后续变更要求": "Ask for follow-up changes",
|
||||
"添加工作区上下文": "Add workspace context",
|
||||
"添加文件": "Add files",
|
||||
"添加文件及更多内容": "Add files and more",
|
||||
"添加照片": "Add photos",
|
||||
"推理强度": "Reasoning effort",
|
||||
"完全访问": "Full access",
|
||||
"完全访问允许 Codex 执行命令、访问互联网并编辑工作区之外的文件。": "Full access lets Codex run commands, use the internet, and edit files outside the workspace.",
|
||||
"网页搜索": "Web search",
|
||||
"未认证": "Unauthenticated",
|
||||
"显示 Codex": "Show Codex",
|
||||
"修改权限": "Change permissions",
|
||||
"需要时询问": "Ask when needed",
|
||||
"已附着当前会话": "Attached to current conversation",
|
||||
"隐藏面板": "Hide panel",
|
||||
"由 Codex 审批": "Let Codex decide",
|
||||
"允许": "Allow",
|
||||
"允许一次": "Allow once",
|
||||
"暂无待处理请求": "No pending requests",
|
||||
"暂无用量数据": "No usage data",
|
||||
"展开面板": "Expand panel",
|
||||
"正在连接": "Connecting",
|
||||
"只读": "Read only",
|
||||
"中断当前 turn": "Interrupt current turn",
|
||||
"重新同步": "Resync",
|
||||
"子代理": "Subagent",
|
||||
"自定义": "Custom",
|
||||
"最近会话": "Recent conversations",
|
||||
"Codex 消息": "Codex messages",
|
||||
"JSON 响应": "JSON response",
|
||||
"语言": "Language",
|
||||
"中文": "Chinese",
|
||||
"跟随浏览器": "Use browser language",
|
||||
"正在连接云端会话": "Connecting to cloud conversation",
|
||||
"正在等待云端连接": "Waiting for cloud connection",
|
||||
"云端连接已断开": "Cloud connection disconnected",
|
||||
"云端连接配置无效": "Invalid cloud connection configuration",
|
||||
"云端连接地址必须与当前页面同源": "The cloud connection URL must be same-origin",
|
||||
"正在获取新的连接凭证": "Requesting new connection credentials",
|
||||
"父页面已断开连接": "Disconnected by the parent page",
|
||||
"当前 relay 需要 token": "This relay requires a token",
|
||||
"WebSocket 未连接": "WebSocket is not connected",
|
||||
"连接中": "Connecting",
|
||||
"同步中": "Syncing",
|
||||
"已连接": "Connected",
|
||||
"认证失败,准备重连": "Authentication failed; preparing to reconnect",
|
||||
"准备重连": "Preparing to reconnect",
|
||||
"重连中": "Reconnecting",
|
||||
"收到无法解析的 relay 消息": "Received an unreadable relay message",
|
||||
"等待 relay 连接": "Waiting for relay connection",
|
||||
"等待 VS Code 主机连接": "Waiting for VS Code host",
|
||||
"VS Code 主机未连接": "VS Code host is disconnected",
|
||||
"等待 VS Code 伴随扩展连接": "Waiting for the VS Code companion extension",
|
||||
"VS Code 伴随扩展未连接": "VS Code companion extension is disconnected",
|
||||
"等待在 VS Code 中打开 Codex 会话": "Open a Codex conversation in VS Code to continue",
|
||||
"会话已关闭": "Conversation closed",
|
||||
"VS Code 会话已关闭": "VS Code conversation closed",
|
||||
"会话操作失败": "Conversation operation failed",
|
||||
"当前任务结束或请求处理后才能切换": "You can switch after the current task or request finishes",
|
||||
"目标会话没有返回 VS Code 快照,请先在官方 Codex 面板打开它": "The target conversation did not return a VS Code snapshot. Open it in the official Codex panel first.",
|
||||
"当前 relay 版本不支持此会话操作,请重启 relay": "This relay version does not support the conversation action. Restart the relay.",
|
||||
"正在读取会话…": "Loading conversations...",
|
||||
"正在切换会话…": "Switching conversation...",
|
||||
"无法读取会话": "Unable to load conversations",
|
||||
"没有匹配的会话": "No matching conversations",
|
||||
"没有可附加的会话": "No attachable conversations",
|
||||
"没有可控制的会话": "No controllable conversations",
|
||||
"正在切换": "Switching",
|
||||
"未打开": "Not open",
|
||||
"当前": "Current",
|
||||
"可切换": "Available",
|
||||
"会话": "Conversation",
|
||||
"当前角色不能创建会话": "Your current role cannot create conversations",
|
||||
"正在创建新会话": "Creating a new conversation",
|
||||
"无法创建新会话": "Unable to create a new conversation",
|
||||
"当前任务仍在运行或等待授权,暂不能切换": "The current task is running or awaiting approval, so it cannot be switched yet",
|
||||
"会话切换失败": "Conversation switch failed",
|
||||
"正在确认会话": "Confirming conversation",
|
||||
"正在加载会话": "Loading conversation",
|
||||
"会话已切换": "Conversation switched",
|
||||
"正在更新模型设置": "Updating model settings",
|
||||
"模型设置已更新": "Model settings updated",
|
||||
"无法更新模型设置": "Unable to update model settings",
|
||||
"已停止": "Stopped",
|
||||
"成功": "Succeeded",
|
||||
"无输出": "No output",
|
||||
"等待输出…": "Waiting for output...",
|
||||
"执行步骤": "Action",
|
||||
"正在读取文件": "Reading files",
|
||||
"读取完成": "Finished reading",
|
||||
"已读取文件运行了命令": "Read files and ran a command",
|
||||
"已读取文件": "Read files",
|
||||
"编辑了文件": "Edited files",
|
||||
"已完成计划": "Completed plan",
|
||||
"读取文件失败": "Failed to read files",
|
||||
"已停止读取文件": "Stopped reading files",
|
||||
"读取文件": "Read files",
|
||||
"已运行命令": "Ran command",
|
||||
"正在运行命令": "Running command",
|
||||
"正在思考": "Thinking",
|
||||
"正在制定计划": "Creating a plan",
|
||||
"正在编辑文件": "Editing files",
|
||||
"正在处理": "Working",
|
||||
"已完成思考": "Finished thinking",
|
||||
"计划完成": "Plan completed",
|
||||
"文件编辑完成": "Finished editing files",
|
||||
"工作说明": "Progress update",
|
||||
"计划": "Plan",
|
||||
"文件变更": "File changes",
|
||||
"等待授权": "Waiting for approval",
|
||||
"正在生成": "Generating",
|
||||
"已中断": "Interrupted",
|
||||
"失败": "Failed",
|
||||
"已完成": "Completed",
|
||||
"正在工作": "Working",
|
||||
"正在等待你的回答": "Waiting for your answer",
|
||||
"正在搜索网页": "Searching the web",
|
||||
"执行失败": "Action failed",
|
||||
"处理中": "Working",
|
||||
"思考": "Reasoning",
|
||||
"编辑文件": "Edit files",
|
||||
"思考中": "Thinking",
|
||||
"编辑中": "Editing",
|
||||
"进行中": "In progress",
|
||||
"异常": "Error",
|
||||
"未读": "Unread",
|
||||
"本地会话": "Local conversation",
|
||||
"默认拒绝,请明确允许": "Denied by default; allow explicitly",
|
||||
"需要远程确认或输入": "Remote confirmation or input is required",
|
||||
"允许运行命令?": "Allow this command?",
|
||||
"允许修改文件?": "Allow file changes?",
|
||||
"需要扩大权限": "Additional permissions required",
|
||||
"Codex 需要你的回答": "Codex needs your answer",
|
||||
"需要外部服务确认": "External service confirmation required",
|
||||
"Codex 请求确认": "Codex requests confirmation",
|
||||
"高风险": "High risk",
|
||||
"低风险": "Low risk",
|
||||
"需确认": "Confirmation required",
|
||||
"请输入": "Enter a response",
|
||||
"提交回答": "Submit answer",
|
||||
"发送自定义响应": "Send custom response",
|
||||
"自定义响应不是有效 JSON": "The custom response is not valid JSON",
|
||||
"响应不是有效 JSON": "The response is not valid JSON",
|
||||
"远程参与者拒绝": "Denied by remote participant",
|
||||
"状态": "Status",
|
||||
"命令": "Command",
|
||||
"详情": "Details",
|
||||
"复制消息": "Copy message",
|
||||
"复制命令": "Copy command",
|
||||
"复制输出": "Copy output",
|
||||
"未知": "Unknown",
|
||||
"未知错误": "Unknown error",
|
||||
"已附着 VS Code 当前 Codex 会话;输入、输出和授权都回到同一个会话。": "Attached to the current VS Code Codex conversation. Messages, output, and approvals all return to that conversation.",
|
||||
"当前为独立 app-server 模式。": "Currently using standalone app-server mode.",
|
||||
"已附着现有会话": "Attached to existing conversation",
|
||||
"通用 Codex 模型": "General-purpose Codex model",
|
||||
"平衡速度与推理": "Balanced speed and reasoning",
|
||||
"可用模型": "Available model",
|
||||
"极低": "Minimal",
|
||||
"轻度": "Low",
|
||||
"标准": "Medium",
|
||||
"深度": "High",
|
||||
"极高": "Extra high",
|
||||
"最大": "Maximum",
|
||||
"此模型使用默认推理强度": "This model uses its default reasoning effort",
|
||||
"返回简洁模型选择": "Return to simple model selection",
|
||||
"显示高级模型选项": "Show advanced model options",
|
||||
"自定义权限由 config.toml 管理": "Custom permissions are managed by config.toml",
|
||||
"正在等待指示": "Waiting for instructions",
|
||||
"正在工作": "Working",
|
||||
"命令输出": "Command output",
|
||||
"工具输出": "Tool output",
|
||||
"发送 Steer": "Send steer",
|
||||
"会话切换失败,已恢复原会话": "Conversation switch failed; restored the previous conversation",
|
||||
"(空消息)": "(empty message)",
|
||||
"今天": "Today",
|
||||
"昨天": "Yesterday",
|
||||
"未完成": "Not completed",
|
||||
"步骤": "Step",
|
||||
"查看图像": "View image",
|
||||
"等待输入": "Waiting for input",
|
||||
"读取文件运行命令失败": "Failed to read files and run a command",
|
||||
"发送输入": "Send input",
|
||||
"工具": "Tool",
|
||||
"工具失败": "Tool failed",
|
||||
"正在搜索": "Searching",
|
||||
"你停止了工作": "You stopped working",
|
||||
"关闭子代理": "Close subagent",
|
||||
"恢复子代理": "Resume subagent",
|
||||
"启动子代理": "Start subagent",
|
||||
"搜索": "Search",
|
||||
"文件": "File",
|
||||
"新会话已在 VS Code 中打开": "The new conversation opened in VS Code",
|
||||
"事件窗口已过期,请以当前快照为准": "The event window expired; the current snapshot is authoritative",
|
||||
"执行状态未知,请等待主机恢复": "Execution status is unknown; wait for the host to recover",
|
||||
"文件已截断": "File truncated",
|
||||
"已拒绝": "Denied",
|
||||
"已开始工作": "Started working",
|
||||
"已添加工作区上下文": "Added workspace context",
|
||||
"已添加网页搜索": "Added web search",
|
||||
"运行命令": "Run command",
|
||||
"整理上下文": "Compacting context",
|
||||
"正在切换会话": "Switching conversation",
|
||||
"MCP 工具": "MCP tool",
|
||||
" · @ 可标记代理": " · @ to mention agents",
|
||||
});
|
||||
|
||||
const EN_PATTERNS = Object.freeze([
|
||||
[/^用时 1分钟(\d+)秒$/, "Worked for 1m{1}s"],
|
||||
[/^用时 (\d+)分(\d+)秒$/, "Worked for {1}m{2}s"],
|
||||
[/^用时 1分钟$/, "Worked for 1m"],
|
||||
[/^用时 (\d+)分$/, "Worked for {1}m"],
|
||||
[/^用时 (\d+)秒$/, "Worked for {1}s"],
|
||||
[/^用时 (\d+)毫秒$/, "Worked for {1}ms"],
|
||||
[/^用时\s+(.+)$/, "Worked for {1}"],
|
||||
[/^已思考 1分钟(\d+)秒$/, "Thought for 1m{1}s"],
|
||||
[/^已思考 (\d+)分(\d+)秒$/, "Thought for {1}m{2}s"],
|
||||
[/^已思考 (\d+)秒$/, "Thought for {1}s"],
|
||||
[/^已思考\s+(.+)$/, "Thought for {1}"],
|
||||
[/^退出码\s+(.+)$/, "Exit code {1}"],
|
||||
[/^正在读取\s+(.+)$/, "Reading {1}", [1]],
|
||||
[/^已读取\s+(.+)$/, "Read {1}", [1]],
|
||||
[/^读取失败\s*·\s*(.+)$/, "Failed to read {1}", [1]],
|
||||
[/^已停止读取\s+(.+)$/, "Stopped reading {1}", [1]],
|
||||
[/^读取\s+(.+)$/, "Read {1}", [1]],
|
||||
[/^已读取这些内容\s*·\s*(\d+)\s*个文件(.*)$/, "Read these items · {1} files{2}"],
|
||||
[/^已在\s+(.+)\s+内运行\s+(.+)$/, "Ran {2} in {1}", [2]],
|
||||
[/^命令运行失败\s*·\s*(.+?)\s*·\s*((?:\d+毫秒|\d+秒|1分钟(?:\d+秒)?|\d+分(?:\d+秒)?))$/, "Command failed · {1} · {2}", [1]],
|
||||
[/^命令运行失败\s*·\s*(.+)$/, "Command failed · {1}", [1]],
|
||||
// Renderer-owned disclosure labels. Keep the captured command/model text
|
||||
// intact; only the surrounding UI words are localized.
|
||||
[/^命令\s*·\s*(.+)$/, "Command · {1}", [1]],
|
||||
[/^已工具\s*·\s*(.+)$/, "Tool completed · {1}"],
|
||||
[/^当前模型\s+(.+?)\s+(极低|轻度|标准|深度|极高|最大),切换模型$/, "Current model: {1} {2}. Change model", [1]],
|
||||
[/^已停止\s*(.+?)\s*·\s*((?:\d+毫秒|\d+秒|1分钟(?:\d+秒)?|\d+分(?:\d+秒)?))$/, "Stopped {1} · {2}", [1]],
|
||||
[/^已运行\s*(.+)$/, "Ran {1}", [1]],
|
||||
[/^命令运行失败\s*(.*)$/, "Command failed{1}", [1]],
|
||||
[/^命令:\s*(.+?)(执行状态未知,请等待主机恢复)$/, "Command: {1} (execution status unknown; wait for the host to recover)", [1]],
|
||||
[/^命令:\s*(.+)$/, "Command: {1}", [1]],
|
||||
[/^已停止\s*(.+)$/, "Stopped {1}", [1]],
|
||||
[/^正在运行\s+(.+)$/, "Running {1}", [1]],
|
||||
[/^(.+?)\s*·\s*失败$/, "{1} · Failed"],
|
||||
[/^(.+?)\s*·\s*已中断$/, "{1} · Interrupted"],
|
||||
[/^(.+)\s+失败$/, "{1} failed"],
|
||||
[/^编辑了文件\s*·\s*(.+)$/, "Edited files · {1}"],
|
||||
[/^已完成计划\s*·\s*(.+)$/, "Completed plan · {1}"],
|
||||
[/^(\d+)\/(\d+)\s*个会话$/, "{1}/{2} conversations"],
|
||||
[/^(\d+)\s*个会话$/, "{1} conversations"],
|
||||
[/^会话\s+(.+)$/, "Conversation {1}", [1]],
|
||||
[/^工作区\s*·\s*(.+)$/, "Workspace · {1}", [1]],
|
||||
[/^昨天\s+(.+)$/, "Yesterday {1}", [1]],
|
||||
[/^正在切换到「(.+)」…?$/, "Switching to “{1}”...", [1]],
|
||||
[/^你在\s+(.+)\s+后停止了$/, "You stopped after {1}"],
|
||||
[/^执行失败\s*·\s*(.+)$/, "Action failed · {1}"],
|
||||
[/^新会话创建失败:(.+)$/, "Unable to create a new conversation: {1}", [1]],
|
||||
[/^模型设置更新失败:(.+)$/, "Unable to update model settings: {1}", [1]],
|
||||
[/^(.+)(执行状态未知,请等待主机恢复)$/, "{1} (execution status unknown; wait for the host to recover)", [1]],
|
||||
[/^(.+) 完成$/, "{1} completed", [1]],
|
||||
[/^请求 #(.+) 已提交$/, "Request #{1} submitted"],
|
||||
[/^请求 #(.+) 已发送,等待 VS Code 主机确认$/, "Request #{1} sent; waiting for the VS Code host"],
|
||||
[/^无法读取 (.+)$/, "Unable to read {1}", [1]],
|
||||
[/^\[图片附件:(.+)\]$/, "[Image attachment: {1}]", [1]],
|
||||
[/^(.+) 已开始工作$/, "{1} started working", [1]],
|
||||
[/^(.+) 已完成$/, "{1} completed", [1]],
|
||||
[/^(.+) 已中断$/, "{1} interrupted", [1]],
|
||||
[/^…(文件已截断)$/, "... (file truncated)"],
|
||||
[/^当前模型\s+(.+),切换模型$/, "Current model: {1}. Change model", [1]],
|
||||
[/^切换模型(当前\s+(.+?)\s+(极低|轻度|标准|深度|极高|最大))$/, "Change model (current: {1} {2})", [1]],
|
||||
[/^切换模型(当前\s+(.+))$/, "Change model (current: {1})", [1]],
|
||||
[/^修改权限,当前为(.+)$/, "Change permissions. Current: {1}"],
|
||||
[/^修改权限(当前:(.+))$/, "Change permissions (current: {1})"],
|
||||
[/^上下文已使用\s*(\d+)%(剩余\s*(\d+)%)$/, "Context used: {1}% ({2}% remaining)"],
|
||||
[/^(\d+)%\s*已使用$/, "{1}% used"],
|
||||
[/^剩余\s+(.+)\s+tokens$/, "{1} tokens remaining"],
|
||||
[/^当前上下文\s+(.+)\s+tokens$/, "Current context: {1} tokens"],
|
||||
[/^最近请求\s+(.+)\s+tokens$/, "Latest request: {1} tokens"],
|
||||
[/^累计\s+(.+)\s+tokens$/, "Total: {1} tokens"],
|
||||
[/^使用\s+(.+)$/, "Using {1}", [1]],
|
||||
[/^已用时\s+(.+)$/, "Elapsed: {1}"],
|
||||
[/^(\d+)\s*个后台代理(.*)$/, "{1} background agents{2}"],
|
||||
[/^(\d+)毫秒$/, "{1}ms"],
|
||||
[/^(\d+)秒$/, "{1}s"],
|
||||
[/^1分钟(\d+)秒$/, "1m{1}s"],
|
||||
[/^(\d+)分(\d+)秒$/, "{1}m{2}s"],
|
||||
[/^1分钟(\d+秒)?$/, "1m{1}"],
|
||||
[/^(\d+)分(\d+秒)?$/, "{1}m{2}"],
|
||||
]);
|
||||
const ZH = Object.freeze(Object.fromEntries(Object.entries(EN).map(([source, translated]) => [translated, source])));
|
||||
|
||||
let currentLocale = "zh-CN";
|
||||
let observer = null;
|
||||
const textSources = new WeakMap();
|
||||
const textRendered = new WeakMap();
|
||||
const attributeSources = new WeakMap();
|
||||
const attributeRendered = new WeakMap();
|
||||
|
||||
function normalizeLocale(value) {
|
||||
const locale = String(value || "").trim().replace("_", "-").toLowerCase();
|
||||
return locale.startsWith("zh") ? "zh-CN" : "en-US";
|
||||
}
|
||||
|
||||
function embeddedMode() {
|
||||
if (root?.AetherVscodexEmbed?.active) return true;
|
||||
try { return new URLSearchParams(root?.location?.search || "").get("embed") === "aether"; }
|
||||
catch { return false; }
|
||||
}
|
||||
|
||||
function interpolate(template, values) {
|
||||
return String(template).replace(/\{(\d+)\}/g, (_, index) => values[Number(index)] ?? "");
|
||||
}
|
||||
|
||||
function translate(value, locale = currentLocale, depth = 0) {
|
||||
const source = String(value ?? "");
|
||||
if (!source) return source;
|
||||
if (normalizeLocale(locale) === "zh-CN") return ZH[source] || source;
|
||||
if (Object.prototype.hasOwnProperty.call(EN, source)) return EN[source];
|
||||
for (const [pattern, template, rawIndexes] of EN_PATTERNS) {
|
||||
const match = source.match(pattern);
|
||||
if (match) {
|
||||
const translatedMatch = match.map((part, index) => index === 0
|
||||
? part
|
||||
: rawIndexes?.includes(index) ? part
|
||||
: depth < 6 ? translate(part, locale, depth + 1) : (EN[part] || part));
|
||||
return interpolate(template, translatedMatch);
|
||||
}
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
function shouldSkipTextNode(node) {
|
||||
const parent = node?.parentElement;
|
||||
return Boolean(parent?.closest?.("code, pre, .message-body, .request-summary, .request-questions, .request-json, .request-command, .diff-output, .terminal-output, .session-option-title, .subagent-name, .subagent-summary-label"));
|
||||
}
|
||||
|
||||
function translateTextNode(node) {
|
||||
if (!node || shouldSkipTextNode(node)) return;
|
||||
const current = node.nodeValue;
|
||||
const previousRendered = textRendered.get(node);
|
||||
if (!textSources.has(node) || current !== previousRendered) textSources.set(node, current);
|
||||
const source = textSources.get(node);
|
||||
const leading = source.match(/^\s*/)?.[0] || "";
|
||||
const trailing = source.match(/\s*$/)?.[0] || "";
|
||||
const core = source.slice(leading.length, source.length - trailing.length);
|
||||
if (!core) return;
|
||||
const translated = translate(core);
|
||||
const rendered = `${leading}${translated}${trailing}`;
|
||||
textRendered.set(node, rendered);
|
||||
if (rendered !== current) node.nodeValue = rendered;
|
||||
}
|
||||
|
||||
function translateAttributes(element) {
|
||||
if (!element?.getAttribute || element.closest?.(".message-body, pre, code")) return;
|
||||
let sources = attributeSources.get(element);
|
||||
let renderedValues = attributeRendered.get(element);
|
||||
if (!sources) { sources = new Map(); attributeSources.set(element, sources); }
|
||||
if (!renderedValues) { renderedValues = new Map(); attributeRendered.set(element, renderedValues); }
|
||||
for (const attribute of ["title", "aria-label", "placeholder", "data-placeholder"]) {
|
||||
if (!element.hasAttribute(attribute)) continue;
|
||||
const current = element.getAttribute(attribute);
|
||||
if (!sources.has(attribute) || current !== renderedValues.get(attribute)) sources.set(attribute, current);
|
||||
const source = sources.get(attribute);
|
||||
const translated = translate(source);
|
||||
renderedValues.set(attribute, translated);
|
||||
if (translated !== current) element.setAttribute(attribute, translated);
|
||||
}
|
||||
}
|
||||
|
||||
function translateTree(node) {
|
||||
if (!root?.document || !node) return;
|
||||
if (node.nodeType === 3) {
|
||||
translateTextNode(node);
|
||||
return;
|
||||
}
|
||||
if (node.nodeType !== 1 && node.nodeType !== 9 && node.nodeType !== 11) return;
|
||||
if (node.nodeType === 1) translateAttributes(node);
|
||||
const walker = root.document.createTreeWalker(node, root.NodeFilter.SHOW_ELEMENT | root.NodeFilter.SHOW_TEXT);
|
||||
for (let current = walker.nextNode(); current; current = walker.nextNode()) {
|
||||
if (current.nodeType === 3) translateTextNode(current);
|
||||
else translateAttributes(current);
|
||||
}
|
||||
}
|
||||
|
||||
function applyDocument() {
|
||||
if (!root?.document) return;
|
||||
root.document.documentElement.lang = currentLocale;
|
||||
translateTree(root.document.body);
|
||||
const selector = root.document.getElementById("localeSelect");
|
||||
if (selector && selector.value !== currentLocale) selector.value = currentLocale;
|
||||
}
|
||||
|
||||
function setLocale(value, options = {}) {
|
||||
currentLocale = SUPPORTED.has(value) ? value : normalizeLocale(value);
|
||||
if (options.persist !== false && root?.localStorage && !embeddedMode()) {
|
||||
try { root.localStorage.setItem(STORAGE_KEY, currentLocale); } catch { /* storage may be disabled */ }
|
||||
}
|
||||
applyDocument();
|
||||
if (root?.CustomEvent) root.dispatchEvent?.(new root.CustomEvent("aether-vscodex:locale", { detail: { locale: currentLocale } }));
|
||||
return currentLocale;
|
||||
}
|
||||
|
||||
function initialLocale() {
|
||||
if (embeddedMode()) return normalizeLocale(root?.navigator?.language);
|
||||
try {
|
||||
const saved = root?.localStorage?.getItem(STORAGE_KEY);
|
||||
if (SUPPORTED.has(saved)) return saved;
|
||||
} catch { /* storage may be disabled */ }
|
||||
return normalizeLocale(root?.navigator?.language);
|
||||
}
|
||||
|
||||
function start() {
|
||||
if (!root?.document) return;
|
||||
currentLocale = initialLocale();
|
||||
applyDocument();
|
||||
if (typeof root.MutationObserver === "function" && !observer) {
|
||||
observer = new root.MutationObserver((records) => {
|
||||
if (currentLocale === "zh-CN") return;
|
||||
for (const record of records) {
|
||||
if (record.type === "characterData") translateTextNode(record.target);
|
||||
else if (record.type === "attributes") translateAttributes(record.target);
|
||||
else for (const node of record.addedNodes) translateTree(node);
|
||||
}
|
||||
});
|
||||
observer.observe(root.document.documentElement, {
|
||||
subtree: true,
|
||||
childList: true,
|
||||
characterData: true,
|
||||
attributes: true,
|
||||
attributeFilter: ["title", "aria-label", "placeholder", "data-placeholder"],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const api = {
|
||||
locale: () => currentLocale,
|
||||
normalizeLocale,
|
||||
setLocale,
|
||||
start,
|
||||
t: (value) => translate(value),
|
||||
translate,
|
||||
translateTree,
|
||||
messages: { "zh-CN": Object.freeze({}), "en-US": EN },
|
||||
};
|
||||
|
||||
if (root?.document) {
|
||||
if (root.document.readyState === "loading") root.document.addEventListener("DOMContentLoaded", start, { once: true });
|
||||
else start();
|
||||
}
|
||||
return api;
|
||||
});
|
||||
@@ -0,0 +1,303 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="color-scheme" content="dark light" />
|
||||
<title>Codex</title>
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
</head>
|
||||
<body class="codex-app local-no-auth">
|
||||
<div class="codex-panel">
|
||||
<div class="connection" aria-live="polite" hidden>
|
||||
<span id="connectionDot" class="dot offline"></span>
|
||||
<span id="connectionText">正在连接</span>
|
||||
<span id="roleBadge" class="badge">未认证</span>
|
||||
</div>
|
||||
<main class="chat-shell">
|
||||
<section class="chat-header" aria-label="当前会话">
|
||||
<div class="thread-heading">
|
||||
<button id="backButton" class="icon-button header-back-button" type="button" data-panel-action="back" title="返回会话列表" aria-label="返回会话列表" hidden>
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M9.75 3.5 5.25 8l4.5 4.5M5.5 8h6.25" /></svg>
|
||||
</button>
|
||||
<button id="sessionPickerButton" class="thread-picker-button" type="button" aria-haspopup="dialog" aria-expanded="false" title="打开会话历史" aria-label="打开会话历史" disabled>
|
||||
<h2 id="threadTitle">Codex</h2>
|
||||
</button>
|
||||
<span id="appState" class="status-text" aria-live="polite">等待 VS Code 主机</span>
|
||||
</div>
|
||||
<div class="thread-actions">
|
||||
<button class="icon-button" type="button" data-panel-action="menu" title="更多操作" aria-label="更多操作">
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="3" cy="8" r="1" /><circle cx="8" cy="8" r="1" /><circle cx="13" cy="8" r="1" /></svg>
|
||||
</button>
|
||||
<button id="historyButton" class="icon-button header-history-button" type="button" data-panel-action="history" title="会话历史" aria-label="会话历史" hidden>
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" /><path d="M3 3v5h5" /><path d="M12 7v5l4 2" /></svg>
|
||||
</button>
|
||||
<button class="icon-button" type="button" data-panel-action="settings" title="设置" aria-label="设置">
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M6.7 2h2.6l.4 1.6c.4.2.8.4 1.2.7l1.6-.6 1.3 2.2-1.2 1.1a5 5 0 0 1 0 1.4l1.2 1.1-1.3 2.2-1.6-.6c-.4.3-.8.5-1.2.7L9.3 14H6.7l-.4-1.6a5 5 0 0 1-1.2-.7l-1.6.6-1.3-2.2 1.2-1.1a5 5 0 0 1 0-1.4L2.2 6l1.3-2.2 1.6.6c.4-.3.8-.5 1.2-.7L6.7 2Z" /><circle cx="8" cy="8" r="1.7" /></svg>
|
||||
</button>
|
||||
<button id="newSessionButton" class="icon-button new-session-button" type="button" data-panel-action="new-session" title="创建新会话" aria-label="创建新会话" hidden>
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M3.25 3.25h5.5a1.5 1.5 0 0 1 1.5 1.5v2.5" /><path d="M3.25 3.25v9.5h6" /><path d="m8.2 11.35 4.55-4.55 1.25 1.25-4.55 4.55-2 .5Z" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
<div id="panelMenu" class="panel-popover panel-menu" hidden>
|
||||
<button type="button" data-menu-action="sessions" hidden>最近会话</button>
|
||||
<button type="button" data-menu-action="clear">清空当前输出</button>
|
||||
<button type="button" data-menu-action="refresh">重新同步</button>
|
||||
<button type="button" data-menu-action="expand">展开面板</button>
|
||||
<button type="button" data-menu-action="close">隐藏面板</button>
|
||||
</div>
|
||||
<div id="detailsPopover" class="panel-popover details-popover settings-popover" hidden role="dialog" aria-label="设置">
|
||||
<div class="popover-title">设置</div>
|
||||
<div class="settings-shortcuts">
|
||||
<button type="button" data-settings-action="model"><span>模型与推理强度</span><span id="settingsModelValue">默认</span></button>
|
||||
<button type="button" data-settings-action="permission"><span>修改权限</span><span id="settingsPermissionValue">工作区写入</span></button>
|
||||
<label id="localeSetting" class="settings-locale">
|
||||
<span>语言</span>
|
||||
<select id="localeSelect" aria-label="语言">
|
||||
<option value="zh-CN">中文</option>
|
||||
<option value="en-US">English</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="settings-divider"></div>
|
||||
<div class="popover-subtitle">当前会话</div>
|
||||
<dl>
|
||||
<dt>工作区</dt><dd id="popoverCwd">-</dd>
|
||||
<dt>模式</dt><dd id="popoverMode">本地模式</dd>
|
||||
<dt>thread</dt><dd id="popoverThread">-</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div id="sessionPicker" class="panel-popover session-picker" hidden role="dialog" aria-label="最近会话">
|
||||
<div class="session-picker-header">
|
||||
<span class="popover-title">最近会话</span>
|
||||
<button id="sessionPickerRefresh" class="session-picker-refresh" type="button" title="刷新会话列表" aria-label="刷新会话列表">
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M13 5V2m0 0h-3m3 0-2.1 2.1A5 5 0 1 0 13 9" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="session-search">
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="6.8" cy="6.8" r="3.8" /><path d="m9.7 9.7 3.2 3.2" /></svg>
|
||||
<label class="sr-only" for="sessionSearchInput">搜索最近会话</label>
|
||||
<input id="sessionSearchInput" type="search" autocomplete="off" spellcheck="false" placeholder="搜索最近会话" aria-label="搜索最近会话" aria-controls="sessionList" aria-expanded="false" />
|
||||
<button id="sessionSearchClear" class="session-search-clear" type="button" title="清除搜索" aria-label="清除搜索" hidden>
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="m4.5 4.5 7 7m0-7-7 7" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div id="sessionPickerStatus" class="session-picker-status" role="status" aria-live="polite"></div>
|
||||
<div id="sessionList" class="session-list" role="listbox" aria-label="可用会话" tabindex="0"></div>
|
||||
</div>
|
||||
|
||||
<section class="chat-panel" aria-label="对话内容">
|
||||
<div id="output" class="output chat-scroll" tabindex="0" aria-live="polite" aria-label="Codex 消息"></div>
|
||||
<button id="scrollToBottom" class="scroll-to-bottom" type="button" aria-label="回到最新消息" aria-hidden="true" tabindex="-1">
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8 3v9M4.5 8.5 8 12l3.5-3.5" /></svg>
|
||||
<span class="scroll-working-dots" aria-hidden="true"><i></i><i></i><i></i></span>
|
||||
</button>
|
||||
<div id="inlineRequests" class="inline-requests" aria-live="polite" aria-label="待处理的 Codex 请求"></div>
|
||||
</section>
|
||||
|
||||
<section id="messageForm" class="composer" aria-label="发送消息">
|
||||
<section id="subagentsPanel" class="subagents-panel" aria-label="子代理" hidden>
|
||||
<button id="subagentsToggle" class="subagents-toggle" type="button" aria-expanded="false">
|
||||
<span class="subagents-title">子代理</span>
|
||||
<span id="subagentsCount" class="subagents-count"></span>
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="m6 3 5 5-5 5" /></svg>
|
||||
</button>
|
||||
<div id="subagentsList" class="subagents-list"></div>
|
||||
</section>
|
||||
<div id="liveActivity" class="live-activity" role="status" aria-live="polite" hidden>
|
||||
<span class="activity-spinner" aria-hidden="true"></span>
|
||||
<span class="activity-label"></span>
|
||||
<span class="activity-dots" aria-hidden="true"><i></i><i></i><i></i></span>
|
||||
<span class="activity-elapsed"></span>
|
||||
</div>
|
||||
<div class="composer-surface">
|
||||
<div
|
||||
id="messageInput"
|
||||
class="composer-editor"
|
||||
contenteditable="true"
|
||||
role="textbox"
|
||||
aria-multiline="true"
|
||||
data-placeholder="提交后续变更要求"
|
||||
spellcheck="true"
|
||||
></div>
|
||||
<div class="composer-footer">
|
||||
<div class="composer-hint">
|
||||
<button id="composerPlusButton" class="composer-icon-button" type="button" aria-haspopup="menu" aria-expanded="false" title="添加文件及更多内容" aria-label="添加文件及更多内容">
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8 3v10M3 8h10" /></svg>
|
||||
</button>
|
||||
<div id="composerPlusMenu" class="composer-popover composer-plus-menu" role="menu" hidden>
|
||||
<div class="composer-popover-heading">添加文件及更多内容</div>
|
||||
<button type="button" role="menuitem" data-composer-action="attach">添加文件</button>
|
||||
<button type="button" role="menuitem" data-composer-action="photo">添加照片</button>
|
||||
<button type="button" role="menuitem" data-composer-action="workspace">添加工作区上下文</button>
|
||||
<button type="button" role="menuitem" data-composer-action="web-search">网页搜索</button>
|
||||
</div>
|
||||
<input id="attachmentInput" type="file" accept=".txt,.md,.json,.js,.ts,.tsx,.jsx,.css,.html,.yml,.yaml,.xml,.py,.go,.rs,.java,.c,.cpp,.h,image/*" multiple hidden />
|
||||
<button id="permissionChip" class="permission-chip" type="button" aria-haspopup="menu" aria-expanded="false" title="修改权限" aria-label="修改权限">
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8 1.8 13 4v3.6c0 3-2 5.6-5 6.6-3-1-5-3.6-5-6.6V4l5-2.2Z" /><path d="m5.5 8 1.6 1.6L10.8 6" /></svg>
|
||||
<span id="permissionLabel">工作区写入</span>
|
||||
<svg class="permission-chevron" viewBox="0 0 16 16" aria-hidden="true"><path d="m4.5 6 3.5 3.5L11.5 6" /></svg>
|
||||
</button>
|
||||
<div id="permissionMenu" class="composer-popover permission-menu" role="menu" aria-label="权限设置" hidden>
|
||||
<div class="composer-popover-heading">修改权限</div>
|
||||
<button type="button" role="menuitemradio" data-permission-mode="ask" aria-checked="false"><span>需要时询问</span><small>编辑外部文件和联网时始终询问</small></button>
|
||||
<button type="button" role="menuitemradio" data-permission-mode="auto" aria-checked="false"><span>由 Codex 审批</span><small>仅对可能不安全的操作询问</small></button>
|
||||
<button type="button" role="menuitemradio" data-permission-mode="full" aria-checked="false"><span>完全访问</span><small>不限制联网或文件访问</small></button>
|
||||
<button type="button" role="menuitemradio" data-permission-mode="custom" aria-checked="false"><span>自定义</span><small>使用 config.toml 中的权限</small></button>
|
||||
<button type="button" role="menuitemradio" data-permission-mode="readonly" aria-checked="false"><span>只读</span><small>仅查看文件,不修改工作区</small></button>
|
||||
</div>
|
||||
<div id="permissionConfirm" class="permission-confirm" role="dialog" aria-modal="true" aria-labelledby="permissionConfirmTitle" hidden>
|
||||
<div id="permissionConfirmTitle" class="permission-confirm-title">确认完全访问</div>
|
||||
<p>完全访问允许 Codex 执行命令、访问互联网并编辑工作区之外的文件。</p>
|
||||
<div class="permission-confirm-actions">
|
||||
<button id="permissionConfirmCancel" type="button">取消</button>
|
||||
<button id="permissionConfirmAccept" class="primary" type="button">确认</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="usagePicker" class="usage-picker" hidden>
|
||||
<button id="usageButton" class="usage-button" type="button" aria-haspopup="dialog" aria-expanded="false" title="查看上下文用量" aria-label="查看上下文用量"><span id="usageRing" class="usage-ring" aria-hidden="true"><span id="usageLabel">0%</span></span></button>
|
||||
<div id="usageMenu" class="composer-popover usage-menu" role="dialog" aria-label="上下文用量" hidden>
|
||||
<div class="composer-popover-heading">上下文用量</div>
|
||||
<div id="usageSummary" class="usage-summary">暂无用量数据</div>
|
||||
<div class="usage-meter"><span id="usageMeterBar"></span></div>
|
||||
<div id="usageDetails" class="usage-details"></div>
|
||||
</div>
|
||||
</div>
|
||||
<span id="factApp" class="sr-only">-</span>
|
||||
<span id="factClients" class="sr-only">-</span>
|
||||
<span id="factRequests" class="sr-only">0</span>
|
||||
</div>
|
||||
<div class="composer-actions">
|
||||
<div id="modelPicker" class="model-picker">
|
||||
<button id="modelPickerButton" class="model-picker-button" type="button" aria-haspopup="menu" aria-expanded="false" title="切换模型与推理强度" hidden>
|
||||
<span id="modelLabel" class="model-label"></span>
|
||||
<span id="modelEffortLabel" class="model-effort-label"></span>
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="m4.5 6 3.5 3.5L11.5 6" /></svg>
|
||||
</button>
|
||||
<div id="modelMenu" class="model-menu" role="menu" aria-label="模型与推理强度" hidden>
|
||||
<div id="modelPowerView" class="model-power-view">
|
||||
<div class="model-power-heading">
|
||||
<span>推理强度</span>
|
||||
<button id="modelAdvancedToggle" class="model-advanced-toggle" type="button">高级</button>
|
||||
</div>
|
||||
<div class="model-power-control">
|
||||
<span class="model-power-label">更高效</span>
|
||||
<input id="modelPowerSlider" class="model-power-slider" type="range" min="0" max="3" step="1" value="1" aria-label="强度" aria-describedby="modelPowerInstructions" />
|
||||
<span class="model-power-label">更智能</span>
|
||||
</div>
|
||||
<div id="modelPowerValue" class="model-power-value"></div>
|
||||
<span id="modelPowerInstructions" class="sr-only">使用左右方向键调整强度</span>
|
||||
</div>
|
||||
<div id="modelAdvancedView" class="model-advanced-view" hidden>
|
||||
<div class="model-advanced-toolbar">
|
||||
<button id="modelAdvancedBack" class="model-advanced-back" type="button" aria-label="返回模型强度">‹</button>
|
||||
<span>模型与推理强度</span>
|
||||
</div>
|
||||
<div class="model-menu-heading">模型</div>
|
||||
<div id="modelOptions" class="model-options" role="listbox" aria-label="模型"></div>
|
||||
<div class="model-menu-heading effort-heading">推理强度</div>
|
||||
<div id="effortOptions" class="effort-options" role="listbox" aria-label="推理强度"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button id="interruptButton" class="compact-action interrupt-action" type="button" disabled title="中断当前 turn" aria-label="中断当前 turn">
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true"><rect x="4.5" y="4.5" width="7" height="7" rx="1" /></svg>
|
||||
</button>
|
||||
<button id="steerButton" class="primary compact-action steer-action" type="button" disabled title="发送后续指令" aria-label="发送后续指令">
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8 12V4M4.5 7.5 8 4l3.5 3.5" /></svg>
|
||||
</button>
|
||||
<button id="startTurnButton" class="primary send-button" type="button" disabled title="发送消息" aria-label="发送消息">
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8 12V4M4.5 7.5 8 4l3.5 3.5" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mode-row">
|
||||
<span class="connection-mode-label">
|
||||
<svg class="mode-icon" viewBox="0 0 16 16" aria-hidden="true"><rect x="2" y="3" width="12" height="8" rx="1" /><path d="M5 13h6M8 11v2" /></svg>
|
||||
<span id="modeLabel">本地模式</span>
|
||||
</span>
|
||||
<div id="controlModeSwitch" class="control-mode-switch" role="group" aria-label="控制模式" aria-busy="false" data-mode="sync" data-switching="false">
|
||||
<button type="button" data-control-mode="sync" aria-pressed="true" title="同步模式跟随 VS Code 当前会话" disabled>同步</button>
|
||||
<button type="button" data-control-mode="async" aria-pressed="false" title="异步模式可独立管理会话" disabled>异步</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
<button id="restorePanel" class="restore-panel" type="button" hidden>显示 Codex</button>
|
||||
|
||||
<!-- Protocol compatibility state stays out of the visual shell. -->
|
||||
<section class="compatibility-state" aria-hidden="true" hidden inert>
|
||||
<details id="sessionSettings">
|
||||
<summary>会话设置</summary>
|
||||
<div class="settings-grid">
|
||||
<label>工作目录<input id="cwdInput" type="text" /></label>
|
||||
<label>模型<input id="modelInput" type="text" placeholder="留空使用默认模型" /></label>
|
||||
<label>沙箱
|
||||
<select id="sandboxInput">
|
||||
<option value="workspace-write">workspace-write</option>
|
||||
<option value="read-only">read-only</option>
|
||||
<option value="danger-full-access">danger-full-access</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>审批策略
|
||||
<select id="approvalInput">
|
||||
<option value="on-request">on-request</option>
|
||||
<option value="untrusted">untrusted</option>
|
||||
<option value="never">never</option>
|
||||
</select>
|
||||
</label>
|
||||
<button id="startThreadButton" class="secondary" type="button">启动新 thread</button>
|
||||
<div class="ids">
|
||||
<span>thread</span><code id="threadId">-</code>
|
||||
<span>turn</span><code id="turnId">-</code>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<details id="connectionSettings">
|
||||
<summary>连接设置</summary>
|
||||
<label class="token-field">
|
||||
<span id="tokenLabel">本机连接(无需 token)</span>
|
||||
<input id="tokenInput" type="password" autocomplete="off" placeholder="本机模式无需填写;认证模式再填写" />
|
||||
</label>
|
||||
</details>
|
||||
<span id="sessionMode">已附着当前会话</span>
|
||||
<span id="latestSeq">seq -</span>
|
||||
<span id="outputHint">等待连接</span>
|
||||
<button id="clearOutputButton" type="button">清空对话</button>
|
||||
<span id="lastEvent">-</span>
|
||||
<details id="requestsPanel"><summary><span>授权与输入</span><span id="requestCount" class="badge warning">0</span></summary><div id="requests" class="requests empty">暂无待处理请求</div></details>
|
||||
</section>
|
||||
|
||||
<template id="requestTemplate">
|
||||
<article class="request">
|
||||
<div class="request-title"><span class="request-icon" aria-hidden="true">!</span><strong class="request-method"></strong><span class="request-risk"></span><span class="request-id"></span></div>
|
||||
<p class="request-summary"></p>
|
||||
<pre class="request-command"></pre>
|
||||
<div class="request-questions"></div>
|
||||
<label class="request-scope-wrap" hidden>
|
||||
<span>授权范围</span>
|
||||
<select class="request-scope">
|
||||
<option value="turn">仅本次 turn</option>
|
||||
<option value="session">当前会话</option>
|
||||
</select>
|
||||
</label>
|
||||
<details class="request-details">
|
||||
<summary>查看请求数据</summary>
|
||||
<pre class="request-json"></pre>
|
||||
</details>
|
||||
<textarea class="request-response" rows="4" aria-label="JSON 响应"></textarea>
|
||||
<div class="button-row request-actions">
|
||||
<button class="primary request-allow">允许</button>
|
||||
<button class="secondary request-deny">拒绝</button>
|
||||
<button class="secondary request-send">发送 JSON</button>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
<script src="./embed-bridge.js" defer></script>
|
||||
<script src="./i18n.js" defer></script>
|
||||
<script src="./app.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user