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", `
-
`);
- 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]) => `
- `).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 `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 模型参数持续进化
展示可解释参数状态管理员可见参数前后值与发布保障
+
+
+
+
+
+
+
+
+
+
+
+
`;
+ }
+
+ function summaryTemplate(model, view) {
+ if (view === "system") {
+ return `当前版本v0.5能力融合期累计迭代5个版本能力改进12项记录最近更新08.122026`;
+ }
+ return `当前用户${model.name}${model.state === "generic" ? "隐私访客模式" : model.statusNote}当前验证误差${formatError(model.error)}kg · 演示数据目标误差±0.10kg有效样本${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 `${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 `${index + 1}${title}${copy}
${label}`;
+ }).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]) => ``).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
)