feat: integrate Pi backend and Pi Web

This commit is contained in:
luckyyzh
2026-07-30 19:37:53 +08:00
commit 7392ab9dd7
1390 changed files with 337197 additions and 0 deletions
+172
View File
@@ -0,0 +1,172 @@
# Internationalization
Pi Web's interface uses a small internal i18n layer. It is intentionally kept
inside the application instead of introducing another runtime dependency, so
the UI can support more languages without coupling the server, API responses,
or Pi's own output to a particular language.
## Current Languages
The built-in language packages are:
- `en` - English
- `zh-CN` - Simplified Chinese
The initial locale is inferred from the browser. Users can change the locale
from the language control in the top bar. The selected locale is persisted in
`localStorage` under `pi-locale`.
## Using Translations In A Component
Client components should get the translation function from `useI18n`:
```tsx
import { useI18n } from "@/hooks/useI18n";
export function ExampleButton() {
const { t } = useI18n();
return <button aria-label={t("example.open")}>{t("example.open")}</button>;
}
```
The component must be rendered below `I18nProvider`. The application root
already provides it in `app/page.tsx`.
Use parameters for values that change at runtime instead of concatenating
translated fragments:
```tsx
<span>{t("session.messageCount", { count: messageCount })}</span>
```
The corresponding message can use `{count}`:
```ts
"session.messageCount": "{count} messages"
```
This keeps the sentence structure under the control of each language package.
## Adding A New Language
### 1. Create The Message File
Add a file under `lib/i18n/messages/`, using a BCP 47-style identifier. For
example, a Japanese package would be `lib/i18n/messages/ja.ts`:
```ts
import type { LocalePlugin } from "../types";
/** Pi Web Japanese language package. */
export const jaLocale: LocalePlugin = {
id: "ja",
label: "日本語",
messages: {
"common.language": "言語",
// Add the rest of the shared keys here.
},
};
```
The message keys must remain stable and language-neutral. Copy the complete
key set from `lib/i18n/messages/en.ts` when starting a new package. Missing
keys fall back to English, but a complete package is preferred for a user
facing language.
### 2. Register The Package
Import and register the package in `lib/i18n/registry.ts`:
```ts
import { jaLocale } from "./messages/ja";
registerLocale(jaLocale);
```
`LocalePlugin.id` must be unique and non-empty. The label is displayed in the
language selector. `getSupportedLocales()` automatically exposes registered
packages to the UI.
### 3. Update Locale-Specific Types If Needed
The built-in `Locale` type currently lists `en` and `zh-CN` for compile-time
safety. When adding a built-in language, update that union in
`lib/i18n/types.ts` and update any locale validation that intentionally lists
the built-in identifiers.
### 4. Add Tests
Extend `lib/i18n/registry.test.mjs` when browser-language detection or registry
behavior changes. Extend `lib/i18n/format.test.mjs` when interpolation or
locale-aware formatting changes. A new language should at minimum verify its
registry id and a representative translation.
## Adding Or Changing UI Text
1. Add a stable key to both `lib/i18n/messages/en.ts` and
`lib/i18n/messages/zh-CN.ts`.
2. Put keys under a feature namespace such as `chat.*`, `files.*`,
`settings.*`, or `common.*`.
3. Use `t("namespace.key")` in visible text, `title`, `aria-label`, and
`placeholder` values.
4. Use interpolation for counts, names, paths, and other runtime values.
5. Keep product names, model names, provider names, commands, file paths,
user content, tool output, and server-provided error details unchanged.
6. Do not translate an API error by matching its English text. Translate only
the local UI fallback around it.
Keep established technical terms in English when translating them would make
the interface harder to map back to commands, configuration fields, or Pi
documentation. This includes terms such as `Agent`, `API Key`, `Provider`,
`Token`, `worktree`, `Diff`, `CWD`, `Shell`, `Git`, `HEAD`, `OAuth`, and
`Mermaid`; preserve their conventional casing. Translate the surrounding
actions and status text. Common concepts with clear localized forms, such as
model, plugin, skill, prompt, context, and cache, can remain localized.
For example:
```ts
// en.ts
"files.uploadedCount": "{count} uploaded"
// zh-CN.ts
"files.uploadedCount": "已上传 {count} 个文件"
```
```tsx
<span>{t("files.uploadedCount", { count: uploadedCount })}</span>
```
If a key is missing from the selected language, the formatter first tries the
English package and finally returns the key itself. Missing keys also produce
a development warning, which makes incomplete translations visible during
development.
## Verification
Run the following commands from the repository root:
```bash
node_modules/.bin/tsc --noEmit
npm run lint
node_modules/.bin/jiti lib/i18n/registry.test.mjs
node_modules/.bin/jiti lib/i18n/format.test.mjs
node --test lib/*.test.mjs
```
Do not run `next build` during normal development. See the repository
development notes for the reason.
## Design Principles
Pi Web is a user-facing web interface. Internationalization is therefore an
accessibility and usability feature: people should be able to understand and
operate the interface in a language they are comfortable with. Language
support should lower the barrier to using Pi Web, not be treated as a measure
of technical expertise or as a reason to exclude otherwise useful
contributions.
The i18n layer is deliberately incremental. Contributors can add a language
or improve a small group of UI messages without rewriting API contracts,
changing Pi's output, or introducing a large translation framework.
+177
View File
@@ -0,0 +1,177 @@
# Release Checklist
This repo publishes two artifacts for each release:
- npm package: `@agegr/pi-web`
- GitHub Release: `agegr/pi-web`
Use this checklist from a clean `main` checkout.
## 1. Preflight
```bash
git status --short --branch
git log --oneline --decorate -5
gh auth status
npm whoami
node -e "const p=require('./package.json'); console.log(p.version)"
```
Expected:
- `git status` is clean, or only contains changes you intentionally plan to release.
- GitHub is authenticated as an account that can push and create releases.
- npm is authenticated as an account that can publish `@agegr/pi-web`.
## 2. Publish to npm
```bash
npm run release
```
The release script runs:
```bash
npm version patch --no-git-tag-version && npm run build && npm publish --access public
```
Notes:
- This bumps `package.json` and `package-lock.json`.
- It intentionally runs a production build. Do not run `next build` during normal development; release work is the exception.
- If `npm view @agegr/pi-web version` briefly shows the previous version, check the exact version instead:
```bash
npm view @agegr/pi-web@<version> version --registry https://registry.npmjs.org/
npm view @agegr/pi-web versions --json --registry https://registry.npmjs.org/
```
## 3. Commit the Version Bump
Replace `<version>` with the new package version, for example `0.7.5`.
```bash
git diff -- package.json package-lock.json
git add package.json package-lock.json
git commit -m "Release v<version>"
```
## 4. Tag and Push
```bash
git tag -a v<version> -m "v<version>"
git push origin main --tags
```
Confirm the tag does not already exist before creating it when unsure:
```bash
git ls-remote --tags origin v<version>
gh release view v<version> --repo agegr/pi-web
```
## 5. Generate Release Notes from Commits
Use the previous release tag as the base.
```bash
git log --oneline --decorate v<previous>..v<version>
git log --format='%h%x09%s%n%b' v<previous>..v<version>
git diff --stat v<previous>..v<version>
```
Write the release notes from those commits, not from memory. Include both Chinese and English sections. Keep commit hashes next to each item when useful.
Suggested structure:
```markdown
## 中文
基于 `v<previous>..v<version>` 的提交整理。
### 新增
- ...
### 修复
- ...
### 改进
- ...
### 内部调整
- 发布 npm 包 `@agegr/pi-web@<version>`。
## English
Prepared from commits in `v<previous>..v<version>`.
### Added
- ...
### Fixed
- ...
### Improved
- ...
### Internal
- Published npm package `@agegr/pi-web@<version>`.
```
## 6. Create or Update the GitHub Release
Create a new release:
```bash
gh release create v<version> \
--repo agegr/pi-web \
--verify-tag \
--title "v<version>" \
--notes-file release-notes.md
```
If the release already exists and only the notes need updating:
```bash
gh release edit v<version> \
--repo agegr/pi-web \
--notes-file release-notes.md
```
You can avoid a temporary file by passing notes through stdin:
```bash
gh release edit v<version> --repo agegr/pi-web --notes-file - <<'EOF'
## 中文
...
## English
...
EOF
```
## 7. Final Verification
```bash
gh release view v<version> --repo agegr/pi-web
npm view @agegr/pi-web@<version> version --registry https://registry.npmjs.org/
git status --short --branch
git log --oneline --decorate -3
```
Expected:
- GitHub Release exists and is not a draft unless intentionally published as one.
- npm exact version resolves.
- `main` is aligned with `origin/main`.
- `HEAD` points at the release commit and `v<version>` tag.
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

