From 7d87dd5b3c146ab6ca70062a4efd86cc3479423d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=80=A7=E9=A9=8A?= Date: Fri, 4 Sep 2026 17:08:56 +0800 Subject: [PATCH] add new feat --- .env.example | 2 + Cargo.lock | 1 + Cargo.toml | 1 + README.md | 2 +- apps/web/package-lock.json | 35 +- apps/web/package.json | 3 +- apps/web/src/App.tsx | 245 ++++++++--- apps/web/src/avatar.tsx | 319 ++++++++++++++ apps/web/src/blobshape.d.ts | 3 - apps/web/src/locales/zh-TW.ts | 35 +- apps/web/src/main.tsx | 2 + apps/web/src/refinements.css | 87 ++-- apps/web/src/types.ts | 10 +- crates/api/src/auth.rs | 17 +- crates/api/src/computer.rs | 359 +++++++++++++--- crates/api/src/db.rs | 120 +++++- crates/api/src/main.rs | 20 +- crates/api/src/mcp.rs | 120 ++++-- crates/api/src/mcp_catalog.rs | 367 +++++++++++++++++ crates/api/src/memory.rs | 224 +++++++--- crates/api/src/rooms.rs | 79 +++- crates/api/src/routes.rs | 253 +++++++++--- crates/api/src/runs.rs | 640 ++++++++++++++++++++++------- crates/api/src/screen_proxy.rs | 29 +- crates/api/src/sessions.rs | 201 +++++---- crates/api/src/state.rs | 9 +- crates/api/src/tools.rs | 358 ++++++++++++++-- crates/api/src/workspace.rs | 234 +++++++++++ crates/contracts/src/action.rs | 30 +- crates/contracts/src/bot.rs | 2 +- crates/contracts/src/computer.rs | 6 +- crates/contracts/src/model.rs | 82 +++- crates/contracts/src/run.rs | 16 +- crates/control/Cargo.toml | 1 + crates/control/src/actions.rs | 109 ++++- crates/control/src/cdp.py | 362 ++++++++++++++++ crates/control/src/cdp.rs | 203 +++++++++ crates/control/src/lease.rs | 4 +- crates/control/src/lib.rs | 4 + crates/control/src/observe.rs | 53 ++- crates/control/src/overlay.rs | 267 ++++++++++++ crates/control/src/path.rs | 19 +- crates/control/src/sandbox.rs | 24 +- crates/control/src/screen.rs | 31 +- crates/control/src/takeover.rs | 27 +- crates/control/src/x11.rs | 165 +++++++- crates/controld/src/main.rs | 44 +- crates/harness/src/resolve.rs | 161 ++++++-- crates/sandbox/src/docker.rs | 50 ++- crates/sandbox/src/fake.rs | 17 +- crates/supervisor/src/docker.rs | 245 ++++++++--- crates/supervisor/src/main.rs | 36 +- docker-compose.yml | 2 + image/api/Dockerfile | 18 +- image/computer/Dockerfile | 2 +- image/computer/start.sh | 2 + migrations/009_workspace_model.sql | 3 + 57 files changed, 4922 insertions(+), 838 deletions(-) create mode 100644 apps/web/src/avatar.tsx delete mode 100644 apps/web/src/blobshape.d.ts create mode 100644 crates/api/src/mcp_catalog.rs create mode 100644 crates/api/src/workspace.rs create mode 100644 crates/control/src/cdp.py create mode 100644 crates/control/src/cdp.rs create mode 100644 crates/control/src/overlay.rs create mode 100644 migrations/009_workspace_model.sql diff --git a/.env.example b/.env.example index b797561..b8e088c 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,6 @@ XAI_API_KEY= +OPENCODE_GO_API_KEY= +OPENAI_API_KEY= # Generate independent values with: openssl rand -hex 32 LAZYBOY_APP_TOKEN= SANDBOX_SUPERVISOR_TOKEN= diff --git a/Cargo.lock b/Cargo.lock index 11baefc..9203251 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2060,6 +2060,7 @@ dependencies = [ "async-trait", "chrono", "hex", + "image", "lazyboy-contracts", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 284ab7f..3988c62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ sha2 = "0.10" hex = "0.4" base64 = "0.22" chrono = { version = "0.4", default-features = false, features = ["clock", "serde"] } +image = { version = "0.25", default-features = false, features = ["jpeg", "png"] } rig-core = "0.42" async-trait = "0.1" tokio = { version = "1", features = ["macros", "rt-multi-thread", "process", "io-util", "fs", "signal", "time"] } diff --git a/README.md b/README.md index 1530928..f27a97e 100644 --- a/README.md +++ b/README.md @@ -69,4 +69,4 @@ For frontend development, run `npm install && npm run dev` in `apps/web`, then o The standalone supervisor listens on `127.0.0.1:7091` by default. Docker Compose reaches it internally as `supervisor:7091`; there is intentionally no host port `7092`. -Model providers: v1 talks to xAI (`XAI_API_KEY`). `openai` / `anthropic` / `openrouter` are reserved on the factory and return `unsupported_provider`. +Model providers: set the workspace default in 本機工作區 → 設定. v1 supports xAI (`XAI_API_KEY`), OpenCode Go (`OPENCODE_GO_API_KEY` or a key saved in settings), and a self-hosted OpenAI-compatible endpoint (base URL + optional key). Keys saved in the UI are stored on the local workspace and take precedence over env vars. diff --git a/apps/web/package-lock.json b/apps/web/package-lock.json index ded63e5..33a1897 100644 --- a/apps/web/package-lock.json +++ b/apps/web/package-lock.json @@ -8,8 +8,9 @@ "name": "lazyboy-web", "version": "0.1.0", "dependencies": { + "@blobatar/react": "^2.7.0", "@fontsource/huninn": "^5.3.0", - "blobshape": "^1.0.0", + "blobatar": "^2.7.0", "react": "^19.0.0", "react-dom": "^19.0.0", "react-useanimations": "^2.10.0" @@ -304,6 +305,16 @@ "node": ">=6.9.0" } }, + "node_modules/@blobatar/react": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@blobatar/react/-/react-2.7.0.tgz", + "integrity": "sha512-rysDM1+2qoeIwIezVw/em+ZxOMyqqjqJOGNEvtOOU1nFp0jmzyEjlXtJTDM6xPKamq59pq6wcG9+mHIUZV572A==", + "license": "MIT", + "peerDependencies": { + "blobatar": "2.x", + "react": ">=18" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", @@ -1327,11 +1338,23 @@ "node": ">=6.0.0" } }, - "node_modules/blobshape": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/blobshape/-/blobshape-1.0.0.tgz", - "integrity": "sha512-GUD6tUIvj7k8zLSMtfdM1RyusWDjFqYL9g6J6QIgwxbVjL3b6mbRz5peerDc+6kbZX/14BSr3ed9sizrJKdkQQ==", - "license": "MIT" + "node_modules/blobatar": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/blobatar/-/blobatar-2.7.0.tgz", + "integrity": "sha512-tL1p2J3dXE/Z9U/ITwHqU7HBHmTH+Am+hif1J1hWAhvFT1VYC2HG+eXtN7081yuY+L2lrp9bs/VaF7dVH3S2jw==", + "license": "MIT", + "peerDependencies": { + "react": ">=18", + "vue": ">=3" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "vue": { + "optional": true + } + } }, "node_modules/browserslist": { "version": "4.28.8", diff --git a/apps/web/package.json b/apps/web/package.json index 23632b1..c960204 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -9,8 +9,9 @@ "typecheck": "tsc --noEmit --pretty false" }, "dependencies": { + "@blobatar/react": "^2.7.0", "@fontsource/huninn": "^5.3.0", - "blobshape": "^1.0.0", + "blobatar": "^2.7.0", "react": "^19.0.0", "react-dom": "^19.0.0", "react-useanimations": "^2.10.0" diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index a197d41..614e0d6 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -16,39 +16,37 @@ import visibility from "react-useanimations/lib/visibility"; import visibility2 from "react-useanimations/lib/visibility2"; import searchToX from "react-useanimations/lib/searchToX"; import { api, ApiError } from "./api"; -import { t } from "./i18n"; -import blobshape from "blobshape"; -import type { AvatarShape, Bot, ComputerMode, ComputerStatus, McpServer, McpTransport, MemoryItem, Message, Room, RoomMember, Session } from "./types"; +import { Avatar, AvatarLookProvider, AvatarStack, BLOBATAR_BACKGROUNDS, BLOBATAR_EXPRESSIONS, BLOBATAR_SHAPES, DEFAULT_LOOK, persistBlobatarShape, readAvatarLooks, resolveBlobatarShape, writeAvatarLook, type AvatarBackground, type AvatarExpression, type AvatarLook } from "./avatar"; +import { t, type MessageKey } from "./i18n"; +import type { AvatarShape, Bot, ComputerMode, ComputerStatus, McpCatalogEntry, McpServer, McpTransport, MemoryItem, Message, ModelProviderId, Room, RoomMember, Session, WorkspaceSettings } from "./types"; const blankComputer:ComputerStatus={botId:"",mode:"team",state:"stopped",controlHolder:"none",takeoverRequested:false,busyBotName:null,busySessionId:null,busyRunId:null,display:null,profileMode:"per-bot",screenAvailable:false}; const SESSION_STORE="lazyboy.sessionByBot"; const PANE_STORE="lazyboy.rightPane"; const WORKSPACE_STORE="lazyboy.workspace"; type RightPart="computer"|"memory"|"settings"|"plugins"; -type AccountDialog="phone"|"settings"|"about"|"help"|"feedback"|null; +type AccountDialog="phone"|"settings"|"model"|"about"|"help"|"feedback"|null; function readSessionStore():Record{try{const raw=localStorage.getItem(SESSION_STORE);return raw?JSON.parse(raw) as Record:{}}catch{return {}}} function writeSessionStore(botId:string,sessionId:string){const store=readSessionStore();store[botId]=sessionId;localStorage.setItem(SESSION_STORE,JSON.stringify(store))} function readPaneStore():{collapsed:boolean;part:RightPart}{try{const raw=localStorage.getItem(PANE_STORE);if(!raw)return{collapsed:false,part:"computer"};const value=JSON.parse(raw) as {collapsed?:boolean;part?:string};return{collapsed:Boolean(value.collapsed),part:value.part==="memory"||value.part==="settings"||value.part==="plugins"?value.part:"computer"}}catch{return{collapsed:false,part:"computer"}}} type WorkspacePrefs={name:string;showHidden:boolean}; function readWorkspace():WorkspacePrefs{try{const raw=localStorage.getItem(WORKSPACE_STORE);if(!raw)return{name:t("localWorkspace"),showHidden:false};const value=JSON.parse(raw) as {name?:string;showHidden?:boolean};const name=value.name?.trim();return{name:name&&name!=="Local workspace"?name:t("localWorkspace"),showHidden:Boolean(value.showHidden)}}catch{return{name:t("localWorkspace"),showHidden:false}}} -const botColors=["#3ec5a8","#f5a03c","#6a6bf5","#9b5cf6","#3b82f6","#d9508a"]; -function Avatar({name,color,shape="blob",active=false,thinking=false,online=false,size=32}:{name:string;color?:string;shape?:AvatarShape;active?:boolean;thinking?:boolean;online?:boolean;size?:number}){const hash=[...`${name}-${shape}`].reduce((n,c)=>(n*31+c.charCodeAt(0))>>>0,7);const resolved=color||botColors[hash%botColors.length];const generated=shape==="blob"||shape.startsWith("organic-");const edges=shape==="blob"?7:Number(shape.slice(8));const generatedPath=generated?blobshape({size:100,growth:shape==="blob"?8:6,edges,seed:hash||1}).path:null;const specialPath=shape==="cloud"?"M23 80C9 80 2 70 7 57C10 48 18 44 27 45C29 30 40 21 53 24C63 25 70 32 72 43C87 42 96 51 95 64C94 75 86 81 73 80C67 88 56 90 48 84C39 91 28 89 23 80Z":shape==="drop"?"M52 5C44 20 18 44 18 65C18 83 32 95 50 95C69 95 83 82 82 64C81 43 60 21 52 5Z":shape==="cat"?"M22 42C16 36 14 20 17 8C28 14 38 22 42 28C47 26.5 53 26.5 58 28C62 22 72 14 83 8C86 20 84 36 78 42C86 50 90 58 90 64C90 82 73 94 50 94C27 94 10 82 10 64C10 58 14 50 22 42Z":shape==="bunny"?"M38 34C32 30 28 18 30 6C40 8 46 20 47 32C49 31 51 31 53 32C54 20 60 8 70 6C72 18 68 30 62 34C76 42 84 54 84 66C84 84 69 94 50 94C31 94 16 84 16 66C16 54 24 42 38 34Z":shape==="star"?"M50 8L62 38L94 40L69 60L77 91L50 74L23 91L31 60L6 40L38 38Z":shape==="heart"?"M50 90C30 74 10 58 10 38C10 22 22 12 35 12C43 12 48 16 50 22C52 16 57 12 65 12C78 12 90 22 90 38C90 58 70 74 50 90Z":shape==="egg"?"M50 6C32 6 16 34 16 58C16 80 31 94 50 94C69 94 84 80 84 58C84 34 68 6 50 6Z":shape==="ghost"?"M50 6C29 6 15 24 15 46L15 82C15 86 18 89 22 89C26 89 28 85 32 85C36 85 38 89 42 89C46 89 48 85 52 85C56 85 58 89 62 89C66 89 68 85 72 85C76 85 78 89 82 89C86 89 89 86 89 82L89 46C89 24 75 6 50 6Z":shape==="sprout"?"M50 36C48 20 38 10 24 10C26 26 36 34 48 36C30 38 18 52 18 66C18 84 32 94 50 94C68 94 82 84 82 66C82 52 70 38 52 36C64 34 74 26 76 10C62 10 52 20 50 36Z":shape==="cactus"?"M50 10C38 10 28 18 28 30L28 58L20 58C15 58 12 61 12 66C12 71 15 74 20 74L28 74L28 78C28 89 38 96 50 96C62 96 72 89 72 78L72 64L80 64C85 64 88 61 88 56C88 51 85 48 80 48L72 48L72 30C72 18 62 10 50 10Z":shape==="mushroom"?"M50 12C28 12 12 28 12 46C12 51 15 54 21 54L38 54C37 59 38 64 38 68L38 82C38 90 42 94 50 94C58 94 62 90 62 82L62 68C62 64 63 59 62 54L79 54C85 54 88 51 88 46C88 28 72 12 50 12Z":shape==="paw"?"M8 40A9 9 0 1 0 26 40A9 9 0 1 0 8 40Z M24.5 27A10.5 10.5 0 1 0 45.5 27A10.5 10.5 0 1 0 24.5 27Z M54.5 27A10.5 10.5 0 1 0 75.5 27A10.5 10.5 0 1 0 54.5 27Z M74 40A9 9 0 1 0 92 40A9 9 0 1 0 74 40Z M20 66A29 22 0 1 0 78 66A29 22 0 1 0 20 66Z":null;const path=generatedPath||specialPath;const svgShape=Boolean(path);return {path&&}{(online||active)&&