Files
penguin-harness/packages/web/src/lib/strings.ts
T

952 lines
47 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* UI copy (bilingual): this file holds the Chinese dictionary `zh` and the runtime
* active dictionary `S`; the English dictionary lives in strings-en.ts (constrained
* to the same shape by the `Strings` type). Locale preference is resolved by
* state/locale.tsx, which calls `setActiveStrings` to switch and remounts the whole
* tree keyed by locale, so `S.x` reads in components always reflect the current
* language (module-level constants do not update on switch — keep reads inside components).
* Keep domain terms capitalized in English — Workspace, Token, Task, Session, Project, Trace.
* "agent" is a common noun: lowercase mid-sentence, capitalized only at the start of a
* label/sentence or in a proper name (Agent State, AgentHub). zh keeps "Agent" as-is.
*/
export const zh = {
appName: "PenguinHarness",
nav: {
chat: "对话",
newChat: "新对话",
agents: "智能体仓库",
skills: "技能库",
models: "模型仓库",
usage: "成本中心",
traces: "轨迹观测",
benchmark: "评估中心",
// Collapsed-rail tooltips (product-specified wording; new chat reuses chat.newSessionMenu, the other pages reuse the page names above).
lastConversation: "最近一次对话",
railAgents: "智能体",
collapseSidebar: "收起侧栏",
expandSidebar: "展开侧栏",
collapseGroup: "折叠",
expandGroup: "展开",
pinGroup: "置顶分组",
unpinGroup: "取消置顶",
},
settings: {
language: "语言",
theme: "主题",
themeLight: "浅色",
themeDark: "深色",
followSystem: "跟随系统",
langZh: "中文",
langEn: "English",
fontSize: "字号",
fontSmall: "小",
fontMedium: "中",
fontLarge: "大",
accent: "主题色",
accentNames: {
neutral: "灰白",
blue: "蓝",
green: "绿",
violet: "紫",
rose: "红",
amber: "橙",
} as Record<string, string>,
},
/** Version footer, update reminder, and admin self-update in the sidebar user menu. */
update: {
/** Version-line date label (owner-specified wording); `date` is formatMonthDay output, e.g. 「最近更新日期 7 月 26 日」. */
lastUpdated: (date: string) => `最近更新日期 ${date}`,
/** Superscript badge on the version lines when the update check found a newer release (owner-specified wording). */
newVersionBadge: "有新版本可用",
newVersion: (v: string) => `新版本 v${v} 可用`,
/** Manual check action in the sidebar user menu (owner request), with its busy label and toast outcomes. */
checkNow: "检查更新",
checking: "检查中…",
upToDate: "已是最新版本",
checkFailed: "检查更新失败,请稍后重试",
checkDisabled: "更新检查已关闭(PENGUIN_UPDATE_CHECK=off)",
releaseNotes: "更新说明",
updateNow: "立即更新",
updating: "更新中…",
updated: "更新完成,重启服务后生效",
restartHint: "在终端重新运行 penguin web(或 penguin server)即可完成重启",
failed: "更新失败",
unsupported: "当前安装方式不支持在线更新",
confirmBody:
"将下载最新版本并安装到服务器上的安装目录(数据目录不受影响)。安装完成后需要重启服务才会生效。",
},
common: {
save: "保存",
cancel: "取消",
create: "创建",
delete: "删除",
edit: "编辑",
settings: "设置",
confirm: "确认",
close: "关闭",
loading: "加载中…",
saved: "已保存",
saving: "保存中…",
/** Clicking save with nothing changed: an info toast instead of a silent no-op. */
noChangesToSave: "当前没有需要保存的修改",
/** Confirm-before-save dialog shared by the settings forms (writes go to server-side config files). */
confirmSaveTitle: "保存修改",
confirmSaveBody: "确定保存这些修改吗?修改将写入服务器上的配置文件。",
none: "(无)",
retry: "重试",
unknownError: "请求失败,请稍后重试",
requiredField: "此项必填",
copied: "已复制",
name: "名称",
username: "用户名",
role: "角色",
actions: "操作",
created: "创建时间",
cost: "成本",
time: "时间",
},
auth: {
usernameHint: "2~32 位:小写字母开头,仅小写字母、数字与下划线",
password: "密码",
passwordHint: "至少 8 个字符",
showPassword: "显示密码",
hidePassword: "隐藏密码",
login: "登录",
logout: "登出",
admin: "管理员",
defaultAdminNote: "首次使用请以内置管理员登录:admin / penguin-2026,登录后请尽快修改密码",
},
account: {
changePassword: "修改密码",
oldPassword: "当前密码",
oldPasswordHint: "内置管理员的默认初始密码为 penguin-2026",
newPassword: "新密码",
confirmPassword: "确认新密码",
passwordMismatch: "两次输入的新密码不一致",
initialPasswordBanner: "当前账号正在使用初始密码,建议尽快修改",
changeNow: "去修改",
},
admin: {
users: "用户管理",
roleAdmin: "管理员",
roleUser: "用户",
createUser: "新增用户",
initialPassword: "初始密码",
initialPasswordFlag: "初始密码",
defaultProjectNote: (id: string): string => `将自动创建默认 Project:${id}`,
resetPassword: "重置密码",
resetPasswordTitle: (u: string): string => `重置 ${u} 的密码`,
resetPasswordNote: "重置后该用户的登录会话全部失效,需用新密码重新登录",
deleteUserTitle: (u: string): string => `删除用户 ${u}`,
deleteUserConfirm: (u: string): string =>
`将删除用户 ${u} 及其名下全部 Project(含数据目录),不可恢复。`,
},
project: {
switcher: "Project",
create: "新建 Project",
createTitle: "新建 Project",
id: "Project id",
idHint: "2~64 位:小写字母开头,仅小写字母、数字与下划线;创建后不可修改",
idPrefixHint: "id 固定以「用户名-」为前缀,后接小写字母、数字或下划线;创建后不可修改",
name: "显示名(可选,缺省为 Project id)",
settings: "Project 设置",
settingsTitle: "Project 设置",
members: "成员",
addMember: "添加成员",
removeMember: "移除",
deleteProject: "删除 Project",
deleteConfirm: "确认删除该 Project?项目目录将被递归删除,不可恢复。",
deleteDefaultForbidden: "default_project 与 CLI 共用,不允许在 Web 端删除",
deleteLastForbidden:
"这是当前账号最后一个 Project,删除后将无 Project 可用;请先创建新的 Project",
noCredentialTitle: "尚未配置模型 credential",
noCredentialBody: "当前 Project 的默认模型尚未配置 API key,发起对话前请先前往模型页配置。",
goToModels: "前往模型页",
later: "稍后再说",
},
agent: {
listTitle: "Agents",
create: "创建 Agent",
createTitle: "创建 Agent",
id: "Agent id",
idHint: "2~64 位:小写字母开头,仅小写字母、数字与下划线;创建后不可修改",
nameHint: "留空则使用 Agent id 作为名称",
description: "描述",
activeSessions: "活跃 Session",
sessionCount: (n: number): string => `${n} 个 Session`,
toolCount: (n: number): string => `${n} 个工具`,
vaultKeyCount: (n: number): string => `${n} 个密钥`,
scheduleCount: (n: number): string => `${n} 个定时任务`,
updatedAt: "最后修改",
activity: (days: number): string => `近 ${days} 天 Session 活跃度`,
settings: "Agent 设置",
backToList: "返回 Agents",
tabOverview: "概览",
tabPrompt: "Prompt",
tabRuntime: "运行参数",
tabTools: "工具",
tabVault: "密钥保险柜",
tabSchedules: "定时任务",
stateDir: "State 路径",
agentsMd: "AGENTS.md",
systemPrompt: "system_prompt 模板",
placeholdersTitle: "可用占位符(点击插入)",
insertPlaceholder: "插入到 system_prompt 光标处",
/** Order must match the default system prompt (core default-config.ts DEFAULT_SYSTEM_PROMPT). */
placeholders: [
["{{AGENTS_MD}}", "注入 AGENTS.md 内容"],
["{{VAULT_KEYS}}", "注入密钥保险柜的键名小节(无键时为空)"],
["{{SKILL_METADATA}}", "注入已安装 Skill 的元数据行(无 Skill 时为空)"],
["{{PLATFORM}}", "运行平台"],
["{{OS_VERSION}}", "操作系统版本"],
["{{DATE}}", "当前日期"],
[
"{{PROJECT_DIR}}",
"PenguinHarness 应用数据根目录(存放全部 Agent 数据与 Project 级数据;不是本次任务的工作目录)",
],
["{{AGENT_ID}}", "当前 Agent id"],
["{{CWD}}", "Workspace 绝对路径"],
["{{PROVIDER}}", "模型 provider 分组"],
["{{MODEL_ID}}", "上游模型 id"],
["{{SESSION_ID}}", "当前 Session id"],
] as ReadonlyArray<readonly [string, string]>,
maxTurns: "max_turns(单 Task 最大轮次,-1 不限制)",
maxTokens: "model.max_tokens",
thinkingLevel: "model.thinking_level",
/** Selectable tiers exclude `none` (many models cannot disable thinking); a stored `none` still displays — see `thinkingLevelNoneKept`. */
thinkingLevelOptions: [
["", "不提交覆盖值,沿用当前生效的配置。"],
["low", "开启较低强度的扩展推理。"],
["medium", "开启中等强度的扩展推理(新建 Agent 的缺省档位)。"],
["high", "开启较高强度的扩展推理,响应更慢。"],
["xhigh", "开启最高强度的扩展推理,部分模型上效果与 high 相同。"],
] as ReadonlyArray<readonly [string, string]>,
/** Row description shown only while the stored config is `none`: displayed as-is, never rewritten, and no longer offered as a choice. */
thinkingLevelNoneKept: "已存的历史档位:新选择不再提供关闭档(多数模型不支持关闭思考)。",
timeoutMs: "model.timeoutMs",
timeoutMsHint: "单次 Request 超时,毫秒",
compaction: "上下文压缩(compaction)",
maxContextLength: "max_context_length",
maxContextLengthHint: "触发压缩的上下文阈值",
maxSessionTurns: "max_session_turns",
maxSessionTurnsHint: "触发压缩的轮数阈值",
compactionMode: "mode(压缩方式)",
compactionModeOptions: [
["", "不提交覆盖值,沿用当前生效的配置。"],
["summarize", "先让模型为旧上下文生成摘要,再从摘要续接新的上下文窗口(缺省)。"],
["discard", "不生成摘要,直接丢弃旧上下文,下一轮从新窗口重新开始。"],
] as ReadonlyArray<readonly [string, string]>,
compactionPrompt: "prompt(摘要提示词)",
maxTurnsInvalid: "max_turns 必须 > 0 或为 -1",
timeoutInvalid: "timeoutMs 必须 > 0 或为 -1",
toolFieldInvalid: (name: string, field: string) => `${name}: ${field} 必须是 > 0 的整数或 -1`,
toolPermission: "permission",
permissionReadLabel: "Read-only",
permissionReadDescription: "仅读取。审批模式为 read-only 时自动放行,无需确认。",
permissionReadWriteLabel: "Read & write",
permissionReadWriteDescription: "可修改。审批模式为 read-only 时需人工确认。",
toolTimeout: "timeoutMs",
toolMaxOutput: "maxOutputLength",
toolCallDescription: "call_description",
callDescriptionHint:
"call_description:开启(缺省)时该工具的 schema 保留可选的 description 参数——模型为每次调用写一句说明,运行期间展示给用户;关闭则装配时从 schema 滤除该参数。仅参数中定义了 description 属性的工具可切换。",
mcpServers: "MCP Server(只读)",
defaultValue: "(缺省)",
/** Reset link next to the runtime dropdowns: rewinds the local pick back to "not overridden" (the menus offer no inherit row). */
deleteAgent: "删除 Agent",
builtinUndeletable: "内置 Agent 不可被删除",
deleteConfirm: (name: string): string =>
`确认删除 Agent「${name}」?其目录(含全部 Trace)将被递归删除,不可恢复。`,
stateVersion: "Agent State 版本",
transferTitle: "导出 / 导入",
transferDesc: "导出当前 Agent State 快照包(tar.gz);导入整目录覆盖,并以包内版本为准。",
exportSnapshot: "导出快照",
importSnapshot: "导入快照",
importing: "导入中…",
importDone: (v: number): string => `导入完成,Agent State 版本 v${v}`,
importConflictTitle: "版本冲突",
importConflictBody: "快照包版本不高于当前版本,导入将覆盖现有 Agent State。确认继续?",
resetConfigTitle: "还原为默认配置",
resetConfigDesc:
"把 system_config.yaml 还原为当前内置默认值(与 Skill 更新同语义):自定义的系统提示词、工具列表、模型/压缩参数与 MCP Server 将被覆盖,仅保留名称、描述与版本号。",
resetConfigAction: "还原为默认配置",
resetConfigConfirmBody:
"此操作会用当前默认值覆盖该 Agent 的现有配置:自定义系统提示词、工具列表、模型/压缩参数与 MCP Server 全部被替换,仅保留名称与描述。与 Skill 更新一样不可撤销,确认继续?",
resetConfigDone: "配置已还原为当前默认值",
},
models: {
title: "模型配置",
addCustom: "添加自定义模型",
addToGroup: "新增模型",
editTitle: "模型配置",
addTitle: "新增模型(OpenAI 协议)",
addTitleVendor: "新增模型",
addProtocolHint:
"新增模型固定走 OpenAI Chat Completions 兼容协议(不按模型 id 自动路由),base URL 填其兼容端点",
addAutoRouteHint:
"该分组的新模型按上游 id 由 AgentHub 自动路由到厂商官方客户端:base URL 留空即官方端点,API key 留空按解析出的客户端读取环境变量",
/** Caution beside the base URL when the entry uses a vendor's official protocol (everything except the OpenAI-protocol path). */
baseUrlOfficialNote:
"注意:该模型走厂商官方协议——自定义 base URL 必须是兼容官方协议的端点,不会因此切换为 OpenAI 协议",
autoRouteNone:
"该 id 无法被 AgentHub 自动路由:请核对 id,或改在 Custom / 自建分组下以 OpenAI 协议接入",
addGroup: "新增分组",
addGroupTitle: "新增分组",
addGroupDesc:
"自建分组与 Custom 同语义:组内模型走 OpenAI Chat Completions 兼容协议(base URL 必填,API key 留空读取 OPENAI_API_KEY)。分组由模型条目承载,保存首个模型后即出现。",
groupNameLabel: "分组名",
groupNameHint: "小写字母 / 数字开头,可含 - 与 _",
groupNameInvalid: "分组名只能用小写字母、数字、- 与 _(首字符为字母或数字),长度不超过 32",
groupNameExists: "该分组名已被内置分组或既有条目占用",
groupEmptyHint: "该分组暂无模型,点「新增模型」添加",
searchPlaceholder: "搜索模型:id / 名称 / 厂商",
noSearchResults: "没有匹配的模型",
syncCatalog: "同步预置",
syncCatalogHint:
"用内置目录更新预置模型:新增缺失条目、以目录字段为准刷新差异;本地新增模型与 API key 保持不变",
syncDone: (added: number, updated: number) => `预置模型已同步:新增 ${added}、更新 ${updated}`,
syncUpToDate: "预置模型已是最新",
homepage: "模型主页",
speedTest: "测速",
speedTestTitle: "分组测速",
speedTestConfirm: (n: number): string =>
`将对该分组的 ${n} 个模型逐个发起一次真实请求,测量首 token 延迟(TTFT)与输出速率(TPS),会消耗少量 API 额度。是否继续?`,
speedTestStart: "开始测速",
speedPending: "测速中…",
speedFailed: "测速失败",
ttftTitle: "首 token 延迟(TTFT)",
tpsTitle: "输出速率(TPS)",
modelCount: (n: number): string => `${n} 个模型`,
modelId: "模型 ID",
modelIdHint: "上游 API 使用的模型 id,如 gpt-5.5",
displayName: "模型名称",
displayNameHint: "留空则展示模型 ID",
providerGroup: "分组",
contextWindow: "上下文窗口",
/** 单位后缀,显示在上下文窗口/最大输出长度输入框内右侧。 */
tokenUnit: "Token",
contextWindowHint: "留空表示未知",
maxTokens: "最大输出长度",
/** Placeholders cannot scroll, so this must fit the half-width box; the full guidance is the input's title tooltip (the owner prefers no visible hint line — saves vertical space). */
maxTokensHint: "留空沿用 Agent 设置",
maxTokensTitle:
"按模型限制单次请求的最大输出 Token 数;留空沿用 Agent 设置,小上下文模型建议调低",
maxTokensInvalid: "必须为正整数",
clientTypeLocked: (t: string): string => `协议:${t}(沿用原配置,不可修改)`,
/** Switch label only — the dialog carries no explanation text for it (per owner). */
vision: "支持视觉",
/** Shown only while the vision switch is OFF: images are then read via the configured vision proxy model (describe_image). */
visionOffProxyHint: "使用视觉代理模型读图",
visionBadge: "视觉",
/** Light-yellow badge on zero-cost models (all three price buckets 0, e.g. the :free variants and openrouter/free). */
freeBadge: "免费",
visionModelBadge: "视觉代理",
setVisionModel: "设为视觉代理模型",
visionModelHint: "供不支持图片的模型经 describe_image 代读图片",
priceUnitShort: "/M tok",
testConnection: "测试连通性",
testing: "测试中…",
testOk: (ms: number): string => `连通正常(${ms} ms)`,
testFailed: (msg: string): string => `连通失败:${msg}`,
priceCacheRead: "缓存读取价格",
priceCacheWrite: "缓存写入价格",
priceOutput: "输出价格",
currency: "币种",
currencyUsd: "美元 $",
currencyCny: "人民币 ¥",
apiKey: "API key",
apiKeyKeepHint: "留空保留现有 key",
apiKeyEnvHint: (envKey: string): string => `留空则使用环境变量 ${envKey}`,
keyConfigured: "已配置 key",
clearApiKey: "清除已存 API key",
baseUrl: "自定义 base URL",
baseUrlHint: "留空使用厂商默认地址",
baseUrlRequired: "必须填写 base URL",
contextWindowDefaultHint: (n: number): string => `留空按 ${n} 计`,
confirmDeleteTitle: "删除模型",
confirmDelete: (name: string): string =>
`确定删除「${name}」?该模型的配置与 API key 将一并移除。`,
groupApiKey: "统一配置 API key",
groupApiKeyTitle: (label: string): string => `为「${label}」统一配置 API key`,
groupApiKeyHint: (n: number): string => `将写入该分组下全部 ${n} 个模型;留空不改动。`,
getApiKey: "获取 API key",
getModelIds: "获取模型 id",
groupKeyApplied: (n: number): string => `已为 ${n} 个模型配置 API key`,
// Providers with separate domestic / international endpoints: note on the default
// endpoint used when left blank via env var (the other side's key needs an explicit
// base URL). Written to match AgentHub's actual behavior; rendered wherever the env fallback hint appears.
providerEnvNotes: {
zhipu:
"缺省端点为 Z.AI 国际版(api.z.ai);智谱开放平台(bigmodel.cn)的 key 需填 base URL https://open.bigmodel.cn/api/paas/v4",
moonshot:
"缺省端点为国内版(api.moonshot.cn);platform.kimi.com(国际)的 key 需填 base URL https://api.moonshot.ai/v1",
} as Record<string, string | undefined>,
confirmVisionModelTitle: "设为视觉代理模型",
confirmVisionModel: (name: string): string =>
`确定把「${name}」设为视觉代理模型?不支持图片的模型将由它经 describe_image 代读图片。`,
confirmSaveTitle: "保存模型配置",
confirmSave: (name: string): string => `确定保存对「${name}」的配置修改?`,
confirmDefaultTitle: "设为默认模型",
confirmDefault: (name: string): string =>
`确定把「${name}」设为默认模型?新建的 Session 将默认使用它。`,
default: "默认",
setDefault: "设为默认模型",
remove: "删除模型",
readOnlyHint: "member 只读;模型与 credential 修改仅 owner 可执行",
empty: "尚未配置任何模型",
noKey: "未配置 key",
/** Chat model dropdown's bottom expander row: reveals the models hidden by the configured-key filter. */
showModelsWithoutKey: (n: number): string => `显示未配置 key 的模型(${n} 个)`,
modelIdExists: "该模型 id 已存在",
pricingAllOrNone: "三项价格需一并填写",
pricingInvalid: "必须为数字",
contextWindowInvalid: "必须为数字",
},
vault: {
desc: "本 Agent 专属的环境变量(存于 agent_state/.vault.toml):键值对注入其 shell 命令(exec_command)的子进程环境;键名会告知模型,值不进入模型上下文。子 Agent 使用各自的保险柜,不继承。保存后自下一个任务起生效(进行中的任务不受影响)。",
key: "键名",
value: "值",
valueMasked: "值(掩码)",
add: "添加",
addTitle: "添加环境变量",
remove: "删除",
deleteTitle: "删除环境变量",
deleteConfirm: (key: string): string => `确认删除环境变量「${key}」?值不可恢复。`,
overwriteTitle: "覆盖已有环境变量",
overwriteConfirm: (key: string): string => `「${key}」已存在,保存将覆盖原值且不可恢复。`,
empty: "尚未配置任何环境变量",
readOnlyHint: "member 只读;Vault 修改仅 owner 可执行",
keyHint: "字母、数字与下划线,不能以数字开头",
keyInvalid: "键名不合法:仅字母、数字与下划线,且不能以数字开头",
valueRequired: "值不能为空",
},
schedule: {
desc: "定时任务(agent_state/schedule/*.toml):到点自动向目标 Session 发送 prompt;文件亦可手工编辑,Web 端修改后即时生效。",
readOnlyHint: "member 只读;定时任务修改仅 owner 可执行",
colStatus: "状态",
colPeriod: "周期",
colTarget: "目标",
colFireTimes: "下次 / 最近触发",
colQueued: "排队",
statusNames: {
active: "生效",
disabled: "停用",
expired: "已过期",
done: "已完成",
missed: "已错过",
invalid: "无效",
} as Record<string, string>,
queued: "排队中",
once: "一次性",
newSession: "新建会话",
invalidFiles: "解析失败的文件(已跳过调度)",
empty: "尚未配置定时任务",
enable: "启用",
disable: "停用",
addTitle: "新建定时任务",
editTitle: (name: string): string => `编辑定时任务「${name}」`,
nameHint: "即文件名(不含 .toml),创建后不可改",
prompt: "Prompt",
enabled: "启用",
startAt: "开始时间",
endAt: "结束时间(可选)",
period: "周期",
periodPlaceholder: "30m / 12h / 7d,留空为一次性",
target: "目标",
targetNew: "每次新建会话",
targetSession: "绑定 Session",
sessionId: "Session id",
workspace: "Workspace(可选,留空自动创建)",
model: "Model",
modelDefault: "Project 默认",
deleteTitle: "删除定时任务",
deleteConfirm: (name: string): string => `确认删除定时任务「${name}」?`,
},
skills: {
pageTitle: "技能库",
pageDesc: "内置 Skill 库:浏览、快捷调用,或安装到 Agent。",
quickInvoke: "快捷调用",
/** Pre-filled body for quick invoke (per UI language; English is `use the <name> skill`). */
quickInvokeText: (name: string): string => `使用 ${name} 技能`,
manageInstall: "管理安装",
manageInstallTitle: (name: string): string => `管理安装:${name}`,
install: "安装",
installed: "已安装",
uninstall: "卸载",
/** Skill count in the group header (small text to the right of the group name). */
skillCount: (n: number): string => `${n} 个技能`,
/** Usage count in the card metadata (shows "unused" instead of a bare 0). */
usedByAgents: (n: number): string => (n === 0 ? "未被使用" : `${n} 个 Agent 在用`),
/** Top toast shown on successful install / uninstall. */
installedToast: (skill: string, agent: string): string => `已将 ${skill} 安装到 ${agent}`,
updateOutdated: (n: number): string => `有新版本:更新 ${n} 个 Agent 的安装`,
updateAction: "更新",
updateConfirmTitle: (name: string): string => `更新 ${name}`,
updateConfirmWarning: (name: string): string =>
`更新 ${name} 会把库内当前副本重装到各 Agent,覆盖其已安装的文件——对已装技能的本地改动会丢失,如有需要请先导出备份。`,
updatedToast: (skill: string, n: number): string =>
`已将 ${skill} 更新到最新版(${n} 个 Agent)`,
uninstalledToast: (skill: string, agent: string): string => `已从 ${agent} 卸载 ${skill}`,
/** Uninstall confirmation: removing the installed copy deletes its files (local edits included). */
uninstallConfirmTitle: (name: string): string => `卸载 ${name}`,
uninstallConfirmBody: (skill: string, agent: string): string =>
`确定从 ${agent} 卸载 ${skill} 吗?已安装的技能文件(含本地改动)将被删除。`,
},
chat: {
newSessionMenu: "新建对话",
chooseAgent: "选择 Agent",
chooseModel: "选择模型",
thinkingLevel: "思考等级",
/** Short tier names for the pre-conversation picker (per review: short names only, no descriptions, no "default" row). `none` exists purely to display a stored legacy value — it is never offered as a choice (many models cannot disable thinking). */
thinkingLevelNames: {
none: "无",
low: "低",
medium: "中",
high: "高",
xhigh: "极高",
} as Readonly<Record<string, string>>,
workspaceUseThis: "使用此目录",
workspaceUp: "上级目录",
workspaceNoSubdirs: "无子目录",
workspaceAuto: "自动临时目录",
workspaceClear: "改用自动临时目录",
workspaceDirInvalid: "目录不存在或无法访问,已回退",
/** 侧栏对话列表的分组切换(默认按工作区)与工作区分组。 */
groupByWorkspace: "按工作区分组",
groupByAgent: "按 Agent 分组",
tempWorkspaces: "临时工作区",
newSessionInWorkspace: "在此工作区新建对话",
draftSubtitle: "最擅长 AI 开发任务的自进化 Agent",
/**
* Example task cards on the draft screen: one click auto-submits the canned prompt (game
* card, then the LoL-player card, then the RAG card). These are the FULL working prompts — the README and
* landing page show a condensed one-sentence version of the RAG example for reading, and
* the cards' own desc lines stay short, but what actually gets submitted stays detailed:
* build quality depends on it.
*/
exampleTasks: {
game: {
label: "示例:2D 企鹅雪橇越野小游戏",
desc: "可爱南极企鹅滑雪橇跳石头,难度由易到难的 2D 纯前端小游戏",
prompt:
"做一个可爱的南极企鹅滑雪橇越野 2D 小游戏:按空格键起跳,跃过冰面上迎面而来的石头;" +
"开局要足够简单、上手无压力,滑行速度与障碍密度随时间平滑、循序渐进地上升,避免突然变难," +
"实时计分,撞上石头即结束并可一键重新开始。" +
"2D 横版画面、可爱卡通风,纯前端实现(单个 HTML 文件即可),界面遵循 web-design 技能。" +
"完成后在浏览器里自测一次,确认开局能轻松玩过几秒,并告诉我怎么打开和怎么玩。",
},
lol: {
label: "示例:英雄联盟音乐播放器",
desc: "用 SoundCloud Widget API 播放历届 Worlds 主题曲,单文件即开即用",
prompt: `用 SoundCloud Widget API(见 https://developers.soundcloud.com/docs/api/html5-widget)做一个英雄联盟 Worlds 主题曲播放器,单文件 index.html,file:// 打开即用。
## 技术约束
- 使用 SC.Widget JS API(widget.load / widget.toggle / widget.setVolume / widget.seekTo),引入 https://w.soundcloud.com/player/api.js
- iframe 必须可见(180px 高),visual=true color=f0b90b single_active=true
- 仅包含以下 8 首已确认可播曲目(oEmbed 验证通过),不要添加未经 oEmbed 验证的曲目:
- Warriors (S4) — soundcloud.com/leagueoflegends/warriors
- Worlds Collide (S5) — soundcloud.com/leagueoflegends/worlds-collide
- Legends Never Die (S7) — soundcloud.com/leagueoflegends/legends-never-die
- Phoenix (S9) — soundcloud.com/leagueoflegends/phoenix
- Burn It All Down (S11) — soundcloud.com/leagueoflegends/burn-it-all-down
- GODS (S13) — soundcloud.com/leagueoflegends/gods
- Heavy Is The Crown (S14) — soundcloud.com/linkinpark/heavy-is-the-crown
- Sacrifice (S15) — soundcloud.com/leagueoflegends/sacrifice
## 布局
- 左侧 260px 粘性侧边栏:曲目列表(S4/S5/… 标签 + emoji + 曲名 + 年份),点击高亮金色边框,SC.Widget.load() 切歌 + auto_play
- 右侧主区域:Hero 标题 + 桌面时钟(80px 等宽金色 HH:MM:SS,每秒刷新,冒号闪烁)+ 心情标签
- 播放器卡片:SoundCloud iframe + 自定义控制栏(⏮ ▶/⏸ ⏭ + 曲目信息 + 音量滑块,点击喇叭图标静音切换)
- 心情波动区:15 根金色动画柱,切歌时重新随机生成
- 键盘快捷键:空格播放暂停、← → 切歌、↑ ↓ 调音量
## 设计
Penguin 视觉风格(见 web-design 技能),深色/浅色主题(<html data-theme>),默认深色,localStorage 记忆。响应式:手机端侧边栏变为顶部横向滚动。
完成后在浏览器打开 index.html 自测一次。`,
},
rag: {
label: "示例:构建 Claude Code 文档专家",
desc: "收集 claude-code-docs 仓库,生成可对话、带来源引用的 RAG 知识应用",
prompt:
"收集 https://github.com/ericbuess/claude-code-docs 的文档,构建一个 RAG 知识应用:" +
"克隆仓库并整理语料,建立检索索引;应用化身 Claude Code 配置专家," +
"检索增强回答 Claude Code 相关问题并标注可点击的来源引用——" +
"引用要能展示命中的原文片段,并链接到真实文档;" +
"按 web-design 技能提供美观的 Web 聊天界面,空态展示几个示例问题。" +
"完成后运行应用、自测一个问题验证流式回答,并告诉我访问方式。",
},
},
sessionList: "Session",
defaultSessionTitle: "新对话",
model: "Model",
workspace: "Workspace",
workspaceHint: "留空自动创建临时目录;指定时必须是服务器上已存在的目录",
approvalMode: "审批模式",
/** Short description (the trigger button shows only the description, not the mode id). */
approvalModeNames: {
"allow-all": "全部放行",
"deny-all": "全部拒绝",
"read-only": "放行只读",
"always-ask": "总是询问",
} as Record<string, string>,
approvalModes: {
"allow-all": "全部放行(allow-all)",
"deny-all": "全部拒绝(deny-all)",
"read-only": "放行只读(read-only)",
"always-ask": "总是询问(always-ask)",
} as Record<string, string>,
statusRunning: "运行中",
statusCompacting: "压缩中",
pendingApprovals: (n: number) => `${n} 个待审批`,
jumpToLatest: "回到最新消息",
inputPlaceholder: "输入消息,Enter 发送,Shift+Enter 换行,可粘贴图片",
inputPlaceholderShort: "输入消息…",
/** Placeholder while a Task is running (mid-run steering): the message is delivered between turns with the next request. */
steerPlaceholder: "给运行中的 Agent 留言,随下一轮对话送达",
steerPlaceholderShort: "给运行中的 Agent 留言…",
steerSend: "发送给运行中的 Agent",
/** Queued hint shown after a successful steer, until the steering message appears in the stream. */
steerQueuedIndicator: "插话已排队,将随下一轮送达",
/** Label of the [user_steering] chip (a mid-run user message delivered between turns). */
userSteering: "用户插话",
/** Mid-run send-mode setting: steer (delivered mid-run) vs follow-up (queued until the run ends). */
steerModeLabel: "运行中发送方式",
steerModeSteer: "插话",
steerModeSteerHint: "立即插话:随下一轮对话送达运行中的 Agent",
steerModeFollowUp: "排队",
steerModeFollowUpHint: "排队跟进:本轮结束后自动作为新消息发送",
followUpPlaceholder: "排队为下一条消息,本轮结束后自动发送",
followUpPlaceholderShort: "排队为下一条消息…",
followUpSend: "排队为下一条消息",
/** Server-side queued follow-up count (auto-sent once the current run finishes). */
followUpQueuedChip: (n: number) => `${n} 条跟进消息已排队,本轮结束后自动发送`,
send: "发送",
stop: "停止",
compact: "压缩上下文",
approve: "允许",
deny: "拒绝",
decisionAllow: "已批准",
decisionDeny: "已拒绝",
decisionManual: "手动",
decisionAuto: "自动",
thinking: "思考",
subagent: "子会话",
subagentRunning: "运行中",
aborted: (reason?: string) => `[已中断]${reason ? `:${reason}` : ""}`,
/** Auth-dead notice (request_end status "auth"): action-only copy — updating the key on the Models page auto-unlocks this Session. */
modelAuthDead: "模型 API 认证失败:请在模型配置页更新该模型的 API key,或新建会话。",
modelAuthDeadOpenModels: "打开模型配置",
modelAuthDeadRetry: "重试",
modelAuthDeadCta: "新建会话",
modelAuthDeadPlaceholder: "模型认证失败,请先更新 API key",
/** Reconnect hint line; `secondsLeft` (waiting state only) switches to the live-countdown wording. */
reconnect: (
status: "timeout" | "malformed",
state: "waiting" | "retried" | "gaveUp",
attempt: number,
secondsLeft?: number,
) => {
const cause = status === "timeout" ? "连接超时或网络中断" : "响应不完整或无法解析";
const action =
state === "gaveUp"
? "已停止重试"
: state === "retried"
? `已发起第 ${attempt} 次重试`
: secondsLeft !== undefined
? `第 ${attempt} 次重试,${secondsLeft} 秒后发起…`
: `正在发起第 ${attempt} 次重试…`;
return `[重试] ${cause},${action}`;
},
/** "Retry now" on the reconnect countdown (skips the remaining backoff wait). */
reconnectRetryNow: "立即重试",
/** "Give up" on the reconnect countdown (the ordinary session abort). */
reconnectGiveUp: "放弃",
imageAlt: "用户上传的图片",
toolImageAlt: "工具输出的图片",
imagesAsPathHint:
"当前模型不支持直接查看图片:发送时图片将保存到会话临时目录,以文件路径转交(模型经 describe_image 查看)",
infoPanel: "Session 信息",
sessionStats: "统计",
statTokens: "Token 累计",
statElapsed: "用时",
statInput: "输入 tokens",
statCached: "已缓存",
statOutput: "输出 tokens",
statTps: "输出 TPS",
/** Copied-stats-line parenthesis wrappers around the cached amount (fullwidth for zh typography). */
statParenOpen: "(",
statParenClose: ")",
noSessions: "还没有 Session",
emptyStream: "发送一条消息开始对话",
historyLoadFailed: "历史消息加载失败",
statsLabel: "统计信息",
removeImage: "移除图片",
openWorkspace: "打开工作区",
/** File summary card at the end of a message (Codex-style): title, inline preview action, and collapsed row. */
filesInMessage: (n: number) => `${n} 个文件`,
openPreview: "点击预览",
showMoreFiles: (n: number) => `显示其余 ${n} 个文件`,
showLess: "收起",
contextUsage: "上下文占用",
contextUnknown: "上下文占用:压缩后待下次请求回报",
slashHint: "输入 / 使用命令",
mentionHint: "@ handoff 给其他 Agent",
mentionRemove: "移除 @ 目标",
/** Skill multi-select dropdown (input toolbar): button text, search box, empty state, and no-match hint. */
skillsSelect: "技能",
skillRemove: "移除技能",
skillsSearchPlaceholder: "搜索技能",
skillsNoMatch: "没有匹配的技能",
skillsEmptyHint: "暂无已装技能,去技能库添加",
/** Auto-generated invocation text when skills are selected and the body is empty (wrapped in [use_skills] before sending). */
skillsAutoMessage: (names: string[]): string => `使用 ${names.join("、")} 技能`,
handoffFrom: (agent: string) => `由 ${agent} 的对话交接而来`,
handoffBack: (title?: string) => (title ? `回到原对话:${title}` : "回到原对话"),
/** /model 切换:命令描述、拾取器标题、切换来源横幅与空正文自动消息。 */
switchModel: "切换模型开启新会话延续本对话",
switchModelTitle: "切换模型",
modelSwitchFrom: (prevModel?: string) =>
prevModel ? `已切换模型(原为 ${prevModel}),延续原会话` : "已切换模型,延续原会话",
/** /model 切换且正文为空时自动发送的首条消息正文(与 skillsAutoMessage 同一约定)。 */
modelSwitchAutoMessage: "换用新模型继续这段对话",
scheduledFrom: (name: string) => `由定时任务「${name}」触发`,
emptyGreeting: "开始一段新对话",
compactionRunning: (mode: string) => `压缩进行中(${mode})…`,
compactionDone: (mode: string): string =>
mode === "discard" ? "[压缩] 完成,旧上下文已丢弃" : "[压缩] 完成,已切换到摘要后的新上下文",
compactionFailed: (status: string) =>
`[压缩] ${status === "aborted" ? "已中断" : "失败"},保留当前上下文`,
unknownTool: "(未知工具)",
workRunning: "运行中",
workDone: "运行完毕",
workGroupSteps: (n: number) => `${n} 步`,
approvalWaiting: "待审批",
copyCode: "复制代码",
copyReply: "复制回复",
copyMessage: "复制消息",
deleteSession: "删除对话",
renameSession: "重命名对话",
renameSessionLabel: "标题",
deleteSessionConfirm: (title: string) =>
`确定删除「${title}」?该对话的消息与 Trace 将被移除,且不可恢复。`,
archiveSession: "归档",
unarchiveSession: "取消归档",
/** Sidebar group "reveal/load next page" row (display cap + server paging). */
loadMore: "更多",
/** Collapsed sidebar folders inside a group (lazy-loaded); the count is the group's exact server share. */
folderGroups: {
subagent: (n: number) => `子智能体(${n})`,
schedule: (n: number) => `定时任务(${n})`,
archived: (n: number) => `已归档(${n})`,
},
skillsBanner: (names: string[]): string => `使用技能:${names.join("、")}`,
/** Composer "+" extension menu (currently only goal mode; more entries later) and the goal chip. */
plusMenu: "更多输入方式",
goalMode: "目标模式",
goalModeDesc: "循环运行直至目标完成",
goalBudgetLabel: "Token 预算",
goalBudgetUnlimited: "预算不限",
goalBudgetValue: (value: string): string => `预算 ${value}`,
goalBudgetPlaceholder: "例如 500k",
goalBudgetHint: "支持 k/m 后缀;留空表示预算不限",
goalBudgetInvalid: "无效预算:应为正数,可带 k/m 后缀(500k、2m)",
goalBudgetSave: "保存预算",
goalRemove: "退出目标模式",
goalRoundBanner: (round: number): string => `目标 · 第 ${round} 轮`,
goalProgress: (rounds: number, tokens: string): string => `第 ${rounds} 轮 · tokens ${tokens}`,
goalStatus: {
active: "进行中",
complete: "已完成",
blocked: "受阻",
budget_limited: "预算耗尽",
aborted: "已中断",
} as Record<string, string>,
},
files: {
title: "文件",
upload: "上传",
download: "下载",
openInNewTab: "新页面打开",
previewNotIsolatedHint:
"当前访问地址无法提供独立预览源,页面将以沙箱模式打开:localStorage、Cookie 与第三方 embed 不可用。经 127.0.0.1 或 localhost 访问,或配置 PENGUIN_PREVIEW_ORIGIN 即可解除。",
refresh: "刷新",
root: "根目录",
empty: "空目录",
previewUnsupported: "该类型不支持预览,请下载查看",
uploaded: "已上传",
/** Upload-overwrite confirmation: same-name files in the current directory will be replaced. */
overwriteTitle: "覆盖同名文件",
overwriteConfirm: (n: number): string => `当前目录已存在以下 ${n} 个同名文件,上传将覆盖:`,
loadFailed: "加载失败",
previewTruncated: "内容过大,预览已截断,请下载查看完整文件",
details: "详情",
workspacePath: "Workspace 路径",
htmlRendered: "渲染视图",
htmlSource: "源码",
backToList: "返回列表",
resizeHandle: "拖拽调整宽度,双击恢复默认",
},
usage: {
title: "成本与统计",
today: "今日",
last7d: "近 7 天",
total: "累计",
tokens: "Token",
requests: "Requests",
from: "起始日期",
to: "结束日期",
colCacheRead: "cache_read",
colCacheWrite: "cache_write",
colOutput: "output",
uncostedNote: "* 只计入配置了价格的模型成本",
filterAllAgents: "全部 Agent",
filterAllModels: "全部模型",
chartAgentCalls: "各 Agent 调用次数",
chartSuccessRate: "各模型成功率",
chartTokenTrend: "Token 逐日变化",
chartCostTrend: "成本逐日变化",
empty: "暂无用量记录",
successAborted: "已中断(不计入)",
errors: "异常",
errorsTotal: "总数",
errorsUnexpected: "未预期",
errorsExpected: "预期内",
errorsTopCode: "最常见",
errorsColCode: "来源 · 错误码",
errorsColKind: "类型",
errorsColMessage: "消息",
errorsEmpty: "暂无异常",
},
traces: {
title: "轨迹观测",
timeline: "执行时间线",
laneLLM: "模型",
kindThinking: "思考",
kindModelReply: "模型回复",
kindToolGen: "工具调用生成",
legendToolExec: "工具调用执行",
legendApprovalWait: "审批等待",
task: (n: number) => `第 ${n} 轮`,
globalSummary: "全局统计",
tasksLabel: "轮次",
messages: "消息",
truncatedNote: (shown: number, total: number) => `仅展示前 ${shown} / ${total} 条消息`,
zoom: "缩放",
zoomReset: "双击复位缩放",
zoomOut: "缩小",
zoomIn: "放大",
linkHint:
"鼠标移到时间线段或消息行即可联动高亮,点击时间线段跳转到对应消息;图例可高亮同类;拖动下方滑块平移/缩放",
filesTitle: "Trace 文件",
selectSession: "在左侧选择一个 Session",
toolCalls: "工具调用",
taskInput: "本轮输入 tokens",
taskOutput: "本轮输出 tokens",
cacheHit: "命中缓存",
hitRate: "命中率",
compactions: "压缩次数",
compactionRound: "压缩",
empty: "该 Agent 暂无 Trace",
inProgress: "进行中",
systemPrompt: "系统提示词",
toolDefs: (n: number) => `工具定义(${n})`,
exportFile: "导出",
importTrace: "导入 Trace",
importing: "导入中…",
/** Client-side pre-check before reading the picked file (same cap as the server's import route). */
fileTooLarge: "文件超过 14MB 上限。",
},
benchmark: {
title: "评估中心",
selectBenchmark: "在左侧选择一个 Benchmark",
emptyAgent: "该 Agent 暂无 Benchmark",
caseCount: (n: number): string => `${n} 题`,
/** Chart title, varies by selected metric (score / cost / duration over time). */
trendTitle: (metric: string): string => `${metric}随时间变化`,
evaluations: "评估明细",
noEvaluations: "暂无评估记录",
/** Evaluation notes (scoreboard's summary: score source and notes on this round's changes). */
summaryLabel: "评估说明",
/** Chart legend: older evaluation records with no model label (gray series). */
legendUnlabeled: "未标注模型",
colVersion: "版本",
colModel: "模型",
colScore: "总分",
colDuration: "耗时",
colCase: "题目",
colRun: "运行",
colSession: "Session",
},
// Server error code → localized copy (the server's message is hardcoded Chinese; this is only a fallback for unknown codes).
errors: {
networkError: "网络错误,请检查连接",
modelCredentialMissing: (modelId: string) =>
`模型 ${modelId} 还没有可用的 API key,请先在「模型」页为它配置`,
noDefaultModel: "该 Project 还没有默认模型,请先在「模型」页添加模型并设为默认",
/** Localized text for the common server error codes (server error messages are English-only); looked up by ApiError.code in apiErrorText, falling back to the raw message for unmapped codes. */
byCode: {
invalid_credentials: "用户名或密码错误。",
password_mismatch: "当前密码不正确。",
invalid_password: "密码至少 8 位。",
admin_required: "仅管理员可执行此操作。",
not_found: "资源不存在,或你没有访问权限。",
agent_not_found: "该 Agent 已不存在。",
agent_exists: "该 Agent id 已被占用。",
project_exists: "该 Project id 已被占用。",
user_exists: "该用户名已被占用。",
user_not_found: "该用户已不存在。",
cannot_delete_admin: "内置 admin 不可删除。",
member_not_found: "该用户不是本 Project 的成员。",
schedule_exists: "已存在同名定时任务。",
schedule_not_found: "该定时任务已不存在。",
unknown_skill: "该技能不在技能库中。",
file_not_found: "该文件已不存在。",
file_too_large: "文件过大。",
payload_too_large: "请求体过大。",
dir_not_absolute: "目录必须是绝对路径。",
not_a_dir: "该路径不是目录。",
path_not_found: "该路径不存在。",
workspace_missing: "该 Session 的 Workspace 已不存在。",
task_in_progress: "该 Session 已有任务在运行。",
version_conflict: "快照版本不高于当前版本。",
invalid_title: "标题无效。",
invalid_trace: "该文件不是有效的 Trace 文件。",
trace_session_exists: "该 Agent 已存在同名 Session,无法导入重复的 Trace。",
},
},
};
/** Dictionary shape (constrains the English dictionary so keys and function signatures line up). */
export type Strings = typeof zh;
/**
* Runtime active dictionary (live binding): the locale Provider calls setActiveStrings
* to switch before render, and remounts the whole tree keyed by locale so every `S.x`
* read reflects the current language.
*/
export let S: Strings = zh;
export function setActiveStrings(next: Strings): void {
S = next;
}