feat: add remote-first education work lake
This commit is contained in:
parent
ac9d051778
commit
333cd222c5
43 changed files with 923 additions and 74 deletions
|
|
@ -1436,6 +1436,7 @@ function MainApp({
|
|||
telemetryConsent: settings.telemetry_consent,
|
||||
vaultIsLoading: vault.isLoading,
|
||||
vaultSwitcher,
|
||||
worldEntryVisible: showHoloLakeHome,
|
||||
})
|
||||
const deepLinks = useDeepLinks({
|
||||
activeEntry: activeTab?.entry ?? null,
|
||||
|
|
|
|||
|
|
@ -19,11 +19,6 @@ const installLinkTargets = [
|
|||
['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-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
|
||||
|
||||
vi.mock('../utils/url', () => ({
|
||||
|
|
|
|||
|
|
@ -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; } }
|
||||
|
|
@ -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('八年级物理:光的折射')
|
||||
})
|
||||
})
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -22,11 +22,13 @@ import { GUANGHU_THEMES, type GuanghuTheme } from '../lib/guanghuTheme'
|
|||
import { planGuanghuLivingSystem } from '../utils/planGuanghuLivingSystem'
|
||||
import { Button } from './ui/button'
|
||||
import { HOLOLAKE_REPOSITORY_URL } from '../constants/feedback'
|
||||
import { isEducationWorkLakePath } from '../lib/educationWorkLake'
|
||||
import { GuanghuRouterConsole } from './GuanghuRouterConsole'
|
||||
import { GuanghuWorldMap } from './GuanghuWorldMap'
|
||||
import { GuanghuWorldLoginGate } from './GuanghuWorldLoginGate'
|
||||
import { FifthDomainSystems } from './FifthDomainSystems'
|
||||
import { EternalLakeHeartPage } from './EternalLakeHeartPage'
|
||||
import { EducationWorkLake } from './EducationWorkLake'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
|
|
@ -89,14 +91,17 @@ export function HoloLakeHome({
|
|||
onOpenLocalWorkspace,
|
||||
livingSystemTarget,
|
||||
}: 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>(() => (
|
||||
createDeterministicLivingSystemPlan(createLivingSystemEvent({
|
||||
currentRoute: 'world',
|
||||
currentRoute: initialRoute,
|
||||
eventId: 'system-start',
|
||||
intent: 'navigate',
|
||||
receiptIds: [],
|
||||
requestedRoute: 'world',
|
||||
requestedRoute: initialRoute,
|
||||
worldOpen: false,
|
||||
}))
|
||||
))
|
||||
|
|
@ -407,6 +412,10 @@ export function HoloLakeHome({
|
|||
)
|
||||
}
|
||||
|
||||
if (route === 'education-work-lake') {
|
||||
return <EducationWorkLake locale={locale} />
|
||||
}
|
||||
|
||||
if (route === 'servers') {
|
||||
return (
|
||||
<section className="channel-stage channel-stage--node">
|
||||
|
|
@ -453,6 +462,7 @@ export function HoloLakeHome({
|
|||
<p className="channel-stage__lead">{t('hololake.channel.heartbeatDescription')}</p>
|
||||
<div className="channel-grid channel-grid--three">
|
||||
<RouteCard eyebrow="PERSONA · AI AGENT" title="人格体工作舱" description="与当前频道的人格体持续协作;保留多 Agent、模型选择、权限模式、工具调用、执行回执与历史会话。" action="打开人格体工作舱" onOpen={onOpenAiWorkspace ? openAiWorkspace : undefined} />
|
||||
<RouteCard eyebrow="INDUSTRY · EDUCATION" title="教育工作湖" description="通过语言指令调出并编辑文档、表格、对比图、仪表盘与知识。第一阶段权限保留在应用内。" action="进入教育工作湖" onOpen={() => navigate('education-work-lake')} />
|
||||
<RouteCard eyebrow="MODULE · KNOWLEDGE" title={t('hololake.home.moduleKnowledgeTitle')} description={t('hololake.home.moduleKnowledgeDescription')} action={t('hololake.channel.openKnowledge')} onOpen={openKnowledgeBase} />
|
||||
<RouteCard eyebrow="LOCAL COMPUTER · LIVE FOLDER" title="本地电脑工作区" description="选择电脑上的任意项目文件夹。编程 AI 直接在原文件夹写入页面,光湖实时显示,不再复制到旧笔记库。" action="打开本地文件夹" onOpen={onOpenLocalWorkspace ? openLocalWorkspace : undefined} />
|
||||
<RouteCard eyebrow="MODULE · VIDEO AI" title={t('hololake.channel.videoAiTitle')} description={t('hololake.channel.videoAiDescription')} status={t('hololake.channel.prototypeMounted')} />
|
||||
|
|
@ -492,6 +502,7 @@ export function HoloLakeHome({
|
|||
<Button data-active={route === 'world'} variant="ghost" onClick={() => navigate('world')}><i data-color="cyan" />光湖世界</Button>
|
||||
<Button variant="ghost" onClick={openKnowledgeBase}><i data-color="blue" />知识库</Button>
|
||||
<Button disabled={!onOpenAiWorkspace} variant="ghost" onClick={openAiWorkspace}><i data-color="violet" />Agent 工作舱</Button>
|
||||
<Button data-active={route === 'education-work-lake'} variant="ghost" onClick={() => navigate('education-work-lake')}><i data-color="green" />教育工作湖</Button>
|
||||
<Button disabled={!onOpenLocalWorkspace} variant="ghost" onClick={openLocalWorkspace}><i data-color="amber" />本地工具与项目</Button>
|
||||
</>}
|
||||
{fifthDomainOpen && <>
|
||||
|
|
|
|||
|
|
@ -57,11 +57,6 @@ describe('useAiAgentsStatus', () => {
|
|||
expect(result.current.claude_code).toEqual({ status: 'installed', version: '1.0.20' })
|
||||
expect(result.current.codex).toEqual({ status: 'missing', version: null })
|
||||
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' })
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1085,9 +1085,9 @@ describe('reload-vault command', () => {
|
|||
expect(cmd).toBeDefined()
|
||||
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-opencode')).toBeDefined()
|
||||
expect(findCommand(result.current, 'switch-ai-agent-pi')).toBeDefined()
|
||||
expect(findCommand(result.current, 'switch-ai-agent-antigravity')).toBeDefined()
|
||||
expect(findCommand(result.current, 'switch-ai-agent-opencode')).toBeUndefined()
|
||||
expect(findCommand(result.current, 'switch-ai-agent-pi')).toBeUndefined()
|
||||
expect(findCommand(result.current, 'switch-ai-agent-antigravity')).toBeUndefined()
|
||||
|
||||
cmd!.execute()
|
||||
expect(onSetDefaultAiAgent).toHaveBeenCalledWith('codex')
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
|
@ -22,6 +22,7 @@ interface UseStartupScreenStateArgs {
|
|||
telemetryConsent: boolean | null
|
||||
vaultIsLoading: boolean
|
||||
vaultSwitcher: StartupVaultSwitcherState
|
||||
worldEntryVisible?: boolean
|
||||
}
|
||||
|
||||
interface StartupScreenState {
|
||||
|
|
@ -122,6 +123,7 @@ export function useStartupScreenState({
|
|||
telemetryConsent,
|
||||
vaultIsLoading,
|
||||
vaultSwitcher,
|
||||
worldEntryVisible = false,
|
||||
}: UseStartupScreenStateArgs): StartupScreenState {
|
||||
const shouldResumeFreshStartOnboarding = useMemo(
|
||||
() => shouldResumeFreshStart(onboardingState, selectedVaultPath, vaultSwitcher),
|
||||
|
|
@ -129,7 +131,7 @@ export function useStartupScreenState({
|
|||
)
|
||||
|
||||
const isStartupLoading = !isNoteWindow && onboardingState.status === 'loading'
|
||||
const shouldShowStartupScreen = shouldShowStartupScreenForState({
|
||||
const shouldShowStartupScreen = !worldEntryVisible && shouldShowStartupScreenForState({
|
||||
aiAgentsPromptVisible,
|
||||
isNoteWindow,
|
||||
isStartupLoading,
|
||||
|
|
|
|||
|
|
@ -11,12 +11,12 @@ describe('aiAgents helpers', () => {
|
|||
expect(normalizeStoredAiAgent('claude_code')).toBe('claude_code')
|
||||
expect(normalizeStoredAiAgent('codex')).toBe('codex')
|
||||
expect(normalizeStoredAiAgent('copilot')).toBe('copilot')
|
||||
expect(normalizeStoredAiAgent('opencode')).toBe('opencode')
|
||||
expect(normalizeStoredAiAgent('pi')).toBe('pi')
|
||||
expect(normalizeStoredAiAgent('antigravity')).toBe('antigravity')
|
||||
expect(normalizeStoredAiAgent('gemini')).toBe('antigravity')
|
||||
expect(normalizeStoredAiAgent('kiro')).toBe('kiro')
|
||||
expect(normalizeStoredAiAgent('hermes')).toBe('hermes')
|
||||
expect(normalizeStoredAiAgent('opencode')).toBeNull()
|
||||
expect(normalizeStoredAiAgent('pi')).toBeNull()
|
||||
expect(normalizeStoredAiAgent('antigravity')).toBeNull()
|
||||
expect(normalizeStoredAiAgent('gemini')).toBeNull()
|
||||
expect(normalizeStoredAiAgent('kiro')).toBeNull()
|
||||
expect(normalizeStoredAiAgent('hermes')).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.codex).toEqual({ status: 'missing', version: null })
|
||||
expect(statuses.copilot).toEqual({ status: 'installed', version: '1.0.58' })
|
||||
expect(statuses.opencode).toEqual({ status: 'installed', version: '0.3.1' })
|
||||
expect(statuses.pi).toEqual({ status: 'installed', version: '0.70.2' })
|
||||
expect(statuses.antigravity).toEqual({ status: 'installed', version: 'Antigravity CLI 1.0.0' })
|
||||
expect(statuses.kiro).toEqual({ status: 'installed', version: '0.12.0' })
|
||||
expect(statuses.hermes).toEqual({ status: 'installed', version: 'Hermes Agent 0.16.0' })
|
||||
expect(statuses.opencode).toBeUndefined()
|
||||
expect(statuses.pi).toBeUndefined()
|
||||
expect(statuses.antigravity).toBeUndefined()
|
||||
expect(statuses.kiro).toBeUndefined()
|
||||
expect(statuses.hermes).toBeUndefined()
|
||||
})
|
||||
|
||||
it('normalizes legacy Gemini status payloads to Antigravity', () => {
|
||||
it('does not expose legacy Gemini status payloads', () => {
|
||||
const statuses = normalizeAiAgentsStatus({
|
||||
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', () => {
|
||||
expect(getNextAiAgentId('claude_code')).toBe('codex')
|
||||
expect(getNextAiAgentId('codex')).toBe('copilot')
|
||||
expect(getNextAiAgentId('copilot')).toBe('opencode')
|
||||
expect(getNextAiAgentId('opencode')).toBe('pi')
|
||||
expect(getNextAiAgentId('pi')).toBe('antigravity')
|
||||
expect(getNextAiAgentId('antigravity')).toBe('kiro')
|
||||
expect(getNextAiAgentId('kiro')).toBe('hermes')
|
||||
expect(getNextAiAgentId('hermes')).toBe('claude_code')
|
||||
expect(getNextAiAgentId('copilot')).toBe('claude_code')
|
||||
expect(getNextAiAgentId('opencode')).toBe('claude_code')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -40,37 +40,40 @@ export const AI_AGENT_DEFINITIONS: readonly AiAgentDefinition[] = [
|
|||
shortLabel: 'Copilot',
|
||||
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',
|
||||
label: 'OpenCode',
|
||||
shortLabel: 'OpenCode',
|
||||
installUrl: 'https://opencode.ai/docs/',
|
||||
},
|
||||
{
|
||||
pi: {
|
||||
id: 'pi',
|
||||
label: 'Pi',
|
||||
shortLabel: 'Pi',
|
||||
installUrl: 'https://pi.dev',
|
||||
},
|
||||
{
|
||||
antigravity: {
|
||||
id: 'antigravity',
|
||||
label: 'Antigravity CLI',
|
||||
label: 'Antigravity',
|
||||
shortLabel: 'Antigravity',
|
||||
installUrl: 'https://antigravity.google/docs/cli/install',
|
||||
},
|
||||
{
|
||||
kiro: {
|
||||
id: 'kiro',
|
||||
label: 'Kiro',
|
||||
shortLabel: 'Kiro',
|
||||
installUrl: 'https://kiro.dev/docs/cli',
|
||||
},
|
||||
{
|
||||
hermes: {
|
||||
id: 'hermes',
|
||||
label: 'Hermes Agent',
|
||||
shortLabel: 'Hermes',
|
||||
installUrl: 'https://hermes-agent.nousresearch.com/docs/getting-started/quickstart',
|
||||
},
|
||||
] as const
|
||||
}
|
||||
|
||||
export function createAiAgentAvailability(status: AiAgentStatus = 'checking', version: string | null = null): AiAgentAvailability {
|
||||
return { status, version }
|
||||
|
|
@ -94,7 +97,6 @@ export function createMissingAiAgentsStatus(): AiAgentsStatus {
|
|||
}
|
||||
|
||||
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
|
||||
return null
|
||||
}
|
||||
|
|
@ -104,7 +106,9 @@ export function resolveDefaultAiAgent(value: string | null | undefined): AiAgent
|
|||
}
|
||||
|
||||
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 {
|
||||
|
|
@ -116,7 +120,7 @@ function normalizeAvailability(agent: { installed?: boolean | null; version?: st
|
|||
}
|
||||
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
default_ai_agent: 'claude_code',
|
||||
default_ai_target: 'kiro',
|
||||
|
|
@ -67,12 +67,12 @@ describe('ai target provider contract', () => {
|
|||
|
||||
expect(target).toMatchObject({
|
||||
kind: 'agent',
|
||||
agent: 'kiro',
|
||||
id: 'agent:kiro',
|
||||
agent: 'claude_code',
|
||||
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({
|
||||
default_ai_agent: 'kiro',
|
||||
default_ai_target: 'agent:claude_code',
|
||||
|
|
@ -80,8 +80,8 @@ describe('ai target provider contract', () => {
|
|||
|
||||
expect(target).toMatchObject({
|
||||
kind: 'agent',
|
||||
agent: 'kiro',
|
||||
id: 'agent:kiro',
|
||||
agent: 'claude_code',
|
||||
id: 'agent:claude_code',
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
|
@ -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')
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ export const GUANGHU_CHANNEL_ROUTES = [
|
|||
'eternal-lake-heart',
|
||||
'light-lake',
|
||||
'heartbeat-core',
|
||||
'education-work-lake',
|
||||
'love-core',
|
||||
'servers',
|
||||
] as const
|
||||
|
|
|
|||
|
|
@ -1121,5 +1121,19 @@
|
|||
"hololake.login.errorTitle": "The world entrance did not open",
|
||||
"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.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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1121,5 +1121,19 @@
|
|||
"hololake.login.errorTitle": "The world entrance did not open",
|
||||
"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.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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1121,5 +1121,19 @@
|
|||
"hololake.login.errorTitle": "The world entrance did not open",
|
||||
"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.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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1000,6 +1000,20 @@
|
|||
"locale.ukUA": "Ukrainian",
|
||||
"locale.svSE": "Swedish",
|
||||
"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.eyebrow": "Guanghu Language World · Fifth Domain",
|
||||
"hololake.home.lead": "Language-personality-driven operating system",
|
||||
|
|
|
|||
|
|
@ -1121,5 +1121,19 @@
|
|||
"hololake.login.errorTitle": "The world entrance did not open",
|
||||
"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.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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1121,5 +1121,19 @@
|
|||
"hololake.login.errorTitle": "The world entrance did not open",
|
||||
"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.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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1121,5 +1121,19 @@
|
|||
"hololake.login.errorTitle": "The world entrance did not open",
|
||||
"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.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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1121,5 +1121,19 @@
|
|||
"hololake.login.errorTitle": "The world entrance did not open",
|
||||
"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.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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1121,5 +1121,19 @@
|
|||
"hololake.login.errorTitle": "The world entrance did not open",
|
||||
"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.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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1121,5 +1121,19 @@
|
|||
"hololake.login.errorTitle": "The world entrance did not open",
|
||||
"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.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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1121,5 +1121,19 @@
|
|||
"hololake.login.errorTitle": "The world entrance did not open",
|
||||
"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.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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1121,5 +1121,19 @@
|
|||
"hololake.login.errorTitle": "The world entrance did not open",
|
||||
"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.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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1121,5 +1121,19 @@
|
|||
"hololake.login.errorTitle": "The world entrance did not open",
|
||||
"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.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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1121,5 +1121,19 @@
|
|||
"hololake.login.errorTitle": "The world entrance did not open",
|
||||
"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.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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1121,5 +1121,19 @@
|
|||
"hololake.login.errorTitle": "The world entrance did not open",
|
||||
"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.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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1121,5 +1121,19 @@
|
|||
"hololake.login.errorTitle": "The world entrance did not open",
|
||||
"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.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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1121,5 +1121,19 @@
|
|||
"hololake.login.errorTitle": "The world entrance did not open",
|
||||
"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.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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1121,5 +1121,19 @@
|
|||
"hololake.login.errorTitle": "The world entrance did not open",
|
||||
"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.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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1121,5 +1121,19 @@
|
|||
"hololake.login.errorTitle": "The world entrance did not open",
|
||||
"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.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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1000,6 +1000,20 @@
|
|||
"locale.ukUA": "乌克兰语",
|
||||
"locale.svSE": "瑞典语",
|
||||
"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.eyebrow": "光湖语言世界 · 第五域",
|
||||
"hololake.home.lead": "语言人格驱动操作系统",
|
||||
|
|
|
|||
|
|
@ -1121,5 +1121,19 @@
|
|||
"hololake.login.errorTitle": "The world entrance did not open",
|
||||
"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.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"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue