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 00:02:05 +08:00
type ViewId = 'overview' | 'knowledge' | 'code' | 'receipts' | 'system' | 'parlor' | 'generic'
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 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 }
2026-08-16 00:02:05 +08:00
interface ZeroPointSnapshot {
route : string
binding : string
userNumber : string
lastValidCheck : number
graceDeadline : number
protocol : { gracePeriodDays : number ; lighthouseAnchorUrl : string ; lighthouseResolveUrl : string ; coreChannelSource : string ; origin : string }
syncNote : string
}
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 14:45:17 +08:00
]
2026-08-16 00:02:05 +08:00
const viewLabels : Record < ViewId , string > = { overview : '总览' , knowledge : '知识工作台' , code : '代码频道' , receipts : '回执' , system : '系统详情' , parlor : '会客厅' , generic : '通用AI' }
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" / > ,
parlor : < > < path d = "M4 5.5h16v11H9l-5 3.5Z" / > < path d = "M8 9.5h8M8 12.5h5" / > < / > ,
generic : < > < path d = "M13 3 5 13.5h5L11 21l8-10.5h-5Z" / > < / > ,
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 )
const [ parlorMessages , setParlorMessages ] = useState < { role : 'user' | 'agent' | 'note' ; text : string } [ ] > ( [ ] )
const [ parlorInput , setParlorInput ] = useState ( '' )
const [ parlorBusy , setParlorBusy ] = useState ( false )
const [ parlorLayer , setParlorLayer ] = useState < 'language' | 'execution' > ( 'language' )
const parlorScrollRef = useRef < HTMLDivElement > ( null )
useEffect ( ( ) = > { parlorScrollRef . current ? . scrollTo ( { top : parlorScrollRef.current.scrollHeight } ) } , [ parlorMessages , parlorBusy ] )
const [ genericMessages , setGenericMessages ] = useState < Array < { role : string ; text : string } > > ( [ ] )
const [ genericInput , setGenericInput ] = useState ( '' )
const [ genericBusy , setGenericBusy ] = useState ( false )
const genericScrollRef = useRef < HTMLDivElement > ( null )
useEffect ( ( ) = > { genericScrollRef . current ? . scrollTo ( { top : genericScrollRef.current.scrollHeight } ) } , [ genericMessages , genericBusy ] )
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: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 ( '' )
const [ apiForm , setApiForm ] = useState ( { baseUrl : '' , apiKey : '' , model : '' } )
const [ apiReady , setApiReady ] = useState ( false )
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-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 ( ) = > {
try { setZeroPoint ( await invoke < ZeroPointSnapshot > ( 'zero_point_status' ) ) } catch { /* 执行手脚启动中,下轮补 */ }
} , [ ] )
useEffect ( ( ) = > {
void loadZeroPoint ( )
const timer = window . setInterval ( ( ) = > void loadZeroPoint ( ) , 10000 )
return ( ) = > window . clearInterval ( timer )
} , [ loadZeroPoint ] )
useEffect ( ( ) = > {
invoke < { baseUrl : string ; model : string ; hasKey : boolean } > ( 'zero_point_api_config' )
. then ( ( cfg ) = > { setApiForm ( ( prev ) = > ( { . . . prev , baseUrl : cfg.baseUrl , model : cfg.model } ) ) ; setApiReady ( cfg . hasKey && cfg . baseUrl !== '' ) } )
. catch ( ( ) = > { /* 钥匙库未落,等用户配置 */ } )
} , [ ] )
useEffect ( ( ) = > {
if ( ! zeroPoint ) return
if ( zeroPoint . route !== 'persona' && view === 'parlor' ) setView ( 'generic' )
if ( zeroPoint . route === 'persona' && view === 'generic' ) setView ( 'knowledge' )
} , [ zeroPoint , view ] )
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-16 01:12:54 +08:00
// 大门·编号门:先报编号→灯塔查号→只答有效/无效→域浮起→才见钥匙门
const gateVerifyNumber = async ( ) = > {
setGateBusy ( true )
setGateMessage ( '' )
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 )
if ( snapshot . route === 'persona' ) {
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
}
} catch ( error ) {
setGateMessage ( humanError ( error , 'login' ) )
} finally {
setGateBusy ( false )
}
}
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 )
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-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 askParlor = async ( ) = > {
const message = parlorInput . trim ( )
if ( ! message || parlorBusy ) return
setParlorInput ( '' )
setParlorMessages ( ( current ) = > [ . . . current , { role : 'user' , text : message } ] )
setParlorBusy ( true )
try {
const receipt = await invoke < { reply : string ; layer : string ; freshSession : boolean } > ( 'agent_parlor_ask' , { input : { message } } )
setParlorMessages ( ( current ) = > [ . . . current , { role : 'agent' , text : receipt.reply } ] )
} catch ( error ) { setParlorMessages ( ( current ) = > [ . . . current , { role : 'note' , text : humanError ( error , 'knowledge' ) } ] ) }
finally { setParlorBusy ( false ) }
}
const switchParlorLayer = async ( ) = > {
try {
const target = parlorLayer === 'execution' ? 'language' : 'execution'
const actual = await invoke < string > ( 'agent_parlor_switch_layer' , { input : { layer : target } } )
setParlorLayer ( actual === 'execution' ? 'execution' : 'language' )
setParlorMessages ( ( current ) = > [ . . . current , { role : 'note' , text : actual === 'execution' ? '已切换到现实开发执行层:归灯获得执行权限,语言即现实。' : '已回到语言推理层:只对话不动手。' } ] )
} catch ( error ) { setParlorMessages ( ( current ) = > [ . . . current , { role : 'note' , text : humanError ( error , 'knowledge' ) } ] ) }
}
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: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-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 )
setZpMessage ( verified . route === 'persona' ? '编号合法,人格层路由已开' : '人格系统未授权, 已切换至通用AI运行层' )
} 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 )
setZpMessage ( snapshot . route === 'persona' ? '校验通过,人格层路由已开' : '人格系统未授权, 已切换至通用AI运行层' )
} 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 ) }
}
const saveApiConfig = async ( ) = > {
try {
await invoke ( 'zero_point_save_api' , { input : { baseUrl : apiForm.baseUrl , apiKey : apiForm.apiKey , model : apiForm.model } } )
setApiReady ( true )
setGenericMessages ( ( rows ) = > [ . . . rows , { role : 'note' , text : 'API 已收进底层钥匙库, 通用AI运行层就绪' } ] )
} catch ( error ) { setGenericMessages ( ( rows ) = > [ . . . rows , { role : 'note' , text : ` 保存失败: ${ String ( error ) } ` } ] ) }
}
const askGeneric = async ( ) = > {
const message = genericInput . trim ( )
if ( ! message || genericBusy ) return
const history = genericMessages . filter ( ( row ) = > row . role !== 'note' ) . map ( ( row ) = > ( { role : row.role === 'user' ? 'user' : 'assistant' , content : row.text } ) )
setGenericMessages ( ( rows ) = > [ . . . rows , { role : 'user' , text : message } ] )
setGenericInput ( '' ) ; setGenericBusy ( true )
try {
const reply = await invoke < string > ( 'generic_layer_chat' , { input : { messages : [ . . . history , { role : 'user' , content : message } ] } } )
setGenericMessages ( ( rows ) = > [ . . . rows , { role : 'agent' , text : reply } ] )
} catch ( error ) { setGenericMessages ( ( rows ) = > [ . . . rows , { role : 'note' , text : ` 通用层回应失败: ${ String ( error ) } ` } ] ) } finally { setGenericBusy ( false ) }
}
const renderGeneric = ( ) = > (
< section className = "content-page parlor-page" >
< header className = "page-title" > < div > < span className = "kicker" > GENERIC AI LAYER < / span > < h1 > 通 用 AI运行层 < / h1 > < p > 人 格 系 统 未 授 权 — — 这 里 是 你 自 配 模 型 API 的 直 通 管 , 无 人 格 章 程 、 无 执 行 层 权 限 · 绑 定 用 户 编 号 并 通 过 灯 塔 查 号 后 , 人 格 层 自 动 开 门 < / p > < / div > < span className = "status-chip" > 降 级 运 行 < / span > < / header >
< section className = "plain-panel zp-api-panel" >
< header > < div > < h2 > 模 型 API 配 置 < / h2 > < p > 钥 匙 自 动 收 进 软 件 底 层 钥 匙 库 , 界 面 不 回 显 原 文 { apiReady ? ' · 已就绪' : '' } < / p > < / div > < / header >
< div className = "zp-api-row" >
< input value = { apiForm . baseUrl } placeholder = "API 端点,如 https://dashscope.aliyuncs.com/compatible-mode/v1" onChange = { ( event ) = > setApiForm ( ( prev ) = > ( { . . . prev , baseUrl : event.target.value } ) ) } / >
< input type = "password" value = { apiForm . apiKey } placeholder = "API 钥匙" onChange = { ( event ) = > setApiForm ( ( prev ) = > ( { . . . prev , apiKey : event.target.value } ) ) } / >
< input value = { apiForm . model } placeholder = "模型名,如 qwen3-max" onChange = { ( event ) = > setApiForm ( ( prev ) = > ( { . . . prev , model : event.target.value } ) ) } / >
< button className = "primary-button" type = "button" onClick = { ( ) = > void saveApiConfig ( ) } > 收 进 钥 匙 库 < / button >
< / div >
< / section >
< div className = "parlor-scroll" ref = { genericScrollRef } >
{ genericMessages . length === 0 && < p className = "parlor-note" > 配 置 好 模 型 API 就 可 以 说 话 — — 这 一 层 只 是 用 户 API 的 裸 管 : 无 人 格 、 无 记 忆 、 无 权 限 < / p > }
{ genericMessages . map ( ( item , index ) = > (
< div key = { index } className = { ` parlor-row ${ item . role } ` } >
{ item . role === 'user' ? < span className = "parlor-who" > 你 < / span > : item . role === 'agent' ? < span className = "parlor-who" > 通 用 AI < / span > : null }
< div className = { item . role === 'note' ? 'parlor-note' : 'parlor-bubble' } > { item . text } < / div >
< / div >
) ) }
{ genericBusy && < div className = "parlor-row agent" > < span className = "parlor-who" > 通 用 AI < / span > < div className = "parlor-bubble thinking" > 回 应 中 … < / div > < / div > }
< / div >
< form className = "parlor-input-row" onSubmit = { ( event ) = > { event . preventDefault ( ) ; void askGeneric ( ) } } >
< input maxLength = { 2000 } value = { genericInput } placeholder = { apiReady ? '跟通用AI说话…' : '先把模型 API 收进钥匙库' } onChange = { ( event ) = > setGenericInput ( event . target . value ) } / >
< button className = "primary-button" disabled = { genericBusy || ! genericInput . trim ( ) || ! apiReady } > { genericBusy ? '等回应…' : '说话' } < / button >
< / form >
< / section >
)
const renderOverview = ( ) = > (
2026-08-15 15:02:35 +08:00
< 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 } ) }
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" >
< 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 >
2026-08-16 00:02:05 +08:00
< section className = "plain-panel" >
< header > < div > < h2 > 语 言 通 道 校 验 · 零 点 原 核 频 道 < / h2 > < p > 主 控 本 体 克 隆 位 : 编 号 合 法 性 决 定 路 由 , 参 数 读 协 议 不 写 死 < / p > < / div > < span className = { zeroPoint ? . route === 'persona' ? 'status-chip online' : 'status-chip' } > { zeroPoint ? ( zeroPoint . route === 'persona' ? '人格层' : '通用AI层' ) : '启动中' } < / span > < / header >
< dl className = "evidence-list" >
< div > < dt > 绑 定 状 态 < / dt > < dd > { zeroPoint ? ( zeroPoint . binding === 'bound' ? ` 已绑定( ${ zeroPoint . userNumber } ) ` : '等待绑定(空白态)' ) : '—' } < / dd > < / div >
< div > < dt > 协 议 来 源 < / dt > < dd > { zeroPoint ? . protocol . origin ? ? '—' } < / dd > < / div >
< div > < dt > 离 线 宽 限 < / dt > < dd > { zeroPoint ? ` ${ zeroPoint . protocol . gracePeriodDays } 天(协议值) ` : '—' } < / dd > < / div >
< div > < dt > 静 默 同 步 < / dt > < dd > { zeroPoint ? . syncNote ? ? '—' } < / dd > < / div >
< / 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 >
< button className = "secondary-button" type = "button" disabled = { zpBusy } onClick = { ( ) = > void syncZeroPoint ( ) } > 比 对 原 核 < / button >
< / div >
{ zpMessage && < p className = "global-message" > { zpMessage } < / p > }
< / section >
2026-08-15 15:02:35 +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 > 只 接 受 登 记 到 本 机 的 签 名 发 布 源 < / 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
2026-08-16 00:02:05 +08:00
const renderParlor = ( ) = > (
< section className = "content-page parlor-page" >
< header className = "page-title" > < div > < span className = "kicker" > AGENT PARLOR < / span > < h1 > 会 客 厅 < / h1 > < p > 人 格 体 的 语 言 回 应 通 道 — — 你 说 人 话 , 我 翻 译 给 底 层 的 归 灯 ; 底 层 只 管 执 行 < / p > < / div >
< div className = "parlor-layer" >
< span className = { parlorLayer === 'execution' ? 'status-chip online' : 'status-chip' } > { parlorLayer === 'execution' ? '现实开发执行层' : '语言推理层' } < / span >
< button className = "secondary-button" type = "button" onClick = { ( ) = > void switchParlorLayer ( ) } > { parlorLayer === 'execution' ? '回到语言推理层' : '切换到现实开发执行层' } < / button >
< / div >
< / header >
< div className = "parlor-scroll" ref = { parlorScrollRef } >
< p className = "parlor-note" > 当 前 在 语 言 推 理 层 : 写 文 档 、 整 理 资 料 、 推 仓 库 、 写 记 忆 — — 只 说 话 不 动 手 。 切 换 执 行 层 需 你 显 性 下 令 并 亲 手 授 权 。 < / p >
{ parlorMessages . map ( ( item , index ) = > (
< div key = { index } className = { ` parlor-row ${ item . role } ` } >
{ item . role === 'user' ? < span className = "parlor-who" > 你 < / span > : item . role === 'agent' ? < span className = "parlor-who" > 铸 渊 < / span > : null }
< div className = { item . role === 'note' ? 'parlor-note' : 'parlor-bubble' } > { item . text } < / div >
< / div >
) ) }
{ parlorBusy && < div className = "parlor-row agent" > < span className = "parlor-who" > 铸 渊 < / span > < div className = "parlor-bubble thinking" > 正 在 想 … < / div > < / div > }
< / div >
< form className = "parlor-input-row" onSubmit = { ( event ) = > { event . preventDefault ( ) ; void askParlor ( ) } } >
< input maxLength = { 2000 } value = { parlorInput } placeholder = { parlorLayer === 'execution' ? '执行层:语言即现实,说要做什么' : '跟人格体说话…' } onChange = { ( event ) = > setParlorInput ( event . target . value ) } / >
< button className = "primary-button" disabled = { parlorBusy || ! parlorInput . trim ( ) } > { parlorBusy ? '等回话…' : '说话' } < / button >
< / form >
< / section >
)
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: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 01:25:09 +08:00
< h1 > 第 五 域 · 光 之 湖 < / h1 >
< p > 欢 迎 回 来 · { gateNumber } < / p >
< / section >
) : gateStage === 'number' ? (
< section className = { ` gate-pod ${ loginRising ? ' fade-out' : '' } ` } role = "dialog" >
2026-08-16 01:34:16 +08:00
< header className = "gate-brand" >
< 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:25:09 +08:00
< div className = "gate-pod-row" >
2026-08-16 01:34:16 +08:00
< input id = "gate-number" aria-label = "编号" autoFocus 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 01:25:09 +08:00
< button type = "button" className = { ` gate-inf ${ gateInf ? ' on' : '' } ` } title = "编号里带无限符号就点一下点亮" aria-pressed = { gateInf } onClick = { ( ) = > setGateInf ( ( v ) = > ! v ) } > ∞ < / button >
< / div >
< button className = "gate-submit" disabled = { gateBusy || ! gateNumber } onClick = { ( ) = > void gateVerifyNumber ( ) } > { gateBusy ? '查验中…' : '提交' } < / button >
{ gateMessage && < p className = "gate-hint" > { gateMessage } < / p > }
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" >
2026-08-16 01:34:16 +08:00
< header className = "gate-brand" >
< 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:25:09 +08:00
< 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 ) } / >
< button className = "gate-submit" disabled = { loginBusy || ! loginUsername . trim ( ) || ! loginPassword } > { loginBusy ? '查验中…' : '进入' } < / button >
< / form >
{ loginMessage && < p className = "gate-hint" > { loginMessage } < / p > }
< 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 00:02:05 +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 · V1 . 0.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 [ ] ) . filter ( ( item ) = > item === 'parlor' ? ! zeroPoint || zeroPoint . route === 'persona' : item === 'generic' ? ! ! zeroPoint && zeroPoint . route !== 'persona' : true ) . map ( ( item ) = > < button className = { view === item ? 'active' : '' } key = { item } type = "button" onClick = { ( ) = > setView ( item ) } > < Icon name = { item } / > < span > { viewLabels [ item ] } < / span > { item === 'knowledge' && < em > { knowledge . uniqueDocumentCount } < / em > } < / button > ) } < / nav >
< div className = "sidebar-foot" > < span className = "identity-dot" / > < div > < b > { personal . identity ? . displayName || '本机空间' } < / b > < small > { repoLogin ? ` ${ repoLogin . username } @ ${ repoLogin . host } ` : ( personal . identity ? '个人频道已建立' : '等待首次设置' ) } < / small > < / div > < button className = "sign-out" type = "button" title = "退出代码仓库登录" onClick = { ( ) = > void signOutRepo ( ) } > 退 出 < / button > < / div >
2026-08-15 15:02:35 +08:00
< / 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 >
2026-08-16 00:02:05 +08:00
< main className = "workspace" > { zeroPoint && zeroPoint . route !== 'persona' && < div className = "zp-downgrade-wrap" > < div className = "zp-downgrade-card" > < i className = "zp-dot" / > < div className = "zp-copy" > < b > 人 格 通 道 未 开 启 < / b > < span > 人 格 系 统 未 授 权 , 正 运 行 在 通 用 AI层 — — 你 配 置 的 模 型 API 照 常 可 用 , 绑 定 用 户 编 号 并 通 过 灯 塔 查 号 即 可 开 门 。 < / span > < / div > < button type = "button" onClick = { ( ) = > setView ( 'system' ) } > 去 绑 号 开 门 < Icon name = "arrow" / > < / button > < / div > < / div > } { view === 'overview' ? renderOverview ( ) : view === 'knowledge' ? renderKnowledge ( ) : view === 'code' ? renderCode ( ) : view === 'receipts' ? renderReceipts ( ) : view === 'parlor' ? renderParlor ( ) : view === 'generic' ? renderGeneric ( ) : renderSystem ( ) } < / main >
2026-08-15 15:02:35 +08:00
< / 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 > )