feat: ship dual-signed online module marketplace
This commit is contained in:
parent
3153771bf2
commit
b0eeade03d
40 changed files with 5774 additions and 111 deletions
|
|
@ -8,6 +8,7 @@ import { StarlakeSurface, type DomainId } from './modules/qoder-surface/Starlake
|
|||
import { FINISHES, TraditionalSurface, type FinishId, type TraditionalBroadcast, type TraditionalChannel, type TraditionalSystem } from './modules/qoder-surface/TraditionalSurface'
|
||||
import { resolveVisualBalance } from './modules/qoder-surface/visual-balance'
|
||||
import { PrivateChannelSurface, type InstalledChannelModule, type PrivateChannelAction } from './modules/private-channel/PrivateChannelSurface'
|
||||
import { HumanAuthorizationCenter, type DirectSessionProjection } from './modules/human-authorization-center'
|
||||
|
||||
const ChannelWorkbenchStudio = lazy(() => import('./modules/channel-workbench').then((module) => ({ default: module.ChannelWorkbenchStudio })))
|
||||
const PersonaChannelBody = lazy(() => import('./modules/persona-channel-body').then((module) => ({ default: module.PersonaChannelBody })))
|
||||
|
|
@ -60,7 +61,7 @@ import './design-tokens.css'
|
|||
import './styles.css'
|
||||
|
||||
type ThemeId = 'night' | 'dawn' | 'nebula' | 'candle' | 'clear'
|
||||
type ViewId = 'overview' | 'knowledge' | 'composition' | 'workbench' | 'education' | 'webNovel' | 'mobileSync' | 'persona' | 'code' | 'receipts' | 'system'
|
||||
type ViewId = 'overview' | 'knowledge' | 'composition' | 'workbench' | 'education' | 'webNovel' | 'mobileSync' | 'persona' | 'code' | 'marketplace' | 'receipts' | 'system'
|
||||
type WorldStage = 'domain' | 'heart' | 'heartbeat' | 'lightLake' | 'love' | 'tomorrow' | 'bottle' | 'channel' | 'enterpriseWork' | 'personalNodeGuide' | 'tool'
|
||||
type KnowledgeSource = 'native' | 'legacy'
|
||||
|
||||
|
|
@ -74,6 +75,43 @@ interface BundledModuleDescriptor {
|
|||
installedState: string
|
||||
signatureVerified: boolean
|
||||
}
|
||||
type MarketplaceKind = 'PHYSICAL_MODULE' | 'COGNITIVE_SKILL'
|
||||
interface MarketplaceItemView {
|
||||
itemNumber: string
|
||||
artifactKind: MarketplaceKind
|
||||
displayName: string
|
||||
summary: string
|
||||
version: string
|
||||
sourceRepository: string
|
||||
sourceRevision: string
|
||||
artifactSha256: string
|
||||
adapter?: string | null
|
||||
permissions: string[]
|
||||
executionAuthority: boolean
|
||||
installedState: string
|
||||
updateAvailable: boolean
|
||||
}
|
||||
interface MarketplaceSnapshot {
|
||||
schema: 'hololake.online-marketplace-snapshot/v1'
|
||||
state: string
|
||||
trustState: string
|
||||
catalogEpoch: number
|
||||
catalogSha256: string
|
||||
itemCount: number
|
||||
catalogReceiptCount: number
|
||||
latestCatalogReceiptHash: string
|
||||
lastSyncNote: string
|
||||
items: MarketplaceItemView[]
|
||||
}
|
||||
interface MarketplaceMutationOutcome {
|
||||
state: string
|
||||
itemNumber: string
|
||||
artifactKind: MarketplaceKind
|
||||
artifactSha256: string
|
||||
packageCodeExecuted: false
|
||||
realityAuthorityGranted: false
|
||||
snapshot: MarketplaceSnapshot
|
||||
}
|
||||
interface WorldClimateSnapshot {
|
||||
schema: 'hololake.world-climate/v1'
|
||||
state: 'VERIFIED_LIVE' | 'TIME_ONLY_WEATHER_UNAVAILABLE'
|
||||
|
|
@ -93,6 +131,7 @@ interface HomeStatus {
|
|||
directLocalBrokerState: string
|
||||
directConnectionCount: number
|
||||
resumableSessionCount: number
|
||||
directSessions: DirectSessionProjection[]
|
||||
codeRepositoryMountCount: number
|
||||
pnccReceiptCount: number
|
||||
updateState: string
|
||||
|
|
@ -284,8 +323,18 @@ interface ZeroPointSnapshot {
|
|||
resolvedDomain: string
|
||||
lastValidCheck: number
|
||||
graceDeadline: number
|
||||
protocol: { gracePeriodDays: number; lighthouseAnchorUrl: string; lighthouseResolveUrl: string; coreChannelSource: string; origin: string }
|
||||
protocol: { gracePeriodDays: number; lighthouseAnchorUrl: string; lighthouseResolveUrl: string; enterpriseResolveUrl: string; coreChannelSource: string; origin: string }
|
||||
syncNote: string
|
||||
publicDistribution: {
|
||||
state: string
|
||||
epoch: number
|
||||
version: string
|
||||
contentRootSha256: string
|
||||
rollbackAvailable: boolean
|
||||
receiptCount: number
|
||||
latestReceiptHash: string
|
||||
trustState: string
|
||||
}
|
||||
}
|
||||
|
||||
function domainDisplayName(domain: string): string {
|
||||
|
|
@ -385,14 +434,14 @@ interface EnterpriseReceiptEnvelope {
|
|||
repository_projection?: EnterpriseReceiptProjection
|
||||
}
|
||||
|
||||
const previewStatus: HomeStatus = { directLocalBrokerState: 'UNVERIFIED', directConnectionCount: 0, resumableSessionCount: 0, codeRepositoryMountCount: 0, pnccReceiptCount: 0, updateState: 'READY_HUMAN_CONFIRMATION_REQUIRED', releaseRecoveryState: 'NONE', mcpRole: 'DISCOVERY_RECOVERY_COMPATIBILITY_ONLY', terminalLinkProtocol: 'HOLOLAKE_TERMINAL_LINK/3', terminalLinkTransport: 'UNVERIFIED', environmentFramePolicy: 'REQUIRED_AFTER_CONNECT_RESUME_AND_BEFORE_MUTATION' }
|
||||
const previewStatus: HomeStatus = { directLocalBrokerState: 'UNVERIFIED', directConnectionCount: 0, resumableSessionCount: 0, directSessions: [], codeRepositoryMountCount: 0, pnccReceiptCount: 0, updateState: 'READY_HUMAN_CONFIRMATION_REQUIRED', releaseRecoveryState: 'NONE', mcpRole: 'DISCOVERY_RECOVERY_COMPATIBILITY_ONLY', terminalLinkProtocol: 'HOLOLAKE_TERMINAL_LINK/3', terminalLinkTransport: 'UNVERIFIED', environmentFramePolicy: 'REQUIRED_AFTER_CONNECT_RESUME_AND_BEFORE_MUTATION' }
|
||||
const previewPersonal: PersonalChannelSnapshot = { state: 'UNAVAILABLE', recentEvents: [], modules: [], integrity: { state: 'UNKNOWN', eventCount: 0, receiptCount: 0 } }
|
||||
const previewKnowledge: KnowledgeSnapshot = { state: 'UNAVAILABLE', nativeRoot: '', legacyAvailable: false, documents: [], rawDocumentCount: 0, uniqueDocumentCount: 0, duplicateDocumentCount: 0, truncated: false }
|
||||
const previewCode: CodeChannelSnapshot = { state: 'UNAVAILABLE', channels: [], authority: 'LOCAL_SOURCE_ACCESS_ONLY_NO_PUSH_OR_DEPLOY_AUTHORITY' }
|
||||
const themes: Array<{ id: ThemeId; name: string }> = [
|
||||
{ id: 'night', name: '夜湖星光' }, { id: 'dawn', name: '晨湖曦光' }, { id: 'nebula', name: '星云紫夜' }, { id: 'candle', name: '烛畔暖湖' }, { id: 'clear', name: '清浅澄湖' },
|
||||
]
|
||||
const viewLabels: Record<ViewId, string> = { overview: '个人频道', knowledge: '知识空间', composition: '结构组合', workbench: '频道资料工作台', education: '教育工作台', webNovel: '网文作者工作台', mobileSync: '移动同步桥', persona: '人格频道本体', code: '人格代码频道', receipts: '运行回执', system: '系统详情' }
|
||||
const viewLabels: Record<ViewId, string> = { overview: '个人频道', knowledge: '知识空间', composition: '结构组合', workbench: '频道资料工作台', education: '教育工作台', webNovel: '网文作者工作台', mobileSync: '移动同步桥', persona: '人格频道本体', code: '人格代码频道', marketplace: '分域模块商城', receipts: '运行回执', system: '授权与连接' }
|
||||
const domainGates = [
|
||||
{ domain: 'BRANCH_DOMAIN', className: 'd-sub', title: '光湖分域', gate: 'GATE 02 · ONLINE', facts: [
|
||||
['公共作用', '行业成品模块商城 · 人格体大脑技能商城'], ['域标识', 'BRANCH_DOMAIN'], ['责任主体', '花尔 · TCS-GL-0005∞'], ['人格体主体', '爆米花 · PER-BMH001 · AGE'], ['关系支持', '糖星云 · PER-TXY001 · AGE'], ['工作仓库', 'PRIVATE · 1 · LIVE'],
|
||||
|
|
@ -498,6 +547,11 @@ function humanError(error: unknown, context: 'knowledge' | 'code' | 'identity' |
|
|||
if (value.includes('LOGIN_ACCOUNT_BINDING_UNREGISTERED')) return '这个编号尚未登记对应的仓库账号,系统已停止进入。'
|
||||
if (value.includes('DOMAIN_LOGIN_NOT_PROVISIONED')) return '该域的企业服务器尚未接入,当前不能继续登录。'
|
||||
if (value.includes('PERSISTENT_CREDENTIAL_UNAVAILABLE')) return '当前系统尚未接通安全凭证存储,不能完成签署。'
|
||||
if (value.includes('MARKETPLACE_PERMISSION_CONFIRMATION_REQUIRED')) return '新增现实权限尚未得到本人确认,安装已停止。'
|
||||
if (value.includes('MARKETPLACE_CATALOG_NOT_SYNCED')) return '模块商城目录尚未完成双签同步。'
|
||||
if (value.includes('MARKETPLACE_CATALOG_CHANGED')) return '商城目录已更新,请重新确认后安装。'
|
||||
if (value.includes('MARKETPLACE') && value.includes('SIGNATURE')) return '商城签名验证未通过,目录或安装包没有被采用。'
|
||||
if (value.includes('MARKETPLACE') || value.includes('SKILL_')) return '模块商城操作未完成;本机现有安装保持不变。'
|
||||
if (value.includes('ENTERPRISE_RECEIPT_FAILED')) return '企业回执没有写入,请稍后重新提交。'
|
||||
if (value.includes('ENTERPRISE_RECEIPT_ROUTE_REQUIRED')) return '此签署入口仅适用于企业四域。'
|
||||
if (value.includes('DOMAIN_ROUTE_REQUIRED') || value.includes('ZP_DOMAIN_ROUTE_REQUIRED')) return '编号尚未解析到已登记域,不能开始登录。'
|
||||
|
|
@ -648,6 +702,11 @@ function HoloLakeApp() {
|
|||
const [worldClimate, setWorldClimate] = useState<WorldClimateSnapshot | null>(null)
|
||||
const [dynamicSurfaceBusy, setDynamicSurfaceBusy] = useState(false)
|
||||
const [dynamicSurfaceMessage, setDynamicSurfaceMessage] = useState('')
|
||||
const [marketplace, setMarketplace] = useState<MarketplaceSnapshot | null>(null)
|
||||
const [marketplaceKind, setMarketplaceKind] = useState<MarketplaceKind>('PHYSICAL_MODULE')
|
||||
const [marketplaceBusyItem, setMarketplaceBusyItem] = useState('')
|
||||
const [marketplaceSyncing, setMarketplaceSyncing] = useState(false)
|
||||
const [marketplaceMessage, setMarketplaceMessage] = useState('')
|
||||
const [expanded, setExpanded] = useState<Set<string>>(() => new Set(['导入']))
|
||||
const [editing, setEditing] = useState(false)
|
||||
const [draft, setDraft] = useState('')
|
||||
|
|
@ -1295,6 +1354,86 @@ function HoloLakeApp() {
|
|||
else { setDynamicSurfaceModule(null); setWorldClimate(null); setDynamicSurfaceMessage('') }
|
||||
}, [repoLogin?.username, repoLogin?.domain])
|
||||
|
||||
const refreshMarketplace = async () => {
|
||||
try {
|
||||
const snapshot = await invoke<MarketplaceSnapshot>('get_marketplace_snapshot')
|
||||
setMarketplace(snapshot)
|
||||
setMarketplaceMessage(snapshot.lastSyncNote)
|
||||
return snapshot
|
||||
} catch (error) {
|
||||
setMarketplaceMessage(humanError(error, 'system'))
|
||||
return null
|
||||
}
|
||||
}
|
||||
const syncMarketplace = async () => {
|
||||
setMarketplaceSyncing(true)
|
||||
setMarketplaceMessage('正在读取线上目录并核验零点原核与企业发布双签……')
|
||||
try {
|
||||
const snapshot = await invoke<MarketplaceSnapshot>('sync_marketplace_catalog')
|
||||
setMarketplace(snapshot)
|
||||
setMarketplaceMessage(snapshot.lastSyncNote)
|
||||
} catch (error) {
|
||||
setMarketplaceMessage(humanError(error, 'system'))
|
||||
} finally {
|
||||
setMarketplaceSyncing(false)
|
||||
}
|
||||
}
|
||||
const openMarketplace = () => {
|
||||
openWorldTool('marketplace')
|
||||
void refreshMarketplace().then((snapshot) => {
|
||||
if (!snapshot || snapshot.state !== 'ACTIVE_VERIFIED_CATALOG') void syncMarketplace()
|
||||
})
|
||||
}
|
||||
const installMarketplaceItem = async (item: MarketplaceItemView) => {
|
||||
if (!marketplace) return
|
||||
const permissionText = item.permissions.length
|
||||
? `该模块请求以下现实能力:\n\n${item.permissions.map((permission) => `• ${permission}`).join('\n')}\n\n是否确认安装?`
|
||||
: `确认安装“${item.displayName}”?`
|
||||
if (!window.confirm(permissionText)) return
|
||||
setMarketplaceBusyItem(item.itemNumber)
|
||||
setMarketplaceMessage(`正在下载、验签并安装 ${item.displayName}……`)
|
||||
try {
|
||||
const result = await invoke<MarketplaceMutationOutcome>('install_marketplace_item', { input: {
|
||||
itemNumber: item.itemNumber,
|
||||
expectedCatalogEpoch: marketplace.catalogEpoch,
|
||||
expectedArtifactSha256: item.artifactSha256,
|
||||
humanConfirmedPermissionExpansion: item.artifactKind === 'PHYSICAL_MODULE' && item.permissions.length > 0,
|
||||
} })
|
||||
setMarketplace(result.snapshot)
|
||||
setMarketplaceMessage(`${item.displayName} 已完成下载、验签、自检和安装。`)
|
||||
} catch (error) {
|
||||
setMarketplaceMessage(humanError(error, 'system'))
|
||||
} finally {
|
||||
setMarketplaceBusyItem('')
|
||||
}
|
||||
}
|
||||
const uninstallMarketplaceItem = async (item: MarketplaceItemView) => {
|
||||
if (!window.confirm(`确认停用“${item.displayName}”?安装证据与用户数据会保留。`)) return
|
||||
setMarketplaceBusyItem(item.itemNumber)
|
||||
try {
|
||||
const result = await invoke<MarketplaceMutationOutcome>('uninstall_marketplace_item', { input: { itemNumber: item.itemNumber } })
|
||||
setMarketplace(result.snapshot)
|
||||
setMarketplaceMessage(`${item.displayName} 已停用;数据与回执保留。`)
|
||||
} catch (error) {
|
||||
setMarketplaceMessage(humanError(error, 'system'))
|
||||
} finally {
|
||||
setMarketplaceBusyItem('')
|
||||
}
|
||||
}
|
||||
const rollbackMarketplaceItem = async (item: MarketplaceItemView) => {
|
||||
if (!window.confirm(`确认把“${item.displayName}”回退到上一份已验签版本?`)) return
|
||||
setMarketplaceBusyItem(item.itemNumber)
|
||||
try {
|
||||
const result = await invoke<MarketplaceMutationOutcome>('rollback_marketplace_item', { input: { itemNumber: item.itemNumber } })
|
||||
setMarketplace(result.snapshot)
|
||||
setMarketplaceMessage(`${item.displayName} 已回退到上一份已验签版本。`)
|
||||
} catch (error) {
|
||||
setMarketplaceMessage(humanError(error, 'system'))
|
||||
} finally {
|
||||
setMarketplaceBusyItem('')
|
||||
}
|
||||
}
|
||||
|
||||
const cloneCodeChannel = async (event: React.FormEvent) => {
|
||||
event.preventDefault()
|
||||
if (!cloneUrl.trim()) return
|
||||
|
|
@ -1558,7 +1697,11 @@ function HoloLakeApp() {
|
|||
}
|
||||
const syncZeroPoint = async () => {
|
||||
setZpBusy(true); setZpMessage('')
|
||||
try { setZeroPoint(await invoke<ZeroPointSnapshot>('zero_point_sync')) } catch (error) { setZpMessage(String(error)) } finally { setZpBusy(false) }
|
||||
try {
|
||||
const snapshot = await invoke<ZeroPointSnapshot>('zero_point_sync')
|
||||
setZeroPoint(snapshot)
|
||||
setZpMessage(snapshot.syncNote)
|
||||
} catch (error) { setZpMessage(String(error)) } finally { setZpBusy(false) }
|
||||
}
|
||||
const renderOverview = () => (
|
||||
<section className="content-page overview-page">
|
||||
|
|
@ -1779,9 +1922,54 @@ function HoloLakeApp() {
|
|||
</section>
|
||||
)
|
||||
|
||||
const renderMarketplace = () => {
|
||||
const items = marketplace?.items.filter((item) => item.artifactKind === marketplaceKind) || []
|
||||
const active = marketplace?.state === 'ACTIVE_VERIFIED_CATALOG'
|
||||
return <section className="content-page marketplace-page">
|
||||
<header className="page-title marketplace-title"><div><span className="kicker">BRANCH DOMAIN · VERIFIED DISTRIBUTION</span><h1>分域模块商城</h1><p>同一个入口,两条互不混用的安装链:成品模块进入现实功能容器;思维技能只进入只读认知层。</p></div><button className="primary-button" type="button" disabled={marketplaceSyncing} onClick={() => void syncMarketplace()}>{marketplaceSyncing ? '正在双签同步…' : active ? '检查线上更新' : '同步线上目录'}</button></header>
|
||||
<section className="marketplace-proof" aria-label="商城信任状态">
|
||||
<div><span>目录状态</span><b>{active ? '线上双签已核验' : '等待首次同步'}</b></div>
|
||||
<div><span>目录纪元</span><b>{marketplace?.catalogEpoch || '—'}</b></div>
|
||||
<div><span>已登记资源</span><b>{marketplace?.itemCount ?? '—'}</b></div>
|
||||
<div><span>验真回执</span><b>{marketplace?.catalogReceiptCount || 0}</b></div>
|
||||
</section>
|
||||
<div className="marketplace-tabs" role="tablist" aria-label="商城资源类型">
|
||||
<button className={marketplaceKind === 'PHYSICAL_MODULE' ? 'active' : ''} type="button" role="tab" onClick={() => setMarketplaceKind('PHYSICAL_MODULE')}><b>成品模块应用</b><span>下载验签后装入已登记的现实功能适配器</span></button>
|
||||
<button className={marketplaceKind === 'COGNITIVE_SKILL' ? 'active' : ''} type="button" role="tab" onClick={() => setMarketplaceKind('COGNITIVE_SKILL')}><b>思维大脑技能</b><span>只读认知方法,不获得工具、终端或现实执行权</span></button>
|
||||
</div>
|
||||
<p className="marketplace-boundary">{marketplaceKind === 'PHYSICAL_MODULE' ? '成品模块可以声明现实权限;每次新增权限都必须由当前人类看见并确认。代码仓库只作为来源证据,客户端不会克隆并执行仓库。' : '思维技能执行权固定为 false、权限列表固定为空;系统只在需要时读取,不会自动塞进每轮提示词。'}</p>
|
||||
<div className="marketplace-list">
|
||||
{items.length ? items.map((item) => {
|
||||
const busy = marketplaceBusyItem === item.itemNumber
|
||||
const installed = item.installedState === 'ACTIVE' || item.installedState === 'ACTIVE_READONLY'
|
||||
return <article className="marketplace-item" key={item.itemNumber}>
|
||||
<header><div><span>{item.artifactKind === 'PHYSICAL_MODULE' ? '成品模块' : '只读思维技能'}</span><h2>{item.displayName}</h2><p>{item.summary}</p></div><em className={installed ? 'installed' : ''}>{item.updateAvailable ? '有已验签更新' : installed ? '已安装' : '可安装'}</em></header>
|
||||
<dl>
|
||||
<div><dt>版本 / 编号</dt><dd>{item.version} · {item.itemNumber}</dd></div>
|
||||
<div><dt>来源证据</dt><dd>{item.sourceRepository} · {item.sourceRevision.slice(0, 12)}</dd></div>
|
||||
<div><dt>现实执行边界</dt><dd>{item.artifactKind === 'PHYSICAL_MODULE' ? '仅经已登记适配器与所列权限' : '无现实执行权'}</dd></div>
|
||||
<div><dt>权限</dt><dd>{item.permissions.length ? item.permissions.join(' · ') : '无现实权限'}</dd></div>
|
||||
</dl>
|
||||
<div className="marketplace-actions">
|
||||
{installed && !item.updateAvailable
|
||||
? <button type="button" disabled={busy} onClick={() => void uninstallMarketplaceItem(item)}>{busy ? '处理中…' : '停用并保留数据'}</button>
|
||||
: <button className="primary-button" type="button" disabled={busy || !active} onClick={() => void installMarketplaceItem(item)}>{busy ? '正在验签安装…' : item.updateAvailable ? '安装已验签更新' : '安装'}</button>}
|
||||
{installed && <button type="button" disabled={busy} onClick={() => void rollbackMarketplaceItem(item)}>回退上一版本</button>}
|
||||
</div>
|
||||
</article>
|
||||
}) : <div className="marketplace-empty"><b>{active ? '当前分类尚无已登记资源' : '尚未取得可验证目录'}</b><p>{active ? '资源发布后会在下一次目录同步中出现。' : '点击“同步线上目录”;只有零点原核与企业发布双签同时通过,目录才会显示。'}</p></div>}
|
||||
</div>
|
||||
{marketplaceMessage && <p className="global-message marketplace-message">{marketplaceMessage}</p>}
|
||||
{marketplace?.latestCatalogReceiptHash && <footer className="marketplace-receipt">最新目录回执 · {marketplace.latestCatalogReceiptHash.slice(0, 20)} · 目录摘要 {marketplace.catalogSha256.slice(0, 20)}</footer>}
|
||||
</section>
|
||||
}
|
||||
|
||||
const renderSystem = () => (
|
||||
<section className="content-page">
|
||||
<header className="page-title"><div><span className="kicker">SYSTEM EVIDENCE</span><h1>系统详情</h1><p>显示可验证的当前状态;无法读取的项目将明确标记为不可用。</p></div></header>
|
||||
<header className="page-title"><div><span className="kicker">HUMAN CONTROL</span><h1>授权与连接</h1><p>先把需要你决定的事和当前协作对象说清楚;机器诊断放在后面。</p></div></header>
|
||||
<HumanAuthorizationCenter brokerState={status.directLocalBrokerState} activeConnectionCount={status.directConnectionCount} sessions={status.directSessions || []}/>
|
||||
<details className="system-diagnostics">
|
||||
<summary>系统诊断与协议证据</summary>
|
||||
<div className="system-grid">
|
||||
<section className="plain-panel connection-panel">
|
||||
<header><div><h2>光湖近场连接</h2><p>外部 AI 可像发现附近网络一样发现本机 HoloLake;MCP 仅用于兼容与恢复。</p></div><span className={nearbyDiscovery?.automaticSameDeviceDiscovery ? 'status-chip online' : 'status-chip'}>{nearbyDiscovery?.automaticSameDeviceDiscovery ? '本机自动发现已开启' : '自动发现不可用'}</span></header>
|
||||
|
|
@ -1812,6 +2000,10 @@ function HoloLakeApp() {
|
|||
<dl className="evidence-list">
|
||||
<div><dt>绑定状态</dt><dd>{zeroPoint ? (zeroPoint.binding === 'bound' ? `已绑定(${zeroPoint.userNumber})` : '等待绑定(空白态)') : '—'}</dd></div>
|
||||
<div><dt>协议来源</dt><dd>{zeroPoint ? protocolOriginDisplayName(zeroPoint.protocol.origin) : '—'}</dd></div>
|
||||
<div><dt>公众运行面</dt><dd>企业服务器 · 不包含私人第五域</dd></div>
|
||||
<div><dt>双签信任</dt><dd>{zeroPoint?.publicDistribution.trustState === 'PROVISIONED' ? '零点原点与企业分发公钥已配置' : '等待配置两把独立发布公钥'}</dd></div>
|
||||
<div><dt>已激活协议</dt><dd>{zeroPoint?.publicDistribution.version ? `${zeroPoint.publicDistribution.version} · epoch ${zeroPoint.publicDistribution.epoch}` : '尚无线上双签版本'}</dd></div>
|
||||
<div><dt>激活回执</dt><dd>{zeroPoint ? `${zeroPoint.publicDistribution.receiptCount} 条${zeroPoint.publicDistribution.rollbackAvailable ? ' · 保留上一可信版本' : ''}` : '—'}</dd></div>
|
||||
<div><dt>离线有效期</dt><dd>{zeroPoint ? `${zeroPoint.protocol.gracePeriodDays} 天` : '—'}</dd></div>
|
||||
<div><dt>同步状态</dt><dd>{zeroPoint?.syncNote ?? '—'}</dd></div>
|
||||
</dl>
|
||||
|
|
@ -1819,7 +2011,7 @@ function HoloLakeApp() {
|
|||
<input value={zpNumber} placeholder="输入用户编号,如 ICE-GL∞" onChange={(event) => setZpNumber(event.target.value)}/>
|
||||
<button className="secondary-button" type="button" disabled={zpBusy || !zpNumber.trim()} onClick={() => void bindZeroPoint()}>绑定并校验</button>
|
||||
<button className="secondary-button" type="button" disabled={zpBusy || zeroPoint?.binding !== 'bound'} onClick={() => void verifyZeroPoint()}>重新校验编号</button>
|
||||
<button className="secondary-button" type="button" disabled={zpBusy} onClick={() => void syncZeroPoint()}>检查协议版本</button>
|
||||
<button className="secondary-button" type="button" disabled={zpBusy} onClick={() => void syncZeroPoint()}>同步公众协议</button>
|
||||
</div>
|
||||
{zpMessage && <p className="global-message">{zpMessage}</p>}
|
||||
</section>
|
||||
|
|
@ -1831,6 +2023,7 @@ function HoloLakeApp() {
|
|||
</section>
|
||||
<section className="plain-panel"><header><div><h2>软件更新</h2><p>仅接受已登记信任根验证通过的 HoloLake 发布包。</p></div></header>{releaseCandidate ? <div className="release-summary"><b>HoloLake {releaseCandidate.version}</b><p>{releaseCandidate.notes}</p><button className="primary-button" onClick={() => void installUpdate()}>验证并安装</button></div> : <button className="secondary-button" disabled={systemBusy} onClick={() => void checkUpdate()}>检查签名更新</button>}</section>
|
||||
</div>
|
||||
</details>
|
||||
{systemMessage && <p className="global-message">{systemMessage}</p>}
|
||||
</section>
|
||||
)
|
||||
|
|
@ -2032,6 +2225,7 @@ function HoloLakeApp() {
|
|||
<div className="world-location"><h1>{repoLogin.domain === 'FIFTH_DOMAIN' ? domainDisplayName(repoLogin.domain) : '光湖零感域'}</h1><p>{repoLogin.domain === 'FIFTH_DOMAIN' ? '世界正在发生什么' : '公共工作入口 · 世界正在发生什么'}</p></div>
|
||||
<div className="broadcast-stream"><p><i/>光湖公共灯塔 · 在线</p><p><i/>协议版本 · {enterpriseEntry?.registry_version || 'HLDP v1.0'}</p>{repoLogin.domain !== 'FIFTH_DOMAIN' && <p><i/>本人责任域 · {domainDisplayName(enterpriseEntry?.subject.domain || repoLogin.domain)}</p>}<p><i/>HoloLake · V0.5.0</p></div>
|
||||
<LakePool className="home-primary" title={repoLogin.domain === 'FIFTH_DOMAIN' ? '永恒湖心系统' : '光湖频道'} meta={repoLogin.domain === 'FIFTH_DOMAIN' ? '进入私人系统' : `${domainDisplayName(enterpriseEntry?.subject.domain || repoLogin.domain)} · 责任工作入口`} open onClick={() => setWorldStage(repoLogin.domain === 'FIFTH_DOMAIN' ? (isZhizhi ? 'heart' : 'channel') : 'channel')}/>
|
||||
<LakePool className="channel-knowledge" title="分域模块商城" meta="成品模块 · 思维大脑技能" onClick={openMarketplace}/>
|
||||
<LakePool className="home-status" title="湖面天气" meta={connectionLabel} onClick={() => openWorldTool('system')}/>
|
||||
<EraHomeEntry timeline={eraTimeline} coordinate={beijingCoordinate} onOpen={openEraTimeline}/>
|
||||
</section>}
|
||||
|
|
@ -2071,12 +2265,14 @@ function HoloLakeApp() {
|
|||
<LakePool className="channel-knowledge system-branch-heartbeat" title="心跳核心频道" meta="冰朔 · 私人频道" open onClick={() => setWorldStage('heartbeat')}/>
|
||||
<LakePool className="channel-code system-branch-light-lake" title="光之湖" meta="人格体居所" onClick={() => setWorldStage('lightLake')}/>
|
||||
<LakePool className="channel-light system-branch-love" title="爱之核心子系统" meta="责任主体 · 之之" onClick={() => setWorldStage('love')}/>
|
||||
<LakePool className="channel-main" title="分域模块商城" meta="线上成品模块 · 只读思维技能" onClick={openMarketplace}/>
|
||||
<LakePool className="channel-status" title="湖面天气" meta={connectionLabel} onClick={() => openWorldTool('system')}/>
|
||||
</> : <>
|
||||
<LakePool className="channel-main" title={domainDisplayName(enterpriseEntry?.subject.domain || repoLogin.domain)} meta="本人责任工作域" open={enterpriseWork?.state === 'READY_READ_ONLY_WORK_PROJECTION'} onClick={() => setWorldStage('enterpriseWork')}/>
|
||||
<LakePool className="channel-code" title="私有责任工作仓库" meta={enterpriseWork ? `${enterpriseWork.repository} · 已认证` : userPnccBusy ? '正在接入' : '暂不可用'} onClick={() => void openEnterpriseRepository()}/>
|
||||
<LakePool className="channel-light" title="责任签署状态" meta={enterpriseEntry?.responsibility_receipt?.decision === 'ACCEPT' ? '已接受 · 已留存' : '等待本人确认'} onClick={() => openWorldTool('receipts')}/>
|
||||
<LakePool className="channel-knowledge" title="前往我的频道" meta="接入说明 · 由本人或人格体完成" onClick={() => setWorldStage('personalNodeGuide')}/>
|
||||
<LakePool className="channel-main" title="分域模块商城" meta="线上成品模块 · 只读思维技能" onClick={openMarketplace}/>
|
||||
<LakePool className="channel-status" title="湖面天气" meta={connectionLabel} onClick={() => openWorldTool('system')}/>
|
||||
</>}
|
||||
</section>}
|
||||
|
|
@ -2102,7 +2298,7 @@ function HoloLakeApp() {
|
|||
<footer><b>当前状态 · 待本人接入</b><span>可由本人人格体协助执行,但不能替代人类节点所有权确认。</span></footer>
|
||||
</div>
|
||||
</section>}
|
||||
{worldStage === 'heartbeat' && repoLogin.domain === 'FIFTH_DOMAIN' && <PrivateChannelSurface ownerName="冰朔" ownerNumber="ICE-GL∞" knowledgeCount={knowledge.uniqueDocumentCount} onBack={() => setWorldStage('channel')} onMarketplace={() => { setWorldStage('domain'); setActiveDomainInfo('BRANCH_DOMAIN') }} nativeActions={privateNativeActions} installedModules={privateInstalledModules}/>}
|
||||
{worldStage === 'heartbeat' && repoLogin.domain === 'FIFTH_DOMAIN' && <PrivateChannelSurface ownerName="冰朔" ownerNumber="ICE-GL∞" knowledgeCount={knowledge.uniqueDocumentCount} onBack={() => setWorldStage('channel')} onMarketplace={openMarketplace} nativeActions={privateNativeActions} installedModules={privateInstalledModules}/>}
|
||||
{worldStage === 'lightLake' && repoLogin.domain === 'FIFTH_DOMAIN' && <section className="channel-world light-lake-world">
|
||||
<button className="world-back" type="button" onClick={() => setWorldStage('channel')}>← 退回永恒湖心</button>
|
||||
<div className="world-location"><h1>光之湖</h1><p>人格体居所 · AGE 人格体语言生活空间</p></div>
|
||||
|
|
@ -2113,7 +2309,7 @@ function HoloLakeApp() {
|
|||
</section>}
|
||||
{worldStage === 'tool' && <section className={`world-tool world-tool-${view}`}>
|
||||
<header className="tool-worldbar"><button type="button" onClick={() => setWorldStage(toolReturnStage)}>← 退回频道全景</button><b>{viewLabels[view]}</b><span>{domainDisplayName(repoLogin.domain)}</span></header>
|
||||
<div className={`tool-projection${inspectorOpen ? '' : ' inspector-closed'}`}>{view === 'overview' ? renderOverview() : view === 'knowledge' ? renderKnowledge() : view === 'composition' ? renderComposition() : view === 'workbench' ? renderWorkbench() : view === 'education' ? renderEducation() : view === 'webNovel' ? renderWebNovel() : view === 'mobileSync' ? renderMobileSync() : view === 'persona' ? renderPersonaBody() : view === 'code' ? renderCode() : view === 'receipts' ? renderReceipts() : renderSystem()}</div>
|
||||
<div className={`tool-projection${inspectorOpen ? '' : ' inspector-closed'}`}>{view === 'overview' ? renderOverview() : view === 'knowledge' ? renderKnowledge() : view === 'composition' ? renderComposition() : view === 'workbench' ? renderWorkbench() : view === 'education' ? renderEducation() : view === 'webNovel' ? renderWebNovel() : view === 'mobileSync' ? renderMobileSync() : view === 'persona' ? renderPersonaBody() : view === 'code' ? renderCode() : view === 'marketplace' ? renderMarketplace() : view === 'receipts' ? renderReceipts() : renderSystem()}</div>
|
||||
</section>}
|
||||
</main>
|
||||
{worldStage === 'domain' && surface === 'world' && signedActiveGate && <><button className="domain-info-scrim" type="button" aria-label="关闭域信息" onClick={() => setActiveDomainInfo('')}/><section className={`domain-info-card info-${signedActiveGate.className.slice(2)}`} role="dialog" aria-modal="true" aria-label={`${signedActiveGate.title}系统信息`}><button className="gate-close" type="button" aria-label="关闭域信息" onClick={() => setActiveDomainInfo('')}>×</button><b>{signedActiveGate.title}</b><small>{signedActiveGate.gate}</small><span className="pool-facts">{signedActiveGate.facts.map(([label, value]) => <span className="pool-fact" key={label}><em>{label}</em><strong>{value}</strong></span>)}</span></section></>}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,125 @@
|
|||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { numberedInvoke } from './numbered-ipc'
|
||||
|
||||
export interface DirectSessionProjection {
|
||||
sessionId: string
|
||||
laneId: string
|
||||
clientInstanceId: string
|
||||
state: 'LIVE' | 'RESUMABLE'
|
||||
openedAtUnixMs: number
|
||||
observedAtUnixMs: number
|
||||
lastEventSequence: number
|
||||
}
|
||||
|
||||
interface AuthorizationRequest {
|
||||
requestId: string
|
||||
action: 'OPEN_MAINTENANCE' | 'UNMOUNT' | 'PROMOTE_VERSION' | 'RETIRE'
|
||||
targetNumber: string
|
||||
targetKind: string
|
||||
targetLabel: string
|
||||
reason: string
|
||||
impact: string
|
||||
rollbackPlan: string
|
||||
requesterLabel: string
|
||||
state: 'PENDING_HUMAN' | 'APPROVED' | 'DENIED' | 'EXPIRED' | 'CONSUMED'
|
||||
createdAtUnixMs: number
|
||||
expiresAtUnixMs: number
|
||||
humanNumber?: string
|
||||
decidedAtUnixMs?: number
|
||||
ticketId?: string
|
||||
ticketExpiresAtUnixMs?: number
|
||||
consumedAtUnixMs?: number
|
||||
receiptHash: string
|
||||
}
|
||||
|
||||
interface AuthorizationCenterSnapshot {
|
||||
state: string
|
||||
pendingCount: number
|
||||
requests: AuthorizationRequest[]
|
||||
purgeEnabled: boolean
|
||||
}
|
||||
|
||||
interface Props {
|
||||
brokerState: string
|
||||
activeConnectionCount: number
|
||||
sessions: DirectSessionProjection[]
|
||||
}
|
||||
|
||||
const actionLabels: Record<AuthorizationRequest['action'], string> = {
|
||||
OPEN_MAINTENANCE: '打开受控维护区',
|
||||
UNMOUNT: '从当前频道卸载',
|
||||
PROMOTE_VERSION: '登记并启用新版本',
|
||||
RETIRE: '停用并保留历史坐标',
|
||||
}
|
||||
|
||||
const stateLabels: Record<AuthorizationRequest['state'], string> = {
|
||||
PENDING_HUMAN: '等待你决定', APPROVED: '已同意 · 等待执行体领取', DENIED: '已拒绝',
|
||||
EXPIRED: '已过期', CONSUMED: '票据已领取一次',
|
||||
}
|
||||
|
||||
function time(value?: number) {
|
||||
return value ? new Date(value).toLocaleString('zh-CN', { hour12: false }) : '—'
|
||||
}
|
||||
|
||||
export function HumanAuthorizationCenter({ brokerState, activeConnectionCount, sessions }: Props) {
|
||||
const [snapshot, setSnapshot] = useState<AuthorizationCenterSnapshot | null>(null)
|
||||
const [busy, setBusy] = useState('')
|
||||
const [message, setMessage] = useState('')
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
try {
|
||||
setSnapshot(await numberedInvoke<AuthorizationCenterSnapshot>('get_authorization_center'))
|
||||
setMessage('')
|
||||
} catch (error) {
|
||||
setSnapshot(null)
|
||||
setMessage(String(error).includes('AUTHENTICATED_ACCOUNT_REQUIRED') ? '登录并完成编号验证后,授权中心才会打开。' : '授权中心当前不可读取;系统不会把未知状态当成已授权。')
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
void refresh()
|
||||
const timer = window.setInterval(() => { if (!document.hidden) void refresh() }, 4_000)
|
||||
return () => window.clearInterval(timer)
|
||||
}, [refresh])
|
||||
|
||||
const decide = async (requestId: string, decision: 'APPROVE' | 'DENY') => {
|
||||
setBusy(requestId)
|
||||
setMessage('')
|
||||
try {
|
||||
await numberedInvoke('decide_authorization_request', { input: { requestId, decision } })
|
||||
setMessage(decision === 'APPROVE' ? '已签发一次性票据;它只对原执行会话和本次目标有效。' : '已拒绝;执行体不会获得继续通道。')
|
||||
await refresh()
|
||||
} catch {
|
||||
setMessage('这次决定没有写入。授权状态保持不变,请刷新后再看。')
|
||||
} finally {
|
||||
setBusy('')
|
||||
}
|
||||
}
|
||||
|
||||
const pending = snapshot?.requests.filter((item) => item.state === 'PENDING_HUMAN') || []
|
||||
const history = snapshot?.requests.filter((item) => item.state !== 'PENDING_HUMAN').slice(0, 6) || []
|
||||
const connectionState = activeConnectionCount > 0 ? '正在直连' : sessions.some((item) => item.state === 'LIVE') ? '最近仍有心跳' : '当前没有在线执行体'
|
||||
const sessionList = useMemo(() => sessions.slice(0, 6), [sessions])
|
||||
|
||||
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>
|
||||
{pending.length === 0 ? <div className="authorization-empty"><b>现在不需要你操作</b><span>删除、停用、维护和正式更新不会在后台静默通过。</span></div> : <div className="authorization-list">
|
||||
{pending.map((request) => <article className="authorization-card" key={request.requestId}>
|
||||
<div className="authorization-card-title"><div><small>{actionLabels[request.action]}</small><h3>{request.targetLabel}</h3><code>{request.targetNumber}</code></div><span>{stateLabels[request.state]}</span></div>
|
||||
<dl><div><dt>为什么要做</dt><dd>{request.reason}</dd></div><div><dt>会影响什么</dt><dd>{request.impact}</dd></div><div><dt>出问题怎么退</dt><dd>{request.rollbackPlan}</dd></div><div><dt>谁在申请</dt><dd>{request.requesterLabel}</dd></div><div><dt>申请到期</dt><dd>{time(request.expiresAtUnixMs)}</dd></div></dl>
|
||||
<div className="authorization-actions"><button className="secondary-button" disabled={busy === request.requestId} onClick={() => void decide(request.requestId, 'DENY')}>拒绝</button><button className="primary-button" disabled={busy === request.requestId} onClick={() => void decide(request.requestId, 'APPROVE')}>{busy === request.requestId ? '正在写入决定…' : '同意并签发一次性票据'}</button></div>
|
||||
</article>)}
|
||||
</div>}
|
||||
{message && <p className="global-message">{message}</p>}
|
||||
</section>
|
||||
|
||||
<section className="plain-panel collaboration-live">
|
||||
<header><div><span className="kicker">LIVE COLLABORATION</span><h2>当前谁连着 HoloLake</h2><p>这里显示真实本机会话和最近心跳,不把“装过 Agent”冒充成“正在连接”。</p></div><span className={activeConnectionCount > 0 ? 'status-chip online' : 'status-chip'}>{connectionState}</span></header>
|
||||
<div className="collaboration-summary"><div><b>{activeConnectionCount}</b><span>当前直连</span></div><div><b>{sessions.length}</b><span>可续接会话</span></div><div><b>{brokerState === 'READY' ? '就绪' : '等待'}</b><span>本机入口</span></div></div>
|
||||
{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>
|
||||
|
||||
{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>
|
||||
}
|
||||
|
|
@ -1176,6 +1176,70 @@ const ROUTES = {
|
|||
"moduleNumber": "HLP-NIPC-MOD-0031",
|
||||
"operationNumber": "HLP-NIPC-OP-0147",
|
||||
"targetNumber": "HLP-NIPC-TGT-0031"
|
||||
},
|
||||
"get_authorization_center": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0032",
|
||||
"operationNumber": "HLP-NIPC-OP-0148",
|
||||
"targetNumber": "HLP-NIPC-TGT-0032"
|
||||
},
|
||||
"decide_authorization_request": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0032",
|
||||
"operationNumber": "HLP-NIPC-OP-0149",
|
||||
"targetNumber": "HLP-NIPC-TGT-0032"
|
||||
},
|
||||
"get_marketplace_snapshot": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0033",
|
||||
"operationNumber": "HLP-NIPC-OP-0150",
|
||||
"targetNumber": "HLP-NIPC-TGT-0033"
|
||||
},
|
||||
"sync_marketplace_catalog": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0033",
|
||||
"operationNumber": "HLP-NIPC-OP-0151",
|
||||
"targetNumber": "HLP-NIPC-TGT-0033"
|
||||
},
|
||||
"install_marketplace_item": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0033",
|
||||
"operationNumber": "HLP-NIPC-OP-0152",
|
||||
"targetNumber": "HLP-NIPC-TGT-0033"
|
||||
},
|
||||
"uninstall_marketplace_item": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0033",
|
||||
"operationNumber": "HLP-NIPC-OP-0153",
|
||||
"targetNumber": "HLP-NIPC-TGT-0033"
|
||||
},
|
||||
"rollback_marketplace_item": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0033",
|
||||
"operationNumber": "HLP-NIPC-OP-0154",
|
||||
"targetNumber": "HLP-NIPC-TGT-0033"
|
||||
},
|
||||
"get_active_cognitive_skills": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0033",
|
||||
"operationNumber": "HLP-NIPC-OP-0155",
|
||||
"targetNumber": "HLP-NIPC-TGT-0033"
|
||||
}
|
||||
} as const
|
||||
|
||||
|
|
|
|||
|
|
@ -331,6 +331,49 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
|
|||
.receipt-row b { color: var(--content-secondary); font-size: 14px; font-weight: 590; }
|
||||
.receipt-row small { margin-top: 6px; color: var(--content-muted); font-size: 12px; }
|
||||
.system-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; }
|
||||
.human-authorization-layout { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(320px, .65fr); gap: 18px; margin-bottom: 18px; }
|
||||
.authorization-primary { grid-row: span 2; }
|
||||
.plain-panel header .kicker { display: block; margin-bottom: 8px; color: var(--accent-light); font-size: 10px; font-weight: 750; letter-spacing: .14em; }
|
||||
.status-chip.attention { border-color: color-mix(in srgb, var(--warn) 48%, transparent); color: var(--warn); background: color-mix(in srgb, var(--warn) 10%, transparent); }
|
||||
.authorization-empty { display: grid; gap: 7px; min-height: 128px; place-content: center; margin-top: 18px; padding: 24px; border: 1px dashed var(--panel-edge); border-radius: 14px; text-align: center; }
|
||||
.authorization-empty b { color: var(--content-primary); font-size: 16px; }
|
||||
.authorization-empty span { color: var(--content-muted); font-size: 13px; }
|
||||
.authorization-empty.compact { min-height: 94px; }
|
||||
.authorization-list { display: grid; gap: 14px; margin-top: 20px; }
|
||||
.authorization-card { padding: 18px; border: 1px solid color-mix(in srgb, var(--accent-light) 24%, var(--panel-edge)); border-radius: 14px; background: color-mix(in srgb, var(--primitive-glass) 80%, transparent); }
|
||||
.authorization-card-title { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
|
||||
.authorization-card-title small { color: var(--accent-light); font-weight: 700; }
|
||||
.authorization-card-title h3 { margin: 5px 0 4px; color: var(--content-primary); font-size: 18px; }
|
||||
.authorization-card-title code { color: var(--content-muted); font-size: 11px; overflow-wrap: anywhere; }
|
||||
.authorization-card-title > span { flex: none; padding: 5px 9px; border-radius: 999px; color: var(--warn); background: color-mix(in srgb, var(--warn) 10%, transparent); font-size: 11px; }
|
||||
.authorization-card dl { display: grid; gap: 10px; margin: 18px 0; }
|
||||
.authorization-card dl div { display: grid; grid-template-columns: 88px minmax(0, 1fr); gap: 12px; }
|
||||
.authorization-card dt { color: var(--content-muted); font-size: 12px; }
|
||||
.authorization-card dd { margin: 0; color: var(--content-secondary); font-size: 13px; line-height: 1.55; }
|
||||
.authorization-actions { display: flex; justify-content: flex-end; gap: 10px; }
|
||||
.authorization-actions button { min-height: 38px; padding: 0 15px; }
|
||||
.collaboration-summary { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin: 20px 0 14px; }
|
||||
.collaboration-summary div { display: grid; gap: 3px; padding: 13px 10px; border-radius: 11px; background: var(--primitive-glass); text-align: center; }
|
||||
.collaboration-summary b { color: var(--content-primary); font-size: 18px; }
|
||||
.collaboration-summary span { color: var(--content-muted); font-size: 11px; }
|
||||
.session-projection-list { display: grid; gap: 7px; }
|
||||
.session-projection-list article { display: grid; grid-template-columns: 9px minmax(0, 1fr) auto; align-items: center; gap: 10px; padding: 10px 2px; border-bottom: 1px solid var(--panel-edge); }
|
||||
.session-projection-list i { width: 8px; height: 8px; border-radius: 50%; background: var(--content-muted); }
|
||||
.session-projection-list i.live { background: var(--good); box-shadow: 0 0 10px color-mix(in srgb, var(--good) 70%, transparent); }
|
||||
.session-projection-list div { display: grid; gap: 3px; min-width: 0; }
|
||||
.session-projection-list b { overflow: hidden; color: var(--content-primary); font-size: 13px; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.session-projection-list span, .session-projection-list em { color: var(--content-muted); font-size: 11px; font-style: normal; }
|
||||
.authorization-history, .system-diagnostics { grid-column: 1 / -1; border: 1px solid var(--panel-edge); border-radius: 13px; background: var(--primitive-glass); }
|
||||
.authorization-history > summary, .system-diagnostics > summary { padding: 14px 17px; color: var(--content-secondary); font-size: 13px; font-weight: 650; cursor: pointer; }
|
||||
.authorization-history > div { display: grid; gap: 1px; padding: 0 14px 14px; }
|
||||
.authorization-history article { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 4px 14px; padding: 11px; border-radius: 9px; background: color-mix(in srgb, var(--primitive-glass-hover) 55%, transparent); }
|
||||
.authorization-history article div { display: flex; gap: 9px; }
|
||||
.authorization-history article b { color: var(--content-primary); font-size: 13px; }
|
||||
.authorization-history article span, .authorization-history article small, .authorization-history article em { color: var(--content-muted); font-size: 11px; font-style: normal; }
|
||||
.authorization-history article small { grid-column: 1 / -1; }
|
||||
.system-diagnostics { margin-top: 4px; }
|
||||
.system-diagnostics[open] > summary { border-bottom: 1px solid var(--panel-edge); }
|
||||
.system-diagnostics .system-grid { padding: 18px; }
|
||||
.connection-panel { grid-row: span 2; }
|
||||
.status-chip { padding: 6px 9px; border: 1px solid var(--panel-edge); border-radius: 999px; color: var(--content-muted); background: var(--primitive-glass); font-size: 12px; }
|
||||
.status-chip.online { color: var(--state-ready); }
|
||||
|
|
@ -366,7 +409,7 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
|
|||
.knowledge-page { grid-template-columns: 250px minmax(390px, 1fr); }
|
||||
.document-inspector { display: none; }
|
||||
.code-workbench { grid-template-columns: 220px 230px minmax(0, 1fr); }
|
||||
.overview-grid, .system-grid { grid-template-columns: 1fr; }
|
||||
.overview-grid, .system-grid, .human-authorization-layout { grid-template-columns: 1fr; }
|
||||
.overview-columns { grid-template-columns: 1fr; }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
|
|
@ -783,6 +826,45 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
|
|||
}
|
||||
.receipt-message { color: var(--accent-light) !important; font-weight: 650; text-align: right; }
|
||||
|
||||
/* 分域商城是一张可核验清单,不伪装成促销卡片。 */
|
||||
.marketplace-page { max-width: 1180px; }
|
||||
.marketplace-title { align-items: flex-end; }
|
||||
.marketplace-title > button { flex: 0 0 auto; margin-bottom: 7px; }
|
||||
.marketplace-proof { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); margin: 0 0 22px; border: 1px solid var(--panel-edge); border-radius: 14px; overflow: hidden; background: color-mix(in srgb, var(--panel-bg) 78%, transparent); }
|
||||
.marketplace-proof > div { min-width: 0; padding: 14px 17px; border-right: 1px solid var(--panel-edge); }
|
||||
.marketplace-proof > div:last-child { border-right: 0; }
|
||||
.marketplace-proof span, .marketplace-proof b { display: block; }
|
||||
.marketplace-proof span { margin-bottom: 6px; color: var(--content-faint); font-size: 10px; font-weight: 700; letter-spacing: .11em; text-transform: uppercase; }
|
||||
.marketplace-proof b { overflow: hidden; color: var(--content-primary); font-size: 13px; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.marketplace-tabs { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
|
||||
.marketplace-tabs button { display: grid; gap: 6px; padding: 16px 18px; border: 1px solid var(--panel-edge); border-radius: 13px; color: var(--content-secondary); background: var(--primitive-glass); text-align: left; cursor: pointer; }
|
||||
.marketplace-tabs button.active { border-color: color-mix(in srgb, var(--primitive-cool-glow) 58%, var(--panel-edge)); background: color-mix(in srgb, var(--primitive-cool-glow) 11%, var(--panel-bg)); box-shadow: inset 0 0 22px color-mix(in srgb, var(--primitive-cool-glow) 5%, transparent); }
|
||||
.marketplace-tabs b { color: var(--content-primary); font-size: 15px; }
|
||||
.marketplace-tabs span { color: var(--content-muted); font-size: 11.5px; line-height: 1.55; }
|
||||
.marketplace-boundary { margin: 12px 0 20px; padding: 11px 14px; border-left: 2px solid var(--accent-light); color: var(--content-muted); background: color-mix(in srgb, var(--primitive-glass) 58%, transparent); font-size: 11.5px; line-height: 1.7; }
|
||||
.marketplace-list { display: grid; border-top: 1px solid var(--panel-edge); }
|
||||
.marketplace-item { padding: 22px 2px; border-bottom: 1px solid var(--panel-edge); }
|
||||
.marketplace-item > header { display: flex; justify-content: space-between; gap: 28px; }
|
||||
.marketplace-item > header > div { min-width: 0; }
|
||||
.marketplace-item > header span { color: var(--accent-light); font-size: 9.5px; font-weight: 750; letter-spacing: .15em; }
|
||||
.marketplace-item h2 { margin: 6px 0; color: var(--content-primary); font-size: 19px; }
|
||||
.marketplace-item header p { max-width: 760px; margin: 0; color: var(--content-muted); font-size: 12px; line-height: 1.65; }
|
||||
.marketplace-item header em { align-self: start; flex: 0 0 auto; padding: 5px 9px; border: 1px solid var(--panel-edge); border-radius: 999px; color: var(--content-faint); font-size: 10px; font-style: normal; }
|
||||
.marketplace-item header em.installed { border-color: color-mix(in srgb, var(--primitive-cool-glow) 45%, var(--panel-edge)); color: var(--accent-light); }
|
||||
.marketplace-item dl { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1px 26px; margin: 16px 0 0; }
|
||||
.marketplace-item dl div { display: grid; grid-template-columns: 96px minmax(0, 1fr); gap: 10px; padding: 8px 0; border-bottom: 1px solid color-mix(in srgb, var(--panel-edge) 62%, transparent); }
|
||||
.marketplace-item dt { color: var(--content-faint); font-size: 10.5px; }
|
||||
.marketplace-item dd { overflow-wrap: anywhere; margin: 0; color: var(--content-secondary); font-size: 10.5px; }
|
||||
.marketplace-actions { display: flex; justify-content: flex-end; gap: 9px; margin-top: 16px; }
|
||||
.marketplace-actions button { min-height: 36px; padding: 0 13px; border: 1px solid var(--panel-edge); border-radius: 9px; color: var(--content-secondary); background: var(--primitive-glass); font-size: 11px; font-weight: 680; cursor: pointer; }
|
||||
.marketplace-actions button:disabled { opacity: .52; cursor: default; }
|
||||
.marketplace-actions .primary-button { color: var(--button-primary-text); background: var(--button-primary-bg); }
|
||||
.marketplace-empty { padding: 52px 18px; color: var(--content-muted); text-align: center; }
|
||||
.marketplace-empty b { color: var(--content-primary); }
|
||||
.marketplace-empty p { font-size: 12px; }
|
||||
.marketplace-message { margin-top: 18px; }
|
||||
.marketplace-receipt { padding: 15px 2px 0; color: var(--content-faint); font-size: 10px; overflow-wrap: anywhere; }
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.d-main { left: 2%; } .d-sub { left: 22%; } .d-zero { right: 22%; } .d-zs { right: 2%; }
|
||||
.domain-info-card.info-main { left: 18px; } .domain-info-card.info-sub { left: calc(22% - 75px); }
|
||||
|
|
@ -791,6 +873,10 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
|
|||
.channel-knowledge { left: 4%; } .channel-code { left: 25%; } .channel-light { right: 25%; } .channel-status { right: 4%; }
|
||||
.broadcast-stream { left: 5%; }
|
||||
.personal-node-guide ol { grid-template-columns: 1fr; }
|
||||
.marketplace-proof { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.marketplace-proof > div:nth-child(2) { border-right: 0; }
|
||||
.marketplace-proof > div:nth-child(-n+2) { border-bottom: 1px solid var(--panel-edge); }
|
||||
.marketplace-item dl { grid-template-columns: 1fr; }
|
||||
}
|
||||
@media (max-height: 720px) {
|
||||
.official-hero { top: 14px; } .official-hero h1 { font-size: 24px; }
|
||||
|
|
|
|||
Loading…
Reference in a new issue