Skip to content

feat: 监控板视觉重设计 + WebUI 自定义密码(4.1.0) - #248

Merged
EterUltimate merged 7 commits into
mainfrom
feat/dashboard-crisp-redesign
Sep 5, 2026
Merged

feat: 监控板视觉重设计 + WebUI 自定义密码(4.1.0)#248
EterUltimate merged 7 commits into
mainfrom
feat/dashboard-crisp-redesign

Conversation

@EterUltimate

@EterUltimate EterUltimate commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

概述

两部分工作:(1) 基于 garden-skills 仓库 web-design-engineer 方法论(Linear 风格配方)的监控板视觉重设计;(2) WebUI 自定义密码能力(免密模式可直接设置密码,修改密码后强制重新登录)。保留信息架构、路由、文案、行为契约。版本号提升至 4.1.0

一、视觉重设计(Linear 风格)

维度
表面 半透明毛玻璃 固态表面 + 发丝线边框
圆角 10/16/24px 4/6/10px
阴影 大软阴影+光晕 近平 1-2px
悬停 上浮+发光 纯边框/背景切换,120ms ease-out
页面切换 260ms 位移 220ms quint-out,仅 opacity+4px
强调色 单一主色 14 页独立强调色(亮/暗分别调校)
  • 每页 accent 仅用于导航激活下划线、kicker、面板图标、StatCard 顶部刻线、模块卡图标(<5% 面积纪律)。
  • 顶部导航去滚动条:滚轮直接横向滑动导航栏(垂直滚轮转横向,到两端放行页面滚动),全引擎隐藏滚动条。
  • 设置页 master-detail:左分组目录(搜索/字段计数/未保存脏点)+ 右详情;搜索跨分组结果视图;依赖安装收侧栏底部。

二、WebUI 自定义密码

  • 新增「WebUI 访问密码」设置面板(免密访问 / 已启用密码保护 两种状态徽标):
    • 免密模式:直接设置自定义密码并启用保护 → POST /api/webui_password/setupmanual_confirmed 确认标记 + 强度校验 + PBKDF2 存储 + 显式 save_config() 持久化 enable_webui_password,重启不回退)。
    • 已启用模式:面板内修改密码(旧密码 + 新密码,复用 /api/plugin_change_password)。
  • 改密即强制重登:两种操作成功后服务端 session.clear(),前端整页跳转 /api/login,要求输入新密码重新进入;独立 change_password.html 沿用同一跳转。
  • 临时密码(webui_initial_password)保留为恢复路径,启用后可被自定义密码取代。

三、验证

  • Python 765 tests / 前端 43 tests 全绿(新增:enable_with_password 服务层 4 例、setup 端点集成 2 例、WebuiPasswordPanel 组件 4 例)。
  • 浏览器实测(mock API + 截图):免密→设密码→确认→跳转登录→状态翻转→已启用模式修改表单,全链路通过;亮/暗主题、无滚动条导航滚动正常。
  • Dashboard 产物按 4.1.0 重建提交;prefers-reduced-motion 降级保留;无新增运行时依赖。

版本同步

metadata.yaml、__init__.py、web_src/package.json、README.md、README_EN.md、docs/README.md 六处 + CHANGELOG。

Summary by Sourcery

Modernize the monitoring dashboard and add persistent self-service WebUI password management with mandatory reauthentication.

New Features:

  • Add WebUI password setup for passwordless installations and in-panel password changes, with persistent protection and forced reauthentication after successful changes.
  • Add browser-local custom CSS editing with stable slx- styling hooks, immediate application, and theme reset support.
  • Introduce per-page accent colors, a streamlined settings master-detail layout, and scrollbar-free horizontal navigation.

Bug Fixes:

  • Fix settings toggle controls rendering as unstyled checkboxes by restoring the switch field styles in the dashboard build.

Enhancements:

  • Redesign the dashboard with a compact Linear-inspired visual system using solid surfaces, subtle borders and shadows, tighter radii, restrained motion, and light/dark theme tuning.
  • Improve settings navigation with cross-group search, field counts, unsaved-change indicators, and independently tracked dependency installation state.
  • Expose consistent styling hooks across dashboard components for targeted user customization.

Documentation:

  • Synchronize the project version to 4.1.0 across metadata, package files, READMEs, documentation, and changelog.

Tests:

  • Add coverage for custom password setup, session invalidation, persistence, custom CSS behavior, and password panel interactions.

Chores:

  • Rebuild and commit the dashboard assets for version 4.1.0 without adding runtime dependencies.

Apply a Linear-inspired visual overhaul (via garden-skills web-design-engineer
methodology) while preserving information architecture, routes, labels, and
behavior contracts:

- Tokens: solid surfaces + hairline borders replace translucent blur panels;
  radius tightened 10/16/24 -> 4/6/10; near-flat shadows; flat background
  without radial gradient washes; motion tokens (120ms hover ease-out,
  220ms quint-out layout transitions, transform/opacity only).
- Per-module accent system: each of the 14 pages gets its own accent hue
  (light + dark variants) applied with discipline - nav active underline,
  page kicker, panel icons, StatCard accent line, module card icons.
- Settings page: flat stacked panels -> master-detail hierarchy (group
  sidebar with search/field counts/dirty dots + detail pane), cross-group
  search results view, unsaved-changes badge and per-group dirty markers,
  dependency install tucked into sidebar footer.
- Nav: underline indicator with per-item accent replaces pill highlight.
- Buttons/cards: crisp border-color transitions replace translateY float
  and glow shadows; nowrap guards against CJK character wrapping.

Verified in-browser (light/dark, settings search & dirty states) with a
mock-API preview; typecheck + 39 frontend tests green; dashboard bundle
rebuilt.
@sourcery-ai

sourcery-ai Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

本 PR 保持路由、信息架构和行为契约不变,集中重做设计令牌与共享组件样式,增加 14 页亮暗主题强调色,并将设置页实现为带跨组搜索、分组选择记忆和逐项脏状态管理的 master-detail 界面;同时更新并提交对应构建产物。

Flow diagram for settings group selection and search

flowchart TD
  Settings[Settings page] --> Query{Search query present?}
  Query -->|No| Groups[Load configuration groups]
  Groups --> Selected[Restore or choose selected group]
  Selected --> Detail[Show group detail and field count]
  Query -->|Yes| Match[Filter fields across groups]
  Match --> Results[Show grouped search results]
  Detail --> Draft[Track draft changes]
  Results --> Draft
  Draft --> Dirty[Compute dirty fields and group markers]
  Dirty --> Actions[Enable reset and save actions]
Loading

File-Level Changes

Change Details Files
将全局视觉令牌与共享组件统一切换为固态、硬朗、低动效的 Linear 风格,并以页面上下文驱动强调色。
  • 重设亮暗主题的背景、表面、边框、阴影、圆角、字体与动效令牌,移除毛玻璃、渐变光斑和大幅悬浮效果。
  • 为 14 个页面定义亮暗主题强调色,并通过 data-page/data-accent 传递到导航、卡片、面板图标、进度条和统计卡等局部视觉元素。
  • 收紧 AppShell、PageNav、PageHeader、Button、Badge、Card、Panel、FormField、ProgressBar、Segmented、SwitchField 等共享布局与交互样式。
  • 扩展 Card 以透传 HTML 属性,支持 data-accent 等上下文标记。
  • 调整页面进入动画为 220ms、quint-out、opacity 加 4px 位移。
web_src/src/styles/_tokens.scss
web_src/src/styles/_mixins.scss
web_src/src/styles/main.scss
web_src/src/components/layout/AppShell.module.scss
web_src/src/components/layout/AppShell.tsx
web_src/src/components/layout/PageHeader.module.scss
web_src/src/components/layout/PageNav.module.scss
web_src/src/components/layout/PageNav.tsx
web_src/src/components/feedback/Dialog.module.scss
web_src/src/components/feedback/PasswordReminder.module.scss
web_src/src/components/ui/Badge.module.scss
web_src/src/components/ui/Button.module.scss
web_src/src/components/ui/Card.module.scss
web_src/src/components/ui/FormField.module.scss
web_src/src/components/ui/Panel.module.scss
web_src/src/components/ui/ProgressBar.module.scss
web_src/src/components/ui/Segmented.module.scss
web_src/src/components/ui/StatCard.module.scss
web_src/src/components/ui/SwitchField.module.scss
web_src/src/components/ui/index.tsx
将首页模块入口与系统入口卡片接入页面级强调色,替换原有渐变、光晕和彩色实体图标。
  • 通过 data-accent 绑定模块页面标识。
  • 使用强调色、强调色浅背景和左侧边框表达层级,统一卡片圆角与图标尺寸。
web_src/src/pages/home/HomePage.tsx
web_src/src/pages/home/HomePage.module.scss
将设置页从分组面板平铺改为支持搜索、记忆选择和脏状态的 master-detail 布局。
  • 新增左侧分组目录,显示字段计数、分组级未保存标记,并持久化当前分组到 localStorage。
  • 无搜索词时仅展示当前分组详情;有搜索词时按分组标题组织跨分组匹配结果。
  • 按字段值与已保存配置逐项比较计算未保存项,驱动头部徽标、重置/保存按钮和分组脏点。
  • 将依赖安装与镜像选择降级收纳至侧栏底部,并适配平板和移动端布局。
web_src/src/pages/settings/SettingsPage.tsx
web_src/src/pages/settings/SettingsPage.module.scss
更新前端构建产物及入口引用以交付本次重设计。
  • 替换 dashboard 的 JS/CSS 哈希产物并同步 index.html 引用。
web_res/static/dashboard/index.html
web_res/static/dashboard/assets/index-GRGz13iX.js
web_res/static/dashboard/assets/index-CLIRYXE2.css
web_res/static/dashboard/assets/index-Z__CDKuy.js
web_res/static/dashboard/assets/index-C77TMjSi.css

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 security issues, and 3 other issues

Security issues:

  • User controlled data in methods like innerHTML, outerHTML or document.write is an anti-pattern that can lead to XSS vulnerabilities (link)
  • User controlled data in a s.innerHTML is an anti-pattern that can lead to XSS vulnerabilities (link)

Fixed security issues:

  • Cross-site scripting (XSS) via untrusted HTML/JS injection in web rendering sinks (link)
Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="web_src/src/pages/settings/SettingsPage.tsx" line_range="121-126" />
<code_context>
+            <Show
+              when={!query()}
+              fallback={
+                <div class={styles['search-results']}>
+                  <For each={matchedGroups()}>{(group) => <>
+                    <h3 class={styles['result-group']}>{groupLabel(group)}</h3>
+                    <div class={styles['config-grid']}><For each={fieldsFor(group)}>{(field) => <ConfigField field={field} />}</For></div>
+                  </>}</For>
+                </div>
+              }
+            >
</code_context>
<issue_to_address>
**issue (bug_risk):** When a search query matches no configuration fields, the detail pane renders an empty `search-results` container instead of an empty state or explanatory message; only the sidebar reports that there are no matches, leaving the main content area blank.

**Triggers:** When the settings search query has no matching fields.

**Suggested fix:** Render an `EmptyState` inside the search-results fallback when `matchedGroups().length` is zero.

```suggestion
                <div class={styles['search-results']}>
                  <Show when={matchedGroups().length} fallback={<EmptyState title="没有匹配的配置项" detail="调整搜索关键词后重试。" />}>
                    <For each={matchedGroups()}>{(group) => <>
                      <h3 class={styles['result-group']}>{groupLabel(group)}</h3>
                      <div class={styles['config-grid']}><For each={fieldsFor(group)}>{(field) => <ConfigField field={field} />}</For></div>
                    </>}</For>
                  </Show>
                </div>
```
</issue_to_address>

### Comment 2
<location path="web_src/src/pages/settings/SettingsPage.tsx" line_range="95" />
<code_context>
+            <Input placeholder="搜索配置项" value={query()} onInput={(event) => setQuery(event.currentTarget.value)} />
+            <nav class={styles['group-list']} aria-label="设置分组">
+              <For each={matchedGroups()}>{(group) =>
+                <button
+                  type="button"
+                  classList={{ [styles['group-item']]: true, [styles['active']]: !query() && activeGroup() === group }}
+                  onClick={() => select(groupKey(group))}
+                >
+                  <span class={styles['group-item-label']}>{groupLabel(group)}</span>
+                  <span class={styles['group-item-meta']}>
+                    <Show when={groupDirty(group)}><span class={styles['dirty-dot']} title="有未保存修改" /></Show>
+                    <span>{fieldsFor(group).length}</span>
+                  </span>
+                </button>
+              }</For>
+              <Show when={!matchedGroups().length}>
</code_context>
<issue_to_address>
**nitpick (bug_risk):** The field count shown beside every group is filtered by the current search query, so it stops representing the group's total field count while search is active and instead shows only the number of matching fields.

**Triggers:** When a settings search query is active.

**Suggested fix:** Render `(group.fields || []).length` for the group count, or label the value explicitly as the number of matching fields when search is active.

```suggestion
                    <span>{(group.fields || []).length}</span>
```
</issue_to_address>

### Comment 3
<location path="web_src/src/pages/settings/SettingsPage.tsx" line_range="72-78" />
<code_context>
     <div class="page">
       <PageHeader title="设置" description="编辑插件配置,并在明确确认后安装可选依赖。" icon="tune" actions={
         <div class="inline-actions">
+          <Show when={dirtyKeys().size}>
+            <Badge tone="warning">未保存 {dirtyKeys().size} 项</Badge>
+          </Show>
           <Button icon="refresh" onClick={dashboard.loadConfig}>重新加载</Button>
-          <Button disabled={!dirty()} onClick={reset}>重置</Button>
-          <Button tone="primary" icon="save" loading={dashboard.busy()} disabled={!dashboard.schema() || !dirty()} onClick={dashboard.saveConfig}>手动保存设置</Button>
+          <Button disabled={!dirtyKeys().size} onClick={reset}>重置</Button>
+          <Button tone="primary" icon="save" loading={dashboard.busy()} disabled={!dashboard.schema() || !dirtyKeys().size} onClick={dashboard.saveConfig}>手动保存设置</Button>
         </div>
       } />
</code_context>
<issue_to_address>
**nitpick (broader_impact):** The save button is driven by the shared `dashboard.busy()` flag, which is also set during dependency installation; starting a dependency install therefore makes the unrelated settings-save action display a loading state and remain disabled until installation finishes.

**Triggers:** When a dependency installation is running.

**Suggested fix:** Use separate busy state for saving configuration and dependency installation, or disable/show loading only on the action that initiated the operation.
</issue_to_address>

