feat: gate the language world behind number verification

This commit is contained in:
冰朔 2026-08-19 22:52:10 +08:00
commit f8c8db4d48
39 changed files with 1445 additions and 129 deletions

View file

@ -39,6 +39,39 @@ interface AuthorizationCenterSnapshot {
purgeEnabled: boolean
}
interface GatewaySkill {
name: string
summary: string
version: string
state: string
readOnly: boolean
executionAuthority: boolean
}
interface GatewayIntegration {
name: string
summary: string
state: string
exposed: boolean
}
interface ExternalAiGatewayStatus {
state: string
exposure: 'OPEN' | 'CLOSED'
humanAuthorizationRequired: boolean
mcpTransport: string
mcpProtocolVersion: string
mcpCommand: string
directProtocol: string
directConnectorCommand: string
brokerState: string
registeredSkillCount: number
activeSkillCount: number
connectedIntegrationCount: number
integrations: GatewayIntegration[]
skills: GatewaySkill[]
}
interface Props {
brokerState: string
activeConnectionCount: number
@ -63,8 +96,10 @@ function time(value?: number) {
export function HumanAuthorizationCenter({ brokerState, activeConnectionCount, sessions }: Props) {
const [snapshot, setSnapshot] = useState<AuthorizationCenterSnapshot | null>(null)
const [gateway, setGateway] = useState<ExternalAiGatewayStatus | null>(null)
const [busy, setBusy] = useState('')
const [message, setMessage] = useState('')
const [gatewayMessage, setGatewayMessage] = useState('')
const refresh = useCallback(async () => {
try {
@ -74,6 +109,13 @@ export function HumanAuthorizationCenter({ brokerState, activeConnectionCount, s
setSnapshot(null)
setMessage(String(error).includes('AUTHENTICATED_ACCOUNT_REQUIRED') ? '登录并完成编号验证后,授权中心才会打开。' : '授权中心当前不可读取;系统不会把未知状态当成已授权。')
}
try {
setGateway(await numberedInvoke<ExternalAiGatewayStatus>('get_external_ai_gateway_status'))
setGatewayMessage('')
} catch {
setGateway(null)
setGatewayMessage('外部编程 AI 入口当前不可读取,因此保持关闭。')
}
}, [])
useEffect(() => {
@ -101,6 +143,20 @@ export function HumanAuthorizationCenter({ brokerState, activeConnectionCount, s
const connectionState = activeConnectionCount > 0 ? '正在直连' : sessions.some((item) => item.state === 'LIVE') ? '最近仍有心跳' : '当前没有在线执行体'
const sessionList = useMemo(() => sessions.slice(0, 6), [sessions])
const setGatewayExposure = async (enabled: boolean) => {
setBusy('external-ai-gateway')
setGatewayMessage('')
try {
const next = await numberedInvoke<ExternalAiGatewayStatus>('set_external_ai_gateway_exposure', { input: { enabled } })
setGateway(next)
setGatewayMessage(enabled ? '已允许本机编程 AI 发现 HoloLake持续协作仍需切换到 HoloLake 本地直连协议。' : '外部编程 AI 入口已关闭;现有 HoloLake 数据与本机会话没有被删除。')
} catch {
setGatewayMessage('入口状态没有改变。请确认编号已验证后再试。')
} finally {
setBusy('')
}
}
return <div className="human-authorization-layout">
<section className="plain-panel authorization-primary">
<header><div><span className="kicker">HUMAN DECISION</span><h2></h2><p>退</p></div><span className={pending.length ? 'status-chip attention' : 'status-chip online'}>{pending.length ? `${pending.length} 项待确认` : '没有待确认事项'}</span></header>
@ -120,6 +176,35 @@ export function HumanAuthorizationCenter({ brokerState, activeConnectionCount, s
{sessionList.length ? <div className="session-projection-list">{sessionList.map((session) => <article key={session.sessionId}><i className={session.state === 'LIVE' ? 'live' : ''}/><div><b>{session.clientInstanceId}</b><span>{session.laneId} · {time(session.observedAtUnixMs)}</span></div><em>{session.state === 'LIVE' ? '在线' : '可续接'}</em></article>)}</div> : <div className="authorization-empty compact"><b></b><span> AI HoloLake </span></div>}
</section>
<section className="plain-panel external-ai-gateway-panel">
<header>
<div><span className="kicker">EXTERNAL PROGRAMMING AI</span><h2> AI </h2><p> MCP HoloLake HoloLake </p></div>
<span className={gateway?.exposure === 'OPEN' ? 'status-chip online' : 'status-chip'}>{gateway?.exposure === 'OPEN' ? '接口已开放' : '接口已关闭'}</span>
</header>
<label className="gateway-exposure-control">
<span><b> AI HoloLake</b><small> MCP </small></span>
<input type="checkbox" checked={gateway?.exposure === 'OPEN'} disabled={!gateway || busy === 'external-ai-gateway'} onChange={(event) => void setGatewayExposure(event.target.checked)}/>
<i aria-hidden="true"/>
</label>
<div className="gateway-summary">
<div><b>{gateway?.registeredSkillCount ?? 0}</b><span></span></div>
<div><b>{gateway?.connectedIntegrationCount ?? 0}</b><span></span></div>
<div><b>{gateway?.activeSkillCount ?? 0}</b><span></span></div>
</div>
<div className="gateway-catalog-grid">
<section>
<header><h3></h3><span></span></header>
<div className="gateway-capability-list">{gateway?.integrations.map((integration) => <article key={integration.name}><i className={integration.exposed ? 'live' : ''}/><div><b>{integration.name}</b><p>{integration.summary}</p></div><em>{integration.state}</em></article>) || <div className="authorization-empty compact"><b></b><span></span></div>}</div>
</section>
<section>
<header><h3></h3><span></span></header>
<div className="gateway-capability-list">{gateway?.skills.map((skill) => <article key={`${skill.name}:${skill.version}`}><i className={skill.state === 'ACTIVE_READONLY' ? 'live' : ''}/><div><b>{skill.name}</b><p>{skill.summary}</p></div><em>{skill.state === 'ACTIVE_READONLY' ? '已启用' : '可安装'}</em></article>) || <div className="authorization-empty compact"><b></b><span></span></div>}</div>
</section>
</div>
<details className="gateway-connection-details"><summary></summary><dl><div><dt>MCP </dt><dd>{gateway?.mcpCommand || '关闭时不暴露命令'}</dd></div><div><dt>HoloLake </dt><dd>{gateway?.directConnectorCommand || '等待本机入口'}</dd></div><div><dt></dt><dd>MCP {gateway?.directProtocol || 'HOLOLAKE_TERMINAL_LINK/3'} </dd></div></dl></details>
{gatewayMessage && <p className="gateway-message" aria-live="polite">{gatewayMessage}</p>}
</section>
{history.length > 0 && <details className="authorization-history"><summary> · {history.length}</summary><div>{history.map((request) => <article key={request.requestId}><div><b>{request.targetLabel}</b><span>{actionLabels[request.action]}</span></div><em>{stateLabels[request.state]}</em><small>{request.humanNumber ? `${request.humanNumber} 决定 · ` : ''}{time(request.decidedAtUnixMs || request.createdAtUnixMs)}</small></article>)}</div></details>}
</div>
}