2026-08-13 13:52:59 +08:00
import { StrictMode , useCallback , useEffect , useMemo , 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-15 15:02:35 +08:00
import DOMPurify from 'dompurify'
import { marked } from 'marked'
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-15 15:02:35 +08:00
type ViewId = 'overview' | 'knowledge' | 'code' | 'receipts' | 'system'
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 }
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 14:01:43 +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:11:48 +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:11:48 +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:11:48 +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 }
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 14:45:17 +08:00
]
2026-08-15 15:02:35 +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-15 15:02:35 +08:00
function humanError ( error : unknown , context : 'knowledge' | 'code' | 'identity' | 'system' ) {
const value = String ( error )
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 '该文件不是当前可阅读的文本格式。'
if ( context === 'identity' ) return '个人空间未能建立。'
if ( context === 'knowledge' ) return '知识操作未完成,原文件未被覆盖。'
if ( context === 'code' ) return '代码频道操作未完成。'
return '系统操作未完成。'
}
function Icon ( { name } : { name : ViewId | 'search' | 'import' | 'folder' | 'copy' | 'arrow' | 'chevron' | 'file' | 'edit' | 'save' | 'back' } ) {
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" / > < / > ,
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
function escapeHtml ( value : string ) {
return value . replaceAll ( '&' , '&' ) . replaceAll ( '<' , '<' ) . replaceAll ( '>' , '>' ) . replaceAll ( '"' , '"' )
}
function splitFrontmatter ( body : string ) {
const normalized = body . replace ( /\r\n/g , '\n' )
if ( ! normalized . startsWith ( '---\n' ) ) return { content : normalized , metadata : { } as Record < string , string > }
const closing = normalized . indexOf ( '\n---\n' , 4 )
if ( closing < 0 ) return { content : normalized , metadata : { } as Record < string , string > }
const metadata : Record < string , string > = { }
for ( const line of normalized . slice ( 4 , closing ) . split ( '\n' ) ) {
const match = /^([A-Za-z0-9_-]+):\s*(.*)$/ . exec ( line )
if ( match ) metadata [ match [ 1 ] ] = match [ 2 ] . replace ( /^['"]|['"]$/g , '' )
}
return { content : normalized.slice ( closing + 5 ) , metadata }
}
function markdownHtml ( body : string ) {
const withWikiLinks = body . replace ( /\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g , ( _whole , target : string , label? : string ) = >
` <button class="wiki-link" type="button" data-wiki=" ${ escapeHtml ( target . trim ( ) ) } "> ${ escapeHtml ( ( label || target ) . trim ( ) ) } </button> ` )
const html = marked . parse ( withWikiLinks , { async : false , gfm : true , breaks : false } ) as string
return DOMPurify . sanitize ( html , { ADD_ATTR : [ 'data-wiki' ] , ADD_TAGS : [ 'button' ] } )
}
function documentOutline ( body : string ) {
return splitFrontmatter ( body ) . content . split ( '\n' ) . flatMap ( ( line , index ) = > {
const match = /^(#{1,4})\s+(.+)$/ . exec ( line . trim ( ) )
return match ? [ { id : ` outline- ${ index } ` , level : match [ 1 ] . length , title : match [ 2 ] . replace ( /[*_`]/g , '' ) } ] : [ ]
} ) . slice ( 0 , 24 )
}
function MarkdownDocument ( { body , onWiki } : { body : string ; onWiki ? : ( target : string ) = > void } ) {
const parsed = useMemo ( ( ) = > splitFrontmatter ( body ) , [ body ] )
const html = useMemo ( ( ) = > markdownHtml ( parsed . content ) , [ parsed . content ] )
return < article className = "markdown-document" onClick = { ( event ) = > {
const element = ( event . target as HTMLElement ) . closest < HTMLElement > ( '[data-wiki]' )
if ( element ? . dataset . wiki && onWiki ) onWiki ( element . dataset . wiki )
} } dangerouslySetInnerHTML = { { __html : html } } / >
}
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 )
}
function KnowledgeTree ( { node , depth , expanded , active , onToggle , onOpen } : { node : TreeNode ; depth : number ; expanded : Set < string > ; active? : string ; onToggle : ( key : string ) = > void ; onOpen : ( item : KnowledgeDocumentSummary ) = > void } ) {
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 >
{ open && < KnowledgeTree node = { folder } depth = { depth + 1 } expanded = { expanded } active = { active } onToggle = { onToggle } onOpen = { onOpen } / > }
< / 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' )
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-15 15:02:35 +08:00
const [ systemBusy , setSystemBusy ] = useState ( false )
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-13 13:52:59 +08:00
useEffect ( ( ) = > {
document . documentElement . dataset . theme = theme
window . localStorage . setItem ( 'hololake-theme' , theme )
} , [ theme ] )
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 ] )
const connectionOnline = status . directConnectionCount > 0
const connectionLabel = connectionOnline
? '本地原生连接 · 客户端在线'
: status . resumableSessionCount > 0
? '原生会话可续接 · 当前离线'
: '原生接口已驻留 · 尚无客户端'
const openDocument = async ( source : KnowledgeSource , path : string ) = > {
setKnowledgeBusy ( true )
setKnowledgeMessage ( '' )
setEditing ( false )
try {
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 13:52:59 +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 13:52:59 +08:00
}
2026-08-15 15:02:35 +08:00
const importKnowledge = async ( ) = > {
setKnowledgeBusy ( true )
setKnowledgeMessage ( '' )
2026-08-13 14:01:43 +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-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:01:43 +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:01:43 +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:11:48 +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 ( '本地代码频道已登记,原文件未移动。' )
}
} 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-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-15 15:02:35 +08:00
const renderOverview = ( ) = > (
< section className = "content-page overview-page" >
< header className = "page-title" >
< div > < span className = "kicker" > PERSONAL HOLOLAKE < / span > < h1 > 个 人 工 作 空 间 < / h1 > < p > 知 识 、 代 码 与 本 机 协 作 的 统 一 入 口 < / p > < / div >
< 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" >
< header > < div > < h2 > 运 行 证 据 < / h2 > < p > 状 态 来 自 当 前 进 程 , 不 使 用 演 示 值 < / p > < / div > < / header >
< 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 } ) }
onOpen = { ( item ) = > void openDocument ( item . source , item . path ) } / >
: < 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 >
< div > { editing
? < > < 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 > < / >
: activeDocument . writable && < button className = "toolbar-button" type = "button" onClick = { ( ) = > { setDraft ( activeDocument . body ) ; setEditing ( true ) } } > < Icon name = "edit" / > 编 辑 < / button > }
< / div >
< / header >
< div className = "document-scroll" >
{ editing
? < textarea className = "document-editor" aria-label = "Markdown 编辑器" value = { draft } onChange = { ( event ) = > setDraft ( event . target . value ) } / >
: < > < header className = "reader-heading" > < h1 > { activeDocument . title } < / h1 > < div > { Object . entries ( parsedDocument ? . metadata || { } ) . slice ( 0 , 5 ) . map ( ( [ key , value ] ) = > < span key = { key } > { key } · { value } < / span > ) } < / div > < / header > < MarkdownDocument body = { activeDocument . body } 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 >
< aside className = "document-inspector" >
< div className = "inspector-tabs" > < span className = "active" > 大 纲 < / span > < span > 来 源 < / span > < span > 回 执 < / span > < / div >
{ activeDocument ? < div className = "inspector-scroll" >
< section > < h2 > 页 面 大 纲 < / h2 > { outline . length ? < nav className = "outline-list" > { outline . map ( ( item ) = > < span key = { item . id } style = { { paddingLeft : ( item . level - 1 ) * 11 } } > { item . title } < / span > ) } < / nav > : < p > 当 前 页 面 没 有 标 题 层 级 。 < / p > } < / section >
< 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 > }
< / aside >
< / 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" >
< 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 > 人 格 体 本 地 原 生 连 接 < / h2 > < p > Unix Stream 长 连 接 为 主 , 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 > }
2026-08-13 13:52:59 +08:00
< / section >
2026-08-15 15:02:35 +08:00
< 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 > < / 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 > 只 接 受 登 记 到 本 机 的 签 名 发 布 源 < / p > < / div > < / header > { releaseCandidate ? < div className = "release-summary" > < b > HoloLake { releaseCandidate . version } < / b > < p > { releaseCandidate . notes } < / p > < button className = "primary-button" onClick = { ( ) = > void installUpdate ( ) } > 验 证 并 安 装 < / button > < / div > : < button className = "secondary-button" disabled = { systemBusy } onClick = { ( ) = > void checkUpdate ( ) } > 检 查 签 名 更 新 < / button > } < / section >
< / div >
{ systemMessage && < p className = "global-message" > { systemMessage } < / p > }
< / section >
2026-08-12 10:29:00 +08:00
)
2026-08-15 15:02:35 +08:00
return < div className = "app-shell" >
< aside className = "sidebar" >
< div className = "brand" > < span className = "brand-mark" > 光 < / span > < div > < b > HoloLake < / b > < small > GH - AIOS < / small > < / div > < / 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 > { personal . identity ? '个人频道已建立' : '等待首次设置' } < / small > < / div > < / div >
< / aside >
< section className = "app-main" >
< 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 >
< / section >
{ 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 ( ) } > 进 入 HoloLake < / button > < / form > { identityMessage && < p > { identityMessage } < / p > } < / section > < / div > }
< / 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 > )