import { useCallback, useEffect, useMemo, useState } from 'react' import { numberedInvoke as invoke } from '../numbered-ipc' import './styles.css' interface PairedDevice { deviceId: string displayName: string platform: string createdAtUnixMs: number lastSeenAtUnixMs?: number state: 'ACTIVE' | 'REVOKED' } interface MobileSyncStatus { schema: string state: 'OFFLINE' | 'PAIRING_READY' | 'RUNNING' running: boolean lanAddress?: string port?: number pairingUri?: string pairingQrSvg?: string pairingExpiresAtUnixMs?: number pairedDevices: PairedDevice[] transport: string encryption: string desktopIsRootNode: boolean platformPrivateDataCustody: boolean } interface MobileCapture { cursor: number captureId: string title: string body: string sourceDeviceId: string createdAtUnixMs: number } interface MobileSyncSnapshot { state: string cursor: number generatedAtUnixMs: number desktopName: string rootNodeOnline: boolean personalChannel: { home: string; growthEventCount: number; integrity: string } webNovel: { workCount: number volumeCount: number chapterCount: number works: Array<{ workId: string; title: string; status: string; updatedAtUnixMs: number }> } education: { activeTableCount: number archivedTableCount: number unassignedTableCount: number sensitiveValuesIncluded: boolean } recentCaptures: MobileCapture[] boundary: { mobileRole: string remoteDesktopClone: boolean desktopOfflineExecution: boolean sensitiveEducationValues: string modelApi: string } } function formatTime(value?: number) { return value ? new Date(value).toLocaleString('zh-CN', { hour12: false }) : '尚未连接' } export function MobileSyncPanel({ onBack }: { onBack: () => void }) { const [status, setStatus] = useState(null) const [snapshot, setSnapshot] = useState(null) const [message, setMessage] = useState('手机是当前个人节点的远程入口,不是第二套人格系统。') const [pending, setPending] = useState(false) const refresh = useCallback(async () => { const next = await invoke('get_mobile_sync_status') setStatus(next) if (next.running) setSnapshot(await invoke('get_mobile_sync_snapshot')) else setSnapshot(null) }, []) useEffect(() => { let cancelled = false refresh().catch((error) => !cancelled && setMessage(String(error))) return () => { cancelled = true } }, [refresh]) const run = async (action: () => Promise, success: string) => { if (pending) return setPending(true) try { await action() setMessage(success) await refresh() } catch (error) { setMessage(String(error)) } finally { setPending(false) } } const qrSource = useMemo( () => status?.pairingQrSvg ? `data:image/svg+xml;charset=utf-8,${encodeURIComponent(status.pairingQrSvg)}` : '', [status?.pairingQrSvg], ) return
HOLOLAKE / SAME PERSONA SYSTEMiPhone 多端同步
{status?.running ? '个人主节点在线' : '同步未开启'}
手机远程身体入口

{status?.running ? '同一频道,正在等待 iPhone' : '由这台电脑守住唯一真相'}

桌面保留身份、人格、数据与执行主权;手机只同步经过授权的投影与快速记录。

{!status?.running ?
同一局域网 · 应用层端到端加密

开启后只在当前登录账号的数据根建立同步库。平台服务器不托管你的作品、教育数据、人格记忆或设备密钥。

:
PAIRING

配对这台 iPhone

{status.pairingUri ? <> {qrSource && HoloLake iPhone 配对二维码}

用 iPhone 版 HoloLake 扫描。二维码内含一次性高强度配对密钥,十分钟后或使用一次后失效。

到期:{formatTime(status.pairingExpiresAtUnixMs)} :
当前配对凭据已使用或到期

需要添加另一台手机时,再生成一枚新的凭据。

}
ROOT NODE PROJECTION

当前同步投影

{snapshot?.webNovel.workCount ?? 0}网文作品
{snapshot?.webNovel.chapterCount ?? 0}章节
{snapshot?.education.activeTableCount ?? 0}教育表格
{snapshot?.personalChannel.growthEventCount ?? 0}成长事件

教育敏感值:{snapshot?.education.sensitiveValuesIncluded ? '已包含' : '不会进入手机摘要'} · 电脑离线时手机不会冒充执行。

DEVICES

已配对设备

{status.pairedDevices.length ? status.pairedDevices.map((device) =>
{device.displayName}{device.platform} · {device.state === 'ACTIVE' ? `最近连接 ${formatTime(device.lastSeenAtUnixMs)}` : '已撤销'}
{device.state === 'ACTIVE' && }
) :

还没有完成配对的 iPhone。

}
MOBILE CAPTURES

手机快速记录

{snapshot?.recentCaptures.length ? snapshot.recentCaptures.map((capture) =>
{capture.title || '未命名记录'}

{capture.body}

) :

手机写下的想法会加密回到这台电脑,并出现在这里。

}
}
{message}
{status?.running && }
}