e2fc1d24cf
- frontend/ eingerichtet: Vite 6, Svelte 5 (Runes), TypeScript - Stores: status/jobs/system mit $state-Runes (status.svelte.ts etc.) - main.ts ersetzt main.js: bundelt Legacy-Panels (overview/models/server/ jobs/cookbook/connect) + mountet neue Svelte-Panels (guides/news) - GuidesPanel.svelte: statischer Guide mit Akkordeon-Tutorials + Konzept-Grid - NewsPanel.svelte: reaktives Newsboard mit API-Fetch + Upgrade-Vorschlaegen - Vite-Output: static/dist/main.js (committet, kein Build auf dem Server) - index.html: laedt jetzt static/dist/main.js statt static/js/main.js - Build: 118 Module → 134 kB (gzip: 44 kB) Naechste Schritte: Overview → Jobs → Server → Models → Cookbook migrieren. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
558 lines
131 KiB
JavaScript
558 lines
131 KiB
JavaScript
var ir=Object.defineProperty;var ns=e=>{throw TypeError(e)};var sr=(e,t,n)=>t in e?ir(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var se=(e,t,n)=>sr(e,typeof t!="symbol"?t+"":t,n),oi=(e,t,n)=>t.has(e)||ns("Cannot "+n);var d=(e,t,n)=>(oi(e,t,"read from private field"),n?n.call(e):t.get(e)),_=(e,t,n)=>t.has(e)?ns("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,n),y=(e,t,n,i)=>(oi(e,t,"write to private field"),i?i.call(e,n):t.set(e,n),n),T=(e,t,n)=>(oi(e,t,"access private method"),n);var Is=Array.isArray,ar=Array.prototype.indexOf,Nn=Array.prototype.includes,Xn=Array.from,rr=Object.defineProperty,rn=Object.getOwnPropertyDescriptor,lr=Object.getOwnPropertyDescriptors,or=Object.prototype,dr=Array.prototype,Gs=Object.getPrototypeOf,is=Object.isExtensible;const cr=()=>{};function ur(e){for(var t=0;t<e.length;t++)e[t]()}function Ks(){var e,t,n=new Promise((i,s)=>{e=i,t=s});return{promise:n,resolve:e,reject:t}}function ss(e,t){if(Array.isArray(e))return e;if(!(Symbol.iterator in e))return Array.from(e);const n=[];for(const i of e)if(n.push(i),n.length===t)break;return n}const U=2,fn=4,ei=8,qs=1<<24,ke=16,$e=32,et=64,ki=128,ve=512,I=1024,q=2048,De=4096,Q=8192,me=16384,Ut=32768,as=1<<25,It=65536,In=1<<17,fr=1<<18,jt=1<<19,pr=1<<20,Ie=1<<25,yt=65536,Gn=1<<21,At=1<<22,Ze=1<<23,di=Symbol("$state"),Dn=Symbol("attributes"),Mi=Symbol("class"),hr=Symbol("style"),Yt=Symbol("text"),ti=new class extends Error{constructor(){super(...arguments);se(this,"name","StaleReactionError");se(this,"message","The reaction that called `getAbortSignal()` was re-run or destroyed")}};function vr(e){throw new Error("https://svelte.dev/e/lifecycle_outside_component")}function mr(){throw new Error("https://svelte.dev/e/async_derived_orphan")}function gr(e,t,n){throw new Error("https://svelte.dev/e/each_key_duplicate")}function br(e){throw new Error("https://svelte.dev/e/effect_in_teardown")}function wr(){throw new Error("https://svelte.dev/e/effect_in_unowned_derived")}function yr(e){throw new Error("https://svelte.dev/e/effect_orphan")}function xr(){throw new Error("https://svelte.dev/e/effect_update_depth_exceeded")}function _r(){throw new Error("https://svelte.dev/e/state_descriptors_fixed")}function kr(){throw new Error("https://svelte.dev/e/state_prototype_fixed")}function Mr(){throw new Error("https://svelte.dev/e/state_unsafe_mutation")}function Sr(){throw new Error("https://svelte.dev/e/svelte_boundary_reset_onerror")}const $r=1,Er=2,Tr=16,Cr=1,Lr=2,N=Symbol("uninitialized"),Us="http://www.w3.org/1999/xhtml",Ar="http://www.w3.org/2000/svg",zr="http://www.w3.org/1998/Math/MathML";function Dr(){console.warn("https://svelte.dev/e/derived_inert")}function Pr(){console.warn("https://svelte.dev/e/svelte_boundary_reset_noop")}function js(e){return e===this.v}function Or(e,t){return e!=e?t==t:e!==t||e!==null&&typeof e=="object"||typeof e=="function"}function Vs(e){return!Or(e,this.v)}let Y=null;function Gt(e){Y=e}function Ws(e,t=!1,n){Y={p:Y,i:!1,c:null,e:null,s:e,x:null,r:E,l:null}}function Js(e){var t=Y,n=t.e;if(n!==null){t.e=null;for(var i of n)ga(i)}return t.i=!0,Y=t.p,{}}function Qs(){return!0}let Et=[];function Br(){var e=Et;Et=[],ur(e)}function zt(e){if(Et.length===0){var t=Et;queueMicrotask(()=>{t===Et&&Br()})}Et.push(e)}function Ys(e){var t=E;if(t===null)return S.f|=Ze,e;if((t.f&Ut)===0&&(t.f&fn)===0)throw e;Je(e,t)}function Je(e,t){if(!(t!==null&&(t.f&me)!==0)){for(;t!==null;){if((t.f&ki)!==0){if((t.f&Ut)===0)throw e;try{t.b.error(e);return}catch(n){e=n}}t=t.parent}throw e}}const Hr=-7169;function F(e,t){e.f=e.f&Hr|t}function Hi(e){(e.f&ve)!==0||e.deps===null?F(e,I):F(e,De)}function Zs(e){if(e!==null)for(const t of e)(t.f&U)===0||(t.f&yt)===0||(t.f^=yt,Zs(t.deps))}function Xs(e,t,n){(e.f&q)!==0?t.add(e):(e.f&De)!==0&&n.add(e),Zs(e.deps),F(e,I)}function Rr(e){let t=0,n=_t(0),i;return()=>{Gi()&&(M(n),cl(()=>(t===0&&(i=La(()=>e(()=>on(n)))),t+=1,()=>{zt(()=>{t-=1,t===0&&(i==null||i(),i=void 0,on(n))})})))}}var Fr=It|jt;function Nr(e,t,n,i){new Ir(e,t,n,i)}var ue,Bi,fe,ut,Z,pe,W,re,He,ft,Ve,Pt,vn,mn,Re,Qn,H,Gr,Kr,qr,Si,Pn,On,$i,Ei;class Ir{constructor(t,n,i,s){_(this,H);se(this,"parent");se(this,"is_pending",!1);se(this,"transform_error");_(this,ue);_(this,Bi,null);_(this,fe);_(this,ut);_(this,Z);_(this,pe,null);_(this,W,null);_(this,re,null);_(this,He,null);_(this,ft,0);_(this,Ve,0);_(this,Pt,!1);_(this,vn,new Set);_(this,mn,new Set);_(this,Re,null);_(this,Qn,Rr(()=>(y(this,Re,_t(d(this,ft))),()=>{y(this,Re,null)})));var a;y(this,ue,t),y(this,fe,n),y(this,ut,r=>{var o=E;o.b=this,o.f|=ki,i(r)}),this.parent=E.b,this.transform_error=s??((a=this.parent)==null?void 0:a.transform_error)??(r=>r),y(this,Z,Ki(()=>{T(this,H,Si).call(this)},Fr))}defer_effect(t){Xs(t,d(this,vn),d(this,mn))}is_rendered(){return!this.is_pending&&(!this.parent||this.parent.is_rendered())}has_pending_snippet(){return!!d(this,fe).pending}update_pending_count(t,n){T(this,H,$i).call(this,t,n),y(this,ft,d(this,ft)+t),!(!d(this,Re)||d(this,Pt))&&(y(this,Pt,!0),zt(()=>{y(this,Pt,!1),d(this,Re)&&Kt(d(this,Re),d(this,ft))}))}get_effect_pending(){return d(this,Qn).call(this),M(d(this,Re))}error(t){if(!d(this,fe).onerror&&!d(this,fe).failed)throw t;x!=null&&x.is_fork?(d(this,pe)&&x.skip_effect(d(this,pe)),d(this,W)&&x.skip_effect(d(this,W)),d(this,re)&&x.skip_effect(d(this,re)),x.oncommit(()=>{T(this,H,Ei).call(this,t)})):T(this,H,Ei).call(this,t)}}ue=new WeakMap,Bi=new WeakMap,fe=new WeakMap,ut=new WeakMap,Z=new WeakMap,pe=new WeakMap,W=new WeakMap,re=new WeakMap,He=new WeakMap,ft=new WeakMap,Ve=new WeakMap,Pt=new WeakMap,vn=new WeakMap,mn=new WeakMap,Re=new WeakMap,Qn=new WeakMap,H=new WeakSet,Gr=function(){try{y(this,pe,he(()=>d(this,ut).call(this,d(this,ue))))}catch(t){this.error(t)}},Kr=function(t){const n=d(this,fe).failed;n&&y(this,re,he(()=>{n(d(this,ue),()=>t,()=>()=>{})}))},qr=function(){const t=d(this,fe).pending;t&&(this.is_pending=!0,y(this,W,he(()=>t(d(this,ue)))),zt(()=>{var n=y(this,He,document.createDocumentFragment()),i=gt();n.append(i),y(this,pe,T(this,H,On).call(this,()=>he(()=>d(this,ut).call(this,i)))),d(this,Ve)===0&&(d(this,ue).before(n),y(this,He,null),bt(d(this,W),()=>{y(this,W,null)}),T(this,H,Pn).call(this,x))}))},Si=function(){try{if(this.is_pending=this.has_pending_snippet(),y(this,Ve,0),y(this,ft,0),y(this,pe,he(()=>{d(this,ut).call(this,d(this,ue))})),d(this,Ve)>0){var t=y(this,He,document.createDocumentFragment());Ui(d(this,pe),t);const n=d(this,fe).pending;y(this,W,he(()=>n(d(this,ue))))}else T(this,H,Pn).call(this,x)}catch(n){this.error(n)}},Pn=function(t){this.is_pending=!1,t.transfer_effects(d(this,vn),d(this,mn))},On=function(t){var n=E,i=S,s=Y;Pe(d(this,Z)),ge(d(this,Z)),Gt(d(this,Z).ctx);try{return xt.ensure(),t()}catch(a){return Ys(a),null}finally{Pe(n),ge(i),Gt(s)}},$i=function(t,n){var i;if(!this.has_pending_snippet()){this.parent&&T(i=this.parent,H,$i).call(i,t,n);return}y(this,Ve,d(this,Ve)+t),d(this,Ve)===0&&(T(this,H,Pn).call(this,n),d(this,W)&&bt(d(this,W),()=>{y(this,W,null)}),d(this,He)&&(d(this,ue).before(d(this,He)),y(this,He,null)))},Ei=function(t){d(this,pe)&&(ie(d(this,pe)),y(this,pe,null)),d(this,W)&&(ie(d(this,W)),y(this,W,null)),d(this,re)&&(ie(d(this,re)),y(this,re,null));var n=d(this,fe).onerror;let i=d(this,fe).failed;var s=!1,a=!1;const r=()=>{if(s){Pr();return}s=!0,a&&Sr(),d(this,re)!==null&&bt(d(this,re),()=>{y(this,re,null)}),T(this,H,On).call(this,()=>{T(this,H,Si).call(this)})},o=c=>{try{a=!0,n==null||n(c,r),a=!1}catch(u){Je(u,d(this,Z)&&d(this,Z).parent)}i&&y(this,re,T(this,H,On).call(this,()=>{try{return he(()=>{var u=E;u.b=this,u.f|=ki,i(d(this,ue),()=>c,()=>r)})}catch(u){return Je(u,d(this,Z).parent),null}}))};zt(()=>{var c;try{c=this.transform_error(t)}catch(u){Je(u,d(this,Z)&&d(this,Z).parent);return}c!==null&&typeof c=="object"&&typeof c.then=="function"?c.then(o,u=>Je(u,d(this,Z)&&d(this,Z).parent)):o(c)})};function Ur(e,t,n,i){const s=Ri;var a=e.filter(m=>!m.settled),r=t.map(s);if(n.length===0&&a.length===0){i(r);return}var o=E,c=jr(),u=a.length===1?a[0].promise:a.length>1?Promise.all(a.map(m=>m.promise)):null;function p(m){if((o.f&me)===0){c();try{i([...r,...m])}catch(h){Je(h,o)}Kn()}}var v=ea();if(n.length===0){u.then(()=>p([])).finally(v);return}function f(){Promise.all(n.map(m=>Vr(m))).then(p).catch(m=>Je(m,o)).finally(v)}u?u.then(()=>{c(),f(),Kn()}):f()}function jr(){var e=E,t=S,n=Y,i=x;return function(a=!0){Pe(e),ge(t),Gt(n),a&&(e.f&me)===0&&(i==null||i.activate(),i==null||i.apply())}}function Kn(e=!0){Pe(null),ge(null),Gt(null),e&&(x==null||x.deactivate())}function ea(){var e=E,t=e.b,n=x,i=!!(t!=null&&t.is_rendered());return t==null||t.update_pending_count(1,n),n.increment(i,e),()=>{t==null||t.update_pending_count(-1,n),n.decrement(i,e)}}function Ri(e){var t=U|q;return E!==null&&(E.f|=jt),{ctx:Y,deps:null,effects:null,equals:js,f:t,fn:e,reactions:null,rv:0,v:N,wv:0,parent:E,ac:null}}const Zt=Symbol("obsolete");function Vr(e,t,n){let i=E;i===null&&mr();var s=void 0,a=_t(N),r=!S,o=new Set;return dl(()=>{var m,h;var c=E,u=Ks();s=u.promise;try{Promise.resolve(e()).then(u.resolve,b=>{b!==ti&&u.reject(b)}).finally(Kn)}catch(b){u.reject(b),Kn()}var p=x;if(r){if((c.f&Ut)!==0)var v=ea();if((m=i.b)!=null&&m.is_rendered())(h=p.async_deriveds.get(c))==null||h.reject(Zt);else for(const b of o.values())b.reject(Zt);o.add(u),p.async_deriveds.set(c,u)}const f=(b,C=void 0)=>{v==null||v(),o.delete(u),C!==Zt&&(p.activate(),C?(a.f|=Ze,Kt(a,C)):((a.f&Ze)!==0&&(a.f^=Ze),Kt(a,b)),p.deactivate())};u.promise.then(f,b=>f(null,b||"unknown"))}),rl(()=>{for(const c of o)c.reject(Zt)}),new Promise(c=>{function u(p){function v(){p===s?c(a):u(s)}p.then(v,v)}u(s)})}function rs(e){const t=Ri(e);return ka(t),t}function Wr(e){const t=Ri(e);return t.equals=Vs,t}function Jr(e){var t=e.effects;if(t!==null){e.effects=null;for(var n=0;n<t.length;n+=1)ie(t[n])}}function Fi(e){var t,n=E,i=e.parent;if(!tt&&i!==null&&e.v!==N&&(i.f&(me|Q))!==0)return Dr(),e.v;Pe(i);try{e.f&=~yt,Jr(e),t=Ea(e)}finally{Pe(n)}return t}function ta(e){var t=Fi(e);if(!e.equals(t)&&(e.wv=Sa(),(!(x!=null&&x.is_fork)||e.deps===null)&&(x!==null?(x.capture(e,t,!0),ln==null||ln.capture(e,t,!0)):e.v=t,e.deps===null))){F(e,I);return}tt||(K!==null?(Gi()||x!=null&&x.is_fork)&&K.set(e,t):Hi(e))}function Qr(e){var t,n;if(e.effects!==null)for(const i of e.effects)(i.teardown||i.ac)&&((t=i.teardown)==null||t.call(i),(n=i.ac)==null||n.abort(ti),i.fn!==null&&(i.teardown=cr),i.ac=null,pn(i,0),qi(i))}function na(e){if(e.effects!==null)for(const t of e.effects)t.teardown&&t.fn!==null&&qt(t)}let ci=null,kt=null,x=null,ln=null,K=null,Ti=null,ui=!1,Tt=null,Bn=null;var ls=0;let Yr=1;var Ot,We,pt,Bt,Ht,Rt,Fe,Ft,X,gn,Ne,xe,Te,Nt,ht,P,Ci,Xt,Li,ia,sa,$t,Zr,en;const Yn=class Yn{constructor(){_(this,P);se(this,"id",Yr++);_(this,Ot,!1);se(this,"linked",!0);_(this,We,null);_(this,pt,null);se(this,"async_deriveds",new Map);se(this,"current",new Map);se(this,"previous",new Map);_(this,Bt,new Set);_(this,Ht,new Set);_(this,Rt,0);_(this,Fe,new Map);_(this,Ft,null);_(this,X,[]);_(this,gn,[]);_(this,Ne,new Set);_(this,xe,new Set);_(this,Te,new Map);_(this,Nt,new Set);se(this,"is_fork",!1);_(this,ht,!1);kt===null?ci=kt=this:(y(kt,pt,this),y(this,We,kt)),kt=this}skip_effect(t){d(this,Te).has(t)||d(this,Te).set(t,{d:[],m:[]}),d(this,Nt).delete(t)}unskip_effect(t,n=i=>this.schedule(i)){var i=d(this,Te).get(t);if(i){d(this,Te).delete(t);for(var s of i.d)F(s,q),n(s);for(s of i.m)F(s,De),n(s)}d(this,Nt).add(t)}capture(t,n,i=!1){t.v!==N&&!this.previous.has(t)&&this.previous.set(t,t.v),(t.f&Ze)===0&&(this.current.set(t,[n,i]),K==null||K.set(t,n)),this.is_fork||(t.v=n)}activate(){x=this}deactivate(){x=null,K=null}flush(){try{ui=!0,x=this,T(this,P,Xt).call(this)}finally{ls=0,Ti=null,Tt=null,Bn=null,ui=!1,x=null,K=null,mt.clear()}}discard(){var t;for(const n of d(this,Ht))n(this);d(this,Ht).clear();for(const n of this.async_deriveds.values())n.reject(Zt);T(this,P,en).call(this),(t=d(this,Ft))==null||t.resolve()}register_created_effect(t){d(this,gn).push(t)}increment(t,n){if(y(this,Rt,d(this,Rt)+1),t){let i=d(this,Fe).get(n)??0;d(this,Fe).set(n,i+1)}}decrement(t,n){if(y(this,Rt,d(this,Rt)-1),t){let i=d(this,Fe).get(n)??0;i===1?d(this,Fe).delete(n):d(this,Fe).set(n,i-1)}d(this,ht)||(y(this,ht,!0),zt(()=>{y(this,ht,!1),this.linked&&this.flush()}))}transfer_effects(t,n){for(const i of t)d(this,Ne).add(i);for(const i of n)d(this,xe).add(i);t.clear(),n.clear()}oncommit(t){d(this,Bt).add(t)}ondiscard(t){d(this,Ht).add(t)}settled(){return(d(this,Ft)??y(this,Ft,Ks())).promise}static ensure(){if(x===null){const t=x=new Yn;ui||zt(()=>{d(t,Ot)||t.flush()})}return x}apply(){{K=null;return}}schedule(t){var s;if(Ti=t,(s=t.b)!=null&&s.is_pending&&(t.f&(fn|ei|qs))!==0&&(t.f&Ut)===0){t.b.defer_effect(t);return}for(var n=t;n.parent!==null;){n=n.parent;var i=n.f;if(Tt!==null&&n===E&&(S===null||(S.f&U)===0))return;if((i&(et|$e))!==0){if((i&I)===0)return;n.f^=I}}d(this,X).push(n)}};Ot=new WeakMap,We=new WeakMap,pt=new WeakMap,Bt=new WeakMap,Ht=new WeakMap,Rt=new WeakMap,Fe=new WeakMap,Ft=new WeakMap,X=new WeakMap,gn=new WeakMap,Ne=new WeakMap,xe=new WeakMap,Te=new WeakMap,Nt=new WeakMap,ht=new WeakMap,P=new WeakSet,Ci=function(){if(this.is_fork)return!0;for(const i of d(this,Fe).keys()){for(var t=i,n=!1;t.parent!==null;){if(d(this,Te).has(t)){n=!0;break}t=t.parent}if(!n)return!0}return!1},Xt=function(){var c,u,p,v;y(this,Ot,!0),ls++>1e3&&(T(this,P,en).call(this),Xr());for(const f of d(this,Ne))d(this,xe).delete(f),F(f,q),this.schedule(f);for(const f of d(this,xe))F(f,De),this.schedule(f);const t=d(this,X);y(this,X,[]),this.apply();var n=Tt=[],i=[],s=Bn=[];for(const f of t)try{T(this,P,Li).call(this,f,n,i)}catch(m){throw la(f),T(this,P,Ci).call(this)||this.discard(),m}if(x=null,s.length>0){var a=Yn.ensure();for(const f of s)a.schedule(f)}if(Tt=null,Bn=null,T(this,P,Ci).call(this)){T(this,P,$t).call(this,i),T(this,P,$t).call(this,n);for(const[f,m]of d(this,Te))ra(f,m);s.length>0&&T(c=x,P,Xt).call(c);return}const r=T(this,P,ia).call(this);if(r){T(this,P,$t).call(this,i),T(this,P,$t).call(this,n),T(u=r,P,sa).call(u,this);return}d(this,Ne).clear(),d(this,xe).clear();for(const f of d(this,Bt))f(this);d(this,Bt).clear(),ln=this,os(i),os(n),ln=null,(p=d(this,Ft))==null||p.resolve();var o=x;if(d(this,Rt)===0&&(d(this,X).length===0||o!==null)&&T(this,P,en).call(this),d(this,X).length>0)if(o!==null){const f=o;d(f,X).push(...d(this,X).filter(m=>!d(f,X).includes(m)))}else o=this;o!==null&&T(v=o,P,Xt).call(v)},Li=function(t,n,i){t.f^=I;for(var s=t.first;s!==null;){var a=s.f,r=(a&($e|et))!==0,o=r&&(a&I)!==0,c=o||(a&Q)!==0||d(this,Te).has(s);if(!c&&s.fn!==null){r?s.f^=I:(a&fn)!==0?n.push(s):xn(s)&&((a&ke)!==0&&d(this,xe).add(s),qt(s));var u=s.first;if(u!==null){s=u;continue}}for(;s!==null;){var p=s.next;if(p!==null){s=p;break}s=s.parent}}},ia=function(){for(var t=d(this,We);t!==null;){if(!t.is_fork){for(const[n,[,i]]of this.current)if(t.current.has(n)&&!i)return t}t=d(t,We)}return null},sa=function(t){var i;for(const[s,a]of t.current)!this.previous.has(s)&&t.previous.has(s)&&this.previous.set(s,t.previous.get(s)),this.current.set(s,a);for(const[s,a]of t.async_deriveds){const r=this.async_deriveds.get(s);r&&a.promise.then(r.resolve).catch(r.reject)}t.async_deriveds.clear(),this.transfer_effects(d(t,Ne),d(t,xe));const n=s=>{var a=s.reactions;if(a!==null)for(const c of a){var r=c.f;if((r&U)!==0)n(c);else{var o=c;r&(At|ke)&&!this.async_deriveds.has(o)&&(d(this,xe).delete(o),F(o,q),this.schedule(o))}}};for(const s of this.current.keys())n(s);this.oncommit(()=>t.discard()),T(i=t,P,en).call(i),x=this,T(this,P,Xt).call(this)},$t=function(t){for(var n=0;n<t.length;n+=1)Xs(t[n],d(this,Ne),d(this,xe))},Zr=function(){var v;for(let f=ci;f!==null;f=d(f,pt)){var t=f.id<this.id,n=[];for(const[m,[h,b]]of this.current){if(f.current.has(m)){var i=f.current.get(m)[0];if(t&&h!==i)f.current.set(m,[h,b]);else continue}n.push(m)}if(t)for(const[m,h]of this.async_deriveds){const b=f.async_deriveds.get(m);b&&h.promise.then(b.resolve).catch(b.reject)}var s=[...f.current.keys()].filter(m=>!f.current.get(m)[1]);if(!(!d(f,Ot)||s.length===0)){var a=s.filter(m=>!this.current.has(m));if(a.length===0)t&&f.discard();else if(n.length>0){if(t)for(const m of d(this,Nt))f.unskip_effect(m,h=>{var b;(h.f&(ke|At))!==0?f.schedule(h):T(b=f,P,$t).call(b,[h])});f.activate();var r=new Set,o=new Map;for(var c of n)aa(c,a,r,o);o=new Map;var u=[...f.current].filter(([m,h])=>{const b=this.current.get(m);return b?b[0]!==h[0]||b[1]!==h[1]:!0}).map(([m])=>m);if(u.length>0)for(const m of d(this,gn))(m.f&(me|Q|In))===0&&Ni(m,u,o)&&((m.f&(At|ke))!==0?(F(m,q),f.schedule(m)):d(f,Ne).add(m));if(d(f,X).length>0&&!d(f,ht)){f.apply();for(var p of d(f,X))T(v=f,P,Li).call(v,p,[],[]);y(f,X,[])}f.deactivate()}}}},en=function(){if(this.linked){var t=d(this,We),n=d(this,pt);t===null?ci=n:y(t,pt,n),n===null?kt=t:y(n,We,t),this.linked=!1}};let xt=Yn;function Xr(){try{xr()}catch(e){Je(e,Ti)}}let we=null;function os(e){var t=e.length;if(t!==0){for(var n=0;n<t;){var i=e[n++];if((i.f&(me|Q))===0&&xn(i)&&(we=new Set,qt(i),i.deps===null&&i.first===null&&i.nodes===null&&i.teardown===null&&i.ac===null&&ya(i),(we==null?void 0:we.size)>0)){mt.clear();for(const s of we){if((s.f&(me|Q))!==0)continue;const a=[s];let r=s.parent;for(;r!==null;)we.has(r)&&(we.delete(r),a.push(r)),r=r.parent;for(let o=a.length-1;o>=0;o--){const c=a[o];(c.f&(me|Q))===0&&qt(c)}}we.clear()}}we=null}}function aa(e,t,n,i){if(!n.has(e)&&(n.add(e),e.reactions!==null))for(const s of e.reactions){const a=s.f;(a&U)!==0?aa(s,t,n,i):(a&(At|ke))!==0&&(a&q)===0&&Ni(s,t,i)&&(F(s,q),Ii(s))}}function Ni(e,t,n){const i=n.get(e);if(i!==void 0)return i;if(e.deps!==null)for(const s of e.deps){if(Nn.call(t,s))return!0;if((s.f&U)!==0&&Ni(s,t,n))return n.set(s,!0),!0}return n.set(e,!1),!1}function Ii(e){x.schedule(e)}function ra(e,t){if(!((e.f&$e)!==0&&(e.f&I)!==0)){(e.f&q)!==0?t.d.push(e):(e.f&De)!==0&&t.m.push(e),F(e,I);for(var n=e.first;n!==null;)ra(n,t),n=n.next}}function la(e){F(e,I);for(var t=e.first;t!==null;)la(t),t=t.next}let qn=new Set;const mt=new Map;let oa=!1;function _t(e,t){var n={f:0,v:e,reactions:null,equals:js,rv:0,wv:0};return n}function ee(e,t){const n=_t(e);return ka(n),n}function el(e,t=!1,n=!0){const i=_t(e);return t||(i.equals=Vs),i}function V(e,t,n=!1){S!==null&&(!Me||(S.f&In)!==0)&&Qs()&&(S.f&(U|ke|At|In))!==0&&(ze===null||!ze.has(e))&&Mr();let i=n?Qe(t):t;return Kt(e,i,Bn)}function Kt(e,t,n=null){if(!e.equals(t)){mt.set(e,tt?t:e.v);var i=xt.ensure();if(i.capture(e,t),(e.f&U)!==0){const s=e;(e.f&q)!==0&&Fi(s),K===null&&Hi(s)}e.wv=Sa(),da(e,q,n),E!==null&&(E.f&I)!==0&&(E.f&($e|et))===0&&(de===null?fl([e]):de.push(e)),!i.is_fork&&qn.size>0&&!oa&&tl()}return t}function tl(){oa=!1;for(const e of qn){(e.f&I)!==0&&F(e,De);let t;try{t=xn(e)}catch{t=!0}t&&qt(e)}qn.clear()}function on(e){V(e,e.v+1)}function da(e,t,n){var i=e.reactions;if(i!==null)for(var s=i.length,a=0;a<s;a++){var r=i[a],o=r.f,c=(o&q)===0;if(c&&F(r,t),(o&In)!==0)qn.add(r);else if((o&U)!==0){var u=r;K==null||K.delete(u),(o&yt)===0&&(o&ve&&(E===null||(E.f&Gn)===0)&&(r.f|=yt),da(u,De,n))}else if(c){var p=r;(o&ke)!==0&&we!==null&&we.add(p),n!==null?n.push(p):Ii(p)}}}function Qe(e){if(typeof e!="object"||e===null||di in e)return e;const t=Gs(e);if(t!==or&&t!==dr)return e;var n=new Map,i=Is(e),s=ee(0),a=wt,r=o=>{if(wt===a)return o();var c=S,u=wt;ge(null),us(a);var p=o();return ge(c),us(u),p};return i&&n.set("length",ee(e.length)),new Proxy(e,{defineProperty(o,c,u){(!("value"in u)||u.configurable===!1||u.enumerable===!1||u.writable===!1)&&_r();var p=n.get(c);return p===void 0?r(()=>{var v=ee(u.value);return n.set(c,v),v}):V(p,u.value,!0),!0},deleteProperty(o,c){var u=n.get(c);if(u===void 0){if(c in o){const p=r(()=>ee(N));n.set(c,p),on(s)}}else V(u,N),on(s);return!0},get(o,c,u){var m;if(c===di)return e;var p=n.get(c),v=c in o;if(p===void 0&&(!v||(m=rn(o,c))!=null&&m.writable)&&(p=r(()=>{var h=Qe(v?o[c]:N),b=ee(h);return b}),n.set(c,p)),p!==void 0){var f=M(p);return f===N?void 0:f}return Reflect.get(o,c,u)},getOwnPropertyDescriptor(o,c){var u=Reflect.getOwnPropertyDescriptor(o,c);if(u&&"value"in u){var p=n.get(c);p&&(u.value=M(p))}else if(u===void 0){var v=n.get(c),f=v==null?void 0:v.v;if(v!==void 0&&f!==N)return{enumerable:!0,configurable:!0,value:f,writable:!0}}return u},has(o,c){var f;if(c===di)return!0;var u=n.get(c),p=u!==void 0&&u.v!==N||Reflect.has(o,c);if(u!==void 0||E!==null&&(!p||(f=rn(o,c))!=null&&f.writable)){u===void 0&&(u=r(()=>{var m=p?Qe(o[c]):N,h=ee(m);return h}),n.set(c,u));var v=M(u);if(v===N)return!1}return p},set(o,c,u,p){var j;var v=n.get(c),f=c in o;if(i&&c==="length")for(var m=u;m<v.v;m+=1){var h=n.get(m+"");h!==void 0?V(h,N):m in o&&(h=r(()=>ee(N)),n.set(m+"",h))}if(v===void 0)(!f||(j=rn(o,c))!=null&&j.writable)&&(v=r(()=>ee(void 0)),V(v,Qe(u)),n.set(c,v));else{f=v.v!==N;var b=r(()=>Qe(u));V(v,b)}var C=Reflect.getOwnPropertyDescriptor(o,c);if(C!=null&&C.set&&C.set.call(p,u),!f){if(i&&typeof c=="string"){var L=n.get("length"),z=Number(c);Number.isInteger(z)&&z>=L.v&&V(L,z+1)}on(s)}return!0},ownKeys(o){M(s);var c=Reflect.ownKeys(o).filter(v=>{var f=n.get(v);return f===void 0||f.v!==N});for(var[u,p]of n)p.v!==N&&!(u in o)&&c.push(u);return c},setPrototypeOf(){kr()}})}var ds,ca,ua,fa;function nl(){if(ds===void 0){ds=window,ca=/Firefox/.test(navigator.userAgent);var e=Element.prototype,t=Node.prototype,n=Text.prototype;ua=rn(t,"firstChild").get,fa=rn(t,"nextSibling").get,is(e)&&(e[Mi]=void 0,e[Dn]=null,e[hr]=void 0,e.__e=void 0),is(n)&&(n[Yt]=void 0)}}function gt(e=""){return document.createTextNode(e)}function Ye(e){return ua.call(e)}function yn(e){return fa.call(e)}function R(e,t){return Ye(e)}function pa(e,t=!1){{var n=Ye(e);return n instanceof Comment&&n.data===""?yn(n):n}}function J(e,t=1,n=!1){let i=e;for(;t--;)i=yn(i);return i}function il(e){e.textContent=""}function ha(){return!1}function va(e,t,n){return t==null||t===Us?n?document.createElement(e,{is:n}):document.createElement(e):n?document.createElementNS(t,e,{is:n}):document.createElementNS(t,e)}function ma(e){var t=S,n=E;ge(null),Pe(null);try{return e()}finally{ge(t),Pe(n)}}function sl(e){E===null&&(S===null&&yr(),wr()),tt&&br()}function al(e,t){var n=t.last;n===null?t.last=t.first=e:(n.next=e,e.prev=n,t.last=e)}function it(e,t){var n=E;n!==null&&(n.f&Q)!==0&&(e|=Q);var i={ctx:Y,deps:null,nodes:null,f:e|q|ve,first:null,fn:t,last:null,next:null,parent:n,b:n&&n.b,prev:null,teardown:null,wv:0,ac:null};x==null||x.register_created_effect(i);var s=i;if((e&fn)!==0)Tt!==null?Tt.push(i):xt.ensure().schedule(i);else if(t!==null){try{qt(i)}catch(r){throw ie(i),r}s.deps===null&&s.teardown===null&&s.nodes===null&&s.first===s.last&&(s.f&jt)===0&&(s=s.first,(e&ke)!==0&&(e&It)!==0&&s!==null&&(s.f|=It))}if(s!==null&&(s.parent=n,n!==null&&al(s,n),S!==null&&(S.f&U)!==0&&(e&et)===0)){var a=S;(a.effects??(a.effects=[])).push(s)}return i}function Gi(){return S!==null&&!Me}function rl(e){const t=it(ei,null);return F(t,I),t.teardown=e,t}function ll(e){sl();var t=E.f,n=!S&&(t&$e)!==0&&Y!==null&&!Y.i;if(n){var i=Y;(i.e??(i.e=[])).push(e)}else return ga(e)}function ga(e){return it(fn|pr,e)}function ol(e){xt.ensure();const t=it(et|jt,e);return(n={})=>new Promise(i=>{n.outro?bt(t,()=>{ie(t),i(void 0)}):(ie(t),i(void 0))})}function dl(e){return it(At|jt,e)}function cl(e,t=0){return it(ei|t,e)}function ot(e,t=[],n=[],i=[]){Ur(i,t,n,s=>{it(ei,()=>{e(...s.map(M))})})}function Ki(e,t=0){var n=it(ke|t,e);return n}function he(e){return it($e|jt,e)}function ba(e){var t=e.teardown;if(t!==null){const n=tt,i=S;cs(!0),ge(null);try{t.call(null)}finally{cs(n),ge(i)}}}function qi(e,t=!1){var n=e.first;for(e.first=e.last=null;n!==null;){const s=n.ac;s!==null&&ma(()=>{s.abort(ti)});var i=n.next;(n.f&et)!==0?n.parent=null:ie(n,t),n=i}}function ul(e){for(var t=e.first;t!==null;){var n=t.next;(t.f&$e)===0&&ie(t),t=n}}function ie(e,t=!0){var n=!1;(t||(e.f&fr)!==0)&&e.nodes!==null&&e.nodes.end!==null&&(wa(e.nodes.start,e.nodes.end),n=!0),e.f|=as,qi(e,t&&!n),pn(e,0);var i=e.nodes&&e.nodes.t;if(i!==null)for(const a of i)a.stop();ba(e),e.f^=as,e.f|=me;var s=e.parent;s!==null&&s.first!==null&&ya(e),e.next=e.prev=e.teardown=e.ctx=e.deps=e.fn=e.nodes=e.ac=e.b=null}function wa(e,t){for(;e!==null;){var n=e===t?null:yn(e);e.remove(),e=n}}function ya(e){var t=e.parent,n=e.prev,i=e.next;n!==null&&(n.next=i),i!==null&&(i.prev=n),t!==null&&(t.first===e&&(t.first=i),t.last===e&&(t.last=n))}function bt(e,t,n=!0){var i=[];xa(e,i,!0);var s=()=>{n&&ie(e),t&&t()},a=i.length;if(a>0){var r=()=>--a||s();for(var o of i)o.out(r)}else s()}function xa(e,t,n){if((e.f&Q)===0){e.f^=Q;var i=e.nodes&&e.nodes.t;if(i!==null)for(const o of i)(o.is_global||n)&&t.push(o);for(var s=e.first;s!==null;){var a=s.next;if((s.f&et)===0){var r=(s.f&It)!==0||(s.f&$e)!==0&&(e.f&ke)!==0;xa(s,t,r?n:!1)}s=a}}}function Un(e){_a(e,!0)}function _a(e,t){if((e.f&Q)!==0){e.f^=Q,(e.f&I)===0&&(F(e,q),xt.ensure().schedule(e));for(var n=e.first;n!==null;){var i=n.next,s=(n.f&It)!==0||(n.f&$e)!==0;_a(n,s?t:!1),n=i}var a=e.nodes&&e.nodes.t;if(a!==null)for(const r of a)(r.is_global||t)&&r.in()}}function Ui(e,t){if(e.nodes)for(var n=e.nodes.start,i=e.nodes.end;n!==null;){var s=n===i?null:yn(n);t.append(n),n=s}}let Hn=!1,tt=!1;function cs(e){tt=e}let S=null,Me=!1;function ge(e){S=e}let E=null;function Pe(e){E=e}let ze=null;function ka(e){S!==null&&(ze??(ze=new Set)).add(e)}let te=null,ae=0,de=null;function fl(e){de=e}let Ma=1,dt=0,wt=dt;function us(e){wt=e}function Sa(){return++Ma}function xn(e){var t=e.f;if((t&q)!==0)return!0;if(t&U&&(e.f&=~yt),(t&De)!==0){for(var n=e.deps,i=n.length,s=0;s<i;s++){var a=n[s];if(xn(a)&&ta(a),a.wv>e.wv)return!0}(t&ve)!==0&&K===null&&F(e,I)}return!1}function $a(e,t,n=!0){var i=e.reactions;if(i!==null&&!(ze!==null&&ze.has(e)))for(var s=0;s<i.length;s++){var a=i[s];(a.f&U)!==0?$a(a,t,!1):t===a&&(n?F(a,q):(a.f&I)!==0&&F(a,De),Ii(a))}}function Ea(e){var b;var t=te,n=ae,i=de,s=S,a=ze,r=Y,o=Me,c=wt,u=e.f;te=null,ae=0,de=null,S=(u&($e|et))===0?e:null,ze=null,Gt(e.ctx),Me=!1,wt=++dt,e.ac!==null&&(ma(()=>{e.ac.abort(ti)}),e.ac=null);try{e.f|=Gn;var p=e.fn,v=p();e.f|=Ut;var f=e.deps,m=x==null?void 0:x.is_fork;if(te!==null){var h;if(m||pn(e,ae),f!==null&&ae>0)for(f.length=ae+te.length,h=0;h<te.length;h++)f[ae+h]=te[h];else e.deps=f=te;if(Gi()&&(e.f&ve)!==0)for(h=ae;h<f.length;h++)((b=f[h]).reactions??(b.reactions=[])).push(e)}else!m&&f!==null&&ae<f.length&&(pn(e,ae),f.length=ae);if(Qs()&&de!==null&&!Me&&f!==null&&(e.f&(U|De|q))===0)for(h=0;h<de.length;h++)$a(de[h],e);if(s!==null&&s!==e){if(dt++,s.deps!==null)for(let C=0;C<n;C+=1)s.deps[C].rv=dt;if(t!==null)for(const C of t)C.rv=dt;de!==null&&(i===null?i=de:i.push(...de))}return(e.f&Ze)!==0&&(e.f^=Ze),v}catch(C){return Ys(C)}finally{e.f^=Gn,te=t,ae=n,de=i,S=s,ze=a,Gt(r),Me=o,wt=c}}function pl(e,t){let n=t.reactions;if(n!==null){var i=ar.call(n,e);if(i!==-1){var s=n.length-1;s===0?n=t.reactions=null:(n[i]=n[s],n.pop())}}if(n===null&&(t.f&U)!==0&&(te===null||!Nn.call(te,t))){var a=t;(a.f&ve)!==0&&(a.f^=ve,a.f&=~yt),a.v!==N&&Hi(a),Qr(a),pn(a,0)}}function pn(e,t){var n=e.deps;if(n!==null)for(var i=t;i<n.length;i++)pl(e,n[i])}function qt(e){var t=e.f;if((t&me)===0){F(e,I);var n=E,i=Hn;E=e,Hn=!0;try{(t&(ke|qs))!==0?ul(e):qi(e),ba(e);var s=Ea(e);e.teardown=typeof s=="function"?s:null,e.wv=Ma;var a}finally{Hn=i,E=n}}}function M(e){var t=e.f,n=(t&U)!==0;if(S!==null&&!Me){var i=E!==null&&(E.f&me)!==0;if(!i&&(ze===null||!ze.has(e))){var s=S.deps;if((S.f&Gn)!==0)e.rv<dt&&(e.rv=dt,te===null&&s!==null&&s[ae]===e?ae++:te===null?te=[e]:te.push(e));else{S.deps??(S.deps=[]),Nn.call(S.deps,e)||S.deps.push(e);var a=e.reactions;a===null?e.reactions=[S]:Nn.call(a,S)||a.push(S)}}}if(tt&&mt.has(e))return mt.get(e);if(n){var r=e;if(tt){var o=r.v;return((r.f&I)===0&&r.reactions!==null||Ca(r))&&(o=Fi(r)),mt.set(r,o),o}var c=(r.f&ve)===0&&!Me&&S!==null&&(Hn||(S.f&ve)!==0),u=(r.f&Ut)===0;xn(r)&&(c&&(r.f|=ve),ta(r)),c&&!u&&(na(r),Ta(r))}if(K!=null&&K.has(e))return K.get(e);if((e.f&Ze)!==0)throw e.v;return e.v}function Ta(e){if(e.f|=ve,e.deps!==null)for(const t of e.deps)(t.reactions??(t.reactions=[])).push(e),(t.f&U)!==0&&(t.f&ve)===0&&(na(t),Ta(t))}function Ca(e){if(e.v===N)return!0;if(e.deps===null)return!1;for(const t of e.deps)if(mt.has(t)||(t.f&U)!==0&&Ca(t))return!0;return!1}function La(e){var t=Me;try{return Me=!0,e()}finally{Me=t}}const hl=["touchstart","touchmove"];function vl(e){return hl.includes(e)}const ct=Symbol("events"),Aa=new Set,Ai=new Set;function fs(e,t,n){(t[ct]??(t[ct]={}))[e]=n}function ml(e){for(var t=0;t<e.length;t++)Aa.add(e[t]);for(var n of Ai)n(e)}let ps=null;function hs(e){var b,C;var t=this,n=t.ownerDocument,i=e.type,s=((b=e.composedPath)==null?void 0:b.call(e))||[],a=s[0]||e.target;ps=e;var r=0,o=ps===e&&e[ct];if(o){var c=s.indexOf(o);if(c!==-1&&(t===document||t===window)){e[ct]=t;return}var u=s.indexOf(t);if(u===-1)return;c<=u&&(r=c)}if(a=s[r]||e.target,a!==t){rr(e,"currentTarget",{configurable:!0,get(){return a||n}});var p=S,v=E;ge(null),Pe(null);try{for(var f,m=[];a!==null&&a!==t;){try{var h=(C=a[ct])==null?void 0:C[i];h!=null&&(!a.disabled||e.target===a)&&h.call(a,e)}catch(L){f?m.push(L):f=L}if(e.cancelBubble)break;r++,a=r<s.length?s[r]:null}if(f){for(let L of m)queueMicrotask(()=>{throw L});throw f}}finally{e[ct]=t,delete e.currentTarget,ge(p),Pe(v)}}}var Rs;const fi=((Rs=globalThis==null?void 0:globalThis.window)==null?void 0:Rs.trustedTypes)&&globalThis.window.trustedTypes.createPolicy("svelte-trusted-html",{createHTML:e=>e});function gl(e){return(fi==null?void 0:fi.createHTML(e))??e}function bl(e){var t=va("template");return t.innerHTML=gl(e.replaceAll("<!>","<!---->")),t.content}function jn(e,t){var n=E;n.nodes===null&&(n.nodes={start:e,end:t,a:null,t:null})}function be(e,t){var n=(t&Cr)!==0,i=(t&Lr)!==0,s,a=!e.startsWith("<!>");return()=>{s===void 0&&(s=bl(a?e:"<!>"+e),n||(s=Ye(s)));var r=i||ca?document.importNode(s,!0):s.cloneNode(!0);if(n){var o=Ye(r),c=r.lastChild;jn(o,c)}else jn(r,r);return r}}function ce(e,t){e!==null&&e.before(t)}function ye(e,t){var n=t==null?"":typeof t=="object"?`${t}`:t;n!==(e[Yt]??(e[Yt]=e.nodeValue))&&(e[Yt]=n,e.nodeValue=`${n}`)}function za(e,t){return wl(e,t)}const An=new Map;function wl(e,{target:t,anchor:n,props:i={},events:s,context:a,intro:r=!0,transformError:o}){nl();var c=void 0,u=ol(()=>{var p=n??t.appendChild(gt());Nr(p,{pending:()=>{}},m=>{Ws({});var h=Y;a&&(h.c=a),s&&(i.$$events=s),c=e(m,i)||{},Js()},o);var v=new Set,f=m=>{for(var h=0;h<m.length;h++){var b=m[h];if(!v.has(b)){v.add(b);var C=vl(b);for(const j of[t,document]){var L=An.get(j);L===void 0&&(L=new Map,An.set(j,L));var z=L.get(b);z===void 0?(j.addEventListener(b,hs,{passive:C}),L.set(b,1)):L.set(b,z+1)}}}};return f(Xn(Aa)),Ai.add(f),()=>{var C;for(var m of v)for(const L of[t,document]){var h=An.get(L),b=h.get(m);--b==0?(L.removeEventListener(m,hs),h.delete(m),h.size===0&&An.delete(L)):h.set(m,b)}Ai.delete(f),p!==n&&((C=p.parentNode)==null||C.removeChild(p))}});return yl.set(c,u),c}let yl=new WeakMap;var _e,Ce,le,vt,bn,wn,Zn;class xl{constructor(t,n=!0){se(this,"anchor");_(this,_e,new Map);_(this,Ce,new Map);_(this,le,new Map);_(this,vt,new Set);_(this,bn,!0);_(this,wn,t=>{if(d(this,_e).has(t)){var n=d(this,_e).get(t),i=d(this,Ce).get(n);if(i)Un(i),d(this,vt).delete(n);else{var s=d(this,le).get(n);s&&(Un(s.effect),d(this,Ce).set(n,s.effect),d(this,le).delete(n),s.fragment.lastChild.remove(),this.anchor.before(s.fragment),i=s.effect)}for(const[a,r]of d(this,_e)){if(d(this,_e).delete(a),a===t)break;const o=d(this,le).get(r);o&&(ie(o.effect),d(this,le).delete(r))}for(const[a,r]of d(this,Ce)){if(a===n||d(this,vt).has(a))continue;const o=()=>{if(Array.from(d(this,_e).values()).includes(a)){var u=document.createDocumentFragment();Ui(r,u),u.append(gt()),d(this,le).set(a,{effect:r,fragment:u})}else ie(r);d(this,vt).delete(a),d(this,Ce).delete(a)};d(this,bn)||!i?(d(this,vt).add(a),bt(r,o,!1)):o()}}});_(this,Zn,t=>{d(this,_e).delete(t);const n=Array.from(d(this,_e).values());for(const[i,s]of d(this,le))n.includes(i)||(ie(s.effect),d(this,le).delete(i))});this.anchor=t,y(this,bn,n)}ensure(t,n){var i=x,s=ha();if(n&&!d(this,Ce).has(t)&&!d(this,le).has(t))if(s){var a=document.createDocumentFragment(),r=gt();a.append(r),d(this,le).set(t,{effect:he(()=>n(r)),fragment:a})}else d(this,Ce).set(t,he(()=>n(this.anchor)));if(d(this,_e).set(i,t),s){for(const[o,c]of d(this,Ce))o===t?i.unskip_effect(c):i.skip_effect(c);for(const[o,c]of d(this,le))o===t?i.unskip_effect(c.effect):i.skip_effect(c.effect);i.oncommit(d(this,wn)),i.ondiscard(d(this,Zn))}else d(this,wn).call(this,i)}}_e=new WeakMap,Ce=new WeakMap,le=new WeakMap,vt=new WeakMap,bn=new WeakMap,wn=new WeakMap,Zn=new WeakMap;function pi(e,t,n=!1){var i=new xl(e),s=n?It:0;function a(r,o){i.ensure(r,o)}Ki(()=>{var r=!1;t((o,c=0)=>{r=!0,a(c,o)}),r||a(-1,null)},s)}function Vn(e,t){return t}function _l(e,t,n){for(var i=[],s=t.length,a,r=t.length,o=0;o<s;o++){let v=t[o];bt(v,()=>{if(a){if(a.pending.delete(v),a.done.add(v),a.pending.size===0){var f=e.outrogroups;zi(e,Xn(a.done)),f.delete(a),f.size===0&&(e.outrogroups=null)}}else r-=1},!1)}if(r===0){var c=i.length===0&&n!==null;if(c){var u=n,p=u.parentNode;il(p),p.append(u),e.items.clear()}zi(e,t,!c)}else a={pending:new Set(t),done:new Set},(e.outrogroups??(e.outrogroups=new Set)).add(a)}function zi(e,t,n=!0){var i;if(e.pending.size>0){i=new Set;for(const r of e.pending.values())for(const o of r)i.add(e.items.get(o).e)}for(var s=0;s<t.length;s++){var a=t[s];if(i!=null&&i.has(a)){a.f|=Ie;const r=document.createDocumentFragment();Ui(a,r)}else ie(t[s],n)}}var vs;function Wn(e,t,n,i,s,a=null){var r=e,o=new Map;{var c=e;r=c.appendChild(gt())}var u=null,p=Wr(()=>{var z=n();return Is(z)?z:z==null?[]:Xn(z)}),v,f=new Map,m=!0;function h(z){(L.effect.f&me)===0&&(L.pending.delete(z),L.fallback=u,kl(L,v,r,t,i),u!==null&&(v.length===0?(u.f&Ie)===0?Un(u):(u.f^=Ie,tn(u,null,r)):bt(u,()=>{u=null})))}function b(z){L.pending.delete(z)}var C=Ki(()=>{v=M(p);for(var z=v.length,j=new Set,oe=x,Oe=ha(),k=0;k<z;k+=1){var A=v[k],G=i(A,k),O=m?null:o.get(G);O?(O.v&&Kt(O.v,A),O.i&&Kt(O.i,k),Oe&&oe.unskip_effect(O.e)):(O=Ml(o,m?r:vs??(vs=gt()),A,G,k,s,t,n),m||(O.e.f|=Ie),o.set(G,O)),j.add(G)}if(z===0&&a&&!u&&(m?u=he(()=>a(r)):(u=he(()=>a(vs??(vs=gt()))),u.f|=Ie)),z>j.size&&gr(),!m)if(f.set(oe,j),Oe){for(const[Wt,Ke]of o)j.has(Wt)||oe.skip_effect(Ke.e);oe.oncommit(h),oe.ondiscard(b)}else h(oe);M(p)}),L={effect:C,items:o,pending:f,outrogroups:null,fallback:u};m=!1}function Qt(e){for(;e!==null&&(e.f&$e)===0;)e=e.next;return e}function kl(e,t,n,i,s){var G;var a=t.length,r=e.items,o=Qt(e.effect.first),c,u=null,p=[],v=[],f,m,h,b;for(b=0;b<a;b+=1){if(f=t[b],m=s(f,b),h=r.get(m).e,e.outrogroups!==null)for(const O of e.outrogroups)O.pending.delete(h),O.done.delete(h);if((h.f&Q)!==0&&Un(h),(h.f&Ie)!==0)if(h.f^=Ie,h===o)tn(h,null,n);else{var C=u?u.next:o;h===e.effect.last&&(e.effect.last=h.prev),h.prev&&(h.prev.next=h.next),h.next&&(h.next.prev=h.prev),qe(e,u,h),qe(e,h,C),tn(h,C,n),u=h,p=[],v=[],o=Qt(u.next);continue}if(h!==o){if(c!==void 0&&c.has(h)){if(p.length<v.length){var L=v[0],z;u=L.prev;var j=p[0],oe=p[p.length-1];for(z=0;z<p.length;z+=1)tn(p[z],L,n);for(z=0;z<v.length;z+=1)c.delete(v[z]);qe(e,j.prev,oe.next),qe(e,u,j),qe(e,oe,L),o=L,u=oe,b-=1,p=[],v=[]}else c.delete(h),tn(h,o,n),qe(e,h.prev,h.next),qe(e,h,u===null?e.effect.first:u.next),qe(e,u,h),u=h;continue}for(p=[],v=[];o!==null&&o!==h;)(c??(c=new Set)).add(o),v.push(o),o=Qt(o.next);if(o===null)continue}(h.f&Ie)===0&&p.push(h),u=h,o=Qt(h.next)}if(e.outrogroups!==null){for(const O of e.outrogroups)O.pending.size===0&&(zi(e,Xn(O.done)),(G=e.outrogroups)==null||G.delete(O));e.outrogroups.size===0&&(e.outrogroups=null)}if(o!==null||c!==void 0){var Oe=[];if(c!==void 0)for(h of c)(h.f&Q)===0&&Oe.push(h);for(;o!==null;)(o.f&Q)===0&&o!==e.fallback&&Oe.push(o),o=Qt(o.next);var k=Oe.length;if(k>0){var A=a===0?n:null;_l(e,Oe,A)}}}function Ml(e,t,n,i,s,a,r,o){var c=(r&$r)!==0?(r&Tr)===0?el(n,!1,!1):_t(n):null,u=(r&Er)!==0?_t(s):null;return{v:c,i:u,e:he(()=>(a(t,c??n,u??s,o),()=>{e.delete(i)}))}}function tn(e,t,n){if(e.nodes)for(var i=e.nodes.start,s=e.nodes.end,a=t&&(t.f&Ie)===0?t.nodes.start:n;i!==null;){var r=yn(i);if(a.before(i),i===s)return;i=r}}function qe(e,t,n){t===null?e.effect.first=n:t.next=n,n===null?e.effect.last=t:n.prev=t}function Sl(e,t,n=!1,i=!1,s=!1,a=!1){var r=e,o="";if(n)var c=e;ot(()=>{var u=E;if(o!==(o=t()??"")){if(n){u.nodes=null,c.innerHTML=o,o!==""&&jn(Ye(c),c.lastChild);return}if(u.nodes!==null&&(wa(u.nodes.start,u.nodes.end),u.nodes=null),o!==""){var p=i?Ar:s?zr:void 0,v=va(i?"svg":s?"math":"template",p);v.innerHTML=o;var f=i||s?v:v.content;if(jn(Ye(f),f.lastChild),i||s)for(;Ye(f);)r.before(Ye(f));else r.before(f)}}})}function $l(e,t,n){var i=e==null?"":""+e;return i===""?null:i}function ms(e,t,n,i,s,a){var r=e[Mi];if(r!==n||r===void 0){var o=$l(n);o==null?e.removeAttribute("class"):e.className=o,e[Mi]=n}return a}const El=Symbol("is custom element"),Tl=Symbol("is html");function Cl(e,t,n,i){var s=Ll(e);s[t]!==(s[t]=n)&&(n==null?e.removeAttribute(t):typeof n!="string"&&Al(e).includes(t)?e[t]=n:e.setAttribute(t,n))}function Ll(e){return e[Dn]??(e[Dn]={[El]:e.nodeName.includes("-"),[Tl]:e.namespaceURI===Us})}var gs=new Map;function Al(e){var t=e.getAttribute("is")||e.nodeName,n=gs.get(t);if(n)return n;gs.set(t,n=[]);for(var i,s=e,a=Element.prototype;a!==s;){i=lr(s);for(var r in i)i[r].set&&r!=="innerHTML"&&r!=="textContent"&&r!=="innerText"&&n.push(r);s=Gs(s)}return n}function zl(e){Y===null&&vr(),ll(()=>{const t=La(e);if(typeof t=="function")return t})}let hn=localStorage.getItem("mc_token")||"";function Da(){return hn}function Dl(e){hn=(e||"").trim(),localStorage.setItem("mc_token",hn)}function Pl(){const e={"Content-Type":"application/json"};return hn&&(e["X-MC-Token"]=hn),e}function _n(){return localStorage.getItem("mc_hf_token")||""}function Ol(e){localStorage.setItem("mc_hf_token",(e||"").trim())}async function $(e,t={}){const n=await fetch(e,{headers:Pl(),...t}),i=await n.json().catch(()=>({}));if(!n.ok)throw new Error(i.error||"HTTP "+n.status);return i}const l=(e,t=document)=>t.querySelector(e),bs=(e,t=document)=>[...t.querySelectorAll(e)];function g(e){return String(e??"").replace(/[&<>]/g,t=>({"&":"&","<":"<",">":">"})[t])}let ws;function w(e,t=!1){const n=l("#toast");n&&(n.textContent=e,n.className="toast show"+(t?" err":""),clearTimeout(ws),ws=setTimeout(()=>n.className="toast",3200))}function Bl(e,t){return e==="running"||e==="ready"?'<span class="badge b-run">geladen</span>':e==="loading"||e==="starting"?'<span class="badge b-load">lädt…</span>':e==="downloading"?`<span class="badge b-load">↓ Download${t!=null?` ${Math.round(t)}%`:""}</span>`:'<span class="badge b-run">bereit</span>'}const D=e=>`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">${e}</svg>`,Pa={logo:D('<circle cx="12" cy="12" r="3.2"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3M5 5l2 2M17 17l2 2M19 5l-2 2M7 17l-2 2"/>'),grid:D('<rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/>'),cpu:D('<rect x="6" y="6" width="12" height="12" rx="2"/><path d="M9 1v3M15 1v3M9 20v3M15 20v3M1 9h3M1 15h3M20 9h3M20 15h3"/>'),pulse:D('<path d="M3 12h4l2 6 4-14 2 8h6"/>'),server:D('<rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><path d="M7 7.5h.01M7 16.5h.01"/>'),book:D('<path d="M4 5a2 2 0 0 1 2-2h13v16H6a2 2 0 0 0-2 2z"/><path d="M19 19H6a2 2 0 0 0-2 2"/>'),help:D('<circle cx="12" cy="12" r="9"/><path d="M9.5 9.2a2.5 2.5 0 0 1 4.8 1c0 1.7-2.3 2-2.3 3.4"/><path d="M12 17h.01"/>'),settings:D('<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.6 1.6 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.6 1.6 0 0 0-2.7 1.1V21a2 2 0 1 1-4 0v-.1A1.6 1.6 0 0 0 6.6 19l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.6 1.6 0 0 0-1.1-2.7H2a2 2 0 1 1 0-4h.1A1.6 1.6 0 0 0 4 6.6l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1A1.6 1.6 0 0 0 9 4.6V4a2 2 0 1 1 4 0v.1A1.6 1.6 0 0 0 17.4 6l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.6 1.6 0 0 0 1.1 2.7H21a2 2 0 1 1 0 4h-.1a1.6 1.6 0 0 0-1.5 1z"/>'),swap:D('<path d="M7 4 3 8l4 4"/><path d="M3 8h12a4 4 0 0 1 0 8h-1"/><path d="m17 20 4-4-4-4"/>'),monitor:D('<rect x="3" y="4" width="18" height="12" rx="2"/><path d="M8 20h8M12 16v4"/>'),layers:D('<path d="m12 2 9 5-9 5-9-5z"/><path d="m3 12 9 5 9-5M3 17l9 5 9-5"/>'),alert:D('<path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/><path d="M12 9v4M12 17h.01"/>'),gauge:D('<path d="M12 14 16 9"/><circle cx="12" cy="13" r="9"/><path d="M12 4v2M21 13h-2M5 13H3"/>'),search:D('<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>'),compass:D('<circle cx="12" cy="12" r="10"/><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"/>'),code:D('<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>'),eye:D('<path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/>'),refresh:D('<path d="M3 12a9 9 0 0 1 15-6.7L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-15 6.7L3 16"/><path d="M3 21v-5h5"/>'),file:D('<path d="M14 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/><path d="M9 13h6M9 17h5"/>'),database:D('<ellipse cx="12" cy="5" rx="8" ry="3"/><path d="M4 5v6c0 1.7 3.6 3 8 3s8-1.3 8-3V5"/><path d="M4 11v6c0 1.7 3.6 3 8 3s8-1.3 8-3v-6"/>'),thermo:D('<path d="M14 14.76V5a2 2 0 0 0-4 0v9.76a4 4 0 1 0 4 0z"/>'),shield:D('<path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7z"/><path d="m9 12 2 2 4-4"/>'),info:D('<circle cx="12" cy="12" r="9"/><path d="M12 16v-4M12 8h.01"/>'),chevron:D('<path d="m9 6 6 6-6 6"/>'),bolt:D('<path d="M13 2 3 14h7l-1 8 10-12h-7z"/>'),x:D('<path d="M18 6 6 18M6 6l12 12"/>'),check:D('<path d="M20 6 9 17l-5-5"/>'),download:D('<path d="M12 3v12M7 10l5 5 5-5"/><path d="M5 21h14"/>'),clock:D('<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/>')};function nt(e){return Pa[e]||""}function Jn(e){return`<span class="info-dot" tabindex="0" title="${g(e)}" aria-label="${g(e)}">${Pa.info}</span>`}function rt(e){if(!e&&e!==0)return"–";const t=e/1024/1024/1024;return t>=1?t.toFixed(1)+" GB":Math.round(e/1024/1024)+" MB"}function Ee({title:e,body:t,confirmLabel:n="Bestätigen",cancelLabel:i="Abbrechen",danger:s=!1}){return new Promise(a=>{const r=document.createElement("div");r.className="modal-overlay",r.innerHTML=`<div class="modal-card" role="dialog" aria-modal="true">
|
||
<h3>${g(e)}</h3>
|
||
<p>${t}</p>
|
||
<div class="modal-actions">
|
||
<button data-x="0">${g(i)}</button>
|
||
<button class="${s?"danger":"primary"}" data-x="1">${g(n)}</button>
|
||
</div></div>`;const o=c=>{r.remove(),a(c)};r.addEventListener("click",c=>{if(c.target===r)return o(!1);const u=c.target.closest("[data-x]");u&&o(u.getAttribute("data-x")==="1")}),document.body.appendChild(r),r.querySelector('[data-x="1"]').focus()})}function Oa({title:e,body:t="",placeholder:n="",password:i=!1,confirmLabel:s="Weiter",danger:a=!1}){return new Promise(r=>{const o=document.createElement("div");o.className="modal-overlay",o.innerHTML=`<div class="modal-card" role="dialog" aria-modal="true">
|
||
<h3>${g(e)}</h3>
|
||
${t?`<p>${t}</p>`:""}
|
||
<input type="${i?"password":"text"}" placeholder="${g(n)}" autocomplete="off" style="margin-bottom:18px">
|
||
<div class="modal-actions">
|
||
<button data-x="0">Abbrechen</button>
|
||
<button class="${a?"danger":"primary"}" data-x="1">${g(s)}</button>
|
||
</div></div>`;const c=o.querySelector("input"),u=p=>{o.remove(),r(p)};o.addEventListener("click",p=>{if(p.target===o)return u(null);const v=p.target.closest("[data-x]");v&&u(v.getAttribute("data-x")==="1"?c.value||"":null)}),c.addEventListener("keydown",p=>{p.key==="Enter"&&u(c.value||""),p.key==="Escape"&&u(null)}),document.body.appendChild(o),c.focus()})}function Hl(e="overview"){const t=bs(".nav-item[data-view]:not(.disabled)"),n=bs(".view[data-view]"),i=new Set(n.map(a=>a.dataset.view));function s(a){i.has(a)||(a=e),n.forEach(r=>r.hidden=r.dataset.view!==a),t.forEach(r=>r.classList.toggle("active",r.dataset.view===a)),location.hash!=="#/"+a&&(location.hash="#/"+a)}return t.forEach(a=>a.addEventListener("click",r=>{r.preventDefault(),s(a.dataset.view)})),window.addEventListener("hashchange",()=>s(location.hash.replace("#/",""))),s(location.hash.replace("#/","")||e),{show:s}}let ys=ee(null);const Rl={get value(){return M(ys)},set(e){V(ys,e,!0)}};let xs=ee(Qe([]));const Fl={get value(){return M(xs)},set(e){V(xs,e||[],!0)}},Nl="5";var Fs;typeof window<"u"&&((Fs=window.__svelte??(window.__svelte={})).v??(Fs.v=new Set)).add(Nl);const Il=[["Dein erster Workflow — in 3 Schritten",`
|
||
<ol class="tut-steps">
|
||
<li><b>Modell holen:</b> Tab <b>Cookbook</b> → wähle deinen Anwendungsfall (z.B. „Coden") →
|
||
<b>Komplettes Setup installieren</b>. Der Download läuft im Tab <b>Aktivität</b>.</li>
|
||
<li><b>Verbinden:</b> Tab <b>Verbinden</b> → Basis-URL kopieren → in deinem Tool (z.B. OpenCode)
|
||
als „OpenAI-kompatibel" einfügen. <b>Verbindung testen</b> bestätigt, dass es läuft.</li>
|
||
<li><b>Arbeiten:</b> Im Tool das Modell (z.B. <code>coder</code>) wählen und loslegen. Das passende
|
||
Modell wird <b>automatisch geladen</b> — du musst nichts starten.</li>
|
||
</ol>`],["Wie leere ich den Kontext? Machen die Tools das selbst?",`
|
||
<p><b>Kurz: meistens ja, automatisch.</b> Der Kontext (das „Gedächtnis") gilt pro Unterhaltung.
|
||
Startest du im Tool einen <b>neuen Chat / eine neue Session</b>, ist der Kontext wieder leer —
|
||
das Tool verwaltet das Fenster für dich.</p>
|
||
<p>Du musst nur selten eingreifen: wird ein Gespräch sehr lang, fang einen neuen an. <b>Wichtig:</b>
|
||
„Grafikspeicher leeren" (Tab Server) ist etwas anderes — das wirft das <i>Modell</i> aus dem Speicher,
|
||
nicht den Gesprächsverlauf.</p>`],["Was passiert, wenn der Kontext volläuft?",`
|
||
<p>Ist das Kontextfenster voll, passiert je nach Tool eins von dreien: das Modell <b>vergisst den
|
||
Anfang</b> (älteste Nachrichten fallen raus), das Tool <b>kürzt automatisch</b>, oder du bekommst
|
||
einen Fehler wie „context length exceeded".</p>
|
||
<p><b>So gehst du vor:</b></p>
|
||
<ol class="tut-steps">
|
||
<li><b>Neuen Chat starten</b> — der einfachste Reset, Kontext ist wieder leer.</li>
|
||
<li><b>Zusammenfassen lassen:</b> „Fasse unseren Stand kurz zusammen", dann im neuen Chat einfügen.</li>
|
||
<li><b>Kontext vergrößern:</b> Tab <b>Modelle</b> → <b>Konfigurieren</b> → wir zeigen das Optimum
|
||
für deine Hardware (mehr Kontext = mehr Speicherbedarf).</li>
|
||
</ol>`],["Was ist ein Agent — und wann brauche ich einen?",`
|
||
<p>Ein nacktes Modell <b>schreibt nur Text zurück</b>. Ein <b>Agent</b> ist ein Programm drumherum,
|
||
das das Modell <b>handeln</b> lässt: Dateien lesen und ändern, Befehle ausführen, im Web suchen —
|
||
Schritt für Schritt in einer Schleife, bis die Aufgabe erledigt ist.</p>
|
||
<p><b>Wann lohnt sich was?</b></p>
|
||
<ul class="tut-steps" style="list-style:disc">
|
||
<li><b>Fragen, Texte, Erklärungen, Brainstorming</b> → kein Agent nötig, eine Chat-Oberfläche
|
||
wie <b>OpenWebUI</b> reicht völlig.</li>
|
||
<li><b>Coden über mehrere Dateien, Befehle ausführen, recherchieren + umsetzen</b>
|
||
→ <b>Agent</b> (OpenCode, Cline, Continue).</li>
|
||
</ul>
|
||
<p><b>Gut zu wissen:</b> Diese Agenten <b>laufen lokal auf deinem PC</b> — du brauchst
|
||
<b>keinen</b> Cloud-Dienst.</p>`],["Was sind Skills — und wie installiere ich sie?",`
|
||
<p>Ein <b>Skill</b> ist <b>verpacktes Können</b>: eine kurze Anleitung, die dem Agenten beibringt,
|
||
eine wiederkehrende Aufgabe jedes Mal so zu erledigen, wie <i>du</i> es willst.</p>
|
||
<p><b>Skill vs. MCP:</b> <b>MCP</b> gibt dem Agenten neue <b>Werkzeuge</b>. <b>Skills</b> geben
|
||
ihm <b>Know-how</b>. Kurz: <b>Werkzeuge = MCP, Anleitungen = Skills.</b></p>
|
||
<p><b>Installieren:</b> meist nur eine Datei ablegen — kein Build, kein Neustart:</p>
|
||
<ol class="tut-steps">
|
||
<li><b>Pro Projekt:</b> in den Projektordner (z.B. <code>AGENTS.md</code>, <code>.clinerules/</code>).</li>
|
||
<li><b>Global:</b> in den Konfig-Ordner des Tools — gilt überall.</li>
|
||
</ol>`],["MCP — deinem Agenten echte Werkzeuge geben",`
|
||
<p><b>MCP</b> (Model Context Protocol) ist ein einheitlicher „Stecker", über den ein Agent zusätzliche
|
||
<b>Werkzeuge</b> bekommt: das Web durchsuchen, Dateien lesen, eine Datenbank abfragen.</p>
|
||
<p>Du fügst in deinem Agenten einen „MCP-Server" hinzu (eine Zeile in der Konfiguration je Werkzeug).
|
||
Danach entscheidet der Agent selbst, wann er das Werkzeug einsetzt.</p>
|
||
<ul class="tut-steps" style="list-style:disc">
|
||
<li><b>Faustregel:</b> Fehlt dem Agenten eine <b>Fähigkeit</b> → MCP. Soll er etwas nur
|
||
<b>besser machen</b> → Skill.</li>
|
||
</ul>`],["Auto-Swap — wie das Laden funktioniert",`
|
||
<p>llama-swap lädt automatisch das Modell, das gerade angefragt wird, und entlädt es nach einer
|
||
Leerlaufzeit wieder. <b>Es läuft immer nur eines gleichzeitig.</b></p>
|
||
<p>Du richtest beliebig viele Modelle ein und wählst im Tool das passende —
|
||
das Laden/Entladen passiert im Hintergrund, ohne dass du etwas tun musst.</p>`],["Die empfohlenen Tools optimal nutzen",`
|
||
<ul class="tut-steps" style="list-style:disc">
|
||
<li><b>OpenCode:</b> Autonomes Coden im Terminal/Editor — ideal für „bau mir Feature X".</li>
|
||
<li><b>Cline:</b> Dasselbe als VS-Code-Erweiterung, mit Bestätigung vor jeder Aktion.</li>
|
||
<li><b>OpenWebUI:</b> Klassische Chat-Oberfläche für Fragen/Texte.</li>
|
||
</ul>
|
||
<p><b>Tipp:</b> starkes Modell (<code>coder</code>) für schwere Aufgaben, schnelles
|
||
(<code>scout</code>) für einfache — du wählst es einfach im Tool aus.</p>`]],Gl=[["LLM / Modell","Die KI selbst — versteht und schreibt Text (wie ChatGPT, nur lokal auf deinem PC). Ein Modell ist eine konkrete Datei davon, oft auf etwas spezialisiert (Code, Bilder, Chat)."],["GGUF","Das lokal lauffähige Dateiformat. Unsere Engine lädt nur GGUF — Repos ohne GGUF kann sie nicht nutzen."],["Quantisierung (Q4_K_M)","Verkleinert ein Modell (16-Bit → 4-Bit), damit es in den Speicher passt — bei kaum Qualitätsverlust. Q4_K_M ist der beliebte Sweetspot."],["Kontextfenster","Das Kurzzeitgedächtnis: wie viel Text sich das Modell gleichzeitig merkt. Größer = mehr Überblick, mehr Speicher, etwas langsamer."],["Auto-Swap","llama-swap lädt/entlädt Modelle automatisch — immer nur eins gleichzeitig. Du managst nichts."],["Agent","Ein Programm (OpenCode, Cline, Continue), das das Modell handeln lässt — Dateien lesen/ändern, Befehle ausführen, planen, im Loop bis die Aufgabe erledigt ist."],["MCP","Model Context Protocol — der Anschluss, über den ein Agent zusätzliche Werkzeuge bekommt (Web, Dateien, Datenbank). MCP = Werkzeuge."],["Skills","Verpackte Anleitung für wiederkehrende Aufgaben — der Agent liest sie und macht es jedes Mal wie du willst. Skills = Anleitungen."],["Rules / Commands","Die konkrete Form von Skills je Tool: AGENTS.md (OpenCode), .clinerules (Cline), Rules/Prompts (Continue)."]];var Kl=be('<details class="guide-acc"><summary> </summary> <div class="acc-body"></div></details>'),ql=be('<div class="concept"><h4> </h4> <p> </p></div>'),Ul=be('<div class="pagehead"><div><h1>Guide für Einsteiger</h1> <div class="sub">Praktische Workflows + die Local-LLM-Welt in Klartext — ohne Vorwissen.</div></div></div> <div class="card-h"><h3>Tutorials & Workflows</h3></div> <div class="card" style="padding:0;overflow:hidden"></div> <div class="card-h" style="margin-top:8px"><h3>Konzepte kurz erklärt</h3></div> <div class="concept-grid"></div>',1);function jl(e){var t=Ul(),n=J(pa(t),4);Wn(n,21,()=>Il,Vn,(s,a,r)=>{var o=rs(()=>ss(M(a),2));let c=()=>M(o)[0],u=()=>M(o)[1];var p=Kl();p.open=r===0;var v=R(p),f=R(v),m=J(v,2);Sl(m,u,!0),ot(()=>ye(f,c())),ce(s,p)});var i=J(n,4);Wn(i,21,()=>Gl,Vn,(s,a)=>{var r=rs(()=>ss(M(a),2));let o=()=>M(r)[0],c=()=>M(r)[1];var u=ql(),p=R(u),v=R(p),f=J(p,2),m=R(f);ot(()=>{ye(v,o()),ye(m,c())}),ce(s,u)}),ce(e,t)}var Vl=be('<div class="li" style="align-items:flex-start"><div class="li-main"><div class="flex items-center gap-2" style="flex-wrap:wrap"><span class="li-id" style="font-family:var(--sans);font-weight:500"> </span> <span> </span></div> <div class="li-sub"> </div></div> <button class="primary">Installieren</button></div>'),Wl=be('<div class="card" style="margin-bottom:18px;border-color:rgba(45,212,191,.25)"><div class="card-h"><h3>Für dich: Upgrades</h3> <span class="meta"> </span></div> <div class="card-sub">Neuere Modelle, die ein installiertes ersetzen können.</div> <div class="list"></div></div>'),Jl=be('<div class="empty" style="text-align:center;padding:40px">Lade News… (erstes Laden kann kurz dauern)</div>'),Ql=be('<div class="alert err" style="margin:0"> </div>'),Yl=be('<div class="empty-c"><div class="e-t">Keine News geladen</div> <div class="e-s">Die Quellen sind gerade nicht erreichbar — später nochmal probieren.</div></div>'),Zl=be('<span class="news-rel">für dich</span>'),Xl=be('<a target="_blank" rel="noopener"><div class="news-meta"><span class="news-src"> </span> <!> <span class="news-date"> </span></div> <div class="news-title"> </div></a>'),eo=be('<div class="news-grid"></div>'),to=be('<div class="pagehead"><div><h1>News</h1> <div class="sub">Aktuelles aus der Local-LLM-Welt — aus kuratierten Quellen.</div></div> <button class="ghost">Aktualisieren</button></div> <!> <!>',1);function no(e,t){Ws(t,!0);let n=ee(Qe([])),i=ee(Qe([])),s=ee(!0),a=ee("");const r=k=>k==="perfect"?"ok":k==="marginal"?"warn":"bad";function o(k){const A=Math.max(0,Date.now()/1e3-k);return A<60?A+"s":A<3600?Math.floor(A/60)+"m":A<86400?Math.floor(A/3600)+"h":Math.floor(A/86400)+"d"}async function c(){V(s,!0),V(a,"");try{V(n,(await $("/api/news")).items||[],!0)}catch(k){V(a,k.message||"Fehler beim Laden",!0)}finally{V(s,!1)}}async function u(){try{V(i,(await $("/api/cookbook/upgrades")).upgrades||[],!0)}catch{V(i,[],!0)}}async function p(k,A){var G;A.disabled=!0,A.textContent="Starte…";try{await $("/api/cookbook/install-model",{method:"POST",body:JSON.stringify({repo:k.repo,role:k.role,params_b:k.params_b,quant:k.quant,hf_token:_n()})}),(G=document.querySelector(".nav-item[data-view='activity']"))==null||G.dispatchEvent(new MouseEvent("click"))}catch(O){A.disabled=!1,A.textContent="Installieren",alert("Fehler: "+O.message)}}function v(){c(),u()}zl(()=>{c(),u()});var f=to(),m=pa(f),h=J(R(m),2),b=J(m,2);{var C=k=>{var A=Wl(),G=R(A),O=J(R(G),2),Wt=R(O),Ke=J(G,4);Wn(Ke,21,()=>M(i),Vn,($n,Be)=>{var En=Vl(),Jt=R(En),Tn=R(Jt),Cn=R(Tn),si=R(Cn),Ln=J(Cn,2),ai=R(Ln),st=J(Tn,2),ri=R(st),nr=J(Jt,2);ot(li=>{ye(si,M(Be).name),ms(Ln,1,`fit-badge ${li??""}`),ye(ai,M(Be).fit.text),ye(ri,`Ersetzt dein „${M(Be).old??""}". ${M(Be).why??""}`)},[()=>r(M(Be).fit.level)]),fs("click",nr,li=>p(M(Be),li.currentTarget)),ce($n,En)}),ot(()=>ye(Wt,M(i).length)),ce(k,A)};pi(b,k=>{M(i).length&&k(C)})}var L=J(b,2);{var z=k=>{var A=Jl();ce(k,A)},j=k=>{var A=Ql(),G=R(A);ot(()=>ye(G,M(a))),ce(k,A)},oe=k=>{var A=Yl();ce(k,A)},Oe=k=>{var A=eo();Wn(A,21,()=>M(n),Vn,(G,O,Wt)=>{var Ke=Xl();ms(Ke,1,`news-card${Wt===0?" featured":""}`);var $n=R(Ke),Be=R($n),En=R(Be),Jt=J(Be,2);{var Tn=st=>{var ri=Zl();ce(st,ri)};pi(Jt,st=>{M(O).relevant&&st(Tn)})}var Cn=J(Jt,2),si=R(Cn),Ln=J($n,2),ai=R(Ln);ot(st=>{Cl(Ke,"href",M(O).link),ye(En,M(O).source),ye(si,st),ye(ai,M(O).title)},[()=>M(O).ts?o(M(O).ts):""]),ce(G,Ke)}),ce(k,A)};pi(L,k=>{M(s)?k(z):M(a)?k(j,1):M(n).length?k(Oe,-1):k(oe,2)})}fs("click",h,v),ce(e,f),Js()}ml(["click"]);let Ge=null,ne=null;const Ba=new Set(["running","ready","loading","starting"]);function ji(){return(Ge==null?void 0:Ge.models)||[]}function Ha(){return ji().find(e=>Ba.has(e.state))}function io(e){var t;(t=document.querySelector(`.nav-item[data-view="${e}"]`))==null||t.click()}async function so(){if(await Ee({title:"Speicher freigeben?",body:"Alle aktuell geladenen Modelle werden aus dem Grafikspeicher geworfen. Beim nächsten Aufruf lädt das jeweilige Modell einfach neu — es geht nichts verloren.",confirmLabel:"Speicher freigeben"}))try{await $("/api/unload",{method:"POST"}),w("Speicher freigegeben."),setTimeout(()=>document.dispatchEvent(new Event("mc:refresh")),600)}catch(t){w(t.message,!0)}}window.mcOv={go:io,freeMemory:so};function Vi(){let e="Verbinde…",t="";if(Ge){const n=ji().length,i=Ha();Ge.swap_ok?ne&&ne.ram.percent>=90?(e="Achtung: Speicher wird knapp.",t=`Arbeitsspeicher bei ${Math.round(ne.ram.percent)} % — eventuell Speicher freigeben.`):(e="Alles läuft rund.",t=`${n} ${n===1?"Modell":"Modelle"} bereit · ${i?`„${i.name}" geladen`:"keins geladen"} · keine Warnungen.`):(e="LLM-Engine offline",t="Der llama-swap-Dienst antwortet gerade nicht.")}l("#ov-hero").innerHTML=`<div class="pagehead"><div>
|
||
<h1>${g(e)}</h1><div class="sub">${g(t)}</div></div></div>`}function Mt(e,t,n,i="",s=""){return`<div class="tile"><div class="t-l">${e}</div>
|
||
<div class="t-v ${i}">${t}</div><div class="t-s ${s}">${n}</div></div>`}function ao(e){return e==null?"":e<55?"kühl & gesund":e<72?"normal":"läuft heiß"}function Wi(){const e=Ha(),t=Ge==null?void 0:Ge.swap_ok;let n=Mt("LLM-Engine",t?"Online":"Offline","llama-swap",t?"ok":"bad"),i=Mt("Aktives Modell",e?g(e.name):"Keins",e?"im Grafikspeicher":"nichts geladen"),s,a;if(ne){const r=ne.gpu_temp??ne.cpu.temp;s=Mt("GPU-Temperatur",r!=null?`${Math.round(r)}°`:"–",ao(r),"",r!=null&&r<72?"ok":"");const o=ne.ram.total-ne.ram.used;a=Mt("Freier Speicher",`${(o/1024**3).toFixed(0)}<small> GB</small>`,`von ${(ne.ram.total/1024**3).toFixed(0)} GB`)}else s=Mt("GPU-Temperatur","–","messe…"),a=Mt("Freier Speicher","–","messe…");l("#ov-tiles").innerHTML=n+i+s+a}function hi(e,t){const n=Math.max(0,Math.min(100,t||0)),i=n>=90?"bad":n>=75?"warn":"";return`<div class="meter"><div class="meter-h"><span class="mk">${e}</span>
|
||
<span class="mv">${Math.round(n)} %</span></div>
|
||
<div class="bar ${i}"><i style="width:${Math.max(2,n)}%"></i></div></div>`}function Ra(){const e=`<div class="card-h"><h3>System-Gesundheit</h3></div>
|
||
<div class="card-sub">So ausgelastet ist dein Mini-PC gerade.</div>`;if(!ne){l("#ov-health").innerHTML=e+'<div class="empty">Warte auf Messwerte…</div>';return}let t=0;const n=ne.gpu;n&&n.vram.total+n.gtt.total>0&&(t=(n.vram.used+n.gtt.used)/(n.vram.total+n.gtt.total)*100),l("#ov-health").innerHTML=e+hi("Prozessor (CPU)",ne.cpu.percent)+hi("Arbeitsspeicher (RAM)",ne.ram.percent)+hi("Grafikspeicher (VRAM)",t)}function vi(e,t,n,i,s){return`<button class="qa" onclick="${s}">
|
||
<span class="qa-ic ${t}">${nt(e)}</span>
|
||
<span class="qa-main"><span class="qa-t">${n}</span><span class="qa-s">${i}</span></span>
|
||
<span class="qa-arrow">${nt("chevron")}</span></button>`}function Fa(){l("#ov-quickstart").innerHTML=`<div class="card-h"><h3>Schnellstart</h3></div>
|
||
<div class="card-sub">Die häufigsten Aufgaben — ein Klick.</div>`+vi("search","teal","Modell finden","Passend zu deiner Hardware","window.mcOv.go('cookbook')")+vi("refresh","amber","Speicher freigeben","Modelle entladen · fragt vorher nach","window.mcOv.freeMemory()")+vi("file","blue","Logs ansehen","Live mitlesen, was läuft","window.mcOv.go('server')")}function ro(e){return e?e.includes("Bild")?'<span class="tag img">Bild</span>':e.includes("Code")?'<span class="tag code">Code</span>':'<span class="tag text">Text</span>':""}function lo(e){var r,o;const t=e.state==="downloading",n=Ba.has(e.state),i=e.state==="loading"||e.state==="starting"||t?"load":n?"on":"";let s;t?s=`↓ Download${e.download_progress!=null?` ${Math.round(e.download_progress)}%`:""}`:s=n?e.state==="loading"?"lädt…":"geladen":"bereit";const a=(r=e.meta)!=null&&r.filename?`<div class="li-sub mono-sm">${g(e.meta.filename)}</div>`:"";return`<div class="li"><span class="li-dot ${i}"></span>
|
||
<div class="li-main">
|
||
<div class="flex items-center gap-2"><span class="li-id">${g(e.name)}</span>${ro((o=e.meta)==null?void 0:o.caps)}</div>${a}</div>
|
||
<span class="li-meta" style="white-space:nowrap">${g(s)}</span></div>`}async function oo(){const e=l("#ov-news");if(!e)return;const t=`<div class="card-h"><h3>Top-News</h3><span class="meta" style="cursor:pointer" onclick="window.mcOv.go('news')">Alle →</span></div>`;e.innerHTML=t+'<div class="empty" style="text-align:center;padding:20px">Lade…</div>';try{const n=((await $("/api/news")).items||[]).slice(0,4);e.innerHTML=t+(n.length?`<div class="list">${n.map(i=>`<a class="li" href="${g(i.link)}" target="_blank" rel="noopener" style="text-decoration:none;color:inherit">
|
||
<div class="li-main"><div style="font-size:13px;line-height:1.4">${g(i.title)}</div>
|
||
<div class="li-sub">${g(i.source)}</div></div></a>`).join("")}</div>`:'<div class="empty">Keine News.</div>')}catch{e.innerHTML=t+'<div class="empty">News gerade nicht ladbar.</div>'}}function Na(){const e=ji();l("#ov-stack").innerHTML=`<div class="card-h"><h3>Dein Stack</h3><span class="meta">${e.length?e.length+" konfiguriert":""}</span></div>`+(e.length?`<div class="list">${e.map(lo).join("")}</div>`:`<div class="empty-c"><div class="e-t">Noch keine Modelle</div>
|
||
<div class="e-s">Hol dir unter „Cookbook" ein Modell, das auf deine Hardware passt.</div></div>`)}function co(){Vi(),Wi(),Ra(),Fa(),Na()}function uo(){Fa(),co(),oo()}function fo(e){Ge=e,Vi(),Wi(),Na()}function po(e){ne=e,Vi(),Wi(),Ra()}const ho={id:"overview",mount:uo,onStatus:fo,onSystem:po};let _s=[];const vo="Kontext = das Kurzzeitgedächtnis des Modells. Größer = merkt sich mehr (längere Dateien/Chats), braucht aber mehr Speicher und wird etwas langsamer.",mo="Die Rolle ist ein zusätzlicher, sprechender Name (z.B. coder). Du kannst in deinen Tools entweder den echten Modellnamen ODER die Rolle angeben — beides führt zum selben Modell.",go=[{id:"coder",label:"Coder",desc:"Programmieren & Code"},{id:"vision",label:"Vision",desc:"Bilder verstehen"},{id:"scout",label:"Scout",desc:"Schneller Allrounder"},{id:"reviewer",label:"Reviewer",desc:"Code/Texte prüfen"},{id:"manager",label:"Manager",desc:"Planen & koordinieren"}];let je=[];function kn(){document.dispatchEvent(new Event("mc:refresh"))}function bo(e){return Bl(e.state,e.download_progress)}function wo(e){return e?`<span class="tag" style="background:rgba(45,212,191,.14);color:var(--accent);border-color:rgba(45,212,191,.3)">${g(e)}</span>`:""}function yo(e){return`<div class="li-sub mono-sm" style="margin-top:3px;display:flex;align-items:center;gap:6px;flex-wrap:wrap">
|
||
<span class="text-mut">API-Name:</span>
|
||
${(e.api_ids&&e.api_ids.length?e.api_ids:[e.name]).map(n=>`<code style="cursor:pointer" data-action="copy" data-name="${g(n)}" title="Klicken zum Kopieren">${g(n)}</code>`).join('<span class="text-mut">·</span>')}
|
||
</div>`}function xo(e){return e!=null&&e.length?e.map(t=>t==="Code"?'<span class="tag code">Code</span>':t==="Bild"?'<span class="tag img">Bild</span>':'<span class="tag text">Text</span>').join(" "):"–"}function _o(e){if(!e)return"–";const t=e.quant||"?",n=e.ctx?Math.round(e.ctx/1024)+"K":"?",i=e.size_bytes?(e.size_bytes/1024**3).toFixed(1)+" GB":"?";return`<span class="mono-sm">${g(t)} · ${n} · ${i}</span>`}function ko(){l("#m-head").innerHTML=`<div class="pagehead"><div>
|
||
<h1>Modelle</h1>
|
||
<div class="sub">Deine konfigurierten Modelle — testen, Kontext anpassen oder aus dem Speicher werfen.</div></div></div>`,l("#m-chat").innerHTML=`
|
||
<div class="card-h"><h3>Schnelltest</h3></div>
|
||
<div class="card-sub">Schreib eine Nachricht — das gewählte Modell wird automatisch geweckt.</div>
|
||
<label>Modell</label>
|
||
<select id="chat-model"></select>
|
||
<label>Nachricht</label>
|
||
<textarea id="chat-msg" placeholder="z.B. „Erklär mir kurz, was du kannst."></textarea>
|
||
<button class="primary" id="chat-btn">Senden</button>
|
||
<div id="chat-reply" class="reply" style="display:none"></div>`,l("#m-table").innerHTML=`
|
||
<div class="card-h"><h3>Modelle & Ports</h3><span class="meta" id="m-count"></span></div>
|
||
<div class="card-sub">Modelle laden automatisch, sobald eine Anfrage kommt — du musst nichts manuell starten.</div>
|
||
<table>
|
||
<thead><tr><th>Modell</th><th>Kann</th><th>Details</th><th>Status</th><th>Port</th><th style="text-align:right">Aktionen</th></tr></thead>
|
||
<tbody id="models"></tbody>
|
||
</table>
|
||
<div id="models-empty" class="empty-c" style="display:none">
|
||
<div class="e-t">Noch keine Modelle konfiguriert</div>
|
||
<div class="e-s">Hol dir unter „Cookbook" ein passendes Modell.</div>
|
||
</div>
|
||
|
||
<div id="cfg-modal" class="modal-overlay" style="display:none">
|
||
<div class="modal-card">
|
||
<button id="cfg-close" class="ghost" style="position:absolute;top:14px;right:14px">Schließen</button>
|
||
<h3>Modell konfigurieren</h3>
|
||
<p class="mono-sm" id="cfg-model-name" style="margin:-4px 0 16px"></p>
|
||
<label>Kontext-Größe (Tokens) ${Jn(vo)}</label>
|
||
<input id="cfg-ctx" type="number" value="8192">
|
||
<div class="tile" id="cfg-rec" style="display:none;margin:8px 0 10px;justify-content:space-between;align-items:center" >
|
||
<div style="flex:1"><div style="font-size:13px">Empfohlen für deine Hardware</div>
|
||
<div class="hint" id="cfg-rec-note" style="margin:4px 0 0"></div></div>
|
||
<button class="ghost" id="cfg-rec-apply">Optimal übernehmen</button>
|
||
</div>
|
||
<div class="hint" style="margin-bottom:14px">Es läuft immer nur <b>ein</b> Modell gleichzeitig — ein größerer Kontext hier beeinflusst deine anderen Modelle nicht.</div>
|
||
<button class="primary" id="cfg-save" style="width:100%;margin-top:6px">Speichern</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="role-modal" class="modal-overlay" style="display:none">
|
||
<div class="modal-card">
|
||
<button id="role-close" class="ghost" style="position:absolute;top:14px;right:14px">Schließen</button>
|
||
<h3>Rolle festlegen ${Jn(mo)}</h3>
|
||
<p class="mono-sm" id="role-model-name" style="margin:-4px 0 16px"></p>
|
||
<div class="hint" style="margin-bottom:10px">Wähle eine Rolle als zweiten, sprechenden Namen. In deinen Tools funktionieren danach <b>beide</b>: der echte Modellname und die Rolle.</div>
|
||
<div id="role-chips" class="flex gap-2" style="flex-wrap:wrap;margin-bottom:12px"></div>
|
||
<label>Eigene Rolle (optional)</label>
|
||
<input id="role-custom" placeholder="z.B. uebersetzer">
|
||
<div class="btn-row" style="margin-top:14px;display:flex;gap:8px">
|
||
<button class="ghost" id="role-clear" style="flex:1">Rolle entfernen</button>
|
||
<button class="primary" id="role-save" style="flex:2">Speichern</button>
|
||
</div>
|
||
</div>
|
||
</div>`,l("#chat-btn").addEventListener("click",Ao),l("#cfg-close").addEventListener("click",()=>l("#cfg-modal").style.display="none"),l("#cfg-modal").addEventListener("click",e=>{e.target.id==="cfg-modal"&&(l("#cfg-modal").style.display="none")}),l("#cfg-save").addEventListener("click",Co),l("#role-close").addEventListener("click",()=>l("#role-modal").style.display="none"),l("#role-modal").addEventListener("click",e=>{e.target.id==="role-modal"&&(l("#role-modal").style.display="none")}),l("#role-save").addEventListener("click",()=>Pi(l("#role-custom").value.trim().toLowerCase())),l("#role-clear").addEventListener("click",()=>Pi("")),l("#m-table").addEventListener("click",e=>{var s,a;const t=e.target.closest("[data-action]");if(!t)return;const n=t.getAttribute("data-name"),i=t.getAttribute("data-action");n&&(i==="cfg"?To(n):i==="role"?Mo(n):i==="unload"?Lo(n):i==="del"?Eo(n):i==="copy"?((s=navigator.clipboard)==null||s.writeText(n),w("Kopiert: "+n)):i==="assign"&&(w("Lade im Cookbook ein Modell und setze den Alias auf: "+n),(a=document.querySelector(".nav-item[data-view='cookbook']"))==null||a.click()))})}let Di=null;function Mo(e){Di=e;const t=je.find(n=>n.name===e);l("#role-model-name").textContent=e,l("#role-custom").value="",l("#role-chips").innerHTML=go.map(n=>`<button class="${(t==null?void 0:t.role)===n.id?"primary":"ghost"}" data-rolepick="${n.id}" title="${g(n.desc)}" style="border-radius:999px">${g(n.label)}</button>`).join(""),l("#role-chips").querySelectorAll("[data-rolepick]").forEach(n=>n.addEventListener("click",()=>Pi(n.getAttribute("data-rolepick")))),l("#role-modal").style.display="flex"}async function Pi(e){if(Di)try{await $("/api/set_role",{method:"POST",body:JSON.stringify({alias:Di,role:e})}),w(e?`Rolle gesetzt: ${e}`:"Rolle entfernt."),l("#role-modal").style.display="none",kn()}catch(t){w(t.message,!0)}}function Ia(){const e=l("#models");if(!e)return;l("#models-empty").style.display=je.length?"none":"flex",l("#m-count").textContent=je.length?je.length+" konfiguriert":"";const t=l("#chat-model"),n=t.value;t.innerHTML="",e.innerHTML=je.map(i=>{var a,r;if(i.incomplete)return`<tr style="opacity:.9">
|
||
<td class="mid" style="font-weight:500">${g(i.name)}<div class="li-sub mono-sm" style="color:var(--warn)">kein Modell hinterlegt</div></td>
|
||
<td>—</td>
|
||
<td><span class="mono-sm text-mut">leere Rolle</span></td>
|
||
<td><span class="badge">leer</span></td>
|
||
<td class="port">—</td>
|
||
<td style="text-align:right;white-space:nowrap">
|
||
<button class="ghost" data-action="assign" data-name="${g(i.name)}">Modell zuweisen</button>
|
||
<button class="ghost del" data-action="del" data-name="${g(i.name)}">Löschen</button>
|
||
</td></tr>`;const s=(a=i.meta)!=null&&a.filename?`<div class="li-sub mono-sm">${g(i.meta.filename)}</div>`:"";return`<tr>
|
||
<td class="mid">
|
||
<div style="font-weight:500;display:flex;align-items:center;gap:7px;flex-wrap:wrap">${g(i.name)}${wo(i.role)}</div>
|
||
${s}${yo(i)}</td>
|
||
<td>${xo((r=i.meta)==null?void 0:r.caps)}</td>
|
||
<td>${_o(i.meta)}</td>
|
||
<td>${bo(i)}</td>
|
||
<td class="port">${i.port??"auto"}</td>
|
||
<td style="text-align:right;white-space:nowrap">
|
||
<button class="ghost" data-action="role" data-name="${g(i.name)}">Rolle</button>
|
||
<button class="ghost" data-action="cfg" data-name="${g(i.name)}">Konfigurieren</button>
|
||
<button class="ghost" data-action="unload" data-name="${g(i.name)}">Entladen</button>
|
||
<button class="ghost del" data-action="del" data-name="${g(i.name)}">Löschen</button>
|
||
</td></tr>`}).join("");for(const i of je)i.incomplete||t.insertAdjacentHTML("beforeend",`<option>${g(i.name)}</option>`);n&&(t.value=n)}function So(e){je=(e==null?void 0:e.models)||[],Ia()}function $o(e){_s=e||[],_s.some(n=>(n.state==="running"||n.state==="queued")&&/^download\b/i.test(n.label))&&Ia()}async function Eo(e){if(await Ee({title:`„${e}" wirklich löschen?`,body:"Das Modell wird aus der Konfiguration ausgetragen und seine Dateien werden von der Festplatte gelöscht, um Speicher freizugeben. <b>Das lässt sich nicht rückgängig machen</b> — später brauchst du dafür einen neuen Download.",confirmLabel:"Endgültig löschen",danger:!0}))try{const n=await $("/api/delete_model",{method:"POST",body:JSON.stringify({alias:e})});w(n.note||"Gelöscht: "+e),setTimeout(kn,600)}catch(n){w(n.message,!0)}}function To(e){var s,a,r,o,c;const t=je.find(u=>u.name===e);if(!t)return;l("#cfg-model-name").textContent=t.name,l("#cfg-ctx").value=((s=t.meta)==null?void 0:s.ctx)||8192;const n=(a=t.meta)==null?void 0:a.optimal_ctx,i=l("#cfg-rec");if(n){const u=((r=t.meta)==null?void 0:r.ctx)||0,p=(o=t.meta)==null?void 0:o.peak_ram_gb,v=(c=t.meta)==null?void 0:c.peak_ram_optimal_gb;l("#cfg-rec-note").innerHTML=`Optimal ~${Math.round(n/1024)}k → ~${v} GB Spitzenbedarf.<br>Aktuell ~${Math.round(u/1024)}k → ~${p} GB.`,l("#cfg-rec-apply").onclick=()=>{l("#cfg-ctx").value=n},i.style.display="flex"}else i.style.display="none";l("#cfg-modal").style.display="flex"}async function Co(){const e=l("#cfg-model-name").textContent,t=parseInt(l("#cfg-ctx").value)||8192;l("#cfg-save").disabled=!0;try{await $("/api/update_model",{method:"POST",body:JSON.stringify({alias:e,ctx:t})}),w("Gespeichert — aktiv beim nächsten Modell-Start."),l("#cfg-modal").style.display="none",kn()}catch(n){w(n.message,!0)}l("#cfg-save").disabled=!1}async function Lo(e){if(await Ee({title:`„${e}" entladen?`,body:"Das Modell wird aus dem Grafikspeicher geworfen und lädt beim nächsten Aufruf automatisch neu.",confirmLabel:"Entladen"}))try{await $("/api/unload?model="+encodeURIComponent(e),{method:"POST"}),w("Entladen: "+e),setTimeout(kn,600)}catch(t){w(t.message,!0)}}async function Ao(){const e=l("#chat-model").value,t=l("#chat-msg").value.trim();if(!e)return w("Kein Modell vorhanden.",!0);if(!t)return;const n=l("#chat-btn");n.disabled=!0,n.textContent="…";const i=l("#chat-reply");i.style.display="block",i.textContent="(wecke Modell, kann beim Laden kurz dauern…)";try{const s=await $("/api/chat",{method:"POST",body:JSON.stringify({model:e,message:t})});i.textContent=s.reply}catch(s){i.textContent="Fehler: "+s.message}n.disabled=!1,n.textContent="Senden",kn()}const zo={id:"models",mount:ko,onStatus:So,onJobs:$o},dn=new Set;let lt=[],B=null;function Ji(e){dn.add(e),Vt()}const Ue={cpu:[],ram:[],gpu:[]},Do=60;function Po(e){return e==="done"?'<span class="badge b-run">fertig</span>':e==="failed"?'<span class="badge b-err">fehler</span>':e==="canceled"?'<span class="badge">abgebrochen</span>':'<span class="badge b-load">läuft…</span>'}function Oo(e){return e==="done"?"on":e==="failed"||e==="canceled"?"":"load"}function Bo(e){if(e.state!=="running")return null;if(typeof e.progress=="number")return Math.min(100,e.progress);const n=((e.log||[])[e.log.length-1]||"").match(/(\d+(?:\.\d+)?)\s*%/);return n?Math.min(100,parseFloat(n[1])):null}function Ho(e){return e==null||e<0?"":e>=90?`noch ~${Math.round(e/60)} min`:`noch ~${Math.max(1,Math.round(e))} s`}async function Ro(e){try{await $(`/api/jobs/${e}/cancel`,{method:"POST"}),w("Abbruch angefordert…"),Vt()}catch(t){w(t.message,!0)}}async function Fo(e){try{await $(`/api/jobs/${e}`,{method:"DELETE"}),Vt()}catch(t){w(t.message,!0)}}async function No(){try{const e=await $("/api/jobs/clear",{method:"POST"});w(`${e.removed} Einträge entfernt.`),Vt()}catch(e){w(e.message,!0)}}function mi(e,t,n){return`<div class="tile"><div class="t-l">${e}</div><div class="t-v">${t}</div><div class="t-s">${n}</div></div>`}function gi(e,t){return'<div style="display:flex;align-items:flex-end;gap:2px;height:38px;margin-top:10px">'+e.map(n=>`<div style="width:4px;background:var(${t});opacity:.55;height:${Math.max(2,n)}%;border-radius:2px"></div>`).join("")+"</div>"}function ks(){const e=B==null?void 0:B.gpu;return e&&e.vram.total+e.gtt.total>0?(e.vram.used+e.gtt.used)/(e.vram.total+e.gtt.total)*100:0}function Ga(){if(!B)return;Ue.cpu.push(B.cpu.percent),Ue.ram.push(B.ram.percent),Ue.gpu.push(ks());for(const s of["cpu","ram","gpu"])Ue[s].length>Do&&Ue[s].shift();const e=l("#act-kpis");e&&(e.innerHTML=mi("Prozessor (CPU)",`${Math.round(B.cpu.percent)}<small> %</small>`,B.cpu.temp!=null?`${Math.round(B.cpu.temp)}° CPU-Temp`:"Auslastung")+mi("Arbeitsspeicher",`${Math.round(B.ram.percent)}<small> %</small>`,`${rt(B.ram.used)} / ${rt(B.ram.total)}`)+mi("Grafikspeicher",`${Math.round(ks())}<small> %</small>`,B.gpu_temp!=null?`${Math.round(B.gpu_temp)}° GPU-Temp`:"VRAM + GTT"));const t=B.gpu,n=t&&t.vram.total+t.gtt.total>0?`${rt(t.vram.used+t.gtt.used)} / ${rt(t.vram.total+t.gtt.total)}`:"–",i=l("#act-sys");i&&(i.innerHTML=`
|
||
<div class="card-h"><h3>System-Metriken (Bosgame)</h3></div>
|
||
<div class="card-sub">Live-Auslastung deines Mini-PCs, alle 0,5 Sekunden.</div>
|
||
<div class="kv" style="margin-bottom:6px">
|
||
<div class="kv-row"><span class="kv-k">Arbeitsspeicher (RAM)</span><span class="kv-v">${rt(B.ram.used)} / ${rt(B.ram.total)}</span></div>
|
||
<div class="kv-row"><span class="kv-k">Grafikspeicher (VRAM + GTT)</span><span class="kv-v">${n}</span></div>
|
||
<div class="kv-row"><span class="kv-k">Speicherplatz (Disk)</span><span class="kv-v">${Math.round(B.disk.percent)} % belegt</span></div>
|
||
<div class="kv-row"><span class="kv-k">Temperatur (GPU / CPU)</span><span class="kv-v">${B.gpu_temp!=null?Math.round(B.gpu_temp)+"°":"–"} / ${B.cpu.temp!=null?Math.round(B.cpu.temp)+"°":"–"}</span></div>
|
||
</div>
|
||
<div class="grid grid-3" style="margin-top:14px">
|
||
<div><div class="meta text-xs">CPU-Verlauf</div>${gi(Ue.cpu,"--accent")}</div>
|
||
<div><div class="meta text-xs">RAM-Verlauf</div>${gi(Ue.ram,"--purple")}</div>
|
||
<div><div class="meta text-xs">VRAM-Verlauf</div>${gi(Ue.gpu,"--on")}</div>
|
||
</div>`)}function Io(){l("#act-head").innerHTML=`<div class="pagehead"><div>
|
||
<h1>Aktivität</h1>
|
||
<div class="sub">Live-Auslastung und laufende Aufgaben (Downloads, Updates) mit Protokoll.</div></div></div>`,l("#v-activity").innerHTML=`
|
||
<div class="card-h"><h3>Hintergrund-Aufgaben</h3><span class="meta" id="job-count"></span>
|
||
<button class="ghost" id="jobs-clear" style="margin-left:auto;display:none;padding:4px 11px;font-size:12px">Verlauf leeren</button></div>
|
||
<div class="card-sub">Downloads & Updates erscheinen hier mit Live-Protokoll — zum Aufklappen klicken.</div>
|
||
<div id="jobs"></div>
|
||
<div id="jobs-empty" class="empty-c"><div class="e-t">Gerade nichts los.</div><div class="e-s">Alles ruhig — keine laufenden Aufgaben.</div></div>`,l("#v-activity").addEventListener("click",e=>{if(e.target.closest("#jobs-clear")){No();return}const t=e.target.closest("[data-cancel]");if(t){e.stopPropagation(),Ro(t.getAttribute("data-cancel"));return}const n=e.target.closest("[data-del]");if(n){e.stopPropagation(),Fo(n.getAttribute("data-del"));return}const i=e.target.closest(".job-h");if(!i)return;const s=i.getAttribute("data-id");dn.has(s)?dn.delete(s):dn.add(s),Vt()}),B&&Ga()}function Vt(){const e=l("#jobs");if(!e)return;l("#jobs-empty").style.display=lt.length?"none":"flex";const t=lt.filter(s=>s.state==="failed").length,n=lt.filter(s=>s.state!=="running"&&s.state!=="queued").length;l("#job-count").textContent=lt.length?t?t+" Fehler":lt.length+" gesamt":"";const i=l("#jobs-clear");i&&(i.style.display=n?"":"none"),e.innerHTML=lt.map(s=>{const a=dn.has(s.id)?`<div class="log">${g((s.log||[]).join(`
|
||
`))}</div>`:"",r=Bo(s),o=r!=null?`<div class="bar" style="margin-top:8px"><i style="width:${r}%"></i></div>`:"",c=s.state==="running"?Ho(s.eta_s):"",u=s.state==="running"&&s.rate_bps?rt(s.rate_bps)+"/s":"",p=[c,u].filter(Boolean).join(" · "),v=r!=null?`<span class="mono-sm" style="margin-left:auto">${Math.round(r)} %${p?` · ${p}`:""}</span>`:"",f=s.state==="running"?`<button class="ghost" data-cancel="${g(s.id)}" style="${r!=null?"":"margin-left:auto;"}margin-right:2px;padding:2px 9px;font-size:12px">Abbrechen</button>`:`<button class="ghost" data-del="${g(s.id)}" title="Aus Verlauf entfernen" style="margin-left:auto;margin-right:2px;padding:2px 9px;font-size:14px;line-height:1">×</button>`;return`<div class="job"><div class="job-h" data-id="${g(s.id)}">
|
||
<span class="li-dot ${Oo(s.state)}"></span><span class="mid">${g(s.label)}</span>${v}${f}${Po(s.state)}</div>${o}${a}</div>`}).join("")}function Go(e){lt=e||[],Vt()}function Ko(e){B=e,l("#act-sys")&&Ga()}const qo={id:"jobs",mount:Io,onJobs:Go,onSystem:Ko};function Qi(){document.dispatchEvent(new Event("mc:refresh"))}function at(e,t,n,i,s){return`<button class="qa${n==="red"?" qa-danger":""}" id="${e}">
|
||
<span class="qa-ic ${n}">${nt(t)}</span>
|
||
<span class="qa-main"><span class="qa-t">${i}</span><span class="qa-s">${s}</span></span>
|
||
<span class="qa-arrow">${nt("chevron")}</span></button>`}function Uo(){l("#wartung").outerHTML=`<div id="wartung" style="display:flex;flex-direction:column;gap:var(--sp-4)">
|
||
<div class="pagehead"><div>
|
||
<h1>Server & Wartung</h1>
|
||
<div class="sub">Dienste steuern, Updates einspielen und live mitlesen — ganz ohne SSH oder Terminal.</div>
|
||
</div></div>
|
||
|
||
<div class="card" id="w-swap-card">
|
||
<div class="card-h"><h3>LLM-Engine (llama-swap)</h3>
|
||
<span class="chip" id="w-swap-state" style="margin-left:auto">prüfe…</span></div>
|
||
<div class="card-sub">Das Herzstück: llama-swap lädt bei jeder Anfrage automatisch das passende Modell und entlädt die anderen — es läuft immer nur <b>eines</b> gleichzeitig.</div>
|
||
<div class="kv" style="margin-bottom:6px">
|
||
<div class="kv-row"><span class="kv-k">Adresse (für deine Tools)</span><span class="kv-v mono-sm" id="w-swap-url">—</span></div>
|
||
<div class="kv-row"><span class="kv-k">Aktuell geladen</span><span class="kv-v" id="w-swap-loaded">—</span></div>
|
||
<div class="kv-row"><span class="kv-k">Konfigurierte Modelle</span><span class="kv-v" id="w-swap-count">—</span></div>
|
||
</div>
|
||
<div class="hint" style="margin:8px 0 12px"><b>Wie ansteuern?</b> In deinem Tool (Zed/OpenCode) die obige Adresse als OpenAI-Basis-URL eintragen und einen <b>Modellnamen oder eine Rolle</b> angeben (siehe „Verbinden"). Eine einzige Anfrage genügt — das Modell wird automatisch geweckt.</div>
|
||
<div class="btn-row" style="display:flex;align-items:center;gap:10px"><button class="ghost" id="w-swap-check">Verbindung prüfen</button><span id="w-swap-check-res" class="mono-sm"></span></div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-h"><h3>Dienste & Applikation</h3></div>
|
||
<div class="card-sub">Diese Aktionen sind harmlos und passwortlos — es geht nichts dabei verloren.</div>
|
||
${at("w-restart-swap","refresh","teal","LLM-Engine neustarten","Startet llama-swap neu. Geladene Modelle werden kurz entladen (~5 Sek), laden danach automatisch wieder.")}
|
||
${at("w-restart-mc","refresh","blue","Dashboard neustarten","Startet diese Oberfläche neu. Sie trennt kurz und verbindet automatisch wieder.")}
|
||
${at("w-self-update","download","teal","Mission Control aktualisieren","Holt die neueste Dashboard-Version aus Gitea und startet automatisch neu (~10 Sek).")}
|
||
${at("w-update","bolt","blue","LLM-Engine aktualisieren","Lädt die neueste llama.cpp-Version (ROCm) für deine GPU und installiert sie.")}
|
||
${at("w-unload","database","amber","Grafikspeicher leeren","Wirft alle aktuell geladenen Modelle aus dem VRAM. Fragt vorher nach.")}
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-h"><h3>Betriebssystem (Bosgame)</h3></div>
|
||
<div class="card-sub">Tiefe Eingriffe — das Dashboard fragt einmalig nach deinem sudo-Passwort.</div>
|
||
${at("w-os-update","refresh","amber","OS-Updates installieren","Führt apt update & upgrade aus. Kann ein paar Minuten dauern — Fortschritt in der Aktivität.")}
|
||
${at("w-reboot","bolt","red","Server neustarten (Reboot)","Bootet den ganzen Bosgame neu. Alles ist für ~1 Minute offline, auch dieses Dashboard.")}
|
||
</div>
|
||
|
||
<div class="card" id="w-update-card" style="display:none">
|
||
<div class="card-h"><h3>Aktueller Vorgang</h3>
|
||
<button class="ghost" id="w-update-cancel" style="margin-left:auto;display:none;padding:3px 12px">Abbrechen</button>
|
||
<span id="w-update-badge" style="margin-left:10px"></span></div>
|
||
<div class="card-sub" id="w-update-label">—</div>
|
||
<div id="w-update-bar" class="bar" style="display:none;margin-bottom:10px"><i style="width:0%"></i></div>
|
||
<div id="w-update-log" class="log" style="max-height:280px"></div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-h"><h3>Live-Konsole</h3>
|
||
<select id="w-console-sel" style="margin:0 0 0 auto;width:240px">
|
||
<option value="llama-swap">LLM-Engine (llama-swap)</option>
|
||
<option value="mission-control">Dashboard (mission-control)</option>
|
||
<option value="system">System (ganzer Server)</option>
|
||
</select>
|
||
</div>
|
||
<div class="card-sub">Live mitlesen, was der Dienst gerade tut.</div>
|
||
<div id="w-console" class="console">Verbinde…</div>
|
||
</div>
|
||
</div>`,l("#w-restart-swap").addEventListener("click",()=>Ss("llama-swap","LLM-Engine neustarten?","Die Engine startet neu. Geladene Modelle werden kurz entladen (~5 Sekunden), laden danach automatisch wieder.")),l("#w-restart-mc").addEventListener("click",()=>Ss("mission-control","Dashboard neustarten?","Diese Oberfläche trennt sich kurz und verbindet automatisch wieder. Laufende Downloads laufen weiter.")),l("#w-self-update").addEventListener("click",Qo),l("#w-update").addEventListener("click",Zo),l("#w-unload").addEventListener("click",Jo),l("#w-os-update").addEventListener("click",Xo),l("#w-reboot").addEventListener("click",ed),l("#w-console-sel").addEventListener("change",Ms),l("#w-update-cancel").addEventListener("click",Wo),l("#w-swap-check").addEventListener("click",jo),Oi&&Ka(Oi),Ms()}let Oi=null;function Ka(e){const t=l("#w-swap-state");if(!t)return;const n=!!(e!=null&&e.swap_ok);t.textContent=n?"● erreichbar":"● offline",t.style.cssText="margin-left:auto;"+(n?"background:rgba(63,185,80,.12);border-color:rgba(63,185,80,.25);color:#7ee29a":"background:rgba(248,81,73,.12);border-color:rgba(248,81,73,.3);color:#ff9b95"),l("#w-swap-url").textContent=((e==null?void 0:e.swap_url)||"—")+"/v1";const i=(e==null?void 0:e.models)||[],s=i.find(r=>r.state==="running"||r.state==="loading");l("#w-swap-loaded").innerHTML=s?`<b>${g(s.name)}</b> <span class="text-mut">(${g(s.state)})</span>`:'<span class="text-mut">nichts geladen — lädt bei der nächsten Anfrage</span>';const a=i.filter(r=>!r.incomplete).length;l("#w-swap-count").textContent=a?`${a} bereit`:"keine"}async function jo(){const e=l("#w-swap-check-res");e.textContent="prüfe…",e.style.color="var(--mut)";try{const t=await $("/api/integration/test");t.ok?(e.style.color="#7ee29a",e.textContent=`✓ Engine antwortet — ${t.models.length} Modell(e): ${t.models.join(", ")}`):(e.style.color="#ff9b95",e.textContent="✗ keine Verbindung: "+t.error)}catch(t){e.style.color="#ff9b95",e.textContent="✗ "+t.message}}function Vo(e){Oi=e,Ka(e)}async function Wo(){if(Le&&await Ee({title:Le.label+" abbrechen?",body:"Der laufende Vorgang wird gestoppt. Bei einem Download bleibt eine unvollständige Datei zurück (kannst du später neu starten).",confirmLabel:"Abbrechen",danger:!0}))try{await $(`/api/jobs/${Le.id}/cancel`,{method:"POST"}),w("Abbruch angefordert…")}catch(e){w(e.message,!0)}}let St=null;function Ms(){St&&(St.close(),St=null);const e=l("#w-console-sel").value,t=l("#w-console");t.textContent="Verbinde mit "+e+`…
|
||
`;const n=location.protocol==="https:"?"wss:":"ws:";St=new WebSocket(`${n}//${location.host}/api/logs/${e}?token=${encodeURIComponent(Da())}`),St.onmessage=i=>{t.textContent+=i.data,t.scrollTop=t.scrollHeight},St.onclose=()=>{t.textContent+=`
|
||
— Verbindung getrennt —`}}async function Ss(e,t,n){if(await Ee({title:t,body:n,confirmLabel:"Neustarten"}))try{await $(`/api/service/${e}/restart`,{method:"POST"}),w("Neustart ausgelöst: "+e),setTimeout(Qi,2e3)}catch(i){w(i.message,!0)}}async function Jo(){if(await Ee({title:"Grafikspeicher leeren?",body:"Alle geladenen Modelle werden entladen. Sie laden beim nächsten Aufruf automatisch neu — es geht nichts verloren.",confirmLabel:"Leeren"}))try{await $("/api/unload",{method:"POST"}),w("Grafikspeicher geleert."),setTimeout(Qi,600)}catch(e){w(e.message,!0)}}async function Qo(){if(await Ee({title:"Mission Control aktualisieren?",body:"Holt die neueste Dashboard-Version aus Gitea und startet automatisch neu. Die Seite trennt kurz und verbindet von selbst wieder (~10 Sek)."}))try{const e=await $("/api/self-update",{method:"POST"});w("Update läuft — Dashboard startet gleich neu."),Ji(e.job_id)}catch(e){w(e.message,!0)}}let Le=null;const $s=new Set;function qa(e,t){Le={id:e,label:t};const n=l("#w-update-card");n&&(n.style.display="",l("#w-update-label").textContent=t+" läuft…",l("#w-update-badge").innerHTML='<span class="badge b-load">läuft…</span>',l("#w-update-log").textContent="Starte…",n.scrollIntoView({behavior:"smooth",block:"nearest"}))}function Yo(e){if(!Le)return;const t=(e||[]).find(v=>v.id===Le.id),n=l("#w-update-card");if(!t||!n)return;n.style.display="";const i=l("#w-update-log");i&&(i.textContent=(t.log||[]).join(`
|
||
`),i.scrollTop=i.scrollHeight);const s=t.state==="done",a=t.state==="failed",r=t.state==="canceled",o=s||a||r,c=l("#w-update-badge");c&&(c.innerHTML=s?'<span class="badge b-run">✓ fertig</span>':a?'<span class="badge b-err">✗ fehlgeschlagen</span>':r?'<span class="badge">abgebrochen</span>':'<span class="badge b-load">läuft…</span>');let u=null;if(!o)if(typeof t.progress=="number")u=t.progress;else{const f=((t.log||[])[t.log.length-1]||"").match(/(\d+(?:\.\d+)?)\s*%/);f&&(u=parseFloat(f[1]))}const p=l("#w-update-bar");p&&(u!=null?(p.style.display="",p.querySelector("i").style.width=Math.min(100,u)+"%"):p.style.display="none"),l("#w-update-cancel").style.display=o?"none":"",o&&(l("#w-update-label").textContent=Le.label+(s?" — erfolgreich abgeschlossen.":r?" — abgebrochen.":" — fehlgeschlagen (siehe Protokoll)."),$s.has(t.id)||($s.add(t.id),w(s?"✓ "+Le.label+" abgeschlossen.":r?Le.label+" abgebrochen.":"✗ "+Le.label+" fehlgeschlagen.",a),s&&setTimeout(Qi,800)))}async function Zo(){if(await Ee({title:"LLM-Engine aktualisieren?",body:"Lädt die neueste llama.cpp-ROCm-Version und installiert sie. Die Engine ist während des Updates kurz nicht erreichbar."}))try{const e=await $("/api/update",{method:"POST"});w("LLM-Engine-Update läuft."),Ji(e.job_id),qa(e.job_id,"LLM-Engine-Update")}catch(e){w(e.message,!0)}}async function Xo(){if(!await Ee({title:"OS-Updates installieren?",body:"Führt <code>apt update & upgrade</code> aus. Das kann ein paar Minuten dauern; der Fortschritt erscheint in der Aktivität."}))return;const e=await Oa({title:"sudo-Passwort",body:"Für die System-Updates wird einmalig dein sudo-Passwort gebraucht.",placeholder:"sudo-Passwort",password:!0,confirmLabel:"Installieren"});if(e)try{const t=await $("/api/os-update",{method:"POST",body:JSON.stringify({password:e})});w("OS-Update gestartet."),Ji(t.job_id),qa(t.job_id,"OS-Update")}catch(t){w(t.message,!0)}}async function ed(){if(!await Ee({title:"Server wirklich neu starten?",body:"Der ganze Bosgame startet physisch neu. Alles ist für ~1 Minute offline — auch dieses Dashboard.",confirmLabel:"Reboot",danger:!0}))return;const e=await Oa({title:"sudo-Passwort",body:"Für den Reboot wird einmalig dein sudo-Passwort gebraucht.",placeholder:"sudo-Passwort",password:!0,confirmLabel:"Jetzt neustarten",danger:!0});if(e)try{await $("/api/reboot",{method:"POST",body:JSON.stringify({password:e})}),w("Reboot ausgelöst — bis gleich.")}catch(t){w(t.message,!0)}}const td={id:"server",mount:Uo,onJobs:Yo,onStatus:Vo},nd="Kontext = das Kurzzeitgedächtnis des Modells. Größer = merkt sich mehr, braucht aber mehr Speicher und wird etwas langsamer.",Ua="GGUF ist das lokal lauffähige Dateiformat. Unsere Engine lädt nur GGUF — Repos ohne GGUF-Datei kann sie nicht nutzen.",id="MoE (Mixture of Experts): viele Parameter fürs Wissen, aber pro Wort ist nur ein kleiner Teil aktiv → fast große-Modell-Qualität bei hohem Tempo. Ideal für deine APU.",sd={vision:"Bilder",coder:"Coden",reasoning:"Logik",agent:"Agenten & Tools",scout:"Allrounder",reviewer:"Review",manager:"Manager"},ad=[{label:"Bilder",kw:["-vl-","-vl","vision","llava","multimodal","-mm-","pixtral"]},{label:"Coden",kw:["coder","-code-","code-","codestral","starcoder"]},{label:"Logik",kw:["-r1","deepseek-r1","reasoning","qwq","magistral","-think","thinking","-o1"]},{label:"Agenten & Tools",kw:["hermes","-tool","command-r","watt","-fc-","function"]}];function ja(e){const t=(e||"").toLowerCase();for(const n of ad)if(n.kw.some(i=>t.includes(i)))return n.label;return"Allrounder"}function rd(e=""){const t=e.toLowerCase();return/\d+x\d+(\.\d+)?b/.test(t)||new RegExp("(?<![a-z])a\\d+(\\.\\d+)?b\\b").test(t)||t.includes("mixtral")||/\bmoe\b/.test(t)}function ld(e=""){const t=e.toLowerCase().match(new RegExp("(?<![a-z])a(\\d+(?:\\.\\d+)?)b\\b"));return t?t[1]:null}function Yi(e){if(!rd(e))return"";const t=ld(e);return`<span class="chip" title="${g(id)}">MoE${t?` · ~${t}B aktiv`:""}</span>`}function Va(e){return e?`<span class="chip">${g(e)}</span>`:""}const od=[{id:"",label:"Alle"},{id:"coder",label:"Coder"},{id:"scout",label:"Scout"},{id:"vision",label:"Vision"},{id:"manager",label:"Manager"},{id:"reviewer",label:"Reviewer"}];let nn=null,sn=[],Ae=null,Dt="",Mn=[],Wa=null,cn=[];const dd={perfect:0,marginal:1,too_tight:2},Sn=e=>e==="perfect"?"ok":e==="marginal"?"warn":"bad",cd=e=>e==="perfect"?"Passt locker":e==="marginal"?"Wird knapp":"Zu groß";function ud(){const e=l(".view[data-view='cookbook']");e.innerHTML=`
|
||
<div class="pagehead"><div>
|
||
<h1>Modell-Cookbook</h1>
|
||
<div class="sub">Wofür willst du es nutzen? Wir empfehlen das passende Setup für deine Hardware.</div>
|
||
</div></div>
|
||
|
||
<div class="card-h" style="align-items:center"><h3>Wofür möchtest du es nutzen?</h3>
|
||
<span class="chip" id="cb-hw" style="margin-left:auto">deine Hardware</span>
|
||
<button class="ghost" id="cb-new-open" style="margin-left:10px">+ Eigenes Setup</button></div>
|
||
<div class="grid grid-3" id="cb-recipes">
|
||
<div class="empty" style="grid-column:1/-1;text-align:center">Lade Setups…</div>
|
||
</div>
|
||
|
||
<div class="card-h" style="align-items:center;margin-top:20px"><h3>Aktuell beste Modelle für dein System</h3>
|
||
<span class="chip" id="cb-disc-when" style="margin-left:auto"></span>
|
||
<button class="ghost" id="cb-disc-refresh" style="margin-left:10px">Aktualisieren</button></div>
|
||
<div class="card-sub" style="margin:-6px 0 10px">Automatisch aus vertrauenswürdigen Quellen (HuggingFace) — laufend aktuell, gefiltert auf das, was auf deine Hardware passt.</div>
|
||
<div id="cb-discover"><div class="empty" style="text-align:center">Lade aktuelle Empfehlungen…</div></div>
|
||
|
||
<details class="guide-acc card" style="padding:0;overflow:hidden;margin-top:18px">
|
||
<summary>Profi-Modus: HuggingFace direkt durchsuchen</summary>
|
||
<div class="acc-body">
|
||
<div class="flex gap-3" style="margin-bottom:8px">
|
||
<input id="cb-search" placeholder="Suchen oder HuggingFace-Link/Repo einfügen (z.B. unsloth/MiniMax-M3-GGUF)…" style="margin:0;flex:1">
|
||
<button class="primary" id="cb-btn-search" style="white-space:nowrap">Suchen</button>
|
||
</div>
|
||
<div class="flex gap-2 items-center" style="flex-wrap:wrap">
|
||
<div id="cb-filters" class="flex gap-2" style="flex-wrap:wrap;flex:1"></div>
|
||
<select id="cb-sort" style="width:auto;margin:0;padding:7px 10px;font-family:var(--sans)">
|
||
<option value="downloads">Beliebteste Downloads</option>
|
||
<option value="likes">Am beliebtesten (Likes)</option>
|
||
<option value="lastModified">Zuletzt aktualisiert</option>
|
||
<option value="trendingScore">Im Trend</option>
|
||
</select>
|
||
</div>
|
||
<div class="legend" style="margin:14px 0">
|
||
<span><i style="background:var(--on)"></i>passt locker</span>
|
||
<span><i style="background:var(--warn)"></i>läuft, aber knapp</span>
|
||
<span><i style="background:var(--err)"></i>zu groß für deinen Speicher</span>
|
||
</div>
|
||
<div class="hint" style="margin:-6px 0 14px">Es werden nur Modelle im GGUF-Format ${Jn(Ua)} gezeigt — nur die laufen lokal.</div>
|
||
<div class="grid grid-3" id="cb-grid"></div>
|
||
</div>
|
||
</details>
|
||
|
||
<div id="cb-recipe-modal" class="modal-overlay" style="display:none">
|
||
<div class="modal-card" style="max-width:580px">
|
||
<button id="cb-r-close" class="ghost" style="position:absolute;top:14px;right:14px">Schließen</button>
|
||
<h3 id="cb-r-title">Setup</h3>
|
||
<p class="text-mut text-sm" id="cb-r-desc" style="margin:-4px 0 16px"></p>
|
||
<div id="cb-r-models"></div>
|
||
<div class="hint" id="cb-r-note" style="margin-top:12px"></div>
|
||
<button class="primary" id="cb-r-install" style="width:100%;margin-top:8px">Komplettes Setup installieren</button>
|
||
<div class="hint" style="margin-top:8px">Lädt alle Modelle herunter und pflegt sie mit optimalem Kontext ein. Fortschritt in der Aktivität.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="cb-new-modal" class="modal-overlay" style="display:none">
|
||
<div class="modal-card" style="max-width:640px">
|
||
<button id="cb-new-close" class="ghost" style="position:absolute;top:14px;right:14px">Schließen</button>
|
||
<h3 id="cb-new-modal-title">Eigenes Setup erstellen</h3>
|
||
<p class="text-mut text-sm" style="margin:-4px 0 16px">Stell dir aus beliebigen Modellen ein eigenes Setup zusammen — es erscheint danach mit Hardware-Ampel hier im Cookbook.</p>
|
||
<label>Titel</label>
|
||
<input id="cb-new-title" placeholder="z.B. Mein Schreib-Stack">
|
||
<label>Kurzbeschreibung (optional)</label>
|
||
<input id="cb-new-desc" placeholder="Wofür ist dieses Setup gut?">
|
||
<label style="margin-top:10px">Modelle</label>
|
||
<div id="cb-new-models"></div>
|
||
<button class="ghost" id="cb-new-add" style="margin-top:8px">+ Modell hinzufügen</button>
|
||
<div class="hint" style="margin-top:10px">Die <b>Repo-ID</b> findest du über die <b>Profi-Suche</b> weiter unten (z.B. <code>unsloth/Qwen3-8B-GGUF</code>). Die Modellgröße erkennen wir automatisch aus dem Namen.</div>
|
||
<button class="primary" id="cb-new-save" style="width:100%;margin-top:14px">Setup speichern</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="cb-modal" class="modal-overlay" style="display:none">
|
||
<div class="modal-card" style="max-width:560px">
|
||
<button id="cb-modal-close" class="ghost" style="position:absolute;top:14px;right:14px">Schließen</button>
|
||
<h3 id="cb-m-title">Modell</h3>
|
||
<p class="mono-sm" id="cb-m-repo" style="margin:-4px 0 16px">repo/name</p>
|
||
<label>Quantisierung (GGUF-Datei) wählen</label>
|
||
<select id="cb-m-files"></select>
|
||
<div class="hint" id="cb-m-loading" style="display:none">Lade Dateien von HuggingFace…</div>
|
||
<div class="row" style="margin-top:4px">
|
||
<div><label>Rolle (optional)</label><input id="cb-m-alias" placeholder="z.B. coder, vision, scout"></div>
|
||
<div><label>Kontext-Größe ${Jn(nd)}</label><input id="cb-m-ctx" type="number" value="8192"></div>
|
||
</div>
|
||
<div class="hint" id="cb-m-ctx-rec" style="margin:-6px 0 14px"></div>
|
||
<div id="cb-m-fit" class="tile" style="display:flex;justify-content:space-between;align-items:center;margin:8px 0 18px">
|
||
<div><div style="font-size:13px">Ressourcen-Check</div>
|
||
<div class="hint" id="cb-m-fit-text" style="margin:4px 0 0">Berechne…</div></div>
|
||
<span id="cb-m-fit-badge"></span>
|
||
</div>
|
||
<button class="primary" id="cb-m-download" style="width:100%">Herunterladen & Einpflegen</button>
|
||
</div>
|
||
</div>`,Ja(),l("#cb-btn-search").addEventListener("click",Rn),l("#cb-search").addEventListener("keydown",t=>{t.key==="Enter"&&Rn()}),l("#cb-sort").addEventListener("change",()=>{(l("#cb-search").value.trim()||Dt)&&Rn()}),l("#cb-modal-close").addEventListener("click",()=>l("#cb-modal").style.display="none"),l("#cb-modal").addEventListener("click",t=>{t.target.id==="cb-modal"&&(l("#cb-modal").style.display="none")}),l("#cb-m-download").addEventListener("click",Td),l("#cb-m-files").addEventListener("change",Sd),l("#cb-m-ctx").addEventListener("change",Cs),window.cbSetCtx=t=>{l("#cb-m-ctx").value=t,Cs()},l("#cb-r-close").addEventListener("click",()=>l("#cb-recipe-modal").style.display="none"),l("#cb-recipe-modal").addEventListener("click",t=>{t.target.id==="cb-recipe-modal"&&(l("#cb-recipe-modal").style.display="none")}),l("#cb-new-open").addEventListener("click",gd),l("#cb-new-close").addEventListener("click",()=>l("#cb-new-modal").style.display="none"),l("#cb-new-modal").addEventListener("click",t=>{t.target.id==="cb-new-modal"&&(l("#cb-new-modal").style.display="none")}),l("#cb-new-add").addEventListener("click",()=>l("#cb-new-models").insertAdjacentHTML("beforeend",Xi())),l("#cb-new-models").addEventListener("click",t=>{const n=t.target.closest(".cb-nm-del");n&&n.closest(".cb-nm-row").remove()}),l("#cb-new-save").addEventListener("click",wd),l("#cb-disc-refresh").addEventListener("click",()=>Es(!0)),l("#cb-discover").addEventListener("click",t=>{const n=t.target.closest("[data-inst]");n&&vd(n.getAttribute("data-inst"),n.getAttribute("data-role"),n.getAttribute("data-pb"),n)}),Qa(),Zi(),Es()}function fd(e){const t=Date.now()/1e3-e;return t<90?"gerade eben":t<3600?`vor ${Math.round(t/60)} min`:t<86400?`vor ${Math.round(t/3600)} h`:`vor ${Math.round(t/86400)} Tg.`}async function Es(e=!1){const t=l("#cb-discover"),n=l("#cb-disc-when"),i=l("#cb-disc-refresh");e&&(i.disabled=!0,i.textContent="Suche…",t.innerHTML='<div class="empty" style="text-align:center">Frage Quellen ab…</div>');try{const s=await $("/api/cookbook/discover"+(e?"?force=true":""));n&&(n.textContent=s.updated?`aktualisiert ${fd(s.updated)}`:""),pd(s)}catch(s){t.innerHTML=`<div class="alert warn" style="margin:0"><span class="a-dot"></span><span>Empfehlungen nicht ladbar: ${g(s.message)}</span></div>`}i.disabled=!1,i.textContent="Aktualisieren"}function pd(e){const t=l("#cb-discover"),n=e.categories||[];if(!n.length){t.innerHTML='<div class="empty" style="text-align:center">Keine passenden Modelle gefunden.</div>';return}t.innerHTML=n.map(i=>`
|
||
<div style="margin-bottom:8px">
|
||
<div class="flex items-center gap-2" style="margin:6px 0 8px"><span class="text-accent">${nt(i.icon)}</span>
|
||
<h4 style="margin:0;font-size:13.5px;font-weight:600">${g(i.title)}</h4></div>
|
||
<div class="grid grid-3">${i.models.map(s=>hd(s,i.recommended)).join("")}</div>
|
||
</div>`).join("")}function hd(e,t){const n=(e.requirements||[]).map(o=>`<div class="li-sub" style="color:var(--warn);margin-top:3px">⚠ ${g(o)}</div>`).join(""),i=t&&e.repo===t,s=i?'<div class="cb-best-tag">★ Beste Wahl für dein System</div>':"",a=`<div class="flex gap-2" style="flex-wrap:wrap;margin-top:7px">${Va(sd[e.role]||ja(e.repo))}${Yi(e.repo)}</div>`,r=e.installed?`<span class="fit-badge ok" title="bereits eingerichtet als ${g(e.installed)}">✓ installiert</span>`:`<button class="primary" data-inst="${g(e.repo)}" data-role="${g(e.role)}" data-pb="${e.params_b}"
|
||
style="padding:6px 12px;font-size:12.5px">Installieren</button>`;return`<div class="card${i?" res-best":""}" style="display:flex;flex-direction:column;position:relative">
|
||
${s}
|
||
<div class="flex justify-between" style="align-items:flex-start;gap:8px">
|
||
<div style="min-width:0"><h3 style="margin:0;font-size:14px;font-weight:500;word-break:break-word">${g(e.name)}</h3>
|
||
<div class="text-xs text-mut" style="margin-top:3px">${g(e.author)} · ${e.params_b}B</div></div>
|
||
<span class="fit-badge ${Sn(e.fit.level)}">${g(e.fit.text)}</span>
|
||
</div>
|
||
${a}
|
||
<div class="mono-sm text-mut" style="margin-top:8px;font-size:11.5px">${Ya(e.fit)} · ⬇ ${(e.downloads||0).toLocaleString()}</div>
|
||
${n}
|
||
<div style="flex:1;min-height:8px"></div>
|
||
<div class="flex justify-end" style="margin-top:10px">${r}</div>
|
||
</div>`}async function vd(e,t,n,i){var s;i.disabled=!0,i.textContent="Starte…";try{await $("/api/cookbook/install-model",{method:"POST",body:JSON.stringify({repo:e,role:t,params_b:parseFloat(n)||7,quant:"Q4_K_M",hf_token:_n()})}),w("Download gestartet — siehe Aktivität."),(s=document.querySelector(".nav-item[data-view='activity']"))==null||s.click()}catch(a){w("Fehler: "+a.message,!0),i.disabled=!1,i.textContent="Installieren"}}async function Zi(){try{const e=await $("/api/cookbook/recipes");Mn=e.recipes||[],Wa=e.recommended_id||null,md()}catch(e){l("#cb-recipes").innerHTML=`<div class="alert err" style="grid-column:1/-1">Setups nicht ladbar: ${g(e.message)}</div>`}}function md(){l("#cb-recipes").innerHTML=Mn.map(e=>{const t=e.id===Wa,n=t?'<div class="cb-best-tag">★ Beste Wahl für dein System</div>':e.user?'<div class="cb-best-tag" style="background:var(--act);color:#fff">Dein Setup</div>':"",i=e.user?`
|
||
<span class="cb-editr" data-editr="${g(e.id)}" title="Setup bearbeiten"
|
||
style="position:absolute;top:10px;right:36px;color:var(--accent);cursor:pointer;font-size:14px;line-height:1">✎</span>
|
||
<span class="cb-delr" data-delr="${g(e.id)}" title="Setup löschen"
|
||
style="position:absolute;top:9px;right:12px;color:var(--err);cursor:pointer;font-size:18px;line-height:1">×</span>`:"";return`<button class="card-btn${t?" cb-best":""}" data-recipe="${g(e.id)}" style="position:relative">
|
||
${i}${n}
|
||
<div class="flex justify-between items-center">
|
||
<span class="flex items-center gap-2"><span class="text-accent">${nt(e.icon)}</span>
|
||
<h3 style="margin:0;font-size:15px">${g(e.title)}</h3></span>
|
||
<span class="fit-badge ${Sn(e.fit_level)}">${cd(e.fit_level)}</span>
|
||
</div>
|
||
<p>${g(e.desc)}</p>
|
||
<div class="text-xs text-mut">${e.models.length} Modell${e.models.length>1?"e":""} im Setup</div>
|
||
</button>`}).join(""),l("#cb-recipes").querySelectorAll("[data-recipe]").forEach(e=>e.addEventListener("click",()=>xd(e.getAttribute("data-recipe")))),l("#cb-recipes").querySelectorAll("[data-delr]").forEach(e=>e.addEventListener("click",t=>{t.stopPropagation(),yd(e.getAttribute("data-delr"))})),l("#cb-recipes").querySelectorAll("[data-editr]").forEach(e=>e.addEventListener("click",t=>{t.stopPropagation(),bd(e.getAttribute("data-editr"))}))}function Xi(){return`<div class="cb-nm-row flex gap-2" style="margin-bottom:8px;align-items:center">
|
||
<input class="cb-nm-repo" placeholder="Repo, z.B. unsloth/Qwen3-8B-GGUF" style="flex:2;margin:0">
|
||
<input class="cb-nm-role" placeholder="Rolle (z.B. coder)" style="flex:1;margin:0">
|
||
<select class="cb-nm-quant" style="width:auto;margin:0;font-family:var(--sans)">
|
||
<option>Q4_K_M</option><option>Q5_K_M</option><option>Q6_K</option><option>Q8_0</option><option>Q3_K_M</option>
|
||
</select>
|
||
<button class="ghost del cb-nm-del" title="Zeile entfernen" style="margin:0;padding:6px 10px">×</button>
|
||
</div>`}let Ct=null;function gd(){Ct=null,l("#cb-new-title").value="",l("#cb-new-desc").value="",l("#cb-new-models").innerHTML=Xi(),l("#cb-new-modal-title").textContent="Eigenes Setup erstellen",l("#cb-new-save").textContent="Setup speichern",l("#cb-new-modal").style.display="flex"}function bd(e){const t=Mn.find(n=>n.id===e);t&&(Ct=e,l("#cb-new-title").value=t.title,l("#cb-new-desc").value=t.desc||"",l("#cb-new-models").innerHTML=t.models.map(n=>{const i=document.createElement("div");i.innerHTML=Xi();const s=i.firstElementChild;s.querySelector(".cb-nm-repo").value=n.repo,s.querySelector(".cb-nm-role").value=n.role;const a=s.querySelector(".cb-nm-quant");return a&&[...a.options].forEach(r=>{r.value===n.quant&&(r.selected=!0)}),s.outerHTML}).join(""),l("#cb-new-modal-title").textContent="Setup bearbeiten",l("#cb-new-save").textContent="Änderungen speichern",l("#cb-new-modal").style.display="flex")}async function wd(){const e=l("#cb-new-title").value.trim();if(!e)return w("Bitte einen Titel angeben.",!0);const t=[...l("#cb-new-models").querySelectorAll(".cb-nm-row")].map(i=>({repo:i.querySelector(".cb-nm-repo").value.trim(),role:i.querySelector(".cb-nm-role").value.trim()||"modell",quant:i.querySelector(".cb-nm-quant").value})).filter(i=>i.repo);if(!t.length)return w("Mindestens ein Modell (Repo) angeben.",!0);const n=l("#cb-new-save");n.disabled=!0,n.textContent="Speichere…";try{const i=JSON.stringify({title:e,desc:l("#cb-new-desc").value.trim(),models:t});Ct?(await $("/api/cookbook/user-recipe/"+encodeURIComponent(Ct),{method:"PUT",body:i}),w("Setup aktualisiert.")):(await $("/api/cookbook/user-recipe",{method:"POST",body:i}),w("Setup gespeichert.")),Ct=null,l("#cb-new-modal").style.display="none",Zi()}catch(i){w("Fehler: "+i.message,!0)}n.disabled=!1,n.textContent=Ct?"Änderungen speichern":"Setup speichern"}async function yd(e){const t=Mn.find(n=>n.id===e);if(await Ee({title:`„${t?t.title:e}" löschen?`,body:"Dein eigenes Setup wird aus dem Cookbook entfernt. Bereits installierte Modelle bleiben unangetastet.",confirmLabel:"Löschen",danger:!0}))try{await $("/api/cookbook/user-recipe/"+encodeURIComponent(e),{method:"DELETE"}),w("Setup gelöscht."),Zi()}catch(n){w(n.message,!0)}}function xd(e){const t=Mn.find(s=>s.id===e);if(!t)return;l("#cb-r-title").textContent=t.title,l("#cb-r-desc").textContent=t.desc,l("#cb-r-models").innerHTML=`<div class="list">${t.models.map(s=>`
|
||
<div class="li" style="align-items:flex-start">
|
||
<div class="li-main">
|
||
<div class="flex items-center gap-2" style="flex-wrap:wrap">
|
||
<span class="li-id">${g(s.name)}</span><span class="tag text">${g(s.role)}</span>${Yi(s.repo)}</div>
|
||
<div class="li-sub">${g(s.why)}</div>
|
||
<div class="li-sub mono-sm">~${s.fit.req_gb.toFixed(1)} GB · ~${Math.round(s.fit.tps)} Tok/s · optimal ~${Math.round(s.optimal_ctx/1024)}k Kontext</div>
|
||
</div>
|
||
<span class="fit-badge ${Sn(s.fit.level)}">${g(s.fit.text)}</span>
|
||
</div>`).join("")}</div>`;const n=Math.max(...t.models.map(s=>s.fit.req_gb));l("#cb-r-note").innerHTML=`Größter Spitzenbedarf: <b>~${n.toFixed(1)} GB</b>. Es läuft immer nur <b>ein</b> Modell gleichzeitig — das größte bestimmt, ob das Setup passt (kein Summieren).`;const i=l("#cb-r-install");i.className=t.fit_level==="too_tight"?"primary warn":"primary",i.textContent=t.fit_level==="too_tight"?"Trotzdem installieren (zu groß)":"Komplettes Setup installieren",i.onclick=()=>_d(t.id),l("#cb-recipe-modal").style.display="flex"}async function _d(e){var n;const t=l("#cb-r-install");t.disabled=!0,t.textContent="Starte…";try{const i=await $("/api/cookbook/install-recipe",{method:"POST",body:JSON.stringify({recipe_id:e,hf_token:_n()})});w(`Setup wird installiert (${i.count} Modelle) — siehe Aktivität.`),l("#cb-recipe-modal").style.display="none",(n=document.querySelector(".nav-item[data-view='activity']"))==null||n.click()}catch(i){w("Fehler: "+i.message,!0)}t.disabled=!1,t.textContent="Komplettes Setup installieren"}function Ja(){l("#cb-filters").innerHTML=od.map(e=>`<button class="${Dt===e.id?"primary":"ghost"}" data-f="${e.id}" style="border-radius:999px">${e.label}</button>`).join(""),l("#cb-filters").querySelectorAll("[data-f]").forEach(e=>e.addEventListener("click",()=>{Dt=e.getAttribute("data-f"),Ja(),Rn()}))}function Qa(){var t;const e=l("#cb-hw");e&&(t=nn==null?void 0:nn.ram)!=null&&t.total&&(e.innerHTML=`${nt("cpu")}${Math.round(nn.ram.total/1024**3)} GB Speicher`)}function Ya(e){return`~${e.req_gb.toFixed(1)} GB · ~${Math.round(e.tps)} Tok/s`}function kd(e){e=e.trim();const t=e.match(/huggingface\.co\/([^\s/]+\/[^\s/?#]+)/i);return t?t[1]:/^[\w.-]+\/[\w.-]+$/.test(e)?e:null}async function Rn(){var s;const e=l("#cb-search").value.trim(),t=kd(e);let n=e;if(Dt&&!t&&(n=n?n+" "+Dt:Dt),!n&&!t){l("#cb-grid").innerHTML="";return}const i=l("#cb-btn-search");i.disabled=!0,i.textContent="Lade…",l("#cb-grid").innerHTML='<div class="empty" style="grid-column:1/-1;text-align:center">Suche auf HuggingFace…</div>';try{if(t)sn=[{id:t,author:t.split("/")[0],downloads:0}],Ts(sn);else{const a=((s=l("#cb-sort"))==null?void 0:s.value)||"downloads",r=`https://huggingface.co/api/models?search=${encodeURIComponent(n)}&filter=gguf&sort=${encodeURIComponent(a)}&direction=-1&limit=40`;sn=await(await fetch(r)).json(),Ts(sn)}}catch(a){l("#cb-grid").innerHTML=`<div class="alert err" style="grid-column:1/-1">${g(a.message)}</div>`}i.disabled=!1,i.textContent="Suchen"}function Ts(e){const t=l("#cb-grid");if(cn=[],!(e!=null&&e.length)){t.innerHTML='<div class="empty" style="grid-column:1/-1;text-align:center">Keine GGUF-Modelle gefunden.</div>';return}t.innerHTML=e.map((n,i)=>`
|
||
<div class="card" style="display:flex;flex-direction:column;cursor:pointer" data-res="${i}">
|
||
<div class="flex justify-between" style="align-items:flex-start;gap:8px">
|
||
<div style="min-width:0"><h3 style="margin:0;font-size:14.5px;font-weight:500;word-break:break-word">${g(n.id.split("/").pop())}</h3>
|
||
<div class="text-xs text-mut" style="margin-top:3px">${g(n.author||"")}</div></div>
|
||
<span class="fit-badge warn" id="cb-b-${i}">prüfe…</span>
|
||
</div>
|
||
<div class="flex gap-2" style="flex-wrap:wrap;margin-top:7px">${Va(ja(n.id))}${Yi(n.id)}</div>
|
||
<div style="flex:1;margin-top:12px"></div>
|
||
<div class="flex justify-between items-center text-xs text-mut" style="border-top:1px solid var(--line);padding-top:11px">
|
||
<span class="mono-sm" id="cb-m-${i}">Hardware-Fit…</span>
|
||
<span class="mono-sm">⬇ ${(n.downloads||0).toLocaleString()}</span></div>
|
||
</div>`).join(""),t.querySelectorAll("[data-res]").forEach(n=>n.addEventListener("click",()=>Ed(+n.getAttribute("data-res")))),e.forEach((n,i)=>Md(i,n.id))}async function Md(e,t){var n;try{const i=await $("/api/cookbook/analyze",{method:"POST",body:JSON.stringify({repo_id:t,ctx:8192})}),s=l("#cb-b-"+e),a=l("#cb-m-"+e);if(!s||!a)return;if(!((n=i.files)!=null&&n.length)){s.className="fit-badge",s.style.cssText="background:rgba(139,151,165,.14);color:var(--mut)",s.title=Ua,s.textContent="kein GGUF",a.textContent="—",cn[e]=null,bi();return}let r=i.files.find(o=>{var c;return(c=o.quant)==null?void 0:c.includes("Q4_K_M")})||i.files[0];s.className="fit-badge "+Sn(r.fit.level),s.textContent=r.fit.text,a.textContent=Ya(r.fit)+" · "+(r.quant||"GGUF"),cn[e]=r.fit.level,bi()}catch{const i=l("#cb-b-"+e);i&&(i.className="fit-badge bad",i.textContent="Fehler"),cn[e]=null,bi()}}function bi(){const e=l("#cb-grid");if(!e)return;let t=-1,n=99;if(cn.forEach((s,a)=>{if(s==null)return;const r=dd[s]??99;r<n&&(n=r,t=a)}),e.querySelectorAll("[data-res]").forEach(s=>{var a;s.classList.remove("res-best"),(a=s.querySelector(".cb-best-tag"))==null||a.remove()}),t<0||n>1)return;const i=e.querySelector(`[data-res="${t}"]`);i&&(i.classList.add("res-best"),i.insertAdjacentHTML("afterbegin",'<div class="cb-best-tag">★ Beste Wahl für dein System</div>'))}function es(){const e=l("#cb-m-files").value,t=Ae==null?void 0:Ae.files.find(a=>a.filename===e);if(!t){l("#cb-m-fit").style.display="none";return}l("#cb-m-fit").style.display="flex",l("#cb-m-fit-text").innerHTML=`Bedarf: <b>~${t.fit.req_gb.toFixed(1)} GB</b> · ${Ae.params_b}B · ${g(t.quant)} · ~${Math.round(t.fit.tps)} Tok/s`,l("#cb-m-fit-badge").innerHTML=`<span class="fit-badge ${Sn(t.fit.level)}">${g(t.fit.text)}</span>`;const n=t.optimal_ctx,i=l("#cb-m-ctx-rec");if(n){const a=l("#cb-m-ctx");a&&(a.value==="8192"||a.value==="")&&(a.value=n)}i&&(i.innerHTML=n?`Empfohlener Kontext für deine Hardware: <b>~${Math.round(n/1024)}k</b> — <a href="#" onclick="event.preventDefault();window.cbSetCtx(${n})">übernehmen</a>`:"");const s=l("#cb-m-download");t.fit.level==="too_tight"?(s.className="primary warn",s.textContent="Trotzdem holen (zu groß)"):(s.className="primary",s.textContent="Herunterladen & Einpflegen")}function Sd(){es()}async function Cs(){if(!Ae)return;const e=parseInt(l("#cb-m-ctx").value)||8192,t=l("#cb-m-files").value;try{Ae=await $("/api/cookbook/analyze",{method:"POST",body:JSON.stringify({repo_id:Ae.repo,ctx:e})}),l("#cb-m-files").value=t,es()}catch{}}function $d(e,t){l("#cb-modal").style.display="flex",l("#cb-m-title").textContent=e,l("#cb-m-repo").textContent=t}async function Ed(e){var n;const t=sn[e];if(t){$d(t.id.split("/").pop(),t.id),l("#cb-m-alias").value="",l("#cb-m-files").style.display="none",l("#cb-m-loading").style.display="block",l("#cb-m-download").disabled=!0;try{const i=parseInt(l("#cb-m-ctx").value)||8192;if(Ae=await $("/api/cookbook/analyze",{method:"POST",body:JSON.stringify({repo_id:t.id,ctx:i})}),l("#cb-m-loading").style.display="none",l("#cb-m-files").style.display="block",!((n=Ae.files)!=null&&n.length)){l("#cb-m-files").innerHTML="<option>Keine GGUF-Dateien gefunden</option>",l("#cb-m-fit").style.display="none";return}l("#cb-m-files").innerHTML=Ae.files.map(s=>{const a=s.fit.level==="perfect"?"●":s.fit.level==="marginal"?"◐":"○";return`<option value="${g(s.filename)}">${a} ${g(s.filename)}</option>`}).join(""),l("#cb-m-download").disabled=!1,es()}catch(i){l("#cb-m-loading").textContent="Fehler: "+i.message}}}async function Td(){var a;const e=l("#cb-m-repo").textContent,t=l("#cb-m-files").value,n=l("#cb-m-alias").value.trim().toLowerCase(),i=parseInt(l("#cb-m-ctx").value)||8192;if(!e||!t)return w("Bitte eine GGUF-Datei wählen.",!0);const s=l("#cb-m-download");s.disabled=!0,s.textContent="Starte…";try{const r=await $("/api/download",{method:"POST",body:JSON.stringify({repo:e,file:t,hf_token:_n()})});await $("/api/register",{method:"POST",body:JSON.stringify({role:n,model_path:r.expected_path,ctx:i})}),w("Download gestartet — siehe Aktivität."),l("#cb-modal").style.display="none",(a=document.querySelector(".nav-item[data-view='activity']"))==null||a.click()}catch(r){w("Fehler: "+r.message,!0)}s.disabled=!1,s.textContent="Herunterladen & Einpflegen"}function Cd(e){nn=e,Qa()}const Ld={id:"cookbook",mount:ud,onSystem:Cd};let Fn=null,Ls="",Lt="zed",Se=[];window.mcCopyCode=e=>{var i;const t=e.parentElement.querySelector("code,pre");(i=navigator.clipboard)==null||i.writeText((t==null?void 0:t.textContent)||"");const n=e.textContent;e.textContent="Kopiert!",setTimeout(()=>e.textContent=n,1400)};window.mcCopyVal=e=>{var i;const t=e.previousElementSibling;(i=navigator.clipboard)==null||i.writeText(t.value);const n=e.textContent;e.textContent="Kopiert!",setTimeout(()=>e.textContent=n,1400)};window.cnGo=e=>{var t;return(t=document.querySelector(`.nav-item[data-view="${e}"]`))==null?void 0:t.click()};window.cnSelectTool=e=>{Lt=e,ts()};window.cnToggleModel=e=>{Se.includes(e)?Se=Se.filter(t=>t!==e):Se.push(e),ts()};function Ad(){return`http://${location.hostname}:8080/v1`}function zd(e){return/^\d{1,3}(\.\d{1,3}){3}$/.test(e)}function Dd(){return location.protocol==="https:"||!zd(location.hostname)}function Xe(){return((Fn==null?void 0:Fn.models)||[]).filter(e=>!e.incomplete)}function Pd(){return Se.length?Se:Xe().map(e=>e.name)}function an(e,t){return`<label>${e}</label>
|
||
<div class="flex gap-2" style="align-items:stretch;margin-bottom:12px">
|
||
<input class="mono-sm" readonly value="${g(t)}" style="flex:1;margin:0">
|
||
<button class="ghost" onclick="window.mcCopyVal(this)">Kopieren</button>
|
||
</div>`}const Za=e=>`<span class="chip" style="background:rgba(63,185,80,.12);border-color:rgba(63,185,80,.25);color:#7ee29a">${nt("check")}${g(e)}</span>`;function Od(e,t){const i=(t.length?Xe().filter(s=>t.includes(s.name)):Xe().length?Xe():[{name:"coder",meta:{}}]).map(s=>{var o;const r=(((o=s.meta)==null?void 0:o.caps)||[]).some(c=>/Bild|Vision/i.test(c))?', "capabilities": { "tools": true, "images": true }':"";return` { "name": "${s.name}", "display_name": "${s.name}", "max_tokens": 32768${r} }`}).join(`,
|
||
`);return`"language_models": {
|
||
"openai_compatible": {
|
||
"bosgame": {
|
||
"api_url": "${e}",
|
||
"available_models": [
|
||
${i}
|
||
]
|
||
}
|
||
}
|
||
}`}function Bd(e,t){const i=(t.length?t:Xe().length?Xe().map(s=>s.name):["coder"]).map(s=>` "${s}": { "name": "${s}" }`).join(`,
|
||
`);return`"provider": {
|
||
"llama-swap": {
|
||
"npm": "@ai-sdk/openai-compatible",
|
||
"name": "Bosgame (llama-swap)",
|
||
"options": {
|
||
"baseURL": "${e}",
|
||
"apiKey": "local"
|
||
},
|
||
"models": {
|
||
${i}
|
||
}
|
||
}
|
||
}`}function Hd(e){return`<div class="card" id="cn-conn-card">
|
||
<div class="card-h"><h3>1 · Verbindung prüfen</h3></div>
|
||
<div class="card-sub">Stelle sicher, dass die LLM-Engine erreichbar ist, bevor du Tools konfigurierst.</div>
|
||
${an("Engine-Adresse (für alle Tools)",e)}
|
||
${Dd()?`<div class="alert warn" style="margin:0 0 12px"><span class="a-dot"></span><span>
|
||
Du öffnest Mission Control über einen <b>Proxy/Namen</b> (HTTPS o.ä.). Die Engine läuft auf <b>Port 8080 ohne HTTPS</b> direkt am Bosgame.
|
||
Trag in deinen Tools die <b>LAN-IP</b> ein: <code>http://192.168.178.151:8080/v1</code> — nicht den Proxy-Namen.</span></div>`:""}
|
||
<div class="btn-row" style="display:flex;align-items:center;gap:10px">
|
||
<button class="primary" id="cn-test">Verbindung testen</button>
|
||
<span id="cn-test-result" class="mono-sm"></span>
|
||
</div>
|
||
</div>`}function Rd(){return`<div class="card">
|
||
<div class="card-h"><h3>Wie funktioniert das Auto-Swap?</h3></div>
|
||
<div style="display:flex;flex-direction:column;gap:10px">
|
||
<div class="tile" style="display:flex;gap:12px;align-items:flex-start">
|
||
<span style="font-size:20px;line-height:1;flex:0 0 auto">⚡</span>
|
||
<div>
|
||
<div style="font-weight:500;font-size:13.5px">Kein manuelles Starten nötig</div>
|
||
<div class="card-sub" style="margin:4px 0 0">Wenn du in Zed oder OpenCode <b>„coder"</b> auswählst und eine Anfrage sendest,
|
||
lädt llama-swap das Modell automatisch — in ca. 2–5 Sekunden. Du siehst es hier im Dashboard unter „Aktives Modell".</div>
|
||
</div>
|
||
</div>
|
||
<div class="tile" style="display:flex;gap:12px;align-items:flex-start">
|
||
<span style="font-size:20px;line-height:1;flex:0 0 auto">🔄</span>
|
||
<div>
|
||
<div style="font-weight:500;font-size:13.5px">Modelle wechseln sich automatisch ab</div>
|
||
<div class="card-sub" style="margin:4px 0 0">Rufst du ein anderes Modell auf, entlädt llama-swap das aktuelle (nach TTL-Ablauf)
|
||
und lädt das neue. Es läuft immer nur <b>eines gleichzeitig</b> — llama-swap verwaltet das komplett selbst.</div>
|
||
</div>
|
||
</div>
|
||
<div class="tile" style="display:flex;gap:12px;align-items:flex-start">
|
||
<span style="font-size:20px;line-height:1;flex:0 0 auto">🧠</span>
|
||
<div>
|
||
<div style="font-weight:500;font-size:13.5px">Zwei Modelle gleichzeitig aktiv halten</div>
|
||
<div class="card-sub" style="margin:4px 0 0">Du hast 124 GB RAM — genug für z.B. Scout 8B (~5 GB) <b>dauerhaft geladen</b> + ein
|
||
größeres Modell das swappt. Dafür beim Scout im <b>Modelle-Tab → Konfigurieren</b> die TTL auf <code>99999</code> setzen
|
||
(= nie automatisch entladen). Das kleine Modell beantwortet schnelle Fragen sofort, ohne jede Wartezeit.</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>`}function Fd(){const e=Xe();if(!e.length)return"";const t=e.map(i=>`<span class="chip" style="cursor:pointer;${Se.includes(i.name)?"background:rgba(45,212,191,.2);border-color:var(--accent);color:var(--accent)":""}"
|
||
onclick="window.cnToggleModel('${g(i.name)}')">${g(i.name)}</span>`).join(" ");return`<div class="card">
|
||
<div class="card-h"><h3>2 · Modelle auswählen</h3><span class="meta">${Se.length?`${Se.length} von ${e.length} ausgewählt`:"alle werden eingeschlossen"}</span></div>
|
||
<div class="card-sub">Wähle, welche Modelle im Config-Snippet erscheinen sollen. Standardmäßig alle.</div>
|
||
<div class="flex gap-2" style="flex-wrap:wrap">${t}</div>
|
||
${Se.length?'<div style="margin-top:10px"><a href="#" onclick="event.preventDefault();window.cnClearModels()" style="color:var(--mut);font-size:12.5px">Auswahl aufheben (alle)</a></div>':""}
|
||
</div>`}window.cnClearModels=()=>{Se=[],ts()};function zn(e,t,n){return`<button class="card-btn${Lt===e?" cb-best":""}" style="text-align:left;padding:12px 16px"
|
||
onclick="window.cnSelectTool('${e}')">
|
||
<div style="font-weight:500;font-size:13.5px">${g(t)}</div>
|
||
<div class="card-sub" style="margin:3px 0 0">${g(n)}</div>
|
||
</button>`}function Nd(){return`<div class="card">
|
||
<div class="card-h"><h3>3 · Tool wählen</h3></div>
|
||
<div class="card-sub">Für welches Werkzeug brauchst du die Konfiguration?</div>
|
||
<div class="grid grid-4" style="gap:8px">
|
||
${zn("zed","Zed","Schneller nativer Editor — Empfohlen")}
|
||
${zn("opencode","OpenCode","Open-Source Coding-Agent (Terminal/Desktop)")}
|
||
${zn("cline","Cline / Cursor","VS Code Extension, Bring-Your-Own-Key")}
|
||
${zn("openwebui","OpenWebUI","Browser-Chat-Interface")}
|
||
</div>
|
||
</div>`}function Id(e){const t=Pd(),n=t[0]||"coder";let i="";if(Lt==="zed"){const s=Od(e,t);i=`
|
||
<p>Öffne die Zed-Settings mit <code>Cmd/Strg + ,</code> und <b>füge diesen Block</b> in deine bestehende
|
||
<code>settings.json</code> ein. Falls bereits ein <code>"bosgame"</code>-Eintrag existiert: vorher löschen.</p>
|
||
<div class="alert warn" style="margin-bottom:12px;padding:10px 14px">
|
||
<span class="a-dot"></span><span>Nur diesen Block einfügen, <b>nicht die ganze Datei ersetzen</b> — sonst gehen andere Einstellungen verloren.</span>
|
||
</div>
|
||
<div style="position:relative;margin-top:4px">
|
||
<button class="ghost" style="position:absolute;top:8px;right:8px;z-index:1" onclick="window.mcCopyCode(this)">Kopieren</button>
|
||
<div class="log" style="max-height:none"><code>${g(s)}</code></div>
|
||
</div>
|
||
<p style="margin-top:12px"><b>API-Key:</b> Öffne den Agent-Panel → Anbieter <b>bosgame</b> → trag irgendeinen Wert ein (z.B. <code>local</code>).
|
||
Alternativ <code>BOSGAME_API_KEY=local</code> als Umgebungsvariable.</p>
|
||
<div class="hint">Danach im Agent-Panel das Modell <b>bosgame / ${g(n)}</b> auswählen. Das Auto-Swap lädt es beim ersten Request.</div>`}else if(Lt==="opencode"){const s=Bd(e,t);i=`
|
||
<p>Datei anlegen oder öffnen:</p>
|
||
<p class="mono-sm" style="line-height:1.6">Windows: <code>%USERPROFILE%\\.config\\opencode\\opencode.json</code><br>
|
||
Linux/Mac: <code>~/.config/opencode/opencode.json</code><br>
|
||
Oder im Projektordner: <code>opencode.json</code></p>
|
||
<div class="alert warn" style="margin:8px 0 12px;padding:10px 14px">
|
||
<span class="a-dot"></span><span>Nur den <code>"provider"</code>-Block einfügen oder mergen.
|
||
Falls bereits ein <code>"llama-swap"</code>-Eintrag vorhanden ist: <b>vorher löschen</b>, sonst erscheinen Modelle doppelt.</span>
|
||
</div>
|
||
<div style="position:relative">
|
||
<button class="ghost" style="position:absolute;top:8px;right:8px;z-index:1" onclick="window.mcCopyCode(this)">Kopieren</button>
|
||
<div class="log" style="max-height:none"><code>${g(s)}</code></div>
|
||
</div>
|
||
<p style="margin-top:12px">Danach in OpenCode <code>/models</code> → <b>llama-swap/${g(n)}</b> wählen.</p>
|
||
<div class="hint">Statt alles zu ersetzen: nur den <code>provider</code>-Block in deine bestehende Config einfügen — so bleiben andere Anbieter erhalten.</div>`}else Lt==="cline"?i=`
|
||
<p>In Cline/Cursor den Provider <b>„OpenAI Compatible"</b> wählen und folgende Felder eintragen:</p>
|
||
${an("Basis-URL (OpenAI Compatible)",e)}
|
||
${an("Modell-ID",n)}
|
||
<div class="hint">API-Key: ein beliebiger Wert (z.B. <code>local</code>) — llama-swap prüft ihn nicht.</div>`:Lt==="openwebui"&&(i=`
|
||
<p>Settings → Admin Panel → Connections → neue OpenAI-Verbindung:</p>
|
||
${an("Basis-URL",e)}
|
||
${an("API-Key","dummy-key")}
|
||
<div class="hint">Nach dem Speichern erscheinen die Modelle automatisch in der Modell-Auswahl.</div>`);return`<div class="card">
|
||
<div class="card-h"><h3>4 · Config-Snippet</h3></div>
|
||
${i}
|
||
</div>`}function Gd(e){var i;const t=(i=Xe().find(s=>{var a,r;return(r=(a=s.meta)==null?void 0:a.caps)==null?void 0:r.includes("Bild")}))==null?void 0:i.name;if(!t)return"";const n=`curl ${e}/chat/completions \\
|
||
-H "Content-Type: application/json" \\
|
||
-d '{
|
||
"model": "${t}",
|
||
"messages": [{"role":"user","content":[
|
||
{"type":"text","text":"Was ist auf dem Bild falsch?"},
|
||
{"type":"image_url","image_url":{"url":"data:image/png;base64,<DEIN_BILD>"}}
|
||
]}]
|
||
}'`;return`<div class="card">
|
||
<div class="card-h"><h3>Bilder einbinden (Vision)</h3>${Za(`„${t}" bereit`)}</div>
|
||
<div class="card-sub">Mit Vision-Modellen kannst du Screenshots oder Fehlerbilder direkt mitschicken.</div>
|
||
<div style="position:relative;margin-top:8px">
|
||
<button class="ghost" style="position:absolute;top:8px;right:8px;z-index:1" onclick="window.mcCopyCode(this)">Kopieren</button>
|
||
<div class="log" style="max-height:none"><code>${g(n)}</code></div>
|
||
</div>
|
||
</div>`}function un(){var n;const e=l(".view[data-view='connect']");if(!e)return;const t=Ad();e.innerHTML=`
|
||
<div class="pagehead"><div>
|
||
<h1>Verbinden</h1>
|
||
<div class="sub">Schritt für Schritt: KI-Modelle in deine Tools einbinden — getestet, erklärt, mit Copy-Paste-Config.</div>
|
||
</div></div>
|
||
|
||
${Hd(t)}
|
||
${Rd()}
|
||
${Fd()}
|
||
${Nd()}
|
||
${Id(t)}
|
||
${Gd(t)}`,(n=l("#cn-test"))==null||n.addEventListener("click",Kd)}function ts(){if(!l(".view[data-view='connect'] .card:nth-of-type(3)")){un();return}const t=l(".view[data-view='connect']"),n=t==null?void 0:t.querySelectorAll(".card");if(!n||n.length<3){un();return}un()}async function Kd(){const e=l("#cn-test-result");e.innerHTML='<span class="text-mut">Teste…</span>';try{const t=await $("/api/integration/test");e.innerHTML=t.ok?Za(`Verbunden — ${t.models.length} Modell(e): ${t.models.join(", ")}`):`<span style="color:var(--err)">✗ Keine Verbindung: ${g(t.error)}</span>`}catch(t){e.innerHTML=`<span style="color:var(--err)">✗ ${g(t.message)}</span>`}}function qd(){un()}function Ud(e){Fn=e;const t=((e==null?void 0:e.models)||[]).map(n=>n.name).join(",")+"|"+!!e;t!==Ls&&(Ls=t,un())}const jd={id:"connect",mount:qd,onStatus:Ud},ni=[ho,zo,td,qo,Ld,jd];let As={};function Vd(){const e=document.getElementById("top-active-text");e&&(e.classList.add("swap-flash"),setTimeout(()=>e.classList.remove("swap-flash"),900))}function zs(e){var s;const t=document.getElementById("swdot"),n=document.getElementById("swlabel"),i=document.getElementById("top-active-text");if(!e)t&&(t.className="dot off"),n&&(n.textContent="Backend nicht erreichbar"),i&&(i.textContent="Backend offline"),Ds("Backend nicht erreichbar – läuft uvicorn?",!1);else{const a=(e.swap_url||"").replace(/^https?:\/\//,"");t&&(t.className="dot "+(e.swap_ok?"on":"off")),n&&(n.textContent=(e.swap_ok?"LLM-Engine: Online – ":"LLM-Engine: Offline – ")+a);const r=(e.models||[]).find(c=>c.state==="running");i&&(i.innerHTML=r?`Geladen: <b style="color:var(--teal)">${r.name}</b>`:"Kein Modell im VRAM"),e.swap_ok?Yd():Ds(`LLM-Engine nicht erreichbar unter <b>${a}</b> – läuft der llama-swap Dienst?`,!0);const o={};for(const c of e.models||[])o[c.name]=c.state;for(const[c,u]of Object.entries(o)){const p=As[c];if(p&&p!==u&&(u==="running"||u==="loading")){Vd();break}}As=o}Wd(e),Rl.set(e);for(const a of ni)(s=a.onStatus)==null||s.call(a,e)}function Wd(e){const t=document.getElementById("sec-chip"),n=document.getElementById("sec-chip-tx");!t||!n||(t.classList.toggle("ok",!!(e!=null&&e.secured)),n.textContent=e!=null&&e.secured?"Geschützt · Heimnetz":"Nur im Heimnetz (offen)")}function Jd(e){var t;Fl.set(e||[]);for(const n of ni)(t=n.onJobs)==null||t.call(n,e)}function Qd(e){var t;for(const n of ni)(t=n.onSystem)==null||t.call(n,e)}function Ds(e,t){const n=document.getElementById("alert");n.className="alert"+(t?" warn":""),n.innerHTML=`<span class="a-dot"></span><span>${e}</span>`,n.style.display="flex"}function Yd(){const e=document.getElementById("alert");e&&(e.style.display="none")}const Zd=e=>{var t;return(t=document.querySelector(`.nav-item[data-view="${e}"]`))==null?void 0:t.click()};function wi(e,t,n,i){const s=document.createElement("span");return s.className="upd-badge"+(t?" "+t:""),s.textContent=e,i&&(s.title=i),s.onclick=()=>Zd(n),s}async function Xa(){try{const e=await $("/api/updates"),t=document.getElementById("update-badges");if(!t)return;const n=e.apt_cache_age_h,i=n!=null&&n>24,s=n!=null?` · apt-Cache: vor ${n}h`:"";t.innerHTML="",t.appendChild(wi(e.os>0?`OS: ${e.os} Updates`:i?"OS: ?":"OS: aktuell",e.os>0||i?"warn":"ok","server",e.os>0?`${e.os} ausstehende Pakete${s}`:i?`apt-Cache veraltet (${n}h)`:`Keine Updates${s}`)),t.appendChild(wi(e.engine>0?"Engine: Update":"Engine: aktuell",e.engine>0?"warn":"ok","server")),t.appendChild(wi(e.models>0?`Modelle: ${e.models}`:"Modelle: aktuell",e.models>0?"warn":"ok","news"))}catch{}}async function ii(){try{zs(await $("/api/status"))}catch{zs(null)}}async function er(){try{Jd(await $("/api/jobs"))}catch{}}function tr(){const e=window.location.protocol==="https:"?"wss:":"ws:",t=new WebSocket(`${e}//${window.location.host}/api/system/stream`);t.onmessage=n=>{try{Qd(JSON.parse(n.data))}catch{}},t.onclose=()=>{setTimeout(tr,3e3)},t.onerror=()=>t.close()}var Ns;for(const e of ni)(Ns=e.mount)==null||Ns.call(e);const Ps=document.querySelector('.view[data-view="guides"]');Ps&&za(jl,{target:Ps});const Os=document.querySelector('.view[data-view="news"]');Os&&za(no,{target:Os});Hl("overview");const yi=document.getElementById("token");yi&&(yi.value=Da(),yi.addEventListener("change",e=>{Dl(e.target.value),ii()}));const xi=document.getElementById("hf-token");xi&&(xi.value=_n(),xi.addEventListener("change",e=>Ol(e.target.value)));const Bs=document.getElementById("nav-settings"),_i=document.getElementById("settings-modal"),Hs=document.getElementById("sm-close");Bs&&_i&&Hs&&(Bs.addEventListener("click",()=>_i.style.display="flex"),Hs.addEventListener("click",()=>_i.style.display="none"));function Xd(){const e=document.getElementById("clock");e&&(e.textContent=new Date().toTimeString().slice(0,5))}document.addEventListener("mc:refresh",ii);ii();er();tr();setInterval(Xd,1e3);setInterval(ii,3e3);setInterval(er,1500);Xa();setInterval(Xa,6e4);
|