+87
View File
@@ -0,0 +1,87 @@
# Worktrees in Pi Web
Pi Web can show all Git worktrees for one project in the sidebar. Use this when you want to keep separate checkouts for different branches, while keeping the project's sessions grouped together.
## When the Worktree Control Appears
The worktree switcher appears below the project picker when the selected directory is a Git repository root.
It is hidden when:
- The selected directory is not a Git repository.
- The selected directory is inside a repository, but not the repository root.
- Git cannot read the repository's worktree list.
If you are inside a repo subdirectory, open the repository root from the project picker to manage worktrees.
## Switching Worktrees
Use the worktree switcher to choose which checkout Pi Web should use for new work in that project.
Switching worktrees affects:
- New sessions started from the sidebar.
- The file Explorer.
- File mentions inserted from the Explorer.
Existing sessions stay grouped under the same project. Opening an existing session moves the effective working directory back to that session's checkout.
## Creating a Worktree
Choose `New worktree...` from the worktree menu and enter a branch name.
Pi Web creates the checkout at:
```text
<repo>-worktrees/<branch>
```
For example, if the main checkout is:
```text
/Users/alex/Documents/Workspace/pi-web
```
and you create branch `codex/worktree-help`, the worktree is created under:
```text
/Users/alex/Documents/Workspace/pi-web-worktrees/codex-worktree-help
```
If the branch already exists, Pi Web adds a worktree for that branch. If it does not exist, Pi Web creates the branch from the current `HEAD`.
## Removing a Worktree
Use the remove button next to a non-main worktree to remove that checkout.
Removing a worktree does not delete:
- The Git branch.
- Pi Web session history.
- The main checkout.
If the worktree has uncommitted or untracked files, Git refuses the removal. Pi Web then offers a force remove action. Force removal discards the uncommitted files in that checkout, so use it only when you no longer need those changes.
## Sessions and Worktrees
Pi Web groups sessions by project root, so sessions from the main checkout and linked worktrees appear together.
Each session still remembers the working directory it was created with. That means:
- A session started in a worktree continues to use that worktree path.
- A session started in the main checkout continues to use the main checkout.
- If a worktree has been removed, old sessions from it stay visible under the project so you can still find the history.
## Troubleshooting
**I do not see the worktree switcher.**
Select a Git repository root. Non-Git directories and repo subdirectories show a small hint instead of the switcher.
**A branch cannot be added as a worktree.**
Git allows a branch to be checked out in only one worktree at a time. Switch to the existing worktree for that branch, or remove it first.
**A removed worktree still shows up in Git.**
Git can keep prunable worktree records after a checkout disappears. Pi Web filters those out of the switcher.
**The Explorer shows a different branch than the open chat.**
The Explorer follows the selected worktree. The chat follows the opened session. Click the session again to move the sidebar back to that session's checkout.
+87
View File
@@ -0,0 +1,87 @@
# Pi Web 里的 Worktree
Pi Web 会把同一个 Git 项目的 main checkout 和 linked worktree 放在同一个项目下。你可以用它在不同分支之间切换工作目录,同时保留统一的会话列表。
## 什么时候会看到 Worktree 控件
当左上角选择的是 Git 仓库根目录时,项目选择器下面会出现 worktree 切换控件。
以下情况不会显示:
- 当前目录不是 Git 仓库。
- 当前目录在某个 Git 仓库里面,但不是仓库根目录。
- Git 无法读取这个仓库的 worktree 列表。
如果你在仓库子目录里,先从项目选择器打开仓库根目录,再管理 worktree。
## 切换 Worktree 会影响什么
worktree 切换器决定 Pi Web 接下来使用哪个 checkout。
它会影响:
- 从侧边栏新建的会话。
- 左侧 Explorer 浏览的文件。
- 从 Explorer 插入到输入框里的文件路径。
已有会话仍然按同一个 project root 分组。点击一个已有会话时,侧边栏会回到这个会话原本所在的 checkout。
## 新建 Worktree
在 worktree 菜单里选择 `New worktree...`,输入 branch name。
Pi Web 会把 checkout 放在:
```text
<repo>-worktrees/<branch>
```
例如 main checkout 是:
```text
/Users/alex/Documents/Workspace/pi-web
```
新建 `codex/worktree-help` 时,目录会是:
```text
/Users/alex/Documents/Workspace/pi-web-worktrees/codex-worktree-help
```
如果这个 branch 已存在,Pi Web 会为它添加 worktree。如果 branch 不存在,Pi Web 会从当前 `HEAD` 创建这个 branch。
## 删除 Worktree
非 main worktree 右侧有删除按钮。它删除的是这个 checkout 目录。
删除 worktree 不会删除:
- Git branch。
- Pi Web 的历史会话。
- main checkout。
如果 worktree 里有未提交或未跟踪文件,Git 会拒绝删除。Pi Web 会再显示 force remove。force remove 会丢弃这个 checkout 里的未提交文件,只在确定不需要这些改动时使用。
## 会话和 Worktree 的关系
Pi Web 按 project root 分组会话,所以 main checkout 和 linked worktree 里的会话会显示在一起。
但每个会话仍然记得自己创建时的 working directory:
- 在某个 worktree 创建的会话,会继续使用那个 worktree path。
- 在 main checkout 创建的会话,会继续使用 main checkout。
- 如果某个 worktree 已被删除,它的历史会话仍会显示在项目下,方便你找回上下文。
## 常见问题
**为什么我看不到 worktree 切换器?**
请确认当前选择的是 Git 仓库根目录。非 Git 目录和仓库子目录会显示一行轻提示,而不是切换器。
**为什么某个 branch 不能创建 worktree?**
Git 不允许同一个 branch 同时被多个 worktree checkout。你可以切到已有的 worktree,或者先删除那个 checkout。
**Git 里还有已经消失的 worktree 记录怎么办?**
Git 有时会保留 prunable worktree 记录。Pi Web 会过滤这些记录,不在切换器里显示。
**Explorer 和当前聊天看起来不在同一个分支?**
Explorer 跟随当前选择的 worktree;聊天跟随打开的会话。重新点击会话,可以把侧边栏切回这个会话所在的 checkout。