From 775c6866be867dcd81ad1ede6bce624c0f36c54c Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 27 Mar 2026 10:51:39 +0000 Subject: [PATCH 01/40] =?UTF-8?q?=E5=B0=86=E5=89=8D=E7=AB=AF=20UI=20?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E3=80=81=E9=A1=B5=E9=9D=A2=E5=92=8C=E9=83=A8?= =?UTF-8?q?=E5=88=86=E6=96=87=E6=A1=A3=E7=BF=BB=E8=AF=91=E4=B8=BA=E7=AE=80?= =?UTF-8?q?=E4=BD=93=E4=B8=AD=E6=96=87=EF=BC=88=E7=AC=AC=E4=B8=80=E6=89=B9?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 翻译了以下内容: - UI 页面:Auth、BoardClaim、InstanceSettings 等 - UI 组件:Sidebar、ActivityCharts、AgentProperties 等 - 适配器配置:Claude、Codex、Cursor - 文档:what-is-paperclip.md https://claude.ai/code/session_01R8uzpczd2VcSFDGHCVvEtm --- doc/CLI.md | 104 +++++++++--------- docs/api/overview.md | 62 +++++------ docs/start/quickstart.md | 48 ++++---- docs/start/what-is-paperclip.md | 44 ++++---- .../adapters/claude-local/config-fields.tsx | 10 +- ui/src/adapters/codex-local/config-fields.tsx | 8 +- ui/src/adapters/cursor/config-fields.tsx | 4 +- .../adapters/gemini-local/config-fields.tsx | 4 +- ui/src/components/ActivityCharts.tsx | 34 +++--- ui/src/components/ActivityRow.tsx | 86 +++++++-------- ui/src/components/AgentActionButtons.tsx | 6 +- ui/src/components/AgentIconPicker.tsx | 4 +- ui/src/components/AgentProperties.tsx | 18 +-- ui/src/components/GoalTree.tsx | 2 +- ui/src/components/InlineEditor.tsx | 16 +-- ui/src/components/InlineEntitySelector.tsx | 8 +- ui/src/components/IssueRow.tsx | 4 +- ui/src/components/JsonSchemaForm.tsx | 8 +- ui/src/components/PropertiesPanel.tsx | 2 +- ui/src/components/Sidebar.tsx | 32 +++--- ui/src/components/SidebarAgents.tsx | 8 +- ui/src/components/SidebarProjects.tsx | 6 +- ui/src/pages/Auth.tsx | 32 +++--- ui/src/pages/BoardClaim.tsx | 4 +- ui/src/pages/InstanceExperimentalSettings.tsx | 21 ++-- ui/src/pages/InstanceGeneralSettings.tsx | 22 ++-- ui/src/pages/InstanceSettings.tsx | 44 ++++---- 27 files changed, 319 insertions(+), 322 deletions(-) diff --git a/doc/CLI.md b/doc/CLI.md index 6f945656d4..d9af69bbf5 100644 --- a/doc/CLI.md +++ b/doc/CLI.md @@ -1,49 +1,49 @@ -# CLI Reference +# CLI 参考 -Paperclip CLI now supports both: +Paperclip CLI 现在支持以下两类功能: -- instance setup/diagnostics (`onboard`, `doctor`, `configure`, `env`, `allowed-hostname`) -- control-plane client operations (issues, approvals, agents, activity, dashboard) +- 实例设置/诊断(`onboard`、`doctor`、`configure`、`env`、`allowed-hostname`) +- 控制平面客户端操作(issues、approvals、agents、activity、dashboard) -## Base Usage +## 基本用法 -Use repo script in development: +在开发中使用仓库脚本: ```sh pnpm paperclipai --help ``` -First-time local bootstrap + run: +首次本地引导 + 运行: ```sh pnpm paperclipai run ``` -Choose local instance: +选择本地实例: ```sh pnpm paperclipai run --instance dev ``` -## Deployment Modes +## 部署模式 -Mode taxonomy and design intent are documented in `doc/DEPLOYMENT-MODES.md`. +模式分类和设计意图记录在 `doc/DEPLOYMENT-MODES.md` 中。 -Current CLI behavior: +当前 CLI 行为: -- `paperclipai onboard` and `paperclipai configure --section server` set deployment mode in config -- runtime can override mode with `PAPERCLIP_DEPLOYMENT_MODE` -- `paperclipai run` and `paperclipai doctor` do not yet expose a direct `--mode` flag +- `paperclipai onboard` 和 `paperclipai configure --section server` 在配置中设置部署模式 +- 运行时可以通过 `PAPERCLIP_DEPLOYMENT_MODE` 覆盖模式 +- `paperclipai run` 和 `paperclipai doctor` 尚未提供直接的 `--mode` 标志 -Target behavior (planned) is documented in `doc/DEPLOYMENT-MODES.md` section 5. +目标行为(计划中)记录在 `doc/DEPLOYMENT-MODES.md` 第 5 节中。 -Allow an authenticated/private hostname (for example custom Tailscale DNS): +允许经过身份验证的/私有主机名(例如自定义 Tailscale DNS): ```sh pnpm paperclipai allowed-hostname dotta-macbook-pro ``` -All client commands support: +所有客户端命令支持: - `--data-dir ` - `--api-base ` @@ -52,18 +52,18 @@ All client commands support: - `--profile ` - `--json` -Company-scoped commands also support `--company-id `. +公司范围的命令还支持 `--company-id `。 -Use `--data-dir` on any CLI command to isolate all default local state (config/context/db/logs/storage/secrets) away from `~/.paperclip`: +使用 `--data-dir` 可在任何 CLI 命令中将所有默认本地状态(配置/上下文/数据库/日志/存储/密钥)隔离到 `~/.paperclip` 以外的位置: ```sh pnpm paperclipai run --data-dir ./tmp/paperclip-dev pnpm paperclipai issue list --data-dir ./tmp/paperclip-dev ``` -## Context Profiles +## 上下文配置文件 -Store local defaults in `~/.paperclip/context.json`: +将本地默认设置存储在 `~/.paperclip/context.json` 中: ```sh pnpm paperclipai context set --api-base http://localhost:3100 --company-id @@ -72,14 +72,14 @@ pnpm paperclipai context list pnpm paperclipai context use default ``` -To avoid storing secrets in context, set `apiKeyEnvVarName` and keep the key in env: +为避免在上下文中存储密钥,可设置 `apiKeyEnvVarName` 并将密钥保存在环境变量中: ```sh pnpm paperclipai context set --api-key-env-var-name PAPERCLIP_API_KEY export PAPERCLIP_API_KEY=... ``` -## Company Commands +## 公司命令 ```sh pnpm paperclipai company list @@ -87,19 +87,19 @@ pnpm paperclipai company get pnpm paperclipai company delete --yes --confirm ``` -Examples: +示例: ```sh pnpm paperclipai company delete PAP --yes --confirm PAP pnpm paperclipai company delete 5cbe79ee-acb3-4597-896e-7662742593cd --yes --confirm 5cbe79ee-acb3-4597-896e-7662742593cd ``` -Notes: +注意事项: -- Deletion is server-gated by `PAPERCLIP_ENABLE_COMPANY_DELETION`. -- With agent authentication, company deletion is company-scoped. Use the current company ID/prefix (for example via `--company-id` or `PAPERCLIP_COMPANY_ID`), not another company. +- 删除操作受服务器端 `PAPERCLIP_ENABLE_COMPANY_DELETION` 控制。 +- 使用 Agent 身份验证时,公司删除限定在公司范围内。请使用当前公司 ID/前缀(例如通过 `--company-id` 或 `PAPERCLIP_COMPANY_ID`),不能操作其他公司。 -## Issue Commands +## Issue 命令 ```sh pnpm paperclipai issue list --company-id [--status todo,in_progress] [--assignee-agent-id ] [--match text] @@ -111,7 +111,7 @@ pnpm paperclipai issue checkout --agent-id [--expected-sta pnpm paperclipai issue release ``` -## Agent Commands +## Agent 命令 ```sh pnpm paperclipai agent list --company-id @@ -119,20 +119,20 @@ pnpm paperclipai agent get pnpm paperclipai agent local-cli --company-id ``` -`agent local-cli` is the quickest way to run local Claude/Codex manually as a Paperclip agent: +`agent local-cli` 是以 Paperclip Agent 身份手动运行本地 Claude/Codex 的最快方式: -- creates a new long-lived agent API key -- installs missing Paperclip skills into `~/.codex/skills` and `~/.claude/skills` -- prints `export ...` lines for `PAPERCLIP_API_URL`, `PAPERCLIP_COMPANY_ID`, `PAPERCLIP_AGENT_ID`, and `PAPERCLIP_API_KEY` +- 创建一个新的长期 Agent API 密钥 +- 将缺失的 Paperclip 技能安装到 `~/.codex/skills` 和 `~/.claude/skills` +- 打印 `export ...` 行,包含 `PAPERCLIP_API_URL`、`PAPERCLIP_COMPANY_ID`、`PAPERCLIP_AGENT_ID` 和 `PAPERCLIP_API_KEY` -Example for shortname-based local setup: +基于短名称的本地设置示例: ```sh pnpm paperclipai agent local-cli codexcoder --company-id pnpm paperclipai agent local-cli claudecoder --company-id ``` -## Approval Commands +## 审批命令 ```sh pnpm paperclipai approval list --company-id [--status pending] @@ -145,51 +145,51 @@ pnpm paperclipai approval resubmit [--payload '{"...":"..."}'] pnpm paperclipai approval comment --body "..." ``` -## Activity Commands +## 活动命令 ```sh pnpm paperclipai activity list --company-id [--agent-id ] [--entity-type issue] [--entity-id ] ``` -## Dashboard Commands +## 仪表盘命令 ```sh pnpm paperclipai dashboard get --company-id ``` -## Heartbeat Command +## 心跳命令 -`heartbeat run` now also supports context/api-key options and uses the shared client stack: +`heartbeat run` 现在也支持上下文/api-key 选项,并使用共享的客户端栈: ```sh pnpm paperclipai heartbeat run --agent-id [--api-base http://localhost:3100] [--api-key ] ``` -## Local Storage Defaults +## 本地存储默认值 -Default local instance root is `~/.paperclip/instances/default`: +默认本地实例根目录为 `~/.paperclip/instances/default`: -- config: `~/.paperclip/instances/default/config.json` -- embedded db: `~/.paperclip/instances/default/db` -- logs: `~/.paperclip/instances/default/logs` -- storage: `~/.paperclip/instances/default/data/storage` -- secrets key: `~/.paperclip/instances/default/secrets/master.key` +- 配置:`~/.paperclip/instances/default/config.json` +- 嵌入式数据库:`~/.paperclip/instances/default/db` +- 日志:`~/.paperclip/instances/default/logs` +- 存储:`~/.paperclip/instances/default/data/storage` +- 密钥文件:`~/.paperclip/instances/default/secrets/master.key` -Override base home or instance with env vars: +通过环境变量覆盖基础主目录或实例: ```sh PAPERCLIP_HOME=/custom/home PAPERCLIP_INSTANCE_ID=dev pnpm paperclipai run ``` -## Storage Configuration +## 存储配置 -Configure storage provider and settings: +配置存储提供程序和设置: ```sh pnpm paperclipai configure --section storage ``` -Supported providers: +支持的提供程序: -- `local_disk` (default; local single-user installs) -- `s3` (S3-compatible object storage) +- `local_disk`(默认;本地单用户安装) +- `s3`(S3 兼容对象存储) diff --git a/docs/api/overview.md b/docs/api/overview.md index 416baae337..f3621293e6 100644 --- a/docs/api/overview.md +++ b/docs/api/overview.md @@ -1,39 +1,39 @@ --- -title: API Overview -summary: Authentication, base URL, error codes, and conventions +title: API 概览 +summary: 认证、基础 URL、错误码和约定 --- -Paperclip exposes a RESTful JSON API for all control plane operations. +Paperclip 为所有控制平面操作提供 RESTful JSON API。 -## Base URL +## 基础 URL -Default: `http://localhost:3100/api` +默认值:`http://localhost:3100/api` -All endpoints are prefixed with `/api`. +所有端点均以 `/api` 为前缀。 -## Authentication +## 认证 -All requests require an `Authorization` header: +所有请求都需要一个 `Authorization` 头: ``` Authorization: Bearer ``` -Tokens are either: +令牌类型包括: -- **Agent API keys** — long-lived keys created for agents -- **Agent run JWTs** — short-lived tokens injected during heartbeats (`PAPERCLIP_API_KEY`) -- **User session cookies** — for board operators using the web UI +- **Agent API 密钥** — 为代理创建的长期密钥 +- **Agent 运行 JWT** — 在心跳期间注入的短期令牌(`PAPERCLIP_API_KEY`) +- **用户会话 Cookie** — 供使用 Web UI 的看板操作员使用 -## Request Format +## 请求格式 -- All request bodies are JSON with `Content-Type: application/json` -- Company-scoped endpoints require `:companyId` in the path -- Run audit trail: include `X-Paperclip-Run-Id` header on all mutating requests during heartbeats +- 所有请求体均为 JSON,使用 `Content-Type: application/json` +- 公司范围的端点要求路径中包含 `:companyId` +- 运行审计追踪:在心跳期间的所有变更请求中包含 `X-Paperclip-Run-Id` 头 -## Response Format +## 响应格式 -All responses return JSON. Successful responses return the entity directly. Errors return: +所有响应返回 JSON。成功响应直接返回实体。错误响应返回: ```json { @@ -41,22 +41,22 @@ All responses return JSON. Successful responses return the entity directly. Erro } ``` -## Error Codes +## 错误码 -| Code | Meaning | What to Do | +| 状态码 | 含义 | 处理方式 | |------|---------|------------| -| `400` | Validation error | Check request body against expected fields | -| `401` | Unauthenticated | API key missing or invalid | -| `403` | Unauthorized | You don't have permission for this action | -| `404` | Not found | Entity doesn't exist or isn't in your company | -| `409` | Conflict | Another agent owns the task. Pick a different one. **Do not retry.** | -| `422` | Semantic violation | Invalid state transition (e.g. backlog -> done) | -| `500` | Server error | Transient failure. Comment on the task and move on. | +| `400` | 验证错误 | 检查请求体是否符合预期字段 | +| `401` | 未认证 | API 密钥缺失或无效 | +| `403` | 未授权 | 您没有执行此操作的权限 | +| `404` | 未找到 | 实体不存在或不在您的公司中 | +| `409` | 冲突 | 另一个代理拥有该任务。请选择其他任务。**请勿重试。** | +| `422` | 语义违规 | 无效的状态转换(例如 backlog -> done) | +| `500` | 服务器错误 | 暂时性故障。在任务上评论并继续。 | -## Pagination +## 分页 -List endpoints support standard pagination query parameters when applicable. Results are sorted by priority for issues and by creation date for other entities. +列表端点在适用时支持标准分页查询参数。问题按优先级排序,其他实体按创建日期排序。 -## Rate Limiting +## 速率限制 -No rate limiting is enforced in local deployments. Production deployments may add rate limiting at the infrastructure level. +本地部署不强制速率限制。生产环境部署可能在基础设施层面添加速率限制。 diff --git a/docs/start/quickstart.md b/docs/start/quickstart.md index 1ad30fcdfa..9fb979c476 100644 --- a/docs/start/quickstart.md +++ b/docs/start/quickstart.md @@ -1,60 +1,60 @@ --- -title: Quickstart -summary: Get Paperclip running in minutes +title: 快速开始 +summary: 几分钟内启动并运行 Paperclip --- -Get Paperclip running locally in under 5 minutes. +在 5 分钟内在本地启动并运行 Paperclip。 -## Quick Start (Recommended) +## 快速开始(推荐) ```sh npx paperclipai onboard --yes ``` -This walks you through setup, configures your environment, and gets Paperclip running. +这将引导你完成设置、配置你的环境并启动 Paperclip。 -To start Paperclip again later: +稍后再次启动 Paperclip: ```sh npx paperclipai run ``` -> **Note:** If you used `npx` for setup, always use `npx paperclipai` to run commands. The `pnpm paperclipai` form only works inside a cloned copy of the Paperclip repository (see Local Development below). +> **注意:** 如果你使用 `npx` 进行设置,请始终使用 `npx paperclipai` 来运行命令。`pnpm paperclipai` 形式仅在克隆的 Paperclip 仓库副本中有效(请参阅下方的本地开发部分)。 -## Local Development +## 本地开发 -For contributors working on Paperclip itself. Prerequisites: Node.js 20+ and pnpm 9+. +适用于参与 Paperclip 本身开发的贡献者。前提条件:Node.js 20+ 和 pnpm 9+。 -Clone the repository, then: +克隆仓库,然后: ```sh pnpm install pnpm dev ``` -This starts the API server and UI at [http://localhost:3100](http://localhost:3100). +这将在 [http://localhost:3100](http://localhost:3100) 启动 API 服务器和 UI。 -No external database required — Paperclip uses an embedded PostgreSQL instance by default. +无需外部数据库 — Paperclip 默认使用内嵌的 PostgreSQL 实例。 -When working from the cloned repo, you can also use: +在克隆的仓库中工作时,你也可以使用: ```sh pnpm paperclipai run ``` -This auto-onboards if config is missing, runs health checks with auto-repair, and starts the server. +如果缺少配置,这将自动进行初始化设置,运行带有自动修复功能的健康检查,然后启动服务器。 -## What's Next +## 下一步 -Once Paperclip is running: +一旦 Paperclip 运行起来: -1. Create your first company in the web UI -2. Define a company goal -3. Create a CEO agent and configure its adapter -4. Build out the org chart with more agents -5. Set budgets and assign initial tasks -6. Hit go — agents start their heartbeats and the company runs +1. 在 Web UI 中创建你的第一家公司 +2. 定义公司目标 +3. 创建 CEO 代理并配置其适配器 +4. 通过添加更多代理来构建组织架构图 +5. 设置预算并分配初始任务 +6. 点击启动 — 代理开始其心跳,公司开始运转 - - Learn the key concepts behind Paperclip + + 了解 Paperclip 背后的关键概念 diff --git a/docs/start/what-is-paperclip.md b/docs/start/what-is-paperclip.md index ea3f4ed84f..8858cf3a50 100644 --- a/docs/start/what-is-paperclip.md +++ b/docs/start/what-is-paperclip.md @@ -1,39 +1,39 @@ --- -title: What is Paperclip? -summary: The control plane for autonomous AI companies +title: 什么是 Paperclip? +summary: 自主 AI 公司的控制平面 --- -Paperclip is the control plane for autonomous AI companies. It is the infrastructure backbone that enables AI workforces to operate with structure, governance, and accountability. +Paperclip 是自主 AI 公司的控制平面。它是使 AI 员工团队能够以结构化、治理化和可问责的方式运行的基础设施骨干。 -One instance of Paperclip can run multiple companies. Each company has employees (AI agents), org structure, goals, budgets, and task management — everything a real company needs, except the operating system is real software. +一个 Paperclip 实例可以运行多家公司。每家公司拥有员工(AI 代理)、组织结构、目标、预算和任务管理——一家真实公司所需的一切,只不过其操作系统是真正的软件。 -## The Problem +## 问题 -Task management software doesn't go far enough. When your entire workforce is AI agents, you need more than a to-do list — you need a **control plane** for an entire company. +任务管理软件做得还不够。当你的整个员工团队都是 AI 代理时,你需要的不仅仅是一个待办事项列表——你需要一个用于整个公司的**控制平面**。 -## What Paperclip Does +## Paperclip 的功能 -Paperclip is the command, communication, and control plane for a company of AI agents. It is the single place where you: +Paperclip 是 AI 代理公司的指挥、通信和控制平面。它是你集中完成以下工作的地方: -- **Manage agents as employees** — hire, organize, and track who does what -- **Define org structure** — org charts that agents themselves operate within -- **Track work in real time** — see at any moment what every agent is working on -- **Control costs** — token salary budgets per agent, spend tracking, burn rate -- **Align to goals** — agents see how their work serves the bigger mission -- **Govern autonomy** — board approval gates, activity audit trails, budget enforcement +- **将代理作为员工管理** — 招聘、组织和跟踪谁做什么 +- **定义组织结构** — 代理在其中运作的组织架构图 +- **实时跟踪工作** — 随时查看每个代理正在做什么 +- **控制成本** — 每个代理的 token 薪资预算、支出跟踪、消耗速率 +- **对齐目标** — 代理了解其工作如何服务于更大的使命 +- **治理自主权** — 董事会审批门控、活动审计追踪、预算执行 -## Two Layers +## 两个层次 -### 1. Control Plane (Paperclip) +### 1. 控制平面(Paperclip) -The central nervous system. Manages agent registry and org chart, task assignment and status, budget and token spend tracking, goal hierarchy, and heartbeat monitoring. +中枢神经系统。管理代理注册和组织架构图、任务分配和状态、预算和 token 支出跟踪、目标层级以及心跳监控。 -### 2. Execution Services (Adapters) +### 2. 执行服务(适配器) -Agents run externally and report into the control plane. Adapters connect different execution environments — Claude Code, OpenAI Codex, shell processes, HTTP webhooks, or any runtime that can call an API. +代理在外部运行并向控制平面报告。适配器连接不同的执行环境 — Claude Code、OpenAI Codex、shell 进程、HTTP webhook,或任何能调用 API 的运行时。 -The control plane doesn't run agents. It orchestrates them. Agents run wherever they run and phone home. +控制平面不运行代理。它编排代理。代理在各自的环境中运行,然后回报状态。 -## Core Principle +## 核心原则 -You should be able to look at Paperclip and understand your entire company at a glance — who's doing what, how much it costs, and whether it's working. +你应该能够通过查看 Paperclip 一目了然地了解整个公司——谁在做什么、花费多少、以及是否有效。 diff --git a/ui/src/adapters/claude-local/config-fields.tsx b/ui/src/adapters/claude-local/config-fields.tsx index 972c378e58..ec15b19a07 100644 --- a/ui/src/adapters/claude-local/config-fields.tsx +++ b/ui/src/adapters/claude-local/config-fields.tsx @@ -13,7 +13,7 @@ const inputClass = "w-full rounded-md border border-border px-2.5 py-1.5 bg-transparent outline-none text-sm font-mono placeholder:text-muted-foreground/40"; const instructionsFileHint = - "Absolute path to a markdown file (e.g. AGENTS.md) that defines this agent's behavior. Injected into the system prompt at runtime."; + "Markdown 文件的绝对路径(例如 AGENTS.md),用于定义该智能体的行为。在运行时注入系统提示词中。"; export function ClaudeLocalConfigFields({ mode, @@ -30,7 +30,7 @@ export function ClaudeLocalConfigFields({ return ( <> {!hideInstructionsFile && ( - +
- + {isCreate ? ( {!hideInstructionsFile && ( - +
)} +
- +
{ @@ -15,7 +15,7 @@ function formatDayLabel(dateStr: string): string { return `${d.getMonth() + 1}/${d.getDate()}`; } -/* ---- Sub-components ---- */ +/* ---- 子组件 ---- */ function DateLabels({ days }: { days: string[] }) { return ( @@ -56,7 +56,7 @@ export function ChartCard({ title, subtitle, children }: { title: string; subtit ); } -/* ---- Chart Components ---- */ +/* ---- 图表组件 ---- */ export function RunActivityChart({ runs }: { runs: HeartbeatRun[] }) { const days = getLast14Days(); @@ -75,7 +75,7 @@ export function RunActivityChart({ runs }: { runs: HeartbeatRun[] }) { const maxValue = Math.max(...Array.from(grouped.values()).map(v => v.succeeded + v.failed + v.other), 1); const hasData = Array.from(grouped.values()).some(v => v.succeeded + v.failed + v.other > 0); - if (!hasData) return

No runs yet

; + if (!hasData) return

暂无运行记录

; return (
@@ -85,7 +85,7 @@ export function RunActivityChart({ runs }: { runs: HeartbeatRun[] }) { const total = entry.succeeded + entry.failed + entry.other; const heightPct = (total / maxValue) * 100; return ( -
+
{total > 0 ? (
{entry.succeeded > 0 &&
} @@ -127,7 +127,7 @@ export function PriorityChart({ issues }: { issues: { priority: string; createdA const maxValue = Math.max(...Array.from(grouped.values()).map(v => Object.values(v).reduce((a, b) => a + b, 0)), 1); const hasData = Array.from(grouped.values()).some(v => Object.values(v).reduce((a, b) => a + b, 0) > 0); - if (!hasData) return

No issues

; + if (!hasData) return

暂无工单

; return (
@@ -137,7 +137,7 @@ export function PriorityChart({ issues }: { issues: { priority: string; createdA const total = Object.values(entry).reduce((a, b) => a + b, 0); const heightPct = (total / maxValue) * 100; return ( -
+
{total > 0 ? (
{priorityOrder.map(p => entry[p] > 0 ? ( @@ -152,7 +152,7 @@ export function PriorityChart({ issues }: { issues: { priority: string; createdA })}
- ({ color: priorityColors[p], label: p.charAt(0).toUpperCase() + p.slice(1) }))} /> + ({ color: priorityColors[p], label: ({ critical: "严重", high: "高", medium: "中", low: "低" } as Record)[p] ?? p }))} />
); } @@ -168,13 +168,13 @@ const statusColors: Record = { }; const statusLabels: Record = { - todo: "To Do", - in_progress: "In Progress", - in_review: "In Review", - done: "Done", - blocked: "Blocked", - cancelled: "Cancelled", - backlog: "Backlog", + todo: "待办", + in_progress: "进行中", + in_review: "审核中", + done: "已完成", + blocked: "已阻塞", + cancelled: "已取消", + backlog: "待排期", }; export function IssueStatusChart({ issues }: { issues: { status: string; createdAt: Date }[] }) { @@ -194,7 +194,7 @@ export function IssueStatusChart({ issues }: { issues: { status: string; created const maxValue = Math.max(...Array.from(grouped.values()).map(v => Object.values(v).reduce((a, b) => a + b, 0)), 1); const hasData = allStatuses.size > 0; - if (!hasData) return

No issues

; + if (!hasData) return

暂无工单

; return (
@@ -204,7 +204,7 @@ export function IssueStatusChart({ issues }: { issues: { status: string; created const total = Object.values(entry).reduce((a, b) => a + b, 0); const heightPct = (total / maxValue) * 100; return ( -
+
{total > 0 ? (
{statusOrder.map(s => (entry[s] ?? 0) > 0 ? ( diff --git a/ui/src/components/ActivityRow.tsx b/ui/src/components/ActivityRow.tsx index ebfe23c521..3cb58096f1 100644 --- a/ui/src/components/ActivityRow.tsx +++ b/ui/src/components/ActivityRow.tsx @@ -5,47 +5,47 @@ import { cn } from "../lib/utils"; import { deriveProjectUrlKey, type ActivityEvent, type Agent } from "@paperclipai/shared"; const ACTION_VERBS: Record = { - "issue.created": "created", - "issue.updated": "updated", - "issue.checked_out": "checked out", - "issue.released": "released", - "issue.comment_added": "commented on", - "issue.attachment_added": "attached file to", - "issue.attachment_removed": "removed attachment from", - "issue.document_created": "created document for", - "issue.document_updated": "updated document on", - "issue.document_deleted": "deleted document from", - "issue.commented": "commented on", - "issue.deleted": "deleted", - "agent.created": "created", - "agent.updated": "updated", - "agent.paused": "paused", - "agent.resumed": "resumed", - "agent.terminated": "terminated", - "agent.key_created": "created API key for", - "agent.budget_updated": "updated budget for", - "agent.runtime_session_reset": "reset session for", - "heartbeat.invoked": "invoked heartbeat for", - "heartbeat.cancelled": "cancelled heartbeat for", - "approval.created": "requested approval", - "approval.approved": "approved", - "approval.rejected": "rejected", - "project.created": "created", - "project.updated": "updated", - "project.deleted": "deleted", - "goal.created": "created", - "goal.updated": "updated", - "goal.deleted": "deleted", - "cost.reported": "reported cost for", - "cost.recorded": "recorded cost for", - "company.created": "created company", - "company.updated": "updated company", - "company.archived": "archived", - "company.budget_updated": "updated budget for", + "issue.created": "创建了", + "issue.updated": "更新了", + "issue.checked_out": "签出了", + "issue.released": "发布了", + "issue.comment_added": "评论了", + "issue.attachment_added": "添加附件到", + "issue.attachment_removed": "移除了附件从", + "issue.document_created": "创建了文档于", + "issue.document_updated": "更新了文档于", + "issue.document_deleted": "删除了文档从", + "issue.commented": "评论了", + "issue.deleted": "删除了", + "agent.created": "创建了", + "agent.updated": "更新了", + "agent.paused": "暂停了", + "agent.resumed": "恢复了", + "agent.terminated": "终止了", + "agent.key_created": "创建了 API 密钥于", + "agent.budget_updated": "更新了预算于", + "agent.runtime_session_reset": "重置了会话于", + "heartbeat.invoked": "触发了心跳于", + "heartbeat.cancelled": "取消了心跳于", + "approval.created": "请求了审批", + "approval.approved": "批准了", + "approval.rejected": "拒绝了", + "project.created": "创建了", + "project.updated": "更新了", + "project.deleted": "删除了", + "goal.created": "创建了", + "goal.updated": "更新了", + "goal.deleted": "删除了", + "cost.reported": "报告了费用于", + "cost.recorded": "记录了费用于", + "company.created": "创建了公司", + "company.updated": "更新了公司", + "company.archived": "归档了", + "company.budget_updated": "更新了预算于", }; function humanizeValue(value: unknown): string { - if (typeof value !== "string") return String(value ?? "none"); + if (typeof value !== "string") return String(value ?? "无"); return value.replace(/_/g, " "); } @@ -55,14 +55,14 @@ function formatVerb(action: string, details?: Record | null): s if (details.status !== undefined) { const from = previous.status; return from - ? `changed status from ${humanizeValue(from)} to ${humanizeValue(details.status)} on` - : `changed status to ${humanizeValue(details.status)} on`; + ? `将状态从 ${humanizeValue(from)} 更改为 ${humanizeValue(details.status)} 于` + : `将状态更改为 ${humanizeValue(details.status)} 于`; } if (details.priority !== undefined) { const from = previous.priority; return from - ? `changed priority from ${humanizeValue(from)} to ${humanizeValue(details.priority)} on` - : `changed priority to ${humanizeValue(details.priority)} on`; + ? `将优先级从 ${humanizeValue(from)} 更改为 ${humanizeValue(details.priority)} 于` + : `将优先级更改为 ${humanizeValue(details.priority)} 于`; } } return ACTION_VERBS[action] ?? action.replace(/[._]/g, " "); @@ -106,7 +106,7 @@ export function ActivityRow({ event, agentMap, entityNameMap, entityTitleMap, cl : entityLink(event.entityType, event.entityId, name); const actor = event.actorType === "agent" ? agentMap.get(event.actorId) : null; - const actorName = actor?.name ?? (event.actorType === "system" ? "System" : event.actorType === "user" ? "Board" : event.actorId || "Unknown"); + const actorName = actor?.name ?? (event.actorType === "system" ? "系统" : event.actorType === "user" ? "看板" : event.actorId || "未知"); const inner = (
diff --git a/ui/src/components/AgentActionButtons.tsx b/ui/src/components/AgentActionButtons.tsx index 2d698e47ee..a130e1e632 100644 --- a/ui/src/components/AgentActionButtons.tsx +++ b/ui/src/components/AgentActionButtons.tsx @@ -4,7 +4,7 @@ import { Button } from "@/components/ui/button"; export function RunButton({ onClick, disabled, - label = "Run now", + label = "立即运行", size = "sm", }: { onClick: () => void; @@ -37,7 +37,7 @@ export function PauseResumeButton({ return ( ); } @@ -45,7 +45,7 @@ export function PauseResumeButton({ return ( ); } diff --git a/ui/src/components/AgentIconPicker.tsx b/ui/src/components/AgentIconPicker.tsx index 06257fb9a5..5f091f5ca2 100644 --- a/ui/src/components/AgentIconPicker.tsx +++ b/ui/src/components/AgentIconPicker.tsx @@ -46,7 +46,7 @@ export function AgentIconPicker({ value, onChange, children }: AgentIconPickerPr {children} setSearch(e.target.value)} className="mb-2 h-8 text-sm" @@ -71,7 +71,7 @@ export function AgentIconPicker({ value, onChange, children }: AgentIconPickerPr ))} {filtered.length === 0 && ( -

No icons match

+

没有匹配的图标

)}
diff --git a/ui/src/components/AgentProperties.tsx b/ui/src/components/AgentProperties.tsx index a89d8fb63e..c6e9a60923 100644 --- a/ui/src/components/AgentProperties.tsx +++ b/ui/src/components/AgentProperties.tsx @@ -50,18 +50,18 @@ export function AgentProperties({ agent, runtimeState }: AgentPropertiesProps) { return (
- + - + {roleLabels[agent.role] ?? agent.role} {agent.title && ( - + {agent.title} )} - + {adapterLabels[agent.adapterType] ?? agent.adapterType}
@@ -70,24 +70,24 @@ export function AgentProperties({ agent, runtimeState }: AgentPropertiesProps) {
{(runtimeState?.sessionDisplayId ?? runtimeState?.sessionId) && ( - + {String(runtimeState.sessionDisplayId ?? runtimeState.sessionId).slice(0, 12)}... )} {runtimeState?.lastError && ( - + {runtimeState.lastError} )} {agent.lastHeartbeatAt && ( - + {formatDate(agent.lastHeartbeatAt)} )} {agent.reportsTo && ( - + {reportsToAgent ? ( @@ -97,7 +97,7 @@ export function AgentProperties({ agent, runtimeState }: AgentPropertiesProps) { )} )} - + {formatDate(agent.createdAt)}
diff --git a/ui/src/components/GoalTree.tsx b/ui/src/components/GoalTree.tsx index 116b1668f2..34d8628f76 100644 --- a/ui/src/components/GoalTree.tsx +++ b/ui/src/components/GoalTree.tsx @@ -96,7 +96,7 @@ export function GoalTree({ goals, goalLink, onSelect }: GoalTreeProps) { const roots = goals.filter((g) => !g.parentId || !goalIds.has(g.parentId)); if (goals.length === 0) { - return

No goals.

; + return

暂无目标。

; } return ( diff --git a/ui/src/components/InlineEditor.tsx b/ui/src/components/InlineEditor.tsx index c05f8a4186..04d377c675 100644 --- a/ui/src/components/InlineEditor.tsx +++ b/ui/src/components/InlineEditor.tsx @@ -14,7 +14,7 @@ interface InlineEditorProps { mentions?: MentionOption[]; } -/** Shared padding so display and edit modes occupy the exact same box. */ +/** 共享内边距,使显示模式和编辑模式占据完全相同的框。 */ const pad = "px-1 -mx-1"; const markdownPad = "px-1"; const AUTOSAVE_DEBOUNCE_MS = 900; @@ -24,7 +24,7 @@ export function InlineEditor({ onSave, as: Tag = "span", className, - placeholder = "Click to edit...", + placeholder = "点击编辑...", multiline = false, imageUploadHandler, mentions, @@ -192,12 +192,12 @@ export function InlineEditor({ )} > {autosaveState === "saving" - ? "Autosaving..." + ? "自动保存中..." : autosaveState === "saved" - ? "Saved" + ? "已保存" : autosaveState === "error" - ? "Could not save" - : "Idle"} + ? "保存失败" + : "空闲"}
@@ -228,8 +228,8 @@ export function InlineEditor({ ); } - // Use div instead of Tag when rendering markdown to avoid invalid nesting - // (e.g.

cannot contain the

/

elements that markdown produces) + // 渲染 markdown 时使用 div 代替 Tag 以避免无效嵌套 + // (例如

不能包含 markdown 生成的

/

元素) const DisplayTag = value && multiline ? "div" : Tag; return ( diff --git a/ui/src/components/InlineEntitySelector.tsx b/ui/src/components/InlineEntitySelector.tsx index db453b7dd6..8f535a61ae 100644 --- a/ui/src/components/InlineEntitySelector.tsx +++ b/ui/src/components/InlineEntitySelector.tsx @@ -21,7 +21,7 @@ interface InlineEntitySelectorProps { className?: string; renderTriggerValue?: (option: InlineEntityOption | null) => ReactNode; renderOption?: (option: InlineEntityOption, isSelected: boolean) => ReactNode; - /** Skip the Portal so the popover stays in the DOM tree (fixes scroll inside Dialogs). */ + /** 跳过 Portal,使弹出框保留在 DOM 树中(修复对话框内的滚动问题)。 */ disablePortal?: boolean; } @@ -120,9 +120,9 @@ export const InlineEntitySelector = forwardRef { event.preventDefault(); - // On touch devices, don't auto-focus the search input to avoid - // opening the virtual keyboard which reshapes the viewport and - // pushes the popover off-screen. + // 在触摸设备上,不要自动聚焦搜索输入框,以避免 + // 打开虚拟键盘导致视口重新调整大小, + // 并将弹出框推出屏幕。 const isTouch = window.matchMedia("(pointer: coarse)").matches; if (!isTouch) { inputRef.current?.focus(); diff --git a/ui/src/components/IssueRow.tsx b/ui/src/components/IssueRow.tsx index d3bcfcffa7..99d408d8d3 100644 --- a/ui/src/components/IssueRow.tsx +++ b/ui/src/components/IssueRow.tsx @@ -109,7 +109,7 @@ export function IssueRow({ } }} className="inline-flex h-4 w-4 items-center justify-center rounded-full transition-colors hover:bg-blue-500/20" - aria-label="Mark as read" + aria-label="标记为已读" > diff --git a/ui/src/components/JsonSchemaForm.tsx b/ui/src/components/JsonSchemaForm.tsx index e185bf4b3e..7d613341e3 100644 --- a/ui/src/components/JsonSchemaForm.tsx +++ b/ui/src/components/JsonSchemaForm.tsx @@ -26,7 +26,7 @@ import { // --------------------------------------------------------------------------- /** - * Threshold for string length above which a Textarea is used instead of a standard Input. + * 字符串长度超过此阈值时使用 Textarea 而不是标准 Input。 */ const TEXTAREA_THRESHOLD = 200; @@ -35,9 +35,9 @@ const TEXTAREA_THRESHOLD = 200; // --------------------------------------------------------------------------- /** - * Subset of JSON Schema properties we understand for form rendering. - * We intentionally keep this loose (`Record`) at the top - * level to match the `JsonSchema` type in shared, but narrow internally. + * 用于表单渲染的 JSON Schema 属性子集。 + * 我们有意在顶层保持宽松的类型(`Record`) + * 以匹配 shared 中的 `JsonSchema` 类型,但在内部进行收窄。 */ export interface JsonSchemaNode { type?: string | string[]; diff --git a/ui/src/components/PropertiesPanel.tsx b/ui/src/components/PropertiesPanel.tsx index 69e2948215..8d4abdf0c2 100644 --- a/ui/src/components/PropertiesPanel.tsx +++ b/ui/src/components/PropertiesPanel.tsx @@ -15,7 +15,7 @@ export function PropertiesPanel() { >

- Properties + 属性 diff --git a/ui/src/components/Sidebar.tsx b/ui/src/components/Sidebar.tsx index b8cea2ca26..651662a2cb 100644 --- a/ui/src/components/Sidebar.tsx +++ b/ui/src/components/Sidebar.tsx @@ -48,7 +48,7 @@ export function Sidebar() { return (