From 0e1cbf5ba50e7647914942d101cba82ca7504437 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E4=B8=80=E5=AF=B0?= Date: Fri, 13 Mar 2026 17:36:03 +0800 Subject: [PATCH] add vnc success --- ui/package.json | 1 + ui/src/ui/app.ts | 1 + ui/src/ui/components/claw-computer-panel.ts | 91 ++++++++++++--------- 3 files changed, 56 insertions(+), 37 deletions(-) diff --git a/ui/package.json b/ui/package.json index c326f70cf3a..e0162770999 100644 --- a/ui/package.json +++ b/ui/package.json @@ -12,6 +12,7 @@ "@lit-labs/signals": "^0.2.0", "@lit/context": "^1.1.6", "@noble/ed25519": "3.0.0", + "@novnc/novnc": "^1.6.0", "dompurify": "^3.3.3", "lit": "^3.3.2", "marked": "^17.0.4", diff --git a/ui/src/ui/app.ts b/ui/src/ui/app.ts index 9f103cdc25c..87bc945a2cb 100644 --- a/ui/src/ui/app.ts +++ b/ui/src/ui/app.ts @@ -87,6 +87,7 @@ import type { import { type ChatAttachment, type ChatQueueItem, type CronFormState } from "./ui-types.ts"; import { generateUUID } from "./uuid.ts"; import type { NostrProfileFormState } from "./views/channels.nostr-profile-form.ts"; +import "./components/claw-computer-panel.ts"; declare global { interface Window { diff --git a/ui/src/ui/components/claw-computer-panel.ts b/ui/src/ui/components/claw-computer-panel.ts index a6312e054f6..6a64707366a 100644 --- a/ui/src/ui/components/claw-computer-panel.ts +++ b/ui/src/ui/components/claw-computer-panel.ts @@ -1,25 +1,32 @@ +// @ts-ignore - noVNC types are not available +import RFB from "@novnc/novnc"; // ui/src/ui/components/claw-computer-panel.ts import { LitElement, html, css } from "lit"; import { customElement, state } from "lit/decorators.js"; import { createRef, ref, Ref } from "lit/directives/ref.js"; +// Compatible with both .default and non-.default versions +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const RFBClass = (RFB as any).default || RFB; + +// RFB instance type definition interface RFBInstance { disconnect(): void; addEventListener(event: string, callback: (e?: unknown) => void): void; scaleViewport: boolean; clipViewport: boolean; + resizeSession: boolean; resize?(): void; } @customElement("claw-computer-panel") export class ClawComputerPanel extends LitElement { - @state() status = "🖥️ Claw Computer 未配置"; + @state() status = "等待連接..."; @state() isConnected = false; @state() isFitted = true; @state() password = ""; private rfb: RFBInstance | null = null; - private RFBConstructor: unknown = null; private screenRef: Ref = createRef(); static styles = css` @@ -111,7 +118,10 @@ export class ClawComputerPanel extends LitElement { render() { return html`
-

Claw Computer - VNC 遠端畫面

+

noVNC - 自動調整版(穩定無錯誤)

+

+ ws://localhost:8081(已轉發到你的 10.75.171.0:25900) +

-
+
`; } - private async loadRFB() { - if (this.RFBConstructor) { - return; - } - const module = await import("@novnc/novnc/lib/rfb.js"); - this.RFBConstructor = (module as unknown as { default?: unknown }).default || module; - } - private connect = async () => { - await this.loadRFB(); - const url = "ws://localhost:8081"; if (this.rfb) { this.rfb.disconnect(); } this.status = "正在連接..."; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + let screen = this.screenRef.value; - const screen = this.screenRef.value; if (!screen) { + // Fallback: try to find the element via shadowRoot if ref failed + screen = this.shadowRoot?.querySelector(".screen") as HTMLDivElement; + } + + if (!screen) { + console.error("Screen element not found"); + this.status = "初始化失败:找不到屏幕元素"; return; } - const Constructor = this.RFBConstructor as new ( - target: HTMLElement, - url: string, - options?: unknown, - ) => RFBInstance; - this.rfb = new Constructor(screen, url, { - credentials: { password: this.password || undefined }, - resizeSession: true, - clipViewport: true, - scaleViewport: this.isFitted, - }); + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const Constructor = RFBClass as new ( + target: HTMLElement, + url: string, + options?: unknown, + ) => RFBInstance; - this.rfb.addEventListener("connect", () => { - this.isConnected = true; - this.status = "已連線成功 ✓(改變視窗大小會自動適配)"; - setTimeout(() => this.rfb?.resize?.(), 100); - }); + this.rfb = new Constructor(screen, url, { + credentials: { password: this.password || undefined }, + resizeSession: true, + clipViewport: true, + }); - this.rfb.addEventListener("disconnect", () => { - this.isConnected = false; - this.status = "連線中斷"; - this.rfb = null; - }); + if (this.rfb) { + this.rfb.scaleViewport = this.isFitted; + } + + this.rfb?.addEventListener("connect", () => { + this.isConnected = true; + this.status = "已連線成功 ✓(改變視窗大小會自動適配)"; + setTimeout(() => this.rfb?.resize?.(), 100); + }); + + this.rfb?.addEventListener("disconnect", () => { + this.isConnected = false; + this.status = "連線中斷"; + this.rfb = null; + }); + } catch (error) { + console.error("Failed to create RFB instance:", error); + this.status = `连接失败: ${error as string}`; + } }; private disconnect = () => { @@ -225,7 +243,6 @@ export class ClawComputerPanel extends LitElement { firstUpdated() { window.addEventListener("resize", this.handleResize); - void this.loadRFB(); } disconnectedCallback() {