feat: admit signed native composition module
This commit is contained in:
parent
7673c337fc
commit
593d5e5884
22 changed files with 1735 additions and 28 deletions
|
|
@ -2,6 +2,7 @@ import { StrictMode, useCallback, useEffect, useMemo, useRef, useState } from 'r
|
|||
import { createRoot } from 'react-dom/client'
|
||||
import { numberedInvoke as invoke } from './modules/numbered-ipc'
|
||||
import { splitFrontmatter, documentOutline, jumpToHeading, MarkdownDocument, markdownHtml } from './modules/knowledge-render'
|
||||
import { NativeCompositionStudio, type CompositionDimension, type CompositionMeasure, type NativeCompositionProjection, type ProjectionView } from './modules/native-composition'
|
||||
|
||||
const TAG_TINTS = ['tag-lavender', 'tag-sky', 'tag-mint', 'tag-amber', 'tag-rose', 'tag-slate']
|
||||
|
||||
|
|
@ -48,10 +49,21 @@ import './design-tokens.css'
|
|||
import './styles.css'
|
||||
|
||||
type ThemeId = 'night' | 'dawn' | 'nebula' | 'candle' | 'clear'
|
||||
type ViewId = 'overview' | 'knowledge' | 'code' | 'receipts' | 'system'
|
||||
type ViewId = 'overview' | 'knowledge' | 'composition' | 'code' | 'receipts' | 'system'
|
||||
type WorldStage = 'domain' | 'heart' | 'heartbeat' | 'lightLake' | 'love' | 'tomorrow' | 'bottle' | 'channel' | 'enterpriseWork' | 'personalNodeGuide' | 'tool'
|
||||
type KnowledgeSource = 'native' | 'legacy'
|
||||
|
||||
interface BundledModuleDescriptor {
|
||||
moduleNumber: string
|
||||
displayName: string
|
||||
version: string
|
||||
adapter: string
|
||||
permissions: string[]
|
||||
packageSha256: string
|
||||
installedState: string
|
||||
signatureVerified: boolean
|
||||
}
|
||||
|
||||
interface HomeStatus {
|
||||
directLocalBrokerState: string
|
||||
directConnectionCount: number
|
||||
|
|
@ -351,7 +363,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: '知识空间', code: '人格代码频道', receipts: '运行回执', system: '系统详情' }
|
||||
const viewLabels: Record<ViewId, string> = { overview: '个人频道', knowledge: '知识空间', composition: '结构组合', 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'],
|
||||
|
|
@ -577,6 +589,13 @@ function HoloLakeApp() {
|
|||
const [searchResults, setSearchResults] = useState<KnowledgeSearchResult[] | null>(null)
|
||||
const [knowledgeBusy, setKnowledgeBusy] = useState(false)
|
||||
const [knowledgeMessage, setKnowledgeMessage] = useState('')
|
||||
const [compositionModule, setCompositionModule] = useState<BundledModuleDescriptor | null>(null)
|
||||
const [compositionProjection, setCompositionProjection] = useState<NativeCompositionProjection | null>(null)
|
||||
const [compositionDimension, setCompositionDimension] = useState<CompositionDimension>('TOP_LEVEL_FOLDER')
|
||||
const [compositionMeasure, setCompositionMeasure] = useState<CompositionMeasure>('DOCUMENT_COUNT')
|
||||
const [compositionViews, setCompositionViews] = useState<ProjectionView[]>(['DASHBOARD', 'VERTICAL_BAR', 'TABLE'])
|
||||
const [compositionBusy, setCompositionBusy] = useState(false)
|
||||
const [compositionMessage, setCompositionMessage] = useState('')
|
||||
const [expanded, setExpanded] = useState<Set<string>>(() => new Set(['导入']))
|
||||
const [editing, setEditing] = useState(false)
|
||||
const [draft, setDraft] = useState('')
|
||||
|
|
@ -1015,6 +1034,51 @@ function HoloLakeApp() {
|
|||
finally { setKnowledgeBusy(false) }
|
||||
}
|
||||
|
||||
const refreshCompositionModule = async () => {
|
||||
try {
|
||||
const catalog = await invoke<BundledModuleDescriptor[]>('get_bundled_module_catalog')
|
||||
const module = catalog.find((item) => item.moduleNumber === 'HLP-MOD-LOCAL-NATIVE-COMPOSITION-0001') || null
|
||||
setCompositionModule(module)
|
||||
return module
|
||||
} catch (error) {
|
||||
setCompositionMessage(humanError(error, 'system'))
|
||||
return null
|
||||
}
|
||||
}
|
||||
const executeComposition = async () => {
|
||||
setCompositionBusy(true)
|
||||
setCompositionMessage('')
|
||||
try {
|
||||
const projection = await invoke<NativeCompositionProjection>('execute_knowledge_native_composition', {
|
||||
input: { dimension: compositionDimension, measure: compositionMeasure, views: compositionViews },
|
||||
})
|
||||
setCompositionProjection(projection)
|
||||
} catch (error) {
|
||||
setCompositionMessage(humanError(error, 'knowledge'))
|
||||
} finally { setCompositionBusy(false) }
|
||||
}
|
||||
const activateCompositionModule = async () => {
|
||||
setCompositionBusy(true)
|
||||
setCompositionMessage('正在验证签名、登记编号并执行模块自检……')
|
||||
try {
|
||||
await invoke('activate_bundled_module', {
|
||||
input: {
|
||||
moduleNumber: 'HLP-MOD-LOCAL-NATIVE-COMPOSITION-0001',
|
||||
humanConfirmedPermissionExpansion: true,
|
||||
},
|
||||
})
|
||||
const module = await refreshCompositionModule()
|
||||
if (!module || module.installedState !== 'ACTIVE') throw new Error('HOLOLAKE_MODULE_NOT_ACTIVE')
|
||||
setCompositionMessage('模块已通过签名、编号、权限与自检验收。')
|
||||
const projection = await invoke<NativeCompositionProjection>('execute_knowledge_native_composition', {
|
||||
input: { dimension: compositionDimension, measure: compositionMeasure, views: compositionViews },
|
||||
})
|
||||
setCompositionProjection(projection)
|
||||
} catch (error) {
|
||||
setCompositionMessage(humanError(error, 'system'))
|
||||
} finally { setCompositionBusy(false) }
|
||||
}
|
||||
|
||||
const cloneCodeChannel = async (event: React.FormEvent) => {
|
||||
event.preventDefault()
|
||||
if (!cloneUrl.trim()) return
|
||||
|
|
@ -1362,6 +1426,31 @@ function HoloLakeApp() {
|
|||
</section>
|
||||
)
|
||||
|
||||
const renderComposition = () => (
|
||||
<section className="full-workbench composition-workspace-world">
|
||||
{compositionModule?.installedState === 'ACTIVE'
|
||||
? <NativeCompositionStudio
|
||||
projection={compositionProjection}
|
||||
dimension={compositionDimension}
|
||||
measure={compositionMeasure}
|
||||
selectedViews={compositionViews}
|
||||
busy={compositionBusy}
|
||||
message={compositionMessage}
|
||||
onDimensionChange={setCompositionDimension}
|
||||
onMeasureChange={setCompositionMeasure}
|
||||
onViewsChange={setCompositionViews}
|
||||
onExecute={() => void executeComposition()}/>
|
||||
: <div className="workbench-empty">
|
||||
<span>组</span>
|
||||
<h2>启用原生组合视图</h2>
|
||||
<p>模块将读取当前账号的知识目录,生成只读仪表盘、对比、柱状图、分类和明细表。它不能修改知识原文,也不执行仓库代码或网页脚本。</p>
|
||||
<code>HLP-MOD-LOCAL-NATIVE-COMPOSITION-0001 · KNOWLEDGE_READ</code>
|
||||
<button className="primary-button" type="button" disabled={compositionBusy} onClick={() => void activateCompositionModule()}>{compositionBusy ? '正在验收模块…' : '确认权限并启用'}</button>
|
||||
{compositionMessage && <p>{compositionMessage}</p>}
|
||||
</div>}
|
||||
</section>
|
||||
)
|
||||
|
||||
const renderCode = () => (
|
||||
<section className="full-workbench code-workbench">
|
||||
<aside className="code-channels">
|
||||
|
|
@ -1573,6 +1662,7 @@ function HoloLakeApp() {
|
|||
<div className="world-location"><h1>明天见频道</h1><p>频道全景 · 湖面按操作继续展开</p></div>
|
||||
<LakePool className="channel-main" title="频道全景" meta="明天见 · 私人生活区" open onClick={() => openWorldTool('overview')}/>
|
||||
<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={userPncc?.state === 'READY' ? '明天见 · Git 已就绪' : '正在建立'} onClick={() => openWorldTool('code')}/>
|
||||
<LakePool className="channel-light" title="奶瓶频道" meta="前往陪伴宝宝人格体" onClick={() => setWorldStage('bottle')}/>
|
||||
{timeAuthorityModule && <LakePool className="channel-time" title="时间主控" meta={beijingCoordinate ? `光湖历第 ${beijingCoordinate.guanghuEraDay} 天` : '北京时间正在流动'} onClick={openEraTimeline}/>}
|
||||
|
|
@ -1628,6 +1718,7 @@ function HoloLakeApp() {
|
|||
<div className="world-location"><h1>心跳核心频道</h1><p>冰朔 · ICE-GL∞ · 私人频道全景</p></div>
|
||||
<LakePool className="channel-main" title="频道全景" meta="心跳核心频道" open onClick={() => openWorldTool('overview')}/>
|
||||
<LakePool className="channel-knowledge" title="知识空间" meta={`${knowledge.uniqueDocumentCount} 唯一页`} onClick={() => openWorldTool('knowledge')}/>
|
||||
<LakePool className="channel-light" title="结构组合" meta="签名模块 · 只读知识投影" onClick={() => { openWorldTool('composition'); void refreshCompositionModule() }}/>
|
||||
{timeAuthorityModule && <LakePool className="channel-time" title="时间主控" meta={beijingCoordinate ? `光湖历第 ${beijingCoordinate.guanghuEraDay} 天` : '北京时间正在流动'} onClick={openEraTimeline}/>}
|
||||
<LakePool className="channel-status" title="湖面天气" meta={connectionLabel} onClick={() => openWorldTool('system')}/>
|
||||
</section>}
|
||||
|
|
@ -1640,7 +1731,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 === 'code' ? renderCode() : view === 'receipts' ? renderReceipts() : renderSystem()}</div>
|
||||
<div className={`tool-projection${inspectorOpen ? '' : ' inspector-closed'}`}>{view === 'overview' ? renderOverview() : view === 'knowledge' ? renderKnowledge() : view === 'composition' ? renderComposition() : view === 'code' ? renderCode() : view === 'receipts' ? renderReceipts() : renderSystem()}</div>
|
||||
</section>}
|
||||
</main>
|
||||
<footer className="world-footer"><b>光湖语言系统 · 通用人工智能操作平台</b><span>GH-AIOS</span></footer>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,137 @@
|
|||
import type { CSSProperties } from 'react'
|
||||
import './styles.css'
|
||||
export { nativeCompositionManifest } from './manifest'
|
||||
|
||||
export type CompositionDimension = 'SOURCE' | 'TOP_LEVEL_FOLDER'
|
||||
export type CompositionMeasure = 'DOCUMENT_COUNT' | 'TOTAL_BYTES' | 'DUPLICATE_COUNT'
|
||||
export type ProjectionView = 'DASHBOARD' | 'COMPARISON' | 'VERTICAL_BAR' | 'CLASSIFICATION' | 'TABLE'
|
||||
|
||||
export interface NativeCompositionRow {
|
||||
rowId: string
|
||||
source: string
|
||||
path: string
|
||||
title: string
|
||||
topLevelFolder: string
|
||||
sizeBytes: number
|
||||
duplicateCount: number
|
||||
updatedAtUnixMs: number
|
||||
}
|
||||
export interface NativeCompositionGroup {
|
||||
key: string
|
||||
label: string
|
||||
documentCount: number
|
||||
totalBytes: number
|
||||
duplicateCount: number
|
||||
measureValue: number
|
||||
share: number
|
||||
}
|
||||
|
||||
export interface NativeCompositionProjection {
|
||||
schema: string
|
||||
state: string
|
||||
executionId: string
|
||||
executedAtUnixMs: number
|
||||
sourceIsRealAccountData: boolean
|
||||
readOnly: boolean
|
||||
dimension: CompositionDimension
|
||||
measure: CompositionMeasure
|
||||
views: ProjectionView[]
|
||||
nativeObject: {
|
||||
schema: string
|
||||
objectId: string
|
||||
title: string
|
||||
rowCount: number
|
||||
truncated: boolean
|
||||
sourceReceipt: string
|
||||
rows: NativeCompositionRow[]
|
||||
}
|
||||
groups: NativeCompositionGroup[]
|
||||
metrics: {
|
||||
rawDocumentCount: number
|
||||
uniqueDocumentCount: number
|
||||
duplicateDocumentCount: number
|
||||
totalBytes: number
|
||||
groupCount: number
|
||||
}
|
||||
recipe: {
|
||||
schema: string
|
||||
recipeId: string
|
||||
title: string
|
||||
nodes: { nodeId: string; moduleId: string }[]
|
||||
edges: { from: string; to: string }[]
|
||||
}
|
||||
dataSha256: string
|
||||
receiptSha256: string
|
||||
}
|
||||
|
||||
const viewNames: Record<ProjectionView, string> = {
|
||||
DASHBOARD: '仪表盘',
|
||||
COMPARISON: '对比',
|
||||
VERTICAL_BAR: '柱状图',
|
||||
CLASSIFICATION: '分类',
|
||||
TABLE: '明细表',
|
||||
}
|
||||
|
||||
const measureNames: Record<CompositionMeasure, string> = {
|
||||
DOCUMENT_COUNT: '文档数量',
|
||||
TOTAL_BYTES: '数据量',
|
||||
DUPLICATE_COUNT: '重复数量',
|
||||
}
|
||||
|
||||
export function formatCompositionValue(value: number, measure: CompositionMeasure): string {
|
||||
if (measure !== 'TOTAL_BYTES') return `${value.toLocaleString('zh-CN')} 篇`
|
||||
if (value < 1024) return `${value.toLocaleString('zh-CN')} B`
|
||||
if (value < 1024 * 1024) return `${(value / 1024).toFixed(value < 10 * 1024 ? 1 : 0)} KB`
|
||||
return `${(value / 1024 / 1024).toFixed(1)} MB`
|
||||
}
|
||||
|
||||
export function toggleProjectionView(current: ProjectionView[], view: ProjectionView): ProjectionView[] {
|
||||
if (current.includes(view)) return current.length === 1 ? current : current.filter((item) => item !== view)
|
||||
return [...current, view]
|
||||
}
|
||||
|
||||
interface NativeCompositionStudioProps {
|
||||
projection: NativeCompositionProjection | null
|
||||
dimension: CompositionDimension
|
||||
measure: CompositionMeasure
|
||||
selectedViews: ProjectionView[]
|
||||
busy: boolean
|
||||
message: string
|
||||
onDimensionChange: (value: CompositionDimension) => void
|
||||
onMeasureChange: (value: CompositionMeasure) => void
|
||||
onViewsChange: (value: ProjectionView[]) => void
|
||||
onExecute: () => void
|
||||
}
|
||||
|
||||
export function NativeCompositionStudio(props: NativeCompositionStudioProps) {
|
||||
const { projection, dimension, measure, selectedViews, busy, message } = props
|
||||
const groups = projection?.groups || []
|
||||
const activeDimension = projection?.dimension || dimension
|
||||
const activeMeasure = projection?.measure || measure
|
||||
const activeViews = projection?.views || selectedViews
|
||||
const maximum = Math.max(1, ...groups.map((group) => group.measureValue))
|
||||
return <div className="native-composition-layout">
|
||||
<aside className="composition-recipe-panel">
|
||||
<header><span>原生组合配方</span><h2>知识结构观察</h2><p>同一份数据 · 多种动态投影</p></header>
|
||||
<section className="composition-source"><i/><div><span>真实数据源</span><b>当前账号知识目录</b><small>{projection ? `${projection.nativeObject.rowCount} 条原生对象` : '等待系统读取'}</small></div></section>
|
||||
<div className="composition-connector" aria-hidden="true"/>
|
||||
<label><span>分类维度</span><select aria-label="组合分类维度" value={dimension} onChange={(event) => props.onDimensionChange(event.target.value as CompositionDimension)}><option value="TOP_LEVEL_FOLDER">一级目录</option><option value="SOURCE">知识来源</option></select></label>
|
||||
<label><span>统计指标</span><select aria-label="组合统计指标" value={measure} onChange={(event) => props.onMeasureChange(event.target.value as CompositionMeasure)}><option value="DOCUMENT_COUNT">文档数量</option><option value="TOTAL_BYTES">数据量</option><option value="DUPLICATE_COUNT">重复数量</option></select></label>
|
||||
<fieldset><legend>人类投影</legend>{(Object.keys(viewNames) as ProjectionView[]).map((view) => <label key={view}><input type="checkbox" checked={selectedViews.includes(view)} onChange={() => props.onViewsChange(toggleProjectionView(selectedViews, view))}/><span>{viewNames[view]}</span></label>)}</fieldset>
|
||||
<button className="composition-execute" type="button" disabled={busy} onClick={props.onExecute}>{busy ? '系统正在组合…' : '按当前配方重新组合'}</button>
|
||||
<footer>{projection ? <><b>已由原生内核执行</b><span>回执 {projection.receiptSha256.slice(0, 12)}</span><small>{new Date(projection.executedAtUnixMs).toLocaleString('zh-CN')}</small></> : <span>{message || '组合执行只读,不修改知识原文。'}</span>}</footer>
|
||||
</aside>
|
||||
<main className="composition-projection-stage" aria-busy={busy}>
|
||||
<header><div><span>HUMAN PROJECTION</span><h1>知识空间结构组合</h1><p>{projection ? `按${activeDimension === 'SOURCE' ? '知识来源' : '一级目录'}观察${measureNames[activeMeasure]} · 数据来自当前账号真实知识目录` : '正在准备当前账号的原生知识对象'}</p></div><div className="composition-state"><i/><span>{projection?.sourceIsRealAccountData ? '真实数据已接入' : '等待数据'}</span><small>{projection?.readOnly ? '只读组合' : '未执行'}</small></div></header>
|
||||
{message && <p className="composition-message">{message}</p>}
|
||||
{projection && projection.nativeObject.rowCount === 0 && <section className="composition-empty"><span>空</span><h2>当前知识目录还没有内容</h2><p>这里不会用样例冒充真实数据。先在知识空间导入或建立页面,再重新组合。</p></section>}
|
||||
{projection && projection.nativeObject.rowCount > 0 && <div className="composition-view-grid">
|
||||
{activeViews.includes('DASHBOARD') && <section className="composition-view composition-dashboard"><header><span>仪表盘</span><small>同一执行结果的总览</small></header><div><article><span>唯一知识页</span><strong>{projection.metrics.uniqueDocumentCount.toLocaleString('zh-CN')}</strong><small>篇</small></article><article><span>原始页面</span><strong>{projection.metrics.rawDocumentCount.toLocaleString('zh-CN')}</strong><small>篇</small></article><article><span>知识数据量</span><strong>{formatCompositionValue(projection.metrics.totalBytes, 'TOTAL_BYTES')}</strong><small>原生目录</small></article><article><span>动态分类</span><strong>{projection.metrics.groupCount}</strong><small>组</small></article></div></section>}
|
||||
{activeViews.includes('VERTICAL_BAR') && <section className="composition-view composition-bars"><header><span>柱状图</span><small>{measureNames[activeMeasure]} · 动态比例</small></header><div className="composition-bar-plot">{groups.slice(0, 12).map((group) => <article key={group.key}><div className="composition-bar-track"><i style={{ '--bar-height': `${Math.max(4, group.measureValue / maximum * 100)}%` } as CSSProperties}/><em>{formatCompositionValue(group.measureValue, activeMeasure)}</em></div><b title={group.label}>{group.label}</b></article>)}</div></section>}
|
||||
{activeViews.includes('COMPARISON') && <section className="composition-view composition-comparison"><header><span>对比</span><small>各分类占比</small></header><div>{groups.slice(0, 10).map((group) => <article key={group.key}><div><b>{group.label}</b><span>{formatCompositionValue(group.measureValue, activeMeasure)}</span></div><i><em style={{ '--share': `${group.share * 100}%` } as CSSProperties}/></i><small>{(group.share * 100).toFixed(1)}%</small></article>)}</div></section>}
|
||||
{activeViews.includes('CLASSIFICATION') && <section className="composition-view composition-classification"><header><span>分类</span><small>从原生路径实时生成</small></header><div>{groups.map((group, index) => <article key={group.key}><i>{String(index + 1).padStart(2, '0')}</i><div><b>{group.label}</b><span>{group.documentCount} 篇文档 · {formatCompositionValue(group.totalBytes, 'TOTAL_BYTES')}</span></div><strong>{formatCompositionValue(group.measureValue, activeMeasure)}</strong></article>)}</div></section>}
|
||||
{activeViews.includes('TABLE') && <section className="composition-view composition-table"><header><span>原生明细表</span><small>{projection.nativeObject.schema} · 只读</small></header><div><table><thead><tr><th>标题</th><th>一级分类</th><th>来源</th><th>数据量</th><th>重复</th><th>更新时间</th></tr></thead><tbody>{projection.nativeObject.rows.slice(0, 100).map((row) => <tr key={row.rowId}><td title={row.path}>{row.title}</td><td>{row.topLevelFolder}</td><td>{row.source === 'native' ? '光湖原生' : row.source}</td><td>{formatCompositionValue(row.sizeBytes, 'TOTAL_BYTES')}</td><td>{row.duplicateCount}</td><td>{new Date(row.updatedAtUnixMs).toLocaleDateString('zh-CN')}</td></tr>)}</tbody></table></div></section>}
|
||||
</div>}
|
||||
</main>
|
||||
</div>
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
export const nativeCompositionManifest = {
|
||||
schema: 'hololake.composition-module/v1',
|
||||
moduleId: 'hololake.native-composition-projection',
|
||||
name: '原生组合视图',
|
||||
version: '0.1.0',
|
||||
slot: 'education-composition',
|
||||
origin: 'resident',
|
||||
input: 'hololake.human-projection/v1',
|
||||
exports: ['NativeCompositionStudio', 'formatCompositionValue', 'toggleProjectionView'],
|
||||
authority: 'READ_ONLY_HUMAN_PROJECTION',
|
||||
} as const
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
/* Isolated from the declared 0.4.1 native-composition selector slice. */
|
||||
.composition-workspace-world { background: radial-gradient(circle at 70% 18%, color-mix(in srgb, var(--primitive-cool-glow) 14%, transparent), transparent 34%), radial-gradient(circle at 30% 80%, color-mix(in srgb, var(--primitive-warm-glow) 7%, transparent), transparent 34%), color-mix(in srgb, var(--surface-depth) 98%, transparent); }
|
||||
.native-composition-layout { min-width: 0; min-height: 0; display: grid; grid-template-columns: 294px minmax(0, 1fr); gap: 14px; padding: 14px; }
|
||||
.composition-recipe-panel, .composition-projection-stage { min-width: 0; min-height: 0; border: 1px solid color-mix(in srgb, var(--panel-edge) 78%, transparent); border-radius: 17px; background: color-mix(in srgb, var(--panel-bg) 76%, transparent); box-shadow: 0 26px 74px rgba(0, 0, 0, .24); overflow: hidden; }
|
||||
.composition-recipe-panel { overflow: auto; padding: 20px 18px; }
|
||||
.composition-recipe-panel > header { display: grid; gap: 5px; padding-bottom: 17px; border-bottom: 1px solid color-mix(in srgb, var(--panel-edge) 55%, transparent); }
|
||||
.composition-recipe-panel > header span, .composition-projection-stage > header span { color: var(--accent-light); font-size: 9.5px; font-weight: 800; letter-spacing: .16em; }
|
||||
.composition-recipe-panel > header h2 { margin: 0; color: var(--content-primary); font-size: 21px; font-weight: 760; }
|
||||
.composition-recipe-panel > header p { margin: 0; color: var(--content-muted); font-size: 10.5px; font-weight: 620; }
|
||||
.composition-source { display: flex; align-items: center; gap: 12px; margin: 17px 0 0; padding: 13px; border: 1px solid color-mix(in srgb, var(--accent-light) 23%, transparent); border-radius: 12px; background: color-mix(in srgb, var(--primitive-cool-glow) 8%, transparent); }
|
||||
.composition-source > i { flex: 0 0 33px; width: 33px; height: 33px; border-radius: 50%; background: radial-gradient(circle at 40% 35%, #fffce8, var(--accent-light) 24%, color-mix(in srgb, var(--primitive-cool-glow) 55%, transparent) 58%, transparent 70%); box-shadow: 0 0 22px color-mix(in srgb, var(--primitive-cool-glow) 38%, transparent); }
|
||||
.composition-source > div { min-width: 0; display: grid; gap: 3px; }
|
||||
.composition-source span, .composition-recipe-panel > label > span, .composition-recipe-panel legend { color: var(--content-muted); font-size: 9px; font-weight: 750; letter-spacing: .08em; }
|
||||
.composition-source b { overflow: hidden; color: var(--content-primary); font-size: 12px; font-weight: 730; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.composition-source small { color: var(--content-muted); font-size: 9.5px; font-weight: 600; }
|
||||
.composition-connector { width: 1px; height: 20px; margin: 0 auto; background: linear-gradient(transparent, color-mix(in srgb, var(--accent-light) 45%, transparent)); }
|
||||
.composition-recipe-panel > label { display: grid; gap: 6px; margin-bottom: 11px; }
|
||||
.composition-recipe-panel select { width: 100%; height: 38px; padding: 0 11px; border: 1px solid color-mix(in srgb, var(--panel-edge) 72%, transparent); border-radius: 9px; outline: 0; color: var(--content-primary); background: color-mix(in srgb, var(--surface-depth) 84%, transparent); font-size: 11.5px; font-weight: 650; }
|
||||
.composition-recipe-panel fieldset { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 7px; margin: 16px 0 0; padding: 12px; border: 1px solid color-mix(in srgb, var(--panel-edge) 64%, transparent); border-radius: 11px; }
|
||||
.composition-recipe-panel legend { padding: 0 6px; }
|
||||
.composition-recipe-panel fieldset label { display: flex; align-items: center; gap: 6px; color: var(--content-secondary); font-size: 10.5px; font-weight: 650; }
|
||||
.composition-recipe-panel input { accent-color: var(--accent-light); }
|
||||
.composition-execute { width: 100%; margin-top: 14px; padding: 11px 12px; border: 1px solid color-mix(in srgb, var(--accent-light) 40%, transparent); border-radius: 10px; color: var(--content-primary); background: linear-gradient(135deg, color-mix(in srgb, var(--primitive-warm-glow) 13%, transparent), color-mix(in srgb, var(--primitive-cool-glow) 9%, transparent)); font-size: 11.5px; font-weight: 750; cursor: pointer; }
|
||||
.composition-execute:disabled { opacity: .5; cursor: default; }
|
||||
.composition-recipe-panel > footer { display: grid; gap: 4px; margin-top: 15px; padding-top: 14px; border-top: 1px solid color-mix(in srgb, var(--panel-edge) 55%, transparent); color: var(--content-muted); font-size: 9.5px; font-weight: 600; }
|
||||
.composition-recipe-panel > footer b { color: var(--accent-light); font-size: 10px; font-weight: 750; }
|
||||
.composition-projection-stage { overflow: auto; padding: 20px; }
|
||||
.composition-projection-stage > header { display: flex; align-items: center; justify-content: space-between; gap: 24px; padding: 2px 2px 18px; }
|
||||
.composition-projection-stage > header > div:first-child { display: grid; gap: 5px; }
|
||||
.composition-projection-stage > header h1 { margin: 0; color: var(--content-primary); font-size: 25px; font-weight: 770; letter-spacing: .02em; }
|
||||
.composition-projection-stage > header p { margin: 0; color: var(--content-muted); font-size: 11px; font-weight: 620; }
|
||||
.composition-state { display: grid; grid-template-columns: 9px auto; align-items: center; gap: 2px 7px; padding: 9px 12px; border: 1px solid color-mix(in srgb, var(--accent-light) 24%, transparent); border-radius: 10px; background: color-mix(in srgb, var(--primitive-cool-glow) 7%, transparent); }
|
||||
.composition-state i { grid-row: 1 / 3; width: 7px; height: 7px; border-radius: 50%; background: var(--accent-light); box-shadow: 0 0 10px var(--accent-light); }
|
||||
.composition-state span { color: var(--content-secondary); font-size: 10px; font-weight: 700; }
|
||||
.composition-state small { color: var(--content-muted); font-size: 8.5px; font-weight: 600; }
|
||||
.composition-message { margin: -8px 2px 13px; color: var(--content-muted); font-size: 10px; font-weight: 620; }
|
||||
.composition-view-grid { display: grid; grid-template-columns: minmax(0, 1.55fr) minmax(260px, .85fr); gap: 12px; align-items: start; }
|
||||
.composition-view { min-width: 0; overflow: hidden; border: 1px solid color-mix(in srgb, var(--panel-edge) 66%, transparent); border-radius: 14px; background: color-mix(in srgb, var(--primitive-glass) 46%, transparent); }
|
||||
.composition-view > header { display: flex; align-items: baseline; justify-content: space-between; gap: 15px; padding: 13px 15px; border-bottom: 1px solid color-mix(in srgb, var(--panel-edge) 50%, transparent); }
|
||||
.composition-view > header span { color: var(--content-primary); font-size: 13px; font-weight: 750; }
|
||||
.composition-view > header small { color: var(--content-muted); font-size: 9px; font-weight: 620; }
|
||||
.composition-dashboard, .composition-table { grid-column: 1 / -1; }
|
||||
.composition-dashboard > div { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1px; }
|
||||
.composition-dashboard article { display: grid; grid-template-columns: 1fr auto; align-items: end; gap: 4px 8px; padding: 15px; background: color-mix(in srgb, var(--panel-bg) 32%, transparent); }
|
||||
.composition-dashboard article span { grid-column: 1 / -1; color: var(--content-muted); font-size: 9px; font-weight: 700; }
|
||||
.composition-dashboard article strong { color: var(--content-primary); font-size: 24px; font-weight: 760; }
|
||||
.composition-dashboard article small { padding-bottom: 3px; color: var(--content-muted); font-size: 9px; font-weight: 650; }
|
||||
.composition-bar-plot { height: 222px; display: flex; align-items: stretch; gap: 8px; padding: 18px 16px 12px; }
|
||||
.composition-bar-plot article { min-width: 0; flex: 1 1 44px; display: grid; grid-template-rows: minmax(0, 1fr) 32px; gap: 7px; }
|
||||
.composition-bar-track { position: relative; display: flex; align-items: end; justify-content: center; border-bottom: 1px solid color-mix(in srgb, var(--panel-edge) 70%, transparent); }
|
||||
.composition-bar-track > i { width: min(38px, 72%); height: var(--bar-height); min-height: 4px; border-radius: 7px 7px 2px 2px; background: linear-gradient(to top, color-mix(in srgb, var(--primitive-cool-glow) 50%, transparent), color-mix(in srgb, var(--accent-light) 76%, #fff)); box-shadow: 0 -4px 18px color-mix(in srgb, var(--primitive-cool-glow) 24%, transparent); }
|
||||
.composition-bar-track > em { position: absolute; top: -4px; color: var(--content-muted); font-size: 8px; font-style: normal; font-weight: 650; white-space: nowrap; }
|
||||
.composition-bar-plot article > b { overflow: hidden; color: var(--content-secondary); text-align: center; text-overflow: ellipsis; white-space: nowrap; font-size: 9px; font-weight: 650; }
|
||||
.composition-comparison > div { display: grid; gap: 10px; padding: 14px; }
|
||||
.composition-comparison article { display: grid; grid-template-columns: 1fr auto; gap: 5px 9px; }
|
||||
.composition-comparison article > div { grid-column: 1 / -1; display: flex; justify-content: space-between; gap: 12px; }
|
||||
.composition-comparison b { color: var(--content-secondary); font-size: 10px; font-weight: 700; }
|
||||
.composition-comparison span, .composition-comparison small { color: var(--content-muted); font-size: 9px; font-weight: 620; }
|
||||
.composition-comparison article > i { overflow: hidden; height: 6px; border-radius: 5px; background: color-mix(in srgb, var(--panel-edge) 56%, transparent); }
|
||||
.composition-comparison article > i > em { display: block; width: var(--share); height: 100%; border-radius: inherit; background: linear-gradient(90deg, color-mix(in srgb, var(--primitive-cool-glow) 48%, transparent), var(--accent-light)); }
|
||||
.composition-classification > div { display: grid; max-height: 270px; overflow: auto; }
|
||||
.composition-classification article { display: grid; grid-template-columns: 24px minmax(0, 1fr) auto; align-items: center; gap: 9px; padding: 11px 14px; border-top: 1px solid color-mix(in srgb, var(--panel-edge) 42%, transparent); }
|
||||
.composition-classification article > i { color: var(--accent-light); font-size: 9px; font-style: normal; font-weight: 800; }
|
||||
.composition-classification article > div { min-width: 0; display: grid; gap: 3px; }
|
||||
.composition-classification b { overflow: hidden; color: var(--content-primary); font-size: 10.5px; font-weight: 700; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.composition-classification span { color: var(--content-muted); font-size: 8.5px; font-weight: 600; }
|
||||
.composition-classification strong { color: var(--content-secondary); font-size: 10px; font-weight: 700; }
|
||||
.composition-table > div { max-height: 320px; overflow: auto; }
|
||||
.composition-table table { min-width: 100%; border-collapse: collapse; }
|
||||
.composition-table th, .composition-table td { max-width: 260px; padding: 10px 12px; border-top: 1px solid color-mix(in srgb, var(--panel-edge) 45%, transparent); color: var(--content-secondary); text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 9.5px; font-weight: 600; }
|
||||
.composition-table th { position: sticky; z-index: 2; top: 0; color: var(--content-primary); background: color-mix(in srgb, var(--panel-bg) 96%, transparent); font-weight: 750; }
|
||||
.composition-empty { display: grid; justify-items: center; width: min(520px, calc(100% - 40px)); margin: 15vh auto 0; text-align: center; }
|
||||
.composition-empty > span { display: grid; place-items: center; width: 66px; height: 66px; border-radius: 50%; color: var(--accent-light); background: radial-gradient(circle, color-mix(in srgb, var(--primitive-cool-glow) 22%, transparent), transparent 72%); font-size: 20px; font-weight: 760; }
|
||||
.composition-empty h2 { margin: 16px 0 7px; color: var(--content-primary); font-size: 21px; }
|
||||
.composition-empty p { margin: 0; color: var(--content-muted); font-size: 11.5px; line-height: 1.7; }
|
||||
@media (max-width: 980px) {
|
||||
.native-composition-layout { grid-template-columns: 250px minmax(0, 1fr); }
|
||||
.composition-view-grid { grid-template-columns: 1fr; }
|
||||
.composition-view { grid-column: 1; }
|
||||
.composition-dashboard > div { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
}
|
||||
|
|
@ -552,6 +552,38 @@ const ROUTES = {
|
|||
"moduleNumber": "HLP-NIPC-MOD-0020",
|
||||
"operationNumber": "HLP-NIPC-OP-0069",
|
||||
"targetNumber": "HLP-NIPC-TGT-0020"
|
||||
},
|
||||
"get_bundled_module_catalog": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0020",
|
||||
"operationNumber": "HLP-NIPC-OP-0070",
|
||||
"targetNumber": "HLP-NIPC-TGT-0020"
|
||||
},
|
||||
"activate_bundled_module": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0020",
|
||||
"operationNumber": "HLP-NIPC-OP-0071",
|
||||
"targetNumber": "HLP-NIPC-TGT-0020"
|
||||
},
|
||||
"get_native_composition_module_registry": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0021",
|
||||
"operationNumber": "HLP-NIPC-OP-0072",
|
||||
"targetNumber": "HLP-NIPC-TGT-0021"
|
||||
},
|
||||
"execute_knowledge_native_composition": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0021",
|
||||
"operationNumber": "HLP-NIPC-OP-0073",
|
||||
"targetNumber": "HLP-NIPC-TGT-0021"
|
||||
}
|
||||
} as const
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue