feat: admit signed education workbench module
This commit is contained in:
parent
15b2651074
commit
df5e8f8e3c
24 changed files with 4986 additions and 20 deletions
|
|
@ -7,6 +7,7 @@ import type { ChannelWorkbenchSnapshot } from './modules/channel-workbench'
|
|||
|
||||
const ChannelWorkbenchStudio = lazy(() => import('./modules/channel-workbench').then((module) => ({ default: module.ChannelWorkbenchStudio })))
|
||||
const PersonaChannelBody = lazy(() => import('./modules/persona-channel-body').then((module) => ({ default: module.PersonaChannelBody })))
|
||||
const EducationWorkspace = lazy(() => import('./modules/education-workspace').then((module) => ({ default: module.EducationWorkspace })))
|
||||
|
||||
const TAG_TINTS = ['tag-lavender', 'tag-sky', 'tag-mint', 'tag-amber', 'tag-rose', 'tag-slate']
|
||||
|
||||
|
|
@ -53,7 +54,7 @@ import './design-tokens.css'
|
|||
import './styles.css'
|
||||
|
||||
type ThemeId = 'night' | 'dawn' | 'nebula' | 'candle' | 'clear'
|
||||
type ViewId = 'overview' | 'knowledge' | 'composition' | 'workbench' | 'persona' | 'code' | 'receipts' | 'system'
|
||||
type ViewId = 'overview' | 'knowledge' | 'composition' | 'workbench' | 'education' | 'persona' | 'code' | 'receipts' | 'system'
|
||||
type WorldStage = 'domain' | 'heart' | 'heartbeat' | 'lightLake' | 'love' | 'tomorrow' | 'bottle' | 'channel' | 'enterpriseWork' | 'personalNodeGuide' | 'tool'
|
||||
type KnowledgeSource = 'native' | 'legacy'
|
||||
|
||||
|
|
@ -367,7 +368,7 @@ const previewCode: CodeChannelSnapshot = { state: 'UNAVAILABLE', channels: [], a
|
|||
const themes: Array<{ id: ThemeId; name: string }> = [
|
||||
{ id: 'night', name: '夜湖星光' }, { id: 'dawn', name: '晨湖曦光' }, { id: 'nebula', name: '星云紫夜' }, { id: 'candle', name: '烛畔暖湖' }, { id: 'clear', name: '清浅澄湖' },
|
||||
]
|
||||
const viewLabels: Record<ViewId, string> = { overview: '个人频道', knowledge: '知识空间', composition: '结构组合', workbench: '频道资料工作台', persona: '人格频道本体', code: '人格代码频道', receipts: '运行回执', system: '系统详情' }
|
||||
const viewLabels: Record<ViewId, string> = { overview: '个人频道', knowledge: '知识空间', composition: '结构组合', workbench: '频道资料工作台', education: '教育工作台', persona: '人格频道本体', code: '人格代码频道', receipts: '运行回执', system: '系统详情' }
|
||||
const domainGates = [
|
||||
{ domain: 'BRANCH_DOMAIN', className: 'd-sub', title: '光湖分域', gate: 'GATE 02 · ONLINE', facts: [
|
||||
['域标识', 'BRANCH_DOMAIN'], ['责任主体', '花尔 · TCS-GL-0005∞'], ['人格体主体', '爆米花 · PER-BMH001 · AGE'], ['关系支持', '糖星云 · PER-TXY001 · AGE'], ['工作仓库', 'PRIVATE · 1 · LIVE'],
|
||||
|
|
@ -607,6 +608,9 @@ function HoloLakeApp() {
|
|||
const [personaBodyModule, setPersonaBodyModule] = useState<BundledModuleDescriptor | null>(null)
|
||||
const [personaBodyBusy, setPersonaBodyBusy] = useState(false)
|
||||
const [personaBodyMessage, setPersonaBodyMessage] = useState('')
|
||||
const [educationModule, setEducationModule] = useState<BundledModuleDescriptor | null>(null)
|
||||
const [educationBusy, setEducationBusy] = useState(false)
|
||||
const [educationMessage, setEducationMessage] = useState('')
|
||||
const [expanded, setExpanded] = useState<Set<string>>(() => new Set(['导入']))
|
||||
const [editing, setEditing] = useState(false)
|
||||
const [draft, setDraft] = useState('')
|
||||
|
|
@ -1157,6 +1161,27 @@ function HoloLakeApp() {
|
|||
finally { setPersonaBodyBusy(false) }
|
||||
}
|
||||
|
||||
const refreshEducationModule = async () => {
|
||||
try {
|
||||
const catalog = await invoke<BundledModuleDescriptor[]>('get_bundled_module_catalog')
|
||||
const module = catalog.find((item) => item.moduleNumber === 'HLP-MOD-OFFICIAL-EDUCATION-WORKBENCH-0001') || null
|
||||
setEducationModule(module)
|
||||
return module
|
||||
} catch (error) { setEducationMessage(humanError(error, 'system')); return null }
|
||||
}
|
||||
const openEducation = () => { openWorldTool('education'); void refreshEducationModule() }
|
||||
const activateEducationModule = async () => {
|
||||
setEducationBusy(true)
|
||||
setEducationMessage('正在验证官方签名、登记模块编号、确认八项边界并执行自检……')
|
||||
try {
|
||||
await invoke('activate_bundled_module', { input: { moduleNumber: 'HLP-MOD-OFFICIAL-EDUCATION-WORKBENCH-0001', humanConfirmedPermissionExpansion: true } })
|
||||
const module = await refreshEducationModule()
|
||||
if (!module || module.installedState !== 'ACTIVE') throw new Error('HOLOLAKE_MODULE_NOT_ACTIVE')
|
||||
setEducationMessage('教育工作台已通过签名、编号、权限和自检验收。')
|
||||
} catch (error) { setEducationMessage(humanError(error, 'system')) }
|
||||
finally { setEducationBusy(false) }
|
||||
}
|
||||
|
||||
const cloneCodeChannel = async (event: React.FormEvent) => {
|
||||
event.preventDefault()
|
||||
if (!cloneUrl.trim()) return
|
||||
|
|
@ -1557,6 +1582,20 @@ function HoloLakeApp() {
|
|||
</section>
|
||||
)
|
||||
|
||||
const renderEducation = () => (
|
||||
<section className="full-workbench education-workbench-world">
|
||||
{educationModule?.installedState === 'ACTIVE'
|
||||
? <Suspense fallback={<div className="workbench-empty"><p>正在装载教育工作台……</p></div>}><EducationWorkspace onBack={() => setWorldStage(toolReturnStage)}/></Suspense>
|
||||
: <div className="workbench-empty">
|
||||
<span>教</span><h2>启用教育工作台</h2>
|
||||
<p>模块复用已验收的文档与真实单元格引擎,并增加账号隔离存储、外部表格翻译、人工归属、显式数据清理和预览后执行的自动化。</p>
|
||||
<code>HLP-MOD-OFFICIAL-EDUCATION-WORKBENCH-0001 · 8 项编号权限</code>
|
||||
<button className="primary-button" type="button" disabled={educationBusy} onClick={() => void activateEducationModule()}>{educationBusy ? '正在验收官方模块…' : '确认八项边界并启用'}</button>
|
||||
{educationMessage && <p>{educationMessage}</p>}
|
||||
</div>}
|
||||
</section>
|
||||
)
|
||||
|
||||
const renderCode = () => (
|
||||
<section className="full-workbench code-workbench">
|
||||
<aside className="code-channels">
|
||||
|
|
@ -1826,6 +1865,7 @@ function HoloLakeApp() {
|
|||
<LakePool className="channel-knowledge" title="知识空间" meta={`${knowledge.uniqueDocumentCount} 唯一页`} onClick={() => openWorldTool('knowledge')}/>
|
||||
<LakePool className="channel-light" title="结构组合" meta="签名模块 · 只读知识投影" onClick={() => { openWorldTool('composition'); void refreshCompositionModule() }}/>
|
||||
<LakePool className="channel-code" title="资料工作台" meta="签名模块 · 文档与智能表格" onClick={openWorkbench}/>
|
||||
<LakePool className="channel-education" title="教育工作台" meta="官方编号模块 · 教育文档、表格与自动化" onClick={openEducation}/>
|
||||
{timeAuthorityModule && <LakePool className="channel-time" title="时间主控" meta={beijingCoordinate ? `光湖历第 ${beijingCoordinate.guanghuEraDay} 天` : '北京时间正在流动'} onClick={openEraTimeline}/>}
|
||||
<LakePool className="channel-status" title="湖面天气" meta={connectionLabel} onClick={() => openWorldTool('system')}/>
|
||||
</section>}
|
||||
|
|
@ -1839,7 +1879,7 @@ function HoloLakeApp() {
|
|||
</section>}
|
||||
{worldStage === 'tool' && <section className={`world-tool world-tool-${view}`}>
|
||||
<header className="tool-worldbar"><button type="button" onClick={() => setWorldStage(toolReturnStage)}>← 退回频道全景</button><b>{viewLabels[view]}</b><span>{domainDisplayName(repoLogin.domain)}</span></header>
|
||||
<div className={`tool-projection${inspectorOpen ? '' : ' inspector-closed'}`}>{view === 'overview' ? renderOverview() : view === 'knowledge' ? renderKnowledge() : view === 'composition' ? renderComposition() : view === 'workbench' ? renderWorkbench() : view === 'persona' ? renderPersonaBody() : view === 'code' ? renderCode() : view === 'receipts' ? renderReceipts() : renderSystem()}</div>
|
||||
<div className={`tool-projection${inspectorOpen ? '' : ' inspector-closed'}`}>{view === 'overview' ? renderOverview() : view === 'knowledge' ? renderKnowledge() : view === 'composition' ? renderComposition() : view === 'workbench' ? renderWorkbench() : view === 'education' ? renderEducation() : view === 'persona' ? renderPersonaBody() : view === 'code' ? renderCode() : view === 'receipts' ? renderReceipts() : renderSystem()}</div>
|
||||
</section>}
|
||||
</main>
|
||||
<footer className="world-footer"><b>光湖语言系统 · 通用人工智能操作平台</b><span>GH-AIOS</span></footer>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
export const EDUCATION_EDITABLE_CELL_BUDGET = 240
|
||||
|
||||
export type EducationAdaptiveView = 'grid' | 'cards' | 'board' | 'dashboard'
|
||||
|
||||
export interface EducationAdaptiveInputs {
|
||||
rowCount: number
|
||||
columnCount: number
|
||||
numericColumnCount: number
|
||||
groupableColumnCount: number
|
||||
focusConfidence: 'high' | 'medium' | 'low'
|
||||
}
|
||||
|
||||
export interface EducationModelRenderProposal {
|
||||
primaryFocus: string
|
||||
groupColumnIndex?: number
|
||||
measureColumnIndex?: number
|
||||
views: EducationAdaptiveView[]
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
export interface EducationAdaptiveRenderPlan {
|
||||
source: 'LOCAL_BASELINE' | 'MODEL_PROPOSAL'
|
||||
state: 'READY' | 'DEGRADED' | 'WAITING_HUMAN_CONFIRMATION' | 'MODEL_PROPOSAL_REJECTED'
|
||||
views: EducationAdaptiveView[]
|
||||
pageSize: number
|
||||
primaryFocus: string
|
||||
reasons: string[]
|
||||
}
|
||||
|
||||
export function educationEditablePageSize(columnCount: number): number {
|
||||
const boundedColumns = Math.max(1, Math.min(30, columnCount))
|
||||
return Math.max(5, Math.min(20, Math.floor(EDUCATION_EDITABLE_CELL_BUDGET / boundedColumns)))
|
||||
}
|
||||
|
||||
export function buildEducationBaselineRenderPlan(inputs: EducationAdaptiveInputs): EducationAdaptiveRenderPlan {
|
||||
const reasons: string[] = []
|
||||
const views: EducationAdaptiveView[] = ['grid', 'cards']
|
||||
if (inputs.groupableColumnCount > 0) views.push('board')
|
||||
else reasons.push('没有可靠分类字段,分类看板已降级停用。')
|
||||
if (inputs.numericColumnCount > 0) views.push('dashboard')
|
||||
else reasons.push('没有可靠数值字段,数据概览降级为计数摘要。')
|
||||
if (inputs.focusConfidence === 'low') reasons.push('重点置信度不足,保留人工选择,不自动突出字段。')
|
||||
if (inputs.rowCount * inputs.columnCount > EDUCATION_EDITABLE_CELL_BUDGET) reasons.push('可编辑单元格超过本机安全预算,已启用动态分页。')
|
||||
return {
|
||||
source: 'LOCAL_BASELINE',
|
||||
state: reasons.length ? 'DEGRADED' : 'READY',
|
||||
views,
|
||||
pageSize: educationEditablePageSize(inputs.columnCount),
|
||||
primaryFocus: inputs.focusConfidence === 'low' ? '重点待选择' : '本地基础算法重点',
|
||||
reasons,
|
||||
}
|
||||
}
|
||||
|
||||
export function validateEducationModelRenderProposal(
|
||||
inputs: EducationAdaptiveInputs,
|
||||
proposal: EducationModelRenderProposal,
|
||||
humanConfirmed: boolean,
|
||||
): EducationAdaptiveRenderPlan {
|
||||
const baseline = buildEducationBaselineRenderPlan(inputs)
|
||||
const reasons: string[] = []
|
||||
const uniqueViews = [...new Set(proposal.views)]
|
||||
if (!proposal.primaryFocus.trim() || proposal.primaryFocus.length > 120) reasons.push('模型建议的重点标题无效。')
|
||||
if (!uniqueViews.length || uniqueViews.some((view) => !['grid', 'cards', 'board', 'dashboard'].includes(view))) reasons.push('模型建议包含未登记视图。')
|
||||
if (proposal.groupColumnIndex !== undefined && (proposal.groupColumnIndex < 0 || proposal.groupColumnIndex >= inputs.columnCount)) reasons.push('模型建议的分类字段不存在。')
|
||||
if (proposal.measureColumnIndex !== undefined && (proposal.measureColumnIndex < 0 || proposal.measureColumnIndex >= inputs.columnCount)) reasons.push('模型建议的统计字段不存在。')
|
||||
const requestedPageSize = proposal.pageSize ?? baseline.pageSize
|
||||
const maximumSafePageSize = educationEditablePageSize(inputs.columnCount)
|
||||
if (!Number.isInteger(requestedPageSize) || requestedPageSize < 5 || requestedPageSize > maximumSafePageSize) reasons.push('模型建议的页面密度超过本机安全预算。')
|
||||
if (reasons.length) return { ...baseline, state: 'MODEL_PROPOSAL_REJECTED', reasons: [...reasons, '已优雅退回本地基础算法。'] }
|
||||
if (!humanConfirmed) return { ...baseline, state: 'WAITING_HUMAN_CONFIRMATION', reasons: ['模型建议已通过本地校验,等待用户确认后启用。'] }
|
||||
return {
|
||||
source: 'MODEL_PROPOSAL',
|
||||
state: 'READY',
|
||||
views: uniqueViews,
|
||||
pageSize: requestedPageSize,
|
||||
primaryFocus: proposal.primaryFocus.trim(),
|
||||
reasons: ['模型建议已通过本地结构校验、资源上限与用户确认。'],
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
export interface EducationDataColumn { columnId: string; title: string }
|
||||
export interface EducationDataRow { rowId: string; cells: string[] }
|
||||
export interface EducationDataTable { columns: EducationDataColumn[]; rows: EducationDataRow[] }
|
||||
|
||||
export const EDUCATION_CLEANUP_CHANGE_PREVIEW_LIMIT = 12
|
||||
|
||||
export type EducationDataChangeKind = 'TRIM_CELL' | 'REMOVE_EMPTY_ROW' | 'REMOVE_DUPLICATE_ROW'
|
||||
|
||||
export interface EducationDataChangePreview {
|
||||
kind: EducationDataChangeKind
|
||||
rowNumber: number
|
||||
columnTitle: string
|
||||
before: string
|
||||
after: string
|
||||
}
|
||||
|
||||
export interface EducationDataAnalysis {
|
||||
originalRows: number
|
||||
cleanedRows: EducationDataRow[]
|
||||
emptyRows: number
|
||||
duplicateRows: number
|
||||
trimmedCells: number
|
||||
changeCount: number
|
||||
changePreview: EducationDataChangePreview[]
|
||||
}
|
||||
|
||||
function previewCell(value: string): string {
|
||||
const singleLine = value.replace(/\t/g, '⇥').replace(/\r?\n/g, '↵').replace(/ /g, '·')
|
||||
return singleLine.length > 120 ? `${singleLine.slice(0, 117)}…` : singleLine
|
||||
}
|
||||
|
||||
export function analyzeEducationTableData(table: EducationDataTable): EducationDataAnalysis {
|
||||
let emptyRows = 0
|
||||
let duplicateRows = 0
|
||||
let trimmedCells = 0
|
||||
let changeCount = 0
|
||||
const seen = new Set<string>()
|
||||
const cleanedRows: EducationDataRow[] = []
|
||||
const changePreview: EducationDataChangePreview[] = []
|
||||
const addPreview = (change: EducationDataChangePreview) => {
|
||||
changeCount += 1
|
||||
if (changePreview.length < EDUCATION_CLEANUP_CHANGE_PREVIEW_LIMIT) changePreview.push(change)
|
||||
}
|
||||
for (const [rowIndex, row] of table.rows.entries()) {
|
||||
const cells = table.columns.map((_, index) => {
|
||||
const original = row.cells[index] || ''
|
||||
const cleaned = original.trim()
|
||||
if (cleaned !== original) {
|
||||
trimmedCells += 1
|
||||
addPreview({
|
||||
kind: 'TRIM_CELL',
|
||||
rowNumber: rowIndex + 1,
|
||||
columnTitle: table.columns[index]?.title || `字段 ${index + 1}`,
|
||||
before: previewCell(original),
|
||||
after: previewCell(cleaned),
|
||||
})
|
||||
}
|
||||
return cleaned
|
||||
})
|
||||
if (cells.every((cell) => !cell)) {
|
||||
emptyRows += 1
|
||||
addPreview({ kind: 'REMOVE_EMPTY_ROW', rowNumber: rowIndex + 1, columnTitle: '整行', before: '完整空行', after: '移除' })
|
||||
continue
|
||||
}
|
||||
const signature = JSON.stringify(cells)
|
||||
if (seen.has(signature)) {
|
||||
duplicateRows += 1
|
||||
addPreview({ kind: 'REMOVE_DUPLICATE_ROW', rowNumber: rowIndex + 1, columnTitle: '整行', before: '与前文完全重复', after: '移除' })
|
||||
continue
|
||||
}
|
||||
seen.add(signature)
|
||||
cleanedRows.push({ ...row, cells })
|
||||
}
|
||||
return { originalRows: table.rows.length, cleanedRows, emptyRows, duplicateRows, trimmedCells, changeCount, changePreview }
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import ChannelDocumentEngine from '../channel-workbench/document-engine'
|
||||
|
||||
export default function EducationDocumentEngine({ body, onChange }: { body: string; onChange: (body: string) => void }) {
|
||||
return <ChannelDocumentEngine body={body} onChange={onChange} />
|
||||
}
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
import { lazy, Suspense, useEffect, useMemo, useState } from 'react'
|
||||
import { numberedInvoke as invoke } from '../numbered-ipc'
|
||||
import { analyzeEducationTableData, type EducationDataAnalysis } from './education-data'
|
||||
import './styles.css'
|
||||
|
||||
const EducationDocumentEngine = lazy(() => import('./education-document-engine'))
|
||||
const ChannelSpreadsheetEngine = lazy(() => import('../channel-workbench/spreadsheet-engine'))
|
||||
|
||||
export const EDUCATION_MODULE_NUMBER = 'HLP-MOD-OFFICIAL-EDUCATION-WORKBENCH-0001'
|
||||
|
||||
interface EducationColumn { columnId: string; title: string }
|
||||
interface EducationRow { rowId: string; cells: string[] }
|
||||
interface DocumentSummary { documentId: string; title: string; revision: number; updatedAtUnixMs: number }
|
||||
interface TableSummary { tableId: string; title: string; revision: number; columnCount: number; rowCount: number; updatedAtUnixMs: number }
|
||||
interface UnassignedTableSummary { tableId: string; title: string; columnCount: number; rowCount: number; importedAtUnixMs: number }
|
||||
interface EducationDocument extends DocumentSummary { body: string; createdAtUnixMs: number }
|
||||
interface EducationTable extends TableSummary { columns: EducationColumn[]; rows: EducationRow[]; createdAtUnixMs: number }
|
||||
interface AutomationRule {
|
||||
ruleId: string; title: string; tableId: string; conditionColumnId: string; operator: string; conditionValue: string
|
||||
actionColumnId: string; actionValue: string; enabled: boolean; revision: number
|
||||
}
|
||||
interface AutomationPreview { state: string; ruleId: string; tableId: string; ruleRevision: number; tableRevision: number; matchedRows: number; changedCells: number; previewToken: string }
|
||||
interface EducationSnapshot {
|
||||
schema: string; state: string; documents: DocumentSummary[]; tables: TableSummary[]; automationRules: AutomationRule[]
|
||||
recentAutomationRuns: Array<{ runId: string; state: string; changedCells: number }>
|
||||
recentImports: Array<{ importId: string; sourceFilename: string; sourceFormat: string; tableCount: number }>
|
||||
unassignedTables: UnassignedTableSummary[]; documentCount: number; tableCount: number; automationRuleCount: number; unassignedCount: number
|
||||
}
|
||||
interface RecognitionCapability { state: string; modelApiState: string; fileTransferDefault: string; deterministicAdapterIds: string[] }
|
||||
type Mode = 'overview' | 'documents' | 'tables' | 'cleanup' | 'automation' | 'import'
|
||||
|
||||
function cleanError(error: unknown) { return String(error).replace(/^Error:\s*/, '') }
|
||||
function localTime(value: number) { return new Intl.DateTimeFormat('zh-CN', { dateStyle: 'medium', timeStyle: 'short' }).format(new Date(value)) }
|
||||
|
||||
export function EducationWorkspace({ onBack }: { onBack: () => void }) {
|
||||
const [snapshot, setSnapshot] = useState<EducationSnapshot | null>(null)
|
||||
const [capability, setCapability] = useState<RecognitionCapability | null>(null)
|
||||
const [mode, setMode] = useState<Mode>('overview')
|
||||
const [busy, setBusy] = useState(false)
|
||||
const [message, setMessage] = useState('')
|
||||
const [document, setDocument] = useState<EducationDocument | null>(null)
|
||||
const [documentTitle, setDocumentTitle] = useState('')
|
||||
const [documentBody, setDocumentBody] = useState('')
|
||||
const [table, setTable] = useState<EducationTable | null>(null)
|
||||
const [tableTitle, setTableTitle] = useState('')
|
||||
const [tableValue, setTableValue] = useState<{ columns: EducationColumn[]; rows: EducationRow[] }>({ columns: [], rows: [] })
|
||||
const [cleanup, setCleanup] = useState<EducationDataAnalysis | null>(null)
|
||||
const [rule, setRule] = useState<AutomationRule | null>(null)
|
||||
const [preview, setPreview] = useState<AutomationPreview | null>(null)
|
||||
|
||||
const refresh = async () => {
|
||||
const [nextSnapshot, nextCapability] = await Promise.all([
|
||||
invoke<EducationSnapshot>('get_education_workspace_snapshot'),
|
||||
invoke<RecognitionCapability>('get_education_recognition_capability'),
|
||||
])
|
||||
setSnapshot(nextSnapshot)
|
||||
setCapability(nextCapability)
|
||||
return nextSnapshot
|
||||
}
|
||||
|
||||
useEffect(() => { void refresh().catch((error) => setMessage(cleanError(error))) }, [])
|
||||
|
||||
const run = async (action: () => Promise<void>) => {
|
||||
setBusy(true); setMessage('')
|
||||
try { await action() } catch (error) { setMessage(cleanError(error)) } finally { setBusy(false) }
|
||||
}
|
||||
|
||||
const openDocument = (documentId: string) => run(async () => {
|
||||
const next = await invoke<EducationDocument>('read_education_document', { input: { documentId } })
|
||||
setDocument(next); setDocumentTitle(next.title); setDocumentBody(next.body); setMode('documents')
|
||||
})
|
||||
const createDocument = () => run(async () => {
|
||||
const next = await invoke<EducationDocument>('create_education_document', { input: { title: '冰朔教育迁移验收' } })
|
||||
setDocument(next); setDocumentTitle(next.title); setDocumentBody(next.body); setMode('documents'); await refresh()
|
||||
})
|
||||
const saveDocument = () => document && run(async () => {
|
||||
const next = await invoke<EducationDocument>('save_education_document', { input: { documentId: document.documentId, title: documentTitle, body: documentBody, expectedRevision: document.revision } })
|
||||
setDocument(next); setDocumentTitle(next.title); setDocumentBody(next.body); setMessage(`文档修订 ${next.revision} 已写入当前账号。`); await refresh()
|
||||
})
|
||||
const archiveDocument = () => document && run(async () => {
|
||||
await invoke('archive_education_document', { input: { documentId: document.documentId } }); setDocument(null); await refresh(); setMessage('文档已进入可恢复归档。')
|
||||
})
|
||||
|
||||
const openTable = (tableId: string, nextMode: Mode = 'tables') => run(async () => {
|
||||
const next = await invoke<EducationTable>('read_education_table', { input: { tableId } })
|
||||
setTable(next); setTableTitle(next.title); setTableValue({ columns: next.columns, rows: next.rows }); setCleanup(null); setPreview(null); setMode(nextMode)
|
||||
})
|
||||
const createTable = () => run(async () => {
|
||||
const next = await invoke<EducationTable>('create_education_table', { input: { title: '冰朔教育迁移验收表' } })
|
||||
setTable(next); setTableTitle(next.title); setTableValue({ columns: next.columns, rows: next.rows }); setMode('tables'); await refresh()
|
||||
})
|
||||
const addAcceptanceRow = () => {
|
||||
if (!tableValue.columns.length) return
|
||||
setTableValue((value) => ({ ...value, rows: [...value.rows, { rowId: `ROW-${crypto.randomUUID()}`, cells: value.columns.map((_, index) => index === 0 ? '冰朔验收' : index === value.columns.length - 1 ? '已迁移' : '教育工作台') }] }))
|
||||
}
|
||||
const saveTableValue = (rows = tableValue.rows, success = '表格已保存。') => table && run(async () => {
|
||||
const next = await invoke<EducationTable>('save_education_table', { input: { tableId: table.tableId, title: tableTitle, columns: tableValue.columns, rows, expectedRevision: table.revision } })
|
||||
setTable(next); setTableTitle(next.title); setTableValue({ columns: next.columns, rows: next.rows }); setCleanup(null); setPreview(null); setMessage(`${success} 当前修订 ${next.revision}。`); await refresh()
|
||||
})
|
||||
const archiveTable = () => table && run(async () => {
|
||||
await invoke('archive_education_table', { input: { tableId: table.tableId } }); setTable(null); await refresh(); setMessage('表格已进入可恢复归档。')
|
||||
})
|
||||
|
||||
const analyzeCleanup = () => setCleanup(analyzeEducationTableData(tableValue))
|
||||
const applyCleanup = () => cleanup && saveTableValue(cleanup.cleanedRows, '清理结果经人工确认后已保存。')
|
||||
|
||||
const createRule = () => table && table.columns.length > 0 && run(async () => {
|
||||
const next = await invoke<AutomationRule>('create_education_automation_rule', { input: {
|
||||
title: '验收状态自动化', tableId: table.tableId, conditionColumnId: table.columns[0].columnId, operator: 'CONTAINS', conditionValue: '冰朔',
|
||||
actionColumnId: table.columns[table.columns.length - 1].columnId, actionValue: '自动化已验收',
|
||||
} })
|
||||
setRule(next); setPreview(null); setMode('automation'); await refresh(); setMessage('规则已建立,但尚未执行。')
|
||||
})
|
||||
const chooseRule = async (nextRule: AutomationRule) => {
|
||||
setRule(nextRule); setPreview(null)
|
||||
if (!table || table.tableId !== nextRule.tableId) await openTable(nextRule.tableId, 'automation')
|
||||
else setMode('automation')
|
||||
}
|
||||
const previewRule = () => rule && table && run(async () => {
|
||||
const next = await invoke<AutomationPreview>('preview_education_automation_rule', { input: { ruleId: rule.ruleId, expectedRuleRevision: rule.revision, expectedTableRevision: table.revision } })
|
||||
setPreview(next); setMessage(`只读预览:命中 ${next.matchedRows} 行,将改变 ${next.changedCells} 个单元格。`)
|
||||
})
|
||||
const executeRule = () => rule && table && preview && run(async () => {
|
||||
const receipt = await invoke<{ state: string; changedCells: number; toTableRevision: number }>('execute_education_automation_rule', { input: {
|
||||
ruleId: rule.ruleId, expectedRuleRevision: preview.ruleRevision, expectedTableRevision: preview.tableRevision, previewToken: preview.previewToken,
|
||||
} })
|
||||
setPreview(null); await openTable(table.tableId, 'automation'); await refresh(); setMessage(`自动化 ${receipt.state},改变 ${receipt.changedCells} 个单元格,修订 ${receipt.toTableRevision}。`)
|
||||
})
|
||||
|
||||
const importTables = () => run(async () => {
|
||||
const outcome = await invoke<{ state: string } | null>('import_education_tables_from_dialog')
|
||||
await refresh(); setMode('import'); setMessage(outcome ? `导入结果:${outcome.state}。数据仍在未归属区,需人工确认。` : '已取消导入。')
|
||||
})
|
||||
const assignTable = (tableId: string) => run(async () => {
|
||||
await invoke('assign_imported_table_to_education', { input: { tableId } }); await refresh(); setMessage('已由人工确认归入教育频道。')
|
||||
})
|
||||
const exportTable = (format: string) => table && run(async () => {
|
||||
const result = await invoke<{ state: string; targetFilename: string } | null>('export_education_table_to_dialog', { input: { table: { tableId: table.tableId, title: tableTitle, columns: tableValue.columns, rows: tableValue.rows, revision: table.revision }, format } })
|
||||
setMessage(result ? `${result.targetFilename} 已导出。` : '已取消导出。')
|
||||
})
|
||||
|
||||
const analysis = useMemo(() => table ? analyzeEducationTableData(tableValue) : null, [table, tableValue])
|
||||
if (!snapshot) return <section className="education-world"><button type="button" onClick={onBack}>← 返回我的频道</button><p>{message || '正在读取当前账号的教育工作台……'}</p></section>
|
||||
|
||||
return <section className="education-world">
|
||||
<header className="education-hero"><button type="button" onClick={onBack}>← 返回我的频道</button><div><span>OFFICIAL NUMBERED MODULE</span><h1>教育工作台</h1><p>文档、表格、清理、导入和自动化都沿唯一编号路由进入当前账号的原生存储。</p></div><aside><b>{snapshot.state}</b><small>{EDUCATION_MODULE_NUMBER}</small></aside></header>
|
||||
<nav className="education-tabs" aria-label="教育工作台功能">{(['overview','documents','tables','cleanup','automation','import'] as Mode[]).map((item) => <button type="button" key={item} className={mode === item ? 'active' : ''} onClick={() => setMode(item)}>{{ overview:'总览', documents:'文档', tables:'表格', cleanup:'数据清理', automation:'自动化', import:'导入与识别' }[item]}</button>)}</nav>
|
||||
|
||||
{mode === 'overview' && <div className="education-overview">
|
||||
<article><span>教育文档</span><strong>{snapshot.documentCount}</strong><button type="button" disabled={busy} onClick={createDocument}>新建文档</button></article>
|
||||
<article><span>教育表格</span><strong>{snapshot.tableCount}</strong><button type="button" disabled={busy} onClick={createTable}>新建表格</button></article>
|
||||
<article><span>人工待归属</span><strong>{snapshot.unassignedCount}</strong><button type="button" disabled={busy} onClick={importTables}>导入文件</button></article>
|
||||
<article><span>自动化规则</span><strong>{snapshot.automationRuleCount}</strong><small>只可预览后人工执行</small></article>
|
||||
<section><h2>最近文档</h2>{snapshot.documents.map((item) => <button type="button" key={item.documentId} onClick={() => void openDocument(item.documentId)}><b>{item.title}</b><small>修订 {item.revision} · {localTime(item.updatedAtUnixMs)}</small></button>)}</section>
|
||||
<section><h2>最近表格</h2>{snapshot.tables.map((item) => <button type="button" key={item.tableId} onClick={() => void openTable(item.tableId)}><b>{item.title}</b><small>{item.rowCount} 行 × {item.columnCount} 列 · 修订 {item.revision}</small></button>)}</section>
|
||||
</div>}
|
||||
|
||||
{mode === 'documents' && <div className="education-editor-shell"><aside><button type="button" onClick={createDocument}>+ 新建文档</button>{snapshot.documents.map((item) => <button type="button" className={document?.documentId === item.documentId ? 'active' : ''} key={item.documentId} onClick={() => void openDocument(item.documentId)}>{item.title}<small>修订 {item.revision}</small></button>)}</aside><main>{document ? <><header><input aria-label="教育文档标题" value={documentTitle} onChange={(event) => setDocumentTitle(event.target.value)}/><div><button type="button" className="danger" onClick={archiveDocument}>归档</button><button type="button" disabled={busy} onClick={saveDocument}>保存修订</button></div></header><Suspense fallback={<p>正在装载文档引擎……</p>}><EducationDocumentEngine key={`${document.documentId}-${document.revision}`} body={documentBody} onChange={setDocumentBody}/></Suspense></> : <p>选择或新建一份教育文档。</p>}</main></div>}
|
||||
|
||||
{mode === 'tables' && <div className="education-editor-shell"><aside><button type="button" onClick={createTable}>+ 新建表格</button>{snapshot.tables.map((item) => <button type="button" className={table?.tableId === item.tableId ? 'active' : ''} key={item.tableId} onClick={() => void openTable(item.tableId)}>{item.title}<small>{item.rowCount} 行 · 修订 {item.revision}</small></button>)}</aside><main>{table ? <><header><input aria-label="教育表格标题" value={tableTitle} onChange={(event) => setTableTitle(event.target.value)}/><div><button type="button" onClick={addAcceptanceRow}>添加验收行</button><button type="button" onClick={() => void exportTable('XLSX')}>导出 XLSX</button><button type="button" className="danger" onClick={archiveTable}>归档</button><button type="button" disabled={busy} onClick={() => void saveTableValue()}>保存修订</button></div></header><Suspense fallback={<p>正在装载真实单元格引擎……</p>}><ChannelSpreadsheetEngine key={`${table.tableId}-${table.revision}`} tableId={table.tableId} title={tableTitle} columns={tableValue.columns} rows={tableValue.rows} onChange={setTableValue}/></Suspense></> : <p>选择或新建一份教育表格。</p>}</main></div>}
|
||||
|
||||
{mode === 'cleanup' && <div className="education-action-layout"><aside><h2>选择表格</h2>{snapshot.tables.map((item) => <button type="button" key={item.tableId} onClick={() => void openTable(item.tableId, 'cleanup')}>{item.title}</button>)}</aside><main><h2>人工确认的数据清理</h2>{table && analysis ? <><p>{table.title} · {analysis.originalRows} 行。系统不会后台自动修改。</p><div className="education-metrics"><b>{analysis.trimmedCells}<small>边界空白</small></b><b>{analysis.emptyRows}<small>空行</small></b><b>{analysis.duplicateRows}<small>重复行</small></b></div><button type="button" onClick={analyzeCleanup}>生成只读预览</button>{cleanup && <section className="education-preview"><h3>变更预览 · 共 {cleanup.changeCount} 项</h3>{cleanup.changePreview.map((item, index) => <p key={`${item.kind}-${index}`}><b>{item.kind}</b> 第 {item.rowNumber} 行 / {item.columnTitle}:{item.before} → {item.after}</p>)}<button type="button" disabled={busy} onClick={applyCleanup}>我确认,写入清理结果</button></section>}</> : <p>请先选择表格。</p>}</main></div>}
|
||||
|
||||
{mode === 'automation' && <div className="education-action-layout"><aside><h2>规则</h2>{snapshot.automationRules.map((item) => <button type="button" className={rule?.ruleId === item.ruleId ? 'active' : ''} key={item.ruleId} onClick={() => void chooseRule(item)}>{item.title}<small>修订 {item.revision}</small></button>)}</aside><main><h2>预览后执行</h2>{table && <button type="button" onClick={createRule}>为当前表格建立验收规则</button>}{rule && table ? <section className="education-rule"><p><b>{rule.title}</b></p><p>条件:{rule.operator} “{rule.conditionValue}” · 动作:写入 “{rule.actionValue}”</p><button type="button" disabled={busy} onClick={previewRule}>生成只读预览令牌</button>{preview && <div className="education-preview"><p>命中 {preview.matchedRows} 行,将改变 {preview.changedCells} 个单元格。</p><code>{preview.previewToken.slice(0, 20)}…</code><button type="button" disabled={busy} onClick={executeRule}>我确认,按此令牌执行</button></div>}</section> : <p>先在“表格”中打开表格,再建立规则;或选择已有规则。</p>}</main></div>}
|
||||
|
||||
{mode === 'import' && <div className="education-import"><header><div><h2>外部表格翻译层</h2><p>本机先识别结构;导入默认进入未归属区,不会自动判断行业。</p></div><button type="button" disabled={busy} onClick={importTables}>选择 XLSX / CSV / TSV / ODS</button></header><dl><div><dt>确定性适配器</dt><dd>{capability?.deterministicAdapterIds.join(' · ')}</dd></div><div><dt>模型接口</dt><dd>{capability?.modelApiState}</dd></div><div><dt>文件外传</dt><dd>{capability?.fileTransferDefault}</dd></div></dl><section><h3>待人工归属</h3>{snapshot.unassignedTables.length ? snapshot.unassignedTables.map((item) => <article key={item.tableId}><div><b>{item.title}</b><small>{item.rowCount} 行 × {item.columnCount} 列</small></div><button type="button" onClick={() => void assignTable(item.tableId)}>确认归入教育频道</button></article>) : <p>没有待归属数据。</p>}</section></div>}
|
||||
{message && <p className="education-message" role="status">{message}</p>}
|
||||
</section>
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
.education-world{min-height:100%;padding:28px;color:#eaf3f3;background:radial-gradient(circle at 75% 0,rgba(84,210,190,.13),transparent 36%),linear-gradient(150deg,#0b1216,#111c21 58%,#0b1318)}
|
||||
.education-world button,.education-world input{font:inherit}.education-hero{display:grid;grid-template-columns:auto 1fr auto;gap:22px;align-items:start}.education-hero>button{background:transparent;border:0;color:#91aaa9;padding:10px}.education-hero span{font-size:11px;letter-spacing:.18em;color:#74d6c5}.education-hero h1{font-size:38px;margin:5px 0}.education-hero p{margin:0;color:#9db1b2}.education-hero aside{display:flex;flex-direction:column;align-items:flex-end;gap:5px}.education-hero aside b{color:#79d9c7}.education-hero aside small{color:#718588;max-width:260px;text-align:right}
|
||||
.education-tabs{display:flex;gap:8px;margin:24px 0;border-bottom:1px solid #263a3d}.education-tabs button{border:0;background:transparent;color:#8da3a4;padding:12px 15px;border-bottom:2px solid transparent}.education-tabs button.active{color:#eafffb;border-color:#63d5c2}
|
||||
.education-overview{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}.education-overview>article,.education-overview>section,.education-import,.education-action-layout>aside,.education-action-layout>main{background:#121f24;border:1px solid #26393d;border-radius:16px;padding:18px}.education-overview>article{display:flex;flex-direction:column;gap:8px}.education-overview>article strong{font-size:30px}.education-overview button,.education-editor-shell button,.education-action-layout button,.education-import button{border:1px solid #345155;background:#172b30;color:#dffbf6;border-radius:9px;padding:9px 12px}.education-overview>section{grid-column:span 2}.education-overview>section>button{width:100%;display:flex;justify-content:space-between;margin-top:8px;text-align:left}.education-overview small{color:#8ba0a2}
|
||||
.education-editor-shell{display:grid;grid-template-columns:230px minmax(0,1fr);gap:14px;min-height:650px}.education-editor-shell>aside{display:flex;flex-direction:column;gap:7px;background:#0e191d;border:1px solid #26393d;border-radius:14px;padding:12px}.education-editor-shell>aside button{text-align:left;display:flex;flex-direction:column}.education-editor-shell button.active{border-color:#62d3c0;background:#1b3537}.education-editor-shell>main{min-width:0;background:#f7faf9;color:#172326;border-radius:14px;overflow:hidden}.education-editor-shell>main>header{display:flex;justify-content:space-between;gap:12px;padding:12px;background:#e8f0ee;border-bottom:1px solid #ccd9d6}.education-editor-shell>main>header input{flex:1;border:0;background:transparent;font-size:19px;font-weight:700;outline:0}.education-editor-shell>main>header>div{display:flex;gap:7px}.education-editor-shell button.danger{color:#ffc4bd;border-color:#734841;background:#33211f}
|
||||
.education-action-layout{display:grid;grid-template-columns:250px 1fr;gap:14px}.education-action-layout aside{display:flex;flex-direction:column;gap:8px}.education-action-layout main{min-height:420px}.education-metrics{display:flex;gap:12px;margin:18px 0}.education-metrics b{font-size:26px;background:#0d191d;border-radius:12px;padding:14px 22px}.education-metrics small{font-size:11px;display:block;color:#91a6a7}.education-preview{margin-top:15px;padding:16px;border:1px solid #40726d;background:#102725;border-radius:12px}.education-preview p{color:#c9d7d5}.education-preview code{display:block;color:#78dbc9;margin:10px 0}.education-rule{margin-top:18px}
|
||||
.education-import>header{display:flex;justify-content:space-between;align-items:center}.education-import dl{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}.education-import dl div{background:#0d181c;border-radius:10px;padding:12px}.education-import dt{color:#789092;font-size:12px}.education-import dd{margin:5px 0 0;overflow-wrap:anywhere}.education-import section article{display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-top:1px solid #273b3e}.education-import section article div{display:flex;flex-direction:column}.education-import small{color:#89a0a1}.education-message{position:sticky;bottom:14px;background:#17302f;border:1px solid #4e8b83;color:#d8fff8;border-radius:12px;padding:12px 16px;margin:16px 0 0}
|
||||
@media(max-width:900px){.education-overview{grid-template-columns:repeat(2,1fr)}.education-editor-shell,.education-action-layout{grid-template-columns:1fr}.education-editor-shell>aside{max-height:180px;overflow:auto}.education-hero{grid-template-columns:1fr}.education-hero>button{justify-self:start}.education-import dl{grid-template-columns:1fr}}
|
||||
|
|
@ -672,6 +672,150 @@ const ROUTES = {
|
|||
"moduleNumber": "HLP-NIPC-MOD-0023",
|
||||
"operationNumber": "HLP-NIPC-OP-0084",
|
||||
"targetNumber": "HLP-NIPC-TGT-0023"
|
||||
},
|
||||
"get_education_workspace_snapshot": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0024",
|
||||
"operationNumber": "HLP-NIPC-OP-0085",
|
||||
"targetNumber": "HLP-NIPC-TGT-0024"
|
||||
},
|
||||
"create_education_document": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0024",
|
||||
"operationNumber": "HLP-NIPC-OP-0086",
|
||||
"targetNumber": "HLP-NIPC-TGT-0024"
|
||||
},
|
||||
"read_education_document": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0024",
|
||||
"operationNumber": "HLP-NIPC-OP-0087",
|
||||
"targetNumber": "HLP-NIPC-TGT-0024"
|
||||
},
|
||||
"save_education_document": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0024",
|
||||
"operationNumber": "HLP-NIPC-OP-0088",
|
||||
"targetNumber": "HLP-NIPC-TGT-0024"
|
||||
},
|
||||
"archive_education_document": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0024",
|
||||
"operationNumber": "HLP-NIPC-OP-0089",
|
||||
"targetNumber": "HLP-NIPC-TGT-0024"
|
||||
},
|
||||
"create_education_table": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0024",
|
||||
"operationNumber": "HLP-NIPC-OP-0090",
|
||||
"targetNumber": "HLP-NIPC-TGT-0024"
|
||||
},
|
||||
"read_education_table": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0024",
|
||||
"operationNumber": "HLP-NIPC-OP-0091",
|
||||
"targetNumber": "HLP-NIPC-TGT-0024"
|
||||
},
|
||||
"save_education_table": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0024",
|
||||
"operationNumber": "HLP-NIPC-OP-0092",
|
||||
"targetNumber": "HLP-NIPC-TGT-0024"
|
||||
},
|
||||
"archive_education_table": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0024",
|
||||
"operationNumber": "HLP-NIPC-OP-0093",
|
||||
"targetNumber": "HLP-NIPC-TGT-0024"
|
||||
},
|
||||
"assign_imported_table_to_education": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0024",
|
||||
"operationNumber": "HLP-NIPC-OP-0094",
|
||||
"targetNumber": "HLP-NIPC-TGT-0024"
|
||||
},
|
||||
"import_education_tables_from_dialog": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0024",
|
||||
"operationNumber": "HLP-NIPC-OP-0095",
|
||||
"targetNumber": "HLP-NIPC-TGT-0024"
|
||||
},
|
||||
"export_education_table_to_dialog": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0024",
|
||||
"operationNumber": "HLP-NIPC-OP-0096",
|
||||
"targetNumber": "HLP-NIPC-TGT-0024"
|
||||
},
|
||||
"get_education_recognition_capability": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0024",
|
||||
"operationNumber": "HLP-NIPC-OP-0097",
|
||||
"targetNumber": "HLP-NIPC-TGT-0024"
|
||||
},
|
||||
"create_education_automation_rule": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0024",
|
||||
"operationNumber": "HLP-NIPC-OP-0098",
|
||||
"targetNumber": "HLP-NIPC-TGT-0024"
|
||||
},
|
||||
"save_education_automation_rule": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0024",
|
||||
"operationNumber": "HLP-NIPC-OP-0099",
|
||||
"targetNumber": "HLP-NIPC-TGT-0024"
|
||||
},
|
||||
"archive_education_automation_rule": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0024",
|
||||
"operationNumber": "HLP-NIPC-OP-0100",
|
||||
"targetNumber": "HLP-NIPC-TGT-0024"
|
||||
},
|
||||
"preview_education_automation_rule": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0024",
|
||||
"operationNumber": "HLP-NIPC-OP-0101",
|
||||
"targetNumber": "HLP-NIPC-TGT-0024"
|
||||
},
|
||||
"execute_education_automation_rule": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0024",
|
||||
"operationNumber": "HLP-NIPC-OP-0102",
|
||||
"targetNumber": "HLP-NIPC-TGT-0024"
|
||||
}
|
||||
} as const
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue