One computer-and-phone still, and the README stops advertising its style guide

The phone shot stood alone as a 300px column, so it read as cut off at
one side. The desktop approval card and the phone sign-in are now one
composite (docs/demo/composite.py), and every README still is dark and
2x-captured, so the page reads as one product.

The document table lists user-facing documents; the README style notes
stay in docs/ for whoever edits the README.
This commit is contained in:
王性驊 2026-11-10 14:21:06 +08:00
parent 81449be33d
commit 818e551e6b
8 changed files with 78 additions and 37 deletions

View File

@ -76,18 +76,14 @@ Running this from an agent, with no questions asked: `sh install.sh --print` rep
Say “do this on my computer” and it does not just run it. You see the exact step and choose allow once, always, or no. Sending, paying, and deleting are asked every time, even when you turned the other prompts off. Say “do this on my computer” and it does not just run it. You see the exact step and choose allow once, always, or no. Sending, paying, and deleting are asked every time, even when you turned the other prompts off.
<p align="center"> <p align="center">
<img src="shots/readme/ask.png" alt="A card asking permission before a command runs on your computer" width="960"> <img src="shots/readme/computer-phone.png" alt="Left: a card asking permission before a command runs on your computer. Right: the phone page, asking you to sign in" width="960">
</p> </p>
- **Files and commands** — it asks first. Or it works only inside its own computer, a small desktop kept apart from yours. - **Files and commands** — it asks first. Or it works only inside its own computer, a small desktop kept apart from yours.
- **Web pages and forms** — its own browser. Passwords and card numbers are filled by the app, not read out to the model. - **Web pages and forms** — its own browser. Passwords and card numbers are filled by the app, not read out to the model.
- **The screen you are using** — it reads the buttons in the screenshot, then clicks. If it needs you to sign in, it hands you the screen. - **The screen you are using** — it reads the buttons in the screenshot, then clicks. If it needs you to sign in, it hands you the screen.
- **A clock** — “every weekday at 9, three news items” becomes a routine. - **A clock** — “every weekday at 9, three news items” becomes a routine.
- **A phone, from anywhere** — open your URL in a browser, sign in, and it is the same chat. - **A phone, from anywhere** — open your URL in a browser, sign in, and it is the same chat; watch it from outside.
<p align="center">
<img src="shots/readme/phone.png" alt="The phone page asks you to sign in" width="300">
</p>
<p align="center"> <p align="center">
<img src="shots/readme/routines.png" alt="A weekday 9:00 news routine created from the chat" width="960"> <img src="shots/readme/routines.png" alt="A weekday 9:00 news routine created from the chat" width="960">
@ -161,7 +157,6 @@ Three programs are enough, and there is no database server: `anybot` (the deskto
| Trust boundaries, where keys live | [SECURITY.md](SECURITY.md) | | Trust boundaries, where keys live | [SECURITY.md](SECURITY.md) |
| Every feature, listed | [FEATURES.md](FEATURES.md) | | Every feature, listed | [FEATURES.md](FEATURES.md) |
| Packaging, CI | [docs/release.md](docs/release.md) | | Packaging, CI | [docs/release.md](docs/release.md) |
| Editing this README without breaking the layout | [docs/readme-style.md](docs/readme-style.md) |
Chinese is the source of truth; `docs/en/` is a summary for people who do not read Chinese. Chinese is the source of truth; `docs/en/` is a summary for people who do not read Chinese.

View File

@ -76,18 +76,14 @@ irm https://code.30cm.net/daniel.w/AnyBot/raw/branch/main/install.ps1 | iex
你說「在我的電腦上做」,它不會默默執行。畫面上出現要做的事,你按允許才繼續。付款、寄出、刪除這類動作,每次都會再問一次,即使你關掉了別的詢問。 你說「在我的電腦上做」,它不會默默執行。畫面上出現要做的事,你按允許才繼續。付款、寄出、刪除這類動作,每次都會再問一次,即使你關掉了別的詢問。
<p align="center"> <p align="center">
<img src="shots/readme/ask.png" alt="Bot 要在你的電腦上執行指令前,先跳出允許一次、一律允許或拒絕" width="960"> <img src="shots/readme/computer-phone.png" alt="左:Bot 要在你的電腦上執行指令前,先跳出允許一次、一律允許或拒絕。右:手機瀏覽器打開你的網址,先登入" width="960">
</p> </p>
- **整理檔案、跑指令** — 做之前先問。也可以只讓它用「它自己的電腦」,一個跟你隔開的小桌面。 - **整理檔案、跑指令** — 做之前先問。也可以只讓它用「它自己的電腦」,一個跟你隔開的小桌面。
- **看網頁、填表** — 用它自己的瀏覽器。密碼和卡號由程式直接填,不會先念給模型聽。 - **看網頁、填表** — 用它自己的瀏覽器。密碼和卡號由程式直接填,不會先念給模型聽。
- **操作你正在用的畫面** — 看截圖上的按鈕再點。點不到,或是要你登入時,把畫面交給你。 - **操作你正在用的畫面** — 看截圖上的按鈕再點。點不到,或是要你登入時,把畫面交給你。
- **到點自己做** — 「每個工作日早上 9 點整理三則新聞」變成例行任務。 - **到點自己做** — 「每個工作日早上 9 點整理三則新聞」變成例行任務。
- **手機隨時插手** — 瀏覽器打開你的網址、登入,就是同一個聊天。 - **手機隨時插手** — 瀏覽器打開你的網址、登入,就是同一個聊天,在外面也能看它做到哪。
<p align="center">
<img src="shots/readme/phone.png" alt="手機瀏覽器打開你的網址,先登入" width="300">
</p>
<p align="center"> <p align="center">
<img src="shots/readme/routines.png" alt="跟 Bot 說每個工作日早上九點整理新聞,它就排好例行任務" width="960"> <img src="shots/readme/routines.png" alt="跟 Bot 說每個工作日早上九點整理新聞,它就排好例行任務" width="960">
@ -161,7 +157,6 @@ flowchart TD
| 信任邊界、金鑰放哪 | [SECURITY.md](SECURITY.md) | | 信任邊界、金鑰放哪 | [SECURITY.md](SECURITY.md) |
| 功能一條條清單 | [FEATURES.md](FEATURES.md) | | 功能一條條清單 | [FEATURES.md](FEATURES.md) |
| 打包、自動建置 | [docs/release.md](docs/release.md) | | 打包、自動建置 | [docs/release.md](docs/release.md) |
| 改這份 README(不跑版的寫法) | [docs/readme-style.md](docs/readme-style.md) |
英文摘要在 `docs/en/`。 英文摘要在 `docs/en/`。

View File

@ -1,19 +1,20 @@
# Demo media # Demo media
README stills in `shots/readme/` are taken from the current build, not from `shots/` (those are older comparisons). README 用的圖(`shots/readme/`)一律從**目前這版程式**重拍,不要用 `shots/`(那是舊的對比圖)。
Capture at 2x, then downscale to 960 px wide: the README image is 960 px, so a 1920 px capture stays sharp on a Retina screen and does not overflow the 980 px document column. 統一用深色(`ANYBOT_SHOT_DARK=1`)、照 2x 拍、再壓到 960px 寬:README 的圖就是 960px,這樣在 Retina 上清楚,也不會撐破文件欄(約 980px)。
## 拍桌面
```sh ```sh
cargo build --bin anybot cargo build --bin anybot
export ANYBOT_DATA_DIR=/tmp/anybot-readme-shot ANYBOT_SHOT_FRAMES=48 export ANYBOT_DATA_DIR=/tmp/anybot-readme-shot ANYBOT_SHOT_FRAMES=48 \
ANYBOT_SHOT_SIZE=1920x1200 ANYBOT_SHOT_DARK=1
# 1) shoot at 2x (1920x1200) ANYBOT_SHOT_VIEW=chat ANYBOT_SHOT_NOPOPUP=1 target/debug/anybot --screenshot /tmp/chat.png
ANYBOT_SHOT_SIZE=1920x1200 ANYBOT_SHOT_VIEW=chat ANYBOT_SHOT_NOPOPUP=1 target/debug/anybot --screenshot /tmp/chat.png target/debug/anybot --screenshot /tmp/ask.png # 預設示範資料:裡面就有允許卡片
ANYBOT_SHOT_SIZE=1920x1200 target/debug/anybot --screenshot /tmp/ask.png # the default demo view: the approval card is in it ANYBOT_SHOT_VIEW=routines ANYBOT_SHOT_NOPOPUP=1 target/debug/anybot --screenshot /tmp/routines.png
ANYBOT_SHOT_SIZE=1920x1200 ANYBOT_SHOT_VIEW=routines ANYBOT_SHOT_NOPOPUP=1 target/debug/anybot --screenshot /tmp/routines.png
# 2) downscale to 960 wide
python3 - <<'PY' python3 - <<'PY'
from PIL import Image from PIL import Image
for n in ('chat', 'ask', 'routines'): for n in ('chat', 'ask', 'routines'):
@ -22,9 +23,11 @@ for n in ('chat', 'ask', 'routines'):
PY PY
``` ```
`ANYBOT_SHOT_NOPOPUP=1` keeps the approval card away (a plain screen). The card shot is the default demo view, which selects the computer helper mid-task, so run that one with no `ANYBOT_SHOT_VIEW` and no `ANYBOT_SHOT_NOPOPUP`. `ANYBOT_SHOT_NOPOPUP=1` 把允許卡片壓掉(拍乾淨的畫面)。要卡片就**不要**加 `ANYBOT_SHOT_VIEW`,預設的示範資料會選到正在等允許的那個 Bot。
`shots/readme/phone.png` is the current `mobile/index.html` sign-in page, captured in a 390×844 browser and downscaled to 300 px wide. It is not the old collage in `shots/phone.png`. ## 拍手機
`shots/readme/phone.png` 是目前的 `mobile/index.html`,390×844 的瀏覽器截圖,壓到 300px 寬。不是舊的 `shots/phone.png` 那張拼圖。
```sh ```sh
python3 -m http.server 8766 --directory mobile & python3 -m http.server 8766 --directory mobile &
@ -34,19 +37,28 @@ kill %1
python3 -c "from PIL import Image; im=Image.open('/tmp/phone.png'); im.resize((300, round(im.size[1]*300/im.size[0])), Image.LANCZOS).save('shots/readme/phone.png', optimize=True)" python3 -c "from PIL import Image; im=Image.open('/tmp/phone.png'); im.resize((300, round(im.size[1]*300/im.size[0])), Image.LANCZOS).save('shots/readme/phone.png', optimize=True)"
``` ```
After any README image change, check the page does not overflow: see [../readme-style.md](../readme-style.md). 手機頁面預設跟著系統深淺色;README 的圖是深色,拍之前在那支手機(或 Chrome 的模擬偏好)設成深色。
The README shows a demo here. To record one: ## 電腦 + 手機那張合成圖
1. Start AnyBot (desktop) and a bot with a key; run at 1440p, hide notifications. README 中間用一張合成圖(左:桌面允許卡片,右:手機登入),手機才不會被单独摆在中间、被挤到一边。合成是程式做的,重拍後重跑一次:
2. Record a 20–40 s clip of one complete task, e.g. "organise the Downloads folder"
(chat → plan → tool steps → files moved) and one computer-use task
(bot opens the browser, fills a form, stops before Send — the approval card
popping up **is** the product).
3. Export GIF: `ffmpeg -i clip.mov -vf "fps=12,scale=960:-1:flags=lanczos,split[a][b];[a]palettegen[p];[b][p]paletteuse" docs/demo/anybot.gif`
(keep it under ~8 MB; if bigger, cut to one task).
4. Name it `docs/demo/anybot.gif`. The README comment shows where to un-comment
the `<img>` line.
Preferred: one GIF (desktop, computer-use with the approval card) + one 30 s ```sh
MP4 (`demo.mp4`) of the phone app pairing a new machine. python3 docs/demo/composite.py # shots/readme/{ask,phone}.png → computer-phone.png
```
改大小就編輯 `docs/demo/composite.py` 最上面的 `W / pad / gap / phone_w`。
## 動圖(還沒有)
README 上最好有一段會動的。要录:
1. 打開桌面 App、金鑰設好,螢幕 1440p,關掉通知。
2. 录 20–40 秒一件事從頭到尾,例如「整理下載資料夾」(聊天 → 計畫 → 工具步驟 → 檔案真的被搬動),加一段操作電腦(開瀏覽器、填表、在送出前停下——**允許卡片跳出來那一刻就是產品**)。
3. 輸出 GIF:`ffmpeg -i clip.mov -vf "fps=12,scale=960:-1:flags=lanczos,split[a][b];[a]palettegen[p];[b][p]paletteuse" docs/demo/anybot.gif`
(控制在 8 MB 內,太大就只留一件事)
4. 檔名 `docs/demo/anybot.gif`,README 註解裡有要放的位置。
偏好:一支 GIF(桌面 + 電腦操作 + 允許卡片)加一支 30 秒 MP4(`demo.mp4`,手機配對新電腦)。
改完圖,照 [../readme-style.md](../readme-style.md) 最後面那段檢查有沒有跑版。

39
docs/demo/composite.py Normal file
View File

@ -0,0 +1,39 @@
#!/usr/bin/env python3
"""Build the README's computer-and-phone still from the two raw shots.
Run docs/demo/README.md's capture steps first (dark, 2x), then:
python3 docs/demo/composite.py
"""
from PIL import Image, ImageDraw
from pathlib import Path
root = Path(__file__).resolve().parents[2]
shots = root / "shots" / "readme"
BG = (22, 22, 22) # the app's dark pane, so the canvas reads as one screen
desk = Image.open(shots / "ask.png").convert("RGB") # the approval card
phone = Image.open(shots / "phone.png").convert("RGB") # the sign-in page
W, pad, gap, phone_w = 960, 24, 24, 264
phone_h = round(phone.size[1] * phone_w / phone.size[0])
desk_w = W - 2 * pad - gap - phone_w
desk_h = round(desk.size[1] * desk_w / desk.size[0])
H = pad + max(phone_h, desk_h) + pad
def rounded(im, r):
mask = Image.new("L", im.size, 0)
ImageDraw.Draw(mask).rounded_rectangle([0, 0, *im.size], radius=r, fill=255)
out = Image.new("RGB", im.size, BG)
out.paste(im, (0, 0), mask)
return out, mask
canvas = Image.new("RGB", (W, H), BG)
im, mask = rounded(desk.resize((desk_w, desk_h), Image.LANCZOS), 12)
canvas.paste(im, (pad, pad + (phone_h - desk_h) // 2), mask)
im, mask = rounded(phone.resize((phone_w, phone_h), Image.LANCZOS), 18)
canvas.paste(im, (W - pad - phone_w, pad), mask)
out = shots / "computer-phone.png"
canvas.save(out, optimize=True)
print(out.relative_to(root), canvas.size)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 70 KiB