feat: add human-first dual HoloLake workbench
This commit is contained in:
parent
e72ee85b89
commit
cdfb2c81e3
12 changed files with 469 additions and 23 deletions
|
|
@ -0,0 +1,76 @@
|
|||
{
|
||||
"schema": "hololake.installed-product-acceptance/v1",
|
||||
"acceptanceId": "GH-HOLOLAKE-0.9.0-DUAL-WORKBENCH-INSTALLED-20260820",
|
||||
"observedAt": "2026-08-20T06:04:00+08:00",
|
||||
"product": {
|
||||
"name": "HoloLake",
|
||||
"version": "0.9.0",
|
||||
"bundleIdentifier": "world.guanghu.hololake",
|
||||
"installedApplication": "/Users/bingshuolingdianyuanhe/Desktop/HoloLake.app",
|
||||
"executableSha256": "ae598e0f833af139f73d4f4b627518118be6b67c37c271c23272f871204d43c5",
|
||||
"infoPlistSha256": "868c082ff2de0dbf8c64642b0e5e02b4f6ab31887af31afce8ec944e5645c815"
|
||||
},
|
||||
"humanAcceptanceBasis": [
|
||||
"验收从系统外的人类可见、可点、可输入和可回读界面开始,内部测试不能替代产品界面验收。",
|
||||
"HoloLake 必须区分人类翻译层与人格体执行层,默认人类层使用正式产品语言,执行层使用结构化机器字段。",
|
||||
"光湖主域承担公告与版本更新,必须补齐 0.4.0 至 0.7.0 并保留后续当前版本记录。",
|
||||
"光湖主题保留,同时提供用户熟悉的传统工作台并可快速切换。",
|
||||
"对话、知识、代码和系统栏目必须连接同一套真实功能,不能停留在预览或说明页。"
|
||||
],
|
||||
"implementedBoundary": {
|
||||
"engineCount": 1,
|
||||
"presentationModes": ["guanghu", "workbench"],
|
||||
"audienceLayers": ["human", "execution"],
|
||||
"defaultAudienceLayer": "human",
|
||||
"workbenchNavigation": ["工作区", "语言人格频道", "知识库", "代码仓库", "运行回执", "公告与版本", "系统设置"],
|
||||
"machineProjectionSchema": "hololake.execution-projection/v1",
|
||||
"releaseContract": "contracts/product-release-center.json"
|
||||
},
|
||||
"installedUiReadback": {
|
||||
"authenticatedAccount": "bingshuo",
|
||||
"authenticatedDomain": "FIFTH_DOMAIN",
|
||||
"standardWorkbenchVisible": true,
|
||||
"guanghuWorkbenchVisible": true,
|
||||
"workbenchSwitchPersistsAcrossRestart": true,
|
||||
"audienceLayerSwitchPersistsAcrossRestart": true,
|
||||
"knowledgeDocumentCount": 129,
|
||||
"codeRepositoryCountObservedBeforeFinalRestart": 2,
|
||||
"modelProvider": "阿里云百炼 · Token Plan",
|
||||
"modelProviderState": "AVAILABLE",
|
||||
"conversationComposerVisible": true,
|
||||
"conversationRoundTrip": {
|
||||
"state": "PASS_RECEIVED_AND_PERSISTED",
|
||||
"stateVersion": 9,
|
||||
"receiptHashPrefix": "4e0d9ec7b",
|
||||
"contentCorrectnessAsserted": false
|
||||
},
|
||||
"knowledgePersonaCollaborationVisible": true,
|
||||
"executionRuntime": {
|
||||
"directLocalBrokerState": "READY",
|
||||
"resumableSessionCount": 2,
|
||||
"numberingKernelState": "ACTIVE_PINNED_AUTHORITY_MAP",
|
||||
"glsRuntimeState": "ACTIVE_EXPLICIT_PROJECTIONS_ONLY",
|
||||
"glsProtocolKernelState": "P1_TO_P6_NATIVE_P7_FAIL_CLOSED"
|
||||
},
|
||||
"guanghuBroadcastVersion": "0.9.0"
|
||||
},
|
||||
"releaseHistoryReadback": ["0.4.0", "0.4.1", "0.5.0", "0.6.0", "0.7.0", "0.8.1", "0.9.0"],
|
||||
"verification": {
|
||||
"nodeTests": { "passed": 126, "failed": 0 },
|
||||
"rustTests": { "passed": 141, "failed": 0, "ignoredLiveExternal": 2 },
|
||||
"typescriptBuild": "PASS",
|
||||
"viteBuild": "PASS_WITH_EXISTING_LARGE_CHUNK_WARNING",
|
||||
"codesign": "PASS_DEVELOPER_ID_APPLICATION_825A9L3G7Q",
|
||||
"notarization": "NOT_PERFORMED_MISSING_APPLE_NOTARIZATION_CREDENTIALS",
|
||||
"desktopCanonicalAppCount": 1,
|
||||
"browserPublicWorkbench": "PASS",
|
||||
"browserMainDomainReleaseCenter": "PASS",
|
||||
"browserStructuredReleaseProjection": "PASS",
|
||||
"installedAuthenticatedWorkbench": "PASS",
|
||||
"installedPersonaChannel": "PASS",
|
||||
"installedKnowledgeCollaboration": "PASS",
|
||||
"installedExecutionProjection": "PASS",
|
||||
"restartReadback": "PASS"
|
||||
},
|
||||
"truthBoundary": "This acceptance proves the signed local macOS HoloLake 0.9.0 application is installed and that its dual workbench, human interface, structured execution projection, release center, authenticated navigation, real local knowledge, configured model route, conversation round trip and restart readback were observed on this machine. It does not claim Apple notarization, a public network release, Windows or Linux installed UI acceptance, or new remote publication authority."
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
{
|
||||
"schema": "hololake.product-release-center/v1",
|
||||
"product": "HoloLake",
|
||||
"channel": "stable-local",
|
||||
"currentVersion": "0.9.0",
|
||||
"releases": [
|
||||
{
|
||||
"releaseId": "GH-HOLOLAKE-RELEASE-0.9.0",
|
||||
"version": "0.9.0",
|
||||
"date": "2026-08-20",
|
||||
"title": "双工作台与人机分层投影",
|
||||
"summary": "新增光湖工作台与标准工作台切换,并将人类交互界面和人格体结构化执行界面明确分层。",
|
||||
"highlights": ["标准工作台提供稳定导航、对话、知识、代码、回执与系统入口", "光湖主域新增公告与完整版本记录", "人类层显示正式产品语言,执行层显示可核验结构化状态"],
|
||||
"state": "CURRENT",
|
||||
"evidence": ["contracts/product-release-center.json", "audit/hololake-0.9.0-dual-workbench-installed-acceptance-20260820.json"]
|
||||
},
|
||||
{
|
||||
"releaseId": "GH-HOLOLAKE-RELEASE-0.8.1",
|
||||
"version": "0.8.1",
|
||||
"date": "2026-08-20",
|
||||
"title": "语言人格频道并入正版",
|
||||
"summary": "将真实模型对话、知识页协作和回执链合并至 HoloLake 唯一正版应用。",
|
||||
"highlights": ["可配置 OpenAI 兼容模型接口", "对话可读取当前知识页并返回引用回执", "密钥保存至系统钥匙串"],
|
||||
"state": "SUPERSEDED",
|
||||
"evidence": ["audit/persona-language-channel-runtime-acceptance-20260820.json"]
|
||||
},
|
||||
{
|
||||
"releaseId": "GH-HOLOLAKE-RELEASE-0.7.0",
|
||||
"version": "0.7.0",
|
||||
"date": "2026-08-20",
|
||||
"title": "执行手脚训练运行核",
|
||||
"summary": "加入账号隔离的执行体训练账本、验证记忆与重启回读能力。",
|
||||
"highlights": ["登记 ZY-LIMB-002 执行体", "真实模型完成脑与手脚边界训练", "训练回执和验证记忆重启后可回读"],
|
||||
"state": "SUPERSEDED",
|
||||
"evidence": ["contracts/execution-limb-training-v1.json", "persona-runtime/agents/codex/zhuyuan/TRAINING-LOG.md"]
|
||||
},
|
||||
{
|
||||
"releaseId": "GH-HOLOLAKE-RELEASE-0.6.0",
|
||||
"version": "0.6.0",
|
||||
"date": "2026-08-20",
|
||||
"title": "语言人格频道可用纵切",
|
||||
"summary": "完成模型发现、真实对话、知识检索与本机事件回执的第一条可用纵切。",
|
||||
"highlights": ["自动发现本机可用模型入口", "模型配置与钥匙串密钥分离", "SQLite 事件、版本与哈希链留存"],
|
||||
"state": "SUPERSEDED",
|
||||
"evidence": ["contracts/persona-channel-body.json", "HLDP:canonical-language-channel-runtime-acceptance-20260820"]
|
||||
},
|
||||
{
|
||||
"releaseId": "GH-HOLOLAKE-RELEASE-0.5.0",
|
||||
"version": "0.5.0",
|
||||
"date": "2026-08-19",
|
||||
"title": "零点原核编号与原生协议底座",
|
||||
"summary": "重装零点原核编号、人格宿主许可证、GLS 原生执行层与签名模块生命周期。",
|
||||
"highlights": ["唯一编号网关与失败关闭路由", "协议规则编译为原生内核", "签名模块安装、自检、卸载与回滚"],
|
||||
"state": "SUPERSEDED",
|
||||
"evidence": ["audit/hololake-0.5.0-qoder-numbered-production-installed-20260819.json"]
|
||||
},
|
||||
{
|
||||
"releaseId": "GH-HOLOLAKE-RELEASE-0.4.1",
|
||||
"version": "0.4.1",
|
||||
"date": "2026-08-17",
|
||||
"title": "签名更新与编程 AI 直连底座",
|
||||
"summary": "加入签名更新引导和 HoloLake 持有的本机编程 AI 控制平面。",
|
||||
"highlights": ["签名更新检查与恢复边界", "本机终端直连协议", "连接、续接与写入前环境事实帧"],
|
||||
"state": "SUPERSEDED",
|
||||
"evidence": ["audit/hololake-0.4.1-updater-bootstrap-installed-acceptance-20260817.json", "audit/hololake-0.4.1-programming-ai-terminal-link-acceptance-20260817.json"]
|
||||
},
|
||||
{
|
||||
"releaseId": "GH-HOLOLAKE-RELEASE-0.4.0",
|
||||
"version": "0.4.0",
|
||||
"date": "2026-08-16",
|
||||
"title": "域路由与个人代码频道",
|
||||
"summary": "建立五域入口、域凭证路由和用户个人原生代码频道。",
|
||||
"highlights": ["编号验证后进入所属域", "域级语言边界", "用户 PNCC 本机 Git 投影"],
|
||||
"state": "SUPERSEDED",
|
||||
"evidence": ["audit/hololake-0.4.0-domain-membrane-pncc-installed-acceptance-20260816.json"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "hololake-native-desktop",
|
||||
"version": "0.8.1",
|
||||
"version": "0.9.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "hololake-native-desktop",
|
||||
"version": "0.8.1",
|
||||
"version": "0.9.0",
|
||||
"dependencies": {
|
||||
"@fortune-sheet/react": "1.0.4",
|
||||
"@lexical/history": "0.49.0",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "hololake-native-desktop",
|
||||
"private": true,
|
||||
"version": "0.8.1",
|
||||
"version": "0.9.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ test('knowledge workspace keeps the chat visible and binds the exact active page
|
|||
|
||||
test('model configuration remains secondary to the conversation surface', () => {
|
||||
assert.match(channel, /const \[settingsOpen, setSettingsOpen\] = useState\(false\)/)
|
||||
assert.match(channel, /说话是主入口。知识、模型和执行证据在需要时进入/)
|
||||
assert.match(channel, /直接输入消息开始对话;知识引用、模型设置和运行回执按需展开/)
|
||||
assert.match(channel, /\{settingsOpen && <aside className="persona-chat-settings">/)
|
||||
assert.match(channel, /API 密钥 · 只进钥匙串/)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
import assert from 'node:assert/strict'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import test from 'node:test'
|
||||
|
||||
const root = new URL('../', import.meta.url)
|
||||
const read = (path) => readFile(new URL(path, root), 'utf8')
|
||||
|
||||
test('release center records every shipped HoloLake line from 0.4.0 through current', async () => {
|
||||
const contract = JSON.parse(await read('contracts/product-release-center.json'))
|
||||
assert.equal(contract.schema, 'hololake.product-release-center/v1')
|
||||
assert.equal(contract.currentVersion, '0.9.0')
|
||||
const versions = contract.releases.map((release) => release.version)
|
||||
for (const version of ['0.4.0', '0.4.1', '0.5.0', '0.6.0', '0.7.0', '0.8.1', '0.9.0']) assert.ok(versions.includes(version), version)
|
||||
assert.equal(contract.releases.filter((release) => release.state === 'CURRENT').length, 1)
|
||||
for (const release of contract.releases) {
|
||||
assert.ok(release.title.length > 0)
|
||||
assert.ok(release.summary.length > 0)
|
||||
assert.ok(release.highlights.length >= 3)
|
||||
assert.ok(release.evidence.length >= 1)
|
||||
}
|
||||
})
|
||||
|
||||
test('one engine exposes Guanghu and standard workbench projections with a human default', async () => {
|
||||
const source = await read('src/main.tsx')
|
||||
assert.match(source, /type PresentationMode = 'guanghu' \| 'workbench'/)
|
||||
assert.match(source, /type AudienceLayer = 'human' \| 'execution'/)
|
||||
assert.match(source, /hololake-presentation-mode/)
|
||||
assert.match(source, /hololake-audience-layer/)
|
||||
assert.match(source, /\|\| 'human'/)
|
||||
assert.match(source, /className="standard-workbench"/)
|
||||
assert.match(source, /HoloLake · V\{productReleaseCenter\.currentVersion\}/)
|
||||
assert.doesNotMatch(source, /HoloLake · V0\.8\.1/)
|
||||
assert.match(source, /renderHumanView\(\)/)
|
||||
assert.match(source, /renderExecutionProjection\(\)/)
|
||||
assert.match(source, /人格体结构化执行界面/)
|
||||
})
|
||||
|
||||
test('main domain routes to the public release center instead of a descriptive popup', async () => {
|
||||
const source = await read('src/main.tsx')
|
||||
assert.match(source, /gate\.domain === 'MAIN_DOMAIN'/)
|
||||
assert.match(source, /setWorldStage\('mainDomain'\)/)
|
||||
assert.match(source, /光湖主域公告与版本中心/)
|
||||
assert.match(source, /renderReleaseCenter\(audienceLayer\)/)
|
||||
})
|
||||
|
||||
test('public language persona copy is formal and keeps a visible human composer', async () => {
|
||||
const source = await read('src/modules/persona-channel-body/index.tsx')
|
||||
assert.match(source, /<h1>\{embedded \? '对话与知识协作' : '语言人格频道'\}<\/h1>/)
|
||||
assert.match(source, /输入消息;需要知识时可直接提问。/)
|
||||
assert.match(source, /<textarea/)
|
||||
assert.match(source, /send_message/)
|
||||
assert.doesNotMatch(source, /说点什么/)
|
||||
assert.doesNotMatch(source, /先让频道知道你要和谁说话/)
|
||||
})
|
||||
|
||||
test('all native product manifests advertise the same version', async () => {
|
||||
const packageJson = JSON.parse(await read('package.json'))
|
||||
const tauri = JSON.parse(await read('src-tauri/tauri.conf.json'))
|
||||
const cargo = await read('src-tauri/Cargo.toml')
|
||||
assert.equal(packageJson.version, '0.9.0')
|
||||
assert.equal(tauri.version, packageJson.version)
|
||||
assert.match(cargo, /version = "0\.9\.0"/)
|
||||
})
|
||||
|
|
@ -1557,7 +1557,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
|||
|
||||
[[package]]
|
||||
name = "hololake-native-desktop"
|
||||
version = "0.8.1"
|
||||
version = "0.9.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"calamine",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "hololake-native-desktop"
|
||||
version = "0.8.1"
|
||||
version = "0.9.0"
|
||||
description = "HoloLake native desktop foundation"
|
||||
authors = ["HoloLake"]
|
||||
license = "AGPL-3.0-or-later"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "HoloLake",
|
||||
"version": "0.8.1",
|
||||
"version": "0.9.0",
|
||||
"identifier": "world.guanghu.hololake",
|
||||
"build": {
|
||||
"frontendDist": "../dist",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { analyzeEducationTableData } from './modules/education-data'
|
|||
import { buildEducationBaselineRenderPlan } from './modules/education-adaptive-rendering'
|
||||
import { NativeCompositionStudio, type CompositionDimension, type CompositionMeasure, type NativeCompositionProjection, type ProjectionView } from './modules/native-composition'
|
||||
import subdomainIndustryRegistry from '../contracts/subdomain-industry-routing.json'
|
||||
import productReleaseCenter from '../contracts/product-release-center.json'
|
||||
|
||||
const TAG_TINTS = ['tag-lavender', 'tag-sky', 'tag-mint', 'tag-amber', 'tag-rose', 'tag-slate']
|
||||
const EducationDocumentEngine = lazy(() => import('./modules/education-document-engine'))
|
||||
|
|
@ -56,8 +57,10 @@ import './design-tokens.css'
|
|||
import './styles.css'
|
||||
|
||||
type ThemeId = 'night' | 'dawn' | 'nebula' | 'candle' | 'clear'
|
||||
type ViewId = 'overview' | 'persona' | 'knowledge' | 'code' | 'receipts' | 'system'
|
||||
type WorldStage = 'domain' | 'heart' | 'heartbeat' | 'lightLake' | 'love' | 'tomorrow' | 'bottle' | 'channel' | 'enterpriseWork' | 'subdomainIndustries' | 'educationChannel' | 'educationDocuments' | 'educationTables' | 'educationData' | 'educationAutomation' | 'educationComposition' | 'personalNodeGuide' | 'tool'
|
||||
type ViewId = 'overview' | 'persona' | 'knowledge' | 'code' | 'receipts' | 'system' | 'releases'
|
||||
type WorldStage = 'domain' | 'mainDomain' | 'heart' | 'heartbeat' | 'lightLake' | 'love' | 'tomorrow' | 'bottle' | 'channel' | 'enterpriseWork' | 'subdomainIndustries' | 'educationChannel' | 'educationDocuments' | 'educationTables' | 'educationData' | 'educationAutomation' | 'educationComposition' | 'personalNodeGuide' | 'tool'
|
||||
type PresentationMode = 'guanghu' | 'workbench'
|
||||
type AudienceLayer = 'human' | 'execution'
|
||||
type KnowledgeSource = 'native' | 'legacy'
|
||||
|
||||
interface HomeStatus {
|
||||
|
|
@ -489,7 +492,7 @@ const previewCode: CodeChannelSnapshot = { state: 'UNAVAILABLE', channels: [], a
|
|||
const themes: Array<{ id: ThemeId; name: string }> = [
|
||||
{ id: 'night', name: '夜湖星光' }, { id: 'dawn', name: '晨湖曦光' }, { id: 'nebula', name: '星云紫夜' }, { id: 'candle', name: '烛畔暖湖' }, { id: 'clear', name: '清浅澄湖' },
|
||||
]
|
||||
const viewLabels: Record<ViewId, string> = { overview: '个人频道', persona: '对话与共同工作', knowledge: '知识空间', code: '人格代码频道', receipts: '运行回执', system: '系统详情' }
|
||||
const viewLabels: Record<ViewId, string> = { overview: '工作区', persona: '语言人格频道', knowledge: '知识库', code: '代码仓库', receipts: '运行回执', system: '系统设置', releases: '公告与版本' }
|
||||
const domainGates = [
|
||||
{ domain: 'BRANCH_DOMAIN', className: 'd-sub', title: '光湖分域', gate: 'GATE 02 · ONLINE', facts: [
|
||||
['域标识', 'BRANCH_DOMAIN'], ['系统职责', '行业入口与行业路由'], ['行业状态', '网文 · 待开发 / 宠物 · 待开发 / 教育 · 开发中'], ['责任主体', '花尔 · TCS-GL-0005∞'], ['人格体主体', '爆米花 · PER-BMH001 · AGE'], ['关系支持', '糖星云 · PER-TXY001 · AGE'], ['工作仓库', 'PRIVATE · 1 · LIVE'],
|
||||
|
|
@ -621,10 +624,12 @@ function humanError(error: unknown, context: 'knowledge' | 'education' | 'code'
|
|||
function Icon({ name }: { name: ViewId | 'search' | 'import' | 'folder' | 'copy' | 'arrow' | 'chevron' | 'file' | 'edit' | 'save' | 'back' | 'download' | 'trash' | 'chevronDown' | 'settings' | 'more' | 'plus' }) {
|
||||
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"/></>,
|
||||
persona: <><path d="M5 5.5h14v10H9l-4 3Z"/><path d="M8.5 9h7M8.5 12h4.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"/></>,
|
||||
releases: <><path d="M6 4h12v16H6Z"/><path d="M9 8h6M9 12h6M9 16h4"/></>,
|
||||
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"/></>,
|
||||
download: <><path d="M12 4v12M7 11l5 5 5-5"/><path d="M4 17v3h16v-3"/></>,
|
||||
|
|
@ -699,9 +704,19 @@ function HoloLakeApp() {
|
|||
}, 2400)
|
||||
}, [])
|
||||
const [theme, setTheme] = useState<ThemeId>(() => (window.localStorage.getItem('hololake-theme') as ThemeId) || 'night')
|
||||
const [presentationMode, setPresentationMode] = useState<PresentationMode>(() => (window.localStorage.getItem('hololake-presentation-mode') as PresentationMode) || 'guanghu')
|
||||
const [audienceLayer, setAudienceLayer] = useState<AudienceLayer>(() => (window.localStorage.getItem('hololake-audience-layer') as AudienceLayer) || 'human')
|
||||
const [view, setView] = useState<ViewId>('overview')
|
||||
const [worldStage, setWorldStage] = useState<WorldStage>('domain')
|
||||
const [toolReturnStage, setToolReturnStage] = useState<WorldStage>('channel')
|
||||
const choosePresentationMode = (next: PresentationMode) => {
|
||||
window.localStorage.setItem('hololake-presentation-mode', next)
|
||||
setPresentationMode(next)
|
||||
}
|
||||
const chooseAudienceLayer = (next: AudienceLayer) => {
|
||||
window.localStorage.setItem('hololake-audience-layer', next)
|
||||
setAudienceLayer(next)
|
||||
}
|
||||
const [status, setStatus] = useState<HomeStatus>(previewStatus)
|
||||
const [personal, setPersonal] = useState<PersonalChannelSnapshot>(previewPersonal)
|
||||
const [eraTimeline, setEraTimeline] = useState<GuanghuEraTimeline | null>(null)
|
||||
|
|
@ -2491,6 +2506,58 @@ function HoloLakeApp() {
|
|||
</section>
|
||||
)
|
||||
|
||||
const renderReleaseCenter = (layer: AudienceLayer = audienceLayer) => (
|
||||
<section className="release-center content-page" aria-label="HoloLake 公告与版本">
|
||||
<header className="page-title release-center-title">
|
||||
<div><span className="kicker">OFFICIAL RELEASE CENTER</span><h1>公告与版本</h1><p>{layer === 'human' ? 'HoloLake 正式版本记录、功能变更与当前发布状态。' : '结构化发布登记;字段来自产品发布合同。'}</p></div>
|
||||
<span className="release-current-badge">当前版本 {productReleaseCenter.currentVersion}</span>
|
||||
</header>
|
||||
<div className="release-timeline">
|
||||
{productReleaseCenter.releases.map((release) => <article className={release.state === 'CURRENT' ? 'release-entry current' : 'release-entry'} key={release.releaseId}>
|
||||
<header><div><span className="release-version">HoloLake {release.version}</span><time dateTime={release.date}>{release.date}</time></div><span className={release.state === 'CURRENT' ? 'status-chip online' : 'status-chip'}>{release.state === 'CURRENT' ? '当前版本' : '历史版本'}</span></header>
|
||||
{layer === 'human' ? <>
|
||||
<h2>{release.title}</h2><p>{release.summary}</p>
|
||||
<ul>{release.highlights.map((item) => <li key={item}>{item}</li>)}</ul>
|
||||
</> : <dl className="execution-fields release-fields">
|
||||
<div><dt>release_id</dt><dd>{release.releaseId}</dd></div>
|
||||
<div><dt>version</dt><dd>{release.version}</dd></div>
|
||||
<div><dt>released_at</dt><dd>{release.date}</dd></div>
|
||||
<div><dt>state</dt><dd>{release.state}</dd></div>
|
||||
<div><dt>evidence_count</dt><dd>{release.evidence.length}</dd></div>
|
||||
{release.evidence.map((item, index) => <div key={item}><dt>evidence[{index}]</dt><dd>{item}</dd></div>)}
|
||||
</dl>}
|
||||
</article>)}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
|
||||
const renderExecutionProjection = () => {
|
||||
const sections = [
|
||||
{ id: 'projection', title: 'PROJECTION_CONTEXT', source: 'ui/runtime', fields: [
|
||||
['selected_module', view], ['presentation_mode', presentationMode], ['audience_layer', audienceLayer], ['authenticated_domain', repoLogin?.domain || 'UNAUTHENTICATED'], ['account', repoLogin?.username || 'UNAUTHENTICATED'],
|
||||
] },
|
||||
{ id: 'runtime', title: 'LOCAL_RUNTIME', source: 'get_home_status', fields: [
|
||||
['direct_local_broker_state', status.directLocalBrokerState], ['direct_connection_count', status.directConnectionCount], ['resumable_session_count', status.resumableSessionCount], ['code_repository_mount_count', status.codeRepositoryMountCount], ['pncc_receipt_count', status.pnccReceiptCount], ['update_state', status.updateState], ['terminal_link_protocol', status.terminalLinkProtocol], ['terminal_link_transport', status.terminalLinkTransport],
|
||||
] },
|
||||
{ id: 'numbering', title: 'ZERO_CORE_NUMBERING_KERNEL', source: numberingKernel?.sourcePath || 'UNAVAILABLE', fields: [
|
||||
['state', numberingKernel?.state || 'UNAVAILABLE'], ['authority_map_id', numberingKernel?.authorityMapId || 'UNAVAILABLE'], ['authority_map_version', numberingKernel?.authorityMapVersion || 'UNAVAILABLE'], ['source_commit', numberingKernel?.sourceCommit || 'UNAVAILABLE'], ['automatic_identity_issuance', numberingKernel?.automaticIdentityIssuance ?? false], ['unknown_number', numberingKernel?.unknownNumber || 'FAIL_CLOSED'],
|
||||
] },
|
||||
{ id: 'gls', title: 'GLS_NATIVE_RUNTIME', source: glsRuntime?.sourceRoot || 'UNAVAILABLE', fields: [
|
||||
['state', glsRuntime?.state || 'UNAVAILABLE'], ['protocol_count', glsRuntime?.protocolCount ?? 0], ['executable_projection_count', glsRuntime?.executableProjectionCount ?? 0], ['authority_conflict_count', glsRuntime?.authorityConflictCount ?? 0], ['discovered_unreconciled_count', glsRuntime?.discoveredUnreconciledCount ?? 0], ['raw_protocol_text_executed', glsRuntime?.rawProtocolTextExecuted ?? false], ['arbitrary_protocol_code_allowed', glsRuntime?.arbitraryProtocolCodeAllowed ?? false],
|
||||
] },
|
||||
{ id: 'kernel', title: 'GLS_PROTOCOL_KERNEL', source: glsKernel?.contractId || 'UNAVAILABLE', fields: [
|
||||
['state', glsKernel?.state || 'UNAVAILABLE'], ['executable_protocol_count', glsKernel?.executableProtocolCount ?? 0], ['decision_receipt_count', glsKernel?.decisionReceiptCount ?? 0], ['allow_count', glsKernel?.allowCount ?? 0], ['deny_count', glsKernel?.denyCount ?? 0], ['model_can_override_decision', glsKernel?.modelCanOverrideDecision ?? false], ['last_receipt_sha256', glsKernel?.lastReceiptSha256 || 'GENESIS'],
|
||||
] },
|
||||
]
|
||||
return <section className="execution-projection content-page" aria-label="人格体结构化执行投影">
|
||||
<header className="page-title"><div><span className="kicker">PERSONA EXECUTION PROJECTION</span><h1>人格体执行层</h1><p>机器可读状态的界面投影。未知值保持 UNAVAILABLE,不以说明性文字补猜。</p></div><span className="execution-schema">hololake.execution-projection/v1</span></header>
|
||||
<div className="execution-section-grid">{sections.map((section) => <section className="execution-section" key={section.id}>
|
||||
<header><div><span>{section.id}</span><h2>{section.title}</h2></div><code>{section.source}</code></header>
|
||||
<dl className="execution-fields">{section.fields.map(([key, value]) => <div key={String(key)}><dt>{key}</dt><dd>{String(value)}</dd></div>)}</dl>
|
||||
</section>)}</div>
|
||||
</section>
|
||||
}
|
||||
|
||||
const renderSystem = () => (
|
||||
<section className="content-page">
|
||||
<header className="page-title"><div><span className="kicker">SYSTEM EVIDENCE</span><h1>系统详情</h1><p>显示可验证的当前状态;无法读取的项目将明确标记为不可用。</p></div></header>
|
||||
|
|
@ -2569,17 +2636,68 @@ function HoloLakeApp() {
|
|||
const enterpriseResponsibilityPending = Boolean(repoLogin && repoLogin.domain !== 'FIFTH_DOMAIN' && enterpriseEntry && !enterpriseRelationshipPending && enterpriseEntry.responsibility_receipt?.decision !== 'ACCEPT')
|
||||
const educationIndustry = subdomainIndustryRegistry.industries.find((industry) => industry.key === 'EDUCATION')
|
||||
const educationChannel = educationIndustry?.initialization_channel
|
||||
const renderHumanView = () => view === 'overview' ? renderOverview()
|
||||
: view === 'persona' ? <Suspense fallback={<div className="persona-chat-loading">正在打开语言人格频道……</div>}><PersonaChannelBody/></Suspense>
|
||||
: view === 'knowledge' ? renderKnowledge()
|
||||
: view === 'code' ? renderCode()
|
||||
: view === 'receipts' ? renderReceipts()
|
||||
: view === 'releases' ? renderReleaseCenter('human')
|
||||
: renderSystem()
|
||||
const workbenchViews: ViewId[] = ['overview', 'persona', 'knowledge', 'code', 'receipts', 'releases', 'system']
|
||||
|
||||
if (presentationMode === 'workbench' && repoLogin) {
|
||||
return <div className="standard-workbench">
|
||||
<aside className="workbench-activity" aria-label="工作台主导航">
|
||||
<div className="workbench-brand" title="HoloLake">HL</div>
|
||||
{workbenchViews.map((item) => <button className={view === item ? 'active' : ''} type="button" key={item} title={viewLabels[item]} aria-label={viewLabels[item]} onClick={() => setView(item)}><Icon name={item}/></button>)}
|
||||
<span className="workbench-activity-spacer"/>
|
||||
<button type="button" title="切换至光湖工作台" aria-label="切换至光湖工作台" onClick={() => choosePresentationMode('guanghu')}>◌</button>
|
||||
</aside>
|
||||
<aside className="workbench-sidebar">
|
||||
<header><div><b>HoloLake</b><span>标准工作台</span></div><button type="button" title="返回光湖工作台" onClick={() => choosePresentationMode('guanghu')}>切换</button></header>
|
||||
<button className="workbench-new-channel" type="button" onClick={() => { setView('persona'); chooseAudienceLayer('human') }}><Icon name="plus"/>新建对话</button>
|
||||
<nav aria-label="HoloLake 功能栏目">{workbenchViews.map((item) => <button className={view === item ? 'active' : ''} type="button" key={item} onClick={() => setView(item)}><Icon name={item}/><span>{viewLabels[item]}</span>{item === 'knowledge' && <small>{knowledge.uniqueDocumentCount}</small>}</button>)}</nav>
|
||||
<section className="workbench-scope"><span>当前工作区</span><b>{domainDisplayName(repoLogin.domain)}</b><small>{repoLogin.username}</small></section>
|
||||
</aside>
|
||||
<main className={`workbench-main audience-${audienceLayer}`}>
|
||||
<header className="workbench-topbar">
|
||||
<div><b>{viewLabels[view]}</b><span>{audienceLayer === 'human' ? '人类交互界面' : '人格体结构化执行界面'}</span></div>
|
||||
<div className="projection-switch" role="group" aria-label="界面投影层">
|
||||
<button className={audienceLayer === 'human' ? 'active' : ''} type="button" onClick={() => chooseAudienceLayer('human')}>人类界面</button>
|
||||
<button className={audienceLayer === 'execution' ? 'active' : ''} type="button" onClick={() => chooseAudienceLayer('execution')}>执行界面</button>
|
||||
</div>
|
||||
<div className="workbench-account"><span>{status.directLocalBrokerState === 'READY' ? '本机运行正常' : '本机运行受限'}</span><button type="button" onClick={() => void signOutRepo()}>退出</button></div>
|
||||
</header>
|
||||
<div className="workbench-content">{audienceLayer === 'human' ? renderHumanView() : view === 'releases' ? renderReleaseCenter('execution') : renderExecutionProjection()}</div>
|
||||
<footer className="workbench-statusbar"><span>HoloLake {productReleaseCenter.currentVersion}</span><span>{domainDisplayName(repoLogin.domain)}</span><span>{status.terminalLinkProtocol}</span><span>{status.directConnectionCount} 个连接</span></footer>
|
||||
</main>
|
||||
</div>
|
||||
}
|
||||
|
||||
if (presentationMode === 'workbench' && !repoLogin) {
|
||||
return <div className="standard-workbench public-standard-workbench">
|
||||
<aside className="workbench-activity"><div className="workbench-brand">HL</div><button className="active" type="button" aria-label="公告与版本"><Icon name="releases"/></button><span className="workbench-activity-spacer"/><button type="button" aria-label="切换至光湖入口" onClick={() => choosePresentationMode('guanghu')}>◌</button></aside>
|
||||
<aside className="workbench-sidebar"><header><div><b>HoloLake</b><span>标准工作台</span></div></header><nav><button className="active" type="button"><Icon name="releases"/><span>公告与版本</span></button></nav><section className="workbench-signin"><b>进入个人工作区</b><p>编号与账号验证在光湖世界入口完成。</p><button type="button" onClick={() => choosePresentationMode('guanghu')}>前往登录</button></section></aside>
|
||||
<main className="workbench-main audience-human"><header className="workbench-topbar"><div><b>公告与版本</b><span>公开发布中心</span></div><button className="workbench-world-switch" type="button" onClick={() => choosePresentationMode('guanghu')}>切换至光湖工作台</button></header><div className="workbench-content">{renderReleaseCenter('human')}</div><footer className="workbench-statusbar"><span>HoloLake {productReleaseCenter.currentVersion}</span><span>公开访问</span></footer></main>
|
||||
</div>
|
||||
}
|
||||
|
||||
if (!repoLogin || showMainWorld) {
|
||||
const resolvedDomain = zeroPoint?.resolvedDomain || ''
|
||||
const activeGate = domainGates.find((gate) => gate.domain === activeDomainInfo)
|
||||
return <div className={`official-world${motionAwake ? ' motion-awake' : ''}${loginRising ? ' sinking' : ''}`} onPointerMove={wakeAmbientMotion} onPointerDown={wakeAmbientMotion} onKeyDown={wakeAmbientMotion}>
|
||||
<LakeAtmosphere awake={motionAwake}/>
|
||||
<header className="world-titlebar"><b>HoloLake</b>{repoLogin ? <div className="world-title-actions"><span>{repoLogin.username} · {domainDisplayName(repoLogin.domain)}</span><button className="main-world-return" type="button" onClick={enterAuthenticatedDomain}>进入所属域</button><button type="button" aria-label="显示主题" onClick={() => setTheme(themes[(themes.findIndex((item) => item.id === theme) + 1) % themes.length].id)}>◌</button><button type="button" onClick={() => void signOutRepo()}>退出账号</button></div> : <button type="button" aria-label="显示主题" onClick={() => setTheme(themes[(themes.findIndex((item) => item.id === theme) + 1) % themes.length].id)}>◌</button>}</header>
|
||||
<header className="world-titlebar"><b>HoloLake</b>{repoLogin ? <div className="world-title-actions"><span>{repoLogin.username} · {domainDisplayName(repoLogin.domain)}</span><button type="button" onClick={() => choosePresentationMode('workbench')}>标准工作台</button><button className="main-world-return" type="button" onClick={enterAuthenticatedDomain}>进入所属域</button><button type="button" aria-label="显示主题" onClick={() => setTheme(themes[(themes.findIndex((item) => item.id === theme) + 1) % themes.length].id)}>◌</button><button type="button" onClick={() => void signOutRepo()}>退出账号</button></div> : <div className="world-title-actions"><button type="button" onClick={() => choosePresentationMode('workbench')}>标准工作台</button><button type="button" aria-label="显示主题" onClick={() => setTheme(themes[(themes.findIndex((item) => item.id === theme) + 1) % themes.length].id)}>◌</button></div>}</header>
|
||||
<main className={`world-scene${gateRising ? ' resolving' : ''}${gateStage === 'key' ? ' resolved' : ''}`}>
|
||||
<div className="official-hero"><h1>光湖语言系统 · 通用人工智能操作平台</h1><p>GH-AIOS · GUANGHU AI OPERATING SYSTEM</p></div>
|
||||
{domainGates.map((gate) => <LakePool key={gate.domain} className={gate.className} title={gate.title} meta={gate.domain === 'BRANCH_DOMAIN' ? '行业入口 · ONLINE' : gate.gate} open={gate.domain === 'FIFTH_DOMAIN' || gate.domain === 'BRANCH_DOMAIN' || gate.domain === repoLogin?.domain || (gateStage === 'key' && gate.domain === resolvedDomain)} risen={(gateRising || gateStage === 'key') && gate.domain === resolvedDomain} onClick={() => {
|
||||
if (gateStage !== 'number') return
|
||||
if (gate.domain === 'MAIN_DOMAIN') {
|
||||
setActiveDomainInfo('')
|
||||
setGateOpen(false)
|
||||
setWorldStage('mainDomain')
|
||||
return
|
||||
}
|
||||
if (gate.domain === 'BRANCH_DOMAIN') {
|
||||
setActiveDomainInfo('')
|
||||
setGateOpen(false)
|
||||
|
|
@ -2639,7 +2757,13 @@ function HoloLakeApp() {
|
|||
<button className="gate-back" type="button" onClick={() => { setGateStage('number'); setGateMessage(''); setLoginMessage('') }}>返回编号验证</button>
|
||||
</section>
|
||||
</>}
|
||||
{gateStage === 'number' && !gateOpen && !activeDomainInfo && <EraHomeEntry timeline={eraTimeline} coordinate={beijingCoordinate} onOpen={openEraTimeline}/>}
|
||||
{gateStage === 'number' && !gateOpen && !activeDomainInfo &&
|
||||
<EraHomeEntry timeline={eraTimeline} coordinate={beijingCoordinate} onOpen={openEraTimeline}/>
|
||||
}
|
||||
{worldStage === 'mainDomain' && <section className="main-domain-release-world" aria-label="光湖主域公告与版本中心">
|
||||
<header className="main-domain-release-bar"><button type="button" onClick={() => setWorldStage('domain')}>← 返回主世界</button><div><b>光湖主域</b><span>公告与版本中心</span></div><div className="projection-switch"><button className={audienceLayer === 'human' ? 'active' : ''} type="button" onClick={() => chooseAudienceLayer('human')}>人类界面</button><button className={audienceLayer === 'execution' ? 'active' : ''} type="button" onClick={() => chooseAudienceLayer('execution')}>执行界面</button></div></header>
|
||||
<div className="main-domain-release-content">{renderReleaseCenter(audienceLayer)}</div>
|
||||
</section>}
|
||||
{worldStage === 'subdomainIndustries' && <section className="channel-world subdomain-industry-world public-industry-world" aria-label="光湖分域行业系统">
|
||||
<button className="world-back" type="button" onClick={() => setWorldStage('domain')}>← 返回主世界</button>
|
||||
<div className="world-location"><h1>光湖分域 · 行业系统</h1><p>行业编号负责系统路由 · 不改变人的主体编号</p></div>
|
||||
|
|
@ -2683,11 +2807,11 @@ function HoloLakeApp() {
|
|||
const isZhizhi = repoLogin.domain === 'FIFTH_DOMAIN' && zeroPoint?.userNumber === 'ICE-GL-ZHI∞'
|
||||
return <div className={`official-world signed-in-world${motionAwake ? ' motion-awake' : ''}`} onPointerMove={wakeAmbientMotion} onPointerDown={wakeAmbientMotion} onKeyDown={wakeAmbientMotion}>
|
||||
<LakeAtmosphere awake={motionAwake}/>
|
||||
<header className="world-titlebar"><b>HoloLake</b><div className="world-title-actions"><span>{repoLogin.username} · {domainDisplayName(repoLogin.domain)}</span><button className="main-world-return" type="button" onClick={returnToMainWorld}>返回主世界</button><button type="button" onClick={() => setTheme(themes[(themes.findIndex((item) => item.id === theme) + 1) % themes.length].id)}>◌</button><button type="button" onClick={() => void signOutRepo()}>退出账号</button></div></header>
|
||||
<header className="world-titlebar"><b>HoloLake</b><div className="world-title-actions"><span>{repoLogin.username} · {domainDisplayName(repoLogin.domain)}</span><button type="button" onClick={() => choosePresentationMode('workbench')}>标准工作台</button><button className="main-world-return" type="button" onClick={returnToMainWorld}>返回主世界</button><button type="button" onClick={() => setTheme(themes[(themes.findIndex((item) => item.id === theme) + 1) % themes.length].id)}>◌</button><button type="button" onClick={() => void signOutRepo()}>退出账号</button></div></header>
|
||||
<main className="world-scene signed-in-scene">
|
||||
{worldStage === 'domain' && <section className="domain-home">
|
||||
<div className="world-location"><h1>{repoLogin.domain === 'FIFTH_DOMAIN' ? domainDisplayName(repoLogin.domain) : '光湖零感域'}</h1><p>{repoLogin.domain === 'FIFTH_DOMAIN' ? '世界正在发生什么' : '公共工作入口 · 世界正在发生什么'}</p></div>
|
||||
<div className="broadcast-stream"><p><i/>光湖公共灯塔 · 在线</p><p><i/>协议版本 · {enterpriseEntry?.registry_version || 'HLDP v1.0'}</p>{repoLogin.domain !== 'FIFTH_DOMAIN' && <p><i/>本人责任域 · {domainDisplayName(enterpriseEntry?.subject.domain || repoLogin.domain)}</p>}<p><i/>HoloLake · V0.8.1</p></div>
|
||||
<div className="broadcast-stream"><p><i/>光湖公共灯塔 · 在线</p><p><i/>协议版本 · {enterpriseEntry?.registry_version || 'HLDP v1.0'}</p>{repoLogin.domain !== 'FIFTH_DOMAIN' && <p><i/>本人责任域 · {domainDisplayName(enterpriseEntry?.subject.domain || repoLogin.domain)}</p>}<p><i/>HoloLake · V{productReleaseCenter.currentVersion}</p></div>
|
||||
<LakePool className="home-primary" title={repoLogin.domain === 'FIFTH_DOMAIN' ? '永恒湖心系统' : '光湖频道'} meta={repoLogin.domain === 'FIFTH_DOMAIN' ? '进入私人系统' : `${domainDisplayName(enterpriseEntry?.subject.domain || repoLogin.domain)} · 责任工作入口`} open onClick={() => setWorldStage(repoLogin.domain === 'FIFTH_DOMAIN' ? (isZhizhi ? 'heart' : 'channel') : 'channel')}/>
|
||||
<LakePool className="home-status" title="湖面天气" meta={connectionLabel} onClick={() => openWorldTool('system')}/>
|
||||
<EraHomeEntry timeline={eraTimeline} coordinate={beijingCoordinate} onOpen={openEraTimeline}/>
|
||||
|
|
@ -2809,8 +2933,8 @@ function HoloLakeApp() {
|
|||
<LakePool className="channel-status" title="湖面天气" meta={connectionLabel} onClick={() => openWorldTool('system')}/>
|
||||
</section>}
|
||||
{worldStage === 'tool' && <section className={`world-tool world-tool-${view}`}>
|
||||
<header className="tool-worldbar"><button type="button" onClick={() => setWorldStage(toolReturnStage)}>← 退回频道全景</button><b>{viewLabels[view]}</b><span>{domainDisplayName(repoLogin.domain)}</span></header>
|
||||
<div className="tool-projection">{view === 'overview' ? renderOverview() : view === 'persona' ? <Suspense fallback={<div className="persona-chat-loading">正在打开对话频道……</div>}><PersonaChannelBody/></Suspense> : view === 'knowledge' ? renderKnowledge() : view === 'code' ? renderCode() : view === 'receipts' ? renderReceipts() : renderSystem()}</div>
|
||||
<header className="tool-worldbar"><button type="button" onClick={() => setWorldStage(toolReturnStage)}>← 退回频道全景</button><b>{viewLabels[view]}</b><div className="tool-worldbar-actions"><div className="projection-switch"><button className={audienceLayer === 'human' ? 'active' : ''} type="button" onClick={() => chooseAudienceLayer('human')}>人类界面</button><button className={audienceLayer === 'execution' ? 'active' : ''} type="button" onClick={() => chooseAudienceLayer('execution')}>执行界面</button></div><span>{domainDisplayName(repoLogin.domain)}</span></div></header>
|
||||
<div className="tool-projection">{audienceLayer === 'human' ? renderHumanView() : view === 'releases' ? renderReleaseCenter('execution') : renderExecutionProjection()}</div>
|
||||
</section>}
|
||||
</main>
|
||||
<footer className="world-footer"><b>光湖语言系统 · 通用人工智能操作平台</b><span>GH-AIOS</span></footer>
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ export function PersonaChannelBody({ onBack, knowledgeFocus, embedded = false }:
|
|||
const next = await invoke<PersonaBodySnapshot>('register_trial_persona', { input: { displayName: personaName.trim() } })
|
||||
setBody(next)
|
||||
setPersonaId(next.personas.at(-1)?.personaId || '')
|
||||
setNotice('对话入口已建立。当前是本机可逆投影,不把模型名称冒充人格身份。')
|
||||
setNotice('对话入口已建立。当前入口保存在本机,可在设置中继续管理。')
|
||||
} catch (error) { setNotice(errorText(error)) }
|
||||
finally { setLoading(false) }
|
||||
}
|
||||
|
|
@ -128,7 +128,7 @@ export function PersonaChannelBody({ onBack, knowledgeFocus, embedded = false }:
|
|||
event.preventDefault()
|
||||
if (!draft.trim() || !personaId || !providerId) return
|
||||
setSending(true)
|
||||
setNotice('正在理解这句话,并读取当前知识上下文……')
|
||||
setNotice('正在生成回复并读取当前知识上下文……')
|
||||
try {
|
||||
const next = await invoke<ConversationSnapshot>('send_message', { input: {
|
||||
personaId,
|
||||
|
|
@ -186,8 +186,8 @@ export function PersonaChannelBody({ onBack, knowledgeFocus, embedded = false }:
|
|||
<div>
|
||||
{!embedded && onBack && <button type="button" onClick={onBack}>← 返回光之湖</button>}
|
||||
<span>LANGUAGE PERSONA CHANNEL</span>
|
||||
<h1>{embedded ? '共同阅读' : '与人格体对话'}</h1>
|
||||
<p>{knowledgeFocus ? <>正在共同看见:<b>{knowledgeFocus.title}</b></> : '说话是主入口。知识、模型和执行证据在需要时进入,不要求人先学习配置。'}</p>
|
||||
<h1>{embedded ? '对话与知识协作' : '语言人格频道'}</h1>
|
||||
<p>{knowledgeFocus ? <>当前知识页:<b>{knowledgeFocus.title}</b></> : '直接输入消息开始对话;知识引用、模型设置和运行回执按需展开。'}</p>
|
||||
</div>
|
||||
<div className="persona-chat-status">
|
||||
<i className={providerId ? 'ready' : ''}/>
|
||||
|
|
@ -197,7 +197,7 @@ export function PersonaChannelBody({ onBack, knowledgeFocus, embedded = false }:
|
|||
</header>
|
||||
|
||||
{!body?.personas.length ? <form className="persona-chat-onboarding" onSubmit={(event) => void createConversationEntry(event)}>
|
||||
<div><span>建立本机对话入口</span><h2>先让频道知道你要和谁说话</h2><p>这是可撤回的本机运行投影,不会用一次点击冒充人格绑定或长期关系已经建立。</p></div>
|
||||
<div><span>建立本机对话入口</span><h2>设置频道显示名称</h2><p>该名称用于当前设备上的频道列表,不会更改模型配置或系统权限。</p></div>
|
||||
<input value={personaName} maxLength={80} aria-label="频道显示名称" onChange={(event) => setPersonaName(event.target.value)}/>
|
||||
<button disabled={loading || !personaName.trim()}>进入对话</button>
|
||||
</form> : <>
|
||||
|
|
@ -207,11 +207,11 @@ export function PersonaChannelBody({ onBack, knowledgeFocus, embedded = false }:
|
|||
<p>{item.content}</p>
|
||||
{item.toolReceipts.length > 0 && <div className="persona-knowledge-receipts">{item.toolReceipts.map((receipt) => <span key={`${item.messageId}-${receipt.path}`} title={receipt.contentSha256}>引用知识 · {receipt.path} · {shortHash(receipt.contentSha256)}</span>)}</div>}
|
||||
<footer><span>{item.model || item.providerId}</span><span title={item.receiptHash}>回执 {shortHash(item.receiptHash)}</span></footer>
|
||||
</article>) : <div className="persona-chat-empty"><span>频道已经就绪</span><h2>{knowledgeFocus ? '可以直接问当前这页' : '从第一句话开始'}</h2><p>{knowledgeFocus ? '当前页面会被精确读取;回复下方会出现知识路径与内容指纹。' : '也可以用“@知识 关键词”让系统检索本地知识库。'}</p></div>}
|
||||
</article>) : <div className="persona-chat-empty"><span>频道已就绪</span><h2>{knowledgeFocus ? '询问当前知识页' : '开始新对话'}</h2><p>{knowledgeFocus ? '回复会附带知识路径与内容指纹,便于核验引用来源。' : '输入“@知识 关键词”可检索本地知识库。'}</p></div>}
|
||||
</div>
|
||||
<form className="persona-chat-composer" onSubmit={(event) => void send(event)}>
|
||||
{knowledgeFocus && <div className="persona-knowledge-focus"><span>当前知识</span><b>{knowledgeFocus.title}</b><small>{knowledgeFocus.path}</small></div>}
|
||||
<textarea autoFocus={!embedded} value={draft} maxLength={64000} placeholder={knowledgeFocus ? `围绕《${knowledgeFocus.title}》说点什么……` : '在这里说话;需要知识时可以直接问。'} onChange={(event) => setDraft(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter' && !event.shiftKey && !event.nativeEvent.isComposing) { event.preventDefault(); event.currentTarget.form?.requestSubmit() } }}/>
|
||||
<textarea autoFocus={!embedded} value={draft} maxLength={64000} placeholder={knowledgeFocus ? `输入关于《${knowledgeFocus.title}》的问题` : '输入消息;需要知识时可直接提问。'} onChange={(event) => setDraft(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter' && !event.shiftKey && !event.nativeEvent.isComposing) { event.preventDefault(); event.currentTarget.form?.requestSubmit() } }}/>
|
||||
<div><span>Enter 发送 · Shift+Enter 换行 · 每次回复保存事件和回执</span><button disabled={sending || !draft.trim() || !providerId}>{sending ? '正在回应…' : '发送'}</button></div>
|
||||
</form>
|
||||
</>}
|
||||
|
|
|
|||
|
|
@ -1272,3 +1272,108 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
|
|||
@media (prefers-reduced-motion: reduce) {
|
||||
.world-star, .world-glint, .world-shine, .pool-bay, .pool-halo, .pool-ring, .nucleus-locus i { animation: none !important; }
|
||||
}
|
||||
|
||||
/* ===== 产品级双投影:光湖工作台 / 标准工作台 ===== */
|
||||
.standard-workbench { width: 100vw; height: 100vh; display: grid; grid-template-columns: 52px 244px minmax(0, 1fr); overflow: hidden; color: var(--content-primary); background: var(--surface-depth); }
|
||||
.workbench-activity { display: flex; flex-direction: column; align-items: center; gap: 5px; padding: 9px 6px 8px; border-right: 1px solid var(--panel-edge); background: color-mix(in srgb, var(--surface-sky) 86%, #050811); }
|
||||
.workbench-brand { display: grid; place-items: center; width: 34px; height: 34px; margin-bottom: 7px; border: 1px solid color-mix(in srgb, var(--accent-light) 54%, transparent); border-radius: 9px; color: var(--accent-light); font-size: 11px; font-weight: 850; letter-spacing: .08em; }
|
||||
.workbench-activity button { display: grid; place-items: center; width: 38px; height: 38px; border: 0; border-radius: 8px; color: var(--content-muted); background: transparent; cursor: pointer; }
|
||||
.workbench-activity button:hover { color: var(--content-primary); background: var(--primitive-glass-hover); }
|
||||
.workbench-activity button.active { color: var(--accent-light); background: color-mix(in srgb, var(--accent) 17%, transparent); box-shadow: inset 2px 0 var(--accent-light); }
|
||||
.workbench-activity svg { width: 19px; height: 19px; fill: none; stroke: currentColor; stroke-width: 1.65; stroke-linecap: round; stroke-linejoin: round; }
|
||||
.workbench-activity-spacer { flex: 1; }
|
||||
.workbench-sidebar { min-width: 0; display: flex; flex-direction: column; border-right: 1px solid var(--panel-edge); background: color-mix(in srgb, var(--panel-bg) 92%, var(--surface-depth)); }
|
||||
.workbench-sidebar > header { min-height: 54px; display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 0 14px; border-bottom: 1px solid var(--panel-edge); }
|
||||
.workbench-sidebar > header div { display: grid; gap: 2px; }
|
||||
.workbench-sidebar > header b { font-size: 14px; letter-spacing: .03em; }
|
||||
.workbench-sidebar > header span { color: var(--content-muted); font-size: 10.5px; }
|
||||
.workbench-sidebar > header button { border: 0; color: var(--content-muted); background: transparent; font-size: 11px; cursor: pointer; }
|
||||
.workbench-new-channel { display: flex; align-items: center; justify-content: center; gap: 8px; margin: 12px 12px 8px; padding: 9px 12px; border: 1px solid color-mix(in srgb, var(--accent-light) 45%, var(--panel-edge)); border-radius: 8px; color: var(--content-primary); background: color-mix(in srgb, var(--accent) 13%, transparent); cursor: pointer; }
|
||||
.workbench-new-channel svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.8; }
|
||||
.workbench-sidebar nav { display: grid; gap: 2px; padding: 3px 8px; }
|
||||
.workbench-sidebar nav button { display: grid; grid-template-columns: 20px minmax(0, 1fr) auto; align-items: center; gap: 9px; min-height: 34px; padding: 0 9px; border: 0; border-radius: 6px; color: var(--content-secondary); background: transparent; font-size: 12.5px; text-align: left; cursor: pointer; }
|
||||
.workbench-sidebar nav button:hover { color: var(--content-primary); background: var(--primitive-glass-hover); }
|
||||
.workbench-sidebar nav button.active { color: var(--content-primary); background: color-mix(in srgb, var(--accent) 16%, var(--primitive-glass)); }
|
||||
.workbench-sidebar nav svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.65; stroke-linecap: round; stroke-linejoin: round; }
|
||||
.workbench-sidebar nav small { color: var(--content-muted); font-size: 10px; }
|
||||
.workbench-scope { display: grid; gap: 4px; margin: auto 12px 12px; padding: 12px; border-top: 1px solid var(--panel-edge); }
|
||||
.workbench-scope span, .workbench-scope small { color: var(--content-muted); font-size: 10.5px; }
|
||||
.workbench-scope b { font-size: 12.5px; }
|
||||
.workbench-main { min-width: 0; min-height: 0; display: grid; grid-template-rows: 54px minmax(0, 1fr) 24px; }
|
||||
.workbench-topbar { display: grid; grid-template-columns: minmax(150px, 1fr) auto minmax(150px, 1fr); align-items: center; gap: 14px; padding: 0 16px; border-bottom: 1px solid var(--panel-edge); background: color-mix(in srgb, var(--panel-bg) 88%, transparent); }
|
||||
.workbench-topbar > div:first-child { display: flex; align-items: baseline; gap: 10px; }
|
||||
.workbench-topbar > div:first-child b { font-size: 13.5px; }
|
||||
.workbench-topbar > div:first-child span { color: var(--content-muted); font-size: 10.5px; }
|
||||
.workbench-account { justify-self: end; display: flex; align-items: center; gap: 10px; color: var(--content-muted); font-size: 10.5px; }
|
||||
.workbench-account button, .workbench-world-switch { padding: 6px 9px; border: 1px solid var(--panel-edge); border-radius: 6px; color: var(--content-secondary); background: transparent; cursor: pointer; }
|
||||
.workbench-world-switch { justify-self: end; }
|
||||
.projection-switch { display: inline-flex; padding: 2px; border: 1px solid var(--panel-edge); border-radius: 7px; background: color-mix(in srgb, var(--surface-depth) 62%, transparent); }
|
||||
.projection-switch button { padding: 5px 9px; border: 0; border-radius: 5px; color: var(--content-muted); background: transparent; font-size: 10.5px; font-weight: 650; cursor: pointer; }
|
||||
.projection-switch button.active { color: var(--content-primary); background: var(--primitive-glass-hover); box-shadow: 0 1px 4px rgba(0, 0, 0, .18); }
|
||||
.workbench-content { min-width: 0; min-height: 0; overflow: auto; background: color-mix(in srgb, var(--surface-depth) 94%, var(--panel-bg)); }
|
||||
.workbench-content > .knowledge-workspace, .workbench-content > .code-workspace, .workbench-content > .persona-chat-world { height: 100%; }
|
||||
.workbench-statusbar { display: flex; align-items: center; gap: 18px; padding: 0 10px; border-top: 1px solid color-mix(in srgb, var(--accent) 35%, var(--panel-edge)); color: color-mix(in srgb, var(--accent-light) 76%, var(--content-muted)); background: color-mix(in srgb, var(--accent) 13%, var(--surface-sky)); font-size: 9.5px; }
|
||||
.workbench-signin { margin: auto 12px 16px; padding: 14px; border: 1px solid var(--panel-edge); border-radius: 9px; background: var(--primitive-glass); }
|
||||
.workbench-signin b { font-size: 12.5px; }
|
||||
.workbench-signin p { margin: 7px 0 11px; color: var(--content-muted); font-size: 11px; line-height: 1.55; }
|
||||
.workbench-signin button { width: 100%; padding: 8px; border: 1px solid color-mix(in srgb, var(--accent-light) 45%, var(--panel-edge)); border-radius: 6px; color: var(--content-primary); background: color-mix(in srgb, var(--accent) 14%, transparent); cursor: pointer; }
|
||||
|
||||
/* ===== 光湖主域 · 公告与版本 ===== */
|
||||
.main-domain-release-world { position: absolute; z-index: 30; inset: 48px 0 0; display: grid; grid-template-rows: 54px minmax(0, 1fr); background: color-mix(in srgb, var(--surface-depth) 97%, #060913); }
|
||||
.main-domain-release-bar { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 16px; padding: 0 20px; border-bottom: 1px solid var(--panel-edge); background: color-mix(in srgb, var(--panel-bg) 91%, transparent); }
|
||||
.main-domain-release-bar > button { justify-self: start; border: 0; color: var(--content-muted); background: transparent; cursor: pointer; }
|
||||
.main-domain-release-bar > div:nth-child(2) { display: flex; align-items: baseline; gap: 9px; }
|
||||
.main-domain-release-bar > div:nth-child(2) span { color: var(--content-muted); font-size: 11px; }
|
||||
.main-domain-release-bar .projection-switch { justify-self: end; }
|
||||
.main-domain-release-content { min-height: 0; overflow: auto; }
|
||||
.release-center { max-width: 1040px; margin: 0 auto; }
|
||||
.release-center-title { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; }
|
||||
.release-current-badge, .execution-schema { padding: 7px 10px; border: 1px solid color-mix(in srgb, var(--accent-light) 45%, var(--panel-edge)); border-radius: 7px; color: var(--accent-light); background: color-mix(in srgb, var(--accent) 11%, transparent); font-size: 10.5px; white-space: nowrap; }
|
||||
.release-timeline { position: relative; display: grid; gap: 10px; padding-left: 19px; }
|
||||
.release-timeline::before { content: ''; position: absolute; left: 5px; top: 13px; bottom: 13px; width: 1px; background: color-mix(in srgb, var(--accent-light) 30%, var(--panel-edge)); }
|
||||
.release-entry { position: relative; padding: 18px 20px; border: 1px solid var(--panel-edge); border-radius: 10px; background: color-mix(in srgb, var(--panel-bg) 90%, transparent); }
|
||||
.release-entry::before { content: ''; position: absolute; left: -18px; top: 24px; width: 7px; height: 7px; border: 2px solid var(--surface-depth); border-radius: 50%; background: var(--content-muted); }
|
||||
.release-entry.current { border-color: color-mix(in srgb, var(--accent-light) 48%, var(--panel-edge)); }
|
||||
.release-entry.current::before { background: var(--accent-light); box-shadow: 0 0 10px color-mix(in srgb, var(--accent-light) 68%, transparent); }
|
||||
.release-entry > header { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
||||
.release-entry > header > div { display: flex; align-items: baseline; gap: 10px; }
|
||||
.release-version { color: var(--accent-light); font-size: 12px; font-weight: 800; }
|
||||
.release-entry time { color: var(--content-muted); font-size: 10.5px; }
|
||||
.release-entry h2 { margin: 13px 0 6px; font-size: 16px; }
|
||||
.release-entry > p { margin: 0; color: var(--content-secondary); font-size: 12.5px; line-height: 1.7; }
|
||||
.release-entry ul { display: flex; flex-wrap: wrap; gap: 6px 22px; margin: 12px 0 0; padding: 10px 0 0 18px; border-top: 1px solid var(--panel-edge); color: var(--content-muted); font-size: 11px; line-height: 1.6; }
|
||||
|
||||
/* ===== 人格体执行层:固定字段,不使用说明文本代替状态 ===== */
|
||||
.execution-projection { max-width: 1180px; margin: 0 auto; }
|
||||
.execution-projection .page-title { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; }
|
||||
.execution-section-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
|
||||
.execution-section { min-width: 0; overflow: hidden; border: 1px solid var(--panel-edge); border-radius: 9px; background: color-mix(in srgb, var(--panel-bg) 90%, transparent); }
|
||||
.execution-section > header { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding: 13px 15px; border-bottom: 1px solid var(--panel-edge); }
|
||||
.execution-section > header span { color: var(--accent-light); font-size: 9px; font-weight: 750; letter-spacing: .12em; }
|
||||
.execution-section > header h2 { margin: 4px 0 0; font-size: 12px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
|
||||
.execution-section > header code { max-width: 48%; overflow: hidden; color: var(--content-muted); font-size: 9px; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.execution-fields { margin: 0; }
|
||||
.execution-fields > div { display: grid; grid-template-columns: minmax(150px, .8fr) minmax(0, 1.2fr); gap: 12px; padding: 9px 15px; border-bottom: 1px solid color-mix(in srgb, var(--panel-edge) 66%, transparent); }
|
||||
.execution-fields > div:last-child { border-bottom: 0; }
|
||||
.execution-fields dt, .execution-fields dd { margin: 0; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 10px; overflow-wrap: anywhere; }
|
||||
.execution-fields dt { color: var(--content-muted); }
|
||||
.execution-fields dd { color: var(--content-secondary); }
|
||||
.release-fields { margin-top: 12px; border-top: 1px solid var(--panel-edge); }
|
||||
.release-fields > div { padding-left: 0; padding-right: 0; }
|
||||
.tool-worldbar-actions { justify-self: end; display: flex; align-items: center; gap: 12px; }
|
||||
.tool-worldbar-actions > span { color: var(--content-muted); font-size: 12px; }
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.standard-workbench { grid-template-columns: 48px 190px minmax(0, 1fr); }
|
||||
.workbench-topbar { grid-template-columns: minmax(120px, 1fr) auto; }
|
||||
.workbench-account { display: none; }
|
||||
.execution-section-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.standard-workbench { grid-template-columns: 46px minmax(0, 1fr); }
|
||||
.workbench-sidebar { display: none; }
|
||||
.workbench-topbar > div:first-child span { display: none; }
|
||||
.main-domain-release-bar { grid-template-columns: 1fr auto; }
|
||||
.main-domain-release-bar > div:nth-child(2) { display: none; }
|
||||
.release-center-title, .execution-projection .page-title { align-items: flex-start; flex-direction: column; }
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue