diff --git a/README.md b/README.md index d60d839..bb43f0a 100644 --- a/README.md +++ b/README.md @@ -110,30 +110,59 @@ Open [http://127.0.0.1:5173](http://127.0.0.1:5173). Tests and the tree layout a The guides below are currently in Traditional Chinese. -| Doc | Contents | -| --- | --- | -| [Architecture](./docs/architecture.md) | Task flow, system architecture, computer lifecycle | -| [Interactive diagram](./docs/workflow.html) | Zoomable, searchable HTML chart; download and open | -| [Operations](./docs/operations.md) | Resources, env vars, security, site checks, sudo | -| [Agent experience](./docs/agent-experience.md) | Turn limits, persistent terminal, live chat | +| Doc | Contents | +| -------------------------------------------------------- | ---------------------------------------------------- | +| [Architecture](./docs/architecture.md) | Task flow, system architecture, computer lifecycle | +| [Interactive diagram](./docs/workflow.html) | Zoomable, searchable HTML chart; download and open | +| [Operations](./docs/operations.md) | Resources, env vars, security, site checks, sudo | +| [Agent experience](./docs/agent-experience.md) | Turn limits, persistent terminal, live chat | | [hermes-agent review](./docs/hermes-agent-cua-review.md) | Cua harness smoothness: comparison with hermes-agent | -| [Development](./docs/development.md) | Local dev, checks and tests, directory layout | -| [Env example](./.env.example) | Environment variables and defaults | +| [Development](./docs/development.md) | Local dev, checks and tests, directory layout | +| [Env example](./.env.example) | Environment variables and defaults | ## Data Chats, memory, browser profiles, and encrypted credentials stay on your host. When you use an external model, the prompts, tool results, and screenshots the task needs may still be sent to that provider. +## Acknowledgements + +LazyBoy is mostly other people's software, carefully assembled. The projects we lean on hardest: + +**Special thanks** + +- **[Cua](https://github.com/trycua/cua)** — the Linux desktop driver behind every click, keystroke, and screenshot. We rebuild `cua-driver-rs` v0.23.2 from source with two small patches kept in this repo: one lets the agent cursor wear the bot's own colour, the other swaps the embedded Latin-only badge font for [jf open Huninn](https://github.com/justfont/open-huninn-font) so Chinese renders. Neither patch touches input handling or permissions. +- **[hermes-agent](https://github.com/NousResearch/hermes-agent)** — not a dependency, but the reference we kept returning to while tuning how smooth an agent's desktop should feel. The comparison is written up in the [hermes-agent review](./docs/hermes-agent-cua-review.md). + +**Agents and retrieval** — [rig](https://github.com/0xPlaygrounds/rig) · [rmcp](https://github.com/modelcontextprotocol/rust-sdk) · [fastembed-rs](https://github.com/Anush008/fastembed-rs) and [ONNX Runtime](https://github.com/microsoft/onnxruntime), running [paraphrase-multilingual-MiniLM-L12-v2](https://huggingface.co/sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2) + +**Rust** — Tokio · Axum · tower-http · SQLx · reqwest · rustls · Bollard · tracing · aes-gcm and hmac from [RustCrypto](https://github.com/RustCrypto) · cap-std · cron · chrono · uuid · thiserror · dotenvy + +**Web** — React · Vite · TypeScript · [noVNC](https://github.com/novnc/noVNC) · react-markdown with [remark-gfm](https://github.com/remarkjs/remark-gfm) and [remark-breaks](https://github.com/remarkjs/remark-breaks) · [Blobatar](https://github.com/Alain00/blobatar) avatars · [react-useanimations](https://github.com/useAnimations/react-useanimations) icons + +**Data** — [PostgreSQL](https://www.postgresql.org) · [pgvector](https://github.com/pgvector/pgvector) + +**The desktop inside each container** — [Docker](https://www.docker.com) · [Debian](https://www.debian.org) · [XFCE](https://www.xfce.org) · [Chromium](https://www.chromium.org) · [Xvfb](https://www.x.org) · Thunar · [x11vnc](https://github.com/LibVNC/x11vnc) · [websockify](https://github.com/novnc/websockify) · [AT-SPI2](https://gitlab.gnome.org/GNOME/at-spi2-core) · [gosu](https://github.com/tianon/gosu) · [LXCFS](https://github.com/lxc/lxcfs) · git · zsh with [Powerlevel10k](https://github.com/romkatv/powerlevel10k) · htop + +**Type and theme** — [jf open Huninn](https://github.com/justfont/open-huninn-font) (SIL OFL) in the UI and on the agent badge · [Noto CJK](https://github.com/notofonts/noto-cjk), DejaVu, and Liberation as fallbacks · [MesloLGS NF](https://github.com/romkatv/powerlevel10k-media) in the terminal · [Arc Dark](https://github.com/horst3180/arc-theme) and [Papirus](https://github.com/PapirusDevelopmentTeam/papirus-icon-theme) for the look of the room + +Every version above is pinned in `Cargo.lock`, `apps/web/package.json`, and `image/computer/Dockerfile`. LazyBoy is Apache-2.0; each project keeps its own license. Thanks also to the maintainers whose names never make it into a README, and to everyone who files a good bug report. + ---
+LazyBoy + +**LazyBoy is free and open source.** If it hands you back an afternoon, a coffee is the nicest way to say so. + +Buy Me a Coffee + +Apache License 2.0 +Release 0.1.0 +Rust and React + **Daniel Wang** Taiwan -[igs170911@gmail.com](mailto:igs170911@gmail.com) - -Buy Me a Coffee - -[Apache License 2.0](./LICENSE) +[igs170911@gmail.com](mailto:igs170911@gmail.com) · [Apache License 2.0](./LICENSE)
diff --git a/README.zh-TW.md b/README.zh-TW.md index 0b47239..4054d5f 100644 --- a/README.zh-TW.md +++ b/README.zh-TW.md @@ -14,7 +14,7 @@ -![LazyBoy 群組對話:訊息由其中一個 Agent 接手回覆,右側是它自己的即時桌面](./docs/readme-hero.png) +![LazyBoy 群組對話:訊息由其中一個 Agent 接手回覆,右側是它自己的即時桌面](./docs/readme-hero.zh-TW.png) LazyBoy 讓 Agent 在 Docker 裡使用自己的 Linux 桌面,操作瀏覽器、終端與檔案。你可以建立不同的 Agent、在群組中協作,把示範整理成技能,再安排定時執行。 @@ -122,16 +122,45 @@ npm run dev 對話、記憶、瀏覽器設定檔與加密憑證保存在自架主機。使用外部模型時,任務所需的提示詞、工具結果與截圖仍可能傳送給該模型供應商。 +## 特別感謝 + +LazyBoy 站在許多開源專案的肩上,下面是我們用到最多、也最想致謝的專案。 + +**首要致謝** + +- **[Cua](https://github.com/trycua/cua)**:每一次點擊、鍵盤輸入與截圖,都由它的 Linux 桌面驅動完成。我們從原始碼重建 `cua-driver-rs` v0.23.2,並在本專案內維護兩處小修補:一處讓 Agent 游標套用機器人自己的顏色,另一處把內嵌的純英文徽章字型換成 [jf open Huninn](https://github.com/justfont/open-huninn-font),讓中文能正確顯示。兩處修補都不碰輸入處理與權限邏輯。 +- **[hermes-agent](https://github.com/NousResearch/hermes-agent)**:它不是相依套件,卻是我們調整操作流暢度時一再回頭參照的對象。對照分析見 [hermes-agent 比較](./docs/hermes-agent-cua-review.md)。 + +**Agent 與記憶檢索**:[rig](https://github.com/0xPlaygrounds/rig) · [rmcp](https://github.com/modelcontextprotocol/rust-sdk) · [fastembed-rs](https://github.com/Anush008/fastembed-rs) 與 [ONNX Runtime](https://github.com/microsoft/onnxruntime),嵌入模型為 [paraphrase-multilingual-MiniLM-L12-v2](https://huggingface.co/sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2) + +**Rust**:Tokio · Axum · tower-http · SQLx · reqwest · rustls · Bollard · tracing · [RustCrypto](https://github.com/RustCrypto) 的 aes-gcm 與 hmac · cap-std · cron · chrono · uuid · thiserror · dotenvy + +**前端**:React · Vite · TypeScript · [noVNC](https://github.com/novnc/noVNC) · react-markdown 搭配 [remark-gfm](https://github.com/remarkjs/remark-gfm) 與 [remark-breaks](https://github.com/remarkjs/remark-breaks) · [Blobatar](https://github.com/Alain00/blobatar) 頭像 · [react-useanimations](https://github.com/useAnimations/react-useanimations) 圖示 + +**資料**:[PostgreSQL](https://www.postgresql.org) · [pgvector](https://github.com/pgvector/pgvector) + +**容器裡的桌面**:[Docker](https://www.docker.com) · [Debian](https://www.debian.org) · [XFCE](https://www.xfce.org) · [Chromium](https://www.chromium.org) · [Xvfb](https://www.x.org) · Thunar · [x11vnc](https://github.com/LibVNC/x11vnc) · [websockify](https://github.com/novnc/websockify) · [AT-SPI2](https://gitlab.gnome.org/GNOME/at-spi2-core) · [gosu](https://github.com/tianon/gosu) · [LXCFS](https://github.com/lxc/lxcfs) · git · 搭配 [Powerlevel10k](https://github.com/romkatv/powerlevel10k) 的 zsh · htop + +**字型與佈景主題**:介面與 Agent 徽章使用 [jf open Huninn](https://github.com/justfont/open-huninn-font)(SIL OFL)· 備援字型為 [Noto CJK](https://github.com/notofonts/noto-cjk)、DejaVu 與 Liberation · 終端機使用 [MesloLGS NF](https://github.com/romkatv/powerlevel10k-media) · 外觀採用 [Arc Dark](https://github.com/horst3180/arc-theme) 與 [Papirus](https://github.com/PapirusDevelopmentTeam/papirus-icon-theme) + +以上版本都釘選在 `Cargo.lock`、`apps/web/package.json` 與 `image/computer/Dockerfile`。LazyBoy 以 Apache-2.0 釋出,上述每個專案保留各自的授權條款。也謝謝那些名字從不出現在 README 裡的維護者,以及每一位認真回報問題的使用者。 + ---
+LazyBoy + +**LazyBoy 是免費的開源專案。** 如果它幫你省下一個下午,一杯咖啡是最好的回禮。 + +請我喝杯咖啡 + +Apache License 2.0 +Release 0.1.0 +Rust and React + **danielwang** Taiwan -[igs170911@gmail.com](mailto:igs170911@gmail.com) - -請我喝杯咖啡 - -[Apache License 2.0](./LICENSE) +[igs170911@gmail.com](mailto:igs170911@gmail.com) · [Apache License 2.0](./LICENSE)
diff --git a/docs/hero.html b/docs/hero.html index 3c1ff95..6a4214b 100644 --- a/docs/hero.html +++ b/docs/hero.html @@ -77,14 +77,11 @@ LazyBoy -
Self-hosted a Linux desktop per agent
+
-

Give an agent a real computer

-

- Assign work by text, voice, or a quick demo, then watch its own Linux desktop do it.
- Chats, memory, and desktops stay on your host. You choose the model. -

+

+

@@ -92,32 +89,63 @@
diff --git a/docs/hero/agent.png b/docs/hero/agent.png deleted file mode 100644 index 74194b1..0000000 Binary files a/docs/hero/agent.png and /dev/null differ diff --git a/docs/hero/desktop-en.png b/docs/hero/desktop-en.png new file mode 100644 index 0000000..77b2cb5 Binary files /dev/null and b/docs/hero/desktop-en.png differ diff --git a/docs/hero/desktop.png b/docs/hero/desktop-zh.png similarity index 51% rename from docs/hero/desktop.png rename to docs/hero/desktop-zh.png index c470eb6..5c8c1e0 100644 Binary files a/docs/hero/desktop.png and b/docs/hero/desktop-zh.png differ diff --git a/docs/hero/room-en.png b/docs/hero/room-en.png new file mode 100644 index 0000000..525220b Binary files /dev/null and b/docs/hero/room-en.png differ diff --git a/docs/hero/room-zh.png b/docs/hero/room-zh.png new file mode 100644 index 0000000..bfd5bbf Binary files /dev/null and b/docs/hero/room-zh.png differ diff --git a/docs/hero/room.png b/docs/hero/room.png deleted file mode 100644 index 286a621..0000000 Binary files a/docs/hero/room.png and /dev/null differ diff --git a/docs/readme-hero.png b/docs/readme-hero.png index 358f833..b099685 100644 Binary files a/docs/readme-hero.png and b/docs/readme-hero.png differ diff --git a/docs/readme-hero.zh-TW.png b/docs/readme-hero.zh-TW.png new file mode 100644 index 0000000..0bcde8c Binary files /dev/null and b/docs/readme-hero.zh-TW.png differ diff --git a/scripts/capture-hero.mjs b/scripts/capture-hero.mjs index a873166..4a7e77a 100644 --- a/scripts/capture-hero.mjs +++ b/scripts/capture-hero.mjs @@ -9,6 +9,11 @@ // node scripts/capture-hero.mjs --out docs/hero/room.png --pick 測試聊天 // node scripts/capture-hero.mjs --out docs/hero/desktop.png --selector .side-card // +// `--lang` pins the UI language so each README gets a banner and a screenshot +// in the same language; the app itself defaults to zh-TW. +// +// node scripts/capture-hero.mjs --lang en --out docs/hero/room-en.png --pick 測試聊天 +// // A capture that goes into the repository must not photograph whatever the // agent's browser happens to have open. `--hide` blanks a region (its layout // box stays, so nothing reflows) and `--anchor` prints where that region sits @@ -40,6 +45,7 @@ const config = { out: option('out', 'docs/hero/app.png'), pick: option('pick', ''), selector: option('selector', '.app-shell'), + lang: option('lang', 'zh-TW'), hide: option('hide', ''), anchor: option('anchor', ''), width: Number(option('width', 1480)), @@ -49,6 +55,10 @@ const config = { settle: Number(option('settle', 6000)), }; +if (!['zh-TW', 'en'].includes(config.lang)) { + throw new Error(`--lang must be zh-TW or en, got "${config.lang}"`); +} + function findChrome() { const candidates = [ process.env.CHROME, @@ -204,6 +214,13 @@ try { sessionId, ).then((result) => result.result?.value); if (status !== 200) throw new Error(`login failed with HTTP ${status}`); + // The app reads its locale once, at module init, so pin it before the reload + // that mounts the real view. + await connection.send( + 'Runtime.evaluate', + { expression: `localStorage.setItem('lazyboy.locale', ${JSON.stringify(config.lang)})` }, + sessionId, + ); await goto(`${config.base}/`); if (config.pick) { diff --git a/scripts/render-readme-hero.sh b/scripts/render-readme-hero.sh index af2b3bd..2ccc2fd 100755 --- a/scripts/render-readme-hero.sh +++ b/scripts/render-readme-hero.sh @@ -1,18 +1,23 @@ #!/usr/bin/env bash -# Render docs/hero.html to docs/readme-hero.png +# Render docs/hero.html into both README banners # # The banner only frames a real capture (see scripts/capture-hero.mjs), so this # step needs any headless Chromium: CHROME=/path/to/chrome, a browser on PATH, # the macOS Brave/Chrome install, or a Playwright download. # -# scripts/render-readme-hero.sh # the group shot (docs/hero/room.png) -# SHOT=agent scripts/render-readme-hero.sh # the single-agent shot +# Each language gets its own frame copy and its own screenshot, so the English +# README never shows a Chinese screen and the Chinese README never shows English. +# +# scripts/render-readme-hero.sh +# -> docs/readme-hero.png, docs/readme-hero.zh-TW.png +# HERO_LANG=en scripts/render-readme-hero.sh # one language only +# HERO_LANG=en OUT=/tmp/preview.png scripts/render-readme-hero.sh set -euo pipefail root="$(cd "$(dirname "$0")/.." && pwd)" html="$root/docs/hero.html" -out="${OUT:-$root/docs/readme-hero.png}" -shot="${SHOT:-room}" size="${SIZE:-1280,760}" +# Deliberately not LANG, which is the system locale and already set. +langs="${HERO_LANG:-en zh-TW}" browser="" for candidate in "${CHROME:-}" \ @@ -34,13 +39,22 @@ if [ -z "$browser" ]; then exit 1 fi -args=(--headless=new --disable-gpu --hide-scrollbars --force-device-scale-factor=2 - --window-size="$size" --screenshot="$out") -# Root needs the sandbox off; nowhere else does. -[ "$(id -u)" = "0" ] && args+=(--no-sandbox) -"$browser" "${args[@]}" "file://$html?shot=$shot" - -python3 - "$out" <<'PY' +for lang in $langs; do + case "$lang" in + zh-TW) name="readme-hero.zh-TW" ;; + en) name="readme-hero" ;; + *) echo "unknown HERO_LANG: $lang (expected: en, zh-TW)" >&2; exit 1 ;; + esac + out="${OUT:-$root/docs/$name.png}" + args=(--headless=new --disable-gpu --hide-scrollbars --force-device-scale-factor=2 + --window-size="$size" --screenshot="$out") + # Root needs the sandbox off; nowhere else does. + [ "$(id -u)" = "0" ] && args+=(--no-sandbox) + if ! noise="$("$browser" "${args[@]}" "file://$html?lang=$lang" 2>&1)"; then + echo "$noise" >&2 + exit 1 + fi + python3 - "$out" <<'PY' from pathlib import Path import struct, sys p = Path(sys.argv[1]) @@ -48,3 +62,4 @@ data = p.read_bytes() w, h = struct.unpack(">II", data[16:24]) print(f"wrote {p} ({w}x{h}, {p.stat().st_size} bytes)") PY +done