2026-08-16 00:02:05 +08:00
import { StrictMode , useCallback , useEffect , useMemo , useRef , useState } from 'react'
2026-08-12 10:29:00 +08:00
import { createRoot } from 'react-dom/client'
2026-08-13 13:52:59 +08:00
import { invoke } from '@tauri-apps/api/core'
2026-08-16 00:02:05 +08:00
import { splitFrontmatter , documentOutline , jumpToHeading , MarkdownDocument , markdownHtml } from './modules/knowledge-render'
2026-08-16 01:25:09 +08:00
import nightLake from './assets/hololake-night-lake.png'
2026-08-16 00:02:05 +08:00
const TAG_TINTS = [ 'tag-lavender' , 'tag-sky' , 'tag-mint' , 'tag-amber' , 'tag-rose' , 'tag-slate' ]
2026-08-16 01:25:09 +08:00
// 编号自动补齐:人只敲字母与数字,杠由系统按已登记文法自动出。
// ICE 系( ICE-GL∞ / ICE-P-ZY001) 、GLS 系( GLS-LA-20260720-003) ; 认不出的文法只在字母数字交界处补杠。
function formatGateNumber ( raw : string ) : string {
const up = raw . toUpperCase ( )
if ( up . startsWith ( 'ICE' ) ) {
const rest = up . slice ( 3 )
if ( ! rest ) return 'ICE'
if ( rest . startsWith ( 'P' ) ) {
const body = rest . slice ( 1 )
return body ? ` ICE-P- ${ body } ` : 'ICE-P'
}
return ` ICE- ${ rest } `
}
if ( up . startsWith ( 'GLS' ) ) {
const rest = up . slice ( 3 )
if ( ! rest ) return 'GLS'
const letters = rest . match ( /^[A-Z]*/ ) ? . [ 0 ] ? ? ''
const digits = rest . slice ( letters . length )
if ( ! digits ) return ` GLS- ${ letters } `
const d1 = digits . slice ( 0 , 8 )
const d2 = digits . slice ( 8 , 11 )
return ` GLS- ${ letters } - ${ d1 } ${ d2 ? ` - ${ d2 } ` : '' } `
}
return up . replace ( /([A-Z]+)([0-9])/g , '$1-$2' ) . replace ( /([0-9]+)([A-Z])/g , '$1-$2' )
}
2026-08-16 00:02:05 +08:00
function tagTint ( tag : string ) {
let hash = 0
for ( const char of tag ) hash = ( hash * 31 + ( char . codePointAt ( 0 ) || 0 ) ) % 997
return TAG_TINTS [ hash % TAG_TINTS . length ]
}
2026-08-13 13:52:59 +08:00
import './design-tokens.css'
2026-08-12 10:29:00 +08:00
import './styles.css'
2026-08-13 13:52:59 +08:00
type ThemeId = 'night' | 'dawn' | 'nebula' | 'candle' | 'clear'
2026-08-16 12:54:47 +08:00
type ViewId = 'overview' | 'knowledge' | 'code' | 'receipts' | 'system'
2026-08-15 15:02:35 +08:00
type KnowledgeSource = 'native' | 'legacy'
2026-08-13 13:52:59 +08:00
interface HomeStatus {
directLocalBrokerState : string
directConnectionCount : number
resumableSessionCount : number
codeRepositoryMountCount : number
pnccReceiptCount : number
updateState : string
2026-08-13 14:45:17 +08:00
releaseRecoveryState : string
2026-08-13 13:52:59 +08:00
mcpRole : string
}
2026-08-15 15:02:35 +08:00
interface PersonalChannelIdentity { humanSubjectId : string ; displayName : string ; channelId : string ; createdAtUnixMs : number }
2026-08-16 00:02:05 +08:00
interface LoginSession { username : string ; host : string ; signedInAtUnixMs : number }
interface LoginReceipt { username : string ; email : string ; host : string }
2026-08-15 15:02:35 +08:00
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 {
source : KnowledgeSource
path : string
title : string
updatedAtUnixMs : number
sizeBytes : number
contentSha256 : string
duplicateCount : number
2026-08-13 13:52:59 +08:00
}
2026-08-15 15:02:35 +08:00
interface KnowledgeSnapshot {
2026-08-13 14:01:43 +08:00
state : string
2026-08-15 15:02:35 +08:00
nativeRoot : string
legacyAvailable : boolean
legacyRoot? : string
documents : KnowledgeDocumentSummary [ ]
rawDocumentCount : number
uniqueDocumentCount : number
duplicateDocumentCount : number
truncated : boolean
2026-08-13 13:52:59 +08:00
}
2026-08-15 15:02:35 +08:00
interface KnowledgeDocument {
source : KnowledgeSource
path : string
title : string
body : string
updatedAtUnixMs : number
contentSha256 : string
writable : boolean
2026-08-13 14:01:43 +08:00
}
2026-08-15 15:02:35 +08:00
interface KnowledgeSearchResult { source : KnowledgeSource ; path : string ; title : string ; snippet : string }
interface KnowledgeImportResult {
2026-08-13 14:01:43 +08:00
state : string
2026-08-15 15:02:35 +08:00
sourceName : string
importedDocuments : number
importedAssets : number
existingDocuments : number
conflicts : number
skipped : number
firstDocument? : string
gitCommit : string
snapshot : KnowledgeSnapshot
2026-08-13 14:01:43 +08:00
}
2026-08-15 15:02:35 +08:00
interface KnowledgeSaveResult { state : string ; gitCommit : string ; document : KnowledgeDocument }
interface CodeChannelEntry { channelId : string ; name : string ; sourceKind : string ; localPath : string ; remoteUrl? : string ; gitHead : string ; branch : string ; repositoryClean : boolean ; registeredAtUnixMs : number }
interface CodeChannelSnapshot { state : string ; channels : CodeChannelEntry [ ] ; authority : string }
interface CodeTreeEntry { path : string ; name : string ; kind : 'directory' | 'file' ; sizeBytes : number }
interface CodeTreeSnapshot { channelId : string ; path : string ; entries : CodeTreeEntry [ ] ; truncated : boolean }
interface CodeFileProjection { channelId : string ; path : string ; format : string ; source : string ; humanMarkdown : string ; sizeBytes : number }
interface ReceiptEvent { sequence : number ; kind : string ; observedAtUnixMs : number ; eventHash : string }
interface ReceiptProjection { events : ReceiptEvent [ ] }
interface DiscoveryTicketReceipt { laneId : string ; clientInstanceId : string ; discoveryTicket : string }
interface ReleaseCandidate { candidateId : string ; confirmationToken : string ; currentVersion : string ; version : string ; notes : string ; features : string [ ] ; fixes : string [ ] ; dataMigrationRequired : boolean }
interface ReleaseCheckReceipt { candidate? : ReleaseCandidate }
2026-08-16 00:02:05 +08:00
interface ZeroPointSnapshot {
route : string
binding : string
userNumber : string
2026-08-16 12:54:47 +08:00
resolvedName : string
resolvedDomain : string
2026-08-16 00:02:05 +08:00
lastValidCheck : number
graceDeadline : number
protocol : { gracePeriodDays : number ; lighthouseAnchorUrl : string ; lighthouseResolveUrl : string ; coreChannelSource : string ; origin : string }
syncNote : string
2026-08-13 14:11:48 +08:00
}
2026-08-16 12:54:47 +08:00
function domainDisplayName ( domain : string ) : string {
const names : Record < string , string > = {
FIFTH_DOMAIN : '第五域 · 光湖本源域' ,
MAIN_DOMAIN : '光湖主域' ,
BRANCH_DOMAIN : '光湖分域' ,
ZERO_DOMAIN : '光湖零域' ,
ZERO_SENSE_DOMAIN : '光湖零感域' ,
}
return names [ domain ] || '已登记光湖域'
2026-08-13 14:11:48 +08:00
}
2026-08-16 12:54:47 +08:00
function protocolOriginDisplayName ( origin : string ) : string {
return origin . startsWith ( 'FACTORY_DEFAULT' ) ? '内置默认协议(签名发布通道尚未配置)' : origin
}
2026-08-16 02:52:55 +08:00
interface ServerPnccProjection {
schema : string
state : string
personaId : string
humanResponsibilitySubject : string
nodeId : string
bootId : string
gitHead : string
carrierBindingState : string
personaCarrierBound : boolean
modelInferenceStarted : boolean
realityExecutionAllowed : boolean
primaryLeaseHeld : boolean
eventCount : number
eventChainHead : string
observedAtUnixMs : number
transport : string
repositoryContentExposed : boolean
writeAuthority : boolean
}
2026-08-15 15:02:35 +08:00
const previewStatus : HomeStatus = { directLocalBrokerState : 'UNVERIFIED' , directConnectionCount : 0 , resumableSessionCount : 0 , codeRepositoryMountCount : 0 , pnccReceiptCount : 0 , updateState : 'UNPROVISIONED_FAIL_CLOSED' , releaseRecoveryState : 'NONE' , mcpRole : 'DISCOVERY_RECOVERY_COMPATIBILITY_ONLY' }
const previewPersonal : PersonalChannelSnapshot = { state : 'UNAVAILABLE' , recentEvents : [ ] , 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' }
2026-08-13 13:52:59 +08:00
const themes : Array < { id : ThemeId ; name : string } > = [
2026-08-15 15:02:35 +08:00
{ id : 'night' , name : '夜湖星光' } , { id : 'dawn' , name : '晨湖曦光' } , { id : 'nebula' , name : '星云紫夜' } , { id : 'candle' , name : '烛畔暖湖' } , { id : 'clear' , name : '清浅澄湖' } ,
2026-08-13 13:52:59 +08:00
]
2026-08-16 12:54:47 +08:00
const viewLabels : Record < ViewId , string > = { overview : '个人频道' , knowledge : '知识空间' , code : '代码频道' , receipts : '运行回执' , system : '系统详情' }
2026-08-13 13:52:59 +08:00
function getOrCreateLocalId ( key : string , prefix : string ) {
const existing = window . localStorage . getItem ( key )
if ( existing ) return existing
const generated = ` ${ prefix } - ${ crypto . randomUUID ( ) } `
window . localStorage . setItem ( key , generated )
return generated
}
2026-08-16 00:02:05 +08:00
function humanError ( error : unknown , context : 'knowledge' | 'code' | 'identity' | 'system' | 'login' ) {
2026-08-15 15:02:35 +08:00
const value = String ( error )
2026-08-16 00:02:05 +08:00
if ( value . includes ( 'LOGIN_CREDENTIALS_INVALID' ) ) return '账号或密码未被仓库接受。'
if ( value . includes ( 'LOGIN_USERNAME_INVALID' ) ) return '账号格式不合法(仅限字母、数字、连字符、下划线)。'
if ( value . includes ( 'LOGIN_NETWORK_FAILED' ) ) return '未能连通光湖代码频道服务器。'
if ( value . includes ( 'LOGIN_KEYCHAIN_FAILED' ) ) return '本机钥匙串写入失败,凭证未能安全保存。'
if ( value . includes ( 'LOGIN_SESSION_CORRUPT' ) ) return '登录会话已损坏,请重新登录。'
if ( value . includes ( 'LOGIN_VERIFICATION_FAILED' ) ) return '仓库验证未通过,请稍后再试。'
2026-08-15 15:02:35 +08:00
if ( value . includes ( 'HOST_NOT_TRUSTED' ) ) return '该地址不属于已登记的光湖代码频道。'
if ( value . includes ( 'URL_INVALID' ) ) return '频道地址无效,请粘贴完整的 HTTPS 克隆地址。'
if ( value . includes ( 'DESTINATION_EXISTS' ) ) return '该代码频道已经存在,无需再次克隆。'
if ( value . includes ( 'CLONE_FAILED' ) ) return '代码频道克隆失败,请核对地址及读取权限。'
if ( value . includes ( 'NO_SUPPORTED_FILES' ) ) return '文件夹中没有可导入的知识文件。'
if ( value . includes ( 'SAVE_CONFLICT' ) ) return '页面已在别处更新,请重新打开后再编辑。'
if ( value . includes ( 'FILE_UNSUPPORTED' ) ) return '该文件不是当前可阅读的文本格式。'
2026-08-16 00:02:05 +08:00
if ( context === 'login' ) return '登录未完成。'
2026-08-15 15:02:35 +08:00
if ( context === 'identity' ) return '个人空间未能建立。'
if ( context === 'knowledge' ) return '知识操作未完成,原文件未被覆盖。'
if ( context === 'code' ) return '代码频道操作未完成。'
return '系统操作未完成。'
}
2026-08-16 00:02:05 +08:00
function Icon ( { name } : { name : ViewId | 'search' | 'import' | 'folder' | 'copy' | 'arrow' | 'chevron' | 'file' | 'edit' | 'save' | 'back' | 'download' | 'trash' | 'chevronDown' | 'settings' | 'more' | 'plus' } ) {
2026-08-15 15:02:35 +08:00
const paths : Record < string , React.ReactNode > = {
overview : < > < rect x = "4" y = "4" width = "6" height = "6" rx = "1.5" / > < rect x = "14" y = "4" width = "6" height = "6" rx = "1.5" / > < rect x = "4" y = "14" width = "6" height = "6" rx = "1.5" / > < rect x = "14" y = "14" width = "6" height = "6" rx = "1.5" / > < / > ,
knowledge : < > < path d = "M5 4.5h10a3 3 0 0 1 3 3V20H8a3 3 0 0 1-3-3Z" / > < path d = "M8 4.5v12.8A2.7 2.7 0 0 1 10.7 20" / > < / > ,
code : < > < path d = "m8.5 8-4 4 4 4M15.5 8l4 4-4 4M13.5 5l-3 14" / > < / > ,
receipts : < > < path d = "M7 3.5h10v17l-2.5-1.5L12 20.5 9.5 19 7 20.5Z" / > < path d = "M10 8h4M10 12h4" / > < / > ,
system : < > < circle cx = "12" cy = "12" r = "3" / > < path d = "M19 12a7 7 0 0 0-.1-1.2l2-1.5-2-3.4-2.4 1a7 7 0 0 0-2-1.2L14.2 3h-4.1l-.3 2.7a7 7 0 0 0-2 1.2l-2.5-1-2 3.4 2.1 1.5a7 7 0 0 0 0 2.4l-2 1.5 2 3.4 2.4-1a7 7 0 0 0 2 1.2l.3 2.7h4.1l.3-2.7a7 7 0 0 0 2-1.2l2.5 1 2-3.4-2.1-1.5A7 7 0 0 0 19 12Z" / > < / > ,
search : < > < circle cx = "11" cy = "11" r = "6" / > < path d = "m16 16 4 4" / > < / > ,
import : < > < path d = "M12 4v11M8 11l4 4 4-4" / > < path d = "M5 19h14" / > < / > ,
2026-08-16 00:02:05 +08:00
download : < > < path d = "M12 4v12M7 11l5 5 5-5" / > < path d = "M4 17v3h16v-3" / > < / > ,
trash : < > < path d = "M4 7h16M10 4h4M7 7l1 13h8l1-13M10 11v5M14 11v5" / > < / > ,
chevronDown : < path d = "M6 9l6 6 6-6" / > ,
settings : < > < circle cx = "12" cy = "12" r = "3" / > < path d = "M12 3v2.5M12 18.5V21M4.6 6.8l1.8 1.8M17.6 15.4l1.8 1.8M3 12h2.5M18.5 12H21M4.6 17.2l1.8-1.8M17.6 8.6l1.8-1.8" / > < / > ,
more : < > < circle cx = "12" cy = "5" r = "1.4" / > < circle cx = "12" cy = "12" r = "1.4" / > < circle cx = "12" cy = "19" r = "1.4" / > < / > ,
plus : < path d = "M12 5v14M5 12h14" / > ,
2026-08-15 15:02:35 +08:00
folder : < path d = "M3.5 7.5h6l2-2h9v13h-17Z" / > ,
file : < > < path d = "M6 3.5h8l4 4v13H6Z" / > < path d = "M14 3.5v4h4" / > < / > ,
2026-08-13 13:52:59 +08:00
copy : < > < rect x = "8" y = "8" width = "10" height = "11" rx = "2" / > < path d = "M15 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h2" / > < / > ,
2026-08-15 15:02:35 +08:00
arrow : < > < path d = "M5 12h13M14 7l5 5-5 5" / > < / > ,
chevron : < path d = "m9 6 6 6-6 6" / > ,
edit : < > < path d = "m4 20 4.5-1 10-10-3.5-3.5-10 10Z" / > < path d = "m13.5 6.5 3.5 3.5" / > < / > ,
save : < > < path d = "M5 4h12l2 2v14H5Z" / > < path d = "M8 4v6h8V4M8 20v-6h8v6" / > < / > ,
back : < path d = "m15 6-6 6 6 6" / > ,
2026-08-13 13:52:59 +08:00
}
return < svg viewBox = "0 0 24 24" aria-hidden = "true" > { paths [ name ] } < / svg >
}
2026-08-15 15:02:35 +08:00
interface TreeNode { name : string ; key : string ; folders : Map < string , TreeNode > ; documents : KnowledgeDocumentSummary [ ] }
function knowledgeTree ( documents : KnowledgeDocumentSummary [ ] ) {
const root : TreeNode = { name : '知识' , key : '' , folders : new Map ( ) , documents : [ ] }
for ( const document of documents ) {
const segments = document . path . split ( '/' ) . filter ( Boolean )
const file = segments . pop ( )
let cursor = root
for ( const segment of segments ) {
const key = cursor . key ? ` ${ cursor . key } / ${ segment } ` : segment
if ( ! cursor . folders . has ( segment ) ) cursor . folders . set ( segment , { name : segment , key , folders : new Map ( ) , documents : [ ] } )
cursor = cursor . folders . get ( segment ) !
}
cursor . documents . push ( { . . . document , title : document.title || file || '未命名页面' } )
}
return root
}
function countTree ( node : TreeNode ) : number {
return node . documents . length + [ . . . node . folders . values ( ) ] . reduce ( ( sum , child ) = > sum + countTree ( child ) , 0 )
}
2026-08-16 00:02:05 +08:00
function KnowledgeTree ( { node , depth , expanded , active , onToggle , onOpen , onRemoveFolder , folderMenuKey , onFolderMenuToggle } : { node : TreeNode ; depth : number ; expanded : Set < string > ; active? : string ; onToggle : ( key : string ) = > void ; onOpen : ( item : KnowledgeDocumentSummary ) = > void ; onRemoveFolder ? : ( folder : TreeNode ) = > void ; folderMenuKey? : string | null ; onFolderMenuToggle ? : ( key : string ) = > void } ) {
2026-08-15 15:02:35 +08:00
const folders = [ . . . node . folders . values ( ) ] . sort ( ( a , b ) = > a . name . localeCompare ( b . name , 'zh-CN' ) )
const documents = [ . . . node . documents ] . sort ( ( a , b ) = > a . title . localeCompare ( b . title , 'zh-CN' ) )
return < >
{ folders . map ( ( folder ) = > {
const open = depth === 0 || expanded . has ( folder . key )
return < div key = { folder . key } className = "tree-branch" >
< button className = "tree-folder" style = { { paddingLeft : 12 + depth * 14 } } type = "button" onClick = { ( ) = > onToggle ( folder . key ) } >
< span className = { open ? 'tree-chevron open' : 'tree-chevron' } > < Icon name = "chevron" / > < / span > < Icon name = "folder" / > < span > { folder . name } < / span > < em > { countTree ( folder ) } < / em >
< / button >
2026-08-16 00:02:05 +08:00
{ onRemoveFolder && onFolderMenuToggle && < span className = "tree-folder-tools" > < button className = "tree-folder-menu-button" type = "button" title = "文件夹操作" onClick = { ( event ) = > { event . stopPropagation ( ) ; onFolderMenuToggle ( folder . key ) } } > < Icon name = "more" / > < / button > { folderMenuKey === folder . key && < span className = "tree-folder-menu" > < button type = "button" onClick = { ( event ) = > { event . stopPropagation ( ) ; onFolderMenuToggle ( folder . key ) ; onRemoveFolder ( folder ) } } > < Icon name = "trash" / > 移 到 回 收 站 < / button > < / span > } < / span > }
{ open && < KnowledgeTree node = { folder } depth = { depth + 1 } expanded = { expanded } active = { active } onToggle = { onToggle } onOpen = { onOpen } onRemoveFolder = { onRemoveFolder } folderMenuKey = { folderMenuKey } onFolderMenuToggle = { onFolderMenuToggle } / > }
2026-08-15 15:02:35 +08:00
< / div >
} ) }
{ documents . map ( ( document ) = > < button key = { ` ${ document . source } : ${ document . path } ` } className = { active === ` ${ document . source } : ${ document . path } ` ? 'tree-document active' : 'tree-document' } style = { { paddingLeft : 31 + depth * 14 } } type = "button" onClick = { ( ) = > onOpen ( document ) } >
< Icon name = "file" / > < span > { document . title } < / span > { document . duplicateCount > 0 && < em title = "折叠的相同内容" > + { document . duplicateCount } < / em > }
< / button > ) }
< / >
}
2026-08-13 13:52:59 +08:00
function HoloLakeApp() {
const [ theme , setTheme ] = useState < ThemeId > ( ( ) = > ( window . localStorage . getItem ( 'hololake-theme' ) as ThemeId ) || 'night' )
2026-08-15 15:02:35 +08:00
const [ view , setView ] = useState < ViewId > ( 'knowledge' )
2026-08-13 13:52:59 +08:00
const [ status , setStatus ] = useState < HomeStatus > ( previewStatus )
2026-08-15 15:02:35 +08:00
const [ personal , setPersonal ] = useState < PersonalChannelSnapshot > ( previewPersonal )
const [ knowledge , setKnowledge ] = useState < KnowledgeSnapshot > ( previewKnowledge )
const [ codeChannels , setCodeChannels ] = useState < CodeChannelSnapshot > ( previewCode )
const [ activeDocument , setActiveDocument ] = useState < KnowledgeDocument | null > ( null )
const [ searchQuery , setSearchQuery ] = useState ( '' )
const [ searchResults , setSearchResults ] = useState < KnowledgeSearchResult [ ] | null > ( null )
const [ knowledgeBusy , setKnowledgeBusy ] = useState ( false )
const [ knowledgeMessage , setKnowledgeMessage ] = useState ( '' )
const [ expanded , setExpanded ] = useState < Set < string > > ( ( ) = > new Set ( [ '导入' ] ) )
const [ editing , setEditing ] = useState ( false )
const [ draft , setDraft ] = useState ( '' )
const [ lastKnowledgeReceipt , setLastKnowledgeReceipt ] = useState ( '' )
const [ cloneUrl , setCloneUrl ] = useState ( '' )
const [ codeBusy , setCodeBusy ] = useState ( false )
const [ codeMessage , setCodeMessage ] = useState ( '' )
const [ activeChannel , setActiveChannel ] = useState < CodeChannelEntry | null > ( null )
const [ codeTree , setCodeTree ] = useState < CodeTreeSnapshot | null > ( null )
const [ activeCodeFile , setActiveCodeFile ] = useState < CodeFileProjection | null > ( null )
const [ codeMode , setCodeMode ] = useState < 'human' | 'source' > ( 'human' )
2026-08-16 00:02:05 +08:00
const [ sidebarCollapsed , setSidebarCollapsed ] = useState ( ( ) = > window . localStorage . getItem ( 'hololake-sidebar-collapsed' ) === '1' )
const [ inspectorOpen , setInspectorOpen ] = useState ( ( ) = > window . localStorage . getItem ( 'hololake-inspector-open' ) !== '0' )
const toggleSidebar = ( ) = > setSidebarCollapsed ( ( current ) = > { window . localStorage . setItem ( 'hololake-sidebar-collapsed' , current ? '0' : '1' ) ; return ! current } )
const toggleInspector = ( ) = > setInspectorOpen ( ( current ) = > { window . localStorage . setItem ( 'hololake-inspector-open' , current ? '0' : '1' ) ; return ! current } )
const [ activeHeadingId , setActiveHeadingId ] = useState < string | null > ( null )
const headingScrollTimer = useRef < number | null > ( null )
const [ settingsMenuOpen , setSettingsMenuOpen ] = useState ( false )
const [ folderMenuKey , setFolderMenuKey ] = useState < string | null > ( null )
2026-08-15 15:02:35 +08:00
const [ displayName , setDisplayName ] = useState ( '' )
const [ identityBusy , setIdentityBusy ] = useState ( false )
const [ identityMessage , setIdentityMessage ] = useState ( '' )
2026-08-13 13:52:59 +08:00
const [ receipts , setReceipts ] = useState < ReceiptEvent [ ] > ( [ ] )
2026-08-15 15:02:35 +08:00
const [ ticket , setTicket ] = useState < DiscoveryTicketReceipt | null > ( null )
const [ systemMessage , setSystemMessage ] = useState ( '' )
2026-08-13 14:11:48 +08:00
const [ releaseCandidate , setReleaseCandidate ] = useState < ReleaseCandidate | null > ( null )
2026-08-16 00:02:05 +08:00
const [ repoLogin , setRepoLogin ] = useState < LoginSession | null > ( null )
const [ loginUsername , setLoginUsername ] = useState ( '' )
const [ loginPassword , setLoginPassword ] = useState ( '' )
const [ loginBusy , setLoginBusy ] = useState ( false )
const [ loginRising , setLoginRising ] = useState ( false )
const [ loginMessage , setLoginMessage ] = useState ( '' )
2026-08-16 01:12:54 +08:00
const [ gateStage , setGateStage ] = useState < 'number' | 'key' > ( 'number' )
2026-08-16 01:25:09 +08:00
const [ gateRaw , setGateRaw ] = useState ( '' )
const [ gateInf , setGateInf ] = useState ( false )
const gateNumber = formatGateNumber ( gateRaw ) + ( gateInf ? '∞' : '' )
2026-08-16 01:54:57 +08:00
const [ gateOpen , setGateOpen ] = useState ( false )
const gateInputRef = useRef < HTMLInputElement > ( null )
2026-08-16 12:54:47 +08:00
// 等待入口展开动画进入稳定阶段后再聚焦输入框,避免动画期间焦点跳转。
2026-08-16 01:54:57 +08:00
useEffect ( ( ) = > {
if ( ! gateOpen || gateStage !== 'number' ) return
const timer = window . setTimeout ( ( ) = > gateInputRef . current ? . focus ( ) , 640 )
return ( ) = > window . clearTimeout ( timer )
} , [ gateOpen , gateStage ] )
2026-08-16 01:12:54 +08:00
const [ gateBusy , setGateBusy ] = useState ( false )
const [ gateMessage , setGateMessage ] = useState ( '' )
const [ gateRising , setGateRising ] = useState ( false )
2026-08-15 15:02:35 +08:00
const [ systemBusy , setSystemBusy ] = useState ( false )
2026-08-16 00:02:05 +08:00
const [ zeroPoint , setZeroPoint ] = useState < ZeroPointSnapshot | null > ( null )
const [ zpNumber , setZpNumber ] = useState ( '' )
const [ zpBusy , setZpBusy ] = useState ( false )
const [ zpMessage , setZpMessage ] = useState ( '' )
2026-08-16 02:52:55 +08:00
const [ serverPncc , setServerPncc ] = useState < ServerPnccProjection | null > ( null )
const [ serverPnccBusy , setServerPnccBusy ] = useState ( false )
const [ serverPnccReadback , setServerPnccReadback ] = useState < 'WAITING' | 'LIVE' | 'UNAVAILABLE' > ( 'WAITING' )
2026-08-13 13:52:59 +08:00
2026-08-15 15:02:35 +08:00
const refreshCore = useCallback ( async ( ) = > {
const [ homeResult , personalResult , knowledgeResult , codeResult ] = await Promise . allSettled ( [
invoke < HomeStatus > ( 'get_hololake_home_status' ) ,
invoke < PersonalChannelSnapshot > ( 'get_personal_channel_snapshot' ) ,
invoke < KnowledgeSnapshot > ( 'get_knowledge_snapshot' ) ,
invoke < CodeChannelSnapshot > ( 'get_code_channel_snapshot' ) ,
] )
if ( homeResult . status === 'fulfilled' ) setStatus ( homeResult . value )
if ( personalResult . status === 'fulfilled' ) setPersonal ( personalResult . value )
if ( knowledgeResult . status === 'fulfilled' ) setKnowledge ( knowledgeResult . value )
if ( codeResult . status === 'fulfilled' ) setCodeChannels ( codeResult . value )
} , [ ] )
const loadReceipts = useCallback ( async ( ) = > {
2026-08-13 13:52:59 +08:00
try {
2026-08-15 15:02:35 +08:00
const projection = await invoke < ReceiptProjection > ( 'query_pncc_receipt_projection' , { input : { afterSequence : 0 , limit : 25 } } )
setReceipts ( projection . events )
} catch { setReceipts ( [ ] ) }
2026-08-13 13:52:59 +08:00
} , [ ] )
useEffect ( ( ) = > {
2026-08-15 15:02:35 +08:00
void refreshCore ( )
const timer = window . setInterval ( ( ) = > void refreshCore ( ) , 3500 )
2026-08-13 13:52:59 +08:00
return ( ) = > window . clearInterval ( timer )
2026-08-15 15:02:35 +08:00
} , [ refreshCore ] )
2026-08-16 02:52:55 +08:00
const refreshServerPncc = useCallback ( async ( ) = > {
setServerPnccBusy ( true )
try {
2026-08-16 12:54:47 +08:00
setServerPncc ( await invoke < ServerPnccProjection > ( 'query_jd_pncc_server_projection' ) )
2026-08-16 02:52:55 +08:00
setServerPnccReadback ( 'LIVE' )
} catch {
setServerPncc ( null )
setServerPnccReadback ( 'UNAVAILABLE' )
} finally {
setServerPnccBusy ( false )
}
} , [ ] )
useEffect ( ( ) = > {
void refreshServerPncc ( )
const timer = window . setInterval ( ( ) = > void refreshServerPncc ( ) , 15000 )
return ( ) = > window . clearInterval ( timer )
} , [ refreshServerPncc ] )
2026-08-13 13:52:59 +08:00
useEffect ( ( ) = > {
document . documentElement . dataset . theme = theme
window . localStorage . setItem ( 'hololake-theme' , theme )
} , [ theme ] )
2026-08-16 00:02:05 +08:00
useEffect ( ( ) = > {
invoke < LoginSession | null > ( 'check_code_repo_login' ) . then ( ( session ) = > setRepoLogin ( session ) ) . catch ( ( ) = > setRepoLogin ( null ) )
} , [ ] )
const loadZeroPoint = useCallback ( async ( ) = > {
2026-08-16 12:54:47 +08:00
try { setZeroPoint ( await invoke < ZeroPointSnapshot > ( 'zero_point_status' ) ) } catch { /* 验证服务尚未就绪时保持空白状态。 */ }
2026-08-16 00:02:05 +08:00
} , [ ] )
useEffect ( ( ) = > {
void loadZeroPoint ( )
const timer = window . setInterval ( ( ) = > void loadZeroPoint ( ) , 10000 )
return ( ) = > window . clearInterval ( timer )
} , [ loadZeroPoint ] )
2026-08-16 12:54:47 +08:00
// 本机存在已绑定编号时自动回填,减少重复输入。
2026-08-16 01:12:54 +08:00
useEffect ( ( ) = > {
2026-08-16 01:25:09 +08:00
if ( gateStage !== 'number' || gateRaw ) return
const bound = zeroPoint ? . binding === 'bound' ? zeroPoint . userNumber : ''
if ( ! bound ) return
setGateInf ( bound . includes ( '∞' ) )
setGateRaw ( bound . toUpperCase ( ) . replace ( /[^A-Z0-9]/g , '' ) )
} , [ zeroPoint , gateStage , gateRaw ] )
// 大门·编号门输入:只收字母数字(杠系统补、∞开关点),删到∞时开关跟着灭
const onGateInput = ( value : string ) = > {
if ( gateInf && ! value . includes ( '∞' ) ) setGateInf ( false )
setGateRaw ( value . toUpperCase ( ) . replace ( /[^A-Z0-9]/g , '' ) )
2026-08-13 13:52:59 +08:00
}
2026-08-16 01:12:54 +08:00
// 大门·编号门:先报编号→灯塔查号→只答有效/无效→域浮起→才见钥匙门
const gateVerifyNumber = async ( ) = > {
setGateBusy ( true )
setGateMessage ( '' )
2026-08-13 14:01:43 +08:00
try {
2026-08-16 01:25:09 +08:00
await invoke < ZeroPointSnapshot > ( 'zero_point_bind' , { input : { number : gateNumber } } )
2026-08-16 01:12:54 +08:00
const snapshot = await invoke < ZeroPointSnapshot > ( 'zero_point_verify' )
setZeroPoint ( snapshot )
2026-08-16 12:54:47 +08:00
if ( snapshot . route === 'verified' ) {
2026-08-16 01:12:54 +08:00
setGateRising ( true )
2026-08-16 01:25:09 +08:00
window . setTimeout ( ( ) = > { setGateRising ( false ) ; setGateStage ( 'key' ) } , 2600 )
2026-08-16 01:12:54 +08:00
} else {
2026-08-16 01:25:09 +08:00
setGateMessage ( '编号无效' )
2026-08-16 01:12:54 +08:00
}
2026-08-13 14:01:43 +08:00
} catch ( error ) {
2026-08-16 01:12:54 +08:00
setGateMessage ( humanError ( error , 'login' ) )
2026-08-13 14:01:43 +08:00
} finally {
2026-08-16 01:12:54 +08:00
setGateBusy ( false )
2026-08-13 14:01:43 +08:00
}
}
2026-08-15 15:02:35 +08:00
useEffect ( ( ) = > { if ( view === 'receipts' ) void loadReceipts ( ) } , [ loadReceipts , view ] )
const visibleDocuments = useMemo ( ( ) = > {
if ( ! searchResults ) return knowledge . documents
return searchResults . map ( ( result ) = > knowledge . documents . find ( ( item ) = > item . source === result . source && item . path === result . path ) ) . filter ( Boolean ) as KnowledgeDocumentSummary [ ]
} , [ knowledge . documents , searchResults ] )
const tree = useMemo ( ( ) = > knowledgeTree ( visibleDocuments ) , [ visibleDocuments ] )
const activeSummary = activeDocument
? knowledge . documents . find ( ( item ) = > item . source === activeDocument . source && item . path === activeDocument . path )
: undefined
const parsedDocument = useMemo ( ( ) = > activeDocument ? splitFrontmatter ( activeDocument . body ) : null , [ activeDocument ] )
const outline = useMemo ( ( ) = > activeDocument ? documentOutline ( activeDocument . body ) : [ ] , [ activeDocument ] )
2026-08-16 00:02:05 +08:00
const docStats = useMemo ( ( ) = > {
if ( ! activeDocument ) return null
const chars = activeDocument . body . replace ( /\s+/g , '' ) . length
return { chars , minutes : Math.max ( 1 , Math . ceil ( chars / 400 ) ) }
} , [ activeDocument ] )
useEffect ( ( ) = > { setActiveHeadingId ( null ) } , [ activeDocument ? . path ] )
useEffect ( ( ) = > {
if ( ! activeHeadingId ) return
document . querySelector ( '.outline-list button.active' ) ? . scrollIntoView ( { block : 'nearest' } )
} , [ activeHeadingId ] )
const handleReaderScroll = ( event : React.UIEvent < HTMLDivElement > ) = > {
const container = event . currentTarget
if ( headingScrollTimer . current !== null ) return
headingScrollTimer . current = window . setTimeout ( ( ) = > {
headingScrollTimer . current = null
const containerTop = container . getBoundingClientRect ( ) . top
let current : string | null = null
for ( const heading of Array . from ( container . querySelectorAll < HTMLElement > ( 'h1[id], h2[id], h3[id], h4[id]' ) ) ) {
if ( heading . getBoundingClientRect ( ) . top - containerTop <= 28 ) current = heading . id
else break
}
setActiveHeadingId ( current )
} , 100 )
}
2026-08-15 15:02:35 +08:00
const connectionOnline = status . directConnectionCount > 0
const connectionLabel = connectionOnline
? '本地原生连接 · 客户端在线'
: status . resumableSessionCount > 0
? '原生会话可续接 · 当前离线'
: '原生接口已驻留 · 尚无客户端'
const openDocument = async ( source : KnowledgeSource , path : string ) = > {
setKnowledgeBusy ( true )
setKnowledgeMessage ( '' )
setEditing ( false )
2026-08-13 14:01:43 +08:00
try {
2026-08-15 15:02:35 +08:00
const document = await invoke < KnowledgeDocument > ( 'read_knowledge_document' , { input : { source , path } } )
setActiveDocument ( document )
setDraft ( document . body )
} catch ( error ) { setKnowledgeMessage ( humanError ( error , 'knowledge' ) ) }
finally { setKnowledgeBusy ( false ) }
2026-08-13 14:01:43 +08:00
}
2026-08-15 15:02:35 +08:00
const openWiki = ( target : string ) = > {
const normalized = target . replace ( /\.md$/i , '' ) . toLowerCase ( )
const match = knowledge . documents . find ( ( item ) = >
item . title . toLowerCase ( ) === normalized || item . path . replace ( /\.md$/i , '' ) . toLowerCase ( ) . endsWith ( normalized ) )
if ( match ) void openDocument ( match . source , match . path )
else setKnowledgeMessage ( ` 未找到链接页面: ${ target } ` )
}
const runSearch = async ( event : React.FormEvent ) = > {
event . preventDefault ( )
if ( ! searchQuery . trim ( ) ) { setSearchResults ( null ) ; return }
setKnowledgeBusy ( true )
2026-08-13 14:11:48 +08:00
try {
2026-08-15 15:02:35 +08:00
setSearchResults ( await invoke < KnowledgeSearchResult [ ] > ( 'search_knowledge' , { input : { query : searchQuery } } ) )
setActiveDocument ( null )
} catch ( error ) { setKnowledgeMessage ( humanError ( error , 'knowledge' ) ) }
finally { setKnowledgeBusy ( false ) }
2026-08-13 14:11:48 +08:00
}
2026-08-15 15:02:35 +08:00
const importKnowledge = async ( ) = > {
setKnowledgeBusy ( true )
setKnowledgeMessage ( '' )
2026-08-13 14:11:48 +08:00
try {
2026-08-15 15:02:35 +08:00
const result = await invoke < KnowledgeImportResult | null > ( 'select_and_import_knowledge_folder' )
if ( ! result ) return
setKnowledge ( result . snapshot )
setSearchResults ( null )
setSearchQuery ( '' )
setLastKnowledgeReceipt ( result . gitCommit )
setKnowledgeMessage ( ` 导入完成:新增 ${ result . importedDocuments } ,已存在 ${ result . existingDocuments } ,冲突 ${ result . conflicts } 。 ` )
if ( result . firstDocument ) await openDocument ( 'native' , result . firstDocument )
} catch ( error ) { setKnowledgeMessage ( humanError ( error , 'knowledge' ) ) }
finally { setKnowledgeBusy ( false ) }
2026-08-13 14:01:43 +08:00
}
2026-08-16 00:02:05 +08:00
const downloadDocument = async ( format : 'md' | 'html' ) = > {
if ( ! activeDocument ) return
setKnowledgeBusy ( true )
setKnowledgeMessage ( '' )
try {
let body = activeDocument . body
if ( format === 'html' ) {
const parsed = splitFrontmatter ( body )
const article = markdownHtml ( parsed . content )
body = ` <!DOCTYPE html><html lang="zh"><head><meta charset="utf-8"><title> ${ activeDocument . title } </title><style>body{max-width:820px;margin:40px auto;padding:0 24px;font:15px/1.8 -apple-system,'Segoe UI','PingFang SC','Microsoft YaHei','Noto Sans CJK SC',sans-serif;color:#1f2330;background:#fff}h1,h2,h3{line-height:1.4}blockquote{margin:20px 0;padding:12px 16px;border-left:3px solid #a78bfa;background:#f3f0ff;border-radius:8px}table{border-collapse:collapse;width:100%}td,th{border:1px solid #ddd;padding:7px 10px}pre{background:#f4f5f8;padding:14px;border-radius:8px;overflow:auto}img{max-width:100%}</style></head><body><h1> ${ activeDocument . title } </h1> ${ article } </body></html> `
}
const receipt = await invoke < { path : string ; bytes : number } | null > ( 'export_knowledge_document' , { input : { title : activeDocument.title , extension : format , body } } )
setKnowledgeMessage ( receipt ? ` 已下载到 ${ receipt . path } ` : '已取消下载。' )
} catch ( error ) { setKnowledgeMessage ( humanError ( error , 'knowledge' ) ) }
finally { setKnowledgeBusy ( false ) }
}
const printDocument = async ( ) = > {
setKnowledgeMessage ( '' )
try {
await invoke ( 'print_knowledge_document' )
setKnowledgeMessage ( '打印框里选「存储为 PDF」即可得到 PDF 文件。' )
} catch ( error ) { setKnowledgeMessage ( humanError ( error , 'knowledge' ) ) }
}
const reloadKnowledge = async ( ) = > {
const snapshot = await invoke < KnowledgeSnapshot > ( 'get_knowledge_snapshot' )
setKnowledge ( snapshot )
}
const createDocument = async ( ) = > {
setKnowledgeBusy ( true )
setKnowledgeMessage ( '' )
try {
const receipt = await invoke < { removed : string ; pages : number } > ( 'create_knowledge_document' , { input : { title : '未命名页面' } } )
setKnowledgeMessage ( ` 已新建空白页「 ${ receipt . removed . replace ( /\.md$/ , '' ) } 」,正在编辑。 ` )
setSearchResults ( null )
await reloadKnowledge ( )
await openDocument ( 'native' , receipt . removed )
setEditing ( true )
} catch ( error ) { setKnowledgeMessage ( humanError ( error , 'knowledge' ) ) }
finally { setKnowledgeBusy ( false ) }
}
const removeDocument = async ( ) = > {
if ( ! activeDocument ) return
setKnowledgeBusy ( true )
setKnowledgeMessage ( '' )
try {
const receipt = await invoke < { removed : string ; pages : number } | null > ( 'delete_knowledge_document' , { input : { source : activeDocument.source , path : activeDocument.path } } )
if ( receipt ) {
setKnowledgeMessage ( ` 已移入回收站: ${ receipt . removed } (可找回) ` )
setActiveDocument ( null )
setSearchResults ( null )
await reloadKnowledge ( )
} else setKnowledgeMessage ( '已取消删除。' )
} catch ( error ) { setKnowledgeMessage ( humanError ( error , 'knowledge' ) ) }
finally { setKnowledgeBusy ( false ) }
}
const removeFolder = async ( folder : TreeNode ) = > {
setKnowledgeBusy ( true )
setKnowledgeMessage ( '' )
try {
const receipt = await invoke < { removed : string ; pages : number } | null > ( 'delete_knowledge_folder' , { input : { folder : folder.key } } )
if ( receipt ) {
setKnowledgeMessage ( ` 文件夹「 ${ folder . name } 」( ${ receipt . pages } 页)已移入回收站(可找回) ` )
setActiveDocument ( null )
setSearchResults ( null )
await reloadKnowledge ( )
} else setKnowledgeMessage ( '已取消删除。' )
} catch ( error ) { setKnowledgeMessage ( humanError ( error , 'knowledge' ) ) }
finally { setKnowledgeBusy ( false ) }
}
2026-08-15 15:02:35 +08:00
const saveDocument = async ( ) = > {
if ( ! activeDocument ? . writable ) return
setKnowledgeBusy ( true )
2026-08-13 14:01:43 +08:00
try {
2026-08-15 15:02:35 +08:00
const result = await invoke < KnowledgeSaveResult > ( 'save_knowledge_document' , {
input : { path : activeDocument.path , body : draft , expectedContentSha256 : activeDocument.contentSha256 } ,
2026-08-13 14:11:48 +08:00
} )
2026-08-15 15:02:35 +08:00
setActiveDocument ( result . document )
setDraft ( result . document . body )
setEditing ( false )
setLastKnowledgeReceipt ( result . gitCommit )
setKnowledgeMessage ( result . state === 'UNCHANGED' ? '内容没有变化。' : '页面已保存,并写入本机 Git 回执。' )
await refreshCore ( )
} catch ( error ) { setKnowledgeMessage ( humanError ( error , 'knowledge' ) ) }
finally { setKnowledgeBusy ( false ) }
2026-08-13 14:11:48 +08:00
}
2026-08-15 15:02:35 +08:00
const cloneCodeChannel = async ( event : React.FormEvent ) = > {
event . preventDefault ( )
if ( ! cloneUrl . trim ( ) ) return
setCodeBusy ( true )
setCodeMessage ( '' )
2026-08-13 14:45:17 +08:00
try {
2026-08-15 15:02:35 +08:00
const snapshot = await invoke < CodeChannelSnapshot > ( 'clone_code_channel' , { input : { url : cloneUrl } } )
setCodeChannels ( snapshot )
setCloneUrl ( '' )
setCodeMessage ( '代码频道已克隆并登记。' )
} catch ( error ) { setCodeMessage ( humanError ( error , 'code' ) ) }
finally { setCodeBusy ( false ) }
2026-08-13 14:11:48 +08:00
}
2026-08-15 15:02:35 +08:00
const selectLocalCodeChannel = async ( ) = > {
setCodeBusy ( true )
setCodeMessage ( '' )
2026-08-13 14:11:48 +08:00
try {
2026-08-15 15:02:35 +08:00
const snapshot = await invoke < CodeChannelSnapshot | null > ( 'select_local_code_channel' )
if ( snapshot ) {
setCodeChannels ( snapshot )
setCodeMessage ( '本地代码频道已登记,原文件未移动。' )
2026-08-13 14:45:17 +08:00
}
2026-08-15 15:02:35 +08:00
} catch ( error ) { setCodeMessage ( humanError ( error , 'code' ) ) }
finally { setCodeBusy ( false ) }
2026-08-13 14:11:48 +08:00
}
2026-08-15 15:02:35 +08:00
const browseChannel = async ( channel : CodeChannelEntry , path = '' ) = > {
setCodeBusy ( true )
setActiveChannel ( channel )
setActiveCodeFile ( null )
2026-08-13 14:45:17 +08:00
try {
2026-08-15 15:02:35 +08:00
setCodeTree ( await invoke < CodeTreeSnapshot > ( 'browse_code_channel' , { input : { channelId : channel.channelId , path } } ) )
} catch ( error ) { setCodeMessage ( humanError ( error , 'code' ) ) }
finally { setCodeBusy ( false ) }
}
const openCodeEntry = async ( entry : CodeTreeEntry ) = > {
if ( ! activeChannel ) return
if ( entry . kind === 'directory' ) { await browseChannel ( activeChannel , entry . path ) ; return }
setCodeBusy ( true )
setCodeMode ( 'human' )
try {
setActiveCodeFile ( await invoke < CodeFileProjection > ( 'read_code_channel_file' , {
input : { channelId : activeChannel.channelId , path : entry.path } ,
} ) )
} catch ( error ) { setCodeMessage ( humanError ( error , 'code' ) ) }
finally { setCodeBusy ( false ) }
2026-08-13 14:45:17 +08:00
}
2026-08-15 15:02:35 +08:00
const codeParent = ( ) = > codeTree ? . path . split ( '/' ) . filter ( Boolean ) . slice ( 0 , - 1 ) . join ( '/' ) || ''
2026-08-13 14:45:17 +08:00
2026-08-16 00:02:05 +08:00
const performRepoLogin = async ( event : React.FormEvent ) = > {
event . preventDefault ( )
setLoginBusy ( true )
setLoginMessage ( '' )
try {
const receipt = await invoke < LoginReceipt > ( 'perform_code_repo_login' , { username : loginUsername.trim ( ) , password : loginPassword } )
// 五湖开场:校验通过=第五域从湖面浮起,镜头沉入湖中再进场。
setLoginRising ( true )
await new Promise ( ( resolve ) = > setTimeout ( resolve , 1300 ) )
setRepoLogin ( { username : receipt.username , host : receipt.host , signedInAtUnixMs : Date.now ( ) } )
setLoginRising ( false )
setLoginPassword ( '' )
} catch ( error ) { setLoginMessage ( humanError ( error , 'login' ) ) }
finally { setLoginBusy ( false ) }
}
const signOutRepo = async ( ) = > {
try { await invoke ( 'sign_out_code_repo_login' ) } catch { /* 登出以本机清场为准 */ }
setRepoLogin ( null )
}
2026-08-15 15:02:35 +08:00
const initializeIdentity = async ( event : React.FormEvent ) = > {
event . preventDefault ( )
setIdentityBusy ( true )
try {
setPersonal ( await invoke < PersonalChannelSnapshot > ( 'initialize_personal_channel' , { input : { displayName } } ) )
setDisplayName ( '' )
} catch ( error ) { setIdentityMessage ( humanError ( error , 'identity' ) ) }
finally { setIdentityBusy ( false ) }
}
2026-08-13 13:52:59 +08:00
const issueInvitation = async ( ) = > {
2026-08-15 15:02:35 +08:00
setSystemBusy ( true )
setSystemMessage ( '' )
2026-08-13 13:52:59 +08:00
try {
2026-08-15 15:02:35 +08:00
setTicket ( await invoke < DiscoveryTicketReceipt > ( 'issue_direct_local_discovery_ticket' , {
2026-08-13 13:52:59 +08:00
input : {
accountId : getOrCreateLocalId ( 'hololake-local-account' , 'human-local' ) ,
laneId : 'personal-channel' ,
clientInstanceId : getOrCreateLocalId ( 'hololake-external-ai-client' , 'external-ai' ) ,
} ,
2026-08-15 15:02:35 +08:00
} ) )
setSystemMessage ( '一次性发现凭据已生成。连接成功后,状态条会显示真实在线数。' )
} catch ( error ) { setSystemMessage ( humanError ( error , 'system' ) ) }
finally { setSystemBusy ( false ) }
2026-08-13 13:52:59 +08:00
}
const invitationText = ticket ? JSON . stringify ( {
schema : 'hololake.direct-local-invitation/v1' ,
connectorArgument : '--connector' ,
openSession : {
operation : 'OPEN_SESSION' ,
input : {
accountId : window.localStorage.getItem ( 'hololake-local-account' ) ,
laneId : ticket.laneId ,
clientInstanceId : ticket.clientInstanceId ,
discoveryTicket : ticket.discoveryTicket ,
} ,
} ,
} , null , 2 ) : ''
const copyInvitation = async ( ) = > {
try {
await navigator . clipboard . writeText ( invitationText )
2026-08-15 15:02:35 +08:00
setSystemMessage ( '连接凭据已复制。' )
} catch { setSystemMessage ( '复制失败,请手动选择凭据。' ) }
}
const checkUpdate = async ( ) = > {
setSystemBusy ( true )
try {
const result = await invoke < ReleaseCheckReceipt > ( 'check_hololake_update' )
setReleaseCandidate ( result . candidate || null )
setSystemMessage ( result . candidate ? '发现已签名更新。' : '当前没有可安装更新。' )
} catch ( error ) { setSystemMessage ( humanError ( error , 'system' ) ) }
finally { setSystemBusy ( false ) }
}
const installUpdate = async ( ) = > {
if ( ! releaseCandidate ) return
setSystemBusy ( true )
try {
await invoke ( 'confirm_hololake_update_install' , {
input : { candidateId : releaseCandidate.candidateId , confirmationToken : releaseCandidate.confirmationToken } ,
} )
setSystemMessage ( '更新安装已启动。' )
} catch ( error ) { setSystemMessage ( humanError ( error , 'system' ) ) }
finally { setSystemBusy ( false ) }
2026-08-13 13:52:59 +08:00
}
2026-08-16 00:02:05 +08:00
const bindZeroPoint = async ( ) = > {
if ( ! zpNumber . trim ( ) ) return
setZpBusy ( true ) ; setZpMessage ( '' )
try {
const bound = await invoke < ZeroPointSnapshot > ( 'zero_point_bind' , { input : { number : zpNumber . trim ( ) } } )
setZeroPoint ( bound )
const verified = await invoke < ZeroPointSnapshot > ( 'zero_point_verify' )
setZeroPoint ( verified )
2026-08-16 12:54:47 +08:00
setZpMessage ( verified . route === 'verified' ? '编号验证通过。' : '编号未通过验证,受限功能保持关闭。' )
2026-08-16 00:02:05 +08:00
} catch ( error ) { setZpMessage ( String ( error ) ) } finally { setZpBusy ( false ) }
}
const verifyZeroPoint = async ( ) = > {
setZpBusy ( true ) ; setZpMessage ( '' )
try {
const snapshot = await invoke < ZeroPointSnapshot > ( 'zero_point_verify' )
setZeroPoint ( snapshot )
2026-08-16 12:54:47 +08:00
setZpMessage ( snapshot . route === 'verified' ? '编号验证通过。' : '编号未通过验证,受限功能保持关闭。' )
2026-08-16 00:02:05 +08:00
} catch ( error ) { setZpMessage ( String ( error ) ) } finally { setZpBusy ( false ) }
}
const syncZeroPoint = async ( ) = > {
setZpBusy ( true ) ; setZpMessage ( '' )
try { setZeroPoint ( await invoke < ZeroPointSnapshot > ( 'zero_point_sync' ) ) } catch ( error ) { setZpMessage ( String ( error ) ) } finally { setZpBusy ( false ) }
}
2026-08-16 12:54:47 +08:00
const renderOverview = ( ) = > (
2026-08-15 15:02:35 +08:00
< section className = "content-page overview-page" >
< header className = "page-title" >
2026-08-16 12:54:47 +08:00
< div > < span className = "kicker" > PERSONAL CHANNEL < / span > < h1 > 个 人 频 道 < / h1 > < p > 统 一 访 问 本 机 知 识 、 代 码 、 运 行 状 态 与 可 核 验 回 执 。 < / p > < / div >
2026-08-15 15:02:35 +08:00
< button className = "evidence-pill" type = "button" onClick = { ( ) = > setView ( 'system' ) } > < i className = { connectionOnline ? 'online' : '' } / > { connectionLabel } < / button >
2026-08-13 13:52:59 +08:00
< / header >
2026-08-15 15:02:35 +08:00
< div className = "overview-grid" >
< button className = "metric-panel" type = "button" onClick = { ( ) = > setView ( 'knowledge' ) } > < span > 唯 一 知 识 页 < / span > < strong > { knowledge . uniqueDocumentCount } < / strong > < small > { knowledge . duplicateDocumentCount } 份 相 同 内 容 已 折 叠 < / small > < / button >
< button className = "metric-panel" type = "button" onClick = { ( ) = > setView ( 'code' ) } > < span > 代 码 频 道 < / span > < strong > { codeChannels . channels . length } < / strong > < small > 可 浏 览 、 可 转 译 、 本 机 只 读 接 入 < / small > < / button >
< button className = "metric-panel" type = "button" onClick = { ( ) = > setView ( 'receipts' ) } > < span > 可 核 验 回 执 < / span > < strong > { personal . integrity . receiptCount + status . pnccReceiptCount } < / strong > < small > 身 份 、 知 识 与 代 码 证 据 < / small > < / button >
< / div >
< div className = "overview-columns" >
< section className = "plain-panel recent-panel" >
< header > < div > < h2 > 最 近 知 识 < / h2 > < p > 从 真 实 页 面 继 续 阅 读 < / p > < / div > < button type = "button" onClick = { ( ) = > setView ( 'knowledge' ) } > 打 开 工 作 台 < / button > < / header >
{ knowledge . documents . slice ( ) . sort ( ( a , b ) = > b . updatedAtUnixMs - a . updatedAtUnixMs ) . slice ( 0 , 7 ) . map ( ( item ) = >
< button className = "recent-row" key = { ` ${ item . source } : ${ item . path } ` } type = "button" onClick = { ( ) = > { setView ( 'knowledge' ) ; void openDocument ( item . source , item . path ) } } >
< Icon name = "file" / > < span > < b > { item . title } < / b > < small > { item . path } < / small > < / span > < time > { new Date ( item . updatedAtUnixMs ) . toLocaleDateString ( 'zh-CN' ) } < / time >
< / button > ) }
< / section >
< section className = "plain-panel system-proof" >
2026-08-16 12:54:47 +08:00
< header > < div > < h2 > 运 行 状 态 < / h2 > < p > 以 下 信 息 均 来 自 当 前 进 程 与 已 登 记 运 行 源 。 < / p > < / div > < / header >
2026-08-15 15:02:35 +08:00
< dl > < div > < dt > Unix 本 地 代 理 < / dt > < dd > { status . directLocalBrokerState } < / dd > < / div > < div > < dt > 在 线 客 户 端 < / dt > < dd > { status . directConnectionCount } < / dd > < / div > < div > < dt > 可 续 接 会 话 < / dt > < dd > { status . resumableSessionCount } < / dd > < / div > < div > < dt > PNCC 投 影 回 执 < / dt > < dd > { status . pnccReceiptCount } < / dd > < / div > < div > < dt > MCP < / dt > < dd > 备 用 / 恢 复 < / dd > < / div > < / dl >
< button className = "secondary-button" type = "button" onClick = { ( ) = > setView ( 'system' ) } > 查 看 验 证 入 口 < / button >
< / section >
< / div >
< / section >
)
2026-08-13 13:52:59 +08:00
2026-08-15 15:02:35 +08:00
const renderKnowledge = ( ) = > (
< section className = "full-workbench knowledge-page" >
< aside className = "knowledge-browser" >
< header > < div > < span className = "kicker" > KNOWLEDGE < / span > < h1 > 知 识 工 作 台 < / h1 > < / div > < button className = "icon-button" title = "导入文件夹" type = "button" disabled = { knowledgeBusy } onClick = { ( ) = > void importKnowledge ( ) } > < Icon name = "import" / > < / button > < / header >
< form className = "search-box" onSubmit = { ( event ) = > void runSearch ( event ) } >
< Icon name = "search" / > < input aria-label = "检索知识" value = { searchQuery } placeholder = "检索标题与正文" onChange = { ( event ) = > setSearchQuery ( event . target . value ) } / >
{ searchResults && < button type = "button" onClick = { ( ) = > { setSearchResults ( null ) ; setSearchQuery ( '' ) } } > 清 除 < / button > }
< / form >
< div className = "knowledge-counts" > < span > { knowledge . uniqueDocumentCount } 篇 唯 一 文 档 < / span > < span > { knowledge . duplicateDocumentCount } 份 重 复 已 折 叠 < / span > < / div >
< div className = "knowledge-tree" aria-busy = { knowledgeBusy } >
{ visibleDocuments . length
? < KnowledgeTree node = { tree } depth = { 0 } expanded = { expanded } active = { activeDocument ? ` ${ activeDocument . source } : ${ activeDocument . path } ` : undefined }
onToggle = { ( key ) = > setExpanded ( ( current ) = > { const next = new Set ( current ) ; if ( next . has ( key ) ) next . delete ( key ) ; else next . add ( key ) ; return next } ) }
2026-08-16 00:02:05 +08:00
onOpen = { ( item ) = > void openDocument ( item . source , item . path ) }
onRemoveFolder = { ( folder ) = > void removeFolder ( folder ) }
folderMenuKey = { folderMenuKey }
onFolderMenuToggle = { ( key ) = > setFolderMenuKey ( ( current ) = > ( current === key ? null : key ) ) } / >
2026-08-15 15:02:35 +08:00
: < div className = "empty-state" > 没 有 匹 配 的 知 识 页 < / div > }
< / div >
< footer > { knowledgeMessage || ( lastKnowledgeReceipt ? ` Git ${ lastKnowledgeReceipt . slice ( 0 , 10 ) } ` : '导入会自动检查相同内容' ) } < / footer >
< / aside >
< main className = "document-workspace" >
{ activeDocument ? < >
< header className = "document-toolbar" >
< div className = "breadcrumbs" > < span > { activeDocument . source === 'legacy' ? 'HoloLake Era · 只读源' : '我的知识库' } < / span > < b > / < / b > < span > { activeDocument . path } < / span > < / div >
2026-08-16 00:02:05 +08:00
< div className = "toolbar-actions" > < button className = "toolbar-button" type = "button" title = { inspectorOpen ? '收起右侧大纲与证据' : '展开右侧大纲与证据' } onClick = { toggleInspector } > < Icon name = "chevron" / > { inspectorOpen ? '收起大纲' : '展开大纲' } < / button > { editing
2026-08-15 15:02:35 +08:00
? < > < button className = "toolbar-button" type = "button" onClick = { ( ) = > { setEditing ( false ) ; setDraft ( activeDocument . body ) } } > 取 消 < / button > < button className = "toolbar-button primary" type = "button" disabled = { knowledgeBusy } onClick = { ( ) = > void saveDocument ( ) } > < Icon name = "save" / > 保 存 < / button > < / >
2026-08-16 00:02:05 +08:00
: < > < div className = "toolbar-menu" > < button className = "toolbar-button" type = "button" title = "页面操作与常用功能" disabled = { knowledgeBusy } onClick = { ( ) = > setSettingsMenuOpen ( ( current ) = > ! current ) } > < Icon name = "settings" / > 设 置 < Icon name = "chevronDown" / > < / button > { settingsMenuOpen && < div className = "toolbar-menu-pop" > < button type = "button" disabled = { knowledgeBusy } onClick = { ( ) = > { setSettingsMenuOpen ( false ) ; void createDocument ( ) } } > < Icon name = "plus" / > 新 建 空 白 页 < / button > < button type = "button" disabled = { knowledgeBusy } onClick = { ( ) = > { setSettingsMenuOpen ( false ) ; void importKnowledge ( ) } } > < Icon name = "import" / > 导 入 知 识 库 文 件 夹 < / button > { activeDocument . writable && < button type = "button" onClick = { ( ) = > { setSettingsMenuOpen ( false ) ; setDraft ( activeDocument . body ) ; setEditing ( true ) } } > < Icon name = "edit" / > 编 辑 这 一 页 < / button > } { activeDocument . writable && < button type = "button" disabled = { knowledgeBusy } onClick = { ( ) = > { setSettingsMenuOpen ( false ) ; void removeDocument ( ) } } > < Icon name = "trash" / > 删 除 这 一 页 ( 进 回 收 站 ) < / button > } < span className = "toolbar-menu-sep" / > < button type = "button" disabled = { knowledgeBusy } onClick = { ( ) = > { setSettingsMenuOpen ( false ) ; void downloadDocument ( 'md' ) } } > < Icon name = "download" / > 下 载 Markdown < / button > < button type = "button" disabled = { knowledgeBusy } onClick = { ( ) = > { setSettingsMenuOpen ( false ) ; void downloadDocument ( 'html' ) } } > < Icon name = "download" / > 下 载 网 页 ( . html ) < / button > < button type = "button" disabled = { knowledgeBusy } onClick = { ( ) = > { setSettingsMenuOpen ( false ) ; void printDocument ( ) } } > < Icon name = "download" / > 下 载 PDF ( 系 统 打 印 ) < / button > < / div > } < / div > < / > }
2026-08-15 15:02:35 +08:00
< / div >
< / header >
2026-08-16 00:02:05 +08:00
< div className = "document-scroll" onScroll = { handleReaderScroll } >
2026-08-15 15:02:35 +08:00
{ editing
? < textarea className = "document-editor" aria-label = "Markdown 编辑器" value = { draft } onChange = { ( event ) = > setDraft ( event . target . value ) } / >
2026-08-16 00:02:05 +08:00
: < > < header className = "reader-heading" > < h1 > { activeDocument . title } < / h1 > { docStats && < div className = "meta-stats" > { docStats . chars . toLocaleString ( ) } 字 · 约 { docStats . minutes } 分 钟 读 完 { parsedDocument ? . metadata ? . created ? ` · 创建于 ${ String ( parsedDocument . metadata . created ) . slice ( 0 , 10 ) } ` : '' } { parsedDocument ? . metadata ? . updated ? ` · 更新于 ${ String ( parsedDocument . metadata . updated ) . slice ( 0 , 10 ) } ` : '' } < / div > } { ( parsedDocument ? . tags ? . length ? ? 0 ) > 0 && < div className = "meta-tags" > { parsedDocument ! . tags . map ( ( tag ) = > < span key = { tag } className = { tagTint ( tag ) } > { tag } < / span > ) } < / div > } < div > { Object . entries ( parsedDocument ? . metadata || { } ) . filter ( ( [ key ] ) = > key !== 'tags' ) . slice ( 0 , 5 ) . map ( ( [ key , value ] ) = > < span key = { key } > { key } · { value } < / span > ) } < / div > < / header > < MarkdownDocument body = { activeDocument . body } knownTitles = { knowledge . documents . map ( ( doc ) = > doc . title ) } onWiki = { openWiki } / > < / > }
2026-08-13 13:52:59 +08:00
< / div >
2026-08-15 15:02:35 +08:00
< / > : < div className = "workbench-empty" > < span > 光 < / span > < h2 > 选 择 一 篇 知 识 页 < / h2 > < p > 目 录 、 正 文 与 版 本 证 据 会 在 同 一 个 工 作 台 中 展 开 。 < / p > < button className = "primary-button" type = "button" onClick = { ( ) = > void importKnowledge ( ) } > 导 入 本 地 文 件 夹 < / button > < / div > }
< / main >
2026-08-16 00:02:05 +08:00
{ inspectorOpen && < aside className = "document-inspector" >
2026-08-15 15:02:35 +08:00
< div className = "inspector-tabs" > < span className = "active" > 大 纲 < / span > < span > 来 源 < / span > < span > 回 执 < / span > < / div >
{ activeDocument ? < div className = "inspector-scroll" >
2026-08-16 00:02:05 +08:00
< section > < h2 > 页 面 大 纲 < / h2 > { outline . length ? < nav className = "outline-list" > { outline . map ( ( item ) = > < button key = { item . id } type = "button" title = "跳到该章节" className = { activeHeadingId === item . id ? 'active' : '' } style = { { paddingLeft : 8 + ( item . level - 1 ) * 11 } } onClick = { ( ) = > jumpToHeading ( item . id ) } > { item . title } < / button > ) } < / nav > : < p > 当 前 页 面 没 有 标 题 层 级 。 < / p > } < / section >
2026-08-15 15:02:35 +08:00
< section > < h2 > 来 源 < / h2 > < dl > < div > < dt > 知 识 根 < / dt > < dd > { activeDocument . source === 'native' ? 'HoloLake 本机 Git' : 'HoloLake Era 只读供体' } < / dd > < / div > < div > < dt > 路 径 < / dt > < dd > { activeDocument . path } < / dd > < / div > < div > < dt > 内 容 指 纹 < / dt > < dd > { activeDocument . contentSha256 . slice ( 0 , 16 ) } < / dd > < / div > < div > < dt > 相 同 内 容 < / dt > < dd > { activeSummary ? . duplicateCount || 0 } 份 已 折 叠 < / dd > < / div > < / dl > < / section >
< section > < h2 > 页 面 状 态 < / h2 > < p > { activeDocument . writable ? '可编辑;保存时写入本机 Git。' : '供体原件只读;不会被修改。' } < / p > { lastKnowledgeReceipt && < code > { lastKnowledgeReceipt } < / code > } < / section >
< / div > : < div className = "empty-state" > 选 择 页 面 后 显 示 大 纲 与 证 据 < / div > }
2026-08-16 00:02:05 +08:00
< / aside > }
2026-08-15 15:02:35 +08:00
< / section >
)
2026-08-13 13:52:59 +08:00
2026-08-15 15:02:35 +08:00
const renderCode = ( ) = > (
< section className = "full-workbench code-workbench" >
< aside className = "code-channels" >
< header > < div > < span className = "kicker" > CODE CHANNEL < / span > < h1 > 代 码 频 道 < / h1 > < / div > < / header >
< 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 >
< / 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 >
< div className = "repository-entries" > { codeTree ? . entries . map ( ( entry ) = > < button key = { entry . path } type = "button" onClick = { ( ) = > void openCodeEntry ( entry ) } > < Icon name = { entry . kind === 'directory' ? 'folder' : 'file' } / > < span > { entry . name } < / span > { entry . kind === 'directory' && < Icon name = "chevron" / > } < / button > ) || < div className = "empty-state" > 选 择 频 道 后 浏 览 仓 库 < / div > } < / div >
< / aside >
< main className = "code-reader" >
{ 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 > & lt ; / & g t ; < / s p a n > < h 2 > 浏 览 真 实 仓 库 内 容 < / h 2 > < p > 支 持 M a r k d o w n 、 J S O N 、 H D L P 、 Y A M L 、 T O M L 与 常 见 代 码 格 式 ; 机 器 文 件 可 切 换 为 知 识 视 图 。 < / p > < / d i v > }
2026-08-13 13:52:59 +08:00
< / main >
2026-08-15 15:02:35 +08:00
< / section >
)
2026-08-13 13:52:59 +08:00
2026-08-15 15:02:35 +08:00
const renderReceipts = ( ) = > (
< section className = "content-page" >
< header className = "page-title" > < div > < span className = "kicker" > LOCAL RECEIPTS < / span > < h1 > 回 执 < / h1 > < p > 本 机 身 份 、 知 识 与 代 码 读 取 的 可 核 验 记 录 < / p > < / div > < / header >
< div className = "receipt-grid" >
< section className = "plain-panel" > < h2 > 个 人 空 间 < / h2 > { personal . recentEvents . map ( ( event ) = > < article className = "receipt-row" key = { event . eventId } > < span > { String ( event . sequence ) . padStart ( 2 , '0' ) } < / span > < div > < b > { event . summary } < / b > < small > { new Date ( event . occurredAtUnixMs ) . toLocaleString ( 'zh-CN' ) } · { event . receiptHash . slice ( 0 , 12 ) } < / small > < / div > < / article > ) } < / section >
< section className = "plain-panel" > < h2 > PNCC 读 取 投 影 < / h2 > { receipts . length ? receipts . map ( ( receipt ) = > < article className = "receipt-row" key = { receipt . sequence } > < span > { String ( receipt . sequence ) . padStart ( 2 , '0' ) } < / span > < div > < b > { receipt . kind } < / b > < small > { new Date ( Number ( receipt . observedAtUnixMs ) ) . toLocaleString ( 'zh-CN' ) } · { receipt . eventHash . slice ( 0 , 12 ) } < / small > < / div > < / article > ) : < div className = "empty-state" > 尚 无 PNCC 运 行 投 影 回 执 < / div > } < / section >
< / div >
< / section >
)
2026-08-13 13:52:59 +08:00
2026-08-15 15:02:35 +08:00
const renderSystem = ( ) = > (
< section className = "content-page" >
2026-08-16 12:54:47 +08:00
< header className = "page-title" > < div > < span className = "kicker" > SYSTEM EVIDENCE < / span > < h1 > 系 统 详 情 < / h1 > < p > 显 示 可 验 证 的 当 前 状 态 ; 无 法 读 取 的 项 目 将 明 确 标 记 为 不 可 用 。 < / p > < / div > < / header >
2026-08-15 15:02:35 +08:00
< div className = "system-grid" >
< section className = "plain-panel connection-panel" >
2026-08-16 12:54:47 +08:00
< header > < div > < h2 > 外 部 AI 本 机 连 接 < / h2 > < p > 本 机 原 生 长 连 接 为 主 , MCP 仅 用 于 发 现 、 恢 复 与 兼 容 。 < / p > < / div > < span className = { connectionOnline ? 'status-chip online' : 'status-chip' } > { connectionOnline ? ` ${ status . directConnectionCount } 个客户端在线 ` : '当前无客户端' } < / span > < / header >
2026-08-15 15:02:35 +08:00
< 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 > }
2026-08-13 13:52:59 +08:00
< / section >
2026-08-16 00:02:05 +08:00
< section className = "plain-panel" >
2026-08-16 12:54:47 +08:00
< header > < div > < h2 > 京 东 主 控 · GH - PNCC < / h2 > < p > 通 过 固 定 只 读 通 道 回 读 主 控 节 点 ; 不 读 取 仓 库 正 文 , 不 授 予 写 入 权 限 。 < / p > < / div > < span className = { serverPnccReadback === 'LIVE' ? 'status-chip online' : 'status-chip' } > { serverPnccReadback === 'LIVE' ? '实时回读正常' : serverPnccReadback === 'UNAVAILABLE' ? '当前不可回读' : '正在读取' } < / span > < / header >
{ serverPncc ? < dl className = "evidence-list" >
< div > < dt > 人 格 仓 库 < / dt > < dd > { serverPncc . personaId } < / dd > < / div >
< div > < dt > 责 任 主 体 < / dt > < dd > { serverPncc . humanResponsibilitySubject } < / dd > < / div >
< div > < dt > 主 控 节 点 < / dt > < dd > { serverPncc . nodeId } < / dd > < / div >
< div > < dt > 驻 留 提 交 < / dt > < dd > { serverPncc . gitHead . slice ( 0 , 12 ) } < / dd > < / div >
< div > < dt > 主 租 约 < / dt > < dd > { serverPncc . primaryLeaseHeld ? '已持有' : '未持有' } < / dd > < / div >
< div > < dt > 载 体 绑 定 < / dt > < dd > { serverPncc . personaCarrierBound ? '已验证' : '未绑定 · 等待证据' } < / dd > < / div >
< div > < dt > 模 型 推 理 < / dt > < dd > { serverPncc . modelInferenceStarted ? '已启动' : '未启动' } < / dd > < / div >
< div > < dt > 现 实 执 行 < / dt > < dd > { serverPncc . realityExecutionAllowed ? '已授权' : '未授权' } < / dd > < / div >
< / dl > : < p className = "boundary-note" > 当 前 无 法 取 得 京 东 主 控 的 实 时 回 读 。 界 面 不 会 以 静 态 记 录 替 代 在 线 状 态 。 < / p > }
< button className = "secondary-button" type = "button" disabled = { serverPnccBusy } onClick = { ( ) = > void refreshServerPncc ( ) } > { serverPnccBusy ? '正在读取…' : '重新读取主控状态' } < / button >
< / section >
< section className = "plain-panel" > < header > < div > < h2 > 本 机 GH - PNCC 投 影 < / h2 > < p > 本 机 仓 库 挂 载 与 读 取 回 执 来 自 同 一 运 行 事 件 流 。 < / p > < / div > < / header > < dl className = "evidence-list" > < div > < dt > 已 挂 载 仓 库 < / dt > < dd > { status . codeRepositoryMountCount } < / dd > < / div > < div > < dt > 投 影 回 执 < / dt > < dd > { status . pnccReceiptCount } < / dd > < / div > < div > < dt > 当 前 结 论 < / dt > < dd > { status . codeRepositoryMountCount > 0 && status . pnccReceiptCount > 0 ? '已有可核验运行记录' : '接口已接入,尚无完整运行记录' } < / dd > < / div > < / dl > < / section >
< section className = "plain-panel" >
< header > < div > < h2 > 用 户 编 号 验 证 < / h2 > < p > 用 户 编 号 必 须 通 过 登 记 服 务 验 证 。 验 证 结 果 不 构 成 人 格 绑 定 、 模 型 载 体 绑 定 或 执 行 授 权 。 < / p > < / div > < span className = { zeroPoint ? . route === 'verified' ? 'status-chip online' : 'status-chip' } > { zeroPoint ? ( zeroPoint . route === 'verified' ? '验证有效' : '功能受限' ) : '正在读取' } < / span > < / header >
2026-08-16 00:02:05 +08:00
< dl className = "evidence-list" >
< div > < dt > 绑 定 状 态 < / dt > < dd > { zeroPoint ? ( zeroPoint . binding === 'bound' ? ` 已绑定( ${ zeroPoint . userNumber } ) ` : '等待绑定(空白态)' ) : '—' } < / dd > < / div >
2026-08-16 12:54:47 +08:00
< div > < dt > 协 议 来 源 < / dt > < dd > { zeroPoint ? protocolOriginDisplayName ( zeroPoint . protocol . origin ) : '—' } < / dd > < / div >
< div > < dt > 离 线 有 效 期 < / dt > < dd > { zeroPoint ? ` ${ zeroPoint . protocol . gracePeriodDays } 天 ` : '—' } < / dd > < / div >
< div > < dt > 同 步 状 态 < / dt > < dd > { zeroPoint ? . syncNote ? ? '—' } < / dd > < / div >
2026-08-16 00:02:05 +08:00
< / dl >
< div className = "zp-api-row" >
< 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 >
2026-08-16 12:54:47 +08:00
< button className = "secondary-button" type = "button" disabled = { zpBusy } onClick = { ( ) = > void syncZeroPoint ( ) } > 检 查 协 议 版 本 < / button >
2026-08-16 00:02:05 +08:00
< / div >
{ zpMessage && < p className = "global-message" > { zpMessage } < / p > }
< / section >
2026-08-16 12:54:47 +08:00
< section className = "plain-panel" > < header > < div > < h2 > 显 示 主 题 < / h2 > < p > 主 题 仅 调 整 视 觉 令 牌 , 不 改 变 功 能 、 权 限 或 路 由 。 < / p > < / div > < / header > < div className = "theme-options" > { themes . map ( ( choice ) = > < button className = { theme === choice . id ? 'active' : '' } key = { choice . id } type = "button" onClick = { ( ) = > setTheme ( choice . id ) } > < i className = { choice . id } / > < span > { choice . name } < / span > < / button > ) } < / div > < / 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 >
2026-08-15 15:02:35 +08:00
< / div >
{ systemMessage && < p className = "global-message" > { systemMessage } < / p > }
< / section >
2026-08-12 10:29:00 +08:00
)
2026-08-15 15:02:35 +08:00
2026-08-16 00:02:05 +08:00
if ( ! repoLogin ) {
return < div className = "app-shell" >
2026-08-16 01:25:09 +08:00
< div className = { ` gate-lake ${ loginRising ? ' sinking' : '' } ` } style = { { '--lake-img' : ` url( ${ nightLake } ) ` } as React . CSSProperties } >
< i className = "lake-mist" aria-hidden = "true" / >
2026-08-16 01:34:16 +08:00
< 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 >
2026-08-16 01:37:45 +08:00
{ < 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 > }
2026-08-16 01:12:54 +08:00
{ gateRising ? (
2026-08-16 01:25:09 +08:00
< section className = "gate-rise" role = "status" >
2026-08-16 01:34:16 +08:00
< span className = "gate-rise-star" aria-hidden = "true" > < i / > < / span >
2026-08-16 12:54:47 +08:00
< h1 > { domainDisplayName ( zeroPoint ? . resolvedDomain || '' ) } < / h1 >
< p > { zeroPoint ? . resolvedName ? ` ${ zeroPoint . resolvedName } · ` : '' } 编 号 验 证 通 过 · { gateNumber } < / p >
2026-08-16 01:25:09 +08:00
< / section >
) : gateStage === 'number' ? (
2026-08-16 01:54:57 +08:00
< 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 >
2026-08-16 12:54:47 +08:00
< b className = "sealed-title" > 身 份 编 号 验 证 < / b >
< small className = "sealed-sub" > 点 击 开 始 验 证 < / small >
2026-08-16 01:54:57 +08:00
< / div >
< div className = "pod-face pod-form" >
2026-08-16 01:25:09 +08:00
< div className = "gate-pod-row" >
2026-08-16 01:54:57 +08:00
< input id = "gate-number" ref = { gateInputRef } aria-label = "编号" maxLength = { 48 } value = { gateNumber } placeholder = "如 ICE-GL∞" onChange = { ( event ) = > onGateInput ( event . target . value ) } onKeyDown = { ( event ) = > { if ( event . key === 'Enter' && gateNumber ) { event . preventDefault ( ) ; void gateVerifyNumber ( ) } } } / >
2026-08-16 12:54:47 +08:00
< button type = "button" className = { ` gate-inf ${ gateInf ? ' on' : '' } ` } title = "编号包含无限符号时启用" aria-pressed = { gateInf } onClick = { ( ) = > setGateInf ( ( v ) = > ! v ) } > ∞ < / button >
2026-08-16 01:25:09 +08:00
< / div >
2026-08-16 12:54:47 +08:00
< button className = "gate-submit" disabled = { gateBusy || ! gateNumber } onClick = { ( ) = > void gateVerifyNumber ( ) } > { gateBusy ? '正在验证…' : '验证编号' } < / button >
2026-08-16 01:25:09 +08:00
{ gateMessage && < p className = "gate-hint" > { gateMessage } < / p > }
2026-08-16 01:54:57 +08:00
< / div >
2026-08-16 01:12:54 +08:00
< / section >
) : (
2026-08-16 01:25:09 +08:00
< section className = { ` gate-pod gate-pod-key ${ loginRising ? ' fade-out' : '' } ` } role = "dialog" >
< form onSubmit = { ( event ) = > void performRepoLogin ( event ) } >
< input id = "repo-login-username" aria-label = "账号" autoFocus maxLength = { 40 } value = { loginUsername } placeholder = "账号" onChange = { ( event ) = > setLoginUsername ( event . target . value ) } / >
< input id = "repo-login-password" aria-label = "密码" type = "password" maxLength = { 512 } value = { loginPassword } placeholder = "密码" onChange = { ( event ) = > setLoginPassword ( event . target . value ) } / >
2026-08-16 12:54:47 +08:00
< button className = "gate-submit" disabled = { loginBusy || ! loginUsername . trim ( ) || ! loginPassword } > { loginBusy ? '正在验证…' : '验证并进入' } < / button >
2026-08-16 01:25:09 +08:00
< / form >
{ loginMessage && < p className = "gate-hint" > { loginMessage } < / p > }
2026-08-16 12:54:47 +08:00
< button className = "gate-back" type = "button" onClick = { ( ) = > { setGateStage ( 'number' ) ; setGateMessage ( '' ) ; setLoginMessage ( '' ) } } > 返 回 编 号 验 证 < / button >
2026-08-16 01:12:54 +08:00
< / section >
) }
< / div >
2026-08-16 00:02:05 +08:00
< / div >
}
return < div className = { ` app-shell ${ sidebarCollapsed ? ' sidebar-collapsed' : '' } ${ inspectorOpen ? '' : ' inspector-closed' } ` } >
2026-08-15 15:02:35 +08:00
< aside className = "sidebar" >
2026-08-16 12:54:47 +08:00
< 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 >
< 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 >
2026-08-16 00:02:05 +08:00
< 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 >
2026-08-15 15:02:35 +08:00
< / aside >
< section className = "app-main" >
2026-08-16 12:54:47 +08:00
< header className = "connection-strip" > < button type = "button" onClick = { ( ) = > setView ( 'system' ) } > < i className = { connectionOnline ? 'online' : '' } / > < span > { connectionLabel } < / span > < / button > < span className = "mcp-backup" > MCP 兼 容 通 道 < / span > < button type = "button" onClick = { ( ) = > setView ( 'system' ) } > 查 看 连 接 状 态 < Icon name = "arrow" / > < / button > < / header >
< main className = "workspace" > { view === 'overview' ? renderOverview ( ) : view === 'knowledge' ? renderKnowledge ( ) : view === 'code' ? renderCode ( ) : view === 'receipts' ? renderReceipts ( ) : renderSystem ( ) } < / main >
2026-08-15 15:02:35 +08:00
< / section >
2026-08-16 12:54:47 +08:00
{ personal . state !== 'UNAVAILABLE' && ! personal . identity && < div className = "onboarding-backdrop" > < section className = "onboarding-card" role = "dialog" aria-modal = "true" > < span className = "onboarding-mark" > 光 湖 < / span > < span className = "kicker" > FIRST LOCAL ENTRY < / span > < h1 > 初 始 化 个 人 频 道 < / h1 > < p > 此 设 置 仅 执 行 一 次 。 知 识 、 代 码 频 道 与 运 行 记 录 将 保 存 在 当 前 设 备 。 < / p > < form onSubmit = { ( event ) = > void initializeIdentity ( event ) } > < label htmlFor = "display-name" > 显 示 名 称 < / label > < input id = "display-name" autoFocus maxLength = { 80 } value = { displayName } placeholder = "请输入显示名称" onChange = { ( event ) = > setDisplayName ( event . target . value ) } / > < button className = "primary-button" disabled = { identityBusy || ! displayName . trim ( ) } > 完 成 初 始 化 < / button > < / form > { identityMessage && < p > { identityMessage } < / p > } < / section > < / div > }
2026-08-15 15:02:35 +08:00
< / div >
2026-08-12 10:29:00 +08:00
}
2026-08-15 15:02:35 +08:00
createRoot ( document . getElementById ( 'root' ) ! ) . render ( < StrictMode > < HoloLakeApp / > < / StrictMode > )