662 lines
34 KiB
TypeScript
662 lines
34 KiB
TypeScript
import { useCallback, useEffect, useRef, useState } from 'react'
|
||
import { isTauri } from '../mock-tauri'
|
||
import { fetchFifthDomainDiscovery, type FifthDomainDiscovery } from '../lib/fifthDomainDiscovery'
|
||
import type { AppLocale, TranslationKey } from '../lib/i18n'
|
||
import { translate } from '../lib/i18n'
|
||
import { trackEvent } from '../lib/telemetry'
|
||
import { openExternalUrl } from '../utils/url'
|
||
import { useGuanghuRouter } from '../hooks/useGuanghuRouter'
|
||
import { useGuanghuShanghaiNode } from '../hooks/useGuanghuShanghaiNode'
|
||
import { useGuanghuWorldLogin } from '../hooks/useGuanghuWorldLogin'
|
||
import { useGuanghuEnterpriseStatus } from '../hooks/useGuanghuEnterpriseStatus'
|
||
import type { AiModelTarget } from '../lib/aiTargets'
|
||
import {
|
||
createDeterministicLivingSystemPlan,
|
||
createLivingSystemEvent,
|
||
createLivingSystemExecutionReceipt,
|
||
type GuanghuChannelRoute,
|
||
type GuanghuLivingIntent,
|
||
type GuanghuLivingSystemPlan,
|
||
} from '../lib/guanghuLivingSystem'
|
||
import { GUANGHU_THEMES, type GuanghuTheme } from '../lib/guanghuTheme'
|
||
import { planGuanghuLivingSystem } from '../utils/planGuanghuLivingSystem'
|
||
import { Button } from './ui/button'
|
||
import { HOLOLAKE_REPOSITORY_URL } from '../constants/feedback'
|
||
import { isEducationWorkLakePath } from '../lib/educationWorkLake'
|
||
import { GuanghuRouterConsole } from './GuanghuRouterConsole'
|
||
import { GuanghuWorldMap } from './GuanghuWorldMap'
|
||
import { GuanghuWorldLoginGate } from './GuanghuWorldLoginGate'
|
||
import { FifthDomainSystems } from './FifthDomainSystems'
|
||
import { EternalLakeHeartPage } from './EternalLakeHeartPage'
|
||
import { EducationWorkLake } from './EducationWorkLake'
|
||
import {
|
||
Dialog,
|
||
DialogContent,
|
||
DialogDescription,
|
||
DialogHeader,
|
||
DialogTitle,
|
||
} from './ui/dialog'
|
||
|
||
export const HOLOLAKE_DEVELOPMENT_REPOSITORY_URL = HOLOLAKE_REPOSITORY_URL
|
||
export const GUANGHU_THEME_INTENT_EVENT = 'guanghu:living-system-theme-intent'
|
||
|
||
type HoloLakeHomeProps = {
|
||
locale: AppLocale
|
||
onEnterKnowledgeBase: () => void
|
||
onOpenAiWorkspace?: () => void
|
||
onOpenLocalWorkspace?: () => void | Promise<void>
|
||
livingSystemTarget?: AiModelTarget | null
|
||
}
|
||
|
||
type ChannelRoute = GuanghuChannelRoute
|
||
|
||
type RouteCardProps = {
|
||
action?: string
|
||
description: string
|
||
eyebrow: string
|
||
onOpen?: () => void
|
||
status?: string
|
||
title: string
|
||
}
|
||
|
||
const architectureRoutes: Array<[string, TranslationKey, TranslationKey]> = [
|
||
['GLW-ENTRY-001', 'hololake.architecture.worldEntry', 'hololake.architecture.worldEntryDescription'],
|
||
['FD-LANGUAGE-001', 'hololake.architecture.fifthDomain', 'hololake.architecture.fifthDomainDescription'],
|
||
['TCS-ROOT-001', 'hololake.architecture.tcs', 'hololake.architecture.tcsDescription'],
|
||
['GLS-SYS-ARCH-001', 'hololake.architecture.gls', 'hololake.architecture.glsDescription'],
|
||
['ELH-LAMP-001', 'hololake.architecture.lakeLamp', 'hololake.architecture.lakeLampDescription'],
|
||
]
|
||
|
||
function RouteCard({ action, description, eyebrow, onOpen, status, title }: RouteCardProps) {
|
||
return (
|
||
<article className="channel-card">
|
||
<span className="channel-card__eyebrow">{eyebrow}</span>
|
||
<h2>{title}</h2>
|
||
<p>{description}</p>
|
||
{onOpen && action
|
||
? <Button variant="link" onClick={onOpen}>{action} <span>→</span></Button>
|
||
: <span className="channel-card__status">{status}</span>}
|
||
</article>
|
||
)
|
||
}
|
||
|
||
function PersonaRoute({ id, title }: { id: string; title: string }) {
|
||
return <li><code>{id}</code><strong>{title}</strong><span>→</span></li>
|
||
}
|
||
|
||
export function HoloLakeHome({
|
||
locale,
|
||
onEnterKnowledgeBase,
|
||
onOpenAiWorkspace,
|
||
onOpenLocalWorkspace,
|
||
livingSystemTarget,
|
||
}: HoloLakeHomeProps) {
|
||
const initialRoute: ChannelRoute = isEducationWorkLakePath(window.location.pathname)
|
||
? 'education-work-lake'
|
||
: 'world'
|
||
const [route, setRoute] = useState<ChannelRoute>(initialRoute)
|
||
const [livingPlan, setLivingPlan] = useState<GuanghuLivingSystemPlan>(() => (
|
||
createDeterministicLivingSystemPlan(createLivingSystemEvent({
|
||
currentRoute: initialRoute,
|
||
eventId: 'system-start',
|
||
intent: 'navigate',
|
||
receiptIds: [],
|
||
requestedRoute: initialRoute,
|
||
worldOpen: false,
|
||
}))
|
||
))
|
||
const [livingKernel, setLivingKernel] = useState<'server' | 'model' | 'fallback' | 'planning'>('fallback')
|
||
const [livingReceiptId, setLivingReceiptId] = useState('local-state:system-start')
|
||
const [livingServerReceiptId, setLivingServerReceiptId] = useState<string>()
|
||
const [architectureOpen, setArchitectureOpen] = useState(false)
|
||
const [releaseNotesOpen, setReleaseNotesOpen] = useState(false)
|
||
const [fifthDomainConnection, setFifthDomainConnection] = useState<
|
||
{ status: 'checking' | 'error' } | { status: 'connected'; discovery: FifthDomainDiscovery }
|
||
>({ status: 'checking' })
|
||
const router = useGuanghuRouter()
|
||
const login = useGuanghuWorldLogin()
|
||
const enterprise = useGuanghuEnterpriseStatus()
|
||
const shanghai = useGuanghuShanghaiNode()
|
||
const restoreAttempted = useRef(false)
|
||
const livingRequestSequence = useRef(0)
|
||
const worldOpen = login.state.phase === 'online'
|
||
const fifthDomainOpen = worldOpen && route !== 'world'
|
||
const worldRouterState = router.state
|
||
const activeRouterReceipt = worldRouterState.status === 'online'
|
||
&& worldRouterState.latestReceipt?.state === 'online'
|
||
? worldRouterState.latestReceipt
|
||
: null
|
||
const t = (key: TranslationKey) => translate(locale, key)
|
||
|
||
const applyLivingPlan = useCallback((
|
||
plan: GuanghuLivingSystemPlan,
|
||
source: 'server' | 'model' | 'fallback',
|
||
serverReceiptId?: string,
|
||
) => {
|
||
setLivingPlan(plan)
|
||
setLivingKernel(source)
|
||
setLivingServerReceiptId(serverReceiptId)
|
||
setRoute(plan.navigationAction.route)
|
||
}, [])
|
||
|
||
const recordLivingExecution = useCallback((
|
||
plan: GuanghuLivingSystemPlan,
|
||
source: 'server' | 'model' | 'fallback',
|
||
outcome: 'executed' | 'failed',
|
||
evidence: string[],
|
||
error?: string,
|
||
serverReceiptId?: string,
|
||
) => {
|
||
const receipt = createLivingSystemExecutionReceipt({
|
||
plan,
|
||
source,
|
||
outcome,
|
||
evidence,
|
||
error,
|
||
})
|
||
setLivingReceiptId(receipt.receiptId)
|
||
trackEvent('guanghu_living_system_receipt', {
|
||
eventId: receipt.eventId,
|
||
outcome: receipt.outcome,
|
||
planId: receipt.planId,
|
||
receiptId: receipt.receiptId,
|
||
route: receipt.route,
|
||
source: receipt.source,
|
||
evidence: receipt.evidence.join(','),
|
||
...(serverReceiptId ? { serverReceiptId } : {}),
|
||
})
|
||
}, [])
|
||
|
||
const navigate = useCallback((
|
||
nextRoute: ChannelRoute,
|
||
intent: GuanghuLivingIntent = 'navigate',
|
||
onAccepted?: () => void | Promise<void>,
|
||
appearanceTheme?: GuanghuTheme,
|
||
) => {
|
||
trackEvent('guanghu_channel_opened', { intent, route: nextRoute })
|
||
const sequence = livingRequestSequence.current + 1
|
||
livingRequestSequence.current = sequence
|
||
const receiptIds = [worldRouterState.latestReceipt?.receipt_id, login.state.workorderId]
|
||
.filter((receiptId): receiptId is string => Boolean(receiptId))
|
||
const event = createLivingSystemEvent({
|
||
currentRoute: route,
|
||
eventId: `ui-${sequence}`,
|
||
intent,
|
||
receiptIds: [...new Set(receiptIds)],
|
||
requestedRoute: nextRoute,
|
||
appearanceTheme,
|
||
worldOpen,
|
||
})
|
||
const executeAcceptedPlan = async (
|
||
plan: GuanghuLivingSystemPlan,
|
||
source: 'server' | 'model' | 'fallback',
|
||
serverReceiptId?: string,
|
||
) => {
|
||
applyLivingPlan(plan, source, serverReceiptId)
|
||
const routeAccepted = plan.intent === intent && plan.navigationAction.route === nextRoute
|
||
try {
|
||
if (routeAccepted && onAccepted) {
|
||
await onAccepted()
|
||
}
|
||
const evidence = [
|
||
`ui.route:${plan.navigationAction.route}`,
|
||
...(routeAccepted && onAccepted && plan.capabilityCall
|
||
? [`capability.${plan.capabilityCall.capabilityId}.result`]
|
||
: []),
|
||
]
|
||
recordLivingExecution(plan, source, 'executed', evidence, undefined, serverReceiptId)
|
||
} catch (error) {
|
||
recordLivingExecution(
|
||
plan,
|
||
source,
|
||
'failed',
|
||
[`ui.route:${plan.navigationAction.route}`],
|
||
error instanceof Error ? error.message : 'host_action_failed',
|
||
serverReceiptId,
|
||
)
|
||
}
|
||
}
|
||
if (!isTauri() && !livingSystemTarget) {
|
||
void executeAcceptedPlan(createDeterministicLivingSystemPlan(event), 'fallback')
|
||
return
|
||
}
|
||
setLivingKernel('planning')
|
||
void planGuanghuLivingSystem({ event, target: livingSystemTarget }).then(result => {
|
||
if (livingRequestSequence.current !== sequence) return
|
||
void executeAcceptedPlan(result.plan, result.source, result.serverReceiptId)
|
||
})
|
||
}, [
|
||
applyLivingPlan,
|
||
livingSystemTarget,
|
||
login.state.workorderId,
|
||
recordLivingExecution,
|
||
route,
|
||
worldOpen,
|
||
worldRouterState.latestReceipt?.receipt_id,
|
||
])
|
||
|
||
const openArchitecture = () => {
|
||
trackEvent('guanghu_architecture_opened', { route: 'GLS-SYS-ARCH-001' })
|
||
setArchitectureOpen(true)
|
||
}
|
||
|
||
const openDevelopmentRepository = () => {
|
||
trackEvent('hololake_development_repository_opened', { route: 'REPO-014' })
|
||
void openExternalUrl(HOLOLAKE_DEVELOPMENT_REPOSITORY_URL)
|
||
}
|
||
|
||
const openKnowledgeBase = () => {
|
||
trackEvent('guanghu_channel_module_opened', { channel: 'heartbeat-core', module: 'knowledge-base' })
|
||
navigate(route, 'open-knowledge', onEnterKnowledgeBase)
|
||
}
|
||
|
||
const openLocalWorkspace = () => {
|
||
trackEvent('local_computer_workspace_opened', { source: 'heartbeat-core' })
|
||
navigate(route, 'open-local-workspace', onOpenLocalWorkspace)
|
||
}
|
||
|
||
const openAiWorkspace = () => {
|
||
trackEvent('guanghu_channel_module_opened', { channel: 'heartbeat-core', module: 'persona-ai-workspace' })
|
||
navigate(route, 'open-agent-workspace', onOpenAiWorkspace)
|
||
}
|
||
|
||
useEffect(() => {
|
||
const handleThemeIntent = (event: Event) => {
|
||
const theme = (event as CustomEvent<unknown>).detail
|
||
if (
|
||
typeof theme !== 'string'
|
||
|| !(GUANGHU_THEMES as readonly string[]).includes(theme)
|
||
) return
|
||
navigate(route, 'apply-theme', undefined, theme as GuanghuTheme)
|
||
}
|
||
window.addEventListener(GUANGHU_THEME_INTENT_EVENT, handleThemeIntent)
|
||
return () => window.removeEventListener(GUANGHU_THEME_INTENT_EVENT, handleThemeIntent)
|
||
}, [navigate, route])
|
||
|
||
useEffect(() => {
|
||
if (
|
||
!worldOpen
|
||
|| restoreAttempted.current
|
||
|| router.state.status !== 'offline'
|
||
) return
|
||
restoreAttempted.current = true
|
||
trackEvent('guanghu_world_session_restore_requested', {
|
||
node: 'JD-FD-PRIMARY',
|
||
source: 'email-authorized-login',
|
||
})
|
||
void router.connect()
|
||
}, [router, worldOpen])
|
||
|
||
useEffect(() => {
|
||
if (route !== 'servers' || fifthDomainConnection.status !== 'checking') return
|
||
const controller = new AbortController()
|
||
void fetchFifthDomainDiscovery(fetch, controller.signal).then(discovery => {
|
||
setFifthDomainConnection({ status: 'connected', discovery })
|
||
trackEvent('fifth_domain_connection_checked', { access: discovery.access, result: 'connected' })
|
||
}).catch(() => {
|
||
if (controller.signal.aborted) return
|
||
setFifthDomainConnection({ status: 'error' })
|
||
trackEvent('fifth_domain_connection_checked', { access: 'public-read-only', result: 'error' })
|
||
})
|
||
return () => controller.abort()
|
||
}, [fifthDomainConnection.status, route])
|
||
|
||
const renderRoute = () => {
|
||
if (route === 'world') {
|
||
return (
|
||
<GuanghuWorldMap
|
||
enterprise={enterprise.state}
|
||
livingScene={livingPlan.uiProjection.scene}
|
||
onEnterFifthDomain={() => navigate(worldOpen ? 'fifth-domain' : 'world-login')}
|
||
onOpenLibrary={openArchitecture}
|
||
onOpenReceipt={() => navigate('servers')}
|
||
onOpenZeroCore={() => navigate('zero-core')}
|
||
router={worldRouterState}
|
||
shanghai={shanghai.state}
|
||
worldOpen={worldOpen}
|
||
/>
|
||
)
|
||
}
|
||
|
||
if (route === 'world-login') {
|
||
return (
|
||
<GuanghuWorldLoginGate
|
||
locale={locale}
|
||
onBack={() => navigate('world')}
|
||
onCheck={() => void login.claim()}
|
||
onLogin={() => worldOpen ? navigate('fifth-domain') : void login.requestLogin()}
|
||
state={login.state}
|
||
/>
|
||
)
|
||
}
|
||
|
||
if (route === 'zero-core') {
|
||
return (
|
||
<section className="channel-stage channel-stage--root">
|
||
<div className="channel-stage__aurora" aria-hidden />
|
||
<div className="channel-root-layout">
|
||
<div className="channel-root-hero">
|
||
<p className="channel-stage__eyebrow">ZERO CORE · 000</p>
|
||
<h1>{t('hololake.channel.zeroCoreTitle')}</h1>
|
||
<p className="channel-stage__lead">{t('hololake.channel.zeroCoreDescription')}</p>
|
||
<div className="channel-root-actions">
|
||
<Button className="hololake-home__primary" onClick={() => navigate('fifth-domain')}>{t('hololake.channel.enterFifthDomain')} <span>→</span></Button>
|
||
<Button variant="outline" onClick={() => setReleaseNotesOpen(true)}>查看内测版 0.4.6 更新说明</Button>
|
||
</div>
|
||
</div>
|
||
<aside className="channel-root-map" aria-label="光湖频道概览">
|
||
<header><span>LIVE CHANNEL MAP</span><b>光湖系统航图</b><small>从主权入口抵达记忆、人格与工具空间</small></header>
|
||
<div className="channel-root-map__route">
|
||
<Button variant="ghost" onClick={() => navigate('fifth-domain')}><i data-color="blue" /><span><small>01</small>第五域</span><b>语言与身份入口</b></Button>
|
||
<Button variant="ghost" onClick={() => navigate('eternal-lake-heart')}><i data-color="violet" /><span><small>02</small>永恒湖心</span><b>个人系统主航道</b></Button>
|
||
<Button variant="ghost" onClick={() => navigate('heartbeat-core')}><i data-color="rose" /><span><small>03</small>心跳核心</span><b>知识、工具与创作</b></Button>
|
||
</div>
|
||
<footer><span><i />本地系统在线</span><code>HL · ERA / 0.4.6</code></footer>
|
||
</aside>
|
||
</div>
|
||
</section>
|
||
)
|
||
}
|
||
|
||
if (route === 'fifth-domain') {
|
||
return (
|
||
<FifthDomainSystems
|
||
onEnterEternalLake={() => navigate('eternal-lake-heart')}
|
||
onEnterPufferfish={() => navigate('pufferfish')}
|
||
/>
|
||
)
|
||
}
|
||
|
||
if (route === 'pufferfish') {
|
||
return (
|
||
<FifthDomainSystems
|
||
detail="pufferfish"
|
||
onEnterEternalLake={() => navigate('eternal-lake-heart')}
|
||
onEnterPufferfish={() => navigate('pufferfish')}
|
||
/>
|
||
)
|
||
}
|
||
|
||
if (route === 'eternal-lake-heart') {
|
||
return (
|
||
<EternalLakeHeartPage
|
||
onBackToFifthDomain={() => navigate('fifth-domain')}
|
||
onEnterHeartbeat={() => navigate('heartbeat-core')}
|
||
onEnterLightLake={() => navigate('light-lake')}
|
||
onEnterLoveCore={() => navigate('love-core')}
|
||
router={worldRouterState}
|
||
/>
|
||
)
|
||
}
|
||
|
||
if (route === 'light-lake') {
|
||
return (
|
||
<section className="channel-stage">
|
||
<h1>{t('hololake.channel.lightLakeTitle')}</h1>
|
||
<p className="channel-stage__lead">{t('hololake.channel.lightLakeDescription')}</p>
|
||
<ul className="persona-route-list">
|
||
<PersonaRoute id="ICE-GL-ZY001" title={t('hololake.channel.zhuyuanRoute')} />
|
||
<PersonaRoute id="ICE-GL-SY001" title={t('hololake.channel.shuangyanRoute')} />
|
||
</ul>
|
||
</section>
|
||
)
|
||
}
|
||
|
||
if (route === 'love-core') {
|
||
return (
|
||
<section className="channel-stage">
|
||
<h1>{t('hololake.channel.loveCoreTitle')}</h1>
|
||
<p className="channel-stage__lead">{t('hololake.channel.loveCoreDescription')}</p>
|
||
<RouteCard eyebrow="ZHI ZHI · CHANNEL" title={t('hololake.channel.tomorrowTitle')} description={t('hololake.channel.tomorrowDescription')} status={t('hololake.channel.realServerConnection')} />
|
||
</section>
|
||
)
|
||
}
|
||
|
||
if (route === 'education-work-lake') {
|
||
return <EducationWorkLake locale={locale} />
|
||
}
|
||
|
||
if (route === 'servers') {
|
||
return (
|
||
<section className="channel-stage channel-stage--node">
|
||
<div className="node-page__heading">
|
||
<div>
|
||
<p className="channel-stage__eyebrow">FIFTH DOMAIN · ACTIVE PATH</p>
|
||
<h1>第五域连接</h1>
|
||
<p className="channel-stage__lead">这里不是服务器列表,而是冰朔当前进入光湖世界的真实路径与回执。</p>
|
||
</div>
|
||
<div className="world-presence world-presence--large">
|
||
<i />
|
||
<span>
|
||
<strong>{activeRouterReceipt ? '第五域连接已回执' : '光湖身份已授权'}</strong>
|
||
<small>{activeRouterReceipt ? `第五域 · ${activeRouterReceipt.node_id}` : '等待真实节点回执'}</small>
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<div className="node-page__content">
|
||
<article className="node-page__primary">
|
||
<span>{activeRouterReceipt ? '当前世界节点' : '登记目标节点'}</span>
|
||
<strong>{activeRouterReceipt?.node_id ?? 'JD-FD-PRIMARY'}</strong>
|
||
<p>{activeRouterReceipt ? '第五域连接已由节点回执证明' : '尚未把身份授权误报为节点连接'}</p>
|
||
<dl>
|
||
<div><dt>身份</dt><dd>冰朔</dd></div>
|
||
<div><dt>身份会话</dt><dd>{worldOpen ? '已授权' : '未授权'}</dd></div>
|
||
<div><dt>节点连接</dt><dd>{activeRouterReceipt ? '已回执' : '未回执'}</dd></div>
|
||
<div><dt>公开发现入口</dt><dd>{fifthDomainConnection.status === 'connected' ? '可用' : fifthDomainConnection.status === 'error' ? '失败' : '检查中'}</dd></div>
|
||
</dl>
|
||
<Button variant="outline" onClick={() => {
|
||
setRoute('world')
|
||
void router.disconnect()
|
||
void login.logout()
|
||
}}>离开光湖世界</Button>
|
||
</article>
|
||
<GuanghuRouterConsole locale={locale} router={router} />
|
||
</div>
|
||
</section>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<section className="channel-stage">
|
||
<h1>{t('hololake.channel.heartbeatTitle')}</h1>
|
||
<p className="channel-stage__lead">{t('hololake.channel.heartbeatDescription')}</p>
|
||
<div className="channel-grid channel-grid--three">
|
||
<RouteCard eyebrow="PERSONA · AI AGENT" title="人格体工作舱" description="与当前频道的人格体持续协作;保留多 Agent、模型选择、权限模式、工具调用、执行回执与历史会话。" action="打开人格体工作舱" onOpen={onOpenAiWorkspace ? openAiWorkspace : undefined} />
|
||
<RouteCard eyebrow="INDUSTRY · EDUCATION" title="教育工作湖" description="通过语言指令调出并编辑文档、表格、对比图、仪表盘与知识。第一阶段权限保留在应用内。" action="进入教育工作湖" onOpen={() => navigate('education-work-lake')} />
|
||
<RouteCard eyebrow="MODULE · KNOWLEDGE" title={t('hololake.home.moduleKnowledgeTitle')} description={t('hololake.home.moduleKnowledgeDescription')} action={t('hololake.channel.openKnowledge')} onOpen={openKnowledgeBase} />
|
||
<RouteCard eyebrow="LOCAL COMPUTER · LIVE FOLDER" title="本地电脑工作区" description="选择电脑上的任意项目文件夹。编程 AI 直接在原文件夹写入页面,光湖实时显示,不再复制到旧笔记库。" action="打开本地文件夹" onOpen={onOpenLocalWorkspace ? openLocalWorkspace : undefined} />
|
||
<RouteCard eyebrow="MODULE · VIDEO AI" title={t('hololake.channel.videoAiTitle')} description={t('hololake.channel.videoAiDescription')} status={t('hololake.channel.prototypeMounted')} />
|
||
<RouteCard eyebrow="SYSTEM · ORIGIN HEARTBEAT" title={t('hololake.channel.bottleBabyTitle')} description={t('hololake.channel.bottleBabyDescription')} status={t('hololake.channel.yaomingOrigin')} />
|
||
<RouteCard eyebrow="INFRASTRUCTURE · SERVERS" title="服务器与灯塔节点" description="查看频道登记的服务器编号、归属与真实监控接口状态。" action="打开服务器登记" onOpen={() => navigate('servers')} />
|
||
</div>
|
||
<div className="channel-stage__tools">
|
||
<Button variant="outline" onClick={openArchitecture}>{t('hololake.home.openArchitecture')}</Button>
|
||
<Button variant="outline" onClick={openDevelopmentRepository}>{t('hololake.home.openRepository')}</Button>
|
||
</div>
|
||
</section>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<main
|
||
aria-label={t('hololake.home.ariaLabel')}
|
||
className="hololake-home"
|
||
data-living-connection={livingPlan.uiProjection.scene.connectionEmphasis}
|
||
data-living-kernel={livingKernel}
|
||
data-living-motion={livingPlan.uiProjection.scene.motion}
|
||
data-living-receipt={livingReceiptId}
|
||
data-living-server-receipt={livingServerReceiptId}
|
||
data-living-scene={livingPlan.uiProjection.scene.depth}
|
||
data-living-stars={livingPlan.uiProjection.scene.starDensity}
|
||
data-route={route}
|
||
data-world-open={worldOpen}
|
||
>
|
||
<aside className="channel-rail" data-locked="false" aria-label="光湖频道导航">
|
||
<div className="channel-rail__heading">
|
||
<span>{fifthDomainOpen ? 'FIFTH DOMAIN' : 'HOLOLAKE OS'}</span>
|
||
<strong>{fifthDomainOpen ? '第五域' : 'HoloLake'}</strong>
|
||
<small>{worldOpen ? fifthDomainOpen ? '身份已授权 · 节点状态看回执' : '系统能力与光湖世界入口' : '本地系统可用 · 第五域需授权'}</small>
|
||
</div>
|
||
<nav>
|
||
{!fifthDomainOpen && <>
|
||
<Button data-active={route === 'world'} variant="ghost" onClick={() => navigate('world')}><i data-color="cyan" />光湖世界</Button>
|
||
<Button variant="ghost" onClick={openKnowledgeBase}><i data-color="blue" />知识库</Button>
|
||
<Button disabled={!onOpenAiWorkspace} variant="ghost" onClick={openAiWorkspace}><i data-color="violet" />Agent 工作舱</Button>
|
||
<Button data-active={route === 'education-work-lake'} variant="ghost" onClick={() => navigate('education-work-lake')}><i data-color="green" />教育工作湖</Button>
|
||
<Button disabled={!onOpenLocalWorkspace} variant="ghost" onClick={openLocalWorkspace}><i data-color="amber" />本地工具与项目</Button>
|
||
</>}
|
||
{fifthDomainOpen && <>
|
||
<Button variant="ghost" onClick={() => navigate('world')}><i data-color="cyan" />返回光湖世界</Button>
|
||
<Button data-active={route === 'fifth-domain'} variant="ghost" onClick={() => navigate('fifth-domain')}><i data-color="blue" />第五域总览</Button>
|
||
<Button data-active={route === 'pufferfish'} variant="ghost" onClick={() => navigate('pufferfish')}><i data-color="green" />胖头鱼语言子系统</Button>
|
||
<Button data-active={route === 'zero-core'} variant="ghost" onClick={() => navigate('zero-core')}><i data-color="cyan" />{t('hololake.channel.zeroCoreTitle')}</Button>
|
||
<Button data-active={['eternal-lake-heart', 'light-lake', 'heartbeat-core', 'love-core', 'servers'].includes(route)} variant="ghost" onClick={() => navigate('eternal-lake-heart')}><i data-color="violet" />{t('hololake.channel.eternalLakeTitle')}</Button>
|
||
<Button data-active={route === 'light-lake'} variant="ghost" onClick={() => navigate('light-lake')}><i data-color="amber" />{t('hololake.channel.lightLakeTitle')}</Button>
|
||
<Button data-active={route === 'heartbeat-core'} variant="ghost" onClick={() => navigate('heartbeat-core')}><i data-color="rose" />{t('hololake.channel.heartbeatTitle')}</Button>
|
||
<Button data-active={route === 'love-core'} variant="ghost" onClick={() => navigate('love-core')}><i data-color="pink" />{t('hololake.channel.loveCoreTitle')}</Button>
|
||
<Button data-active={route === 'servers'} variant="ghost" onClick={() => navigate('servers')}><i data-color="green" />第五域连接</Button>
|
||
</>}
|
||
</nav>
|
||
{fifthDomainOpen
|
||
? <div className="channel-rail__modules"><span>已开启功能</span><b>知识湖</b><b>短剧视频 AI</b><b>人格体恢复路径</b></div>
|
||
: <div className="channel-rail__modules"><span>第五域内部系统仅在星图授权进入后显示</span></div>}
|
||
</aside>
|
||
|
||
<div className="channel-workspace">
|
||
<div
|
||
aria-live="polite"
|
||
className="living-system-status"
|
||
data-state={livingKernel}
|
||
role="status"
|
||
>
|
||
<i aria-hidden />
|
||
<span>
|
||
{livingKernel === 'planning'
|
||
? '活系统正在适配'
|
||
: livingKernel === 'server'
|
||
? '服务器模型已回执'
|
||
: livingKernel === 'model'
|
||
? '本地模型已适配'
|
||
: '安全执行路径'}
|
||
</span>
|
||
<small>{livingKernel === 'server' ? '真实节点' : livingKernel === 'planning' ? '请稍候' : '可继续操作'}</small>
|
||
</div>
|
||
{worldOpen && route !== 'world-login' ? <>
|
||
<header className="world-session-bar">
|
||
<div className="world-presence">
|
||
<i />
|
||
<span>
|
||
<strong>{activeRouterReceipt ? '第五域连接已回执' : '光湖身份已授权'}</strong>
|
||
<small>
|
||
{activeRouterReceipt
|
||
? `第五域 · ${activeRouterReceipt.node_id}`
|
||
: fifthDomainOpen
|
||
? '域内界面已打开 · 节点未回执'
|
||
: '世界入口 · 等待域跳转'}
|
||
</small>
|
||
</span>
|
||
</div>
|
||
<div className="world-session-bar__actions">
|
||
{fifthDomainOpen && onOpenAiWorkspace
|
||
? <Button className="world-session-bar__ai" variant="ghost" size="sm" onClick={openAiWorkspace}>人格体工作舱</Button>
|
||
: null}
|
||
<Button variant="ghost" size="sm" onClick={() => navigate(fifthDomainOpen ? 'servers' : 'world')}>{fifthDomainOpen ? '查看连接凭证' : '世界总图'}</Button>
|
||
</div>
|
||
</header>
|
||
{route !== 'world' && <nav className="channel-breadcrumb" aria-label={t('hololake.channel.breadcrumbLabel')}>
|
||
<Button variant="ghost" size="sm" onClick={() => navigate('world')}>光湖语言世界</Button><span>›</span>
|
||
<Button variant="ghost" size="sm" onClick={() => navigate('zero-core')}>{t('hololake.channel.zeroCoreTitle')}</Button>
|
||
{route !== 'zero-core' && <><span>›</span><Button variant="ghost" size="sm" onClick={() => navigate('fifth-domain')}>{t('hololake.channel.fifthDomainTitle')}</Button></>}
|
||
{!['zero-core', 'fifth-domain', 'pufferfish'].includes(route) && <><span>›</span><Button variant="ghost" size="sm" onClick={() => navigate('eternal-lake-heart')}>{t('hololake.channel.eternalLakeTitle')}</Button></>}
|
||
{route === 'pufferfish' && <><span>›</span><Button variant="ghost" size="sm" onClick={() => navigate('pufferfish')}>胖头鱼语言子系统</Button></>}
|
||
</nav>}
|
||
{renderRoute()}
|
||
</> : renderRoute()}
|
||
</div>
|
||
|
||
{worldOpen && <nav className="channel-mobile-nav" aria-label="手机频道导航">
|
||
<Button data-active={route === 'world'} variant="ghost" onClick={() => navigate('world')}><span>◎</span><small>世界</small></Button>
|
||
<Button data-active={route === 'fifth-domain'} variant="ghost" onClick={() => navigate('fifth-domain')}><span>◇</span><small>第五域</small></Button>
|
||
{fifthDomainOpen && <>
|
||
<Button data-active={route === 'heartbeat-core'} variant="ghost" onClick={() => navigate('heartbeat-core')}><span>♥</span><small>心跳</small></Button>
|
||
<Button data-active={route === 'light-lake'} variant="ghost" onClick={() => navigate('light-lake')}><span>✦</span><small>人格体</small></Button>
|
||
<Button data-active={route === 'servers'} variant="ghost" onClick={() => navigate('servers')}><span>▦</span><small>节点</small></Button>
|
||
</>}
|
||
</nav>}
|
||
|
||
<Dialog open={architectureOpen} onOpenChange={setArchitectureOpen}>
|
||
<DialogContent className="guanghu-architecture" aria-label={t('hololake.architecture.title')}>
|
||
<DialogHeader>
|
||
<DialogTitle>{t('hololake.architecture.title')}</DialogTitle>
|
||
<DialogDescription>{t('hololake.architecture.description')}</DialogDescription>
|
||
</DialogHeader>
|
||
<ol className="guanghu-architecture__routes">
|
||
{architectureRoutes.map(([id, title, description]) => (
|
||
<li key={id}>
|
||
<code>{id}</code>
|
||
<div><strong>{t(title)}</strong><p>{t(description)}</p></div>
|
||
</li>
|
||
))}
|
||
</ol>
|
||
<p className="guanghu-architecture__route">GLW-ENTRY-001 → FD-LANGUAGE-001 → TCS-ROOT-001 → GLS-SYS-ARCH-001 → ELH-LAMP-001</p>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
<Dialog open={releaseNotesOpen} onOpenChange={setReleaseNotesOpen}>
|
||
<DialogContent aria-label="HoloLake Era 内测版 0.4.6 更新说明">
|
||
<DialogHeader>
|
||
<DialogTitle>HoloLake Era 内测版 0.4.6 · 本次更新</DialogTitle>
|
||
<DialogDescription>更可靠的内置 AI、可验证的工具执行,以及由人格体按需深入的 HLDP 结构化上下文。</DialogDescription>
|
||
</DialogHeader>
|
||
<div className="space-y-4 text-sm leading-6">
|
||
<section>
|
||
<h3 className="font-semibold">真实光湖世界与第五域会话</h3>
|
||
<p className="text-muted-foreground">启动后会恢复第五域签名路由,并把企业节点公开状态投影到世界拓扑。只有收到真实回执的节点才会点亮;在线只读、执行关闭和连接失败都会原样显示,不用静态占位冒充服务状态。</p>
|
||
</section>
|
||
<section>
|
||
<h3 className="font-semibold">轻量星系主题系统</h3>
|
||
<p className="text-muted-foreground">世界首页、节点地图与第五域频道使用同一套星系视觉语言。主题以静态压缩资源和少量透明度、位移动效实现,并尊重系统的“减少动态效果”设置,避免持续旋转与高成本滤镜。</p>
|
||
</section>
|
||
<section>
|
||
<h3 className="font-semibold">内置知识库 Agent</h3>
|
||
<p className="text-muted-foreground">AI 可以连续搜索、读取、新建、写入、编辑和删除页面,并根据每一步真实回执继续处理,不再把“准备操作”误报成“已经完成”。</p>
|
||
</section>
|
||
<section>
|
||
<h3 className="font-semibold">主动联网调研</h3>
|
||
<p className="text-muted-foreground">新增公开网页搜索与页面读取。AI 可主动查找资料、打开相关结果并附上来源;仅允许公共 HTTPS 页面,不包含登录、内网访问或替用户执行外部操作。</p>
|
||
</section>
|
||
<section>
|
||
<h3 className="font-semibold">聊天历史与中文过程提示</h3>
|
||
<p className="text-muted-foreground">恢复历史会话显示;工具区会用中文说明正在搜索什么、读取哪个页面、写入是否成功,同时仍可展开查看原始输入和结果。</p>
|
||
</section>
|
||
<section>
|
||
<h3 className="font-semibold">竖向聊天历史</h3>
|
||
<p className="text-muted-foreground">侧边 AI 工作区改为可独立滚动的竖向会话列表,长标题自动收起;支持归档、恢复和带二次确认的永久删除。</p>
|
||
</section>
|
||
<section>
|
||
<h3 className="font-semibold">更自然的当前实例</h3>
|
||
<p className="text-muted-foreground">身份与权限边界继续由事实约束,但不再要求背诵固定自我介绍。当前实例可以结合对话,自主组织语气、详略和表达风格。</p>
|
||
</section>
|
||
<section>
|
||
<h3 className="font-semibold">多模型 API 兼容</h3>
|
||
<p className="text-muted-foreground">按接口能力协商工具参数与多轮执行,覆盖 OpenAI 兼容接口、DeepSeek、Qwen、Gemini、OpenRouter、Anthropic、Ollama 与 LM Studio 等常见路线。模型或服务端不支持某项能力时会明确提示或安全降级。</p>
|
||
</section>
|
||
<section>
|
||
<h3 className="font-semibold">HLDP 协作记忆与本地心跳</h3>
|
||
<p className="text-muted-foreground">HLDP 已作为内置技能自动加载。协作过程以追加式纯文本心跳留在本机,可凭回执恢复和继续记录;会话结束后再由人格体判断哪些内容需要整理与长期保留。</p>
|
||
</section>
|
||
<section>
|
||
<h3 className="font-semibold">网页节点树与按需下钻</h3>
|
||
<p className="text-muted-foreground">长网页不再整页塞进对话或粗暴截断。浏览器 Agent 先整理为 HLDP 节点树,只返回结构摘要;人格体需要细节时,可按文档和节点编号精确展开对应分支。</p>
|
||
</section>
|
||
<section>
|
||
<h3 className="font-semibold">浏览器观察链路</h3>
|
||
<p className="text-muted-foreground">联网搜索、网页读取、节点存储、指定节点展开与当前时间能力已通过人格体实测。浏览器观察看板将沿用同一条回执链展示整理进度;工具链已连通,界面渲染仍作为独立项目持续验收。</p>
|
||
</section>
|
||
<p className="rounded-md bg-muted p-3 text-muted-foreground"><strong className="text-foreground">能力边界:</strong>内置模式是面向知识库和公开网页的工具 Agent,不等同于拥有终端、代码工程和系统权限的完整编程 Agent。</p>
|
||
</div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
</main>
|
||
)
|
||
}
|