feat: 监控板视觉重设计 + WebUI 自定义密码(4.1.0) - #248
Merged
Merged
Conversation
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.
Contributor
Reviewer's Guide本 PR 保持路由、信息架构和行为契约不变,集中重做设计令牌与共享组件样式,增加 14 页亮暗主题强调色,并将设置页实现为带跨组搜索、分组选择记忆和逐项脏状态管理的 master-detail 界面;同时更新并提交对应构建产物。 Flow diagram for settings group selection and searchflowchart 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]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Contributor
There was a problem hiding this comment.
Hey - I've found 2 security issues, and 3 other issues
Security issues:
- User controlled data in methods like
innerHTML,outerHTMLordocument.writeis an anti-pattern that can lead to XSS vulnerabilities (link) - User controlled data in a
s.innerHTMLis 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)&<(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- 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.
- 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.
- 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概述
两部分工作:(1) 基于 garden-skills 仓库
web-design-engineer方法论(Linear 风格配方)的监控板视觉重设计;(2) WebUI 自定义密码能力(免密模式可直接设置密码,修改密码后强制重新登录)。保留信息架构、路由、文案、行为契约。版本号提升至 4.1.0。一、视觉重设计(Linear 风格)
二、WebUI 自定义密码
POST /api/webui_password/setup(manual_confirmed确认标记 + 强度校验 + PBKDF2 存储 + 显式save_config()持久化enable_webui_password,重启不回退)。/api/plugin_change_password)。session.clear(),前端整页跳转/api/login,要求输入新密码重新进入;独立change_password.html沿用同一跳转。webui_initial_password)保留为恢复路径,启用后可被自定义密码取代。三、验证
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:
slx-styling hooks, immediate application, and theme reset support.Bug Fixes:
Enhancements:
Documentation:
Tests:
Chores: