From 4ed1ab3b9158219628c7909fb3abd86048f50313 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 13:41:09 +0800 Subject: [PATCH 1/7] fix avator --- .gitignore | 1 + Cargo.lock | 117 ++++++ Makefile | 172 ++++++++ README.md | 33 ++ apps/web/package-lock.json | 32 +- apps/web/package.json | 26 +- apps/web/src/App.tsx | 424 ++++++++++++++++--- apps/web/src/animated-icons.tsx | 56 +++ apps/web/src/i18n.ts | 33 +- apps/web/src/locales/zh-TW.ts | 49 +++ apps/web/src/main.tsx | 3 +- apps/web/src/refinements.css | 212 +++++++++- apps/web/src/types.ts | 11 +- crates/api/Cargo.toml | 2 + crates/api/src/computer.rs | 27 +- crates/api/src/db.rs | 16 +- crates/api/src/main.rs | 8 +- crates/api/src/mcp.rs | 694 +++++++++++++++++++++++++++++++ crates/api/src/rooms.rs | 314 ++++++++++++++ crates/api/src/routes.rs | 6 +- crates/api/src/runs.rs | 140 +++++-- crates/api/src/sessions.rs | 162 +++++++- crates/api/src/state.rs | 3 + crates/api/src/tools.rs | 6 + crates/contracts/src/computer.rs | 2 + crates/contracts/src/lib.rs | 4 + crates/contracts/src/mcp.rs | 66 +++ crates/contracts/src/room.rs | 35 ++ crates/contracts/src/session.rs | 4 + migrations/007_group_rooms.sql | 27 ++ migrations/008_mcp_servers.sql | 20 + reference/rakazo | 2 +- 32 files changed, 2534 insertions(+), 173 deletions(-) create mode 100644 Makefile create mode 100644 apps/web/src/animated-icons.tsx create mode 100644 apps/web/src/locales/zh-TW.ts create mode 100644 crates/api/src/mcp.rs create mode 100644 crates/api/src/rooms.rs create mode 100644 crates/contracts/src/mcp.rs create mode 100644 crates/contracts/src/room.rs create mode 100644 migrations/007_group_rooms.sql create mode 100644 migrations/008_mcp_servers.sql diff --git a/.gitignore b/.gitignore index b615746..7c1fa46 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ image/computer/lazyboy-controld node_modules dist .DS_Store +.gstack/ diff --git a/Cargo.lock b/Cargo.lock index 0938bc5..11baefc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2020,6 +2020,7 @@ dependencies = [ "futures-util", "hex", "hmac", + "http", "http-body-util", "lazyboy-contracts", "lazyboy-control", @@ -2027,6 +2028,7 @@ dependencies = [ "lazyboy-sandbox", "reqwest 0.12.28", "rig-core", + "rmcp", "serde", "serde_json", "sha2", @@ -2423,6 +2425,18 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.13.1", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "no_std_io2" version = "0.9.4" @@ -2869,6 +2883,20 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "process-wrap" +version = "9.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e842efad9119158434d193c6682e2ebee4b44d6ad801d7b349623b3f57cdf55" +dependencies = [ + "futures", + "indexmap 2.14.1", + "nix", + "tokio", + "tracing", + "windows", +] + [[package]] name = "profiling" version = "1.0.18" @@ -3434,6 +3462,31 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rmcp" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b6914fac0be956fe704a38239c3f44a9f841d1b06a5713d2f638065593f5b5" +dependencies = [ + "base64 0.23.1", + "bytes", + "chrono", + "futures", + "http", + "indexmap 2.14.1", + "pin-project-lite", + "process-wrap", + "reqwest 0.13.4", + "serde", + "serde_json", + "sse-stream", + "thiserror", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", +] + [[package]] name = "rsa" version = "0.9.10" @@ -4141,6 +4194,19 @@ dependencies = [ "uuid", ] +[[package]] +name = "sse-stream" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c25ac7aff0abd1dbc474536e40416e1102c7dd9bfba0b9861c6d357f835dcfb4" +dependencies = [ + "bytes", + "futures-util", + "http-body", + "http-body-util", + "pin-project-lite", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -5084,6 +5150,27 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core", +] + [[package]] name = "windows-core" version = "0.62.2" @@ -5097,6 +5184,17 @@ dependencies = [ "windows-strings", ] +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + [[package]] name = "windows-implement" version = "0.60.2" @@ -5125,6 +5223,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core", + "windows-link", +] + [[package]] name = "windows-registry" version = "0.6.1" @@ -5212,6 +5320,15 @@ dependencies = [ "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6fb0f2d --- /dev/null +++ b/Makefile @@ -0,0 +1,172 @@ +# LazyBoy — developer Makefile +# Wraps docker compose + cargo so the whole stack can be built and launched +# with a few targets. See `make help`. + +.DEFAULT_GOAL := help + +COMPOSE ?= docker compose +COMPUTER_IMAGE ?= lazyboy/computer:local +WEB_DIR ?= apps/web +DATA_DIR ?= ./data + +.PHONY: help env env-force \ + up logs ps health down purge \ + computer postgres postgres-down \ + build build-api build-supervisor build-controld \ + fmt clippy test clean \ + web \ + dev dev-supervisor dev-api + +help: ## Show this help + @echo "LazyBoy — make targets" + @echo "" + @echo " Full stack (recommended, everything in Docker):" + @echo " make env Create .env with generated tokens (idempotent)" + @echo " make up Build all images + start postgres/computer/supervisor/api" + @echo " make health Curl the API health endpoint" + @echo " make logs Tail container logs" + @echo " make ps Show container status" + @echo " make down Stop + remove containers (keep postgres data)" + @echo " make purge Stop + remove containers AND the postgres volume" + @echo "" + @echo " Individual pieces:" + @echo " make computer Build the heavy Debian desktop image (lazyboy/computer:local)" + @echo " make postgres Start only postgres (127.0.0.1:5434) and wait for ready" + @echo " make postgres-down Stop postgres" + @echo "" + @echo " Local dev (postgres in Docker, Rust services on the host):" + @echo " make dev Prep .env + postgres + computer image, then print run steps" + @echo " make dev-supervisor Run lazyboy-supervisor on the host (terminal 1)" + @echo " make dev-api Run lazyboy-api on the host (terminal 2)" + @echo "" + @echo " Rust / web:" + @echo " make build cargo build --release (whole workspace)" + @echo " make build-api cargo build --release -p lazyboy-api" + @echo " make fmt cargo fmt --all" + @echo " make clippy cargo clippy (deny warnings)" + @echo " make test cargo test --workspace" + @echo " make web Build the frontend in $(WEB_DIR) (needs node/npm)" + @echo " make clean cargo clean" + @echo "" + @echo " After 'make up': open http://127.0.0.1:3101 and sign in with LAZYBOY_APP_TOKEN." + @echo " Set XAI_API_KEY in .env for chat." + +# --- Environment ----------------------------------------------------------- + +env: ## Create .env from example with fresh random tokens (no-op if .env exists) + @if [ -f .env ]; then \ + echo ".env already exists — leaving it untouched (use 'make env-force' to regenerate tokens)."; \ + else \ + cp .env.example .env; \ + APP=$$(openssl rand -hex 32); SUP=$$(openssl rand -hex 32); \ + sed "s|^LAZYBOY_APP_TOKEN=.*|LAZYBOY_APP_TOKEN=$$APP|" .env > .env.tmp; \ + sed "s|^SANDBOX_SUPERVISOR_TOKEN=.*|SANDBOX_SUPERVISOR_TOKEN=$$SUP|" .env.tmp > .env.tmp2; \ + mv .env.tmp2 .env; rm -f .env.tmp; \ + echo "created .env with generated LAZYBOY_APP_TOKEN / SANDBOX_SUPERVISOR_TOKEN (64 hex chars)."; \ + echo "next: set XAI_API_KEY in .env, then run 'make up'."; \ + fi + +env-force: ## Regenerate .env (wipes XAI_API_KEY — you will set it again) + @cp .env.example .env; \ + APP=$$(openssl rand -hex 32); SUP=$$(openssl rand -hex 32); \ + sed "s|^LAZYBOY_APP_TOKEN=.*|LAZYBOY_APP_TOKEN=$$APP|" .env > .env.tmp; \ + sed "s|^SANDBOX_SUPERVISOR_TOKEN=.*|SANDBOX_SUPERVISOR_TOKEN=$$SUP|" .env.tmp > .env.tmp2; \ + mv .env.tmp2 .env; rm -f .env.tmp; \ + echo "regenerated .env tokens (XAI_API_KEY reset — set it again)." + +# --- Full Docker stack ----------------------------------------------------- + +up: env ## Build every image and start the whole stack in Docker + @echo "building images + starting stack (first run is slow: builds desktop + rust + web)..." + $(COMPOSE) up -d --build + @echo "" + @echo "stack launched. open http://127.0.0.1:3101 and sign in with LAZYBOY_APP_TOKEN." + $(COMPOSE) ps + +logs: ## Tail logs for all services + $(COMPOSE) logs -f + +ps: ## Show container status + $(COMPOSE) ps + +health: ## Check the API health endpoint + @curl -fsS http://127.0.0.1:3101/api/health && echo " <- ok" + +down: ## Stop and remove containers (keeps postgres volume) + $(COMPOSE) down + +purge: ## Stop containers and delete the postgres data volume + $(COMPOSE) down -v + +# --- Individual pieces ----------------------------------------------------- + +computer: ## Build the Debian desktop image used to spawn bot computers + docker build -f image/computer/Dockerfile -t $(COMPUTER_IMAGE) . + +postgres: ## Start only postgres and wait until it is ready + $(COMPOSE) up -d postgres + @echo "waiting for postgres (127.0.0.1:5434) to be ready..." + @for i in $$(seq 1 40); do if $(COMPOSE) exec -T postgres pg_isready -U lazyboy >/dev/null 2>&1; then echo "postgres ready"; break; fi; sleep 0.5; done + +postgres-down: ## Stop postgres + $(COMPOSE) down postgres + +# --- Rust / web ------------------------------------------------------------ + +build: ## Release build of the whole workspace + cargo build --release + +build-api: ## Release build of the API binary + cargo build --release -p lazyboy-api + +build-supervisor: ## Release build of the supervisor binary + cargo build --release -p lazyboy-supervisor + +build-controld: ## Release build of the controld binary + cargo build --release -p lazyboy-controld + +fmt: ## Format all Rust code + cargo fmt --all + +clippy: ## Run clippy, denying warnings + cargo clippy --all-targets -- -D warnings + +test: ## Run the test suite + cargo test --workspace + +web: ## Build the frontend (needs node/npm) + cd $(WEB_DIR) && npm install && npm run build + +clean: ## Remove cargo build artifacts + cargo clean + +# --- Local dev (postgres in Docker, Rust on the host) ---------------------- + +dev: env postgres computer ## Prep local dev, then print the two run commands + @echo "" + @echo "prep complete. run these in TWO terminals:" + @echo " terminal 1: make dev-supervisor" + @echo " terminal 2: make dev-api" + @echo "then open http://127.0.0.1:3101" + +dev-supervisor: ## Run lazyboy-supervisor on the host (terminal 1) + @test -f .env || { echo "run 'make env' first"; exit 1; } + @set -a; . ./.env; set +a; \ + export DATA_DIR="$${DATA_DIR:-$$PWD/data}"; mkdir -p "$$DATA_DIR"; \ + export SUPERVISOR_BIND="$${SUPERVISOR_BIND:-127.0.0.1:7091}"; \ + export LAZYBOY_COMPUTER_IMAGE="$${LAZYBOY_COMPUTER_IMAGE:-$(COMPUTER_IMAGE)}"; \ + echo "starting supervisor on $$SUPERVISOR_BIND (log: stderr)"; \ + exec cargo run -p lazyboy-supervisor + +dev-api: ## Run lazyboy-api on the host (terminal 2) + @test -f .env || { echo "run 'make env' first"; exit 1; } + @set -a; . ./.env; set +a; \ + export DATABASE_URL="$${DATABASE_URL:-postgres://lazyboy:lazyboy@127.0.0.1:5434/lazyboy}"; \ + export SANDBOX_PROVIDER="$${SANDBOX_PROVIDER:-docker}"; \ + export SANDBOX_SUPERVISOR_URL="$${SANDBOX_SUPERVISOR_URL:-http://127.0.0.1:7091}"; \ + export DATA_DIR="$${DATA_DIR:-$$PWD/data}"; \ + export API_BIND="$${API_BIND:-0.0.0.0:3101}"; \ + export LAZYBOY_WEB_DIR="$${LAZYBOY_WEB_DIR:-$$PWD/$(WEB_DIR)}"; \ + mkdir -p "$$DATA_DIR"; \ + echo "starting api on $$API_BIND (web dir $$LAZYBOY_WEB_DIR)"; \ + exec cargo run -p lazyboy-api diff --git a/README.md b/README.md index 8316160..1530928 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,39 @@ Create a bot in the browser, give it a Team or Private computer, and let it drive a Linux desktop. +## Run (full Docker, recommended) + +Everything (postgres, desktop image build, supervisor, API + web UI) runs in +Docker Compose, driven by the Makefile: + +```bash +make env # creates .env with two generated 64-hex tokens (no-op if .env exists) +# then set XAI_API_KEY in .env +make up # builds all images and starts the stack +make health +``` + +Open `http://127.0.0.1:3101` and sign in with `LAZYBOY_APP_TOKEN`. + +Useful targets (see `make help`): + +| target | what it does | +| --- | --- | +| `make up` / `make down` / `make purge` | start / stop (keep data) / stop + delete postgres volume | +| `make logs`, `make ps`, `make health` | observe the stack | +| `make computer` | build only the heavy desktop image `lazyboy/computer:local` | +| `make postgres`, `make postgres-down` | start/stop just the database on `127.0.0.1:5434` | +| `make build`, `make fmt`, `make clippy`, `make test` | cargo workspace tasks | +| `make web` | build `apps/web` with npm (needs node) | +| `make dev`, `make dev-supervisor`, `make dev-api` | local dev: postgres in Docker, Rust services on the host in two terminals | + +Without make, the full-stack equivalent is: + +```bash +cp .env.example .env # fill in the two tokens + XAI_API_KEY +docker compose up -d --build +``` + ## Run (local) Postgres is on `127.0.0.1:5434` so it does not collide with other stacks. diff --git a/apps/web/package-lock.json b/apps/web/package-lock.json index 8beccd5..ded63e5 100644 --- a/apps/web/package-lock.json +++ b/apps/web/package-lock.json @@ -10,9 +10,9 @@ "dependencies": { "@fontsource/huninn": "^5.3.0", "blobshape": "^1.0.0", - "lucide-react": "^0.468.0", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "react-useanimations": "^2.10.0" }, "devDependencies": { "@types/react": "^19.0.0", @@ -1555,6 +1555,12 @@ "node": ">=6" } }, + "node_modules/lottie-web": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/lottie-web/-/lottie-web-5.13.0.tgz", + "integrity": "sha512-+gfBXl6sxXMPe8tKQm7qzLnUy5DUPJPKIyRHwtpCpyUEYjHYRJC/5gjUvdkuO2c3JllrPtHXH5UJJK8LRYl5yQ==", + "license": "MIT" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -1565,15 +1571,6 @@ "yallist": "^3.0.2" } }, - "node_modules/lucide-react": { - "version": "0.468.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.468.0.tgz", - "integrity": "sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA==", - "license": "ISC", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -1690,6 +1687,19 @@ "node": ">=0.10.0" } }, + "node_modules/react-useanimations": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/react-useanimations/-/react-useanimations-2.10.0.tgz", + "integrity": "sha512-MzGNv8vkvb6qEvMBCj+O6nUloUHSJRubMAH3uE7J4M+pjt5ud5xDaXBrQgv5GbvBg29XzviKWHTfvvkofDEu+Q==", + "license": "MIT", + "dependencies": { + "lottie-web": "^5.5.7" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/rollup": { "version": "4.63.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.1.tgz", diff --git a/apps/web/package.json b/apps/web/package.json index 4cf56aa..23632b1 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1 +1,25 @@ -{"name":"lazyboy-web","private":true,"version":"0.1.0","type":"module","scripts":{"dev":"vite --host 0.0.0.0","build":"tsc --noEmit && vite build","typecheck":"tsc --noEmit --pretty false"},"dependencies":{"@fontsource/huninn":"^5.3.0","blobshape":"^1.0.0","lucide-react":"^0.468.0","react":"^19.0.0","react-dom":"^19.0.0"},"devDependencies":{"@types/react":"^19.0.0","@types/react-dom":"^19.0.0","@vitejs/plugin-react":"^4.3.4","typescript":"^5.7.2","vite":"^6.0.7"}} \ No newline at end of file +{ + "name": "lazyboy-web", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite --host 0.0.0.0", + "build": "tsc --noEmit && vite build", + "typecheck": "tsc --noEmit --pretty false" + }, + "dependencies": { + "@fontsource/huninn": "^5.3.0", + "blobshape": "^1.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-useanimations": "^2.10.0" + }, + "devDependencies": { + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "^5.7.2", + "vite": "^6.0.7" + } +} diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index f765625..a197d41 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -1,112 +1,408 @@ -import { FormEvent, useCallback, useEffect, useMemo, useState } from "react"; -import { ArrowUp, Bot as BotIcon, Brain, ChevronDown, Clipboard, ClipboardPaste, Computer, Ellipsis, Eye, EyeOff, FolderPlus, LoaderCircle, LogOut, Mail, Menu, Pin, Plus, Search, Settings, Square, Trash2, Users, X } from "lucide-react"; +import { FormEvent, useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { BotIcon, Brain, ChevronDown, ChevronsRight, CircleHelp, ClipboardPaste, Computer, Ellipsis, Info, LogOut, Megaphone, Pin, Plug, Plus, RefreshCw, Settings, Smartphone, Square, Users, X } from "./animated-icons"; +import UseAnimations from "react-useanimations"; +import loading from "react-useanimations/lib/loading"; +import loading2 from "react-useanimations/lib/loading2"; +import arrowUp from "react-useanimations/lib/arrowUp"; +import bookmark from "react-useanimations/lib/bookmark"; +import copy from "react-useanimations/lib/copy"; +import folder from "react-useanimations/lib/folder"; +import mail from "react-useanimations/lib/mail"; +import menu from "react-useanimations/lib/menu"; +import plusToX from "react-useanimations/lib/plusToX"; +import settings from "react-useanimations/lib/settings"; +import trash2 from "react-useanimations/lib/trash2"; +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, MemoryItem, Message, Session } from "./types"; +import type { AvatarShape, Bot, ComputerMode, ComputerStatus, McpServer, McpTransport, MemoryItem, Message, Room, RoomMember, Session } from "./types"; -const blankComputer:ComputerStatus={botId:"",mode:"team",state:"stopped",controlHolder:"none",takeoverRequested:false,busyBotName:null,display:null,profileMode:"per-bot",screenAvailable:false}; +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; +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,size=32}:{name:string;color?:string;shape?:AvatarShape;active?:boolean;thinking?: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":null;const path=generatedPath||specialPath;const svgShape=Boolean(path);return {path&&}} +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)&&