add vnc success

This commit is contained in:
赵一寰 2026-03-13 17:36:03 +08:00
parent 4be856caf3
commit 0e1cbf5ba5
3 changed files with 56 additions and 37 deletions

View File

@ -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",

View File

@ -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 {

View File

@ -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<HTMLDivElement> = createRef<HTMLDivElement>();
static styles = css`
@ -111,7 +118,10 @@ export class ClawComputerPanel extends LitElement {
render() {
return html`
<div class="container">
<h2>Claw Computer - VNC </h2>
<h2>noVNC - 調</h2>
<p style="text-align:center; color:#888;">
ws://localhost:8081已轉發到你的 10.75.171.0:25900
</p>
<div class="controls">
<label>VNC :</label>
<input type="password" placeholder="留空 = 無密碼"
@ -135,58 +145,66 @@ export class ClawComputerPanel extends LitElement {
</div>
</div>
<div class="screen-container">
<div ${ref(this.screenRef)} class="screen"></div>
<div ${ref(this.screenRef)} class="screen" style="display: ${this.isConnected ? "block" : "none"}"></div>
</div>
</div>
`;
}
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() {