import { useState } from 'react'; import { Button, Select } from '../components/ui'; import type { SimulationSnapshot } from '../simulation/SimulationSession'; import type { BridgeStatus } from './ExternalControlClient'; import { ROBOT_PROFILES } from './registry'; export interface ExternalControlUI { status: BridgeStatus; endpoint: string; setEndpoint: (value: string) => void; token: string; setToken: (value: string) => void; observationAgeMs?: number; connect: () => Promise; disconnect: (reason?: string) => void; authorize: () => void; stop: () => void; } export function ExternalControlPanel({ snapshot, loading, bridge, onApplyProfile, }: { snapshot: SimulationSnapshot; loading: boolean; bridge: ExternalControlUI; onApplyProfile: (id?: string) => void; }) { const [profileId, setProfileId] = useState(snapshot.robot?.profileId ?? ''); const external = snapshot.externalControl, ready = bridge.status.phase === 'ready'; const owner = { manual: '手动', python: 'Python', policy: 'ONNX', external: '外部' }[ snapshot.controlOwner ?? 'manual' ]; return (

仅显式选择后生效;未知模型/版本拒绝接管。LeKiwi 使用简化动力学,不是实机标定。

当前 profile:{snapshot.robot?.profileId ?? '未启用'}

{snapshot.robot && (

模型指纹: {snapshot.robot.modelFingerprint.slice(0, 16)}

)}

连接: {ready ? '已连接' : bridge.status.phase === 'connecting' ? '连接中' : '未连接'}

当前控制所有者:{owner}

{external?.connected ? '外部控制者已连接' : external?.enabled ? '等待 Python 控制者' : '未授权'}

{snapshot.paused ? '已暂停(非实时观测)' : bridge.observationAgeMs === undefined ? '尚无观测' : `观测年龄 ${bridge.observationAgeMs.toFixed(0)} ms${bridge.observationAgeMs > 500 ? ' · 已过期' : ''}`}

{external && (

epoch {external.modelEpoch} · applied seq {external.lastActionSeq}

)} {(bridge.status.error || (external?.error && !external.enabled)) && (

{bridge.status.error || external?.error}

)}

先播放再授权。接管会停止 Python/ONNX,并锁定 1×。暂停、重载、页面隐藏和500ms超时均撤销授权;不会自动恢复旧命令。

物理/观测不依赖渲染帧率。外控视图上限30FPS;检测到软件渲染时关闭阴影并降至5FPS,为控制保留时间。

V1:底盘/臂/夹爪和状态回读;不支持相机、锁步、LeRobot 数据采集或训练。

); }