diff --git a/apps/web-console/console-v2.css b/apps/web-console/console-v2.css index 64dad45..7457844 100644 --- a/apps/web-console/console-v2.css +++ b/apps/web-console/console-v2.css @@ -1364,543 +1364,6 @@ opacity: 0.78; } -/* Self-evolution journal ------------------------------------------------- */ - -.console-v2 .v2-self-evolution { - display: none; -} - -.console-v2[data-module="self-evolution"] .main-panel { - grid-template-rows: 36px minmax(0, 1fr); -} - -.console-v2[data-module="self-evolution"] .v2-operation-shell, -.console-v2[data-module="self-evolution"] .v2-output-shell { - display: none; -} - -.console-v2[data-module="self-evolution"] .v2-self-evolution { - min-width: 0; - min-height: 0; - display: block; - grid-row: 2; - overflow: auto; - background: - radial-gradient(circle at 10% 0%, color-mix(in srgb, var(--v2-accent-soft) 68%, white), transparent 27%), - var(--v2-surface); - border: 1px solid color-mix(in srgb, var(--v2-accent) 14%, transparent); - border-radius: clamp(17px, 1.4vw, 24px); - box-shadow: var(--v2-shadow); -} - -.v2-evolution-shell { - min-width: 820px; - min-height: 100%; - display: grid; - grid-template-rows: auto minmax(330px, 1fr) auto; -} - -.v2-evolution-overview { - min-width: 0; - display: grid; - grid-template-columns: minmax(300px, 1fr) auto; - align-items: start; - gap: clamp(20px, 3vw, 48px); - padding: var(--v2-module-title-block) var(--v2-module-title-inline); - border-bottom: 1px solid var(--v2-line); -} - -.v2-evolution-heading .section-kicker { - display: inline-flex; - align-items: center; - gap: 7px; - color: var(--v2-accent-strong); - font-size: 9px; - font-weight: 850; - letter-spacing: 0.18em; -} - -.v2-evolution-heading .section-kicker svg { - width: 15px; - height: 15px; -} - -.v2-evolution-heading h1 { - margin: 8px 0 0; - color: var(--v2-ink); - font-size: clamp(24px, 2vw, 34px); - line-height: 1.06; - letter-spacing: -0.04em; -} - -.v2-evolution-heading > p { - margin: 9px 0 0; - color: var(--v2-muted); - font-size: 12px; - line-height: 1.6; -} - -.v2-evolution-summary { - min-width: 430px; - display: grid; - grid-template-columns: 1.25fr repeat(3, 1fr); - margin: 0; - overflow: hidden; - background: color-mix(in srgb, var(--v2-accent-soft) 38%, white); - border: 1px solid color-mix(in srgb, var(--v2-accent) 13%, transparent); - border-radius: 14px; -} - -.v2-evolution-summary > div { - min-width: 0; - display: grid; - align-content: center; - padding: 11px 14px; - border-left: 1px solid var(--v2-line); -} - -.v2-evolution-summary > div:first-child { - border-left: 0; -} - -.v2-evolution-summary dt, -.v2-evolution-summary small { - color: var(--v2-muted); - font-size: 8px; - font-weight: 750; -} - -.v2-evolution-summary dd { - margin: 2px 0 0; - color: var(--v2-ink); - font: 850 19px/1.1 "SFMono-Regular", Consolas, "Microsoft YaHei", monospace; -} - -.v2-evolution-summary .v2-evolution-current dd { - color: var(--v2-accent-strong); -} - -.v2-evolution-workspace { - min-width: 0; - min-height: 0; - display: grid; - grid-template-columns: clamp(225px, 21vw, 290px) minmax(0, 1fr); -} - -.v2-evolution-timeline { - min-width: 0; - padding: 20px 18px 18px 24px; - background: color-mix(in srgb, var(--v2-accent-soft) 13%, white); - border-right: 1px solid var(--v2-line); -} - -.v2-evolution-timeline-heading { - padding: 0 8px 12px 15px; -} - -.v2-evolution-timeline-heading span, -.v2-evolution-next small { - display: block; - color: var(--v2-accent-strong); - font-size: 8px; - font-weight: 850; - letter-spacing: 0.16em; -} - -.v2-evolution-timeline-heading strong { - display: block; - margin-top: 4px; - color: var(--v2-ink); - font-size: 13px; -} - -.v2-evolution-version-list { - position: relative; - display: grid; - gap: 4px; -} - -.v2-evolution-version-list::before { - width: 1px; - position: absolute; - inset: 11px auto 11px 6px; - content: ""; - background: color-mix(in srgb, var(--v2-accent) 24%, transparent); -} - -.v2-evolution-version { - min-width: 0; - position: relative; - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - gap: 2px 10px; - padding: 8px 9px 8px 25px; - color: var(--v2-muted); - text-align: left; - background: transparent; - border: 0; - border-radius: var(--v2-sidebar-card-radius); - cursor: pointer; -} - -.v2-evolution-version::before { - width: 7px; - height: 7px; - position: absolute; - top: 14px; - left: 3px; - z-index: 1; - content: ""; - background: white; - border: 2px solid color-mix(in srgb, var(--v2-accent) 42%, #a8b4b4); - border-radius: 50%; -} - -.v2-evolution-version:hover { - color: var(--v2-ink); - background: color-mix(in srgb, var(--v2-accent-soft) 38%, transparent); -} - -.v2-evolution-version.active { - color: var(--v2-accent-strong); - background: color-mix(in srgb, var(--v2-accent-soft) 72%, white); -} - -.v2-evolution-version.active::before { - background: var(--v2-accent); - border-color: white; - box-shadow: 0 0 0 4px color-mix(in srgb, var(--v2-accent) 14%, transparent); -} - -.v2-evolution-version > span { - display: flex; - align-items: center; - gap: 8px; -} - -.v2-evolution-version strong { - color: currentColor; - font: 850 12px/1.2 "SFMono-Regular", Consolas, monospace; -} - -.v2-evolution-version time, -.v2-evolution-version small { - color: var(--v2-muted); - font-size: 8px; -} - -.v2-evolution-version b { - grid-column: 1 / -1; - overflow: hidden; - color: var(--v2-ink); - font-size: 11px; - text-overflow: ellipsis; - white-space: nowrap; -} - -.v2-evolution-version small { - grid-column: 2; - grid-row: 1; - align-self: center; - white-space: nowrap; -} - -.v2-evolution-detail { - min-width: 0; - padding: clamp(20px, 2.2vw, 32px) clamp(24px, 2.7vw, 42px) 20px; -} - -.v2-evolution-detail-heading { - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; -} - -.v2-evolution-detail-heading > div { - display: flex; - align-items: center; - gap: 10px; -} - -.v2-evolution-status { - padding: 4px 9px; - color: var(--v2-accent-strong); - background: var(--v2-accent-soft); - border-radius: 999px; - font-size: 9px; - font-weight: 850; -} - -.v2-evolution-detail-version { - color: var(--v2-muted); - font: 800 11px/1 "SFMono-Regular", Consolas, monospace; -} - -.v2-evolution-detail-heading time { - color: var(--v2-muted); - font: 750 10px/1 "SFMono-Regular", Consolas, monospace; -} - -.v2-evolution-detail > h2 { - margin: 12px 0 0; - color: var(--v2-ink); - font-size: clamp(20px, 1.7vw, 28px); - line-height: 1.15; - letter-spacing: -0.035em; -} - -.v2-evolution-lead { - max-width: 860px; - margin: 7px 0 0; - color: var(--v2-muted); - font-size: 11px; - line-height: 1.6; -} - -.v2-evolution-detail-grid { - display: grid; - grid-template-columns: minmax(0, 1fr) minmax(240px, 0.92fr); - gap: clamp(24px, 3vw, 48px); - margin-top: 18px; - padding-top: 16px; - border-top: 1px solid var(--v2-line); -} - -.v2-evolution-detail-grid h3 { - display: flex; - align-items: center; - gap: 7px; - margin: 0 0 12px; - color: var(--v2-ink); - font-size: 11px; -} - -.v2-evolution-detail-grid h3 svg { - width: 14px; - height: 14px; - color: var(--v2-accent); -} - -.v2-evolution-changes > div { - display: grid; - gap: 10px; -} - -.v2-evolution-changes > div > div { - min-width: 0; - display: grid; - grid-template-columns: 22px minmax(0, 1fr); - gap: 9px; -} - -.v2-evolution-changes > div > div > svg { - width: 22px; - height: 22px; - padding: 5px; - color: var(--v2-accent-strong); - background: var(--v2-accent-soft); - border-radius: 50%; -} - -.v2-evolution-changes p { - margin: 0; -} - -.v2-evolution-changes p strong, -.v2-evolution-changes p span { - display: block; -} - -.v2-evolution-changes p strong { - color: var(--v2-ink); - font-size: 10px; -} - -.v2-evolution-changes p span { - margin-top: 2px; - color: var(--v2-muted); - font-size: 9px; - line-height: 1.45; -} - -.v2-evolution-evidence > div { - display: grid; - gap: 12px; -} - -.v2-evolution-metric > div { - min-width: 0; - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; -} - -.v2-evolution-metric > div > span { - overflow: hidden; - color: var(--v2-ink); - font-size: 9px; - font-weight: 750; - text-overflow: ellipsis; - white-space: nowrap; -} - -.v2-evolution-metric p { - display: flex; - align-items: center; - gap: 5px; - margin: 0; - white-space: nowrap; -} - -.v2-evolution-metric del { - color: #98a5a6; - font-size: 8px; - text-decoration-thickness: 1px; -} - -.v2-evolution-metric p svg { - width: 10px; - height: 10px; - color: var(--v2-muted); -} - -.v2-evolution-metric p strong { - color: var(--v2-accent-strong); - font-size: 10px; -} - -.v2-evolution-progress { - width: 100%; - height: 5px; - display: block; - margin-top: 5px; - overflow: hidden; - background: color-mix(in srgb, var(--v2-accent-soft) 50%, #e7eceb); - border-radius: 999px; -} - -.v2-evolution-progress i { - width: var(--evolution-progress); - height: 100%; - display: block; - background: linear-gradient(90deg, color-mix(in srgb, var(--v2-accent) 55%, white), var(--v2-accent)); - border-radius: inherit; - transition: width 220ms ease; -} - -.v2-evolution-validation { - display: grid; - grid-template-columns: auto minmax(0, 1fr); - align-items: center; - gap: 14px; - margin-top: 18px; - padding-top: 14px; - border-top: 1px solid var(--v2-line); -} - -.v2-evolution-module-tags { - display: flex; - flex-wrap: wrap; - gap: 5px; -} - -.v2-evolution-module-tags span, -.v2-evolution-next-steps span { - padding: 4px 8px; - color: var(--v2-accent-strong); - background: color-mix(in srgb, var(--v2-accent-soft) 62%, white); - border: 1px solid color-mix(in srgb, var(--v2-accent) 12%, transparent); - border-radius: 999px; - font-size: 8px; - font-weight: 800; - white-space: nowrap; -} - -.v2-evolution-validation > p { - min-width: 0; - display: flex; - align-items: center; - justify-content: flex-end; - gap: 7px; - margin: 0; - color: var(--v2-muted); - font-size: 9px; - text-align: right; -} - -.v2-evolution-validation > p svg { - width: 14px; - height: 14px; - flex: 0 0 auto; - color: var(--v2-accent); -} - -.console-v2 [data-evolution-scope="admin"] { - display: none; -} - -.console-v2[data-demo-role="admin"] [data-evolution-scope="user"] { - display: none; -} - -.console-v2[data-demo-role="admin"] [data-evolution-scope="admin"] { - display: inline; -} - -.v2-evolution-next { - min-width: 0; - display: grid; - grid-template-columns: 38px minmax(280px, 1fr) auto; - align-items: center; - gap: 13px; - padding: 13px clamp(22px, 2.2vw, 34px); - background: color-mix(in srgb, var(--v2-accent-soft) 58%, white); - border-top: 1px solid color-mix(in srgb, var(--v2-accent) 15%, transparent); -} - -.v2-evolution-next-icon { - width: 36px; - height: 36px; - display: grid; - place-items: center; - color: white; - background: var(--v2-accent); - border-radius: 11px; - box-shadow: 0 7px 18px color-mix(in srgb, var(--v2-accent) 22%, transparent); -} - -.v2-evolution-next-icon svg { - width: 16px; - height: 16px; -} - -.v2-evolution-next strong { - display: inline-block; - margin-top: 2px; - color: var(--v2-ink); - font-size: 12px; -} - -.v2-evolution-next p { - display: inline; - margin: 0 0 0 8px; - color: var(--v2-muted); - font-size: 9px; -} - -.v2-evolution-next-steps { - display: flex; - align-items: center; - gap: 6px; -} - -.v2-evolution-next-steps svg { - width: 12px; - height: 12px; - color: var(--v2-accent); -} .console-v2 .v2-inline-gesture .v2-camera-toggle { width: 42px; diff --git a/apps/web-console/console-v2.js b/apps/web-console/console-v2.js index 279ea1c..4613fdf 100644 --- a/apps/web-console/console-v2.js +++ b/apps/web-console/console-v2.js @@ -504,215 +504,6 @@ mainPanel.prepend(switcher); } - function buildEvolutionJournal(mainPanel) { - const evolutionEntries = [ - { - version: "v0.5", - date: "2026.08.12", - status: "当前版本", - title: "统一主题与权限体验", - summary: "把权限、主题与核心交互收束到同一套体验规则,让不同模块看起来、用起来都属于同一个系统。", - changes: [ - ["主题统一", "自扩展固定使用 user 绿色主题,避免权限语义混用。"], - ["孪生同步", "3D 模型渲染、玩具动作和车端动作统一跟随当前主题。"], - ["能力扩充", "新增自进化入口,让 user 与 admin 共同查看演进过程。"], - ], - modules: ["自扩展", "数字孪生", "权限体系"], - metrics: [ - ["主题一致性", "72%", "100%", 100], - ["可见能力模块", "4 个", "5 个", 92], - ["待修正视觉项", "4 项", "0 项", 100], - ], - userValidation: "核心板块已完成主题复核,权限色与交互反馈保持一致。", - adminValidation: "前端契约 11/11 · 自标定/自修复检查 8/8", - }, - { - version: "v0.4", - date: "2026.08.11", - status: "稳定版本", - title: "自修复闭环增强", - summary: "从发现异常走向诊断、修复与验证闭环,同时补齐离线场景的默认内容,确保功能状态始终可见。", - changes: [ - ["故障覆盖", "补充六类量产故障用例,覆盖更真实的车端异常。"], - ["修复闭环", "形成发现、定位、决策、执行、验证五阶段流程。"], - ["离线韧性", "服务暂时离线时仍保留默认案例和演示路径。"], - ], - modules: ["自修复", "故障诊断", "离线体验"], - metrics: [ - ["故障用例", "2 类", "6 类", 86], - ["修复阶段", "3 阶段", "5 阶段", 92], - ["离线可用性", "空白", "可演示", 100], - ], - userValidation: "断开服务后仍可理解自修复能力,并可继续浏览默认案例。", - adminValidation: "默认数据回退、流程状态与接口异常路径均已验证。", - }, - { - version: "v0.3", - date: "2026.08.10", - status: "能力升级", - title: "外设与车端统一编排", - summary: "将玩具动作、车端设备动作与数字孪生放进同一个控制流程,减少跨区域理解和操作成本。", - changes: [ - ["动作融合", "玩具动作与车端设备动作进入统一动作界面。"], - ["布局优化", "孪生界面位于左侧,动作界面固定在右侧。"], - ["映射精简", "移除重复外框,让实时映射关系更直接。"], - ], - modules: ["自扩展", "外设学习", "整车控制"], - metrics: [ - ["动作入口", "2 处", "1 处", 100], - ["映射层级", "3 层", "2 层", 88], - ["协同对象", "玩具", "玩具 + 车端", 94], - ], - userValidation: "动作与孪生左右分区清晰,学习结果可以直接用于统一控制。", - adminValidation: "外设连接状态、动作映射与车端执行链路保持独立可追踪。", - }, - { - version: "v0.2", - date: "2026.08.09", - status: "体系建立", - title: "多权限主题体系", - summary: "建立 user 与 admin 的视觉身份,使权限差异不只体现在可用功能,也能被用户直观感知。", - changes: [ - ["角色主题", "user 使用绿色,admin 使用蓝色,形成稳定的权限认知。"], - ["能力边界", "按角色控制模块入口,同时保留清晰的切换反馈。"], - ["模型联动", "数字孪生模型与界面主题保持同色语义。"], - ], - modules: ["权限体系", "主题系统", "数字孪生"], - metrics: [ - ["角色主题", "1 套", "2 套", 100], - ["权限反馈", "文字", "视觉 + 文字", 90], - ["主题联动", "界面", "界面 + 模型", 96], - ], - userValidation: "绿色主题贯穿 user 可用能力,角色身份更加明确。", - adminValidation: "角色切换事件可同步驱动界面变量与模型渲染参数。", - }, - { - version: "v0.1", - date: "2026.08.08", - status: "起点版本", - title: "整车认知控制台诞生", - summary: "完成从自然语言意图到整车状态回读的第一条闭环,建立后续能力持续进化的共同底座。", - changes: [ - ["自然语言控制", "用贴近日常表达的指令控制座舱功能。"], - ["整车孪生", "把座椅、空调、车窗等状态集中映射到一个界面。"], - ["执行回读", "串联 CAN 计划、设备执行与状态反馈。"], - ], - modules: ["自编排", "数字孪生", "CAN 执行"], - metrics: [ - ["能力模块", "0 个", "1 个", 100], - ["控制链路", "分散", "闭环", 92], - ["状态视图", "单设备", "整车", 96], - ], - userValidation: "自然语言、执行过程与结果反馈首次形成连续体验。", - adminValidation: "意图解析、CAN 编译计划与硬件回读具备完整追踪链路。", - }, - ]; - - const versionButtons = evolutionEntries.map((entry, index) => ` - `).join(""); - - const evolution = make("section", "v2-self-evolution", ` -
-
-
- SELF-EVOLUTION -

能力进化日志

-

记录每次问题发现、能力升级与验证结果,见证系统持续进化。

-
-
-
当前版本
v0.5
能力融合期
-
累计迭代
5
个版本
-
能力改进
12
项记录
-
最近更新
08.12
2026
-
-
- -
- - -
-
-
- - -
- -
-

-

-
-
-

本次进化

-
-
-
-

变好的证据

-
-
-
-
-
-

-
-
-
- - -
`); - evolution.id = "v2SelfEvolution"; - evolution.setAttribute("aria-label", "自进化能力日志"); - mainPanel.append(evolution); - - const renderEntry = (index) => { - const entry = evolutionEntries[index]; - if (!entry) return; - $("#v2EvolutionStatus", evolution).textContent = entry.status; - $("#v2EvolutionVersion", evolution).textContent = entry.version; - const date = $("#v2EvolutionDate", evolution); - date.textContent = entry.date; - date.dateTime = entry.date.replaceAll(".", "-"); - $("#v2EvolutionTitle", evolution).textContent = entry.title; - $("#v2EvolutionLead", evolution).textContent = entry.summary; - $("#v2EvolutionChangeList", evolution).innerHTML = entry.changes.map(([label, copy]) => ` -

${label}${copy}

`).join(""); - $("#v2EvolutionMetrics", evolution).innerHTML = entry.metrics.map(([label, before, after, progress]) => ` -
-
${label}

${before}${after}

- -
`).join(""); - $("#v2EvolutionModules", evolution).innerHTML = entry.modules - .map((module) => `${module}`).join(""); - $("#v2EvolutionUserValidation", evolution).textContent = entry.userValidation; - $("#v2EvolutionAdminValidation", evolution).textContent = entry.adminValidation; - $$("[data-evolution-index]", evolution).forEach((button, buttonIndex) => { - const active = buttonIndex === index; - button.classList.toggle("active", active); - button.setAttribute("aria-pressed", String(active)); - }); - window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } }); - }; - - $(".v2-evolution-version-list", evolution)?.addEventListener("click", (event) => { - const button = event.target.closest("[data-evolution-index]"); - if (button) renderEntry(Number(button.dataset.evolutionIndex)); - }); - renderEntry(0); - } function buildOutput(mainPanel) { const output = make("section", "v2-output-shell", ` @@ -863,6 +654,34 @@ } } + function syncSidebarControlForModule(evolutionView = window.CognitiveAgentSelfEvolution?.getView?.()) { + const evolutionMode = ui.activeModule === "self-evolution"; + const caption = $(".v2-control-section > .v2-field-caption"); + if (caption) caption.textContent = evolutionMode ? "进化视图" : "控制方式"; + const definitions = evolutionMode + ? [ + { label: "座椅称重进化", icon: "scale", active: evolutionView !== "system" }, + { label: "系统能力日志", icon: "scroll-text", active: evolutionView === "system" }, + ] + : [ + { label: "自然语言控制", icon: "message-square-text", active: ui.controlMode === "natural" }, + { label: "手势控制", icon: "scan-face", active: ui.controlMode === "gesture" }, + ]; + $$('[data-v2-control]').forEach((button, index) => { + const definition = definitions[index]; + if (!definition) return; + const label = $("span", button); + const icon = $("svg, i", button); + if (label) label.textContent = definition.label; + if (icon) icon.setAttribute("data-lucide", definition.icon); + button.classList.toggle("active", definition.active); + button.setAttribute("aria-pressed", String(definition.active)); + }); + const switcher = $(".v2-control-switch"); + switcher?.setAttribute("aria-label", evolutionMode ? "自进化视图" : "控制方式"); + window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } }); + } + function setModule(module, { persist = true } = {}) { if (["self-calibration", "self-repair"].includes(module) && ui.role !== "admin") { setRole("admin", { allowModuleTransition: true }); @@ -897,6 +716,7 @@ } else { setControlMode(ui.controlMode, { triggerGesture: false }); } + syncSidebarControlForModule(); window.dispatchEvent(new CustomEvent("cognitive-agent:module-change", { detail: { module: ui.activeModule }, })); @@ -1059,7 +879,18 @@ function bindEvents() { $("#v2RoleSelect")?.addEventListener("change", (event) => setRole(event.target.value)); $$('[data-v2-control]').forEach((button) => { - button.addEventListener("click", () => setControlMode(button.dataset.v2Control)); + button.addEventListener("click", () => { + if (ui.activeModule === "self-evolution") { + const view = button.dataset.v2Control === "gesture" ? "system" : "weighing"; + window.CognitiveAgentSelfEvolution?.setView?.(view); + syncSidebarControlForModule(view); + return; + } + setControlMode(button.dataset.v2Control); + }); + }); + window.addEventListener("cognitive-agent:evolution-view-change", (event) => { + if (ui.activeModule === "self-evolution") syncSidebarControlForModule(event.detail?.view); }); $$('.v2-module-tab').forEach((button) => { button.addEventListener("click", () => { @@ -1159,7 +990,7 @@ gestureDrawer, ); buildModuleSwitcher(mainPanel); - buildEvolutionJournal(mainPanel); + window.CognitiveAgentSelfEvolution?.mount(mainPanel); $(".pipeline", mainPanel)?.classList.add("v2-legacy-hidden"); $(".topbar")?.classList.add("v2-legacy-hidden"); diff --git a/apps/web-console/index.html b/apps/web-console/index.html index 1524d72..60fe8e4 100755 --- a/apps/web-console/index.html +++ b/apps/web-console/index.html @@ -10,7 +10,8 @@ 智舱随行 · 整车智能控制台 - + + @@ -22,7 +23,8 @@ - + + diff --git a/apps/web-console/self-evolution.css b/apps/web-console/self-evolution.css new file mode 100644 index 0000000..b96a601 --- /dev/null +++ b/apps/web-console/self-evolution.css @@ -0,0 +1,1381 @@ +/* Self-evolution journal ------------------------------------------------- */ + +.console-v2 .v2-self-evolution { + display: none; +} + +.console-v2[data-module="self-evolution"] .main-panel { + grid-template-rows: 36px minmax(0, 1fr); +} + +.console-v2[data-module="self-evolution"] .v2-operation-shell, +.console-v2[data-module="self-evolution"] .v2-output-shell { + display: none; +} + +.console-v2[data-module="self-evolution"] .v2-self-evolution { + min-width: 0; + min-height: 0; + display: block; + grid-row: 2; + overflow: auto; + background: + radial-gradient(circle at 10% 0%, color-mix(in srgb, var(--v2-accent-soft) 68%, white), transparent 27%), + var(--v2-surface); + border: 1px solid color-mix(in srgb, var(--v2-accent) 14%, transparent); + border-radius: clamp(17px, 1.4vw, 24px); + box-shadow: var(--v2-shadow); +} + +.v2-evolution-shell { + min-width: 820px; + min-height: 100%; + display: grid; + grid-template-rows: auto minmax(330px, 1fr) auto; +} + +.v2-evolution-overview { + min-width: 0; + display: grid; + grid-template-columns: minmax(300px, 1fr) auto; + align-items: start; + gap: clamp(20px, 3vw, 48px); + padding: var(--v2-module-title-block) var(--v2-module-title-inline); + border-bottom: 1px solid var(--v2-line); +} + +.v2-evolution-heading .section-kicker { + display: inline-flex; + align-items: center; + gap: 7px; + color: var(--v2-accent-strong); + font-size: 9px; + font-weight: 850; + letter-spacing: 0.18em; +} + +.v2-evolution-heading .section-kicker svg { + width: 15px; + height: 15px; +} + +.v2-evolution-heading h1 { + margin: 8px 0 0; + color: var(--v2-ink); + font-size: clamp(24px, 2vw, 34px); + line-height: 1.06; + letter-spacing: -0.04em; +} + +.v2-evolution-heading > p { + margin: 9px 0 0; + color: var(--v2-muted); + font-size: 14px; + line-height: 1.6; +} + +.v2-evolution-summary { + min-width: 430px; + display: grid; + grid-template-columns: 1.25fr repeat(3, 1fr); + margin: 0; + overflow: hidden; + background: color-mix(in srgb, var(--v2-accent-soft) 38%, white); + border: 1px solid color-mix(in srgb, var(--v2-accent) 13%, transparent); + border-radius: 14px; +} + +.v2-evolution-summary > div { + min-width: 0; + display: grid; + align-content: center; + padding: 11px 14px; + border-left: 1px solid var(--v2-line); +} + +.v2-evolution-summary > div:first-child { + border-left: 0; +} + +.v2-evolution-summary dt, +.v2-evolution-summary small { + color: var(--v2-muted); + font-size: 8px; + font-weight: 750; +} + +.v2-evolution-summary dd { + margin: 2px 0 0; + color: var(--v2-ink); + font: 850 19px/1.1 "SFMono-Regular", Consolas, "Microsoft YaHei", monospace; +} + +.v2-evolution-summary .v2-evolution-current dd { + color: var(--v2-accent-strong); +} + +.v2-evolution-workspace { + min-width: 0; + min-height: 0; + display: grid; + grid-template-columns: clamp(225px, 21vw, 290px) minmax(0, 1fr); +} + +.v2-evolution-timeline { + min-width: 0; + padding: 20px 18px 18px 24px; + background: color-mix(in srgb, var(--v2-accent-soft) 13%, white); + border-right: 1px solid var(--v2-line); +} + +.v2-evolution-timeline-heading { + padding: 0 8px 12px 15px; +} + +.v2-evolution-timeline-heading span, +.v2-evolution-next small { + display: block; + color: var(--v2-accent-strong); + font-size: 8px; + font-weight: 850; + letter-spacing: 0.16em; +} + +.v2-evolution-timeline-heading strong { + display: block; + margin-top: 4px; + color: var(--v2-ink); + font-size: 13px; +} + +.v2-evolution-version-list { + position: relative; + display: grid; + gap: 4px; +} + +.v2-evolution-version-list::before { + width: 1px; + position: absolute; + inset: 11px auto 11px 6px; + content: ""; + background: color-mix(in srgb, var(--v2-accent) 24%, transparent); +} + +.v2-evolution-version { + min-width: 0; + position: relative; + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 2px 10px; + padding: 8px 9px 8px 25px; + color: var(--v2-muted); + text-align: left; + background: transparent; + border: 0; + border-radius: var(--v2-sidebar-card-radius); + cursor: pointer; +} + +.v2-evolution-version::before { + width: 7px; + height: 7px; + position: absolute; + top: 14px; + left: 3px; + z-index: 1; + content: ""; + background: white; + border: 2px solid color-mix(in srgb, var(--v2-accent) 42%, #a8b4b4); + border-radius: 50%; +} + +.v2-evolution-version:hover { + color: var(--v2-ink); + background: color-mix(in srgb, var(--v2-accent-soft) 38%, transparent); +} + +.v2-evolution-version.active { + color: var(--v2-accent-strong); + background: color-mix(in srgb, var(--v2-accent-soft) 72%, white); +} + +.v2-evolution-version.active::before { + background: var(--v2-accent); + border-color: white; + box-shadow: 0 0 0 4px color-mix(in srgb, var(--v2-accent) 14%, transparent); +} + +.v2-evolution-version > span { + display: flex; + align-items: center; + gap: 8px; +} + +.v2-evolution-version strong { + color: currentColor; + font: 850 12px/1.2 "SFMono-Regular", Consolas, monospace; +} + +.v2-evolution-version time, +.v2-evolution-version small { + color: var(--v2-muted); + font-size: 8px; +} + +.v2-evolution-version b { + grid-column: 1 / -1; + overflow: hidden; + color: var(--v2-ink); + font-size: 11px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.v2-evolution-version small { + grid-column: 2; + grid-row: 1; + align-self: center; + white-space: nowrap; +} + +.v2-evolution-detail { + min-width: 0; + padding: clamp(20px, 2.2vw, 32px) clamp(24px, 2.7vw, 42px) 20px; +} + +.v2-evolution-detail-heading { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; +} + +.v2-evolution-detail-heading > div { + display: flex; + align-items: center; + gap: 10px; +} + +.v2-evolution-status { + padding: 4px 9px; + color: var(--v2-accent-strong); + background: var(--v2-accent-soft); + border-radius: 999px; + font-size: 10px; + font-weight: 850; +} + +.v2-evolution-detail-version { + color: var(--v2-muted); + font: 800 11px/1 "SFMono-Regular", Consolas, monospace; +} + +.v2-evolution-detail-heading time { + color: var(--v2-muted); + font: 750 10px/1 "SFMono-Regular", Consolas, monospace; +} + +.v2-evolution-detail > h2 { + margin: 12px 0 0; + color: var(--v2-ink); + font-size: clamp(20px, 1.7vw, 28px); + line-height: 1.15; + letter-spacing: -0.035em; +} + +.v2-evolution-lead { + max-width: 860px; + margin: 7px 0 0; + color: var(--v2-muted); + font-size: 11px; + line-height: 1.6; +} + +.v2-evolution-detail-grid { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(240px, 0.92fr); + gap: clamp(24px, 3vw, 48px); + margin-top: 18px; + padding-top: 16px; + border-top: 1px solid var(--v2-line); +} + +.v2-evolution-detail-grid h3 { + display: flex; + align-items: center; + gap: 7px; + margin: 0 0 12px; + color: var(--v2-ink); + font-size: 11px; +} + +.v2-evolution-detail-grid h3 svg { + width: 14px; + height: 14px; + color: var(--v2-accent); +} + +.v2-evolution-changes > div { + display: grid; + gap: 10px; +} + +.v2-evolution-changes > div > div { + min-width: 0; + display: grid; + grid-template-columns: 22px minmax(0, 1fr); + gap: 9px; +} + +.v2-evolution-changes > div > div > svg { + width: 22px; + height: 22px; + padding: 5px; + color: var(--v2-accent-strong); + background: var(--v2-accent-soft); + border-radius: 50%; +} + +.v2-evolution-changes p { + margin: 0; +} + +.v2-evolution-changes p strong, +.v2-evolution-changes p span { + display: block; +} + +.v2-evolution-changes p strong { + color: var(--v2-ink); + font-size: 10px; +} + +.v2-evolution-changes p span { + margin-top: 2px; + color: var(--v2-muted); + font-size: 9px; + line-height: 1.45; +} + +.v2-evolution-evidence > div { + display: grid; + gap: 12px; +} + +.v2-evolution-metric > div { + min-width: 0; + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.v2-evolution-metric > div > span { + overflow: hidden; + color: var(--v2-ink); + font-size: 9px; + font-weight: 750; + text-overflow: ellipsis; + white-space: nowrap; +} + +.v2-evolution-metric p { + display: flex; + align-items: center; + gap: 5px; + margin: 0; + white-space: nowrap; +} + +.v2-evolution-metric del { + color: #98a5a6; + font-size: 8px; + text-decoration-thickness: 1px; +} + +.v2-evolution-metric p svg { + width: 10px; + height: 10px; + color: var(--v2-muted); +} + +.v2-evolution-metric p strong { + color: var(--v2-accent-strong); + font-size: 10px; +} + +.v2-evolution-progress { + width: 100%; + height: 5px; + display: block; + margin-top: 5px; + overflow: hidden; + background: color-mix(in srgb, var(--v2-accent-soft) 50%, #e7eceb); + border-radius: 999px; +} + +.v2-evolution-progress i { + width: var(--evolution-progress); + height: 100%; + display: block; + background: linear-gradient(90deg, color-mix(in srgb, var(--v2-accent) 55%, white), var(--v2-accent)); + border-radius: inherit; + transition: width 220ms ease; +} + +.v2-evolution-validation { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + align-items: center; + gap: 14px; + margin-top: 18px; + padding-top: 14px; + border-top: 1px solid var(--v2-line); +} + +.v2-evolution-module-tags { + display: flex; + flex-wrap: wrap; + gap: 5px; +} + +.v2-evolution-module-tags span, +.v2-evolution-next-steps span { + padding: 4px 8px; + color: var(--v2-accent-strong); + background: color-mix(in srgb, var(--v2-accent-soft) 62%, white); + border: 1px solid color-mix(in srgb, var(--v2-accent) 12%, transparent); + border-radius: 999px; + font-size: 8px; + font-weight: 800; + white-space: nowrap; +} + +.v2-evolution-validation > p { + min-width: 0; + display: flex; + align-items: center; + justify-content: flex-end; + gap: 7px; + margin: 0; + color: var(--v2-muted); + font-size: 9px; + text-align: right; +} + +.v2-evolution-validation > p svg { + width: 14px; + height: 14px; + flex: 0 0 auto; + color: var(--v2-accent); +} + +.console-v2 [data-evolution-scope="admin"] { + display: none; +} + +.console-v2[data-demo-role="admin"] [data-evolution-scope="user"] { + display: none; +} + +.console-v2[data-demo-role="admin"] [data-evolution-scope="admin"] { + display: inline; +} + +.v2-evolution-next { + min-width: 0; + display: grid; + grid-template-columns: 38px minmax(280px, 1fr) auto; + align-items: center; + gap: 13px; + padding: 13px clamp(22px, 2.2vw, 34px); + background: color-mix(in srgb, var(--v2-accent-soft) 58%, white); + border-top: 1px solid color-mix(in srgb, var(--v2-accent) 15%, transparent); +} + +.v2-evolution-next-icon { + width: 36px; + height: 36px; + display: grid; + place-items: center; + color: white; + background: var(--v2-accent); + border-radius: 11px; + box-shadow: 0 7px 18px color-mix(in srgb, var(--v2-accent) 22%, transparent); +} + +.v2-evolution-next-icon svg { + width: 16px; + height: 16px; +} + +.v2-evolution-next strong { + display: inline-block; + margin-top: 2px; + color: var(--v2-ink); + font-size: 12px; +} + +.v2-evolution-next p { + display: inline; + margin: 0 0 0 8px; + color: var(--v2-muted); + font-size: 9px; +} + +.v2-evolution-next-steps { + display: flex; + align-items: center; + gap: 6px; +} + +.v2-evolution-next-steps svg { + width: 12px; + height: 12px; + color: var(--v2-accent); +} + +/* Seat-weighing self-evolution ------------------------------------------- */ + +.console-v2 .v2-self-evolution { + --evolution-warning: #c4772b; + --evolution-warning-soft: #fff4e7; +} + +.console-v2[data-module="self-evolution"] .v2-self-evolution { + position: relative; + overflow: hidden; +} + +.v2-self-evolution .v2-evolution-shell { + height: auto; + min-width: 920px; + min-height: 0; + position: absolute; + inset: 0; + grid-template-rows: auto minmax(0, 1fr) auto; +} + +.v2-self-evolution .v2-evolution-overview { + grid-template-columns: minmax(360px, 1fr) minmax(480px, 0.94fr); + align-items: center; + gap: clamp(18px, 2.2vw, 34px); + padding-block: clamp(14px, 1.8vh, 21px); +} + +.v2-evolution-title-row { + display: flex; + align-items: flex-start; + gap: 14px; +} + +.v2-evolution-title-row > div { + min-width: 0; +} + +.v2-demo-data { + display: inline-flex; + align-items: center; + gap: 4px; + margin-top: 1px; + padding: 4px 8px; + color: var(--v2-accent-strong); + background: color-mix(in srgb, var(--v2-accent-soft) 78%, white); + border: 1px solid color-mix(in srgb, var(--v2-accent) 13%, transparent); + border-radius: 999px; + font-size: 8px; + font-weight: 800; + white-space: nowrap; +} + +.v2-demo-data svg { + width: 11px; + height: 11px; +} + +.v2-self-evolution .v2-evolution-heading h1 { + margin-top: 6px; +} + +.v2-self-evolution .v2-evolution-heading > p { + max-width: 660px; + margin-top: 6px; +} + +.v2-self-evolution .v2-evolution-summary { + width: 100%; + min-width: 0; +} + +.v2-self-evolution .v2-evolution-summary > div { + padding: 10px 12px; +} + +.v2-self-evolution .v2-evolution-summary dd { + font-size: clamp(16px, 1.3vw, 19px); +} + +.v2-evolution-page { + min-width: 0; + min-height: 0; + overflow: hidden; +} + +.v2-evolution-page[hidden], +.v2-evolution-next[hidden] { + display: none !important; +} + +.v2-weighing-workspace { + min-width: 0; + height: 100%; + min-height: 0; + display: grid; + grid-template-columns: clamp(235px, 20vw, 292px) minmax(0, 1fr); +} + +.v2-family-panel { + min-width: 0; + min-height: 0; + padding: 20px 16px 16px 24px; + background: color-mix(in srgb, var(--v2-accent-soft) 13%, white); + border-right: 1px solid var(--v2-line); +} + +.v2-family-heading { + padding: 0 8px 12px; +} + +.v2-family-heading span { + display: block; + color: var(--v2-accent-strong); + font: 850 8px/1.2 "SFMono-Regular", Consolas, monospace; + letter-spacing: 0.17em; +} + +.v2-family-heading strong { + display: block; + margin-top: 5px; + color: var(--v2-ink); + font-size: 14px; +} + +.v2-family-list { + position: relative; + display: grid; + gap: 4px; +} + +.v2-family-list::before { + width: 1px; + position: absolute; + inset: 15px auto 15px 7px; + content: ""; + background: color-mix(in srgb, var(--v2-accent) 28%, transparent); +} + +.v2-family-model { + min-width: 0; + min-height: 58px; + position: relative; + display: grid; + grid-template-columns: 13px minmax(0, 1fr) auto; + align-items: center; + gap: 8px; + padding: 8px 9px 8px 2px; + color: var(--v2-muted); + text-align: left; + background: transparent; + border: 0; + border-radius: 12px; + cursor: pointer; + transition: background 160ms ease, transform 160ms ease; +} + +.v2-family-model:hover { + background: color-mix(in srgb, var(--v2-accent-soft) 42%, white); +} + +.v2-family-model.active { + background: color-mix(in srgb, var(--v2-accent-soft) 76%, white); +} + +.v2-family-marker { + width: 9px; + height: 9px; + position: relative; + z-index: 1; + display: block; + background: white; + border: 2px solid color-mix(in srgb, var(--v2-accent) 58%, #99a9a8); + border-radius: 50%; +} + +.v2-family-model.active .v2-family-marker { + background: var(--v2-accent); + border-color: white; + box-shadow: 0 0 0 5px color-mix(in srgb, var(--v2-accent) 13%, transparent); +} + +.v2-family-copy { + min-width: 0; + display: grid; + gap: 4px; +} + +.v2-family-copy > span { + min-width: 0; + display: flex; + align-items: center; + gap: 6px; +} + +.v2-family-copy strong { + color: var(--v2-ink); + font-size: 11px; +} + +.v2-family-copy em { + padding: 2px 5px; + color: var(--v2-accent-strong); + background: color-mix(in srgb, var(--v2-accent-soft) 72%, white); + border-radius: 999px; + font-size: 7px; + font-style: normal; + font-weight: 800; + white-space: nowrap; +} + +.v2-family-copy em[data-model-state="cold"], +.v2-family-copy em[data-model-state="generic"] { + color: #718080; + background: #eef3f2; +} + +.v2-family-copy > span > small { + color: var(--v2-muted); + font: 750 8px/1 "SFMono-Regular", Consolas, monospace; +} + +.v2-family-copy > small { + overflow: hidden; + color: var(--v2-muted); + font-size: 8px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.v2-family-error { + display: grid; + justify-items: end; + color: var(--v2-accent-strong); +} + +.v2-family-error strong { + font: 850 12px/1 "SFMono-Regular", Consolas, monospace; +} + +.v2-family-error small { + margin-top: 3px; + color: var(--v2-muted); + font-size: 7px; +} + +.v2-privacy-note { + display: grid; + grid-template-columns: 19px minmax(0, 1fr); + gap: 8px; + margin: 15px 8px 0; + padding: 10px; + color: var(--v2-muted); + background: rgba(255, 255, 255, 0.66); + border: 1px solid var(--v2-line); + border-radius: 10px; +} + +.v2-privacy-note svg { + width: 16px; + height: 16px; + color: var(--v2-accent); +} + +.v2-privacy-note p { + margin: 0; + font-size: 8px; + line-height: 1.55; +} + +.v2-model-state-guide { + display: flex; + flex-wrap: wrap; + gap: 5px 9px; + margin: 11px 8px 0; +} + +.v2-model-state-guide span { + display: inline-flex; + align-items: center; + gap: 4px; + color: var(--v2-muted); + font-size: 7px; +} + +.v2-model-state-guide span::before { + width: 5px; + height: 5px; + content: ""; + background: var(--v2-accent); + border-radius: 50%; +} + +.v2-model-state-guide span[data-state="cold"]::before { background: #a3afae; } +.v2-model-state-guide span[data-state="learning"]::before { background: #d29a36; } +.v2-model-state-guide span[data-state="warning"]::before { background: var(--evolution-warning); } + +.v2-weighing-detail { + min-width: 0; + min-height: 0; + padding: 18px clamp(22px, 2.4vw, 36px) 17px; + overflow: auto; +} + +.v2-weighing-detail-heading, +.v2-model-title-row, +.v2-convergence-card > header, +.v2-linkage-card > header, +.v2-parameter-section > header, +.v2-model-events > header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 15px; +} + +.v2-weighing-detail-heading > div:first-child { + display: flex; + align-items: center; + gap: 9px; +} + +.v2-model-badge { + padding: 4px 9px; + color: var(--v2-accent-strong); + background: var(--v2-accent-soft); + border-radius: 999px; + font-size: 8px; + font-weight: 850; +} + +.v2-model-badge[data-model-state="learning"] { + color: #9a6818; + background: #fff3d7; +} + +.v2-model-badge[data-model-state="cold"], +.v2-model-badge[data-model-state="generic"] { + color: #677575; + background: #edf2f2; +} + +.v2-model-code { + color: var(--v2-muted); + font: 750 8px/1 "SFMono-Regular", Consolas, monospace; +} + +.v2-home-sync { + display: grid; + justify-items: end; + gap: 3px; +} + +.v2-home-sync strong { + display: flex; + align-items: center; + gap: 6px; + color: var(--v2-accent-strong); + font-size: 9px; +} + +.v2-home-sync strong i { + width: 6px; + height: 6px; + background: #20a993; + border-radius: 50%; + box-shadow: 0 0 0 3px rgba(32, 169, 147, 0.1); +} + +.v2-home-sync small { + color: var(--v2-muted); + font: 700 7px/1 "SFMono-Regular", Consolas, monospace; +} + +.v2-model-title-row { + align-items: flex-end; + margin-top: 9px; + padding-bottom: 11px; + border-bottom: 1px solid var(--v2-line); +} + +.v2-model-title-row h2 { + margin: 0; + color: var(--v2-ink); + font-size: clamp(19px, 1.55vw, 25px); + line-height: 1.15; + letter-spacing: -0.035em; +} + +.v2-model-title-row p { + max-width: 700px; + margin: 5px 0 0; + color: var(--v2-muted); + font-size: 9px; + line-height: 1.5; +} + +.v2-play-evolution { + min-height: 30px; + display: inline-flex; + align-items: center; + gap: 6px; + padding: 0 10px; + color: var(--v2-accent-strong); + background: color-mix(in srgb, var(--v2-accent-soft) 65%, white); + border: 1px solid color-mix(in srgb, var(--v2-accent) 15%, transparent); + border-radius: 8px; + font-size: 8px; + font-weight: 800; + cursor: pointer; + white-space: nowrap; +} + +.v2-play-evolution:hover:not(:disabled) { + color: white; + background: var(--v2-accent); +} + +.v2-play-evolution:disabled { + cursor: default; + opacity: 0.45; +} + +.v2-play-evolution.playing svg { + animation: evolutionPulse 700ms ease-in-out infinite alternate; +} + +@keyframes evolutionPulse { + to { transform: scale(1.25); } +} + +.v2-play-evolution svg { + width: 12px; + height: 12px; +} + +.v2-weighing-core { + min-width: 0; + display: grid; + grid-template-columns: minmax(410px, 1.75fr) minmax(235px, 0.88fr); + gap: 14px; + margin-top: 12px; +} + +.v2-convergence-card, +.v2-linkage-card { + min-width: 0; + padding: 11px 12px; + background: color-mix(in srgb, var(--v2-accent-soft) 10%, white); + border: 1px solid var(--v2-line); + border-radius: 11px; +} + +.v2-convergence-card h3, +.v2-linkage-card h3, +.v2-parameter-section h3, +.v2-model-events h3 { + margin: 0; + color: var(--v2-ink); + font-size: 12px; +} + +.v2-chart-legend { + display: flex; + align-items: center; + gap: 13px; +} + +.v2-chart-legend span { + display: inline-flex; + align-items: center; + gap: 5px; + color: var(--v2-muted); + font-size: 7px; + white-space: nowrap; +} + +.v2-chart-legend span::before { + width: 14px; + height: 2px; + content: ""; + background: var(--v2-accent); +} + +.v2-chart-legend span[data-line="baseline"]::before { background: #9aa8a7; } +.v2-chart-legend span[data-line="target"]::before { height: 1px; background: #d97c36; } + +.v2-chart-wrap { + min-height: 164px; + position: relative; + margin-top: 4px; +} + +.v2-chart-wrap svg { + width: 100%; + height: auto; + display: block; + overflow: visible; +} + +.v2-chart-grid line { + stroke: #e5ebea; + stroke-width: 1; +} + +.v2-chart-grid text, +.v2-chart-labels text { + fill: #899897; + font: 9px "SFMono-Regular", Consolas, monospace; +} + +.v2-chart-labels text { text-anchor: middle; } +.v2-baseline-line { stroke: #9aa8a7; stroke-width: 1.5; stroke-dasharray: 7 5; } +.v2-target-line { stroke: #d97c36; stroke-width: 1.4; stroke-dasharray: 5 4; } +.v2-chart-area { fill: color-mix(in srgb, var(--v2-accent-soft) 50%, transparent); } +.v2-personal-line { fill: none; stroke: var(--v2-accent); stroke-width: 4; stroke-linecap: round; stroke-linejoin: round; } +.v2-chart-point circle:not(.hit) { fill: white; stroke: var(--v2-accent); stroke-width: 3; } +.v2-chart-point.current circle:not(.hit) { fill: var(--v2-accent); stroke: white; } +.v2-chart-point .hit { fill: transparent; stroke: transparent; cursor: pointer; } +.v2-chart-point:focus { outline: none; } +.v2-chart-point:focus circle:not(.hit) { stroke-width: 5; } +.v2-current-label rect { fill: color-mix(in srgb, var(--v2-accent-soft) 88%, white); } +.v2-current-label text { fill: var(--v2-accent-strong); font: 800 9px "SFMono-Regular", Consolas, monospace; text-anchor: middle; } + +.v2-chart-empty { + position: absolute; + inset: 38px 20px 20px 46px; + display: grid; + place-content: center; + justify-items: center; + gap: 5px; + color: var(--v2-muted); + background: rgba(255, 255, 255, 0.82); + border: 1px dashed var(--v2-line); + border-radius: 9px; + text-align: center; +} + +.v2-chart-empty svg { width: 20px; height: 20px; color: var(--v2-accent); } +.v2-chart-empty strong { color: var(--v2-ink); font-size: 9px; } +.v2-chart-empty span { font-size: 8px; } + +.v2-sample-insight { + min-height: 14px; + margin: 1px 0 0 47px; + color: var(--v2-muted); + font-size: 7px; + line-height: 1.4; +} + +.v2-linkage-card > header span { + color: var(--v2-accent-strong); + font-size: 9px; + font-weight: 800; +} + +.v2-linkage-card ol { + display: grid; + gap: 0; + margin: 9px 0 0; + padding: 0; + list-style: none; +} + +.v2-linkage-card li { + min-width: 0; + min-height: 35px; + position: relative; + display: grid; + grid-template-columns: 21px minmax(0, 1fr) auto; + align-items: start; + gap: 7px; +} + +.v2-linkage-card li::before { + width: 1px; + position: absolute; + top: 20px; + bottom: -1px; + left: 10px; + content: ""; + background: color-mix(in srgb, var(--v2-accent) 20%, #dfe6e5); +} + +.v2-linkage-card li:last-child::before { display: none; } + +.v2-linkage-card li > span { + width: 20px; + height: 20px; + position: relative; + z-index: 1; + display: grid; + place-items: center; + color: #7d8b8a; + background: #eef3f2; + border-radius: 50%; + font: 800 10px/1 "SFMono-Regular", Consolas, monospace; +} + +.v2-linkage-card li[data-step-state="done"] > span, +.v2-linkage-card li[data-step-state="active"] > span { + color: var(--v2-accent-strong); + background: var(--v2-accent-soft); +} + +.v2-linkage-card li p { + min-width: 0; + display: grid; + gap: 2px; + margin: 0; +} + +.v2-linkage-card li strong { color: var(--v2-ink); font-size: 11.5px; } +.v2-linkage-card li small { overflow: hidden; color: var(--v2-muted); font-size: 10px; text-overflow: ellipsis; white-space: nowrap; } +.v2-linkage-card li em { color: var(--v2-muted); font-size: 10px; font-style: normal; white-space: nowrap; } +.v2-linkage-card li[data-step-state="done"] em { color: var(--v2-accent-strong); } +.v2-linkage-card li[data-step-state="active"] em { color: #a77320; } + +.v2-linkage-guard { + display: grid; + grid-template-columns: 17px minmax(0, 1fr); + gap: 7px; + margin-top: 5px; + padding: 7px 8px; + color: var(--v2-muted); + background: color-mix(in srgb, var(--v2-accent-soft) 30%, white); + border-radius: 8px; +} + +.v2-linkage-guard svg { width: 15px; height: 15px; color: var(--v2-accent); } +.v2-linkage-guard p { display: grid; gap: 2px; margin: 0; } +.v2-linkage-guard strong { color: var(--v2-ink); font-size: 10px; } +.v2-linkage-guard span { font-size: 9.5px; line-height: 1.5; } + +.v2-parameter-section { + margin-top: 11px; +} + +.v2-parameter-section > header p, +.v2-model-events > header p { + margin: 0; + color: var(--v2-muted); + font-size: 9.5px; +} + +.v2-parameter-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 8px; + margin-top: 7px; +} + +.v2-parameter-card { + min-width: 0; + padding: 8px 9px; + background: color-mix(in srgb, var(--v2-accent-soft) 9%, white); + border: 1px solid var(--v2-line); + border-radius: 9px; +} + +.v2-parameter-card > div { + min-width: 0; + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.v2-parameter-card strong { + overflow: hidden; + color: var(--v2-ink); + font-size: 11.5px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.v2-parameter-card > div > span { + color: var(--v2-accent-strong); + font-size: 10px; + font-weight: 850; + white-space: nowrap; +} + +.v2-parameter-card > div > span[data-evolution-scope="admin"] { + align-items: center; + gap: 4px; + font: 750 9.5px/1 "SFMono-Regular", Consolas, monospace; +} + +.v2-parameter-card > div > span svg { width: 9px; height: 9px; } +.v2-parameter-card > div em { color: #8e9b9a; font-style: normal; } +.v2-parameter-card > div b { color: var(--v2-accent-strong); } + +.v2-parameter-card > small { + display: block; + margin-top: 4px; + overflow: hidden; + color: var(--v2-muted); + font-size: 9.5px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.v2-parameter-progress { + width: 100%; + height: 3px; + display: block; + margin-top: 7px; + overflow: hidden; + background: #e8efee; + border-radius: 99px; +} + +.v2-parameter-progress i { + width: var(--parameter-progress); + height: 100%; + display: block; + background: var(--v2-accent); + border-radius: inherit; +} + +.v2-model-events { + margin-top: 10px; +} + +.v2-model-events > header { + padding-bottom: 5px; + border-bottom: 1px solid var(--v2-line); +} + +.v2-model-events > div { + display: grid; +} + +.v2-model-event { + min-width: 0; + display: grid; + grid-template-columns: 76px minmax(180px, 1.4fr) minmax(95px, 0.7fr) minmax(100px, 0.7fr) auto; + align-items: center; + gap: 10px; + padding: 6px 0; + color: var(--v2-muted); + border-bottom: 1px solid color-mix(in srgb, var(--v2-line) 65%, transparent); + font-size: 10px; +} + +.v2-model-event:last-child { border-bottom: 0; } +.v2-model-event time { color: var(--v2-ink); font: 750 9.5px/1 "SFMono-Regular", Consolas, monospace; } +.v2-model-event strong { overflow: hidden; color: var(--v2-ink); font-size: 10px; text-overflow: ellipsis; white-space: nowrap; } +.v2-model-event span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.v2-model-event em { color: var(--v2-accent-strong); font-size: 10px; font-style: normal; font-weight: 800; white-space: nowrap; } +.v2-model-event.warning em { color: var(--evolution-warning); } + +.v2-self-evolution .v2-evolution-next { + position: relative; + z-index: 5; + grid-template-columns: 38px minmax(280px, 1fr) minmax(140px, 190px); + padding-block: 9px; +} + +.v2-milestone-progress { + display: grid; + justify-items: end; + gap: 4px; +} + +.v2-milestone-progress strong { + color: var(--v2-accent-strong); + font: 850 10px/1 "SFMono-Regular", Consolas, monospace; +} + +.v2-milestone-progress > span { + width: 100%; + height: 4px; + display: block; + overflow: hidden; + background: rgba(255, 255, 255, 0.8); + border-radius: 99px; +} + +.v2-milestone-progress > span i { + width: 0; + height: 100%; + display: block; + background: var(--v2-accent); + border-radius: inherit; + transition: width 240ms ease; +} + +.v2-milestone-progress small { + color: var(--v2-muted); + font-size: 7px; +} + +.v2-self-evolution[data-evolution-view="system"] .v2-evolution-shell { + grid-template-rows: auto minmax(0, 1fr); +} + +.v2-self-evolution[data-evolution-view="system"] .v2-evolution-page[data-evolution-page="system"] { + overflow: auto; +} + +.v2-self-evolution[data-evolution-view="system"] .v2-evolution-workspace { + min-height: 100%; +} + +.console-v2 [data-evolution-scope="admin"] { + display: none; +} + +.console-v2[data-demo-role="admin"] [data-evolution-scope="user"] { + display: none; +} + +.console-v2[data-demo-role="admin"] [data-evolution-scope="admin"] { + display: inline-flex; +} + +@media (max-width: 1250px) { + .v2-self-evolution .v2-evolution-overview { + grid-template-columns: minmax(320px, 0.8fr) minmax(430px, 1fr); + } + + .v2-weighing-core { + grid-template-columns: minmax(390px, 1.55fr) minmax(220px, 0.8fr); + } + + .v2-family-panel { + padding-left: 18px; + } +} + +@media (max-height: 790px) { + .v2-self-evolution .v2-evolution-overview { padding-block: 11px; } + .v2-self-evolution .v2-evolution-heading h1 { font-size: 23px; } + .v2-self-evolution .v2-evolution-heading > p { margin-top: 3px; font-size: 9px; } + .v2-family-panel { padding-top: 14px; } + .v2-family-model { min-height: 50px; } + .v2-weighing-detail { padding-top: 13px; } + .v2-chart-wrap { min-height: 145px; } + .v2-convergence-card, .v2-linkage-card { padding-block: 8px; } + .v2-linkage-card li { min-height: 32px; } +} diff --git a/apps/web-console/self-evolution.js b/apps/web-console/self-evolution.js new file mode 100644 index 0000000..30436f7 --- /dev/null +++ b/apps/web-console/self-evolution.js @@ -0,0 +1,494 @@ +(function () { + "use strict"; + + const $ = (selector, root = document) => root.querySelector(selector); + const $$ = (selector, root = document) => [...root.querySelectorAll(selector)]; + + const familyModels = [ + { + id: "user-a", + name: "用户 A", + code: "USER-A", + version: "v1.7", + state: "enabled", + stateLabel: "已启用", + statusNote: "专属模型已启用", + error: 0.18, + samples: 8, + sampleTarget: 11, + sync: "2026.08.14 · 08:29", + errors: [0.50, 0.44, 0.38, 0.32, 0.27, 0.23, 0.20, 0.18], + parameters: [ + { name: "零点偏移补偿", user: "已优化", before: "+0.38", after: "+0.11", progress: 88 }, + { name: "坐姿角度补偿", user: "稳定", before: "0.64", after: "0.91", progress: 91 }, + { name: "压力分布权重", user: "已优化", before: "0.72", after: "0.93", progress: 93 }, + { name: "温度漂移补偿", user: "学习中", before: "42%", after: "76%", progress: 76 }, + ], + events: [ + { time: "08.14 08:29", title: "模型 v1.7 验证通过并启用", meta: "有效样本 8", result: "±0.22 → ±0.18 kg", state: "已发布" }, + { time: "08.14 08:27", title: "家庭称重与座椅采样配对", meta: "身份匹配成功", result: "采样质量 96%", state: "已进入学习" }, + { time: "08.12 19:06", title: "模型 v1.6 参数更新", meta: "有效样本 7", result: "±0.24 → ±0.22 kg", state: "已验证" }, + ], + adminReference: "家庭体脂秤 62.40 kg · 座椅估计 62.58 kg", + }, + { + id: "user-b", + name: "用户 B", + code: "USER-B", + version: "v1.3", + state: "learning", + stateLabel: "学习中", + statusNote: "4 / 6 个有效样本", + error: 0.31, + samples: 4, + sampleTarget: 6, + sync: "2026.08.13 · 20:18", + errors: [0.50, 0.43, 0.37, 0.31], + parameters: [ + { name: "零点偏移补偿", user: "学习中", before: "+0.41", after: "+0.24", progress: 62 }, + { name: "坐姿角度补偿", user: "学习中", before: "0.61", after: "0.76", progress: 58 }, + { name: "压力分布权重", user: "已优化", before: "0.69", after: "0.82", progress: 70 }, + { name: "温度漂移补偿", user: "等待样本", before: "18%", after: "34%", progress: 34 }, + ], + events: [ + { time: "08.13 20:22", title: "新参数验证未优于 v1.3,保持旧模型", meta: "本次未参与发布", result: "±0.31 → ±0.34 kg", state: "已回滚", tone: "warning" }, + { time: "08.13 20:19", title: "检测到坐姿不稳定", meta: "异常样本已隔离", result: "采样质量 61%", state: "未参与更新", tone: "warning" }, + { time: "08.10 07:51", title: "第 4 组有效样本完成配对", meta: "身份匹配成功", result: "±0.37 → ±0.31 kg", state: "学习中" }, + ], + adminReference: "家庭体脂秤 71.25 kg · 异常座椅估计 71.59 kg", + }, + { + id: "user-c", + name: "用户 C", + code: "USER-C", + version: "—", + state: "cold", + stateLabel: "冷启动", + statusNote: "等待首次家庭称重", + error: 0.50, + samples: 0, + sampleTarget: 6, + sync: "尚未同步", + errors: [], + parameters: [ + { name: "零点偏移补偿", user: "等待数据", before: "—", after: "—", progress: 0 }, + { name: "坐姿角度补偿", user: "等待数据", before: "—", after: "—", progress: 0 }, + { name: "压力分布权重", user: "等待数据", before: "—", after: "—", progress: 0 }, + { name: "温度漂移补偿", user: "等待数据", before: "—", after: "—", progress: 0 }, + ], + events: [ + { time: "等待中", title: "尚无可用的车家配对样本", meta: "需要先在家庭体脂秤完成称重", result: "使用通用模型", state: "±0.50 kg" }, + ], + adminReference: "尚无家庭体脂秤参考值", + }, + { + id: "guest", + name: "访客", + code: "GENERIC", + version: "通用", + state: "generic", + stateLabel: "通用模型", + statusNote: "未识别用户使用通用模型", + error: 0.50, + samples: 0, + sampleTarget: 0, + sync: "无需同步", + errors: [0.50], + parameters: [ + { name: "零点偏移补偿", user: "通用参数", before: "0.00", after: "0.00", progress: 50 }, + { name: "坐姿角度补偿", user: "通用参数", before: "0.50", after: "0.50", progress: 50 }, + { name: "压力分布权重", user: "通用参数", before: "0.68", after: "0.68", progress: 50 }, + { name: "温度漂移补偿", user: "通用参数", before: "50%", after: "50%", progress: 50 }, + ], + events: [ + { time: "当前策略", title: "未识别用户不创建专属模型", meta: "不保存个人参考体重", result: "通用模型基线", state: "±0.50 kg" }, + ], + adminReference: "访客模式不记录精确体重", + }, + ]; + + const systemEntries = [ + { + version: "v0.5", date: "2026.08.12", status: "当前版本", title: "统一主题与权限体验", + summary: "把权限、主题与核心交互收束到同一套体验规则,让不同模块看起来、用起来都属于同一个系统。", + changes: [["主题统一", "自扩展固定使用 user 绿色主题,避免权限语义混用。"], ["孪生同步", "3D 模型渲染、玩具动作和车端动作统一跟随当前主题。"], ["能力扩充", "新增自进化入口,让 user 与 admin 共同查看演进过程。"]], + modules: ["自扩展", "数字孪生", "权限体系"], metrics: [["主题一致性", "72%", "100%", 100], ["可见能力模块", "4 个", "5 个", 92], ["待修正视觉项", "4 项", "0 项", 100]], + userValidation: "核心板块已完成主题复核,权限色与交互反馈保持一致。", adminValidation: "前端契约 11/11 · 自标定/自修复检查 8/8", + }, + { + version: "v0.4", date: "2026.08.11", status: "稳定版本", title: "自修复闭环增强", + summary: "从发现异常走向诊断、修复与验证闭环,同时补齐离线场景的默认内容,确保功能状态始终可见。", + changes: [["故障覆盖", "补充六类量产故障用例,覆盖更真实的车端异常。"], ["修复闭环", "形成发现、定位、决策、执行、验证五阶段流程。"], ["离线韧性", "服务暂时离线时仍保留默认案例和演示路径。"]], + modules: ["自修复", "故障诊断", "离线体验"], metrics: [["故障用例", "2 类", "6 类", 86], ["修复阶段", "3 阶段", "5 阶段", 92], ["离线可用性", "空白", "可演示", 100]], + userValidation: "断开服务后仍可理解自修复能力,并可继续浏览默认案例。", adminValidation: "默认数据回退、流程状态与接口异常路径均已验证。", + }, + { + version: "v0.3", date: "2026.08.10", status: "能力升级", title: "外设与车端统一编排", + summary: "将玩具动作、车端设备动作与数字孪生放进同一个控制流程,减少跨区域理解和操作成本。", + changes: [["动作融合", "玩具动作与车端设备动作进入统一动作界面。"], ["布局优化", "孪生界面位于左侧,动作界面固定在右侧。"], ["映射精简", "移除重复外框,让实时映射关系更直接。"]], + modules: ["自扩展", "外设学习", "整车控制"], metrics: [["动作入口", "2 处", "1 处", 100], ["映射层级", "3 层", "2 层", 88], ["协同对象", "玩具", "玩具 + 车端", 94]], + userValidation: "动作与孪生左右分区清晰,学习结果可以直接用于统一控制。", adminValidation: "外设连接状态、动作映射与车端执行链路保持独立可追踪。", + }, + { + version: "v0.2", date: "2026.08.09", status: "体系建立", title: "多权限主题体系", + summary: "建立 user 与 admin 的视觉身份,使权限差异不只体现在可用功能,也能被用户直观感知。", + changes: [["角色主题", "user 使用绿色,admin 使用蓝色,形成稳定的权限认知。"], ["能力边界", "按角色控制模块入口,同时保留清晰的切换反馈。"], ["模型联动", "数字孪生模型与界面主题保持同色语义。"]], + modules: ["权限体系", "主题系统", "数字孪生"], metrics: [["角色主题", "1 套", "2 套", 100], ["权限反馈", "文字", "视觉 + 文字", 90], ["主题联动", "界面", "界面 + 模型", 96]], + userValidation: "绿色主题贯穿 user 可用能力,角色身份更加明确。", adminValidation: "角色切换事件可同步驱动界面变量与模型渲染参数。", + }, + { + version: "v0.1", date: "2026.08.08", status: "起点版本", title: "整车认知控制台诞生", + summary: "完成从自然语言意图到整车状态回读的第一条闭环,建立后续能力持续进化的共同底座。", + changes: [["自然语言控制", "用贴近日常表达的指令控制座舱功能。"], ["整车孪生", "把座椅、空调、车窗等状态集中映射到一个界面。"], ["执行回读", "串联 CAN 计划、设备执行与状态反馈。"]], + modules: ["自编排", "数字孪生", "CAN 执行"], metrics: [["能力模块", "0 个", "1 个", 100], ["控制链路", "分散", "闭环", 92], ["状态视图", "单设备", "整车", 96]], + userValidation: "自然语言、执行过程与结果反馈首次形成连续体验。", adminValidation: "意图解析、CAN 编译计划与硬件回读具备完整追踪链路。", + }, + ]; + + let playbackTimer = null; + let activeModelId = "user-a"; + let visibleSamples = null; + + function make(tag, className, html) { + const node = document.createElement(tag); + if (className) node.className = className; + if (html != null) node.innerHTML = html; + return node; + } + + function formatError(value) { + return `±${Number(value).toFixed(2)}`; + } + + function memberButtons() { + return familyModels.map((model, index) => ` + `).join(""); + } + + function systemVersionButtons() { + return systemEntries.map((entry, index) => ` + `).join(""); + } + + function shellTemplate() { + return ` +
+
+
+
+
SELF-EVOLUTION · SEAT WEIGHING

座椅称重模型进化

+ 演示数据 +
+

通过车家联动持续校准家庭成员专属模型,让称重误差从 ±0.50 kg 逐步收敛至 ±0.10 kg。

+
+
+
+ +
+
+ + +
+
+
+
车家同步正常
+
+
+

+ +
+ +
+
+

验证误差收敛趋势

通用模型专属模型目标误差
+
+

+
+ +
+

本轮车家联动闭环

+
    +
    +
    +
    + +
    +

    模型参数持续进化

    展示可解释参数状态管理员可见参数前后值与发布保障

    +
    +
    + +
    +

    最近进化记录

    仅显示与当前用户相关的状态

    +
    +
    +
    +
    +
    + + + + +
    `; + } + + function summaryTemplate(model, view) { + if (view === "system") { + return `
    当前版本
    v0.5
    能力融合期
    累计迭代
    5
    个版本
    能力改进
    12
    项记录
    最近更新
    08.12
    2026
    `; + } + return `
    当前用户
    ${model.name}
    ${model.state === "generic" ? "隐私访客模式" : model.statusNote}
    当前验证误差
    ${formatError(model.error)}
    kg · 演示数据
    目标误差
    ±0.10
    kg
    有效样本
    ${model.samples}
    ${model.sampleTarget ? `组车家配对` : "无需配对"}
    `; + } + + function chartTemplate(model, count = model.errors.length) { + const values = model.errors.slice(0, count); + const width = 720; + const left = 48; + const right = 698; + const top = 18; + const bottom = 184; + const y = (error) => top + ((0.52 - error) / 0.44) * (bottom - top); + const x = (index) => values.length <= 1 ? left : left + (index / (Math.max(model.errors.length, 2) - 1)) * (right - left); + const grid = [0.5, 0.4, 0.3, 0.2, 0.1].map((error) => ` + ±${error.toFixed(2)}`).join(""); + const points = values.map((error, index) => `${x(index)},${y(error)}`).join(" "); + const circles = values.map((error, index) => ` + + + `).join(""); + const labels = model.errors.map((_, index) => `样本${index + 1}`).join(""); + const area = values.length > 1 ? `` : ""; + const line = values.length > 1 ? `` : ""; + const current = values.length ? values[values.length - 1] : null; + return ` + ${grid} + ${area}${line}${circles}${labels} + ${current != null ? `当前 ${formatError(current)}` : ""} + ${values.length ? "" : `
    等待首组车家配对样本当前继续使用通用模型 ±0.50 kg
    `}`; + } + + function linkageTemplate(model) { + const baseSteps = [ + ["家庭体脂秤", "收到用户参考体重"], ["用户 ID 匹配", "家庭账号与车端身份一致"], ["座椅稳定采样", "坐姿、温度、座椅位置质量合格"], ["专属参数更新", "生成候选参数并离线验证"], ["验证并发布", "优于旧模型后正式生效"], + ]; + let done = model.state === "enabled" ? 5 : model.state === "learning" ? 3 : 0; + if (model.state === "generic") done = 0; + const list = baseSteps.map(([title, copy], index) => { + const state = index < done ? "done" : index === done && model.state === "learning" ? "active" : "pending"; + const label = state === "done" ? "已完成" : state === "active" ? "进行中" : "等待"; + return `
  1. ${index + 1}

    ${title}${copy}

    ${label}
  2. `; + }).join(""); + const guard = model.id === "user-b" + ? `

    退化保护已触发候选模型误差上升至 ±0.34 kg,已自动回滚并保留 v1.3。

    ` + : model.state === "enabled" + ? `

    发布保护正常只有验证结果优于当前模型时才会发布新参数。

    ` + : `

    专属模型尚未发布样本不足时保持通用模型,不以未验证参数影响称重。

    `; + return { list, guard, done }; + } + + function parameterTemplate(model) { + return model.parameters.map((item) => ` +
    +
    ${item.name}${item.user}${item.before}${item.after}
    + ${item.user === "等待数据" ? "等待有效车家样本" : "根据近期有效样本持续校准"}参数窗口 8 组 · 可回滚 · MODEL ${model.version} + +
    `).join(""); + } + + function eventTemplate(model) { + return model.events.map((event) => ` +
    ${event.title}${event.meta}${event.result}${event.state}
    `).join(""); + } + + function renderModel(root, modelId, { keepPlayback = false } = {}) { + const model = familyModels.find((item) => item.id === modelId) || familyModels[0]; + activeModelId = model.id; + if (!keepPlayback) { + clearInterval(playbackTimer); + playbackTimer = null; + visibleSamples = null; + $("#v2PlayEvolution", root)?.classList.remove("playing"); + const playLabel = $("#v2PlayEvolution span", root); + if (playLabel) playLabel.textContent = "演示进化过程"; + } + $("#v2EvolutionSummary", root).innerHTML = summaryTemplate(model, "weighing"); + $$("[data-family-model]", root).forEach((button) => { + const active = button.dataset.familyModel === model.id; + button.classList.toggle("active", active); + button.setAttribute("aria-pressed", String(active)); + }); + const badge = $("#v2ModelStateBadge", root); + badge.textContent = model.state === "enabled" ? "专属模型已启用" : model.stateLabel; + badge.dataset.modelState = model.state; + $("#v2ModelCode", root).textContent = `MODEL ${model.version} · ${model.code}`; + $("#v2HomeSyncState", root).textContent = model.state === "generic" ? "访客隐私模式" : model.sync === "尚未同步" ? "等待车家同步" : "车家同步正常"; + $("#v2HomeSyncTime", root).textContent = model.sync; + $("#v2ModelTitle", root).textContent = `${model.name} · ${model.state === "generic" ? "通用称重模型" : "个性化称重模型"}`; + $("#v2ModelLead", root).textContent = model.state === "enabled" ? "最近一次家庭参考值与车端座椅采样已完成配对,新参数通过验证并正式生效。" : model.state === "learning" ? "专属参数仍在学习;样本不足或验证退化时,车端继续使用已验证模型。" : model.state === "cold" ? "尚未收到家庭体脂秤参考值,当前安全使用通用模型。" : "未识别用户不会创建个人参数或保存精确体重。"; + const play = $("#v2PlayEvolution", root); + play.disabled = model.errors.length < 2; + play.title = play.disabled ? "当前没有可回放的专属模型样本" : "按有效样本顺序回放误差收敛过程"; + $("#v2EvolutionChart", root).innerHTML = chartTemplate(model, visibleSamples ?? model.errors.length); + $("#v2SampleInsight", root).textContent = model.errors.length > 1 ? `已从通用模型基线 ±0.50 kg 收敛至 ${formatError(model.error)} kg;点击曲线节点可查看该次样本。` : "个性化模型仅使用身份匹配、时间有效且座椅采样稳定的数据。"; + const linkage = linkageTemplate(model); + $("#v2LinkageSteps", root).innerHTML = linkage.list; + $("#v2LinkageGuard", root).innerHTML = linkage.guard; + $("#v2LinkageProgress", root).textContent = `${linkage.done} / 5 已完成`; + $("#v2ParameterGrid", root).innerHTML = parameterTemplate(model); + $("#v2EventList", root).innerHTML = eventTemplate(model); + $("#v2AdminReference", root).textContent = model.adminReference; + const remaining = Math.max(0, model.sampleTarget - model.samples); + const percent = model.sampleTarget ? Math.min(100, Math.round((model.samples / model.sampleTarget) * 100)) : 100; + $("#v2MilestoneCopy", root).textContent = model.state === "generic" ? "访客称重不会进入任何家庭成员的参数空间。" : model.state === "cold" ? "完成家庭体脂秤绑定后,首次有效配对将启动专属模型学习。" : remaining ? `预计再完成 ${remaining} 组有效样本后进入目标精度验证阶段。` : "样本数量已满足要求,正在持续验证 ±0.10 kg 目标。"; + $("#v2MilestoneCount", root).textContent = model.sampleTarget ? `${model.samples} / ${model.sampleTarget} 组` : "隐私模式"; + $("#v2MilestonePercent", root).textContent = model.sampleTarget ? `当前学习进度 ${percent}%` : "不留存个人数据"; + $("#v2MilestoneBar", root).style.width = `${percent}%`; + window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } }); + } + + function renderSystemEntry(root, index) { + const entry = systemEntries[index]; + if (!entry) return; + $("#v2SystemStatus", root).textContent = entry.status; + $("#v2SystemVersion", root).textContent = entry.version; + const date = $("#v2SystemDate", root); + date.textContent = entry.date; + date.dateTime = entry.date.replaceAll(".", "-"); + $("#v2SystemTitle", root).textContent = entry.title; + $("#v2SystemLead", root).textContent = entry.summary; + $("#v2SystemChangeList", root).innerHTML = entry.changes.map(([label, copy]) => `

    ${label}${copy}

    `).join(""); + $("#v2SystemMetrics", root).innerHTML = entry.metrics.map(([label, before, after, progress]) => `
    ${label}

    ${before}${after}

    `).join(""); + $("#v2SystemModules", root).innerHTML = entry.modules.map((module) => `${module}`).join(""); + $("#v2SystemUserValidation", root).textContent = entry.userValidation; + $("#v2SystemAdminValidation", root).textContent = entry.adminValidation; + $$("[data-system-evolution-index]", root).forEach((button, buttonIndex) => { + const active = buttonIndex === index; + button.classList.toggle("active", active); + button.setAttribute("aria-pressed", String(active)); + }); + window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } }); + } + + function setView(root, view) { + const system = view === "system"; + clearInterval(playbackTimer); + playbackTimer = null; + $$("[data-evolution-page]", root).forEach((page) => { + const active = page.dataset.evolutionPage === view; + page.classList.toggle("active", active); + page.hidden = !active; + }); + $("#v2EvolutionKicker", root).textContent = system ? "SELF-EVOLUTION · SYSTEM JOURNAL" : "SELF-EVOLUTION · SEAT WEIGHING"; + $("#v2EvolutionPageTitle", root).textContent = system ? "系统能力日志" : "座椅称重模型进化"; + $("#v2EvolutionPageIntro", root).textContent = system ? "保留每次问题发现、能力升级与验证结果,持续记录控制台系统能力演进。" : "通过车家联动持续校准家庭成员专属模型,让称重误差从 ±0.50 kg 逐步收敛至 ±0.10 kg。"; + $("#v2EvolutionDataBadge", root).innerHTML = system + ? '历史记录' + : '演示数据'; + $("#v2EvolutionSummary", root).innerHTML = summaryTemplate(familyModels.find((item) => item.id === activeModelId), view); + const footer = $("#v2EvolutionFooter", root); + footer.hidden = system; + root.dataset.evolutionView = view; + window.dispatchEvent(new CustomEvent("cognitive-agent:evolution-view-change", { + detail: { view }, + })); + window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } }); + } + + function playEvolution(root) { + const model = familyModels.find((item) => item.id === activeModelId); + if (!model || model.errors.length < 2) return; + clearInterval(playbackTimer); + let count = 1; + visibleSamples = count; + const button = $("#v2PlayEvolution", root); + button.classList.add("playing"); + $("#v2PlayEvolution span", root).textContent = "正在回放"; + renderModel(root, model.id, { keepPlayback: true }); + playbackTimer = window.setInterval(() => { + count += 1; + visibleSamples = count; + renderModel(root, model.id, { keepPlayback: true }); + if (count >= model.errors.length) { + clearInterval(playbackTimer); + playbackTimer = null; + button.classList.remove("playing"); + $("#v2PlayEvolution span", root).textContent = "再次回放"; + } + }, 520); + } + + function bind(root) { + $(".v2-family-list", root).addEventListener("click", (event) => { + const button = event.target.closest("[data-family-model]"); + if (button) renderModel(root, button.dataset.familyModel); + }); + $(".v2-evolution-version-list", root).addEventListener("click", (event) => { + const button = event.target.closest("[data-system-evolution-index]"); + if (button) renderSystemEntry(root, Number(button.dataset.systemEvolutionIndex)); + }); + $("#v2PlayEvolution", root).addEventListener("click", () => playEvolution(root)); + $("#v2EvolutionChart", root).addEventListener("click", (event) => { + const point = event.target.closest("[data-sample-index]"); + const model = familyModels.find((item) => item.id === activeModelId); + if (!point || !model) return; + const index = Number(point.dataset.sampleIndex); + $("#v2SampleInsight", root).textContent = `样本 ${index + 1}:验证误差 ${formatError(model.errors[index])} kg。该节点来自固定演示数据,不代表实时算法结果。`; + }); + $("#v2EvolutionChart", root).addEventListener("keydown", (event) => { + if (!["Enter", " "].includes(event.key)) return; + const point = event.target.closest("[data-sample-index]"); + if (point) { event.preventDefault(); point.dispatchEvent(new MouseEvent("click", { bubbles: true })); } + }); + } + + function mount(mainPanel) { + const existing = $("#v2SelfEvolution", mainPanel); + if (existing) return existing; + const evolution = make("section", "v2-self-evolution", shellTemplate()); + evolution.id = "v2SelfEvolution"; + evolution.dataset.evolutionView = "weighing"; + evolution.setAttribute("aria-label", "自进化 · 座椅称重个性化模型进化"); + mainPanel.append(evolution); + bind(evolution); + renderModel(evolution, activeModelId); + renderSystemEntry(evolution, 0); + setView(evolution, "weighing"); + return evolution; + } + + window.CognitiveAgentSelfEvolution = { + mount, + getView: () => $("#v2SelfEvolution")?.dataset.evolutionView || "weighing", + setView: (view) => { + const root = $("#v2SelfEvolution"); + if (root) setView(root, view === "system" ? "system" : "weighing"); + }, + }; +}()); diff --git a/apps/web-console/service-worker.js b/apps/web-console/service-worker.js index dc8b944..383ad9c 100644 --- a/apps/web-console/service-worker.js +++ b/apps/web-console/service-worker.js @@ -1,4 +1,4 @@ -const CACHE_VERSION = "cognitive-agent-pad-v119"; +const CACHE_VERSION = "cognitive-agent-pad-v120"; const SHELL_CACHE = `${CACHE_VERSION}-shell`; const RUNTIME_CACHE = `${CACHE_VERSION}-runtime`; const APP_SHELL = [ @@ -9,8 +9,10 @@ const APP_SHELL = [ "/app.js?v=dual-seat-82", "/gesture-control.js?v=android-pad-7", "/pwa.js?v=android-pad-1", - "/console-v2.css?v=desktop-refactor-106", - "/console-v2.js?v=desktop-refactor-35", + "/console-v2.css?v=desktop-refactor-107", + "/console-v2.js?v=desktop-refactor-36", + "/self-evolution.css?v=seat-weighing-5", + "/self-evolution.js?v=seat-weighing-2", "/self-extension.css?v=extension-19", "/self-extension.js?v=extension-21", "/self-calibration.css?v=calibration-12", diff --git a/tests/e2e/test_seat_web_api.py b/tests/e2e/test_seat_web_api.py index 5b9ea44..d0033b5 100755 --- a/tests/e2e/test_seat_web_api.py +++ b/tests/e2e/test_seat_web_api.py @@ -184,13 +184,21 @@ operations: ) as response: gesture_script = response.read().decode("utf-8") with self.opener.open( - f"{self.base_url}/console-v2.js?v=desktop-refactor-35", timeout=3 + f"{self.base_url}/console-v2.js?v=desktop-refactor-36", timeout=3 ) as response: console_script = response.read().decode("utf-8") with self.opener.open( - f"{self.base_url}/console-v2.css?v=desktop-refactor-106", timeout=3 + f"{self.base_url}/console-v2.css?v=desktop-refactor-107", timeout=3 ) as response: console_styles = response.read().decode("utf-8") + with self.opener.open( + f"{self.base_url}/self-evolution.js?v=seat-weighing-2", timeout=3 + ) as response: + evolution_script = response.read().decode("utf-8") + with self.opener.open( + f"{self.base_url}/self-evolution.css?v=seat-weighing-5", timeout=3 + ) as response: + evolution_styles = response.read().decode("utf-8") with self.opener.open( f"{self.base_url}/self-extension.js?v=extension-21", timeout=3 ) as response: @@ -227,8 +235,10 @@ operations: self.assertIn('href="/styles.css?v=dual-seat-9"', html) self.assertIn('src="/gesture-control.js?v=android-pad-7"', html) self.assertNotIn('class="vehicle-system-hud"', html) - self.assertIn('src="/console-v2.js?v=desktop-refactor-35"', html) - self.assertIn('href="/console-v2.css?v=desktop-refactor-106"', html) + self.assertIn('src="/console-v2.js?v=desktop-refactor-36"', html) + self.assertIn('href="/console-v2.css?v=desktop-refactor-107"', html) + self.assertIn('src="/self-evolution.js?v=seat-weighing-2"', html) + self.assertIn('href="/self-evolution.css?v=seat-weighing-5"', html) self.assertIn('src="/self-extension.js?v=extension-21"', html) self.assertIn('href="/self-extension.css?v=extension-19"', html) self.assertNotIn("滚动查看更多", html) @@ -268,13 +278,26 @@ operations: self.assertIn('label: "自修复"', console_script) self.assertIn('id: "self-evolution", label: "自进化", available: true', console_script) self.assertIn( - 'evolution.setAttribute("aria-label", "自进化能力日志")', + 'window.CognitiveAgentSelfEvolution?.mount(mainPanel)', console_script, ) - self.assertIn('SELF-EVOLUTION', console_script) - self.assertIn('v2-evolution-heading v2-module-heading', console_script) - self.assertIn('data-evolution-index', console_script) - self.assertIn('NEXT EVOLUTION · v0.6', console_script) + self.assertIn('caption.textContent = evolutionMode ? "进化视图" : "控制方式"', console_script) + self.assertIn('{ label: "座椅称重进化", icon: "scale"', console_script) + self.assertIn('{ label: "系统能力日志", icon: "scroll-text"', console_script) + self.assertIn('aria-label", "自进化 · 座椅称重个性化模型进化"', evolution_script) + self.assertIn('SELF-EVOLUTION · SEAT WEIGHING', evolution_script) + self.assertIn('座椅称重模型进化', evolution_script) + self.assertIn('系统能力日志', evolution_script) + self.assertIn('data-family-model="${model.id}"', evolution_script) + self.assertIn('用户 A', evolution_script) + self.assertIn('用户 B', evolution_script) + self.assertIn('用户 C', evolution_script) + self.assertIn('访客', evolution_script) + self.assertIn('退化保护已触发', evolution_script) + self.assertIn('本次未参与发布', evolution_script) + self.assertIn('演示进化过程', evolution_script) + self.assertIn('家庭体脂秤', evolution_script) + self.assertIn('目标误差', evolution_script) self.assertIn('["self-orchestration", "self-extension", "self-evolution"]', console_script) self.assertIn('roleSelect.disabled = ui.activeModule === "self-repair"', console_script) self.assertNotIn('label: "自诊断"', console_script) @@ -299,7 +322,7 @@ operations: self.assertIn('.v2-control-switch {', console_styles) self.assertIn('.v2-module-switcher {', console_styles) self.assertIn('grid-template-columns: repeat(5, minmax(0, 1fr))', console_styles) - self.assertIn('[data-module="self-evolution"] .v2-self-evolution', console_styles) + self.assertIn('[data-module="self-evolution"] .v2-self-evolution', evolution_styles) self.assertIn( '.console-v2 .v2-module-heading.v2-module-heading h1', console_styles, @@ -319,8 +342,14 @@ operations: '[data-module="self-repair"] .v2-self-repair', console_styles, ) - self.assertIn('.v2-evolution-version.active', console_styles) - self.assertIn('[data-evolution-scope="admin"]', console_styles) + self.assertIn('.v2-evolution-version.active', evolution_styles) + self.assertIn('[data-evolution-scope="admin"]', evolution_styles) + self.assertIn('.v2-weighing-workspace', evolution_styles) + self.assertIn('.v2-family-model.active', evolution_styles) + self.assertIn('.v2-personal-line', evolution_styles) + self.assertIn('.v2-linkage-guard', evolution_styles) + self.assertIn('.v2-parameter-grid', evolution_styles) + self.assertIn('[data-demo-role="admin"] [data-evolution-scope="admin"]', evolution_styles) self.assertIn( 'color-mix(in srgb, var(--v2-accent-soft) 86%, white)', console_styles, @@ -455,7 +484,9 @@ operations: self.assertIn("window.requestAnimationFrame(initSeatTwin)", app_script) self.assertIn('canvas.addEventListener("webglcontextlost"', app_script) self.assertIn('"cognitive-agent:module-change"', app_script) - self.assertIn('const CACHE_VERSION = "cognitive-agent-pad-v119"', service_worker) + self.assertIn('const CACHE_VERSION = "cognitive-agent-pad-v120"', service_worker) + self.assertIn('"/self-evolution.js?v=seat-weighing-2"', service_worker) + self.assertIn('"/self-evolution.css?v=seat-weighing-5"', service_worker) self.assertIn( '"/assets/models/n7_seat.glb?v=e7f50f03"', service_worker )