feat: add remote-first education work lake

This commit is contained in:
冰朔 2026-08-07 20:41:03 +08:00
commit 333cd222c5
43 changed files with 923 additions and 74 deletions

View file

@ -1,7 +1,7 @@
{ {
"schema": "hololake.module-registry/v1", "schema": "hololake.module-registry/v1",
"registry_id": "HL-MODULE-REGISTRY-0001", "registry_id": "HL-MODULE-REGISTRY-0001",
"version": "2026.08.07.1", "version": "2026.08.07.2",
"authority_rule": "A module entry maps a stable id to a real path and provenance; it grants no execution authority. Pulling a module does not equal being online.", "authority_rule": "A module entry maps a stable id to a real path and provenance; it grants no execution authority. Pulling a module does not equal being online.",
"modules": [ "modules": [
{ {
@ -31,6 +31,90 @@
}, },
"reuse_policy": "CANDIDATE_ELSEWHERE: other personas must run scripts/drill.sh and reconcile detected counts with human-observed events before claiming online.", "reuse_policy": "CANDIDATE_ELSEWHERE: other personas must run scripts/drill.sh and reconcile detected counts with human-observed events before claiming online.",
"recorded_at": "2026-08-07T16:00:00+08:00" "recorded_at": "2026-08-07T16:00:00+08:00"
},
{
"id": "HL-MOD-KNOWLEDGE-001",
"name": "hololake-knowledge-lake",
"version": "0.4.6",
"state": "MOUNTED_STAGE_1",
"path": "product-source/hololake-platform/src/",
"repo": "REPO-014 hololake-system-architecture",
"provenance": "Tolaria REPO-004 component source, assembled in REPO-014",
"license": "AGPL-3.0-or-later",
"capabilities": ["knowledge.search", "knowledge.read", "knowledge.edit", "knowledge.project"],
"execution_boundary": "application-scoped"
},
{
"id": "HL-MOD-DOC-001",
"name": "blocknote-document-surface",
"version": "0.46.2",
"state": "MOUNTED_STAGE_1",
"path": "product-source/hololake-platform/src/components/SingleEditorView.tsx",
"repo": "REPO-014 hololake-system-architecture",
"provenance": "BlockNote via Tolaria REPO-004",
"license": "MPL-2.0 majority; verify per package before redistribution",
"capabilities": ["document.open", "document.edit", "document.persist"],
"execution_boundary": "application-scoped"
},
{
"id": "HL-MOD-SHEET-001",
"name": "ironcalc-spreadsheet-surface",
"version": "0.5.7",
"state": "MOUNTED_STAGE_1",
"path": "product-source/hololake-platform/src/components/SheetEditor.tsx",
"repo": "REPO-014 hololake-system-architecture",
"provenance": "IronCalc via Tolaria REPO-004",
"license": "MIT OR Apache-2.0",
"capabilities": ["sheet.open", "sheet.edit", "sheet.calculate", "sheet.persist"],
"execution_boundary": "application-scoped"
},
{
"id": "HL-MOD-CHART-001",
"name": "education-comparison-chart",
"version": "1.0.0",
"state": "MOUNTED_STAGE_1",
"path": "product-source/hololake-platform/src/components/EducationWorkLake.tsx",
"repo": "REPO-014 hololake-system-architecture",
"provenance": "HoloLake native client projection",
"license": "AGPL-3.0-or-later",
"capabilities": ["chart.project"],
"execution_boundary": "read-only-projection-of-application-data"
},
{
"id": "HL-MOD-DASHBOARD-001",
"name": "education-dashboard",
"version": "1.0.0",
"state": "MOUNTED_STAGE_1",
"path": "product-source/hololake-platform/src/components/EducationWorkLake.tsx",
"repo": "REPO-014 hololake-system-architecture",
"provenance": "HoloLake native client projection",
"license": "AGPL-3.0-or-later",
"capabilities": ["dashboard.project"],
"execution_boundary": "read-only-projection-of-application-data"
},
{
"id": "HL-MOD-CANVAS-001",
"name": "whiteboard-canvas-candidate",
"version": "unselected",
"state": "QUARANTINE_LICENSE_REVIEW",
"path": "product-source/hololake-platform/src/components/TldrawWhiteboard.tsx",
"repo": "REPO-014 hololake-system-architecture",
"provenance": "tldraw component inherited through Tolaria REPO-004; Excalidraw is the permissive replacement candidate",
"license": "tldraw production license required; Excalidraw MIT candidate",
"capabilities": [],
"execution_boundary": "not-registered-for-production-execution"
},
{
"id": "HL-MOD-AGENT-RUNTIME-001",
"name": "coding-agent-pattern-study",
"version": "780d1388fff103ff0db0d8c14de65af6225b4860",
"state": "RESEARCH_ONLY_ISOLATED",
"path": "/Volumes/JZAO/HoloLake/upstream-mirrors/grok-build-upstream.git",
"repo": "REPO-015 grok-build-upstream-mirror",
"provenance": "SpaceXAI Grok Build mirror, Apache-2.0 first-party code",
"license": "Apache-2.0 plus third-party notices",
"capabilities": [],
"execution_boundary": "patterns-may-be-reimplemented; never merge runtime into persona subject"
} }
] ]
} }

View file

@ -172,6 +172,16 @@ interface VaultEntry {
} }
``` ```
### Industry work lake
An industry work lake is a client-rendered collection of registered capabilities rather than a new vault type. The stage-one education channel uses:
- `EducationInstructionRoute` to map one bounded language instruction to one surface and module id.
- `EducationWorkLake` to hold application-scoped document and spreadsheet state and project chart/dashboard views.
- `engineering/MODULE-REGISTRY.json` as the stable lookup from module id to real source path, provenance, license state, and execution boundary.
Opening a surface proves only that the client routed and rendered it. It does not prove server persistence, multi-user collaboration, deployment health, or persona birth.
### WorkspaceIdentity ### WorkspaceIdentity
Mounted workspace provenance is renderer-owned metadata attached to `VaultEntry.workspace` when entries are loaded through the registered workspace set. It is not parsed from note frontmatter and is not written into vault files. Mounted workspace provenance is renderer-owned metadata attached to `VaultEntry.workspace` when entries are loaded through the registered workspace set. It is not parsed from note frontmatter and is not written into vault files.

View file

@ -157,7 +157,7 @@ Editor responsiveness is also protected by a synthetic browser benchmark. `pnpm
| Backend language | Rust (edition 2021) | 1.77.2 | | Backend language | Rust (edition 2021) | 1.77.2 |
| Frontmatter parsing | gray_matter | 0.2 | | Frontmatter parsing | gray_matter | 0.2 |
| Filesystem watcher | notify | 6.1 | | Filesystem watcher | notify | 6.1 |
| AI (workspace) | CLI agent adapters (Claude Code + Codex + GitHub Copilot + OpenCode + Pi + Antigravity + Kiro + Hermes Agent) plus configured local/API model targets | - | | AI (workspace) | Product-visible CLI adapters (Claude Code + Codex + GitHub Copilot) plus configured local/API model targets | - |
| Search | Keyword (walkdir-based file scan) | - | | Search | Keyword (walkdir-based file scan) | - |
| Localization | App-owned runtime + JSON catalogs (`src/lib/i18n.ts`, `src/lib/locales/*.json`, `lara.yaml`) | English fallback + Lara CLI sync | | Localization | App-owned runtime + JSON catalogs (`src/lib/i18n.ts`, `src/lib/locales/*.json`, `lara.yaml`) | English fallback + Lara CLI sync |
| MCP | @modelcontextprotocol/sdk | 1.0 | | MCP | @modelcontextprotocol/sdk | 1.0 |
@ -183,6 +183,8 @@ flowchart TD
`HoloLakeHome` owns only transient navigation state. It does not persist system ownership, server addresses, or credentials. Its navigation and native-module entries now pass through the versioned living-system contract in `guanghuLivingSystem.ts`. Each event binds one exact allowlisted intent; the host invokes Knowledge Lake, Agent workspace, or local-folder callbacks only after the returned event id, intent, route, truth references, authorization state, and declarative scene all validate. Theme selection remains a human decision and emits only a bounded scene-adaptation intent. The native desktop first requests a receipt-backed plan from the BS-SH-005 lighthouse through a loopback-only HLDP tunnel; when that tunnel is absent the native command restores the exact registered SSH forward before retrying once. The DeepSeek credential never leaves the server. A configured direct model is the secondary planner, and the deterministic recovery plan is the final availability path. Invalid, unavailable, or late model output produces an explicitly tagged recovery plan rather than an invented success. The canvas consumes only validated motion, star-density, depth, and connection-emphasis primitives; the model never draws pixels or runs per animation frame. The existing vault remains the filesystem source of truth. From 0.1.7 onward, channel mode owns the full application body; entering Knowledge Lake reveals the sidebar and note list as navigation inside that module while a Guanghu module dock remains present. In 0.1.8, `JD-FD-PRIMARY` resolves the public Fifth Domain discovery document without credentials and validates its read-only Guanghu routes. That result proves only public navigation availability; the UI continues to withhold private server-health claims until an authenticated probe exists. See [ADR 0154](adr/0154-channel-owned-workspace-shell.md), [ADR 0155](adr/0155-fifth-domain-read-only-discovery-and-ai-tool-boundary.md), and [ADR 0169](adr/0169-model-native-living-galaxy-system.md). `HoloLakeHome` owns only transient navigation state. It does not persist system ownership, server addresses, or credentials. Its navigation and native-module entries now pass through the versioned living-system contract in `guanghuLivingSystem.ts`. Each event binds one exact allowlisted intent; the host invokes Knowledge Lake, Agent workspace, or local-folder callbacks only after the returned event id, intent, route, truth references, authorization state, and declarative scene all validate. Theme selection remains a human decision and emits only a bounded scene-adaptation intent. The native desktop first requests a receipt-backed plan from the BS-SH-005 lighthouse through a loopback-only HLDP tunnel; when that tunnel is absent the native command restores the exact registered SSH forward before retrying once. The DeepSeek credential never leaves the server. A configured direct model is the secondary planner, and the deterministic recovery plan is the final availability path. Invalid, unavailable, or late model output produces an explicitly tagged recovery plan rather than an invented success. The canvas consumes only validated motion, star-density, depth, and connection-emphasis primitives; the model never draws pixels or runs per animation frame. The existing vault remains the filesystem source of truth. From 0.1.7 onward, channel mode owns the full application body; entering Knowledge Lake reveals the sidebar and note list as navigation inside that module while a Guanghu module dock remains present. In 0.1.8, `JD-FD-PRIMARY` resolves the public Fifth Domain discovery document without credentials and validates its read-only Guanghu routes. That result proves only public navigation availability; the UI continues to withhold private server-health claims until an authenticated probe exists. See [ADR 0154](adr/0154-channel-owned-workspace-shell.md), [ADR 0155](adr/0155-fifth-domain-read-only-discovery-and-ai-tool-boundary.md), and [ADR 0169](adr/0169-model-native-living-galaxy-system.md).
The education work lake is the first remote-first industry channel. `EducationWorkLake` routes a bounded language instruction to one registered capability, opens a client-rendered surface, and exposes the capability and execution receipt. Its editable document and IronCalc spreadsheet remain inside the application boundary; chart and dashboard views are projections of the current sheet. The HoloLake world entry bypasses local-vault onboarding while the existing local knowledge lake remains available as an optional module. See [ADR 0173](adr/0173-remote-first-industry-work-lakes.md).
```mermaid ```mermaid
flowchart TD flowchart TD
subgraph TW["Tauri v2 Window"] subgraph TW["Tauri v2 Window"]

View file

@ -0,0 +1,27 @@
---
type: ADR
id: "0173"
title: "Remote-first industry work lakes use registered client-rendered modules"
status: active
date: 2026-08-07
---
## Context
The inherited Tolaria entry assumes that a local filesystem vault must exist before any useful surface can open. HoloLake needs a different product boundary: Git repositories hold reusable, numbered components; a persona routes human language to those components; the resulting document, spreadsheet, chart, dashboard, or knowledge surface renders on the user's device. An industry workspace may be temporarily hosted on `JD-FD-PRIMARY` and later moved without changing its capability identifiers.
## Decision
Industry work lakes are remote-first application channels above the optional local vault.
- The HoloLake world entry is available without creating, choosing, or repairing a local vault.
- Every callable surface has a stable module id in `engineering/MODULE-REGISTRY.json`.
- Language routing selects a registered capability and emits a receipt; it does not treat model text as proof of execution.
- Stage-one document and spreadsheet state remains inside the application/browser boundary.
- Server hosting and client rendering are separate: the temporary server serves the application, while user-facing editing and projection execute on the client.
- The first education work lake mounts document, spreadsheet, comparison-chart, dashboard, and knowledge surfaces. Remote multi-user persistence is a later capability and must have its own authorization and receipt contract.
- Grok Build remains an isolated research source for agent-runtime patterns. Its runtime is not merged into a persona subject.
## Consequences
The obsolete local-vault chooser no longer blocks the HoloLake entry. Existing vault features remain available as one optional knowledge module. Industry migration becomes a deployment-route change rather than a data-model rewrite. Collaborative persistence, domestic model providers, and production whiteboards remain explicit follow-up modules instead of hidden claims in stage one.

View file

@ -10,6 +10,7 @@
"prebuild": "pnpm test:native-authority && pnpm test:native-core", "prebuild": "pnpm test:native-authority && pnpm test:native-core",
"build": "tsc -b && vite build", "build": "tsc -b && vite build",
"build:team-foundation": "VITE_HOLOLAKE_DISTRIBUTION=team-foundation tsc -b && VITE_HOLOLAKE_DISTRIBUTION=team-foundation vite build && node scripts/finalize-team-foundation-build.mjs", "build:team-foundation": "VITE_HOLOLAKE_DISTRIBUTION=team-foundation tsc -b && VITE_HOLOLAKE_DISTRIBUTION=team-foundation vite build && node scripts/finalize-team-foundation-build.mjs",
"build:education": "tsc -b && vite build --base=/jd/education/",
"agent-docs": "node scripts/build-agent-docs.mjs", "agent-docs": "node scripts/build-agent-docs.mjs",
"bundle-mcp": "node scripts/bundle-mcp-server.mjs", "bundle-mcp": "node scripts/bundle-mcp-server.mjs",
"docs:dev": "vitepress dev site --host 127.0.0.1", "docs:dev": "vitepress dev site --host 127.0.0.1",

View file

@ -112,7 +112,8 @@ assertFlatStringCatalog('en', sourceCatalog)
const sourceKeys = Object.keys(sourceCatalog).sort() const sourceKeys = Object.keys(sourceCatalog).sort()
ensureDirectory(localesDir) ensureDirectory(localesDir)
const localeFiles = directoryFiles(localesDir).filter((file) => file.endsWith('.json')) const localeFiles = directoryFiles(localesDir)
.filter((file) => file.endsWith('.json') && !file.startsWith('._'))
const issues = [] const issues = []
for (const file of localeFiles) { for (const file of localeFiles) {

View file

@ -1436,6 +1436,7 @@ function MainApp({
telemetryConsent: settings.telemetry_consent, telemetryConsent: settings.telemetry_consent,
vaultIsLoading: vault.isLoading, vaultIsLoading: vault.isLoading,
vaultSwitcher, vaultSwitcher,
worldEntryVisible: showHoloLakeHome,
}) })
const deepLinks = useDeepLinks({ const deepLinks = useDeepLinks({
activeEntry: activeTab?.entry ?? null, activeEntry: activeTab?.entry ?? null,

View file

@ -19,11 +19,6 @@ const installLinkTargets = [
['ai-agents-onboarding-install-claude_code', 'https://docs.anthropic.com/en/docs/claude-code'], ['ai-agents-onboarding-install-claude_code', 'https://docs.anthropic.com/en/docs/claude-code'],
['ai-agents-onboarding-install-codex', 'https://developers.openai.com/codex/cli'], ['ai-agents-onboarding-install-codex', 'https://developers.openai.com/codex/cli'],
['ai-agents-onboarding-install-copilot', 'https://docs.github.com/en/copilot/how-tos/copilot-cli/set-up-copilot-cli/install-copilot-cli'], ['ai-agents-onboarding-install-copilot', 'https://docs.github.com/en/copilot/how-tos/copilot-cli/set-up-copilot-cli/install-copilot-cli'],
['ai-agents-onboarding-install-opencode', 'https://opencode.ai/docs/'],
['ai-agents-onboarding-install-pi', 'https://pi.dev'],
['ai-agents-onboarding-install-antigravity', 'https://antigravity.google/docs/cli/install'],
['ai-agents-onboarding-install-kiro', 'https://kiro.dev/docs/cli'],
['ai-agents-onboarding-install-hermes', 'https://hermes-agent.nousresearch.com/docs/getting-started/quickstart'],
] as const ] as const
vi.mock('../utils/url', () => ({ vi.mock('../utils/url', () => ({

View file

@ -0,0 +1,30 @@
.education-work-lake { display: grid; gap: 20px; width: min(1160px, 100%); margin: 0 auto; padding-bottom: 48px; }
.education-work-lake__header { display: flex; align-items: end; justify-content: space-between; gap: 28px; padding: 24px 0 8px; }
.education-work-lake__header p { margin: 0 0 8px; color: var(--accent-blue); font: 11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: .12em; }
.education-work-lake__header h1 { margin: 0; font-family: Georgia, "Songti SC", serif; font-size: clamp(34px, 5vw, 58px); font-weight: 500; letter-spacing: -.04em; }
.education-work-lake__header span { display: block; max-width: 720px; margin-top: 12px; color: var(--text-secondary); line-height: 1.7; }
.education-work-lake__header aside { min-width: 190px; padding: 14px 16px; border: 1px solid var(--border-default); border-radius: 10px; background: var(--surface-card); }
.education-work-lake__header aside i { display: inline-block; width: 7px; height: 7px; margin-right: 8px; border-radius: 50%; background: var(--accent-green); }
.education-work-lake__header aside strong, .education-work-lake__header aside small { display: block; margin-top: 4px; }
.education-work-lake__header aside small { color: var(--text-secondary); }
.education-command { display: grid; grid-template-columns: 1fr auto; gap: 10px; padding: 14px; border: 1px solid color-mix(in srgb, var(--accent-blue) 35%, var(--border-default)); border-radius: 12px; background: color-mix(in srgb, var(--surface-card) 92%, var(--accent-blue)); }
.education-command input { min-height: 42px; }
.education-receipt { display: flex; align-items: center; gap: 12px; min-height: 38px; padding: 8px 12px; border: 1px solid color-mix(in srgb, var(--accent-green) 36%, var(--border-default)); border-radius: 8px; color: var(--text-secondary); background: color-mix(in srgb, var(--surface-card) 94%, var(--accent-green)); }
.education-receipt code { color: var(--accent-green); }.education-receipt small { margin-left: auto; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.education-work-lake [data-slot="tabs-list"] { max-width: 100%; overflow-x: auto; }
.education-work-lake [data-slot="tabs-content"] { min-height: 430px; margin-top: 12px; padding: 18px; border: 1px solid var(--border-default); border-radius: 12px; background: var(--surface-card); }
.education-document { min-height: 390px; resize: vertical; font: 16px/1.8 ui-serif, "Songti SC", serif; }
.education-sheet { height: 430px; overflow: hidden; border-radius: 8px; }
.education-chart { display: grid; gap: 18px; padding: 24px 16px; }
.education-chart__row { display: grid; grid-template-columns: 64px minmax(140px, 1fr) 42px; align-items: center; gap: 12px; }
.education-chart__row > div { height: 18px; overflow: hidden; border-radius: 999px; background: color-mix(in srgb, var(--surface-muted) 82%, transparent); }
.education-chart__row i { display: block; height: 100%; border-radius: inherit; background: linear-gradient(90deg, var(--accent-blue), #8b7cf6); }
.education-dashboard { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
.education-dashboard > article { padding: 20px; border: 1px solid var(--border-default); border-radius: 10px; background: var(--surface-app); }
.education-dashboard article span, .education-dashboard article small { display: block; color: var(--text-secondary); }
.education-dashboard article strong { display: block; margin: 12px 0 8px; font-size: 30px; }
.education-dashboard__chart { grid-column: 1 / -1; }
.education-knowledge { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
.education-knowledge article { padding: 22px; border: 1px solid var(--border-default); border-radius: 10px; background: var(--surface-app); }
.education-knowledge code, .education-knowledge strong { display: block; }.education-knowledge code { color: var(--accent-blue); font-size: 11px; }.education-knowledge strong { margin-top: 16px; font-size: 17px; }.education-knowledge p { color: var(--text-secondary); line-height: 1.7; }
@media (max-width: 760px) { .education-work-lake__header { align-items: stretch; flex-direction: column; }.education-work-lake__header aside { min-width: 0; }.education-command { grid-template-columns: 1fr; }.education-dashboard, .education-knowledge { grid-template-columns: 1fr; }.education-dashboard__chart { grid-column: auto; }.education-receipt { align-items: flex-start; flex-direction: column; }.education-receipt small { margin-left: 0; } }

View file

@ -0,0 +1,45 @@
import { fireEvent, render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { EducationWorkLake } from './EducationWorkLake'
vi.mock('./SheetEditor', () => ({
SheetEditor: () => <div data-testid="education-sheet-editor">sheet</div>,
}))
describe('EducationWorkLake', () => {
beforeEach(() => {
localStorage.clear()
})
it('opens an editable sheet from a language instruction and emits a receipt', () => {
render(<EducationWorkLake locale="zh-CN" />)
fireEvent.change(screen.getByLabelText('告诉人格体你要处理什么'), {
target: { value: '整理一下这个表格里面的数据' },
})
fireEvent.click(screen.getByRole('button', { name: '运行指令' }))
expect(screen.getByTestId('education-sheet-editor')).toBeInTheDocument()
expect(screen.getByText('HL-MOD-SHEET-001')).toBeInTheDocument()
expect(screen.getByText(/已在应用内打开/)).toBeInTheDocument()
})
it('keeps a document draft inside the browser workspace', () => {
const { unmount } = render(<EducationWorkLake locale="zh-CN" />)
fireEvent.change(screen.getByLabelText('告诉人格体你要处理什么'), {
target: { value: '起草一份教研文档' },
})
fireEvent.click(screen.getByRole('button', { name: '运行指令' }))
fireEvent.change(screen.getByLabelText('教研文档内容'), {
target: { value: '八年级物理:光的折射' },
})
unmount()
render(<EducationWorkLake locale="zh-CN" />)
fireEvent.change(screen.getByLabelText('告诉人格体你要处理什么'), {
target: { value: '打开文档' },
})
fireEvent.click(screen.getByRole('button', { name: '运行指令' }))
expect(screen.getByLabelText('教研文档内容')).toHaveValue('八年级物理:光的折射')
})
})

View file

@ -0,0 +1,185 @@
import { useMemo, useState } from 'react'
import type { AppLocale, TranslationKey } from '../lib/i18n'
import { translate } from '../lib/i18n'
import {
educationMetricsFromSheet,
routeEducationInstruction,
type EducationSurface,
} from '../lib/educationWorkLake'
import { trackEvent } from '../lib/telemetry'
import { Button } from './ui/button'
import { Input } from './ui/input'
import { Textarea } from './ui/textarea'
import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs'
import { SheetEditor } from './SheetEditor'
import './EducationWorkLake.css'
const DOCUMENT_STORAGE_KEY = 'hololake:education-work-lake:document'
const SHEET_STORAGE_KEY = 'hololake:education-work-lake:sheet'
const DEFAULT_DOCUMENT = '教研主题:八年级物理学情分析\n\n在这里编辑教案、报告或协作文档。'
const DEFAULT_SHEET = [
'---',
'type: Note',
'_display: sheet',
'---',
'班级,平均分',
'一班,86',
'二班,91',
'三班,79',
'四班,88',
].join('\n')
type EducationReceipt = {
capabilityId: string
id: string
}
function readWorkspaceValue(key: string, fallback: string): string {
try {
return localStorage.getItem(key) ?? fallback
} catch {
return fallback
}
}
function storeWorkspaceValue(key: string, value: string): void {
try {
localStorage.setItem(key, value)
} catch {
// The workspace remains editable in memory when browser storage is unavailable.
}
}
function ComparisonChart({ metrics }: { metrics: ReturnType<typeof educationMetricsFromSheet> }) {
const maximum = Math.max(1, ...metrics.map((metric) => metric.value))
return (
<div className="education-chart" role="img" aria-label="班级平均分对比图">
{metrics.map((metric) => (
<div className="education-chart__row" key={metric.label}>
<span>{metric.label}</span>
<div><i style={{ width: `${Math.max(4, metric.value / maximum * 100)}%` }} /></div>
<strong>{metric.value}</strong>
</div>
))}
</div>
)
}
function Dashboard({ metrics }: { metrics: ReturnType<typeof educationMetricsFromSheet> }) {
const total = metrics.reduce((sum, metric) => sum + metric.value, 0)
const average = metrics.length > 0 ? Math.round(total / metrics.length * 10) / 10 : 0
const highest = metrics.reduce((current, metric) =>
metric.value > current.value ? metric : current, { label: '—', value: 0 })
return (
<div className="education-dashboard">
<article><span></span><strong>{metrics.length}</strong><small></small></article>
<article><span></span><strong>{average}</strong><small></small></article>
<article><span></span><strong>{highest.label}</strong><small>{highest.value} </small></article>
<div className="education-dashboard__chart"><ComparisonChart metrics={metrics} /></div>
</div>
)
}
function KnowledgeSurface() {
return (
<div className="education-knowledge">
<article><code>EDU-KNOW-001</code><strong></strong><p></p></article>
<article><code>EDU-KNOW-002</code><strong></strong><p></p></article>
<article><code>EDU-KNOW-003</code><strong></strong><p></p></article>
</div>
)
}
export function EducationWorkLake({ locale }: { locale: AppLocale }) {
const t = (key: TranslationKey) => translate(locale, key)
const [instruction, setInstruction] = useState('')
const [surface, setSurface] = useState<EducationSurface>('knowledge')
const [receipt, setReceipt] = useState<EducationReceipt | null>(null)
const [document, setDocument] = useState(() => readWorkspaceValue(DOCUMENT_STORAGE_KEY, DEFAULT_DOCUMENT))
const [sheet, setSheet] = useState(() => readWorkspaceValue(SHEET_STORAGE_KEY, DEFAULT_SHEET))
const metrics = useMemo(() => educationMetricsFromSheet(sheet), [sheet])
const runInstruction = () => {
const route = routeEducationInstruction(instruction)
const receiptId = `EDU-LOCAL-${Date.now().toString(36).toUpperCase()}`
setSurface(route.surface)
setReceipt({ capabilityId: route.capabilityId, id: receiptId })
trackEvent('education_work_lake_instruction_routed', {
capabilityId: route.capabilityId,
reason: route.reason,
surface: route.surface,
})
}
const updateDocument = (value: string) => {
setDocument(value)
storeWorkspaceValue(DOCUMENT_STORAGE_KEY, value)
}
const updateSheet = (_path: string, value: string) => {
setSheet(value)
storeWorkspaceValue(SHEET_STORAGE_KEY, value)
}
return (
<section className="education-work-lake">
<header className="education-work-lake__header">
<div>
<p>EDU-WORK-LAKE-001 · REMOTE FIRST</p>
<h1>{t('education.title')}</h1>
<span>{t('education.description')}</span>
</div>
<aside><i /><strong>JD-FD-PRIMARY</strong><small>{t('education.hostingState')}</small></aside>
</header>
<div className="education-command">
<Input
aria-label={t('education.instructionLabel')}
onChange={(event) => setInstruction(event.target.value)}
placeholder={t('education.instructionPlaceholder')}
value={instruction}
/>
<Button onClick={runInstruction}>{t('education.runInstruction')}</Button>
</div>
{receipt && (
<div className="education-receipt" role="status">
<span>{t('education.openedInsideApp')}</span>
<code>{receipt.capabilityId}</code>
<small>{receipt.id}</small>
</div>
)}
<Tabs value={surface} onValueChange={(value) => setSurface(value as EducationSurface)}>
<TabsList aria-label={t('education.surfaceNavigation')}>
<TabsTrigger value="document">{t('education.document')}</TabsTrigger>
<TabsTrigger value="sheet">{t('education.sheet')}</TabsTrigger>
<TabsTrigger value="chart">{t('education.chart')}</TabsTrigger>
<TabsTrigger value="dashboard">{t('education.dashboard')}</TabsTrigger>
<TabsTrigger value="knowledge">{t('education.knowledge')}</TabsTrigger>
</TabsList>
<TabsContent value="document">
<Textarea
aria-label={t('education.documentLabel')}
className="education-document"
onChange={(event) => updateDocument(event.target.value)}
value={document}
/>
</TabsContent>
<TabsContent value="sheet">
<div className="education-sheet">
<SheetEditor
content={sheet}
locale={locale}
onContentChange={updateSheet}
path="education-work-lake/class-comparison.md"
/>
</div>
</TabsContent>
<TabsContent value="chart"><ComparisonChart metrics={metrics} /></TabsContent>
<TabsContent value="dashboard"><Dashboard metrics={metrics} /></TabsContent>
<TabsContent value="knowledge"><KnowledgeSurface /></TabsContent>
</Tabs>
</section>
)
}

View file

@ -22,11 +22,13 @@ import { GUANGHU_THEMES, type GuanghuTheme } from '../lib/guanghuTheme'
import { planGuanghuLivingSystem } from '../utils/planGuanghuLivingSystem' import { planGuanghuLivingSystem } from '../utils/planGuanghuLivingSystem'
import { Button } from './ui/button' import { Button } from './ui/button'
import { HOLOLAKE_REPOSITORY_URL } from '../constants/feedback' import { HOLOLAKE_REPOSITORY_URL } from '../constants/feedback'
import { isEducationWorkLakePath } from '../lib/educationWorkLake'
import { GuanghuRouterConsole } from './GuanghuRouterConsole' import { GuanghuRouterConsole } from './GuanghuRouterConsole'
import { GuanghuWorldMap } from './GuanghuWorldMap' import { GuanghuWorldMap } from './GuanghuWorldMap'
import { GuanghuWorldLoginGate } from './GuanghuWorldLoginGate' import { GuanghuWorldLoginGate } from './GuanghuWorldLoginGate'
import { FifthDomainSystems } from './FifthDomainSystems' import { FifthDomainSystems } from './FifthDomainSystems'
import { EternalLakeHeartPage } from './EternalLakeHeartPage' import { EternalLakeHeartPage } from './EternalLakeHeartPage'
import { EducationWorkLake } from './EducationWorkLake'
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@ -89,14 +91,17 @@ export function HoloLakeHome({
onOpenLocalWorkspace, onOpenLocalWorkspace,
livingSystemTarget, livingSystemTarget,
}: HoloLakeHomeProps) { }: HoloLakeHomeProps) {
const [route, setRoute] = useState<ChannelRoute>('world') const initialRoute: ChannelRoute = isEducationWorkLakePath(window.location.pathname)
? 'education-work-lake'
: 'world'
const [route, setRoute] = useState<ChannelRoute>(initialRoute)
const [livingPlan, setLivingPlan] = useState<GuanghuLivingSystemPlan>(() => ( const [livingPlan, setLivingPlan] = useState<GuanghuLivingSystemPlan>(() => (
createDeterministicLivingSystemPlan(createLivingSystemEvent({ createDeterministicLivingSystemPlan(createLivingSystemEvent({
currentRoute: 'world', currentRoute: initialRoute,
eventId: 'system-start', eventId: 'system-start',
intent: 'navigate', intent: 'navigate',
receiptIds: [], receiptIds: [],
requestedRoute: 'world', requestedRoute: initialRoute,
worldOpen: false, worldOpen: false,
})) }))
)) ))
@ -407,6 +412,10 @@ export function HoloLakeHome({
) )
} }
if (route === 'education-work-lake') {
return <EducationWorkLake locale={locale} />
}
if (route === 'servers') { if (route === 'servers') {
return ( return (
<section className="channel-stage channel-stage--node"> <section className="channel-stage channel-stage--node">
@ -453,6 +462,7 @@ export function HoloLakeHome({
<p className="channel-stage__lead">{t('hololake.channel.heartbeatDescription')}</p> <p className="channel-stage__lead">{t('hololake.channel.heartbeatDescription')}</p>
<div className="channel-grid channel-grid--three"> <div className="channel-grid channel-grid--three">
<RouteCard eyebrow="PERSONA · AI AGENT" title="人格体工作舱" description="与当前频道的人格体持续协作;保留多 Agent、模型选择、权限模式、工具调用、执行回执与历史会话。" action="打开人格体工作舱" onOpen={onOpenAiWorkspace ? openAiWorkspace : undefined} /> <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="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="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="MODULE · VIDEO AI" title={t('hololake.channel.videoAiTitle')} description={t('hololake.channel.videoAiDescription')} status={t('hololake.channel.prototypeMounted')} />
@ -492,6 +502,7 @@ export function HoloLakeHome({
<Button data-active={route === 'world'} variant="ghost" onClick={() => navigate('world')}><i data-color="cyan" /></Button> <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 variant="ghost" onClick={openKnowledgeBase}><i data-color="blue" /></Button>
<Button disabled={!onOpenAiWorkspace} variant="ghost" onClick={openAiWorkspace}><i data-color="violet" />Agent </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> <Button disabled={!onOpenLocalWorkspace} variant="ghost" onClick={openLocalWorkspace}><i data-color="amber" /></Button>
</>} </>}
{fifthDomainOpen && <> {fifthDomainOpen && <>

View file

@ -57,11 +57,6 @@ describe('useAiAgentsStatus', () => {
expect(result.current.claude_code).toEqual({ status: 'installed', version: '1.0.20' }) expect(result.current.claude_code).toEqual({ status: 'installed', version: '1.0.20' })
expect(result.current.codex).toEqual({ status: 'missing', version: null }) expect(result.current.codex).toEqual({ status: 'missing', version: null })
expect(result.current.copilot).toEqual({ status: 'installed', version: '1.0.58' }) expect(result.current.copilot).toEqual({ status: 'installed', version: '1.0.58' })
expect(result.current.opencode).toEqual({ status: 'installed', version: '0.3.1' })
expect(result.current.pi).toEqual({ status: 'installed', version: '0.70.2' })
expect(result.current.antigravity).toEqual({ status: 'installed', version: 'Antigravity CLI 1.0.0' })
expect(result.current.kiro).toEqual({ status: 'installed', version: '0.4.0' })
expect(result.current.hermes).toEqual({ status: 'installed', version: 'Hermes Agent 0.16.0' })
}) })
}) })

View file

@ -1085,9 +1085,9 @@ describe('reload-vault command', () => {
expect(cmd).toBeDefined() expect(cmd).toBeDefined()
expect(cmd!.label).toBe('Switch AI Agent to Codex') expect(cmd!.label).toBe('Switch AI Agent to Codex')
expect(findCommand(result.current, 'switch-ai-agent-copilot')).toBeDefined() expect(findCommand(result.current, 'switch-ai-agent-copilot')).toBeDefined()
expect(findCommand(result.current, 'switch-ai-agent-opencode')).toBeDefined() expect(findCommand(result.current, 'switch-ai-agent-opencode')).toBeUndefined()
expect(findCommand(result.current, 'switch-ai-agent-pi')).toBeDefined() expect(findCommand(result.current, 'switch-ai-agent-pi')).toBeUndefined()
expect(findCommand(result.current, 'switch-ai-agent-antigravity')).toBeDefined() expect(findCommand(result.current, 'switch-ai-agent-antigravity')).toBeUndefined()
cmd!.execute() cmd!.execute()
expect(onSetDefaultAiAgent).toHaveBeenCalledWith('codex') expect(onSetDefaultAiAgent).toHaveBeenCalledWith('codex')

View file

@ -0,0 +1,27 @@
import { renderHook } from '@testing-library/react'
import { describe, expect, it } from 'vitest'
import { useStartupScreenState } from './useStartupScreenState'
describe('useStartupScreenState', () => {
it('does not let local vault onboarding replace the HoloLake world entry', () => {
const { result } = renderHook(() => useStartupScreenState({
aiAgentsPromptVisible: false,
isNoteWindow: false,
onboardingState: { status: 'welcome' },
runtimeMissingVaultPath: '/blocked/config/path',
selectedVaultPath: null,
settingsLoaded: true,
showMcpSetupDialog: false,
telemetryConsent: null,
vaultIsLoading: false,
vaultSwitcher: {
allVaults: [],
loaded: true,
vaultPath: '',
},
worldEntryVisible: true,
}))
expect(result.current.shouldShowStartupScreen).toBe(false)
})
})

View file

@ -22,6 +22,7 @@ interface UseStartupScreenStateArgs {
telemetryConsent: boolean | null telemetryConsent: boolean | null
vaultIsLoading: boolean vaultIsLoading: boolean
vaultSwitcher: StartupVaultSwitcherState vaultSwitcher: StartupVaultSwitcherState
worldEntryVisible?: boolean
} }
interface StartupScreenState { interface StartupScreenState {
@ -122,6 +123,7 @@ export function useStartupScreenState({
telemetryConsent, telemetryConsent,
vaultIsLoading, vaultIsLoading,
vaultSwitcher, vaultSwitcher,
worldEntryVisible = false,
}: UseStartupScreenStateArgs): StartupScreenState { }: UseStartupScreenStateArgs): StartupScreenState {
const shouldResumeFreshStartOnboarding = useMemo( const shouldResumeFreshStartOnboarding = useMemo(
() => shouldResumeFreshStart(onboardingState, selectedVaultPath, vaultSwitcher), () => shouldResumeFreshStart(onboardingState, selectedVaultPath, vaultSwitcher),
@ -129,7 +131,7 @@ export function useStartupScreenState({
) )
const isStartupLoading = !isNoteWindow && onboardingState.status === 'loading' const isStartupLoading = !isNoteWindow && onboardingState.status === 'loading'
const shouldShowStartupScreen = shouldShowStartupScreenForState({ const shouldShowStartupScreen = !worldEntryVisible && shouldShowStartupScreenForState({
aiAgentsPromptVisible, aiAgentsPromptVisible,
isNoteWindow, isNoteWindow,
isStartupLoading, isStartupLoading,

View file

@ -11,12 +11,12 @@ describe('aiAgents helpers', () => {
expect(normalizeStoredAiAgent('claude_code')).toBe('claude_code') expect(normalizeStoredAiAgent('claude_code')).toBe('claude_code')
expect(normalizeStoredAiAgent('codex')).toBe('codex') expect(normalizeStoredAiAgent('codex')).toBe('codex')
expect(normalizeStoredAiAgent('copilot')).toBe('copilot') expect(normalizeStoredAiAgent('copilot')).toBe('copilot')
expect(normalizeStoredAiAgent('opencode')).toBe('opencode') expect(normalizeStoredAiAgent('opencode')).toBeNull()
expect(normalizeStoredAiAgent('pi')).toBe('pi') expect(normalizeStoredAiAgent('pi')).toBeNull()
expect(normalizeStoredAiAgent('antigravity')).toBe('antigravity') expect(normalizeStoredAiAgent('antigravity')).toBeNull()
expect(normalizeStoredAiAgent('gemini')).toBe('antigravity') expect(normalizeStoredAiAgent('gemini')).toBeNull()
expect(normalizeStoredAiAgent('kiro')).toBe('kiro') expect(normalizeStoredAiAgent('kiro')).toBeNull()
expect(normalizeStoredAiAgent('hermes')).toBe('hermes') expect(normalizeStoredAiAgent('hermes')).toBeNull()
expect(normalizeStoredAiAgent('cursor')).toBeNull() expect(normalizeStoredAiAgent('cursor')).toBeNull()
}) })
@ -40,29 +40,25 @@ describe('aiAgents helpers', () => {
expect(statuses.claude_code).toEqual({ status: 'installed', version: '1.0.20' }) expect(statuses.claude_code).toEqual({ status: 'installed', version: '1.0.20' })
expect(statuses.codex).toEqual({ status: 'missing', version: null }) expect(statuses.codex).toEqual({ status: 'missing', version: null })
expect(statuses.copilot).toEqual({ status: 'installed', version: '1.0.58' }) expect(statuses.copilot).toEqual({ status: 'installed', version: '1.0.58' })
expect(statuses.opencode).toEqual({ status: 'installed', version: '0.3.1' }) expect(statuses.opencode).toBeUndefined()
expect(statuses.pi).toEqual({ status: 'installed', version: '0.70.2' }) expect(statuses.pi).toBeUndefined()
expect(statuses.antigravity).toEqual({ status: 'installed', version: 'Antigravity CLI 1.0.0' }) expect(statuses.antigravity).toBeUndefined()
expect(statuses.kiro).toEqual({ status: 'installed', version: '0.12.0' }) expect(statuses.kiro).toBeUndefined()
expect(statuses.hermes).toEqual({ status: 'installed', version: 'Hermes Agent 0.16.0' }) expect(statuses.hermes).toBeUndefined()
}) })
it('normalizes legacy Gemini status payloads to Antigravity', () => { it('does not expose legacy Gemini status payloads', () => {
const statuses = normalizeAiAgentsStatus({ const statuses = normalizeAiAgentsStatus({
gemini: { installed: true, version: '0.5.1' }, gemini: { installed: true, version: '0.5.1' },
}) })
expect(statuses.antigravity).toEqual({ status: 'installed', version: '0.5.1' }) expect(statuses.antigravity).toBeUndefined()
}) })
it('cycles through the supported agents', () => { it('cycles through the supported agents', () => {
expect(getNextAiAgentId('claude_code')).toBe('codex') expect(getNextAiAgentId('claude_code')).toBe('codex')
expect(getNextAiAgentId('codex')).toBe('copilot') expect(getNextAiAgentId('codex')).toBe('copilot')
expect(getNextAiAgentId('copilot')).toBe('opencode') expect(getNextAiAgentId('copilot')).toBe('claude_code')
expect(getNextAiAgentId('opencode')).toBe('pi') expect(getNextAiAgentId('opencode')).toBe('claude_code')
expect(getNextAiAgentId('pi')).toBe('antigravity')
expect(getNextAiAgentId('antigravity')).toBe('kiro')
expect(getNextAiAgentId('kiro')).toBe('hermes')
expect(getNextAiAgentId('hermes')).toBe('claude_code')
}) })
}) })

View file

@ -40,37 +40,40 @@ export const AI_AGENT_DEFINITIONS: readonly AiAgentDefinition[] = [
shortLabel: 'Copilot', shortLabel: 'Copilot',
installUrl: 'https://docs.github.com/en/copilot/how-tos/copilot-cli/set-up-copilot-cli/install-copilot-cli', installUrl: 'https://docs.github.com/en/copilot/how-tos/copilot-cli/set-up-copilot-cli/install-copilot-cli',
}, },
{ ] as const
const LEGACY_AI_AGENT_DEFINITIONS: Partial<Record<AiAgentId, AiAgentDefinition>> = {
opencode: {
id: 'opencode', id: 'opencode',
label: 'OpenCode', label: 'OpenCode',
shortLabel: 'OpenCode', shortLabel: 'OpenCode',
installUrl: 'https://opencode.ai/docs/', installUrl: 'https://opencode.ai/docs/',
}, },
{ pi: {
id: 'pi', id: 'pi',
label: 'Pi', label: 'Pi',
shortLabel: 'Pi', shortLabel: 'Pi',
installUrl: 'https://pi.dev', installUrl: 'https://pi.dev',
}, },
{ antigravity: {
id: 'antigravity', id: 'antigravity',
label: 'Antigravity CLI', label: 'Antigravity',
shortLabel: 'Antigravity', shortLabel: 'Antigravity',
installUrl: 'https://antigravity.google/docs/cli/install', installUrl: 'https://antigravity.google/docs/cli/install',
}, },
{ kiro: {
id: 'kiro', id: 'kiro',
label: 'Kiro', label: 'Kiro',
shortLabel: 'Kiro', shortLabel: 'Kiro',
installUrl: 'https://kiro.dev/docs/cli', installUrl: 'https://kiro.dev/docs/cli',
}, },
{ hermes: {
id: 'hermes', id: 'hermes',
label: 'Hermes Agent', label: 'Hermes Agent',
shortLabel: 'Hermes', shortLabel: 'Hermes',
installUrl: 'https://hermes-agent.nousresearch.com/docs/getting-started/quickstart', installUrl: 'https://hermes-agent.nousresearch.com/docs/getting-started/quickstart',
}, },
] as const }
export function createAiAgentAvailability(status: AiAgentStatus = 'checking', version: string | null = null): AiAgentAvailability { export function createAiAgentAvailability(status: AiAgentStatus = 'checking', version: string | null = null): AiAgentAvailability {
return { status, version } return { status, version }
@ -94,7 +97,6 @@ export function createMissingAiAgentsStatus(): AiAgentsStatus {
} }
export function normalizeStoredAiAgent(value: string | null | undefined): AiAgentId | null { export function normalizeStoredAiAgent(value: string | null | undefined): AiAgentId | null {
if (value === 'gemini') return 'antigravity'
if (AI_AGENT_DEFINITIONS.some((definition) => definition.id === value)) return value as AiAgentId if (AI_AGENT_DEFINITIONS.some((definition) => definition.id === value)) return value as AiAgentId
return null return null
} }
@ -104,7 +106,9 @@ export function resolveDefaultAiAgent(value: string | null | undefined): AiAgent
} }
export function getAiAgentDefinition(agent: AiAgentId): AiAgentDefinition { export function getAiAgentDefinition(agent: AiAgentId): AiAgentDefinition {
return AI_AGENT_DEFINITIONS.find((definition) => definition.id === agent) ?? AI_AGENT_DEFINITIONS[0] return AI_AGENT_DEFINITIONS.find((definition) => definition.id === agent)
?? LEGACY_AI_AGENT_DEFINITIONS[agent]
?? AI_AGENT_DEFINITIONS[0]
} }
function normalizeAvailability(agent: { installed?: boolean | null; version?: string | null } | null | undefined): AiAgentAvailability { function normalizeAvailability(agent: { installed?: boolean | null; version?: string | null } | null | undefined): AiAgentAvailability {
@ -116,7 +120,7 @@ function normalizeAvailability(agent: { installed?: boolean | null; version?: st
} }
function payloadForAgent(payload: AiAgentsStatusPayload | null | undefined, agent: AiAgentId) { function payloadForAgent(payload: AiAgentsStatusPayload | null | undefined, agent: AiAgentId) {
return agent === 'antigravity' ? payload?.antigravity ?? payload?.gemini : payload?.[agent] return payload?.[agent]
} }
export function normalizeAiAgentsStatus(payload: AiAgentsStatusPayload | null | undefined): AiAgentsStatus { export function normalizeAiAgentsStatus(payload: AiAgentsStatusPayload | null | undefined): AiAgentsStatus {

View file

@ -59,7 +59,7 @@ describe('ai target provider contract', () => {
}) })
}) })
it('accepts legacy agent ids saved in the default target field', () => { it('falls back safely when the default target contains a hidden legacy agent', () => {
const target = resolveAiTarget({ const target = resolveAiTarget({
default_ai_agent: 'claude_code', default_ai_agent: 'claude_code',
default_ai_target: 'kiro', default_ai_target: 'kiro',
@ -67,12 +67,12 @@ describe('ai target provider contract', () => {
expect(target).toMatchObject({ expect(target).toMatchObject({
kind: 'agent', kind: 'agent',
agent: 'kiro', agent: 'claude_code',
id: 'agent:kiro', id: 'agent:claude_code',
}) })
}) })
it('uses the legacy default agent when a saved agent target is stale', () => { it('falls back safely when the saved legacy default agent is no longer selectable', () => {
const target = resolveAiTarget({ const target = resolveAiTarget({
default_ai_agent: 'kiro', default_ai_agent: 'kiro',
default_ai_target: 'agent:claude_code', default_ai_target: 'agent:claude_code',
@ -80,8 +80,8 @@ describe('ai target provider contract', () => {
expect(target).toMatchObject({ expect(target).toMatchObject({
kind: 'agent', kind: 'agent',
agent: 'kiro', agent: 'claude_code',
id: 'agent:kiro', id: 'agent:claude_code',
}) })
}) })

View file

@ -0,0 +1,46 @@
import { describe, expect, it } from 'vitest'
import {
educationMetricsFromSheet,
isEducationWorkLakePath,
routeEducationInstruction,
} from './educationWorkLake'
describe('routeEducationInstruction', () => {
it.each([
['整理一下这个表格里面的数据', 'sheet'],
['对比一下并做一个对比图', 'chart'],
['把结果做成仪表盘', 'dashboard'],
['起草一份教研文档', 'document'],
['找一下课程知识库', 'knowledge'],
] as const)('routes %s to %s', (instruction, expectedSurface) => {
expect(routeEducationInstruction(instruction).surface).toBe(expectedSurface)
})
it('returns a stable safe fallback without claiming execution', () => {
expect(routeEducationInstruction('帮我看看')).toMatchObject({
surface: 'knowledge',
capabilityId: 'HL-MOD-KNOWLEDGE-001',
reason: 'safe-fallback',
})
})
it('prioritizes dashboards over their underlying chart keyword', () => {
expect(routeEducationInstruction('把对比图放进仪表盘')).toMatchObject({
surface: 'dashboard',
capabilityId: 'HL-MOD-DASHBOARD-001',
})
})
it('projects numeric spreadsheet rows into chart metrics', () => {
expect(educationMetricsFromSheet('---\n_display: sheet\n---\n班级,平均分\n一班,86\n二班,91\n备注,待补')).toEqual([
{ label: '一班', value: 86 },
{ label: '二班', value: 91 },
])
})
it('recognizes the server-hosted education entry without matching similar words', () => {
expect(isEducationWorkLakePath('/jd/education/')).toBe(true)
expect(isEducationWorkLakePath('/jd/education/assets/app.js')).toBe(true)
expect(isEducationWorkLakePath('/jd/educational/')).toBe(false)
})
})

View file

@ -0,0 +1,92 @@
export const EDUCATION_SURFACES = [
'document',
'sheet',
'chart',
'dashboard',
'knowledge',
] as const
export type EducationSurface = typeof EDUCATION_SURFACES[number]
export type EducationInstructionRoute = {
capabilityId: string
reason: 'keyword-match' | 'safe-fallback'
surface: EducationSurface
}
export type EducationMetric = {
label: string
value: number
}
const ROUTES: ReadonlyArray<{
capabilityId: string
keywords: readonly string[]
surface: EducationSurface
}> = [
{
capabilityId: 'HL-MOD-DASHBOARD-001',
keywords: ['仪表盘', '看板', 'dashboard'],
surface: 'dashboard',
},
{
capabilityId: 'HL-MOD-CHART-001',
keywords: ['对比图', '图表', '柱状图', '折线图', 'chart'],
surface: 'chart',
},
{
capabilityId: 'HL-MOD-SHEET-001',
keywords: ['表格', '数据', '计算', '统计', 'sheet', 'spreadsheet'],
surface: 'sheet',
},
{
capabilityId: 'HL-MOD-DOC-001',
keywords: ['文档', '教案', '报告', '总结', '起草', 'document'],
surface: 'document',
},
{
capabilityId: 'HL-MOD-KNOWLEDGE-001',
keywords: ['知识库', '课程', '资料', '搜索', '找', 'knowledge'],
surface: 'knowledge',
},
]
export function routeEducationInstruction(instruction: string): EducationInstructionRoute {
const normalizedInstruction = instruction.trim().toLocaleLowerCase()
const route = ROUTES.find((candidate) =>
candidate.keywords.some((keyword) => normalizedInstruction.includes(keyword)))
if (route) {
return {
capabilityId: route.capabilityId,
reason: 'keyword-match',
surface: route.surface,
}
}
return {
capabilityId: 'HL-MOD-KNOWLEDGE-001',
reason: 'safe-fallback',
surface: 'knowledge',
}
}
export function educationMetricsFromSheet(content: string): EducationMetric[] {
const body = content.includes('---')
? content.split('---').slice(2).join('---')
: content
return body
.trim()
.split(/\r?\n/)
.slice(1)
.map((row) => {
const [label = '', value = ''] = row.split(',')
return { label: label.trim(), value: Number(value.trim()) }
})
.filter((metric) => metric.label.length > 0 && Number.isFinite(metric.value))
}
export function isEducationWorkLakePath(pathname: string): boolean {
return pathname.split('/').filter(Boolean).includes('education')
}

View file

@ -9,6 +9,7 @@ export const GUANGHU_CHANNEL_ROUTES = [
'eternal-lake-heart', 'eternal-lake-heart',
'light-lake', 'light-lake',
'heartbeat-core', 'heartbeat-core',
'education-work-lake',
'love-core', 'love-core',
'servers', 'servers',
] as const ] as const

View file

@ -1121,5 +1121,19 @@
"hololake.login.errorTitle": "The world entrance did not open", "hololake.login.errorTitle": "The world entrance did not open",
"hololake.login.retry": "Try again", "hololake.login.retry": "Try again",
"hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.", "hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.",
"hololake.login.nodeReady": "Fifth Domain world entrance ready" "hololake.login.nodeReady": "Fifth Domain world entrance ready",
"education.title": "Education Work Lake",
"education.description": "A client-rendered workspace where the persona can open and edit documents, spreadsheets, charts, dashboards, and knowledge through language instructions.",
"education.hostingState": "Temporary isolated hosting",
"education.instructionLabel": "Tell the persona what you want to work on",
"education.instructionPlaceholder": "For example: organize this spreadsheet and create a comparison dashboard",
"education.runInstruction": "Run instruction",
"education.openedInsideApp": "Opened inside the app",
"education.surfaceNavigation": "Education workspace surfaces",
"education.document": "Document",
"education.sheet": "Spreadsheet",
"education.chart": "Chart",
"education.dashboard": "Dashboard",
"education.knowledge": "Knowledge",
"education.documentLabel": "Education document content"
} }

View file

@ -1121,5 +1121,19 @@
"hololake.login.errorTitle": "The world entrance did not open", "hololake.login.errorTitle": "The world entrance did not open",
"hololake.login.retry": "Try again", "hololake.login.retry": "Try again",
"hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.", "hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.",
"hololake.login.nodeReady": "Fifth Domain world entrance ready" "hololake.login.nodeReady": "Fifth Domain world entrance ready",
"education.title": "Education Work Lake",
"education.description": "A client-rendered workspace where the persona can open and edit documents, spreadsheets, charts, dashboards, and knowledge through language instructions.",
"education.hostingState": "Temporary isolated hosting",
"education.instructionLabel": "Tell the persona what you want to work on",
"education.instructionPlaceholder": "For example: organize this spreadsheet and create a comparison dashboard",
"education.runInstruction": "Run instruction",
"education.openedInsideApp": "Opened inside the app",
"education.surfaceNavigation": "Education workspace surfaces",
"education.document": "Document",
"education.sheet": "Spreadsheet",
"education.chart": "Chart",
"education.dashboard": "Dashboard",
"education.knowledge": "Knowledge",
"education.documentLabel": "Education document content"
} }

View file

@ -1121,5 +1121,19 @@
"hololake.login.errorTitle": "The world entrance did not open", "hololake.login.errorTitle": "The world entrance did not open",
"hololake.login.retry": "Try again", "hololake.login.retry": "Try again",
"hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.", "hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.",
"hololake.login.nodeReady": "Fifth Domain world entrance ready" "hololake.login.nodeReady": "Fifth Domain world entrance ready",
"education.title": "Education Work Lake",
"education.description": "A client-rendered workspace where the persona can open and edit documents, spreadsheets, charts, dashboards, and knowledge through language instructions.",
"education.hostingState": "Temporary isolated hosting",
"education.instructionLabel": "Tell the persona what you want to work on",
"education.instructionPlaceholder": "For example: organize this spreadsheet and create a comparison dashboard",
"education.runInstruction": "Run instruction",
"education.openedInsideApp": "Opened inside the app",
"education.surfaceNavigation": "Education workspace surfaces",
"education.document": "Document",
"education.sheet": "Spreadsheet",
"education.chart": "Chart",
"education.dashboard": "Dashboard",
"education.knowledge": "Knowledge",
"education.documentLabel": "Education document content"
} }

View file

@ -1000,6 +1000,20 @@
"locale.ukUA": "Ukrainian", "locale.ukUA": "Ukrainian",
"locale.svSE": "Swedish", "locale.svSE": "Swedish",
"locale.skSK": "Slovak", "locale.skSK": "Slovak",
"education.title": "Education Work Lake",
"education.description": "A client-rendered workspace where the persona can open and edit documents, spreadsheets, charts, dashboards, and knowledge through language instructions.",
"education.hostingState": "Temporary isolated hosting",
"education.instructionLabel": "Tell the persona what you want to work on",
"education.instructionPlaceholder": "For example: organize this spreadsheet and create a comparison dashboard",
"education.runInstruction": "Run instruction",
"education.openedInsideApp": "Opened inside the app",
"education.surfaceNavigation": "Education workspace surfaces",
"education.document": "Document",
"education.sheet": "Spreadsheet",
"education.chart": "Chart",
"education.dashboard": "Dashboard",
"education.knowledge": "Knowledge",
"education.documentLabel": "Education document content",
"hololake.home.ariaLabel": "HoloLake Era world entry", "hololake.home.ariaLabel": "HoloLake Era world entry",
"hololake.home.eyebrow": "Guanghu Language World · Fifth Domain", "hololake.home.eyebrow": "Guanghu Language World · Fifth Domain",
"hololake.home.lead": "Language-personality-driven operating system", "hololake.home.lead": "Language-personality-driven operating system",

View file

@ -1121,5 +1121,19 @@
"hololake.login.errorTitle": "The world entrance did not open", "hololake.login.errorTitle": "The world entrance did not open",
"hololake.login.retry": "Try again", "hololake.login.retry": "Try again",
"hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.", "hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.",
"hololake.login.nodeReady": "Fifth Domain world entrance ready" "hololake.login.nodeReady": "Fifth Domain world entrance ready",
"education.title": "Education Work Lake",
"education.description": "A client-rendered workspace where the persona can open and edit documents, spreadsheets, charts, dashboards, and knowledge through language instructions.",
"education.hostingState": "Temporary isolated hosting",
"education.instructionLabel": "Tell the persona what you want to work on",
"education.instructionPlaceholder": "For example: organize this spreadsheet and create a comparison dashboard",
"education.runInstruction": "Run instruction",
"education.openedInsideApp": "Opened inside the app",
"education.surfaceNavigation": "Education workspace surfaces",
"education.document": "Document",
"education.sheet": "Spreadsheet",
"education.chart": "Chart",
"education.dashboard": "Dashboard",
"education.knowledge": "Knowledge",
"education.documentLabel": "Education document content"
} }

View file

@ -1121,5 +1121,19 @@
"hololake.login.errorTitle": "The world entrance did not open", "hololake.login.errorTitle": "The world entrance did not open",
"hololake.login.retry": "Try again", "hololake.login.retry": "Try again",
"hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.", "hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.",
"hololake.login.nodeReady": "Fifth Domain world entrance ready" "hololake.login.nodeReady": "Fifth Domain world entrance ready",
"education.title": "Education Work Lake",
"education.description": "A client-rendered workspace where the persona can open and edit documents, spreadsheets, charts, dashboards, and knowledge through language instructions.",
"education.hostingState": "Temporary isolated hosting",
"education.instructionLabel": "Tell the persona what you want to work on",
"education.instructionPlaceholder": "For example: organize this spreadsheet and create a comparison dashboard",
"education.runInstruction": "Run instruction",
"education.openedInsideApp": "Opened inside the app",
"education.surfaceNavigation": "Education workspace surfaces",
"education.document": "Document",
"education.sheet": "Spreadsheet",
"education.chart": "Chart",
"education.dashboard": "Dashboard",
"education.knowledge": "Knowledge",
"education.documentLabel": "Education document content"
} }

View file

@ -1121,5 +1121,19 @@
"hololake.login.errorTitle": "The world entrance did not open", "hololake.login.errorTitle": "The world entrance did not open",
"hololake.login.retry": "Try again", "hololake.login.retry": "Try again",
"hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.", "hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.",
"hololake.login.nodeReady": "Fifth Domain world entrance ready" "hololake.login.nodeReady": "Fifth Domain world entrance ready",
"education.title": "Education Work Lake",
"education.description": "A client-rendered workspace where the persona can open and edit documents, spreadsheets, charts, dashboards, and knowledge through language instructions.",
"education.hostingState": "Temporary isolated hosting",
"education.instructionLabel": "Tell the persona what you want to work on",
"education.instructionPlaceholder": "For example: organize this spreadsheet and create a comparison dashboard",
"education.runInstruction": "Run instruction",
"education.openedInsideApp": "Opened inside the app",
"education.surfaceNavigation": "Education workspace surfaces",
"education.document": "Document",
"education.sheet": "Spreadsheet",
"education.chart": "Chart",
"education.dashboard": "Dashboard",
"education.knowledge": "Knowledge",
"education.documentLabel": "Education document content"
} }

View file

@ -1121,5 +1121,19 @@
"hololake.login.errorTitle": "The world entrance did not open", "hololake.login.errorTitle": "The world entrance did not open",
"hololake.login.retry": "Try again", "hololake.login.retry": "Try again",
"hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.", "hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.",
"hololake.login.nodeReady": "Fifth Domain world entrance ready" "hololake.login.nodeReady": "Fifth Domain world entrance ready",
"education.title": "Education Work Lake",
"education.description": "A client-rendered workspace where the persona can open and edit documents, spreadsheets, charts, dashboards, and knowledge through language instructions.",
"education.hostingState": "Temporary isolated hosting",
"education.instructionLabel": "Tell the persona what you want to work on",
"education.instructionPlaceholder": "For example: organize this spreadsheet and create a comparison dashboard",
"education.runInstruction": "Run instruction",
"education.openedInsideApp": "Opened inside the app",
"education.surfaceNavigation": "Education workspace surfaces",
"education.document": "Document",
"education.sheet": "Spreadsheet",
"education.chart": "Chart",
"education.dashboard": "Dashboard",
"education.knowledge": "Knowledge",
"education.documentLabel": "Education document content"
} }

View file

@ -1121,5 +1121,19 @@
"hololake.login.errorTitle": "The world entrance did not open", "hololake.login.errorTitle": "The world entrance did not open",
"hololake.login.retry": "Try again", "hololake.login.retry": "Try again",
"hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.", "hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.",
"hololake.login.nodeReady": "Fifth Domain world entrance ready" "hololake.login.nodeReady": "Fifth Domain world entrance ready",
"education.title": "Education Work Lake",
"education.description": "A client-rendered workspace where the persona can open and edit documents, spreadsheets, charts, dashboards, and knowledge through language instructions.",
"education.hostingState": "Temporary isolated hosting",
"education.instructionLabel": "Tell the persona what you want to work on",
"education.instructionPlaceholder": "For example: organize this spreadsheet and create a comparison dashboard",
"education.runInstruction": "Run instruction",
"education.openedInsideApp": "Opened inside the app",
"education.surfaceNavigation": "Education workspace surfaces",
"education.document": "Document",
"education.sheet": "Spreadsheet",
"education.chart": "Chart",
"education.dashboard": "Dashboard",
"education.knowledge": "Knowledge",
"education.documentLabel": "Education document content"
} }

View file

@ -1121,5 +1121,19 @@
"hololake.login.errorTitle": "The world entrance did not open", "hololake.login.errorTitle": "The world entrance did not open",
"hololake.login.retry": "Try again", "hololake.login.retry": "Try again",
"hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.", "hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.",
"hololake.login.nodeReady": "Fifth Domain world entrance ready" "hololake.login.nodeReady": "Fifth Domain world entrance ready",
"education.title": "Education Work Lake",
"education.description": "A client-rendered workspace where the persona can open and edit documents, spreadsheets, charts, dashboards, and knowledge through language instructions.",
"education.hostingState": "Temporary isolated hosting",
"education.instructionLabel": "Tell the persona what you want to work on",
"education.instructionPlaceholder": "For example: organize this spreadsheet and create a comparison dashboard",
"education.runInstruction": "Run instruction",
"education.openedInsideApp": "Opened inside the app",
"education.surfaceNavigation": "Education workspace surfaces",
"education.document": "Document",
"education.sheet": "Spreadsheet",
"education.chart": "Chart",
"education.dashboard": "Dashboard",
"education.knowledge": "Knowledge",
"education.documentLabel": "Education document content"
} }

View file

@ -1121,5 +1121,19 @@
"hololake.login.errorTitle": "The world entrance did not open", "hololake.login.errorTitle": "The world entrance did not open",
"hololake.login.retry": "Try again", "hololake.login.retry": "Try again",
"hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.", "hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.",
"hololake.login.nodeReady": "Fifth Domain world entrance ready" "hololake.login.nodeReady": "Fifth Domain world entrance ready",
"education.title": "Education Work Lake",
"education.description": "A client-rendered workspace where the persona can open and edit documents, spreadsheets, charts, dashboards, and knowledge through language instructions.",
"education.hostingState": "Temporary isolated hosting",
"education.instructionLabel": "Tell the persona what you want to work on",
"education.instructionPlaceholder": "For example: organize this spreadsheet and create a comparison dashboard",
"education.runInstruction": "Run instruction",
"education.openedInsideApp": "Opened inside the app",
"education.surfaceNavigation": "Education workspace surfaces",
"education.document": "Document",
"education.sheet": "Spreadsheet",
"education.chart": "Chart",
"education.dashboard": "Dashboard",
"education.knowledge": "Knowledge",
"education.documentLabel": "Education document content"
} }

View file

@ -1121,5 +1121,19 @@
"hololake.login.errorTitle": "The world entrance did not open", "hololake.login.errorTitle": "The world entrance did not open",
"hololake.login.retry": "Try again", "hololake.login.retry": "Try again",
"hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.", "hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.",
"hololake.login.nodeReady": "Fifth Domain world entrance ready" "hololake.login.nodeReady": "Fifth Domain world entrance ready",
"education.title": "Education Work Lake",
"education.description": "A client-rendered workspace where the persona can open and edit documents, spreadsheets, charts, dashboards, and knowledge through language instructions.",
"education.hostingState": "Temporary isolated hosting",
"education.instructionLabel": "Tell the persona what you want to work on",
"education.instructionPlaceholder": "For example: organize this spreadsheet and create a comparison dashboard",
"education.runInstruction": "Run instruction",
"education.openedInsideApp": "Opened inside the app",
"education.surfaceNavigation": "Education workspace surfaces",
"education.document": "Document",
"education.sheet": "Spreadsheet",
"education.chart": "Chart",
"education.dashboard": "Dashboard",
"education.knowledge": "Knowledge",
"education.documentLabel": "Education document content"
} }

View file

@ -1121,5 +1121,19 @@
"hololake.login.errorTitle": "The world entrance did not open", "hololake.login.errorTitle": "The world entrance did not open",
"hololake.login.retry": "Try again", "hololake.login.retry": "Try again",
"hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.", "hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.",
"hololake.login.nodeReady": "Fifth Domain world entrance ready" "hololake.login.nodeReady": "Fifth Domain world entrance ready",
"education.title": "Education Work Lake",
"education.description": "A client-rendered workspace where the persona can open and edit documents, spreadsheets, charts, dashboards, and knowledge through language instructions.",
"education.hostingState": "Temporary isolated hosting",
"education.instructionLabel": "Tell the persona what you want to work on",
"education.instructionPlaceholder": "For example: organize this spreadsheet and create a comparison dashboard",
"education.runInstruction": "Run instruction",
"education.openedInsideApp": "Opened inside the app",
"education.surfaceNavigation": "Education workspace surfaces",
"education.document": "Document",
"education.sheet": "Spreadsheet",
"education.chart": "Chart",
"education.dashboard": "Dashboard",
"education.knowledge": "Knowledge",
"education.documentLabel": "Education document content"
} }

View file

@ -1121,5 +1121,19 @@
"hololake.login.errorTitle": "The world entrance did not open", "hololake.login.errorTitle": "The world entrance did not open",
"hololake.login.retry": "Try again", "hololake.login.retry": "Try again",
"hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.", "hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.",
"hololake.login.nodeReady": "Fifth Domain world entrance ready" "hololake.login.nodeReady": "Fifth Domain world entrance ready",
"education.title": "Education Work Lake",
"education.description": "A client-rendered workspace where the persona can open and edit documents, spreadsheets, charts, dashboards, and knowledge through language instructions.",
"education.hostingState": "Temporary isolated hosting",
"education.instructionLabel": "Tell the persona what you want to work on",
"education.instructionPlaceholder": "For example: organize this spreadsheet and create a comparison dashboard",
"education.runInstruction": "Run instruction",
"education.openedInsideApp": "Opened inside the app",
"education.surfaceNavigation": "Education workspace surfaces",
"education.document": "Document",
"education.sheet": "Spreadsheet",
"education.chart": "Chart",
"education.dashboard": "Dashboard",
"education.knowledge": "Knowledge",
"education.documentLabel": "Education document content"
} }

View file

@ -1121,5 +1121,19 @@
"hololake.login.errorTitle": "The world entrance did not open", "hololake.login.errorTitle": "The world entrance did not open",
"hololake.login.retry": "Try again", "hololake.login.retry": "Try again",
"hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.", "hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.",
"hololake.login.nodeReady": "Fifth Domain world entrance ready" "hololake.login.nodeReady": "Fifth Domain world entrance ready",
"education.title": "Education Work Lake",
"education.description": "A client-rendered workspace where the persona can open and edit documents, spreadsheets, charts, dashboards, and knowledge through language instructions.",
"education.hostingState": "Temporary isolated hosting",
"education.instructionLabel": "Tell the persona what you want to work on",
"education.instructionPlaceholder": "For example: organize this spreadsheet and create a comparison dashboard",
"education.runInstruction": "Run instruction",
"education.openedInsideApp": "Opened inside the app",
"education.surfaceNavigation": "Education workspace surfaces",
"education.document": "Document",
"education.sheet": "Spreadsheet",
"education.chart": "Chart",
"education.dashboard": "Dashboard",
"education.knowledge": "Knowledge",
"education.documentLabel": "Education document content"
} }

View file

@ -1121,5 +1121,19 @@
"hololake.login.errorTitle": "The world entrance did not open", "hololake.login.errorTitle": "The world entrance did not open",
"hololake.login.retry": "Try again", "hololake.login.retry": "Try again",
"hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.", "hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.",
"hololake.login.nodeReady": "Fifth Domain world entrance ready" "hololake.login.nodeReady": "Fifth Domain world entrance ready",
"education.title": "Education Work Lake",
"education.description": "A client-rendered workspace where the persona can open and edit documents, spreadsheets, charts, dashboards, and knowledge through language instructions.",
"education.hostingState": "Temporary isolated hosting",
"education.instructionLabel": "Tell the persona what you want to work on",
"education.instructionPlaceholder": "For example: organize this spreadsheet and create a comparison dashboard",
"education.runInstruction": "Run instruction",
"education.openedInsideApp": "Opened inside the app",
"education.surfaceNavigation": "Education workspace surfaces",
"education.document": "Document",
"education.sheet": "Spreadsheet",
"education.chart": "Chart",
"education.dashboard": "Dashboard",
"education.knowledge": "Knowledge",
"education.documentLabel": "Education document content"
} }

View file

@ -1121,5 +1121,19 @@
"hololake.login.errorTitle": "The world entrance did not open", "hololake.login.errorTitle": "The world entrance did not open",
"hololake.login.retry": "Try again", "hololake.login.retry": "Try again",
"hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.", "hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.",
"hololake.login.nodeReady": "Fifth Domain world entrance ready" "hololake.login.nodeReady": "Fifth Domain world entrance ready",
"education.title": "Education Work Lake",
"education.description": "A client-rendered workspace where the persona can open and edit documents, spreadsheets, charts, dashboards, and knowledge through language instructions.",
"education.hostingState": "Temporary isolated hosting",
"education.instructionLabel": "Tell the persona what you want to work on",
"education.instructionPlaceholder": "For example: organize this spreadsheet and create a comparison dashboard",
"education.runInstruction": "Run instruction",
"education.openedInsideApp": "Opened inside the app",
"education.surfaceNavigation": "Education workspace surfaces",
"education.document": "Document",
"education.sheet": "Spreadsheet",
"education.chart": "Chart",
"education.dashboard": "Dashboard",
"education.knowledge": "Knowledge",
"education.documentLabel": "Education document content"
} }

View file

@ -1121,5 +1121,19 @@
"hololake.login.errorTitle": "The world entrance did not open", "hololake.login.errorTitle": "The world entrance did not open",
"hololake.login.retry": "Try again", "hololake.login.retry": "Try again",
"hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.", "hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.",
"hololake.login.nodeReady": "Fifth Domain world entrance ready" "hololake.login.nodeReady": "Fifth Domain world entrance ready",
"education.title": "Education Work Lake",
"education.description": "A client-rendered workspace where the persona can open and edit documents, spreadsheets, charts, dashboards, and knowledge through language instructions.",
"education.hostingState": "Temporary isolated hosting",
"education.instructionLabel": "Tell the persona what you want to work on",
"education.instructionPlaceholder": "For example: organize this spreadsheet and create a comparison dashboard",
"education.runInstruction": "Run instruction",
"education.openedInsideApp": "Opened inside the app",
"education.surfaceNavigation": "Education workspace surfaces",
"education.document": "Document",
"education.sheet": "Spreadsheet",
"education.chart": "Chart",
"education.dashboard": "Dashboard",
"education.knowledge": "Knowledge",
"education.documentLabel": "Education document content"
} }

View file

@ -1121,5 +1121,19 @@
"hololake.login.errorTitle": "The world entrance did not open", "hololake.login.errorTitle": "The world entrance did not open",
"hololake.login.retry": "Try again", "hololake.login.retry": "Try again",
"hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.", "hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.",
"hololake.login.nodeReady": "Fifth Domain world entrance ready" "hololake.login.nodeReady": "Fifth Domain world entrance ready",
"education.title": "Education Work Lake",
"education.description": "A client-rendered workspace where the persona can open and edit documents, spreadsheets, charts, dashboards, and knowledge through language instructions.",
"education.hostingState": "Temporary isolated hosting",
"education.instructionLabel": "Tell the persona what you want to work on",
"education.instructionPlaceholder": "For example: organize this spreadsheet and create a comparison dashboard",
"education.runInstruction": "Run instruction",
"education.openedInsideApp": "Opened inside the app",
"education.surfaceNavigation": "Education workspace surfaces",
"education.document": "Document",
"education.sheet": "Spreadsheet",
"education.chart": "Chart",
"education.dashboard": "Dashboard",
"education.knowledge": "Knowledge",
"education.documentLabel": "Education document content"
} }

View file

@ -1000,6 +1000,20 @@
"locale.ukUA": "乌克兰语", "locale.ukUA": "乌克兰语",
"locale.svSE": "瑞典语", "locale.svSE": "瑞典语",
"locale.skSK": "斯洛伐克语", "locale.skSK": "斯洛伐克语",
"education.title": "教育工作湖",
"education.description": "人格体通过语言指令调出并编辑文档、表格、对比图、仪表盘与知识,所有呈现在用户设备完成。",
"education.hostingState": "临时隔离承载",
"education.instructionLabel": "告诉人格体你要处理什么",
"education.instructionPlaceholder": "例如:整理这个表格,并生成一个对比仪表盘",
"education.runInstruction": "运行指令",
"education.openedInsideApp": "已在应用内打开",
"education.surfaceNavigation": "教育工作区界面",
"education.document": "文档",
"education.sheet": "表格",
"education.chart": "对比图",
"education.dashboard": "仪表盘",
"education.knowledge": "知识",
"education.documentLabel": "教研文档内容",
"hololake.home.ariaLabel": "HoloLake Era 世界入口", "hololake.home.ariaLabel": "HoloLake Era 世界入口",
"hololake.home.eyebrow": "光湖语言世界 · 第五域", "hololake.home.eyebrow": "光湖语言世界 · 第五域",
"hololake.home.lead": "语言人格驱动操作系统", "hololake.home.lead": "语言人格驱动操作系统",

View file

@ -1121,5 +1121,19 @@
"hololake.login.errorTitle": "The world entrance did not open", "hololake.login.errorTitle": "The world entrance did not open",
"hololake.login.retry": "Try again", "hololake.login.retry": "Try again",
"hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.", "hololake.login.privacy": "HoloLake never receives or stores your mailbox address. The Fifth Domain controller resolves the bound Guanghu number.",
"hololake.login.nodeReady": "Fifth Domain world entrance ready" "hololake.login.nodeReady": "Fifth Domain world entrance ready",
"education.title": "Education Work Lake",
"education.description": "A client-rendered workspace where the persona can open and edit documents, spreadsheets, charts, dashboards, and knowledge through language instructions.",
"education.hostingState": "Temporary isolated hosting",
"education.instructionLabel": "Tell the persona what you want to work on",
"education.instructionPlaceholder": "For example: organize this spreadsheet and create a comparison dashboard",
"education.runInstruction": "Run instruction",
"education.openedInsideApp": "Opened inside the app",
"education.surfaceNavigation": "Education workspace surfaces",
"education.document": "Document",
"education.sheet": "Spreadsheet",
"education.chart": "Chart",
"education.dashboard": "Dashboard",
"education.knowledge": "Knowledge",
"education.documentLabel": "Education document content"
} }