### Comment 4
<location path="web_res/static/dashboard/assets/index-GRGz13iX.js" line_range="1" />
<code_context>
import{u as sr,i as or,a as cr,b as ur,c as dr,d as gr,e as hr,f as fr,g as _r,h as mr,j as dn}from"./echarts-BFYZiO-4.js";(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))r(i);new MutationObserver(i=>{for(const a of i)if(a.type==="childList")for(const l of a.addedNodes)l.tagName==="LINK"&&l.rel==="modulepreload"&&r(l)}).observe(document,{childList:!0,subtree:!0});function n(i){const a={};return i.integrity&&(a.integrity=i.integrity),i.referrerPolicy&&(a.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?a.credentials="include":i.crossOrigin==="anonymous"?a.credentials="omit":a.credentials="same-origin",a}function r(i){if(i.ep)return;i.ep=!0;const a=n(i);fetch(i.href,a)}})();const pr=!1,vr=(e,t)=>e===t,Ae=Symbol("solid-proxy"),zn=typeof Proxy=="function",Ht=Symbol("solid-track"),Et={equals:vr};let Un=Kn;const ze=1,It=2,Fn={owned:null,cleanups:null,context:null,owner:null};var ne=null;let qt=null,br=null,ie=null,fe=null,Me=null,Lt=0;function st(e,t){const n=ie,r=ne,i=e.length===0,a=t===void 0?r:t,l=i?Fn:{owned:null,cleanups:null,context:a?a.context:null,owner:a},s=i?e:()=>e(()=>Pe(()=>dt(l)));ne=l,ie=null;try{return Ke(s,!0)}finally{ie=n,ne=r}}function A(e,t){t=t?Object.assign({},Et,t):Et;const n={value:e,observers:null,observerSlots:null,comparator:t.equals||void 0},r=i=>(typeof i=="function"&&(i=i(n.value)),Vn(n,i));return[Gn.bind(n),r]}function y(e,t,n){const r=rn(e,t,!1,ze);_t(r)}function ct(e,t,n){Un=Ar;const r=rn(e,t,!1,ze);(!n||!n.render)&&(r.user=!0),Me?Me.push(r):_t(r)}function ae(e,t,n){n=n?Object.assign({},Et,n):Et;const r=rn(e,t,!0,0);return r.observers=null,r.observerSlots=null,r.comparator=n.equals||void 0,_t(r),Gn.bind(r)}function $r(e){return Ke(e,!1)}function Pe(e){if(ie===null)return e();const t=ie;ie=null;try{return e()}finally{ie=t}}function je(e){ct(()=>Pe(e))}function ut(e){return ne===null||(ne.cleanups===null?ne.cleanups=[e]:ne.cleanups.push(e)),e}function Jt(){return ie}function yr(){return ne}function wr(e,t){const n=ne,r=ie;ne=e,ie=null;try{return Ke(t,!0)}catch(i){an(i)}finally{ne=n,ie=r}}function Sr(e,t){const n=Symbol("context");return{id:n,Provider:jr(n),defaultValue:e}}function Cr(e){let t;return ne&&ne.context&&(t=ne.context[e.id])!==void 0?t:e.defaultValue}function Bn(e){const t=ae(e),n=ae(()=>Wt(t()));return n.toArray=()=>{const r=n();return Array.isArray(r)?r:r!=null?[r]:[]},n}function Gn(){if(this.sources&&this.state)if(this.state===ze)_t(this);else{const e=fe;fe=null,Ke(()=>Tt(this),!1),fe=e}if(ie){const e=this.observers?this.observers.length:0;ie.sources?(ie.sources.push(this),ie.sourceSlots.push(e)):(ie.sources=[this],ie.sourceSlots=[e]),this.observers?(this.observers.push(ie),this.observerSlots.push(ie.sources.length-1)):(this.observers=[ie],this.observerSlots=[ie.sources.length-1])}return this.value}function Vn(e,t,n){let r=e.value;return(!e.comparator||!e.comparator(r,t))&&(e.value=t,e.observers&&e.observers.length&&Ke(()=>{for(let i=0;i<e.observers.length;i+=1){const a=e.observers[i],l=qt&&qt.running;l&&qt.disposed.has(a),(l?!a.tState:!a.state)&&(a.pure?fe.push(a):Me.push(a),a.observers&&Qn(a)),l||(a.state=ze)}if(fe.length>1e6)throw fe=[],new Error},!1)),t}function _t(e){if(!e.fn)return;dt(e);const t=Lt;kr(e,e.value,t)}function kr(e,t,n){let r;const i=ne,a=ie;ie=ne=e;try{r=e.fn(t)}catch(l){return e.pure&&(e.state=ze,e.owned&&e.owned.forEach(dt),e.owned=null),e.updatedAt=n+1,an(l)}finally{ie=a,ne=i}(!e.updatedAt||e.updatedAt<=n)&&(e.updatedAt!=null&&"observers"in e?Vn(e,r):e.value=r,e.updatedAt=n)}function rn(e,t,n,r=ze,i){const a={fn:e,state:r,updatedAt:null,owned:null,sources:null,sourceSlots:null,cleanups:null,value:t,owner:ne,context:ne?ne.context:null,pure:n};return ne===null||ne!==Fn&&(ne.owned?ne.owned.push(a):ne.owned=[a]),a}function Ot(e){if(e.state===0)return;if(e.state===It)return Tt(e);if(e.suspense&&Pe(e.suspense.inFallback))return e.suspense.effects.push(e);const t=[e];for(;(e=e.owner)&&(!e.updatedAt||e.updatedAt<Lt);)e.state&&t.push(e);for(let n=t.length-1;n>=0;n--)if(e=t[n],e.state===ze)_t(e);else if(e.state===It){const r=fe;fe=null,Ke(()=>Tt(e,t[0]),!1),fe=r}}function Ke(e,t){if(fe)return e();let n=!1;t||(fe=[]),Me?n=!0:Me=[],Lt++;try{const r=e();return xr(n),r}catch(r){n||(Me=null),fe=null,an(r)}}function xr(e){if(fe&&(Kn(fe),fe=null),e)return;const t=Me;Me=null,t.length&&Ke(()=>Un(t),!1)}function Kn(e){for(let t=0;t<e.length;t++)Ot(e[t])}function Ar(e){let t,n=0;for(t=0;t<e.length;t++){const r=e[t];r.user?e[n++]=r:Ot(r)}for(t=0;t<n;t++)Ot(e[t])}function Tt(e,t){e.state=0;for(let n=0;n<e.sources.length;n+=1){const r=e.sources[n];if(r.sources){const i=r.state;i===ze?r!==t&&(!r.updatedAt||r.updatedAt<Lt)&&Ot(r):i===It&&Tt(r,t)}}}function Qn(e){for(let t=0;t<e.observers.length;t+=1){const n=e.observers[t];n.state||(n.state=It,n.pure?fe.push(n):Me.push(n),n.observers&&Qn(n))}}function dt(e){let t;if(e.sources)for(;e.sources.length;){const n=e.sources.pop(),r=e.sourceSlots.pop(),i=n.observers;if(i&&i.length){const a=i.pop(),l=n.observerSlots.pop();r<i.length&&(a.sourceSlots[l]=r,i[r]=a,n.observerSlots[r]=l)}}if(e.tOwned){for(t=e.tOwned.length-1;t>=0;t--)dt(e.tOwned[t]);delete e.tOwned}if(e.owned){for(t=e.owned.length-1;t>=0;t--)dt(e.owned[t]);e.owned=null}if(e.cleanups){for(t=e.cleanups.length-1;t>=0;t--)e.cleanups[t]();e.cleanups=null}e.state=0}function Pr(e){return e instanceof Error?e:new Error(typeof e=="string"?e:"Unknown error",{cause:e})}function an(e,t=ne){throw Pr(e)}function Wt(e){if(typeof e=="function"&&!e.length)return Wt(e());if(Array.isArray(e)){const t=[];for(let n=0;n<e.length;n++){const r=Wt(e[n]);Array.isArray(r)?t.push.apply(t,r):t.push(r)}return t}return e}function jr(e,t){return function(r){let i;return y(()=>i=Pe(()=>(ne.context={...ne.context,[e]:r.value},Bn(()=>r.children))),void 0),i}}const Er=Symbol("fallback");function gn(e){for(let t=0;t<e.length;t++)e[t]()}function Ir(e,t,n={}){let r=[],i=[],a=[],l=0,s=t.length>1?[]:null;return ut(()=>gn(a)),()=>{let c=e()||[],g=c.length,d,h;return c[Ht],Pe(()=>{let m,$,C,w,j,B,G,S,T;if(g===0)l!==0&&(gn(a),a=[],r=[],i=[],l=0,s&&(s=[])),n.fallback&&(r=[Er],i[0]=st(F=>(a[0]=F,n.fallback())),l=1);else if(l===0){for(i=new Array(g),h=0;h<g;h++)r[h]=c[h],i[h]=st(f);l=g}else{for(C=new Array(g),w=new Array(g),s&&(j=new Array(g)),B=0,G=Math.min(l,g);B<G&&r[B]===c[B];B++);for(G=l-1,S=g-1;G>=B&&S>=B&&r[G]===c[S];G--,S--)C[S]=i[G],w[S]=a[G],s&&(j[S]=s[G]);for(m=new Map,$=new Array(S+1),h=S;h>=B;h--)T=c[h],d=m.get(T),$[h]=d===void 0?-1:d,m.set(T,h);for(d=B;d<=G;d++)T=r[d],h=m.get(T),h!==void 0&&h!==-1?(C[h]=i[d],w[h]=a[d],s&&(j[h]=s[d]),h=$[h],m.set(T,h)):a[d]();for(h=B;h<g;h++)h in C?(i[h]=C[h],a[h]=w[h],s&&(s[h]=j[h],s[h](h))):i[h]=st(f);i=i.slice(0,l=g),r=c.slice(0)}return i});function f(m){if(a[h]=m,s){const[$,C]=A(h);return s[h]=C,t(c[h],$)}return t(c[h])}}}function u(e,t){return Pe(()=>e(t||{}))}function wt(){return!0}const Xt={get(e,t,n){return t===Ae?n:e.get(t)},has(e,t){return t===Ae?!0:e.has(t)},set:wt,deleteProperty:wt,getOwnPropertyDescriptor(e,t){return{configurable:!0,enumerable:!0,get(){return e.get(t)},set:wt,deleteProperty:wt}},ownKeys(e){return e.keys()}};function zt(e){return(e=typeof e=="function"?e():e)?e:{}}function Or(){for(let e=0,t=this.length;e<t;++e){const n=this[e]();if(n!==void 0)return n}}function tt(...e){let t=!1;for(let l=0;l<e.length;l++){const s=e[l];t=t||!!s&&Ae in s,e[l]=typeof s=="function"?(t=!0,ae(s)):s}if(zn&&t)return new Proxy({get(l){for(let s=e.length-1;s>=0;s--){const c=zt(e[s])[l];if(c!==void 0)return c}},has(l){for(let s=e.length-1;s>=0;s--)if(l in zt(e[s]))return!0;return!1},keys(){const l=[];for(let s=0;s<e.length;s++)l.push(...Object.keys(zt(e[s])));return[...new Set(l)]}},Xt);const n={},r=Object.create(null);for(let l=e.length-1;l>=0;l--){const s=e[l];if(!s)continue;const c=Object.getOwnPropertyNames(s);for(let g=c.length-1;g>=0;g--){const d=c[g];if(d==="__proto__"||d==="constructor")continue;const h=Object.getOwnPropertyDescriptor(s,d);if(!r[d])r[d]=h.get?{enumerable:!0,configurable:!0,get:Or.bind(n[d]=[h.get.bind(s)])}:h.value!==void 0?h:void 0;else{const f=n[d];f&&(h.get?f.push(h.get.bind(s)):h.value!==void 0&&f.push(()=>h.value))}}}const i={},a=Object.keys(r);for(let l=a.length-1;l>=0;l--){const s=a[l],c=r[s];c&&c.get?Object.defineProperty(i,s,c):i[s]=c?c.value:void 0}return i}function mt(e,...t){if(zn&&Ae in e){const i=new Set(t.length>1?t.flat():t[0]),a=t.map(l=>new Proxy({get(s){return l.includes(s)?e[s]:void 0},has(s){return l.includes(s)&&s in e},keys(){return l.filter(s=>s in e)}},Xt));return a.push(new Proxy({get(l){return i.has(l)?void 0:e[l]},has(l){return i.has(l)?!1:l in e},keys(){return Object.keys(e).filter(l=>!i.has(l))}},Xt)),a}const n={},r=t.map(()=>({}));for(const i of Object.getOwnPropertyNames(e)){const a=Object.getOwnPropertyDescriptor(e,i),l=!a.get&&!a.set&&a.enumerable&&a.writable&&a.configurable;let s=!1,c=0;for(const g of t)g.includes(i)&&(s=!0,l?r[c][i]=a.value:Object.defineProperty(r[c],i,a)),++c;s||(l?n[i]=a.value:Object.defineProperty(n,i,a))}return[...r,n]}const Hn=e=>`Stale read from <${e}>.`;function ee(e){const t="fallback"in e&&{fallback:()=>e.fallback};return ae(Ir(()=>e.each,e.children,t||void 0))}function M(e){const t=e.keyed,n=ae(()=>e.when,void 0,void 0),r=t?n:ae(n,void 0,{equals:(i,a)=>!i==!a});return ae(()=>{const i=r();if(i){const a=e.children;return typeof a=="function"&&a.length>0?Pe(()=>a(t?i:()=>{if(!Pe(r))throw Hn("Show");return n()})):a}return e.fallback},void 0,void 0)}function Tr(e){const t=Bn(()=>e.children),n=ae(()=>{const r=t(),i=Array.isArray(r)?r:[r];let a=()=>{};for(let l=0;l<i.length;l++){const s=l,c=i[l],g=a,d=ae(()=>g()?void 0:c.when,void 0,void 0),h=c.keyed?d:ae(d,void 0,{equals:(f,m)=>!f==!m});a=()=>g()||(h()?[s,d,c]:void 0)}return a});return ae(()=>{const r=n()();if(!r)return e.fallback;const[i,a,l]=r,s=l.children;return typeof s=="function"&&s.length>0?Pe(()=>s(l.keyed?a():()=>{if(Pe(n)()?.[0]!==i)throw Hn("Match");return a()})):s},void 0,void 0)}function pe(e){return e}const Nr=["allowfullscreen","async","alpha","autofocus","autoplay","checked","controls","default","disabled","formnovalidate","hidden","indeterminate","inert","ismap","loop","multiple","muted","nomodule","novalidate","open","playsinline","readonly","required","reversed","seamless","selected","adauctionheaders","browsingtopics","credentialless","defaultchecked","defaultmuted","defaultselected","defer","disablepictureinpicture","disableremoteplayback","preservespitch","shadowrootclonable","shadowrootcustomelementregistry","shadowrootdelegatesfocus","shadowrootserializable","sharedstoragewritable"],Lr=new Set(["className","value","readOnly","noValidate","formNoValidate","isMap","noModule","playsInline","adAuctionHeaders","allowFullscreen","browsingTopics","defaultChecked","defaultMuted","defaultSelected","disablePictureInPicture","disableRemotePlayback","preservesPitch","shadowRootClonable","shadowRootCustomElementRegistry","shadowRootDelegatesFocus","shadowRootSerializable","sharedStorageWritable",...Nr]),Dr=new Set(["innerHTML","textContent","innerText","children"]),Mr=Object.assign(Object.create(null),{className:"class",htmlFor:"for"}),Rr=Object.assign(Object.create(null),{class:"className",novalidate:{$:"noValidate",FORM:1},formnovalidate:{$:"formNoValidate",BUTTON:1,INPUT:1},ismap:{$:"isMap",IMG:1},nomodule:{$:"noModule",SCRIPT:1},playsinline:{$:"playsInline",VIDEO:1},readonly:{$:"readOnly",INPUT:1,TEXTAREA:1},adauctionheaders:{$:"adAuctionHeaders",IFRAME:1},allowfullscreen:{$:"allowFullscreen",IFRAME:1},browsingtopics:{$:"browsingTopics",IMG:1},defaultchecked:{$:"defaultChecked",INPUT:1},defaultmuted:{$:"defaultMuted",AUDIO:1,VIDEO:1},defaultselected:{$:"defaultSelected",OPTION:1},disablepictureinpicture:{$:"disablePictureInPicture",VIDEO:1},disableremoteplayback:{$:"disableRemotePlayback",AUDIO:1,VIDEO:1},preservespitch:{$:"preservesPitch",AUDIO:1,VIDEO:1},shadowrootclonable:{$:"shadowRootClonable",TEMPLATE:1},shadowrootdelegatesfocus:{$:"shadowRootDelegatesFocus",TEMPLATE:1},shadowrootserializable:{$:"shadowRootSerializable",TEMPLATE:1},sharedstoragewritable:{$:"sharedStorageWritable",IFRAME:1,IMG:1}});function qr(e,t){const n=Rr[e];return typeof n=="object"?n[t]?n.$:void 0:n}const zr=new Set(["beforeinput","click","dblclick","contextmenu","focusin","focusout","input","keydown","keyup","mousedown","mousemove","mouseout","mouseover","mouseup","pointerdown","pointermove","pointerout","pointerover","pointerup","touchend","touchmove","touchstart"]),de=e=>ae(()=>e());function Ur(e,t,n){let r=n.length,i=t.length,a=r,l=0,s=0,c=t[i-1].nextSibling,g=null;for(;l<i||s<a;){if(t[l]===n[s]){l++,s++;continue}for(;t[i-1]===n[a-1];)i--,a--;if(i===l){const d=a<r?s?n[s-1].nextSibling:n[a-s]:c;for(;s<a;)e.insertBefore(n[s++],d)}else if(a===s)for(;l<i;)(!g||!g.has(t[l]))&&t[l].remove(),l++;else if(t[l]===n[a-1]&&n[s]===t[i-1]){const d=t[--i].nextSibling;e.insertBefore(n[s++],t[l++].nextSibling),e.insertBefore(n[--a],d),t[i]=n[a]}else{if(!g){g=new Map;let h=s;for(;h<a;)g.set(n[h],h++)}const d=g.get(t[l]);if(d!=null)if(s<d&&d<a){let h=l,f=1,m;for(;++h<i&&h<a&&!((m=g.get(t[h]))==null||m!==d+f);)f++;if(f>d-s){const $=t[l];for(;s<d;)e.insertBefore(n[s++],$)}else e.replaceChild(n[s++],t[l++])}else l++;else t[l++].remove()}}}const hn="_$DX_DELEGATE";function Fr(e,t,n,r={}){let i;return st(a=>{i=a,t===document?e():o(t,e(),t.firstChild?null:void 0,n)},r.owner),()=>{i(),t.textContent=""}}function _(e,t,n,r){let i;const a=()=>{const s=document.createElement("template");return s.innerHTML=e,s.content.firstChild},l=()=>(i||(i=a())).cloneNode(!0);return l.cloneNode=l,l}function Ee(e,t=window.document){const n=t[hn]||(t[hn]=new Set);for(let r=0,i=e.length;r<i;r++){const a=e[r];n.has(a)||(n.add(a),t.addEventListener(a,Jr))}}function xe(e,t,n){n==null?e.removeAttribute(t):e.setAttribute(t,n)}function Br(e,t,n){n?e.setAttribute(t,""):e.removeAttribute(t)}function v(e,t){t==null?e.removeAttribute("class"):e.className=t}function Gr(e,t,n,r){if(r)Array.isArray(n)?(e[`$$${t}`]=n[0],e[`$$${t}Data`]=n[1]):e[`$$${t}`]=n;else if(Array.isArray(n)){const i=n[0];e.addEventListener(t,n[0]=a=>i.call(e,n[1],a))}else e.addEventListener(t,n,typeof n!="function"&&n)}function Qe(e,t,n={}){const r=Object.keys(t||{}),i=Object.keys(n);let a,l;for(a=0,l=i.length;a<l;a++){const s=i[a];!s||s==="undefined"||t[s]||(fn(e,s,!1),delete n[s])}for(a=0,l=r.length;a<l;a++){const s=r[a],c=!!t[s];!s||s==="undefined"||n[s]===c||!c||(fn(e,s,!0),n[s]=c)}return n}function Vr(e,t,n){if(!t)return n?xe(e,"style"):t;const r=e.style;if(typeof t=="string")return r.cssText=t;typeof n=="string"&&(r.cssText=n=void 0),n||(n={}),t||(t={});let i,a;for(a in n)t[a]==null&&r.removeProperty(a),delete n[a];for(a in t)i=t[a],i!==n[a]&&(r.setProperty(a,i),n[a]=i);return n}function Kr(e,t,n){n!=null?e.style.setProperty(t,n):e.style.removeProperty(t)}function pt(e,t={},n,r){const i={};return r||y(()=>i.children=gt(e,t.children,i.children)),y(()=>typeof t.ref=="function"&&ln(t.ref,e)),y(()=>Qr(e,t,n,!0,i,!0)),i}function ln(e,t,n){return Pe(()=>e(t,n))}function o(e,t,n,r){if(n!==void 0&&!r&&(r=[]),typeof t!="function")return gt(e,t,r,n);y(i=>gt(e,t(),i,n),r)}function Qr(e,t,n,r,i={},a=!1){t||(t={});for(const l in i)if(!(l in t)){if(l==="children")continue;i[l]=_n(e,l,null,i[l],n,a,t)}for(const l in t){if(l==="children")continue;const s=t[l];i[l]=_n(e,l,s,i[l],n,a,t)}}function Hr(e){return e.toLowerCase().replace(/-([a-z])/g,(t,n)=>n.toUpperCase())}function fn(e,t,n){const r=t.trim().split(/\s+/);for(let i=0,a=r.length;i<a;i++)e.classList.toggle(r[i],n)}function _n(e,t,n,r,i,a,l){let s,c,g,d,h;if(t==="style")return Vr(e,n,r);if(t==="classList")return Qe(e,n,r);if(n===r)return r;if(t==="ref")a||n(e);else if(t.slice(0,3)==="on:"){const f=t.slice(3);r&&e.removeEventListener(f,r,typeof r!="function"&&r),n&&e.addEventListener(f,n,typeof n!="function"&&n)}else if(t.slice(0,10)==="oncapture:"){const f=t.slice(10);r&&e.removeEventListener(f,r,!0),n&&e.addEventListener(f,n,!0)}else if(t.slice(0,2)==="on"){const f=t.slice(2).toLowerCase(),m=zr.has(f);if(!m&&r){const $=Array.isArray(r)?r[0]:r;e.removeEventListener(f,$)}(m||n)&&(Gr(e,f,n,m),m&&Ee([f]))}else t.slice(0,5)==="attr:"?xe(e,t.slice(5),n):t.slice(0,5)==="bool:"?Br(e,t.slice(5),n):(h=t.slice(0,5)==="prop:")||(g=Dr.has(t))||(d=qr(t,e.tagName))||(c=Lr.has(t))||(s=e.nodeName.includes("-")||"is"in l)?(h&&(t=t.slice(5),c=!0),t==="class"||t==="className"?v(e,n):s&&!c&&!g?e[Hr(t)]=n:e[d||t]=n):xe(e,Mr[t]||t,n);return n}function Jr(e){let t=e.target;const n=`$$${e.type}`,r=e.target,i=e.currentTarget,a=c=>Object.defineProperty(e,"target",{configurable:!0,value:c}),l=()=>{const c=t[n];if(c&&!t.disabled){const g=t[`${n}Data`];if(g!==void 0?c.call(t,g,e):c.call(t,e),e.cancelBubble)return}return t.host&&typeof t.host!="string"&&!t.host._$host&&t.contains(e.target)&&a(t.host),!0},s=()=>{for(;l()&&(t=t._$host||t.parentNode||t.host););};if(Object.defineProperty(e,"currentTarget",{configurable:!0,get(){return t||document}}),e.composedPath){const c=e.composedPath();a(c[0]);for(let g=0;g<c.length-2&&(t=c[g],!!l());g++){if(t._$host){t=t._$host,s();break}if(t.parentNode===i)break}}else s();a(r)}function gt(e,t,n,r,i){for(;typeof n=="function";)n=n();if(t===n)return n;const a=typeof t,l=r!==void 0;if(e=l&&n[0]&&n[0].parentNode||e,a==="string"||a==="number"){if(a==="number"&&(t=t.toString(),t===n))return n;if(l){let s=n[0];s&&s.nodeType===3?s.data!==t&&(s.data=t):s=document.createTextNode(t),n=Je(e,n,r,s)}else n!==""&&typeof n=="string"?n=e.firstChild.data=t:n=e.textContent=t}else if(t==null||a==="boolean")n=Je(e,n,r);else{if(a==="function")return y(()=>{let s=t();for(;typeof s=="function";)s=s();n=gt(e,s,n,r)}),()=>n;if(Array.isArray(t)){const s=[],c=n&&Array.isArray(n);if(Yt(s,t,n,i))return y(()=>n=gt(e,s,n,r,!0)),()=>n;if(s.length===0){if(n=Je(e,n,r),l)return n}else c?n.length===0?mn(e,s,r):Ur(e,n,s):(n&&Je(e),mn(e,s));n=s}else if(t.nodeType){if(Array.isArray(n)){if(l)return n=Je(e,n,r,t);Je(e,n,null,t)}else n==null||n===""||!e.firstChild?e.appendChild(t):e.replaceChild(t,e.firstChild);n=t}}return n}function Yt(e,t,n,r){let i=!1;for(let a=0,l=t.length;a<l;a++){let s=t[a],c=n&&n[e.length],g;if(!(s==null||s===!0||s===!1))if((g=typeof s)=="object"&&s.nodeType)e.push(s);else if(Array.isArray(s))i=Yt(e,s,c)||i;else if(g==="function")if(r){for(;typeof s=="function";)s=s();i=Yt(e,Array.isArray(s)?s:[s],Array.isArray(c)?c:[c])||i}else e.push(s),i=!0;else{const d=String(s);c&&c.nodeType===3&&c.data===d?e.push(c):e.push(document.createTextNode(d))}}return i}function mn(e,t,n=null){for(let r=0,i=t.length;r<i;r++)e.insertBefore(t[r],n)}function Je(e,t,n,r){if(n===void 0)return e.textContent="";const i=r||document.createTextNode("");if(t.length){let a=!1;for(let l=t.length-1;l>=0;l--){const s=t[l];if(i!==s){const c=s.parentNode===e;!a&&!l?c?e.replaceChild(i,s):e.insertBefore(i,n):c&&s.remove()}else a=!0}}else e.insertBefore(i,n);return[i]}const Wr="http://www.w3.org/2000/svg";function Xr(e,t=!1,n=void 0){return t?document.createElementNS(Wr,e):document.createElement(e,{is:n})}function Jn(e){const{useShadow:t}=e,n=document.createTextNode(""),r=()=>e.mount||document.body,i=yr();let a;return ct(()=>{a||(a=wr(i,()=>ae(()=>e.children)));const l=r();if(l instanceof HTMLHeadElement){const[s,c]=A(!1),g=()=>c(!0);st(d=>o(l,()=>s()?d():a(),null)),ut(g)}else{const s=Xr(e.isSVG?"g":"div",e.isSVG),c=t&&s.attachShadow?s.attachShadow({mode:"open"}):s;Object.defineProperty(s,"_$host",{get(){return n.parentNode},configurable:!0}),o(c,a),l.appendChild(s),e.ref&&e.ref(s),ut(()=>l.removeChild(s))}},void 0,{render:!0}),n}const Zt=Symbol("store-raw"),Ze=Symbol("store-node"),De=Symbol("store-has"),Wn=Symbol("store-self");function Xn(e){let t=e[Ae];if(!t&&(Object.defineProperty(e,Ae,{value:t=new Proxy(e,ei)}),!Array.isArray(e))){const n=Object.keys(e),r=Object.getOwnPropertyDescriptors(e);for(let i=0,a=n.length;i<a;i++){const l=n[i];r[l].get&&Object.defineProperty(e,l,{enumerable:r[l].enumerable,get:r[l].get.bind(t)})}}return t}function Re(e){let t;return e!=null&&typeof e=="object"&&(e[Ae]||!(t=Object.getPrototypeOf(e))||t===Object.prototype||Array.isArray(e))}function et(e,t=new Set){let n,r,i,a;if(n=e!=null&&e[Zt])return n;if(!Re(e)||t.has(e))return e;if(Array.isArray(e)){Object.isFrozen(e)?e=e.slice(0):t.add(e);for(let l=0,s=e.length;l<s;l++)i=e[l],(r=et(i,t))!==i&&(e[l]=r)}else{Object.isFrozen(e)?e=Object.assign({},e):t.add(e);const l=Object.keys(e),s=Object.getOwnPropertyDescriptors(e);for(let c=0,g=l.length;c<g;c++)a=l[c],!s[a].get&&(i=e[a],(r=et(i,t))!==i&&(e[a]=r))}return e}function Nt(e,t){let n=e[t];return n||Object.defineProperty(e,t,{value:n=Object.create(null)}),n}function ht(e,t,n){if(e[t])return e[t];const[r,i]=A(n,{equals:!1,internal:!0});return r.$=i,e[t]=r}function Yr(e,t){const n=Reflect.getOwnPropertyDescriptor(e,t);return!n||n.get||!n.configurable||t===Ae||t===Ze||(delete n.value,delete n.writable,n.get=()=>e[Ae][t]),n}function Yn(e){Jt()&&ht(Nt(e,Ze),Wn)()}function Zr(e){return Yn(e),Reflect.ownKeys(e)}const ei={get(e,t,n){if(t===Zt)return e;if(t===Ae)return n;if(t===Ht)return Yn(e),n;const r=Nt(e,Ze),i=r[t];let a=i?i():e[t];if(t===Ze||t===De||t==="__proto__")return a;if(!i){const l=Object.getOwnPropertyDescriptor(e,t);Jt()&&(typeof a!="function"||e.hasOwnProperty(t))&&!(l&&l.get)&&(a=ht(r,t,a)())}return Re(a)?Xn(a):a},has(e,t){return t===Zt||t===Ae||t===Ht||t===Ze||t===De||t==="__proto__"?!0:(Jt()&&ht(Nt(e,De),t)(),t in e)},set(){return!0},deleteProperty(){return!0},ownKeys:Zr,getOwnPropertyDescriptor:Yr};function ke(e,t,n,r=!1){if(!r&&e[t]===n)return;const i=e[t],a=e.length;n===void 0?(delete e[t],e[De]&&e[De][t]&&i!==void 0&&e[De][t].$()):(e[t]=n,e[De]&&e[De][t]&&i===void 0&&e[De][t].$());let l=Nt(e,Ze),s;if((s=ht(l,t,i))&&s.$(()=>n),Array.isArray(e)&&e.length!==a){for(let c=e.length;c<a;c++)(s=l[c])&&s.$();(s=ht(l,"length",a))&&s.$(e.length)}(s=l[Wn])&&s.$()}function Zn(e,t){const n=Object.keys(t);for(let r=0;r<n.length;r+=1){const i=n[r];ke(e,i,t[i])}}function ti(e,t){if(typeof t=="function"&&(t=t(e)),t=et(t),Array.isArray(t)){if(e===t)return;let n=0,r=t.length;for(;n<r;n++){const i=t[n];e[n]!==i&&ke(e,n,i)}ke(e,"length",r)}else Zn(e,t)}function lt(e,t,n=[]){let r,i=e;if(t.length>1){r=t.shift();const l=typeof r,s=Array.isArray(e);if(Array.isArray(r)){for(let c=0;c<r.length;c++)lt(e,[r[c]].concat(t),n);return}else if(s&&l==="function"){for(let c=0;c<e.length;c++)r(e[c],c)&&lt(e,[c].concat(t),n);return}else if(s&&l==="object"){const{from:c=0,to:g=e.length-1,by:d=1}=r;for(let h=c;h<=g;h+=d)lt(e,[h].concat(t),n);return}else if(t.length>1){lt(e[r],t,[r].concat(n));return}i=e[r],n=[r].concat(n)}let a=t[0];typeof a=="function"&&(a=a(i,n),a===i)||r===void 0&&a==null||(a=et(a),r===void 0||Re(i)&&Re(a)&&!Array.isArray(a)?Zn(i,a):ke(e,r,a))}function pn(...[e,t]){const n=et(e||{}),r=Array.isArray(n),i=Xn(n);function a(...l){$r(()=>{r&&l.length===1?ti(n,l[0]):lt(n,l)})}return[i,a]}const en=Symbol("store-root");function Xe(e,t,n,r,i){const a=t[n];if(e===a)return;const l=Array.isArray(e);if(n!==en&&(!Re(e)||!Re(a)||l!==Array.isArray(a)||i&&e[i]!==a[i])){ke(t,n,e);return}if(l){if(e.length&&a.length&&(!r||i&&e[0]&&e[0][i]!=null)){let g,d,h,f,m,$,C,w;for(h=0,f=Math.min(a.length,e.length);h<f&&(a[h]===e[h]||i&&a[h]&&e[h]&&a[h][i]&&a[h][i]===e[h][i]);h++)Xe(e[h],a,h,r,i);const j=new Array(e.length),B=new Map;for(f=a.length-1,m=e.length-1;f>=h&&m>=h&&(a[f]===e[m]||i&&a[f]&&e[m]&&a[f][i]&&a[f][i]===e[m][i]);f--,m--)j[m]=a[f];if(h>m||h>f){for(d=h;d<=m;d++)ke(a,d,e[d]);for(;d<e.length;d++)ke(a,d,j[d]),Xe(e[d],a,d,r,i);a.length>e.length&&ke(a,"length",e.length);return}for(C=new Array(m+1),d=m;d>=h;d--)$=e[d],w=i&&$?$[i]:$,g=B.get(w),C[d]=g===void 0?-1:g,B.set(w,d);for(g=h;g<=f;g++)$=a[g],w=i&&$?$[i]:$,d=B.get(w),d!==void 0&&d!==-1&&(j[d]=a[g],d=C[d],B.set(w,d));for(d=h;d<e.length;d++)d in j?(ke(a,d,j[d]),Xe(e[d],a,d,r,i)):ke(a,d,e[d])}else for(let g=0,d=e.length;g<d;g++)Xe(e[g],a,g,r,i);a.length>e.length&&ke(a,"length",e.length);return}const s=Object.keys(e);for(let g=0,d=s.length;g<d;g++)Xe(e[s[g]],a,s[g],r,i);const c=Object.keys(a);for(let g=0,d=c.length;g<d;g++)e[c[g]]===void 0&&ke(a,c[g],void 0)}function Ut(e,t={}){const{merge:n,key:r="id"}=t,i=et(e);return a=>{if(!Re(a)||!Re(i))return i;const l=Xe(i,{[en]:a},en,n,r);return l===void 0?a:l}}class ni extends Error{constructor(t,n,r){super(t),this.status=n,this.payload=r,this.name="ApiError"}}const ri=(e,t)=>{if(e&&typeof e=="object"){const n=e;for(const r of["error","message","detail"])if(typeof n[r]=="string"&&n[r])return n[r]}return`请求失败(HTTP ${t})`};async function St(e,t={},n){const r=await fetch(e,{credentials:"same-origin",...t,signal:n??t.signal,headers:{Accept:"application/json",...t.body?{"Content-Type":"application/json"}:{},...t.headers}}),i=await r.text();let a=null;if(i)try{a=JSON.parse(i)}catch{a=i}if(!r.ok)throw new ni(ri(a,r.status),r.status,a);return a}const N={get:(e,t)=>St(e,{},t),post:(e,t,n)=>St(e,{method:"POST",body:JSON.stringify(t)},n),put:(e,t,n)=>St(e,{method:"PUT",body:JSON.stringify(t)},n),delete:(e,t)=>St(e,{method:"DELETE"},t)},ii=["home","overview","insights","monitoring","reviews","jargon-learning","expression-learning","persona-learning","content","reply-strategy","shadow-mode","graphs","integrations","settings"];function vn(e=window.location.hash){const t=e.replace(/^#\/?/,"").split(/[?#]/)[0];return ii.includes(t)?t:"home"}function ai(e){const t=e.trim().replace(/^\[(.*)\]$/,"$1").toLowerCase();return t?t==="localhost"||t==="0.0.0.0"||t==="::"||t==="::1"||t==="0:0:0:0:0:0:0:0"||t==="0:0:0:0:0:0:0:1"||/^127(?:\.\d{1,3}){3}$/.test(t):!0}function li(e){const t=e.trim().replace(/^\[(.*)\]$/,"$1");return t.includes(":")?`[${t}]`:t}function si(e,t=window.location.href){const n=e.trim();if(!n||n==="#"||n.startsWith("#"))return n;let r,i;try{r=new URL(n,t),i=new URL(t)}catch{return n}if(!/^https?:$/.test(r.protocol)||!ai(r.hostname))return n;const a=li(i.hostname);return a?(r.host=r.port?`${a}:${r.port}`:a,r.href):n}const er=Sr(),Ct=e=>e&&typeof e=="object"&&"data"in e&&e.data!==void 0?e.data:e;function oi(e){const[t,n]=A(vn()),r=localStorage.getItem("sl-dashboard-theme")||"light",[i,a]=A(r),[l,s]=pn({}),[c,g]=A(!1),[d,h]=A(""),[f,m]=A(null),[$,C]=A(null),[w,j]=A({}),[B,G]=pn({}),[S,T]=A(null),[F,V]=A(!1),[U,I]=A([]),[P,p]=A(null),[b,x]=A(!1);let O=0;const R=(k,L="default")=>{const W=++O;I(re=>[...re,{id:W,message:k,tone:L}]),window.setTimeout(()=>I(re=>re.filter(te=>te.id!==W)),4200)},J=k=>new Promise(L=>p({...k,resolve:L})),X=k=>{const L=P();L&&(p(null),L.resolve(k))},q=async(k=!1)=>{if(!c()){k||g(!0),h("");try{const L=await Promise.allSettled([N.get("/api/metrics"),N.get("/api/metrics/trends"),N.get("/api/monitoring/health"),N.get("/api/monitoring/functions"),N.get("/api/persona_updates?limit=10"),N.get("/api/style_learning/reviews?limit=5"),N.get("/api/jargon/list?page_size=5&confirmed=false&pending=true"),N.get("/api/jargon/stats"),N.get("/api/persona_management/current?group_id=default"),N.get("/api/persona_backups/list?limit=8"),N.get("/api/persona_updates/reviewed?limit=5"),N.get("/api/data/statistics"),N.get("/api/hub/v1/status")]),W=re=>L[re].status==="fulfilled"?Ct(L[re].value):{};if(s(Ut({metrics:W(0),trends:W(1),health:W(2),functions:W(3),persona_updates:W(4),style_learning_reviews:W(5),jargon_reviews:W(6),jargon_stats:W(7),persona_current:W(8),persona_backups:W(9),persona_reviewed:W(10),data_statistics:W(11),hub_status:W(12)})),m(new Date),L.every(re=>re.status==="rejected"))throw new Error("所有 Dashboard 接口均请求失败")}catch(L){const W=L instanceof Error?L.message:"Dashboard 加载失败";h(W),k||R(W,"danger")}finally{g(!1)}}},K=async()=>{try{const[k,L]=await Promise.all([N.get("/api/config/schema"),N.get("/api/config")]),W=Ct(k),re=Ct(L);C(W),j(re),G(Ut(structuredClone(re)))}catch(k){R(k instanceof Error?k.message:"配置加载失败","danger")}},se=async()=>{V(!0);try{const k=await N.post("/api/config",B),L=ci(k,["new_config","data","config"])||structuredClone(B);j(L),G(Ut(structuredClone(L))),R("配置已保存","success")}catch(k){R(k instanceof Error?k.message:"配置保存失败","danger")}finally{V(!1)}},z=async()=>{try{const k=await N.get("/api/integrations/status");T(Ct(k))}catch(k){R(k instanceof Error?k.message:"融合状态加载失败","danger")}},H=k=>{t()!==k&&(window.location.hash=`#/${k}`,n(k),window.scrollTo({top:0,behavior:matchMedia("(prefers-reduced-motion: reduce)").matches?"auto":"smooth"}))};ct(()=>{document.documentElement.dataset.theme=i(),localStorage.setItem("sl-dashboard-theme",i())}),je(()=>{const k=()=>n(vn());window.addEventListener("hashchange",k),q(),K(),z();const L=window.setInterval(()=>{!b()&&!F()&&q(!0)},6e4);ut(()=>{window.removeEventListener("hashchange",k),window.clearInterval(L)})});const Q=ae(()=>({page:t,navigate:H,theme:i,toggleTheme:()=>a(k=>k==="light"?"dark":"light"),data:l,setData:s,loading:c,error:d,lastUpdated:f,refresh:q,schema:$,config:w,configDraft:B,setConfigDraft:G,loadConfig:K,saveConfig:se,integrations:S,loadIntegrations:z,busy:F,setBusy:V,toasts:U,toast:R,confirm:J,confirmRequest:P,resolveConfirm:X,editing:b,setEditing:x}));return u(er.Provider,{get value(){return Q()},get children(){return e.children}})}function ci(e,t){for(const n of t){const r=e[n];if(r&&typeof r=="object"&&!Array.isArray(r))return r}return Object.keys(e).length?e:null}function le(){const e=Cr(er);if(!e)throw new Error("useDashboard must be used inside DashboardProvider");return e}const jt={"ui-button":"_ui-button_rvkki_241","material-icons":"_material-icons_rvkki_273","size-sm":"_size-sm_rvkki_277","icon-only":"_icon-only_rvkki_283","tone-primary":"_tone-primary_rvkki_291","tone-success":"_tone-success_rvkki_304","tone-warning":"_tone-warning_rvkki_313","tone-danger":"_tone-danger_rvkki_322"},ui={"ui-spinner":"_ui-spinner_133zy_241"},di="_interactive_ia12s_248",bn={"ui-card":"_ui-card_ia12s_241",interactive:di},kt={"ui-panel":"_ui-panel_1levk_241","ui-panel-head":"_ui-panel-head_1levk_250","ui-panel-actions":"_ui-panel-actions_1levk_277","ui-panel-body":"_ui-panel-body_1levk_283"},$n={"stat-card":"_stat-card_bgyeo_241","stat-card-label":"_stat-card-label_bgyeo_269"},yn={"ui-badge":"_ui-badge_1whtr_241","tone-success":"_tone-success_1whtr_254","tone-warning":"_tone-warning_1whtr_260","tone-danger":"_tone-danger_1whtr_266"},xt={"progress-wrap":"_progress-wrap_8qpkp_241","progress-label":"_progress-label_8qpkp_246","progress-track":"_progress-track_8qpkp_253","tone-success":"_tone-success_8qpkp_265"},Ft={"ui-field":"_ui-field_14t9o_241","ui-field-label":"_ui-field-label_14t9o_269","field-error":"_field-error_14t9o_274"},gi="_segmented_w60ir_241",hi="_active_w60ir_260",wn={segmented:gi,active:hi},fi="_pagination_zcgeq_241",_i={pagination:fi},Sn={"state-view":"_state-view_9w7jo_241"};var mi=_("<span aria-hidden=true>"),pi=_("<span class=material-icons aria-hidden=true>"),vi=_("<button><span>"),tr=_("<div>"),sn=_("<span class=material-icons>"),bi=_("<h2>"),nr=_("<p>"),$i=_("<header><div></div><div>"),yi=_("<section><div>"),wi=_("<strong>"),tn=_("<small>"),rr=_("<span>"),Si=_("<div><span></span><b>%"),Ci=_("<div><div><span>"),ki=_("<label>"),xi=_("<input>"),Ai=_("<select>"),Pi=_("<textarea>"),ji=_("<div role=group>"),Ei=_("<button>"),Ii=_("<nav aria-label=分页><span>第 <!> / <!> 页"),Oi=_("<div><span class=material-icons></span><strong>");function D(e){const[t,n]=mt(e,["children","tone","size","loading","icon","class"]),r=()=>jt[`tone-${t.tone||"default"}`]||"",i=()=>jt[`size-${t.size||"md"}`]||"";return(()=>{var a=vi(),l=a.firstChild;return pt(a,tt(n,{get class(){return`${jt["ui-button"]} ${r()} ${i()} ${t.class||""}`},get disabled(){return t.loading||n.disabled}}),!1,!0),o(a,u(M,{get when(){return t.loading},get children(){var s=mi();return y(()=>v(s,ui["ui-spinner"])),s}}),l),o(a,u(M,{get when(){return t.icon},get children(){var s=pi();return o(s,()=>t.icon),s}}),l),o(l,()=>t.children),a})()}function Cn(e){return u(D,tt(e,{get class(){return`${jt["icon-only"]} ${e.class||""}`},get title(){return e.label},get"aria-label"(){return e.label}}))}function qe(e){const[t,n]=mt(e,["children","class","interactive"]);return(()=>{var r=tr();return pt(r,tt(n,{get class(){return`${bn["ui-card"]} ${t.interactive?bn.interactive:""} ${t.class||""}`}}),!1,!0),o(r,()=>t.children),r})()}function ce(e){return(()=>{var t=yi(),n=t.firstChild;return o(t,u(M,{get when(){return e.title||e.actions},get children(){var r=$i(),i=r.firstChild,a=i.nextSibling;return o(i,u(M,{get when(){return e.title},get children(){var l=bi();return o(l,u(M,{get when(){return e.icon},get children(){var s=sn();return o(s,()=>e.icon),s}}),null),o(l,()=>e.title,null),l}}),null),o(i,u(M,{get when(){return e.hint},get children(){var l=nr();return o(l,()=>e.hint),l}}),null),o(a,()=>e.actions),y(l=>{var s=kt["ui-panel-head"],c=kt["ui-panel-actions"];return s!==l.e&&v(r,l.e=s),c!==l.t&&v(a,l.t=c),l},{e:void 0,t:void 0}),r}}),n),o(n,()=>e.children),y(r=>{var i=`${kt["ui-panel"]} ${e.class||""}`,a=kt["ui-panel-body"];return i!==r.e&&v(t,r.e=i),a!==r.t&&v(n,r.t=a),r},{e:void 0,t:void 0}),t})()}function Fe(e){return u(qe,{get class(){return $n["stat-card"]},get children(){return[(()=>{var t=tr();return o(t,u(M,{get when(){return e.icon},get children(){var n=sn();return o(n,()=>e.icon),n}}),null),o(t,()=>e.label,null),y(()=>v(t,$n["stat-card-label"])),t})(),(()=>{var t=wi();return o(t,()=>e.value),t})(),u(M,{get when(){return e.note},get children(){var t=tn();return o(t,()=>e.note),t}})]}})}function Ne(e){const t=()=>yn[`tone-${e.tone||"default"}`]||"";return(()=>{var n=rr();return o(n,()=>e.children),y(()=>v(n,`${yn["ui-badge"]} ${t()}`)),n})()}function Ti(e){const t=()=>Math.max(0,Math.min(100,e.value)),n=()=>xt[`tone-${e.tone||"primary"}`]||"";return(()=>{var r=Ci(),i=r.firstChild,a=i.firstChild;return o(r,u(M,{get when(){return e.label},get children(){var l=Si(),s=l.firstChild,c=s.nextSibling,g=c.firstChild;return o(s,()=>e.label),o(c,()=>t().toFixed(0),g),y(()=>v(l,xt["progress-label"])),l}}),i),y(l=>{var s=xt["progress-wrap"],c=xt["progress-track"],g=n(),d=`${t()}%`;return s!==l.e&&v(r,l.e=s),c!==l.t&&v(i,l.t=c),g!==l.a&&v(a,l.a=g),d!==l.o&&Kr(a,"width",l.o=d),l},{e:void 0,t:void 0,a:void 0,o:void 0}),r})()}function on(e){return(()=>{var t=ki();return o(t,u(M,{get when(){return e.label},get children(){var n=rr();return o(n,()=>e.label),y(()=>v(n,Ft["ui-field-label"])),n}}),null),o(t,()=>e.children,null),o(t,u(M,{get when(){return e.hint},get children(){var n=tn();return o(n,()=>e.hint),n}}),null),o(t,u(M,{get when(){return e.error},get children(){var n=tn();return o(n,()=>e.error),y(()=>v(n,Ft["field-error"])),n}}),null),y(()=>v(t,`${Ft["ui-field"]} ${e.class||""}`)),t})()}function Te(e){const[t,n]=mt(e,["label","hint","error","class"]);return u(on,tt(t,{get children(){var r=xi();return pt(r,n,!1,!1),r}}))}function vt(e){const[t,n]=mt(e,["label","hint","error","class","children"]);let r;return je(()=>{r&&n.value!==void 0&&(r.value=String(n.value??""))}),u(on,tt(t,{get children(){var i=Ai(),a=r;return typeof a=="function"?ln(a,i):r=i,pt(i,n,!1,!0),o(i,()=>t.children),i}}))}function cn(e){const[t,n]=mt(e,["label","hint","error","class"]);return u(on,tt(t,{get children(){var r=Pi();return pt(r,n,!1,!1),r}}))}function ft(e){return(()=>{var t=ji();return o(t,u(ee,{get each(){return e.options},children:n=>(()=>{var r=Ei();return r.$$click=()=>e.onChange(n.value),o(r,u(M,{get when(){return n.icon},get children(){var i=sn();return o(i,()=>n.icon),i}}),null),o(r,()=>n.label,null),y(i=>Qe(r,{[wn.active]:n.value===e.value},i)),r})()})),y(n=>{var r=wn.segmented,i=e.label;return r!==n.e&&v(t,n.e=r),i!==n.t&&xe(t,"aria-label",n.t=i),n},{e:void 0,t:void 0}),t})()}function ir(e){return(()=>{var t=Ii(),n=t.firstChild,r=n.firstChild,i=r.nextSibling,a=i.nextSibling,l=a.nextSibling;return l.nextSibling,o(t,u(D,{size:"sm",icon:"chevron_left",get disabled(){return e.disabled||e.page<=1},onClick:()=>e.onChange(e.page-1),children:"上一页"}),n),o(n,()=>e.page,i),o(n,()=>Math.max(1,e.totalPages),l),o(t,u(D,{size:"sm",icon:"chevron_right",get disabled(){return e.disabled||e.page>=e.totalPages},onClick:()=>e.onChange(e.page+1),children:"下一页"}),null),y(()=>v(t,_i.pagination)),t})()}function _e(e){return(()=>{var t=Oi(),n=t.firstChild,r=n.nextSibling;return o(n,()=>e.icon||"inbox"),o(r,()=>e.title||"暂无数据"),o(t,u(M,{get when(){return e.detail},get children(){var i=nr();return o(i,()=>e.detail),i}}),null),o(t,()=>e.action,null),y(()=>v(t,`${Sn["state-view"]} ${Sn.empty||""}`)),t})()}Ee(["click"]);const he=(e,t=0)=>{const n=Number(e);return Number.isFinite(n)?n:t},Ni=(e,t=0,n=100)=>Math.max(t,Math.min(n,he(e))),Li=new Intl.NumberFormat("zh-CN",{maximumFractionDigits:1}),Z=e=>Li.format(Math.round(he(e))),ot=(e,t=0)=>`${Ni(e).toFixed(t)}%`,Di=(e,t=2)=>he(e).toFixed(t),Ge=e=>{if(e==null||e==="")return"--";const t=typeof e=="string"&&/^\d+$/.test(e)?Number(e):e,n=typeof t=="number"?new Date(t>1e12?t:t*1e3):new Date(String(t));return Number.isNaN(n.getTime())?String(e):n.toLocaleString("zh-CN")},oe=e=>e==null||e===""?"--":String(e),Mi=(e,t,n=0)=>{let r=e;for(const i of t.split(".")){if(!r||typeof r!="object")return n;r=r[i]}return r??n},Ve=e=>{if(e==null||e==="")return"--";if(typeof e=="string")return e;try{return JSON.stringify(e,null,2)}catch{return String(e)}},Ri="_reminder_e9bhc_241",qi="_dismiss_e9bhc_264",kn={reminder:Ri,dismiss:qi};var zi=_("<div role=status><span class=material-icons aria-hidden=true>lock_open</span><p>当前 WebUI 处于免密模式,建议在插件配置中启用 WebUI 密码以保护管理面板。</p><button type=button>知道了");const xn="selflearning.password-reminder.dismissed";function Ui(){const[e,t]=A(!1);je(async()=>{if(localStorage.getItem(xn)!=="1")try{(await N.get("/api/password_status")).password_enabled===!1&&t(!0)}catch{}});const n=()=>{localStorage.setItem(xn,"1"),t(!1)};return u(M,{get when(){return e()},get children(){var r=zi(),i=r.firstChild,a=i.nextSibling,l=a.nextSibling;return l.$$click=n,y(s=>{var c=kn.reminder,g=kn.dismiss;return c!==s.e&&v(r,s.e=c),g!==s.t&&v(l,s.t=g),s},{e:void 0,t:void 0}),r}})}Ee(["click"]);const Fi="_active_5o5u4_276",An={"page-nav":"_page-nav_5o5u4_241",active:Fi};var Bi=_('<nav aria-label="Dashboard 页面">'),Gi=_("<a><span class=material-icons></span><span>");const Vi=[{id:"home",label:"模块入口",icon:"home",accent:"home"},{id:"overview",label:"总览",icon:"dashboard",accent:"overview"},{id:"insights",label:"AI 巡检",icon:"auto_awesome",accent:"insights"},{id:"monitoring",label:"运行监控",icon:"monitor_heart",accent:"monitoring"},{id:"reviews",label:"审查队列",icon:"fact_check",accent:"reviews"},{id:"jargon-learning",label:"黑话学习",icon:"forum",accent:"jargon-learning"},{id:"expression-learning",label:"表达学习",icon:"record_voice_over",accent:"expression-learning"},{id:"persona-learning",label:"人格学习",icon:"person_search",accent:"persona-learning"},{id:"shadow-mode",label:"影子模式",icon:"theater_comedy",accent:"shadow-mode"},{id:"content",label:"学习内容",icon:"library_books",accent:"content"},{id:"graphs",label:"图谱",icon:"hub",accent:"graphs"},{id:"reply-strategy",label:"回复策略",icon:"quickreply",accent:"reply-strategy"},{id:"integrations",label:"功能融合",icon:"extension",accent:"integrations"},{id:"settings",label:"设置",icon:"tune",accent:"settings"}];function Ki(){const e=le();return(()=>{var t=Bi();return o(t,u(ee,{each:Vi,children:n=>(()=>{var r=Gi(),i=r.firstChild,a=i.nextSibling;return r.$$click=l=>{l.preventDefault(),e.navigate(n.id)},o(i,()=>n.icon),o(a,()=>n.label),y(l=>{var s=`#/${n.id}`,c=n.accent,g={[An.active]:e.page()===n.id};return s!==l.e&&xe(r,"href",l.e=s),c!==l.t&&xe(r,"data-accent",l.t=c),l.a=Qe(r,g,l.a),l},{e:void 0,t:void 0,a:void 0}),r})()})),y(()=>v(t,An["page-nav"])),t})()}Ee(["click"]);const Qi="_topbar_lqg9r_245",Hi="_brand_lqg9r_259",Ji="_eyebrow_lqg9r_290",Wi="_toolbar_lqg9r_304",Xi="_pulse_lqg9r_327",Le={"app-shell":"_app-shell_lqg9r_241",topbar:Qi,brand:Hi,"brand-mark":"_brand-mark_lqg9r_280",eyebrow:Ji,"page-container":"_page-container_lqg9r_298",toolbar:Wi,"update-pill":"_update-pill_lqg9r_310",pulse:Xi};var Yi=_("<div><header><a href=#/home aria-label=返回模块入口><span>psychology</span><h1><div>SELF LEARNING</div><div>监控板</div></h1></a><div><span><span></span></span></div></header><main>");function Zi(e){const t=le();return(()=>{var n=Yi(),r=n.firstChild,i=r.firstChild,a=i.firstChild,l=a.nextSibling,s=l.firstChild,c=i.nextSibling,g=c.firstChild,d=g.firstChild,h=r.nextSibling;return i.$$click=f=>{f.preventDefault(),t.navigate("home")},o(g,u(M,{get when(){return t.lastUpdated()},fallback:"等待首次刷新",get children(){return["更新于 ",de(()=>Ge(t.lastUpdated()))]}}),null),o(c,u(D,{icon:"refresh",get loading(){return t.loading()},onClick:()=>t.refresh(),children:"刷新"}),null),o(c,u(Cn,{get icon(){return t.theme()==="dark"?"light_mode":"dark_mode"},label:"切换主题",get onClick(){return t.toggleTheme}}),null),o(c,u(Cn,{icon:"settings",label:"打开设置",tone:"primary",onClick:()=>t.navigate("settings")}),null),o(n,u(Ki,{}),h),o(n,u(Ui,{}),h),o(h,()=>e.children),y(f=>{var m=Le["app-shell"],$=Le.topbar,C=Le.brand,w=`${Le["brand-mark"]} material-icons`,j=Le.eyebrow,B=Le.toolbar,G=Le["update-pill"],S={[Le.pulse]:t.loading()},T=Le["page-container"],F=t.page();return m!==f.e&&v(n,f.e=m),$!==f.t&&v(r,f.t=$),C!==f.a&&v(i,f.a=C),w!==f.o&&v(a,f.o=w),j!==f.i&&v(s,f.i=j),B!==f.n&&v(c,f.n=B),G!==f.s&&v(g,f.s=G),f.h=Qe(d,S,f.h),T!==f.r&&v(h,f.r=T),F!==f.d&&xe(h,"data-page",f.d=F),f},{e:void 0,t:void 0,a:void 0,o:void 0,i:void 0,n:void 0,s:void 0,h:void 0,r:void 0,d:void 0}),n})()}Ee(["click"]);const ea="_toast_1ohj5_241",Bt={"toast-viewport":"_toast-viewport_1ohj5_241",toast:ea,"toast-in":"_toast-in_1ohj5_1","tone-success":"_tone-success_1ohj5_263","material-icons":"_material-icons_1ohj5_263","tone-danger":"_tone-danger_1ohj5_267"},ta="_dialog_rnl4f_241",Pn={"dialog-overlay":"_dialog-overlay_rnl4f_241",dialog:ta};var na=_("<div aria-live=polite>"),ra=_("<div><span class=material-icons></span><span>"),ia=_("<div role=presentation><section role=alertdialog aria-modal=true aria-labelledby=confirm-title><h2 id=confirm-title></h2><p></p><footer>");function aa(){const e=le();return u(Jn,{get children(){var t=na();return o(t,u(ee,{get each(){return e.toasts()},children:n=>(()=>{var r=ra(),i=r.firstChild,a=i.nextSibling;return o(i,(()=>{var l=de(()=>n.tone==="danger");return()=>l()?"error":n.tone==="success"?"check_circle":"info"})()),o(a,()=>n.message),y(()=>v(r,`${Bt.toast} ${Bt[`tone-${n.tone}`]||""}`)),r})()})),y(()=>v(t,Bt["toast-viewport"])),t}})}function la(){const e=le();return u(M,{get when(){return e.confirmRequest()},keyed:!0,children:t=>u(Jn,{get children(){var n=ia(),r=n.firstChild,i=r.firstChild,a=i.nextSibling,l=a.nextSibling;return n.$$click=s=>s.target===s.currentTarget&&e.resolveConfirm(!1),o(i,()=>t.title),o(a,()=>t.message),o(l,u(D,{onClick:()=>e.resolveConfirm(!1),children:"取消"}),null),o(l,u(D,{get tone(){return t.tone||"danger"},onClick:()=>e.resolveConfirm(!0),get children(){return t.confirmText||"确认"}}),null),y(s=>{var c=Pn["dialog-overlay"],g=Pn.dialog;return c!==s.e&&v(n,s.e=c),g!==s.t&&v(r,s.t=g),s},{e:void 0,t:void 0}),n}})})}Ee(["click"]);const E=e=>e&&typeof e=="object"&&!Array.isArray(e)?e:{},ve=e=>{if(Array.isArray(e))return e;const t=E(e);for(const n of["items","data","results","updates","reviews","backups","batches","dashboards","jargon_list"])if(Array.isArray(t[n]))return t[n];return[]},Be=(e,t)=>ve(e[t]),Ye=(e,t,n=0)=>{for(const r of t){const i=Mi(e,r,void 0);if(i!=null)return he(i,n)}return n},At={"page-header":"_page-header_dyj7g_241","page-header-heading":"_page-header-heading_dyj7g_261","page-back-button":"_page-back-button_dyj7g_267","page-header-actions":"_page-header-actions_dyj7g_276"};var sa=_("<header><div><div><h2></h2><p></p></div></div><div>");function be(e){const t=le();return(()=>{var n=sa(),r=n.firstChild,i=r.firstChild,a=i.firstChild,l=a.nextSibling,s=r.nextSibling;return o(r,u(M,{get when(){return!e.home},get children(){return u(D,{get class(){return At["page-back-button"]},icon:"arrow_back_ios",title:"返回上一级页面",onClick:()=>t.navigate("home")})}}),i),o(a,()=>e.title),o(l,()=>e.description),o(s,()=>e.actions),y(c=>{var g=At["page-header"],d=At["page-header-heading"],h=At["page-header-actions"];return g!==c.e&&v(n,c.e=g),d!==c.t&&v(r,c.t=d),h!==c.a&&v(s,c.a=h),c},{e:void 0,t:void 0,a:void 0}),n})()}const ge={"hero-command":"_hero-command_tyt77_241","hero-command-copy":"_hero-command-copy_tyt77_257","hero-pulse-grid":"_hero-pulse-grid_tyt77_274","pulse-stat":"_pulse-stat_tyt77_281","quick-dock":"_quick-dock_tyt77_302","learning-module-grid":"_learning-module-grid_tyt77_345","learning-module-card":"_learning-module-card_tyt77_351","module-icon":"_module-icon_tyt77_363","module-grid":"_module-grid_tyt77_385","system-entry-grid":"_system-entry-grid_tyt77_391","system-entry-card":"_system-entry-card_tyt77_395","route-card":"_route-card_tyt77_402","entry-card-head":"_entry-card-head_tyt77_423","entry-card-copy":"_entry-card-copy_tyt77_436","entry-card-state":"_entry-card-state_tyt77_440"};var oa=_("<div class=page><section><div><span>LEARNING PULSE</span><h3>学习系统正在持续整理对话经验</h3><p>所有写操作仍由你确认,自动刷新不会打断正在编辑的内容。</p><nav aria-label=学习快捷入口></nav></div><div><div><span>学习效率</span><strong></strong></div><div><span>待办总量</span><strong></strong></div><div><span>内容样本</span><strong></strong></div><div><span>最近批次</span><strong></strong></div></div></section><h3 class=section-label>Independent Learning Modules</h3><div></div><h3 class=section-label>System Entry Points</h3><div>"),ca=_("<a><span class=material-icons>"),ua=_("<a><span></span><div><strong></strong><p></p><small></small></div><span class=material-icons>arrow_forward"),da=_("<a><div><span class=material-icons></span></div><div><strong></strong><p></p></div><div><strong></strong><span>");const ga=[{page:"jargon-learning",title:"黑话学习",description:"群聊词汇、语义和确认队列",icon:"forum",tone:"violet"},{page:"expression-learning",title:"表达方式学习",description:"对话样本、风格特征与学习记录",icon:"record_voice_over",tone:"cyan"},{page:"persona-learning",title:"人格学习",description:"当前人格、演化建议与备份",icon:"person_search",tone:"amber"},{page:"shadow-mode",title:"影子模式",description:"选择群友并学习其表达习惯",icon:"theater_comedy",tone:"cyan"}],ha=[{page:"jargon-learning",label:"黑话",icon:"translate"},{page:"expression-learning",label:"表达",icon:"record_voice_over"},{page:"persona-learning",label:"人格",icon:"psychology"},{page:"shadow-mode",label:"影子",icon:"theater_comedy"},{page:"reviews",label:"审查",icon:"rule"},{page:"content",label:"内容",icon:"article"},{page:"monitoring",label:"监控",icon:"monitor_heart"}];function jn(){const e=le(),t=ae(()=>{const r=E(e.data.metrics),i=E(e.data.health),a=E(e.data.trends),l=E(e.data.jargon_stats),s=E(E(e.data.data_statistics).data??e.data.data_statistics),c=E(e.data.persona_updates),g=E(e.data.style_learning_reviews),d=Be(e.data,"persona_updates"),h=he(c.total,d.length),f=he(g.total,Be(e.data,"style_learning_reviews").length),$=d.some(z=>z.review_source==="style_learning")?Math.max(0,h-f):h,C=he(l.total_candidates??E(l.data).total_candidates),w=he(l.confirmed_jargon??E(l.data).confirmed_jargon),j=Math.max(0,C-w),B=he(s.style_learning),G=he(s.memory)+he(s.knowledge_graph),S=ve(e.integrations()?.dashboards??e.integrations()),T=S.filter(z=>z.active).length,F=S.filter(z=>z.delegated).length,V=S.find(z=>z.id==="group_chat_plus"),U=S.find(z=>z.id==="livingmemory"),I=he(r.total_messages_collected),P=he(r.filtered_messages),p=I?P/I*100:0,b=he(r.learning_efficiency),x=$+f+j,O=ve(a.recent_batches).length,R=E(r.llm_call_summary),J=he(R.total_calls),X=he(R.abnormal_provider_count),K=(e.schema()?.groups||[]).reduce((z,H)=>z+(H.fields||[]).filter(Q=>Q.editable!==!1).length,e.schema()?.fields?.filter(z=>z.editable!==!1).length||0),se=i.overall!=="healthy"||X>0?"danger":x>20?"warning":"success";return{health:i,totalMessages:I,filterRate:p,learningEfficiency:b,backlog:x,batches:O,personaPending:$,styleTotal:f,jargonCandidates:C,jargonConfirmed:w,jargonPending:j,contentCount:B,graphNodes:G,integrations:S,activeIntegrations:T,delegatedIntegrations:F,reply:V,memory:U,llmCalls:J,llmAbnormal:X,editableSettings:K,insightTone:se,backups:ve(E(e.data.persona_backups).backups).length}}),n=ae(()=>{const r=t(),i=String(r.health.overall||"unknown");return[{page:"overview",title:"总览",description:"核心指标与消息趋势",icon:"dashboard",value:ot(r.learningEfficiency),note:`${Z(r.totalMessages)} 条消息 · 筛选率 ${ot(r.filterRate)}`,status:"数据已同步",tone:r.totalMessages>0?"success":"warning"},{page:"insights",title:"AI 巡检",description:"异常、瓶颈与下一步建议",icon:"auto_awesome",value:r.insightTone==="danger"?"需关注":r.insightTone==="warning"?"有积压":"正常",note:`${Z(r.backlog)} 项待办 · ${Z(r.llmAbnormal)} 个模型异常`,status:r.insightTone==="success"?"暂无高优先级问题":"建议查看",tone:r.insightTone},{page:"monitoring",title:"运行监控",description:"健康状态、热点与模型调用",icon:"monitor_heart",value:i,note:`${Z(r.llmCalls)} 次模型调用 · ${Z(r.llmAbnormal)} 个异常`,status:i==="healthy"?"系统健康":"健康检查异常",tone:i==="healthy"?"success":"danger"},{page:"reviews",title:"审查队列",description:"人格、风格、黑话与批次",icon:"rate_review",value:Z(r.backlog),note:`人格 ${Z(r.personaPending)} · 风格 ${Z(r.styleTotal)} · 黑话 ${Z(r.jargonPending)}`,status:r.backlog?"等待处理":"队列已清空",tone:r.backlog>20?"warning":"success"},{page:"content",title:"学习内容",description:"对话、分析、表达模式与历史",icon:"article",value:r.contentCount?Z(r.contentCount):"--",note:"当前表达学习内容总量",status:r.contentCount?"内容可浏览":"暂无内容",tone:r.contentCount?"success":"default"},{page:"reply-strategy",title:"回复策略",description:"Group Chat Plus 面板",icon:"forum",value:r.reply?.delegated?"ON":r.reply?.active?"本地":"--",note:r.reply?.delegated?"回复已委托":r.reply?.active?"插件已加载":"插件未加载",status:r.reply?.active?"可用":"不可用",tone:r.reply?.active?"success":"warning"},{page:"graphs",title:"记忆 / 知识图谱",description:"本地图谱与记忆后端",icon:"hub",value:r.graphNodes?Z(r.graphNodes):r.memory?.active?"后端":"--",note:r.graphNodes?"个当前记忆图谱节点":r.memory?.delegated?"读取 LivingMemory 后端":"本地图谱",status:r.graphNodes||r.memory?.active?"可用":"暂无节点",tone:r.graphNodes||r.memory?.active?"success":"default"},{page:"integrations",title:"功能融合",description:"插件分工、面板和开发 API",icon:"extension",value:r.integrations.length?`${r.activeIntegrations}/${r.integrations.length}`:"--",note:r.delegatedIntegrations?`${r.delegatedIntegrations} 项已委托`:"插件 API 入口",status:r.activeIntegrations?"已连接":"仅本插件在线",tone:r.activeIntegrations?"success":"warning"},{page:"integrations",title:"世界书 / QQ 导入",description:"预览、导入和结果统计",icon:"menu_book",value:"API",note:"世界书与聊天记录导入接口",status:r.integrations.length?"可用":"等待融合状态",tone:r.integrations.length?"success":"default"}]});return(()=>{var r=oa(),i=r.firstChild,a=i.firstChild,l=a.firstChild,s=l.nextSibling,c=s.nextSibling,g=c.nextSibling,d=a.nextSibling,h=d.firstChild,f=h.firstChild,m=f.nextSibling,$=h.nextSibling,C=$.firstChild,w=C.nextSibling,j=$.nextSibling,B=j.firstChild,G=B.nextSibling,S=j.nextSibling,T=S.firstChild,F=T.nextSibling,V=i.nextSibling,U=V.nextSibling,I=U.nextSibling,P=I.nextSibling;return o(r,u(be,{home:!0,title:"学习模块控制台",description:"把自主学习链路拆成可观察、可审查、可干预的模块。",icon:"psychology"}),i),o(g,u(ee,{each:ha,children:p=>(()=>{var b=ca(),x=b.firstChild;return b.$$click=O=>{O.preventDefault(),e.navigate(p.page)},o(x,()=>p.icon),o(b,()=>p.label,null),y(()=>xe(b,"href",`#/${p.page}`)),b})()})),o(m,()=>ot(t().learningEfficiency)),o(w,()=>Z(t().backlog)),o(G,(()=>{var p=de(()=>!!t().contentCount);return()=>p()?Z(t().contentCount):"--"})()),o(F,()=>Z(t().batches)),o(U,u(ee,{each:ga,children:p=>u(qe,{interactive:!0,get class(){return ge["learning-module-card"]},get"data-accent"(){return p.page},get children(){var b=ua(),x=b.firstChild,O=x.nextSibling,R=O.firstChild,J=R.nextSibling,X=J.nextSibling;return b.$$click=q=>{q.preventDefault(),e.navigate(p.page)},o(x,()=>p.icon),o(R,()=>p.title),o(J,()=>p.description),o(X,(()=>{var q=de(()=>p.page==="jargon-learning");return()=>q()?`候选 ${Z(t().jargonCandidates)} · 已确认 ${Z(t().jargonConfirmed)}`:de(()=>p.page==="expression-learning")()?`内容 ${Z(t().contentCount)} · 待审 ${Z(t().styleTotal)}`:de(()=>p.page==="persona-learning")()?`待审 ${Z(t().personaPending)} · 备份 ${Z(t().backups)}`:"现有群聊与导入记录均可学习"})()),y(q=>{var K=`#/${p.page}`,se=`${ge["module-icon"]} material-icons`;return K!==q.e&&xe(b,"href",q.e=K),se!==q.t&&v(x,q.t=se),q},{e:void 0,t:void 0}),b}})})),o(P,u(ee,{get each(){return n()},children:p=>u(qe,{interactive:!0,get class(){return`${ge["route-card"]} ${ge["system-entry-card"]}`},get"data-accent"(){return p.page},get children(){var b=da(),x=b.firstChild,O=x.firstChild,R=x.nextSibling,J=R.firstChild,X=J.nextSibling,q=R.nextSibling,K=q.firstChild,se=K.nextSibling;return b.$$click=z=>{z.preventDefault(),e.navigate(p.page)},o(O,()=>p.icon),o(x,u(M,{get when(){return!e.loading()},get children(){return u(Ne,{get tone(){return p.tone},get children(){return p.status}})}}),null),o(J,()=>p.title),o(X,()=>p.description),o(K,(()=>{var z=de(()=>!!e.loading());return()=>z()?"--":p.value})()),o(se,(()=>{var z=de(()=>!!e.loading());return()=>z()?"加载中...":p.note})()),y(z=>{var H=`#/${p.page}`,Q=ge["entry-card-head"],k=ge["entry-card-copy"],L=ge["entry-card-state"];return H!==z.e&&xe(b,"href",z.e=H),Q!==z.t&&v(x,z.t=Q),k!==z.a&&v(R,z.a=k),L!==z.o&&v(q,z.o=L),z},{e:void 0,t:void 0,a:void 0,o:void 0}),b}})})),y(p=>{var b=ge["hero-command"],x=ge["hero-command-copy"],O=ge["quick-dock"],R=ge["hero-pulse-grid"],J=ge["pulse-stat"],X=ge["pulse-stat"],q=ge["pulse-stat"],K=ge["pulse-stat"],se=ge["learning-module-grid"],z=`${ge["module-grid"]} ${ge["system-entry-grid"]}`;return b!==p.e&&v(i,p.e=b),x!==p.t&&v(a,p.t=x),O!==p.a&&v(g,p.a=O),R!==p.o&&v(d,p.o=R),J!==p.i&&v(h,p.i=J),X!==p.n&&v($,p.n=X),q!==p.s&&v(j,p.s=q),K!==p.h&&v(S,p.h=K),se!==p.r&&v(U,p.r=se),z!==p.d&&v(P,p.d=z),p},{e:void 0,t:void 0,a:void 0,o:void 0,i:void 0,n:void 0,s:void 0,h:void 0,r:void 0,d:void 0}),r})()}Ee(["click"]);const fa="_echart_1r527_1",_a={echart:fa};var ma=_("<div role=img>");sr([or,cr,ur,dr,gr,hr,fr,_r,mr]);function un(e){const t=le();let n,r,i;return je(()=>{r=dn(n,t.theme()),r.setOption(e.option,!0),e.onReady?.(r),i=new ResizeObserver(()=>r?.resize()),i.observe(n)}),ct(()=>{const a=t.theme();if(!r)return;const l=e.option;r.dispose(),r=dn(n,a),r.setOption(l,!0),e.onReady?.(r)}),ct(()=>r?.setOption(e.option,!0)),ut(()=>{i?.disconnect(),r?.dispose()}),(()=>{var a=ma(),l=n;return typeof l=="function"?ln(l,a):n=a,y(()=>v(a,`${_a.echart} ${e.class||""}`)),a})()}const pa={"summary-copy":"_summary-copy_1u4fi_241"};var va=_("<div><span class=material-icons>insights</span><h3>系统总体运行平稳</h3><p>若某项指标为 0 或暂无曲线,通常表示对应数据尚未积累,而不是页面故障。"),ba=_("<div class=page><div class=metrics-grid></div><div class=two-column>");function $a(){const e=le(),t=ae(()=>{const n=E(e.data.trends),r=E(n.daily_messages),i=Array.isArray(n.recent_batches)?n.recent_batches:[],a=Object.keys(r),l=a.map(c=>Number(r[c]??0)),s=new Map;for(const c of i){const g=c.created_at??c.start_time,d=g?new Date(typeof g=="number"&&g<1e12?g*1e3:String(g)).toISOString().slice(0,10):"";d&&s.set(d,(s.get(d)||0)+Number(c.processed_messages??c.message_count??0))}for(const c of s.keys())a.includes(c)||a.push(c);return a.sort(),{tooltip:{trigger:"axis"},grid:{left:42,right:24,top:28,bottom:34},xAxis:{type:"category",data:a},yAxis:{type:"value"},series:[{name:"消息",type:"bar",data:a.map(c=>l[Object.keys(r).indexOf(c)]||0),itemStyle:{color:"#6476ff"}},{name:"学习处理",type:"line",smooth:!0,data:a.map(c=>s.get(c)||0),lineStyle:{color:"#17b6a4"}}]}});return(()=>{var n=ba(),r=n.firstChild,i=r.nextSibling;return o(n,u(be,{title:"总览",description:"学习系统的核心健康度、吞吐量和近期趋势。",icon:"dashboard"}),r),o(r,u(Fe,{label:"总消息数",icon:"chat",get value(){return Z(Ye(e.data,["metrics.total_messages_collected"]))},note:"进入插件的数据总量"}),null),o(r,u(Fe,{label:"有效学习率",icon:"school",tone:"success",get value(){return ot(Ye(e.data,["metrics.learning_efficiency","metrics.efficiency"])*(Ye(e.data,["metrics.learning_efficiency","metrics.efficiency"])<=1?100:1))}}),null),o(r,u(Fe,{label:"过滤消息",icon:"filter_alt",tone:"warning",get value(){return Z(Ye(e.data,["metrics.filtered_messages","metrics.messages.filtered"]))}}),null),o(r,u(Fe,{label:"系统内存",icon:"memory",tone:"warning",get value(){return ot(Ye(e.data,["metrics.system_metrics.memory_percent"]))}}),null),o(i,u(ce,{title:"消息与学习趋势",hint:"按后端聚合时间窗口展示",get children(){return u(un,{get option(){return t()},class:"chart-lg"})}}),null),o(i,u(ce,{title:"当前状态",hint:"最近一次健康快照",get children(){var a=va();return y(()=>v(a,pa["summary-copy"])),a}}),null),n})()}const Gt={"insight-hero":"_insight-hero_q7ni8_241","insight-list":"_insight-list_q7ni8_261","insight-card":"_insight-card_q7ni8_266"};var ya=_("<div class=page><div><span class=material-icons>auto_awesome</span><div><strong>巡检建议只用于导航和辅助判断</strong><p>所有实际审批、删除和配置修改仍需要明确确认。</p></div></div><div>"),wa=_("<button><div><strong></strong></div><p></p><span class=material-icons>arrow_forward");function Sa(){const e=le(),t=ae(()=>{const r=E(e.data.persona_updates),i=E(e.data.style_learning_reviews),a=E(e.data.jargon_stats),l=Number(r.total||0)+Number(i.total||0)+Math.max(0,Number(a.total_candidates||0)-Number(a.confirmed_jargon||0)),s=Ye(e.data,["metrics.filtered_messages","metrics.messages.filtered"]),c=Object.values(E(E(e.data.health).checks)).filter(d=>E(d).status&&E(d).status!=="healthy").length,g=[];return l>0&&g.push({title:"审查队列存在积压",detail:`当前约 ${Z(l)} 条记录等待处理。`,tone:l>20?"danger":"warning",page:"reviews",value:l}),s>0&&g.push({title:"过滤链路持续工作",detail:`${Z(s)} 条消息因质量或规则未进入学习。`,tone:"default",page:"monitoring",value:s}),c>0&&g.push({title:"健康检查发现异常",detail:`${Z(c)} 项检查未处于健康状态。`,tone:"danger",page:"monitoring",value:c}),g.length||g.push({title:"暂未发现需要立即处理的问题",detail:"当前快照没有明显积压或健康告警。",tone:"success",page:"overview",value:0}),g}),n=async()=>{const r=t().map(i=>`- ${i.title}: ${i.detail}`).join(`
</code_context>
<issue_to_address>
**security (javascript.browser.security.insecure-document-method):** User controlled data in methods like `innerHTML`, `outerHTML` or `document.write` is an anti-pattern that can lead to XSS vulnerabilities

*Source: opengrep*
</issue_to_address>

### Comment 5
<location path="web_res/static/dashboard/assets/index-GRGz13iX.js" line_range="1" />
<code_context>
import{u as sr,i as or,a as cr,b as ur,c as dr,d as gr,e as hr,f as fr,g as _r,h as mr,j as dn}from"./echarts-BFYZiO-4.js";(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))r(i);new MutationObserver(i=>{for(const a of i)if(a.type==="childList")for(const l of a.addedNodes)l.tagName==="LINK"&&l.rel==="modulepreload"&&r(l)}).observe(document,{childList:!0,subtree:!0});function n(i){const a={};return i.integrity&&(a.integrity=i.integrity),i.referrerPolicy&&(a.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?a.credentials="include":i.crossOrigin==="anonymous"?a.credentials="omit":a.credentials="same-origin",a}function r(i){if(i.ep)return;i.ep=!0;const a=n(i);fetch(i.href,a)}})();const pr=!1,vr=(e,t)=>e===t,Ae=Symbol("solid-proxy"),zn=typeof Proxy=="function",Ht=Symbol("solid-track"),Et={equals:vr};let Un=Kn;const ze=1,It=2,Fn={owned:null,cleanups:null,context:null,owner:null};var ne=null;let qt=null,br=null,ie=null,fe=null,Me=null,Lt=0;function st(e,t){const n=ie,r=ne,i=e.length===0,a=t===void 0?r:t,l=i?Fn:{owned:null,cleanups:null,context:a?a.context:null,owner:a},s=i?e:()=>e(()=>Pe(()=>dt(l)));ne=l,ie=null;try{return Ke(s,!0)}finally{ie=n,ne=r}}function

Comment thread web_src/src/pages/settings/SettingsPage.tsx Outdated
Comment thread web_src/src/pages/settings/SettingsPage.tsx Outdated
Comment thread web_src/src/pages/settings/SettingsPage.tsx
Comment thread web_res/static/dashboard/assets/index-GRGz13iX.js Outdated
Comment thread web_res/static/dashboard/assets/index-GRGz13iX.js Outdated
- Show an EmptyState in the detail pane when search matches nothing
  instead of a blank search-results container.
- Group field counts in the sidebar always show the group's total field
  count; filtered counts remain visible only in the detail header.
sourcery-ai[bot]
sourcery-ai Bot previously approved these changes Sep 4, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sourcery assessment

Approved.

sourcery-ai[bot]
sourcery-ai Bot previously approved these changes Sep 4, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sourcery assessment

Approved.

- New 'WebUI 访问密码' settings panel: passwordless mode allows setting a
  custom password that enables protection (POST /api/webui_password/setup,
  manual_confirmed + strength validated, PBKDF2 stored); enabled mode allows
  changing the password (old + new via /api/plugin_change_password).
- enable_webui_password is persisted via plugin_config.save_config() so the
  flag survives restarts.
- Both operations clear the server session and the SPA performs a full-page
  redirect to /api/login, requiring the new password to re-enter.
