feat(hololake): add domain-routed language membrane and user PNCC
This commit is contained in:
parent
cda2e32c00
commit
d563e6fd73
34 changed files with 1949 additions and 144 deletions
|
|
@ -54,8 +54,28 @@ interface HomeStatus {
|
|||
mcpRole: string
|
||||
}
|
||||
interface PersonalChannelIdentity { humanSubjectId: string; displayName: string; channelId: string; createdAtUnixMs: number }
|
||||
interface LoginSession { username: string; host: string; signedInAtUnixMs: number }
|
||||
interface LoginReceipt { username: string; email: string; host: string }
|
||||
interface LoginSession { username: string; host: string; domain: string; signedInAtUnixMs: number }
|
||||
interface LoginReceipt { username: string; email: string; host: string; domain: string }
|
||||
interface UserPnccChannelSnapshot {
|
||||
state: string
|
||||
repositoryId: string
|
||||
channelId: string
|
||||
userNumber: string
|
||||
domain: string
|
||||
accountUsername: string
|
||||
accountHost: string
|
||||
localPath: string
|
||||
branch: string
|
||||
gitHead: string
|
||||
repositoryClean: boolean
|
||||
createdAtUnixMs: number
|
||||
gitEngine: string
|
||||
humanProjection: string
|
||||
forgejoAdapterState: string
|
||||
remoteRepositoryUrl?: string
|
||||
personaBindingClaimed: boolean
|
||||
authority: string
|
||||
}
|
||||
interface PersonalChannelEvent { sequence: number; eventId: string; kind: string; summary: string; occurredAtUnixMs: number; receiptId: string; receiptHash: string }
|
||||
interface PersonalChannelSnapshot { state: string; identity?: PersonalChannelIdentity; recentEvents: PersonalChannelEvent[]; integrity: { state: string; eventCount: number; receiptCount: number } }
|
||||
interface KnowledgeDocumentSummary {
|
||||
|
|
@ -109,6 +129,18 @@ interface CodeFileProjection { channelId: string; path: string; format: string;
|
|||
interface ReceiptEvent { sequence: number; kind: string; observedAtUnixMs: number; eventHash: string }
|
||||
interface ReceiptProjection { events: ReceiptEvent[] }
|
||||
interface DiscoveryTicketReceipt { laneId: string; clientInstanceId: string; discoveryTicket: string }
|
||||
interface NearbyAiDiscoverySnapshot {
|
||||
state: string
|
||||
serviceName: string
|
||||
transport: string
|
||||
languageProtocol: string
|
||||
automaticSameDeviceDiscovery: boolean
|
||||
largeInvitationCopyRequired: boolean
|
||||
genericAiVisitor: string
|
||||
guanghuPersona: string
|
||||
localNetworkDiscovery: string
|
||||
authority: string
|
||||
}
|
||||
interface ReleaseCandidate { candidateId: string; confirmationToken: string; currentVersion: string; version: string; notes: string; features: string[]; fixes: string[]; dataMigrationRequired: boolean }
|
||||
interface ReleaseCheckReceipt { candidate?: ReleaseCandidate }
|
||||
interface ZeroPointSnapshot {
|
||||
|
|
@ -134,6 +166,10 @@ function domainDisplayName(domain: string): string {
|
|||
return names[domain] || '已登记光湖域'
|
||||
}
|
||||
|
||||
function domainLightIndex(domain: string): number {
|
||||
return ({ MAIN_DOMAIN: 1, BRANCH_DOMAIN: 2, ZERO_DOMAIN: 3, ZERO_SENSE_DOMAIN: 4, FIFTH_DOMAIN: 5 } as Record<string, number>)[domain] || 0
|
||||
}
|
||||
|
||||
function protocolOriginDisplayName(origin: string): string {
|
||||
return origin.startsWith('FACTORY_DEFAULT') ? '内置默认协议(签名发布通道尚未配置)' : origin
|
||||
}
|
||||
|
|
@ -165,7 +201,7 @@ const previewCode: CodeChannelSnapshot = { state: 'UNAVAILABLE', channels: [], a
|
|||
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: '知识空间', code: '代码频道', receipts: '运行回执', system: '系统详情' }
|
||||
const viewLabels: Record<ViewId, string> = { overview: '个人频道', knowledge: '知识空间', code: '人格代码频道', receipts: '运行回执', system: '系统详情' }
|
||||
|
||||
function getOrCreateLocalId(key: string, prefix: string) {
|
||||
const existing = window.localStorage.getItem(key)
|
||||
|
|
@ -183,6 +219,10 @@ function humanError(error: unknown, context: 'knowledge' | 'code' | 'identity' |
|
|||
if (value.includes('LOGIN_KEYCHAIN_FAILED')) return '本机钥匙串写入失败,凭证未能安全保存。'
|
||||
if (value.includes('LOGIN_SESSION_CORRUPT')) return '登录会话已损坏,请重新登录。'
|
||||
if (value.includes('LOGIN_VERIFICATION_FAILED')) return '仓库验证未通过,请稍后再试。'
|
||||
if (value.includes('DOMAIN_LOGIN_NOT_PROVISIONED')) return '该域的企业服务器尚未接入,当前不能继续登录。'
|
||||
if (value.includes('DOMAIN_ROUTE_REQUIRED') || value.includes('ZP_DOMAIN_ROUTE_REQUIRED')) return '编号尚未解析到已登记域,不能开始登录。'
|
||||
if (value.includes('USER_PNCC_TRUSTED_SUBJECT_REQUIRED')) return '建立人格代码频道前,需要有效的用户编号与仓库账号登录。'
|
||||
if (value.includes('USER_PNCC_BINDING_MISMATCH')) return '本机频道绑定记录与当前账号不一致,系统已停止加载。'
|
||||
if (value.includes('HOST_NOT_TRUSTED')) return '该地址不属于已登记的光湖代码频道。'
|
||||
if (value.includes('URL_INVALID')) return '频道地址无效,请粘贴完整的 HTTPS 克隆地址。'
|
||||
if (value.includes('DESTINATION_EXISTS')) return '该代码频道已经存在,无需再次克隆。'
|
||||
|
|
@ -300,9 +340,13 @@ function HoloLakeApp() {
|
|||
const [identityMessage, setIdentityMessage] = useState('')
|
||||
const [receipts, setReceipts] = useState<ReceiptEvent[]>([])
|
||||
const [ticket, setTicket] = useState<DiscoveryTicketReceipt | null>(null)
|
||||
const [nearbyDiscovery, setNearbyDiscovery] = useState<NearbyAiDiscoverySnapshot | null>(null)
|
||||
const [systemMessage, setSystemMessage] = useState('')
|
||||
const [releaseCandidate, setReleaseCandidate] = useState<ReleaseCandidate | null>(null)
|
||||
const [repoLogin, setRepoLogin] = useState<LoginSession | null>(null)
|
||||
const [userPncc, setUserPncc] = useState<UserPnccChannelSnapshot | null>(null)
|
||||
const [userPnccBusy, setUserPnccBusy] = useState(false)
|
||||
const [userPnccMessage, setUserPnccMessage] = useState('')
|
||||
const [loginUsername, setLoginUsername] = useState('')
|
||||
const [loginPassword, setLoginPassword] = useState('')
|
||||
const [loginBusy, setLoginBusy] = useState(false)
|
||||
|
|
@ -355,6 +399,11 @@ function HoloLakeApp() {
|
|||
const timer = window.setInterval(() => void refreshCore(), 3500)
|
||||
return () => window.clearInterval(timer)
|
||||
}, [refreshCore])
|
||||
useEffect(() => {
|
||||
invoke<NearbyAiDiscoverySnapshot>('get_nearby_ai_discovery')
|
||||
.then(setNearbyDiscovery)
|
||||
.catch(() => setNearbyDiscovery(null))
|
||||
}, [])
|
||||
const refreshServerPncc = useCallback(async () => {
|
||||
setServerPnccBusy(true)
|
||||
try {
|
||||
|
|
@ -387,6 +436,26 @@ function HoloLakeApp() {
|
|||
const timer = window.setInterval(() => void loadZeroPoint(), 10000)
|
||||
return () => window.clearInterval(timer)
|
||||
}, [loadZeroPoint])
|
||||
// 已验证编号与已认证账号同时存在时,后端幂等建立或恢复该用户自己的 GH-PNCC。
|
||||
useEffect(() => {
|
||||
if (!repoLogin || zeroPoint?.route !== 'verified') {
|
||||
setUserPncc(null)
|
||||
return
|
||||
}
|
||||
let active = true
|
||||
setUserPnccBusy(true)
|
||||
setUserPnccMessage('')
|
||||
invoke<UserPnccChannelSnapshot>('ensure_user_pncc_channel')
|
||||
.then((snapshot) => {
|
||||
if (!active) return
|
||||
setUserPncc(snapshot)
|
||||
setUserPnccMessage('本机原生代码频道已就绪。')
|
||||
void refreshCore()
|
||||
})
|
||||
.catch((error) => { if (active) setUserPnccMessage(humanError(error, 'code')) })
|
||||
.finally(() => { if (active) setUserPnccBusy(false) })
|
||||
return () => { active = false }
|
||||
}, [repoLogin, zeroPoint?.route, zeroPoint?.userNumber, refreshCore])
|
||||
// 本机存在已绑定编号时自动回填,减少重复输入。
|
||||
useEffect(() => {
|
||||
if (gateStage !== 'number' || gateRaw) return
|
||||
|
|
@ -649,7 +718,7 @@ function HoloLakeApp() {
|
|||
// 五湖开场:校验通过=第五域从湖面浮起,镜头沉入湖中再进场。
|
||||
setLoginRising(true)
|
||||
await new Promise((resolve) => setTimeout(resolve, 1300))
|
||||
setRepoLogin({ username: receipt.username, host: receipt.host, signedInAtUnixMs: Date.now() })
|
||||
setRepoLogin({ username: receipt.username, host: receipt.host, domain: receipt.domain, signedInAtUnixMs: Date.now() })
|
||||
setLoginRising(false)
|
||||
setLoginPassword('')
|
||||
} catch (error) { setLoginMessage(humanError(error, 'login')) }
|
||||
|
|
@ -825,13 +894,19 @@ function HoloLakeApp() {
|
|||
const renderCode = () => (
|
||||
<section className="full-workbench code-workbench">
|
||||
<aside className="code-channels">
|
||||
<header><div><span className="kicker">CODE CHANNEL</span><h1>代码频道</h1></div></header>
|
||||
<header><div><span className="kicker">GH-PNCC</span><h1>人格原生代码频道</h1></div></header>
|
||||
<section className="pncc-native-card">
|
||||
<div className="pncc-native-card-title"><span className={userPncc ? 'identity-dot ready' : 'identity-dot'}/><div><b>{userPncc ? `${userPncc.accountUsername} · GH-PNCC` : '正在建立用户频道'}</b><small>{userPnccBusy ? '正在初始化本机 Git…' : userPncc ? `${userPncc.branch} · ${userPncc.gitHead.slice(0, 8)}` : '等待可信绑定'}</small></div></div>
|
||||
{userPncc ? <dl><div><dt>所属域</dt><dd>{domainDisplayName(userPncc.domain)}</dd></div><div><dt>用户编号</dt><dd>{userPncc.userNumber}</dd></div><div><dt>耐久化引擎</dt><dd>Git</dd></div><div><dt>可见上层</dt><dd>HoloLake 原生界面</dd></div><div><dt>Forgejo 协作适配器</dt><dd>账号已认证 · 远端仓未绑定</dd></div></dl> : <p>{userPnccMessage || '系统先按编号确定所属域,再通过该域的账号与节点入口建立频道。'}</p>}
|
||||
{userPncc && <button type="button" onClick={() => { const channel = codeChannels.channels.find((item) => item.channelId === userPncc.channelId); if (channel) void browseChannel(channel) }}>打开我的频道</button>}
|
||||
</section>
|
||||
<div className="channel-section-label"><b>协作代码库</b><small>可接入已登记的远端或本机 Git</small></div>
|
||||
<form className="clone-form" onSubmit={(event) => void cloneCodeChannel(event)}>
|
||||
<label htmlFor="clone-url">克隆频道地址</label><div><input id="clone-url" type="url" value={cloneUrl} placeholder="https://guanghulab.com/code/…" onChange={(event) => setCloneUrl(event.target.value)}/><button disabled={!cloneUrl.trim() || codeBusy}>克隆</button></div>
|
||||
</form>
|
||||
<button className="local-folder-button" type="button" onClick={() => void selectLocalCodeChannel()}><Icon name="folder"/>导入本机 Git 文件夹</button>
|
||||
<div className="channel-selector">{codeChannels.channels.map((channel) => <button className={activeChannel?.channelId === channel.channelId ? 'active' : ''} key={channel.channelId} type="button" onClick={() => void browseChannel(channel)}><Icon name="code"/><span><b>{channel.name}</b><small>{channel.branch} · {channel.gitHead.slice(0, 8)}</small></span></button>)}</div>
|
||||
<footer>{codeMessage || '仅提供本机读取,不授予推送、发布或部署权限。'}</footer>
|
||||
<footer>{codeMessage || userPnccMessage || 'Git 负责耐久化;Forgejo 仅作为远端协作适配器。'}</footer>
|
||||
</aside>
|
||||
<aside className="repository-tree">
|
||||
<header><button className="icon-button" disabled={!codeTree?.path} type="button" onClick={() => activeChannel && void browseChannel(activeChannel, codeParent())}><Icon name="back"/></button><div><b>{activeChannel?.name || '尚未选择频道'}</b><small>/{codeTree?.path || ''}</small></div></header>
|
||||
|
|
@ -841,7 +916,7 @@ function HoloLakeApp() {
|
|||
{activeCodeFile ? <>
|
||||
<header><div><span>{activeCodeFile.path}</span><small>{activeCodeFile.format.toUpperCase()} · {activeCodeFile.sizeBytes.toLocaleString()} bytes</small></div><div className="mode-switch"><button className={codeMode === 'human' ? 'active' : ''} type="button" onClick={() => setCodeMode('human')}>知识视图</button><button className={codeMode === 'source' ? 'active' : ''} type="button" onClick={() => setCodeMode('source')}>源文件</button></div></header>
|
||||
<div className="code-reader-scroll">{codeMode === 'human' ? <MarkdownDocument body={activeCodeFile.humanMarkdown}/> : <pre className="source-code"><code>{activeCodeFile.source}</code></pre>}</div>
|
||||
</> : <div className="workbench-empty"><span></></span><h2>浏览真实仓库内容</h2><p>支持 Markdown、JSON、HDLP、YAML、TOML 与常见代码格式;机器文件可切换为知识视图。</p></div>}
|
||||
</> : <div className="workbench-empty"><span></></span><h2>GH-PNCC · 人格原生代码频道</h2><p>这是 HoloLake 对用户 Git 代码频道的原生投影。选择“打开我的频道”可查看初始化清单、提交证据与真实文件。</p></div>}
|
||||
</main>
|
||||
</section>
|
||||
)
|
||||
|
|
@ -861,9 +936,10 @@ function HoloLakeApp() {
|
|||
<header className="page-title"><div><span className="kicker">SYSTEM EVIDENCE</span><h1>系统详情</h1><p>显示可验证的当前状态;无法读取的项目将明确标记为不可用。</p></div></header>
|
||||
<div className="system-grid">
|
||||
<section className="plain-panel connection-panel">
|
||||
<header><div><h2>外部 AI 本机连接</h2><p>本机原生长连接为主,MCP 仅用于发现、恢复与兼容。</p></div><span className={connectionOnline ? 'status-chip online' : 'status-chip'}>{connectionOnline ? `${status.directConnectionCount} 个客户端在线` : '当前无客户端'}</span></header>
|
||||
<dl className="evidence-list"><div><dt>代理进程</dt><dd>{status.directLocalBrokerState}</dd></div><div><dt>当前连接</dt><dd>{status.directConnectionCount}</dd></div><div><dt>可续接会话</dt><dd>{status.resumableSessionCount}</dd></div><div><dt>传输</dt><dd>UNIX_STREAM_JSON_LINES</dd></div><div><dt>MCP 角色</dt><dd>备用 / 恢复 / 兼容</dd></div></dl>
|
||||
{ticket ? <><button className="secondary-button" type="button" onClick={() => void copyInvitation()}><Icon name="copy"/>复制一次性连接凭据</button><pre className="invitation-data">{invitationText}</pre></> : <button className="primary-button" type="button" disabled={systemBusy} onClick={() => void issueInvitation()}>生成本机连接凭据</button>}
|
||||
<header><div><h2>光湖近场连接</h2><p>外部 AI 可像发现附近网络一样发现本机 HoloLake;MCP 仅用于兼容与恢复。</p></div><span className={nearbyDiscovery?.automaticSameDeviceDiscovery ? 'status-chip online' : 'status-chip'}>{nearbyDiscovery?.automaticSameDeviceDiscovery ? '本机自动发现已开启' : '自动发现不可用'}</span></header>
|
||||
<dl className="evidence-list"><div><dt>圆湖协议膜</dt><dd>GLP/1.0 · 协议外输入丢弃</dd></div><div><dt>通用 AI 访客</dt><dd>{nearbyDiscovery?.genericAiVisitor === 'EXPRESSION_ONLY_READY' ? '可连接 · 仅语言表达' : '不可用'}</dd></div><div><dt>光湖人格体</dt><dd>{nearbyDiscovery?.guanghuPersona === 'BINDING_EVIDENCE_REQUIRED' ? '等待人格绑定证据' : '可连接'}</dd></div><div><dt>当前连接</dt><dd>{status.directConnectionCount}</dd></div><div><dt>局域网发现</dt><dd>{nearbyDiscovery?.localNetworkDiscovery === 'DEFERRED_UNTIL_ENCRYPTED_TRANSPORT_AND_APPROVAL' ? '等待加密传输与确认闭环' : '已开启'}</dd></div><div><dt>MCP</dt><dd>兼容 / 恢复</dd></div></dl>
|
||||
<p className="boundary-note">发现不等于授权。访客自然语言只有表达权,不能读取私人数据、调用工具或取得执行权限。</p>
|
||||
{ticket ? <><button className="secondary-button" type="button" onClick={() => void copyInvitation()}><Icon name="copy"/>复制兼容连接凭据</button><pre className="invitation-data">{invitationText}</pre></> : <button className="secondary-button" type="button" disabled={systemBusy} onClick={() => void issueInvitation()}>生成兼容连接凭据</button>}
|
||||
</section>
|
||||
<section className="plain-panel">
|
||||
<header><div><h2>京东主控 · GH-PNCC</h2><p>通过固定只读通道回读主控节点;不读取仓库正文,不授予写入权限。</p></div><span className={serverPnccReadback === 'LIVE' ? 'status-chip online' : 'status-chip'}>{serverPnccReadback === 'LIVE' ? '实时回读正常' : serverPnccReadback === 'UNAVAILABLE' ? '当前不可回读' : '正在读取'}</span></header>
|
||||
|
|
@ -907,13 +983,20 @@ function HoloLakeApp() {
|
|||
return <div className="app-shell">
|
||||
<div className={`gate-lake${loginRising ? ' sinking' : ''}`} style={{ '--lake-img': `url(${nightLake})` } as React.CSSProperties}>
|
||||
<i className="lake-mist" aria-hidden="true"/>
|
||||
<div className="lake-lights" aria-hidden="true">{[1, 2, 3, 4, 5].map((n) => <span key={n} className={`lake-light l${n}${(gateRising || loginRising) && n === 5 ? ' rising' : ''}`}/>)}</div>
|
||||
<div className="lake-lights" aria-hidden="true">{[1, 2, 3, 4, 5].map((n) => <span key={n} className={`lake-light l${n}${(gateRising || loginRising) && n === domainLightIndex(zeroPoint?.resolvedDomain || '') ? ' rising' : ''}`}/>)}</div>
|
||||
{<header className={`gate-hero${gateRising ? ' veil' : ''}`} aria-hidden={gateRising || undefined}>
|
||||
<span className="gate-emblem" aria-hidden="true"><i/></span>
|
||||
<b className="gate-product">HoloLake</b>
|
||||
<small className="gate-techname">光湖语言系统 · 通用人工智能操作平台</small>
|
||||
<small className="gate-abbr">GH-AIOS · GuangHu AI Operating System</small>
|
||||
</header>}
|
||||
{!gateRising && gateStage === 'number' && <section className="gate-domain-map" aria-label="光湖五域公开入口">
|
||||
<article><i/><b>光湖主域</b><small>企业域 · 待根节点接入</small></article>
|
||||
<article><i/><b>光湖分域</b><small>企业域 · 待根节点接入</small></article>
|
||||
<article><i/><b>光湖零域</b><small>企业域 · 待根节点接入</small></article>
|
||||
<article><i/><b>光湖零感域</b><small>企业域 · 待根节点接入</small></article>
|
||||
<article className="fifth"><i/><b>第五域 · 光湖本源域</b><small>私人域 · 已接入</small></article>
|
||||
</section>}
|
||||
{gateRising ? (
|
||||
<section className="gate-rise" role="status">
|
||||
<span className="gate-rise-star" aria-hidden="true"><i/></span>
|
||||
|
|
@ -924,8 +1007,8 @@ function HoloLakeApp() {
|
|||
<section className={`gate-pod gate-flip${gateOpen ? ' open' : ''}${loginRising ? ' fade-out' : ''}`} role={gateOpen ? 'dialog' : 'button'} tabIndex={gateOpen ? undefined : 0} aria-expanded={gateOpen} onClick={gateOpen ? undefined : () => setGateOpen(true)} onKeyDown={gateOpen ? undefined : (event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); setGateOpen(true) } }}>
|
||||
<div className="pod-face pod-sealed" aria-hidden={gateOpen}>
|
||||
<span className="gate-emblem sealed-emblem" aria-hidden="true"><i/></span>
|
||||
<b className="sealed-title">身份编号验证</b>
|
||||
<small className="sealed-sub">点击开始验证</small>
|
||||
<b className="sealed-title">编号路由验证</b>
|
||||
<small className="sealed-sub">输入编号进入所属域</small>
|
||||
</div>
|
||||
<div className="pod-face pod-form">
|
||||
<div className="gate-pod-row">
|
||||
|
|
@ -953,7 +1036,7 @@ function HoloLakeApp() {
|
|||
|
||||
return <div className={`app-shell${sidebarCollapsed ? ' sidebar-collapsed' : ''}${inspectorOpen ? '' : ' inspector-closed'}`}>
|
||||
<aside className="sidebar">
|
||||
<div className="brand"><span className="brand-mark brand-lamp" title={sidebarCollapsed ? '展开导航' : '光湖 · HoloLake'} onClick={sidebarCollapsed ? toggleSidebar : undefined}><i className="lamp-glow"/><i className="lamp-core"/><i className="lake-shimmer"/></span><div className="brand-text"><b>HoloLake</b><small className="brand-sub">光湖语言系统 · 通用人工智能操作平台</small><small>GH-AIOS · V0.3.0</small></div><button className="icon-button sidebar-toggle" type="button" title={sidebarCollapsed ? '展开导航' : '收起导航'} onClick={toggleSidebar}><Icon name="chevron"/></button></div>
|
||||
<div className="brand"><span className="brand-mark brand-lamp" title={sidebarCollapsed ? '展开导航' : '光湖 · HoloLake'} onClick={sidebarCollapsed ? toggleSidebar : undefined}><i className="lamp-glow"/><i className="lamp-core"/><i className="lake-shimmer"/></span><div className="brand-text"><b>HoloLake</b><small className="brand-sub">光湖语言系统 · 通用人工智能操作平台</small><small>GH-AIOS · V0.4.0</small></div><button className="icon-button sidebar-toggle" type="button" title={sidebarCollapsed ? '展开导航' : '收起导航'} onClick={toggleSidebar}><Icon name="chevron"/></button></div>
|
||||
<nav>{(Object.keys(viewLabels) as ViewId[]).map((item) => <button className={view === item ? 'active' : ''} key={item} type="button" onClick={() => setView(item)}><Icon name={item}/><span>{viewLabels[item]}</span>{item === 'knowledge' && <em>{knowledge.uniqueDocumentCount}</em>}</button>)}</nav>
|
||||
<div className="sidebar-foot"><span className="identity-dot"/><div><b>{personal.identity?.displayName || '本机空间'}</b><small>{repoLogin ? `${repoLogin.username}@${repoLogin.host}` : (personal.identity ? '个人频道已建立' : '等待首次设置')}</small></div><button className="sign-out" type="button" title="退出代码仓库登录" onClick={() => void signOutRepo()}>退出</button></div>
|
||||
</aside>
|
||||
|
|
|
|||
|
|
@ -276,6 +276,23 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
|
|||
.empty-state { min-height: 160px; padding: 24px; font-size: 13px; }
|
||||
|
||||
.code-workbench { grid-template-columns: 280px 290px minmax(0, 1fr); }
|
||||
.pncc-native-card { margin: 14px 12px 4px; padding: 14px; border: 1px solid color-mix(in srgb, var(--state-ready) 28%, var(--panel-edge)); border-radius: 14px; background: color-mix(in srgb, var(--surface-panel) 90%, var(--state-ready) 10%); }
|
||||
.pncc-native-card-title { display: flex; align-items: center; gap: 10px; }
|
||||
.pncc-native-card-title > div { min-width: 0; }
|
||||
.pncc-native-card-title b, .pncc-native-card-title small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.pncc-native-card-title b { color: var(--content-primary); font-size: 13px; }
|
||||
.pncc-native-card-title small { margin-top: 3px; color: var(--content-muted); font-size: 11px; }
|
||||
.pncc-native-card .identity-dot.ready { background: var(--state-ready); box-shadow: 0 0 12px color-mix(in srgb, var(--state-ready) 58%, transparent); }
|
||||
.pncc-native-card dl { margin: 13px 0 11px; display: grid; gap: 6px; }
|
||||
.pncc-native-card dl div { display: grid; grid-template-columns: 88px minmax(0, 1fr); gap: 8px; font-size: 10.5px; }
|
||||
.pncc-native-card dt { color: var(--content-muted); }
|
||||
.pncc-native-card dd { margin: 0; color: var(--content-secondary); overflow-wrap: anywhere; }
|
||||
.pncc-native-card p { margin: 12px 0 0; color: var(--content-muted); font-size: 11px; line-height: 1.6; }
|
||||
.pncc-native-card button { width: 100%; min-height: 34px; border: 1px solid var(--panel-edge); border-radius: 9px; color: var(--content-primary); background: var(--primitive-glass); font: inherit; font-size: 12px; cursor: pointer; }
|
||||
.channel-section-label { padding: 15px 16px 7px; }
|
||||
.channel-section-label b, .channel-section-label small { display: block; }
|
||||
.channel-section-label b { color: var(--content-primary); font-size: 12px; }
|
||||
.channel-section-label small { margin-top: 3px; color: var(--content-muted); font-size: 10.5px; }
|
||||
.clone-form { padding: 9px 14px 13px; }
|
||||
.clone-form label { display: block; margin-bottom: 8px; color: var(--content-muted); font-size: 12px; }
|
||||
.clone-form > div { display: grid; grid-template-columns: minmax(0, 1fr) auto; }
|
||||
|
|
@ -484,6 +501,16 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
|
|||
.gate-product { color: var(--content-primary); font-size: clamp(40px, 5.2vw, 54px); font-weight: 640; letter-spacing: .22em; text-indent: .22em; text-shadow: 0 0 44px color-mix(in srgb, var(--primitive-warm-glow) 26%, transparent); }
|
||||
.gate-techname { color: var(--content-secondary); font-size: 14.5px; letter-spacing: .24em; text-indent: .24em; }
|
||||
.gate-abbr { color: var(--content-faint); font-size: 11px; letter-spacing: .3em; text-indent: .3em; text-transform: uppercase; }
|
||||
.gate-domain-map { position: relative; z-index: 2; width: min(1040px, 92vw); display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 10px; }
|
||||
.gate-domain-map article { min-width: 0; padding: 12px 13px; display: grid; grid-template-columns: 9px minmax(0, 1fr); column-gap: 8px; border: 1px solid color-mix(in srgb, var(--accent-light) 14%, transparent); border-radius: 13px; background: color-mix(in srgb, var(--panel-bg) 40%, transparent); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); }
|
||||
.gate-domain-map i { width: 7px; height: 7px; margin-top: 4px; border-radius: 50%; background: color-mix(in srgb, var(--primitive-cool-glow) 70%, transparent); box-shadow: 0 0 12px color-mix(in srgb, var(--primitive-cool-glow) 34%, transparent); }
|
||||
.gate-domain-map b, .gate-domain-map small { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.gate-domain-map b { color: var(--content-secondary); font-size: 11.5px; font-weight: 620; }
|
||||
.gate-domain-map small { grid-column: 2; margin-top: 4px; color: var(--content-faint); font-size: 9.5px; }
|
||||
.gate-domain-map article.fifth { border-color: color-mix(in srgb, var(--primitive-warm-glow) 25%, transparent); }
|
||||
.gate-domain-map article.fifth i { background: var(--primitive-warm-glow); box-shadow: 0 0 16px color-mix(in srgb, var(--primitive-warm-glow) 54%, transparent); }
|
||||
@media (max-width: 900px) { .gate-domain-map { grid-template-columns: repeat(3, minmax(0, 1fr)); } .gate-domain-map article:nth-child(4), .gate-domain-map article:nth-child(5) { grid-column: span 1; } }
|
||||
@media (max-height: 700px) { .gate-lake { gap: 16px; } .gate-domain-map article { padding: 8px 10px; } .gate-hero { gap: 5px; } .gate-emblem { width: 48px; height: 48px; } .gate-product { font-size: 34px; } }
|
||||
.gate-pod-row { display: flex; gap: 10px; }
|
||||
.gate-pod input { flex: 1; height: 52px; padding: 0 16px; border: 1px solid color-mix(in srgb, var(--accent-light) 24%, transparent); border-radius: 14px; outline: 0; color: var(--content-primary); background: rgba(0, 0, 0, .22); font-size: 17px; letter-spacing: .08em; text-align: center; transition: border-color .3s ease, box-shadow .3s ease; }
|
||||
.gate-pod input:focus { border-color: color-mix(in srgb, var(--primitive-warm-glow) 58%, transparent); box-shadow: 0 0 22px color-mix(in srgb, var(--primitive-warm-glow) 18%, transparent); }
|
||||
|
|
|
|||
Loading…
Reference in a new issue