- Top nav scrollbar removed: wheel converts vertical to horizontal scrolling
  (releases page scroll at both ends); scrollbar hidden across engines.
- Version bump to 4.1.0; dashboard bundle rebuilt.

Python 765 tests / frontend 43 tests green; browser-verified both panel
modes with a mock-API preview.
@EterUltimate EterUltimate changed the title feat: 监控板视觉重设计——硬朗风格 + 每页强调色 + 设置页层次化 feat: 监控板视觉重设计 + WebUI 自定义密码(4.1.0) Sep 5, 2026
- New '自定义样式 (CSS)' settings panel: edit custom CSS in a code editor,
  '保存并应用' takes effect instantly (persisted per browser via
  localStorage, injected as <style id=slx-custom-style> before first paint),
  '重置默认风格' clears custom CSS + theme override and reloads to defaults.
- All dashboard components now carry stable, uniquely-prefixed slx-* style
  hooks (slx-panel/slx-btn/slx-nav-item/slx-stat/... plus #slx-app root and
  design-variable overrides) so user CSS can target any element precisely
  without colliding with other stylesheets; full hook catalog is documented
  inside the panel.
- Fix pre-existing bug: settings toggles rendered as unstyled checkboxes
  because ConfigField referenced a literal switch-field class that was never
  in the bundle; now uses the SwitchField module class (+ slx-switch hook).

Frontend 50 tests green; verified in browser (custom vars restyle the whole
dashboard, reset restores defaults).
Per Sourcery review: dependency installation now uses a dedicated
installing signal so the unrelated save button no longer shows a loading
state or stays disabled while pip install runs; both install buttons
reflect the in-flight state instead.
@EterUltimate
EterUltimate merged commit fb61b93 into main Sep 5, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant