iobroker.ai-usage 0.9.2 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -18
- package/admin/custom/assets/Components-SGigWPbz.js +1 -0
- package/admin/custom/assets/ConfigPanel-7wtMdF7R.js +1 -0
- package/admin/custom/assets/{index-DKkCKYc8.js → index-BOIC_Bq7.js} +1 -1
- package/admin/custom/assets/{mf-entry-bootstrap-0-d02c5f06.js → mf-entry-bootstrap-0-d8335d6f.js} +1 -1
- package/admin/custom/assets/virtualExposes--n4JvPAZ.js +1 -0
- package/admin/custom/assets/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__AiUsageComponentSet__customComponents_js-BjDe1IXR.js → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__AiUsageComponentSet__customComponents_js-haafVBNm.js} +1 -1
- package/admin/custom/assets/virtual_mf-exposes-ssr___mfe_internal__AiUsageComponentSet__customComponents_js-CGHI3HBU.js +2 -0
- package/admin/custom/assets/{virtual_mf-exposes___mfe_internal__AiUsageComponentSet__customComponents_js-Wtw6jqAg.js → virtual_mf-exposes___mfe_internal__AiUsageComponentSet__customComponents_js-DAX-5uLT.js} +2 -2
- package/admin/custom/customComponents.js +1 -1
- package/admin/custom/mf-manifest.json +1 -1
- package/build/lib/poll-engine.js +6 -6
- package/build/lib/poll-engine.js.map +1 -1
- package/build/lib/provider.js.map +2 -2
- package/build/lib/providers/chatgpt-sub.js +56 -2
- package/build/lib/providers/chatgpt-sub.js.map +2 -2
- package/build/lib/providers/claude-auth.js +21 -2
- package/build/lib/providers/claude-auth.js.map +2 -2
- package/build/lib/providers/claude-sub.js +5 -4
- package/build/lib/providers/claude-sub.js.map +2 -2
- package/build/lib/providers/deepseek.js +1 -1
- package/build/lib/providers/deepseek.js.map +2 -2
- package/build/lib/providers/gemini-sub.js +1 -1
- package/build/lib/providers/gemini-sub.js.map +2 -2
- package/build/lib/providers/openrouter.js +1 -1
- package/build/lib/providers/openrouter.js.map +2 -2
- package/build/lib/snapshot-tree.js +50 -9
- package/build/lib/snapshot-tree.js.map +2 -2
- package/build/main.js +20 -6
- package/build/main.js.map +2 -2
- package/io-package.json +27 -27
- package/package.json +2 -2
- package/admin/custom/assets/Components-C4wFRka3.js +0 -1
- package/admin/custom/assets/ConfigPanel-CGCj6Crq.js +0 -1
- package/admin/custom/assets/virtualExposes-CdmVMFlA.js +0 -1
- package/admin/custom/assets/virtual_mf-exposes-ssr___mfe_internal__AiUsageComponentSet__customComponents_js-DA9gAGbr.js +0 -2
package/README.md
CHANGED
|
@@ -80,7 +80,7 @@ ai-usage.0.
|
|
|
80
80
|
│ ├── warning — this account is above its warn threshold (bool)
|
|
81
81
|
│ ├── limitReached — a plan-wide window of this account is full (bool)
|
|
82
82
|
│ ├── info.unreach — account is not delivering (bool) — drives the connection icon
|
|
83
|
-
│ ├── info.error — why there is no data
|
|
83
|
+
│ ├── info.error — why there is no data; empty while all is well, "Unknown" while the adapter itself has nothing to report
|
|
84
84
|
│ ├── info.lastUpdate — time of the last successful read
|
|
85
85
|
│ ├── limits.<window>.* — percent + reset time (session, week, per model, …)
|
|
86
86
|
│ └── credits.* — where the provider reports a balance
|
|
@@ -96,7 +96,7 @@ Only what an account's source actually delivers is created.
|
|
|
96
96
|
**The connection icon** sits next to each account, green while it delivers. A throttle keeps it
|
|
97
97
|
green — the last values stay valid while the adapter waits. A rejected sign-in or a broken service
|
|
98
98
|
switch it off at once, an unreachable service after three attempts, so a hiccup does not make it
|
|
99
|
-
flap. `info.error`
|
|
99
|
+
flap. `info.error` names the cause whenever the provider gave one.
|
|
100
100
|
|
|
101
101
|
**Only plan-wide windows raise the warning** — your session and your week — and the message names
|
|
102
102
|
the window it came from. A window belonging to a single model keeps its own datapoints but stays
|
|
@@ -114,6 +114,9 @@ Read `info.error` — it names the cause. A rejected sign-in means signing in ag
|
|
|
114
114
|
or that the key is not an organisation admin key. A service fault or a missing connection is
|
|
115
115
|
outside your instance and clears up by itself. The log states the same reason once.
|
|
116
116
|
|
|
117
|
+
`Unknown` there means the adapter itself has nothing to report — it is switched off, or it has just
|
|
118
|
+
started and has not asked yet.
|
|
119
|
+
|
|
117
120
|
### A subscription says "not signed in" although you just signed in
|
|
118
121
|
Save the settings first, then sign in — the row needs a saved account to attach the sign-in to.
|
|
119
122
|
After a successful sign-in the account is queried immediately, so values appear within seconds.
|
|
@@ -126,6 +129,19 @@ After a successful sign-in the account is queried immediately, so values appear
|
|
|
126
129
|
Placeholder for the next version (at the beginning of the line):
|
|
127
130
|
### **WORK IN PROGRESS**
|
|
128
131
|
-->
|
|
132
|
+
### 0.10.0 (2026-09-01)
|
|
133
|
+
|
|
134
|
+
- Fixed: The reset-time datapoint of a limit window no longer disappears and reappears — it stays and simply empties while no window is running
|
|
135
|
+
- Fixed: The settings page no longer shows the sign-in screen to a signed-in account, and its rows load without waiting for the credential storage scan
|
|
136
|
+
- Improved: Claude usage is read with far fewer rejections — the query now identifies itself the way the endpoint expects
|
|
137
|
+
- Changed: New Claude sign-ins request only the profile permission — the stored access can no longer create API keys or run models
|
|
138
|
+
- New: ChatGPT accounts show their purchasable limit-reset credits — how many are available and when the next one expires
|
|
139
|
+
- Improved: An unreadable provider answer is now reported as a service fault instead of a missing connection
|
|
140
|
+
|
|
141
|
+
### 0.9.3 (2026-08-27)
|
|
142
|
+
|
|
143
|
+
- Fixed: The first start after updating no longer leaves a warning in the log
|
|
144
|
+
|
|
129
145
|
### 0.9.2 (2026-08-27)
|
|
130
146
|
|
|
131
147
|
- Fixed: Stopping the instance now marks the accounts as offline on installations that were updated too, not only on fresh ones — the previous version left them showing as online
|
|
@@ -139,22 +155,6 @@ After a successful sign-in the account is queried immediately, so values appear
|
|
|
139
155
|
- Fixed: Switching the instance off now shows every account as offline in the object tree and the settings, instead of leaving them green for as long as the adapter is not running
|
|
140
156
|
- Fixed: After a crash or a hard kill an account no longer keeps claiming to deliver data; every account starts as "not delivering" until its first answer arrives
|
|
141
157
|
|
|
142
|
-
### 0.8.0 (2026-08-27)
|
|
143
|
-
|
|
144
|
-
- Fixed: A switched-off instance no longer leaves its accounts standing green in the object tree — every account is marked as not delivering when the adapter stops
|
|
145
|
-
- Fixed: Signing out of a subscription now really stops it — the adapter kept polling with what it still held in memory, and the next token refresh even restored the deleted sign-in
|
|
146
|
-
- Fixed: A restarted adapter no longer shows every account as offline until its first answer arrives, complete with struck-through icon and a red badge in the settings
|
|
147
|
-
- Fixed: From the second round on, all accounts queried at the same moment instead of spread out, which is exactly what makes a provider throttle or lock an account
|
|
148
|
-
- Fixed: Limit windows and models a provider stops reporting are now removed instead of staying in the object tree forever, frozen on their last value
|
|
149
|
-
- Fixed: Signing in no longer risks signing the subscription straight back out, which could happen when the immediate first query collided with a scheduled one
|
|
150
|
-
- Fixed: Google accounts are now spoken for by their fullest model quota, and the warning names that model — before, every single model could raise the account's alarm
|
|
151
|
-
- Fixed: "Configured accounts" counts what you switched on, including accounts whose credential could not be read; the sign-in button no longer hangs for up to 15 seconds
|
|
152
|
-
- Fixed: A damaged or unreadable stored sign-in now says so in the log instead of looking exactly like "never signed in"
|
|
153
|
-
|
|
154
|
-
### 0.7.1 (2026-08-27)
|
|
155
|
-
|
|
156
|
-
- Fixed: Restarting the adapter no longer writes one unchanged value into every status datapoint, so a recorded history stays free of restart noise
|
|
157
|
-
|
|
158
158
|
[Older changelogs can be found there](CHANGELOG_OLD.md)
|
|
159
159
|
|
|
160
160
|
## Support
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{t as e}from"./ConfigPanel-7wtMdF7R.js";var t={ConfigPanel:e};export{t as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{t as e}from"./_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare___mf_0_iobroker_mf_1_json_mf_2_config__loadShare__.js-DEdJInQG.js";import{Br as t,Dr as n,Er as r,Fi as i,Ha as a,Hi as o,Or as s,Ti as c,Tr as l,Va as u,Xr as d,_o as f,ai as p,ci as m,ei as h,ga as g,ii as _,in as v,kr as y,li as b,ni as x,oi as S}from"./_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-DzPBJWNQ.js";f();var C={"claude-sub":`claude`,"chatgpt-sub":`chatgpt`,"gemini-sub":`gemini`};function w(e,t){let n=C[e];if(n)return n;let r=t.replace(/^system\.credentials\./,``).trim().replace(/[^a-zA-Z0-9_-]/g,`_`).replace(/_{2,}/g,`_`).replace(/^_+|_+$/g,``);return r?`${r}-api`:``}function T(e,t){if(e==null)return null;let n=typeof t==`string`?t:``;return e===!0?{key:`aiu_stOffline`,color:`error`,title:n}:n?{key:`aiu_stLimited`,color:`warning`,title:n}:{key:`aiu_stOnline`,color:`success`,title:``}}var E=[{provider:`claude-sub`,label:`Claude`,captionKey:`aiu_capClaude`},{provider:`chatgpt-sub`,label:`ChatGPT`,captionKey:`aiu_capChatgpt`},{provider:`gemini-sub`,label:`Gemini`,captionKey:`aiu_capGemini`}];function D(e,t){let n=`${e} ${t}`.toLowerCase();return n.includes(`gemini`)?null:n.includes(`anthropic`)||n.includes(`claude`)?{provider:`anthropic-api`,label:`Anthropic`,needsAdminKey:!0}:n.includes(`chatgpt`)||n.includes(`openai`)?{provider:`openai`,label:`OpenAI`,needsAdminKey:!0}:n.includes(`deepseek`)?{provider:`deepseek`,label:`DeepSeek`,needsAdminKey:!1}:n.includes(`openrouter`)||n.includes(`router`)?{provider:`openrouter`,label:`OpenRouter`,needsAdminKey:!1}:null}var O=[{provider:`openrouter`,label:`OpenRouter`,needsAdminKey:!1},{provider:`deepseek`,label:`DeepSeek`,needsAdminKey:!1},{provider:`openai`,label:`OpenAI`,needsAdminKey:!0},{provider:`anthropic-api`,label:`Anthropic`,needsAdminKey:!0}];function k(e,t){return e.find(e=>e.provider===t)}function A(e,t,n,r){let i=e.filter(e=>e.provider!==t);return n?[...i,{name:r,provider:t,credentialId:``,warnThreshold:80}]:i}function j(e,t,n,r){let i=e.filter(e=>e.credentialId!==t.id);return!r||!n?i:[...i,{name:t.name,provider:n,credentialId:t.id,warnThreshold:80}]}function M(e,t,n){let r=Math.min(100,Math.max(10,Math.round(Number(n))||80));return e.map(e=>(t.credentialId?e.credentialId===t.credentialId:e.provider===t.provider)?{...e,warnThreshold:r}:e)}var N=class extends e{constructor(e){super(e),g(this,`timer`,null),this.state={...this.state,credentials:[],credentialsLoaded:!1,signIn:{},drafts:{},providerChoice:{},serviceState:{},busy:``}}async componentDidMount(){var e;(e=super.componentDidMount)==null||e.call(this),await Promise.all([this.loadCredentials(),this.refresh()]),this.timer=setInterval(()=>void this.refresh(),4e3)}componentWillUnmount(){var e;this.timer&&=(clearInterval(this.timer),null),(e=super.componentWillUnmount)==null||e.call(this)}async loadCredentials(){try{let e=await this.props.oContext.socket.getObjectViewSystem(`config`,`system.credentials.`,`system.credentials.香`),t=Object.values(e||{}).filter(e=>{var t;return!!e&&((t=e.native)==null?void 0:t.type)===`ai`}).map(e=>{var t,n;let r=e._id.substring(19),i=(t=e.common)==null?void 0:t.name,a=typeof i==`string`?i:(i==null?void 0:i.en)||Object.values(i||{})[0]||r;return{id:e._id,suffix:r,name:a,icon:typeof((n=e.common)==null?void 0:n.icon)==`string`?e.common.icon:void 0}}).sort((e,t)=>e.name.localeCompare(t.name));this.setState({credentials:t,credentialsLoaded:!0})}catch{this.setState({credentialsLoaded:!0})}}async refresh(){await this.refreshSignIn(),await this.refreshServiceState()}async refreshServiceState(){let e=this.props.oContext,t={};await Promise.all(this.accounts().map(async n=>{let r=w(n.provider,n.credentialId);if(r)try{let[n,i]=await Promise.all([e.socket.getState(`${e.adapterName}.${e.instance}.${r}.info.unreach`),e.socket.getState(`${e.adapterName}.${e.instance}.${r}.info.error`)]);n&&n.val!==null&&n.val!==void 0&&(t[`${r}.unreach`]=n.val,t[`${r}.error`]=(i==null?void 0:i.val)??``)}catch{}})),this.setState({serviceState:t})}renderServiceBadge(e,t){let n=w(e,t),r=T(this.state.serviceState[`${n}.unreach`],this.state.serviceState[`${n}.error`]);return r?u(m,{size:`small`,color:r.color,variant:`outlined`,label:v.t(r.key),title:r.title}):null}async refreshSignIn(){if(!this.props.alive)return;let e=await Promise.all(E.filter(e=>k(this.accounts(),e.provider)).map(async e=>({provider:e.provider,answer:await this.ask(`signInStatus`,e.provider)})));this.setState(t=>{let n={...t.signIn};for(let{provider:t,answer:r}of e)r&&(n[t]=r);return{signIn:n}})}async ask(e,t,n){let r=this.props.oContext;try{let i=await r.socket.sendTo(`${r.adapterName}.${r.instance}`,e,{provider:t,value:n});return i?i.error?{status:`failed`,reason:i.error}:i:null}catch{return null}}async run(e,t,n){this.setState({busy:t});let r=await this.ask(e,t,n)??{status:`failed`,reason:v.t(`aiu_noAnswer`)};this.setState(e=>({busy:``,signIn:{...e.signIn,[t]:r},drafts:{...e.drafts,[t]:``}}))}accounts(){let t=e.getValue(this.props.data,`accounts`);return Array.isArray(t)?t:[]}commit(e){this.onChange(`accounts`,e)}renderThreshold(e,t,n){return u(d,{size:`small`,type:`number`,label:v.t(`aiu_warnAt`),defaultValue:t.warnThreshold||80,onBlur:e=>this.commit(M(this.accounts(),n,e.target.value)),slotProps:{htmlInput:{min:10,max:100,style:{width:60}},inputLabel:{shrink:!0}},sx:{ml:`auto`}},e)}renderSignIn(e){let t=this.state.signIn[e],i=this.state.busy===e;if(!this.props.alive)return u(c,{severity:`warning`,children:v.t(`aiu_instanceNotRunning`)});if(this.props.changed)return u(c,{severity:`info`,children:v.t(`aiu_saveFirst`)});if(!t)return u(b,{size:20});if(t.status===`signed-in`)return a(x,{sx:{display:`flex`,alignItems:`center`,gap:1,flexWrap:`wrap`},children:[u(y,{color:`success`}),u(h,{sx:{color:`success.main`},children:v.t(`aiu_signedIn`)}),u(_,{size:`small`,startIcon:u(r,{}),disabled:i,onClick:()=>void this.run(`signOut`,e),children:v.t(`aiu_signOut`)})]});if(t.status===`awaiting-device`)return a(x,{sx:{display:`flex`,flexDirection:`column`,gap:1.5},children:[u(h,{variant:`body2`,children:v.t(`aiu_deviceStep1`)}),a(x,{sx:{display:`flex`,gap:1,alignItems:`center`,flexWrap:`wrap`},children:[u(m,{label:t.userCode,sx:{fontSize:20,fontFamily:`monospace`,py:2.5,px:1}}),u(_,{size:`small`,startIcon:u(s,{}),onClick:()=>{var e;(e=navigator.clipboard)==null||e.writeText(t.userCode)},children:v.t(`aiu_copyCode`)}),u(_,{variant:`contained`,startIcon:u(n,{}),href:t.verificationUrl,target:`_blank`,rel:`noopener noreferrer`,children:v.t(`aiu_openPage`)})]}),u(c,{severity:`info`,children:v.t(`aiu_deviceWaiting`)})]});if(t.status===`awaiting-paste`){let r=t.flow===`paste-url`;return a(x,{sx:{display:`flex`,flexDirection:`column`,gap:1.5},children:[u(h,{variant:`body2`,children:v.t(r?`aiu_googleStep1`:`aiu_claudeStep1`)}),a(x,{sx:{display:`flex`,gap:1,flexWrap:`wrap`},children:[u(_,{variant:`contained`,startIcon:u(n,{}),href:t.url,target:`_blank`,rel:`noopener noreferrer`,children:v.t(`aiu_openSignIn`)}),u(_,{variant:`outlined`,startIcon:u(s,{}),onClick:()=>{var e;(e=navigator.clipboard)==null||e.writeText(t.url)},children:v.t(`aiu_copyLink`)})]}),r?u(c,{severity:`warning`,children:v.t(`aiu_googleErrorPageHint`)}):null,u(h,{variant:`body2`,children:v.t(r?`aiu_googleStep2`:`aiu_claudeStep2`)}),a(x,{sx:{display:`flex`,gap:1,alignItems:`center`,flexWrap:`wrap`},children:[u(d,{size:`small`,label:v.t(r?`aiu_addressLabel`:`aiu_codeLabel`),value:this.state.drafts[e]??``,onChange:t=>this.setState(n=>({drafts:{...n.drafts,[e]:t.target.value}})),sx:{minWidth:340,flexGrow:1}}),u(_,{variant:`contained`,disabled:i||!(this.state.drafts[e]??``).trim(),onClick:()=>void this.run(`signInSubmit`,e,this.state.drafts[e]),children:i?u(b,{size:20}):v.t(`aiu_redeem`)})]})]})}return a(x,{sx:{display:`flex`,flexDirection:`column`,gap:1},children:[t.status===`failed`?u(c,{severity:`error`,children:t.reason}):null,u(x,{children:u(_,{variant:`contained`,startIcon:u(n,{}),disabled:i,onClick:()=>void this.run(`signInStart`,e),children:i?u(b,{size:20}):v.t(`aiu_startSignIn`)})})]})}renderSubscriptionRow(e){let n=k(this.accounts(),e.provider);return a(x,{children:[a(x,{sx:{display:`flex`,alignItems:`center`,gap:1.5,py:1,borderBottom:1,borderColor:`divider`},children:[u(o,{sx:{width:28,height:28,bgcolor:`transparent`},children:u(l,{fontSize:`small`,color:`primary`})}),a(x,{sx:{minWidth:180},children:[u(h,{children:e.label}),u(h,{variant:`caption`,sx:{opacity:.7},children:v.t(e.captionKey)})]}),n?this.renderServiceBadge(e.provider,``):null,n?this.renderThreshold(`${e.provider}-t`,n,{provider:e.provider}):null,u(t,{checked:!!n,onChange:t=>this.commit(A(this.accounts(),e.provider,t.target.checked,e.label)),sx:{ml:n?0:`auto`}})]}),n?u(x,{sx:{pl:6,py:1.5,borderBottom:1,borderColor:`divider`},children:this.renderSignIn(e.provider)}):null]},e.provider)}renderCredentialRow(e){var n;let r=this.accounts(),s=r.find(t=>t.credentialId===e.id),f=D(e.suffix,e.name),p=(s==null?void 0:s.provider)||(f==null?void 0:f.provider)||this.state.providerChoice[e.id]||``,m=((n=O.find(e=>e.provider===p))==null?void 0:n.needsAdminKey)??(f==null?void 0:f.needsAdminKey)??!1,g=!f&&!this.state.providerChoice[e.id]&&!s;return a(x,{sx:{borderBottom:1,borderColor:`divider`},children:[a(x,{sx:{display:`flex`,alignItems:`center`,gap:1.5,py:1},children:[u(o,{src:e.icon,sx:{width:28,height:28,bgcolor:`transparent`},children:u(l,{fontSize:`small`})}),a(x,{sx:{minWidth:180},children:[u(h,{children:e.name}),u(h,{variant:`caption`,sx:{opacity:.7},children:v.t(`aiu_storedKey`)})]}),g?u(d,{select:!0,size:`small`,label:v.t(`aiu_provider`),value:this.state.providerChoice[e.id]||``,onChange:t=>this.setState(n=>({providerChoice:{...n.providerChoice,[e.id]:t.target.value}})),sx:{minWidth:170},children:O.map(e=>u(i,{value:e.provider,children:e.label},e.provider))}):null,s?this.renderServiceBadge(s.provider,e.id):null,s?this.renderThreshold(`${e.id}-t`,s,{credentialId:e.id}):null,u(t,{checked:!!s,disabled:!p,onChange:t=>this.commit(j(r,e,p,t.target.checked)),sx:{ml:s?0:`auto`}})]}),s&&m?u(c,{severity:`info`,sx:{mb:1},children:v.t(`aiu_adminKeyHint`)}):null]},e.id)}renderItem(){return u(x,{"data-testid":`aiu-config`,sx:{maxWidth:760},children:u(p,{variant:`outlined`,children:a(S,{children:[u(h,{variant:`h6`,sx:{mb:.5},children:v.t(`aiu_storedTitle`)}),u(h,{variant:`body2`,sx:{opacity:.8,mb:1},children:v.t(`aiu_storedHint`)}),E.map(e=>this.renderSubscriptionRow(e)),this.state.credentialsLoaded?this.state.credentials.map(e=>this.renderCredentialRow(e)):u(b,{size:24,sx:{mt:1}}),this.state.credentialsLoaded&&this.state.credentials.length===0?u(c,{severity:`info`,sx:{mt:1},children:v.t(`aiu_noCredentials`)}):null]})})})}};export{N as t};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./virtual_mf-exposes___mfe_internal__AiUsageComponentSet__customComponents_js-Wtw6jqAg.js";import"./virtual_mf-REMOTE_ENTRY_ID___mfe_internal__AiUsageComponentSet__customComponents_js-BjDe1IXR.js";import{Ba as e,Qt as t,Sa as n,Va as r,Za as i,_o as a,in as o,ni as s,sn as c}from"./_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-DzPBJWNQ.js";import{t as l}from"./_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-CFn_lBXc.js";import{t as u}from"./ConfigPanel-CGCj6Crq.js";(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})(),a();var d={aiu_storedTitle:`AI accounts`,aiu_storedHint:`Switch on what you want to monitor. Subscriptions sign in with your own account; the entries below come from the admin's central credential storage (Settings → Credentials).`,aiu_capClaude:`Subscription — sign in once with your Claude account`,aiu_capChatgpt:`Subscription — sign in once with your ChatGPT account`,aiu_capGemini:`Subscription — sign in once with your Google account`,aiu_storedKey:`Stored key`,aiu_provider:`Provider`,aiu_warnAt:`Warn at %`,aiu_signedIn:`Signed in`,aiu_signOut:`Sign out`,aiu_startSignIn:`Sign in`,aiu_claudeStep1:`1. Open the sign-in page and log in with your Claude account.`,aiu_claudeStep2:`2. Copy the code shown there and redeem it here.`,aiu_codeLabel:`Code`,aiu_googleStep1:`1. Open the sign-in page and log in with your Google account.`,aiu_googleStep2:`2. Copy the ENTIRE address from your browser's address bar and paste it here.`,aiu_googleErrorPageHint:`Your browser will then show an error page ("site cannot be reached") — that is expected. Google sends the result to your own computer, where nothing is listening. The address bar still contains the sign-in result, and that is what is needed here.`,aiu_addressLabel:`Address from the browser`,aiu_deviceStep1:`1. Open the OpenAI page and type in this code:`,aiu_copyCode:`Copy code`,aiu_openPage:`Open page`,aiu_deviceWaiting:`Waiting for your confirmation — this card updates by itself once you are done.`,aiu_openSignIn:`Open sign-in page`,aiu_copyLink:`Copy link`,aiu_redeem:`Redeem`,aiu_saveFirst:`Save the settings first — then the sign-in can start.`,aiu_instanceNotRunning:`The instance is not running — start it to sign in.`,aiu_noAnswer:`the instance did not answer`,aiu_noCredentials:`No AI credentials stored yet — add them in the admin under Settings → Credentials.`,aiu_adminKeyHint:`This account needs an ADMIN key of your organisation, not the key the admin AI assistant uses. Personal accounts without an organisation cannot deliver usage reports.`,aiu_stOnline:`Online`,aiu_stLimited:`Limited`,aiu_stOffline:`Offline`},f={aiu_storedTitle:`KI-Konten`,aiu_storedHint:`Einschalten, was überwacht werden soll. Abos melden sich mit Ihrem eigenen Konto an; die Einträge darunter stammen aus dem zentralen Zugangsdaten-Speicher des Admin (Einstellungen → Zugangsdaten).`,aiu_capClaude:`Abo — einmalig mit Ihrem Claude-Konto anmelden`,aiu_capChatgpt:`Abo — einmalig mit Ihrem ChatGPT-Konto anmelden`,aiu_capGemini:`Abo — einmalig mit Ihrem Google-Konto anmelden`,aiu_storedKey:`Gespeicherter Schlüssel`,aiu_provider:`Anbieter`,aiu_warnAt:`Warnen ab %`,aiu_signedIn:`Angemeldet`,aiu_signOut:`Abmelden`,aiu_startSignIn:`Anmelden`,aiu_claudeStep1:`1. Anmeldeseite öffnen und mit Ihrem Claude-Konto anmelden.`,aiu_claudeStep2:`2. Den dort angezeigten Code kopieren und hier einlösen.`,aiu_codeLabel:`Code`,aiu_googleStep1:`1. Anmeldeseite öffnen und mit Ihrem Google-Konto anmelden.`,aiu_googleStep2:`2. Die KOMPLETTE Adresse aus der Adresszeile des Browsers kopieren und hier einfügen.`,aiu_googleErrorPageHint:`Ihr Browser zeigt danach eine Fehlerseite („Seite nicht erreichbar") — das ist richtig so. Google schickt das Ergebnis an Ihren eigenen Rechner, wo nichts darauf wartet. In der Adresszeile steht das Anmelde-Ergebnis trotzdem — und genau das wird hier gebraucht.`,aiu_addressLabel:`Adresse aus dem Browser`,aiu_deviceStep1:`1. Die OpenAI-Seite öffnen und diesen Code eintippen:`,aiu_copyCode:`Code kopieren`,aiu_openPage:`Seite öffnen`,aiu_deviceWaiting:`Warte auf Ihre Bestätigung — diese Karte aktualisiert sich von selbst, sobald Sie fertig sind.`,aiu_openSignIn:`Anmeldeseite öffnen`,aiu_copyLink:`Link kopieren`,aiu_redeem:`Einlösen`,aiu_saveFirst:`Zuerst die Einstellungen speichern — danach kann die Anmeldung starten.`,aiu_instanceNotRunning:`Die Instanz läuft nicht — zum Anmelden bitte starten.`,aiu_noAnswer:`die Instanz hat nicht geantwortet`,aiu_noCredentials:`Noch keine KI-Zugangsdaten gespeichert — im Admin unter Einstellungen → Zugangsdaten anlegen.`,aiu_adminKeyHint:`Dieses Konto braucht einen VERWALTUNGS-Schlüssel Ihrer Organisation, nicht den Schlüssel des Admin-KI-Assistenten. Einzelkonten ohne Organisation können keine Verbrauchs-Berichte liefern.`,aiu_stOnline:`Online`,aiu_stLimited:`Eingeschränkt`,aiu_stOffline:`Offline`},p={aiu_storedTitle:`Аккаунты ИИ`,aiu_storedHint:`Включите то, что нужно отслеживать. Подписки входят под вашим аккаунтом; записи ниже — из центрального хранилища учётных данных (Настройки → Учётные данные).`,aiu_capClaude:`Подписка — один раз войдите со своим аккаунтом Claude`,aiu_capChatgpt:`Подписка — один раз войдите со своим аккаунтом ChatGPT`,aiu_capGemini:`Подписка — один раз войдите со своим аккаунтом Google`,aiu_storedKey:`Сохранённый ключ`,aiu_provider:`Провайдер`,aiu_warnAt:`Предупреждать с %`,aiu_signedIn:`Вход выполнен`,aiu_signOut:`Выйти`,aiu_startSignIn:`Войти`,aiu_claudeStep1:`1. Откройте страницу входа и войдите со своим аккаунтом Claude.`,aiu_claudeStep2:`2. Скопируйте показанный код и активируйте его здесь.`,aiu_codeLabel:`Код`,aiu_googleStep1:`1. Откройте страницу входа и войдите со своим аккаунтом Google.`,aiu_googleStep2:`2. Скопируйте ВЕСЬ адрес из адресной строки браузера и вставьте сюда.`,aiu_googleErrorPageHint:`Затем браузер покажет страницу ошибки («не удаётся открыть сайт») — так и должно быть. Google отправляет результат на ваш компьютер, где его никто не ждёт. Но в адресной строке результат входа есть — именно он здесь и нужен.`,aiu_addressLabel:`Адрес из браузера`,aiu_deviceStep1:`1. Откройте страницу OpenAI и введите этот код:`,aiu_copyCode:`Копировать код`,aiu_openPage:`Открыть страницу`,aiu_deviceWaiting:`Ожидание подтверждения — карточка обновится сама.`,aiu_openSignIn:`Открыть страницу входа`,aiu_copyLink:`Копировать ссылку`,aiu_redeem:`Активировать`,aiu_saveFirst:`Сначала сохраните настройки — затем можно начать вход.`,aiu_instanceNotRunning:`Экземпляр не запущен — запустите его для входа.`,aiu_noAnswer:`экземпляр не ответил`,aiu_noCredentials:`Данные доступа ИИ ещё не сохранены — добавьте их: Настройки → Учётные данные.`,aiu_adminKeyHint:`Этому аккаунту нужен АДМИНИСТРАТИВНЫЙ ключ организации, а не ключ ИИ-ассистента. Личные аккаунты без организации не выдают отчёты о расходе.`,aiu_stOnline:`В сети`,aiu_stLimited:`Ограничено`,aiu_stOffline:`Не в сети`},m={aiu_storedTitle:`Contas de IA`,aiu_storedHint:`Ative o que deseja monitorizar. As assinaturas iniciam sessão com a sua conta; as entradas abaixo vêm do armazenamento central de credenciais (Definições → Credenciais).`,aiu_capClaude:`Assinatura — inicie sessão uma vez com a sua conta Claude`,aiu_capChatgpt:`Assinatura — inicie sessão uma vez com a sua conta ChatGPT`,aiu_capGemini:`Assinatura — inicie sessão uma vez com a sua conta Google`,aiu_storedKey:`Chave guardada`,aiu_provider:`Fornecedor`,aiu_warnAt:`Avisar a partir de %`,aiu_signedIn:`Sessão iniciada`,aiu_signOut:`Terminar sessão`,aiu_startSignIn:`Iniciar sessão`,aiu_claudeStep1:`1. Abra a página de início de sessão e entre com a sua conta Claude.`,aiu_claudeStep2:`2. Copie o código mostrado e resgate-o aqui.`,aiu_codeLabel:`Código`,aiu_googleStep1:`1. Abra a página de início de sessão e entre com a sua conta Google.`,aiu_googleStep2:`2. Copie o endereço COMPLETO da barra de endereços e cole-o aqui.`,aiu_googleErrorPageHint:`O navegador mostrará depois uma página de erro («não é possível aceder») — isso é esperado. A Google envia o resultado para o seu computador, onde nada está à escuta. O endereço contém na mesma o resultado — e é isso que é preciso aqui.`,aiu_addressLabel:`Endereço do navegador`,aiu_deviceStep1:`1. Abra a página da OpenAI e introduza este código:`,aiu_copyCode:`Copiar código`,aiu_openPage:`Abrir página`,aiu_deviceWaiting:`À espera da sua confirmação — este cartão atualiza-se sozinho.`,aiu_openSignIn:`Abrir página de início de sessão`,aiu_copyLink:`Copiar link`,aiu_redeem:`Resgatar`,aiu_saveFirst:`Guarde primeiro as definições — depois o início de sessão pode começar.`,aiu_instanceNotRunning:`A instância não está em execução — inicie-a para entrar.`,aiu_noAnswer:`a instância não respondeu`,aiu_noCredentials:`Ainda não há credenciais de IA guardadas — adicione-as em Definições → Credenciais.`,aiu_adminKeyHint:`Esta conta precisa de uma chave de ADMINISTRAÇÃO da organização, não da chave do assistente de IA. Contas pessoais sem organização não fornecem relatórios de uso.`,aiu_stOnline:`Online`,aiu_stLimited:`Limitado`,aiu_stOffline:`Offline`},h={aiu_storedTitle:`AI-accounts`,aiu_storedHint:`Schakel in wat u wilt bewaken. Abonnementen melden zich aan met uw eigen account; de vermeldingen daaronder komen uit de centrale opslag van toegangsgegevens (Instellingen → Toegangsgegevens).`,aiu_capClaude:`Abonnement — meld u eenmalig aan met uw Claude-account`,aiu_capChatgpt:`Abonnement — meld u eenmalig aan met uw ChatGPT-account`,aiu_capGemini:`Abonnement — meld u eenmalig aan met uw Google-account`,aiu_storedKey:`Opgeslagen sleutel`,aiu_provider:`Aanbieder`,aiu_warnAt:`Waarschuwen vanaf %`,aiu_signedIn:`Aangemeld`,aiu_signOut:`Afmelden`,aiu_startSignIn:`Aanmelden`,aiu_claudeStep1:`1. Open de aanmeldpagina en log in met uw Claude-account.`,aiu_claudeStep2:`2. Kopieer de getoonde code en wissel die hier in.`,aiu_codeLabel:`Code`,aiu_googleStep1:`1. Open de aanmeldpagina en log in met uw Google-account.`,aiu_googleStep2:`2. Kopieer het VOLLEDIGE adres uit de adresbalk en plak het hier.`,aiu_googleErrorPageHint:`Uw browser toont daarna een foutpagina («site kan niet worden bereikt») — dat hoort zo. Google stuurt het resultaat naar uw eigen computer, waar niets luistert. In de adresbalk staat het resultaat wél — en dat is wat hier nodig is.`,aiu_addressLabel:`Adres uit de browser`,aiu_deviceStep1:`1. Open de OpenAI-pagina en typ deze code in:`,aiu_copyCode:`Code kopiëren`,aiu_openPage:`Pagina openen`,aiu_deviceWaiting:`Wachten op uw bevestiging — deze kaart werkt zichzelf bij.`,aiu_openSignIn:`Aanmeldpagina openen`,aiu_copyLink:`Link kopiëren`,aiu_redeem:`Inwisselen`,aiu_saveFirst:`Sla eerst de instellingen op — daarna kan het aanmelden starten.`,aiu_instanceNotRunning:`De instantie draait niet — start deze om aan te melden.`,aiu_noAnswer:`de instantie antwoordde niet`,aiu_noCredentials:`Nog geen AI-toegangsgegevens opgeslagen — voeg ze toe via Instellingen → Toegangsgegevens.`,aiu_adminKeyHint:`Dit account heeft een BEHEER-sleutel van uw organisatie nodig, niet die van de AI-assistent. Persoonlijke accounts zonder organisatie leveren geen verbruiksrapporten.`,aiu_stOnline:`Online`,aiu_stLimited:`Beperkt`,aiu_stOffline:`Offline`},g={aiu_storedTitle:`Comptes IA`,aiu_storedHint:`Activez ce que vous voulez surveiller. Les abonnements se connectent avec votre propre compte ; les entrées ci-dessous viennent du stockage central des identifiants (Réglages → Identifiants).`,aiu_capClaude:`Abonnement — connectez-vous une fois avec votre compte Claude`,aiu_capChatgpt:`Abonnement — connectez-vous une fois avec votre compte ChatGPT`,aiu_capGemini:`Abonnement — connectez-vous une fois avec votre compte Google`,aiu_storedKey:`Clé enregistrée`,aiu_provider:`Fournisseur`,aiu_warnAt:`Avertir à partir de %`,aiu_signedIn:`Connecté`,aiu_signOut:`Se déconnecter`,aiu_startSignIn:`Se connecter`,aiu_claudeStep1:`1. Ouvrez la page de connexion et connectez-vous avec votre compte Claude.`,aiu_claudeStep2:`2. Copiez le code affiché et validez-le ici.`,aiu_codeLabel:`Code`,aiu_googleStep1:`1. Ouvrez la page de connexion et connectez-vous avec votre compte Google.`,aiu_googleStep2:`2. Copiez l'adresse COMPLÈTE depuis la barre d'adresse et collez-la ici.`,aiu_googleErrorPageHint:`Votre navigateur affichera ensuite une page d'erreur (« site inaccessible ») — c'est normal. Google envoie le résultat vers votre propre ordinateur, où rien n'écoute. L'adresse contient malgré tout le résultat — et c'est précisément ce qu'il faut ici.`,aiu_addressLabel:`Adresse du navigateur`,aiu_deviceStep1:`1. Ouvrez la page OpenAI et saisissez ce code :`,aiu_copyCode:`Copier le code`,aiu_openPage:`Ouvrir la page`,aiu_deviceWaiting:`En attente de votre confirmation — cette carte se met à jour toute seule.`,aiu_openSignIn:`Ouvrir la page de connexion`,aiu_copyLink:`Copier le lien`,aiu_redeem:`Valider`,aiu_saveFirst:`Enregistrez d'abord les réglages — la connexion pourra ensuite démarrer.`,aiu_instanceNotRunning:`L'instance ne tourne pas — démarrez-la pour vous connecter.`,aiu_noAnswer:`l'instance n'a pas répondu`,aiu_noCredentials:`Aucun identifiant IA enregistré — ajoutez-les via Réglages → Identifiants.`,aiu_adminKeyHint:`Ce compte nécessite une clé d'ADMINISTRATION de votre organisation, pas celle de l'assistant IA. Les comptes personnels sans organisation ne fournissent pas de rapports d'utilisation.`,aiu_stOnline:`En ligne`,aiu_stLimited:`Limité`,aiu_stOffline:`Hors ligne`},_={aiu_storedTitle:`Account IA`,aiu_storedHint:`Attiva ciò che vuoi monitorare. Gli abbonamenti accedono con il tuo account; le voci sotto provengono dall'archivio centrale delle credenziali (Impostazioni → Credenziali).`,aiu_capClaude:`Abbonamento — accedi una volta con il tuo account Claude`,aiu_capChatgpt:`Abbonamento — accedi una volta con il tuo account ChatGPT`,aiu_capGemini:`Abbonamento — accedi una volta con il tuo account Google`,aiu_storedKey:`Chiave salvata`,aiu_provider:`Fornitore`,aiu_warnAt:`Avvisa da %`,aiu_signedIn:`Accesso effettuato`,aiu_signOut:`Disconnetti`,aiu_startSignIn:`Accedi`,aiu_claudeStep1:`1. Apri la pagina di accesso ed entra con il tuo account Claude.`,aiu_claudeStep2:`2. Copia il codice mostrato e riscattalo qui.`,aiu_codeLabel:`Codice`,aiu_googleStep1:`1. Apri la pagina di accesso ed entra con il tuo account Google.`,aiu_googleStep2:`2. Copia l'INTERO indirizzo dalla barra degli indirizzi e incollalo qui.`,aiu_googleErrorPageHint:`Il browser mostrerà poi una pagina di errore («sito non raggiungibile») — è corretto così. Google invia il risultato al tuo computer, dove nessuno è in ascolto. Nell'indirizzo il risultato c'è comunque — ed è proprio quello che serve qui.`,aiu_addressLabel:`Indirizzo dal browser`,aiu_deviceStep1:`1. Apri la pagina OpenAI e digita questo codice:`,aiu_copyCode:`Copia codice`,aiu_openPage:`Apri pagina`,aiu_deviceWaiting:`In attesa della tua conferma — questa scheda si aggiorna da sola.`,aiu_openSignIn:`Apri pagina di accesso`,aiu_copyLink:`Copia link`,aiu_redeem:`Riscatta`,aiu_saveFirst:`Salva prima le impostazioni — poi l'accesso può iniziare.`,aiu_instanceNotRunning:`L'istanza non è in esecuzione — avviala per accedere.`,aiu_noAnswer:`l'istanza non ha risposto`,aiu_noCredentials:`Nessuna credenziale IA salvata — aggiungila in Impostazioni → Credenziali.`,aiu_adminKeyHint:`Questo account richiede una chiave di AMMINISTRAZIONE della tua organizzazione, non quella dell'assistente IA. Gli account personali senza organizzazione non forniscono rapporti di utilizzo.`,aiu_stOnline:`Online`,aiu_stLimited:`Limitato`,aiu_stOffline:`Offline`},v={aiu_storedTitle:`Cuentas de IA`,aiu_storedHint:`Active lo que quiera supervisar. Las suscripciones inician sesión con su propia cuenta; las entradas de abajo provienen del almacén central de credenciales (Ajustes → Credenciales).`,aiu_capClaude:`Suscripción — inicie sesión una vez con su cuenta Claude`,aiu_capChatgpt:`Suscripción — inicie sesión una vez con su cuenta ChatGPT`,aiu_capGemini:`Suscripción — inicie sesión una vez con su cuenta Google`,aiu_storedKey:`Clave guardada`,aiu_provider:`Proveedor`,aiu_warnAt:`Avisar desde %`,aiu_signedIn:`Sesión iniciada`,aiu_signOut:`Cerrar sesión`,aiu_startSignIn:`Iniciar sesión`,aiu_claudeStep1:`1. Abra la página de inicio de sesión y entre con su cuenta Claude.`,aiu_claudeStep2:`2. Copie el código mostrado y canjéelo aquí.`,aiu_codeLabel:`Código`,aiu_googleStep1:`1. Abra la página de inicio de sesión y entre con su cuenta Google.`,aiu_googleStep2:`2. Copie la dirección COMPLETA de la barra de direcciones y péguela aquí.`,aiu_googleErrorPageHint:`Su navegador mostrará después una página de error («no se puede acceder al sitio») — es lo esperado. Google envía el resultado a su propio ordenador, donde nada está escuchando. La dirección contiene el resultado igualmente — y eso es lo que hace falta aquí.`,aiu_addressLabel:`Dirección del navegador`,aiu_deviceStep1:`1. Abra la página de OpenAI y escriba este código:`,aiu_copyCode:`Copiar código`,aiu_openPage:`Abrir página`,aiu_deviceWaiting:`Esperando su confirmación — esta tarjeta se actualiza sola.`,aiu_openSignIn:`Abrir página de inicio de sesión`,aiu_copyLink:`Copiar enlace`,aiu_redeem:`Canjear`,aiu_saveFirst:`Guarde primero la configuración — después puede comenzar el inicio de sesión.`,aiu_instanceNotRunning:`La instancia no está en marcha — iníciela para entrar.`,aiu_noAnswer:`la instancia no respondió`,aiu_noCredentials:`Aún no hay credenciales de IA guardadas — añádalas en Ajustes → Credenciales.`,aiu_adminKeyHint:`Esta cuenta necesita una clave de ADMINISTRACIÓN de su organización, no la del asistente de IA. Las cuentas personales sin organización no ofrecen informes de uso.`,aiu_stOnline:`En línea`,aiu_stLimited:`Limitado`,aiu_stOffline:`Sin conexión`},y={aiu_storedTitle:`Konta AI`,aiu_storedHint:`Włącz to, co ma być monitorowane. Subskrypcje logują się Twoim kontem; pozycje poniżej pochodzą z centralnego magazynu danych dostępu (Ustawienia → Dane dostępu).`,aiu_capClaude:`Subskrypcja — zaloguj się raz kontem Claude`,aiu_capChatgpt:`Subskrypcja — zaloguj się raz kontem ChatGPT`,aiu_capGemini:`Subskrypcja — zaloguj się raz kontem Google`,aiu_storedKey:`Zapisany klucz`,aiu_provider:`Dostawca`,aiu_warnAt:`Ostrzegaj od %`,aiu_signedIn:`Zalogowano`,aiu_signOut:`Wyloguj`,aiu_startSignIn:`Zaloguj`,aiu_claudeStep1:`1. Otwórz stronę logowania i zaloguj się kontem Claude.`,aiu_claudeStep2:`2. Skopiuj pokazany kod i zrealizuj go tutaj.`,aiu_codeLabel:`Kod`,aiu_googleStep1:`1. Otwórz stronę logowania i zaloguj się kontem Google.`,aiu_googleStep2:`2. Skopiuj CAŁY adres z paska adresu i wklej go tutaj.`,aiu_googleErrorPageHint:`Przeglądarka pokaże potem stronę błędu («nie można otworzyć strony») — tak ma być. Google wysyła wynik na Twój komputer, gdzie nic nie nasłuchuje. W pasku adresu wynik jednak jest — i właśnie on jest tu potrzebny.`,aiu_addressLabel:`Adres z przeglądarki`,aiu_deviceStep1:`1. Otwórz stronę OpenAI i wpisz ten kod:`,aiu_copyCode:`Kopiuj kod`,aiu_openPage:`Otwórz stronę`,aiu_deviceWaiting:`Czekam na potwierdzenie — ta karta odświeży się sama.`,aiu_openSignIn:`Otwórz stronę logowania`,aiu_copyLink:`Kopiuj link`,aiu_redeem:`Zrealizuj`,aiu_saveFirst:`Najpierw zapisz ustawienia — potem można rozpocząć logowanie.`,aiu_instanceNotRunning:`Instancja nie działa — uruchom ją, aby się zalogować.`,aiu_noAnswer:`instancja nie odpowiedziała`,aiu_noCredentials:`Brak zapisanych danych dostępu AI — dodaj je w Ustawienia → Dane dostępu.`,aiu_adminKeyHint:`To konto wymaga klucza ADMINISTRACYJNEGO organizacji, a nie klucza asystenta AI. Konta osobiste bez organizacji nie dostarczają raportów zużycia.`,aiu_stOnline:`Online`,aiu_stLimited:`Ograniczone`,aiu_stOffline:`Offline`},b={aiu_storedTitle:`Акаунти ШІ`,aiu_storedHint:`Увімкніть те, що треба відстежувати. Підписки входять під вашим акаунтом; записи нижче — з центрального сховища даних доступу (Налаштування → Дані доступу).`,aiu_capClaude:`Підписка — увійдіть один раз акаунтом Claude`,aiu_capChatgpt:`Підписка — увійдіть один раз акаунтом ChatGPT`,aiu_capGemini:`Підписка — увійдіть один раз акаунтом Google`,aiu_storedKey:`Збережений ключ`,aiu_provider:`Постачальник`,aiu_warnAt:`Попереджати від %`,aiu_signedIn:`Вхід виконано`,aiu_signOut:`Вийти`,aiu_startSignIn:`Увійти`,aiu_claudeStep1:`1. Відкрийте сторінку входу та увійдіть акаунтом Claude.`,aiu_claudeStep2:`2. Скопіюйте показаний код і активуйте його тут.`,aiu_codeLabel:`Код`,aiu_googleStep1:`1. Відкрийте сторінку входу та увійдіть акаунтом Google.`,aiu_googleStep2:`2. Скопіюйте ВЕСЬ адрес з адресного рядка та вставте сюди.`,aiu_googleErrorPageHint:`Далі браузер покаже сторінку помилки («сайт недоступний») — так і має бути. Google надсилає результат на ваш комп'ютер, де ніхто не слухає. В адресному рядку результат усе одно є — саме він тут і потрібен.`,aiu_addressLabel:`Адреса з браузера`,aiu_deviceStep1:`1. Відкрийте сторінку OpenAI та введіть цей код:`,aiu_copyCode:`Копіювати код`,aiu_openPage:`Відкрити сторінку`,aiu_deviceWaiting:`Очікую підтвердження — картка оновиться сама.`,aiu_openSignIn:`Відкрити сторінку входу`,aiu_copyLink:`Копіювати посилання`,aiu_redeem:`Активувати`,aiu_saveFirst:`Спочатку збережіть налаштування — потім можна починати вхід.`,aiu_instanceNotRunning:`Екземпляр не запущено — запустіть його для входу.`,aiu_noAnswer:`екземпляр не відповів`,aiu_noCredentials:`Ще немає збережених даних доступу ШІ — додайте їх у Налаштування → Дані доступу.`,aiu_adminKeyHint:`Цей акаунт потребує АДМІНІСТРАТИВНОГО ключа організації, а не ключа ШІ-асистента. Особисті акаунти без організації не дають звітів про використання.`,aiu_stOnline:`У мережі`,aiu_stLimited:`Обмежено`,aiu_stOffline:`Не в мережі`},x={aiu_storedTitle:`AI 账户`,aiu_storedHint:`开启需要监控的项目。订阅使用您自己的账户登录;下方条目来自管理界面的中央凭据存储(设置 → 凭据)。`,aiu_capClaude:`订阅——使用您的 Claude 账户登录一次`,aiu_capChatgpt:`订阅——使用您的 ChatGPT 账户登录一次`,aiu_capGemini:`订阅——使用您的 Google 账户登录一次`,aiu_storedKey:`已保存的密钥`,aiu_provider:`提供商`,aiu_warnAt:`警告阈值 %`,aiu_signedIn:`已登录`,aiu_signOut:`退出登录`,aiu_startSignIn:`登录`,aiu_claudeStep1:`1. 打开登录页面并使用您的 Claude 账户登录。`,aiu_claudeStep2:`2. 复制页面显示的代码并在此兑换。`,aiu_codeLabel:`代码`,aiu_googleStep1:`1. 打开登录页面并使用您的 Google 账户登录。`,aiu_googleStep2:`2. 复制浏览器地址栏中的完整地址并粘贴到这里。`,aiu_googleErrorPageHint:`随后浏览器会显示错误页面(无法访问此网站)——这是正常的。Google 把结果发送到您自己的电脑,而那里没有程序在接收。地址栏中仍然包含登录结果——这正是此处需要的。`,aiu_addressLabel:`来自浏览器的地址`,aiu_deviceStep1:`1. 打开 OpenAI 页面并输入此代码:`,aiu_copyCode:`复制代码`,aiu_openPage:`打开页面`,aiu_deviceWaiting:`正在等待您的确认——完成后此卡片会自动更新。`,aiu_openSignIn:`打开登录页面`,aiu_copyLink:`复制链接`,aiu_redeem:`兑换`,aiu_saveFirst:`请先保存设置——之后即可开始登录。`,aiu_instanceNotRunning:`实例未运行——请启动后再登录。`,aiu_noAnswer:`实例没有响应`,aiu_noCredentials:`尚未保存任何 AI 凭据——请在 设置 → 凭据 中添加。`,aiu_adminKeyHint:`此账户需要您所在组织的管理密钥,而不是管理界面 AI 助手使用的密钥。没有组织的个人账户无法提供用量报告。`,aiu_stOnline:`在线`,aiu_stLimited:`受限`,aiu_stOffline:`离线`},S=class extends t{constructor(e){super(e,{...e}),this.state={...this.state,data:{accounts:[]},originalData:{accounts:[]},theme:this.createTheme()};let t={en:d,de:f,ru:p,pt:m,nl:h,fr:g,it:_,es:v,pl:y,uk:b,"zh-cn":x};for(let[e,n]of Object.entries(t))o.extendTranslations(n,e);let n=(navigator.language||navigator.userLanguage||`en`).toLowerCase();o.setLanguage(n.startsWith(`zh`)?`zh-cn`:n.substring(0,2))}render(){return this.state.loaded?r(e,{injectFirst:!0,children:r(n,{theme:this.state.theme,children:r(s,{sx:{height:`100%`,bgcolor:`background.default`,color:`text.primary`},children:r(`div`,{style:{padding:50,width:760},children:r(u,{oContext:{adapterName:`ai-usage`,socket:this.socket,instance:0,themeType:this.state.theme.palette.mode,isFloatComma:!0,dateFormat:``,forceUpdate:()=>{},systemConfig:{},theme:this.state.theme,_themeName:this.state.themeName,onCommandRunning:()=>{}},alive:!0,changed:!1,themeName:this.state.theme.palette.mode,common:{},attr:`_accounts`,data:this.state.data,originalData:this.state.originalData,onError:()=>{},schema:{url:`custom/customComponents.js`,i18n:!0,name:`AiUsageComponentSet/Components/ConfigPanel`,type:`custom`},onChange:e=>this.setState({data:e})})})})})}):r(e,{injectFirst:!0,children:r(n,{theme:this.state.theme,children:r(c,{themeType:this.state.themeType})})})}};a(),window.adapterName=`ai-usage`;var C=document.getElementById(`root`);C&&l(C).render(r(i.StrictMode,{children:r(S,{socket:{port:8081}})}));
|
|
1
|
+
import"./virtual_mf-exposes___mfe_internal__AiUsageComponentSet__customComponents_js-DAX-5uLT.js";import"./virtual_mf-REMOTE_ENTRY_ID___mfe_internal__AiUsageComponentSet__customComponents_js-haafVBNm.js";import{Ba as e,Qt as t,Sa as n,Va as r,Za as i,_o as a,in as o,ni as s,sn as c}from"./_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-DzPBJWNQ.js";import{t as l}from"./_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-CFn_lBXc.js";import{t as u}from"./ConfigPanel-7wtMdF7R.js";(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})(),a();var d={aiu_storedTitle:`AI accounts`,aiu_storedHint:`Switch on what you want to monitor. Subscriptions sign in with your own account; the entries below come from the admin's central credential storage (Settings → Credentials).`,aiu_capClaude:`Subscription — sign in once with your Claude account`,aiu_capChatgpt:`Subscription — sign in once with your ChatGPT account`,aiu_capGemini:`Subscription — sign in once with your Google account`,aiu_storedKey:`Stored key`,aiu_provider:`Provider`,aiu_warnAt:`Warn at %`,aiu_signedIn:`Signed in`,aiu_signOut:`Sign out`,aiu_startSignIn:`Sign in`,aiu_claudeStep1:`1. Open the sign-in page and log in with your Claude account.`,aiu_claudeStep2:`2. Copy the code shown there and redeem it here.`,aiu_codeLabel:`Code`,aiu_googleStep1:`1. Open the sign-in page and log in with your Google account.`,aiu_googleStep2:`2. Copy the ENTIRE address from your browser's address bar and paste it here.`,aiu_googleErrorPageHint:`Your browser will then show an error page ("site cannot be reached") — that is expected. Google sends the result to your own computer, where nothing is listening. The address bar still contains the sign-in result, and that is what is needed here.`,aiu_addressLabel:`Address from the browser`,aiu_deviceStep1:`1. Open the OpenAI page and type in this code:`,aiu_copyCode:`Copy code`,aiu_openPage:`Open page`,aiu_deviceWaiting:`Waiting for your confirmation — this card updates by itself once you are done.`,aiu_openSignIn:`Open sign-in page`,aiu_copyLink:`Copy link`,aiu_redeem:`Redeem`,aiu_saveFirst:`Save the settings first — then the sign-in can start.`,aiu_instanceNotRunning:`The instance is not running — start it to sign in.`,aiu_noAnswer:`the instance did not answer`,aiu_noCredentials:`No AI credentials stored yet — add them in the admin under Settings → Credentials.`,aiu_adminKeyHint:`This account needs an ADMIN key of your organisation, not the key the admin AI assistant uses. Personal accounts without an organisation cannot deliver usage reports.`,aiu_stOnline:`Online`,aiu_stLimited:`Limited`,aiu_stOffline:`Offline`},f={aiu_storedTitle:`KI-Konten`,aiu_storedHint:`Einschalten, was überwacht werden soll. Abos melden sich mit Ihrem eigenen Konto an; die Einträge darunter stammen aus dem zentralen Zugangsdaten-Speicher des Admin (Einstellungen → Zugangsdaten).`,aiu_capClaude:`Abo — einmalig mit Ihrem Claude-Konto anmelden`,aiu_capChatgpt:`Abo — einmalig mit Ihrem ChatGPT-Konto anmelden`,aiu_capGemini:`Abo — einmalig mit Ihrem Google-Konto anmelden`,aiu_storedKey:`Gespeicherter Schlüssel`,aiu_provider:`Anbieter`,aiu_warnAt:`Warnen ab %`,aiu_signedIn:`Angemeldet`,aiu_signOut:`Abmelden`,aiu_startSignIn:`Anmelden`,aiu_claudeStep1:`1. Anmeldeseite öffnen und mit Ihrem Claude-Konto anmelden.`,aiu_claudeStep2:`2. Den dort angezeigten Code kopieren und hier einlösen.`,aiu_codeLabel:`Code`,aiu_googleStep1:`1. Anmeldeseite öffnen und mit Ihrem Google-Konto anmelden.`,aiu_googleStep2:`2. Die KOMPLETTE Adresse aus der Adresszeile des Browsers kopieren und hier einfügen.`,aiu_googleErrorPageHint:`Ihr Browser zeigt danach eine Fehlerseite („Seite nicht erreichbar") — das ist richtig so. Google schickt das Ergebnis an Ihren eigenen Rechner, wo nichts darauf wartet. In der Adresszeile steht das Anmelde-Ergebnis trotzdem — und genau das wird hier gebraucht.`,aiu_addressLabel:`Adresse aus dem Browser`,aiu_deviceStep1:`1. Die OpenAI-Seite öffnen und diesen Code eintippen:`,aiu_copyCode:`Code kopieren`,aiu_openPage:`Seite öffnen`,aiu_deviceWaiting:`Warte auf Ihre Bestätigung — diese Karte aktualisiert sich von selbst, sobald Sie fertig sind.`,aiu_openSignIn:`Anmeldeseite öffnen`,aiu_copyLink:`Link kopieren`,aiu_redeem:`Einlösen`,aiu_saveFirst:`Zuerst die Einstellungen speichern — danach kann die Anmeldung starten.`,aiu_instanceNotRunning:`Die Instanz läuft nicht — zum Anmelden bitte starten.`,aiu_noAnswer:`die Instanz hat nicht geantwortet`,aiu_noCredentials:`Noch keine KI-Zugangsdaten gespeichert — im Admin unter Einstellungen → Zugangsdaten anlegen.`,aiu_adminKeyHint:`Dieses Konto braucht einen VERWALTUNGS-Schlüssel Ihrer Organisation, nicht den Schlüssel des Admin-KI-Assistenten. Einzelkonten ohne Organisation können keine Verbrauchs-Berichte liefern.`,aiu_stOnline:`Online`,aiu_stLimited:`Eingeschränkt`,aiu_stOffline:`Offline`},p={aiu_storedTitle:`Аккаунты ИИ`,aiu_storedHint:`Включите то, что нужно отслеживать. Подписки входят под вашим аккаунтом; записи ниже — из центрального хранилища учётных данных (Настройки → Учётные данные).`,aiu_capClaude:`Подписка — один раз войдите со своим аккаунтом Claude`,aiu_capChatgpt:`Подписка — один раз войдите со своим аккаунтом ChatGPT`,aiu_capGemini:`Подписка — один раз войдите со своим аккаунтом Google`,aiu_storedKey:`Сохранённый ключ`,aiu_provider:`Провайдер`,aiu_warnAt:`Предупреждать с %`,aiu_signedIn:`Вход выполнен`,aiu_signOut:`Выйти`,aiu_startSignIn:`Войти`,aiu_claudeStep1:`1. Откройте страницу входа и войдите со своим аккаунтом Claude.`,aiu_claudeStep2:`2. Скопируйте показанный код и активируйте его здесь.`,aiu_codeLabel:`Код`,aiu_googleStep1:`1. Откройте страницу входа и войдите со своим аккаунтом Google.`,aiu_googleStep2:`2. Скопируйте ВЕСЬ адрес из адресной строки браузера и вставьте сюда.`,aiu_googleErrorPageHint:`Затем браузер покажет страницу ошибки («не удаётся открыть сайт») — так и должно быть. Google отправляет результат на ваш компьютер, где его никто не ждёт. Но в адресной строке результат входа есть — именно он здесь и нужен.`,aiu_addressLabel:`Адрес из браузера`,aiu_deviceStep1:`1. Откройте страницу OpenAI и введите этот код:`,aiu_copyCode:`Копировать код`,aiu_openPage:`Открыть страницу`,aiu_deviceWaiting:`Ожидание подтверждения — карточка обновится сама.`,aiu_openSignIn:`Открыть страницу входа`,aiu_copyLink:`Копировать ссылку`,aiu_redeem:`Активировать`,aiu_saveFirst:`Сначала сохраните настройки — затем можно начать вход.`,aiu_instanceNotRunning:`Экземпляр не запущен — запустите его для входа.`,aiu_noAnswer:`экземпляр не ответил`,aiu_noCredentials:`Данные доступа ИИ ещё не сохранены — добавьте их: Настройки → Учётные данные.`,aiu_adminKeyHint:`Этому аккаунту нужен АДМИНИСТРАТИВНЫЙ ключ организации, а не ключ ИИ-ассистента. Личные аккаунты без организации не выдают отчёты о расходе.`,aiu_stOnline:`В сети`,aiu_stLimited:`Ограничено`,aiu_stOffline:`Не в сети`},m={aiu_storedTitle:`Contas de IA`,aiu_storedHint:`Ative o que deseja monitorizar. As assinaturas iniciam sessão com a sua conta; as entradas abaixo vêm do armazenamento central de credenciais (Definições → Credenciais).`,aiu_capClaude:`Assinatura — inicie sessão uma vez com a sua conta Claude`,aiu_capChatgpt:`Assinatura — inicie sessão uma vez com a sua conta ChatGPT`,aiu_capGemini:`Assinatura — inicie sessão uma vez com a sua conta Google`,aiu_storedKey:`Chave guardada`,aiu_provider:`Fornecedor`,aiu_warnAt:`Avisar a partir de %`,aiu_signedIn:`Sessão iniciada`,aiu_signOut:`Terminar sessão`,aiu_startSignIn:`Iniciar sessão`,aiu_claudeStep1:`1. Abra a página de início de sessão e entre com a sua conta Claude.`,aiu_claudeStep2:`2. Copie o código mostrado e resgate-o aqui.`,aiu_codeLabel:`Código`,aiu_googleStep1:`1. Abra a página de início de sessão e entre com a sua conta Google.`,aiu_googleStep2:`2. Copie o endereço COMPLETO da barra de endereços e cole-o aqui.`,aiu_googleErrorPageHint:`O navegador mostrará depois uma página de erro («não é possível aceder») — isso é esperado. A Google envia o resultado para o seu computador, onde nada está à escuta. O endereço contém na mesma o resultado — e é isso que é preciso aqui.`,aiu_addressLabel:`Endereço do navegador`,aiu_deviceStep1:`1. Abra a página da OpenAI e introduza este código:`,aiu_copyCode:`Copiar código`,aiu_openPage:`Abrir página`,aiu_deviceWaiting:`À espera da sua confirmação — este cartão atualiza-se sozinho.`,aiu_openSignIn:`Abrir página de início de sessão`,aiu_copyLink:`Copiar link`,aiu_redeem:`Resgatar`,aiu_saveFirst:`Guarde primeiro as definições — depois o início de sessão pode começar.`,aiu_instanceNotRunning:`A instância não está em execução — inicie-a para entrar.`,aiu_noAnswer:`a instância não respondeu`,aiu_noCredentials:`Ainda não há credenciais de IA guardadas — adicione-as em Definições → Credenciais.`,aiu_adminKeyHint:`Esta conta precisa de uma chave de ADMINISTRAÇÃO da organização, não da chave do assistente de IA. Contas pessoais sem organização não fornecem relatórios de uso.`,aiu_stOnline:`Online`,aiu_stLimited:`Limitado`,aiu_stOffline:`Offline`},h={aiu_storedTitle:`AI-accounts`,aiu_storedHint:`Schakel in wat u wilt bewaken. Abonnementen melden zich aan met uw eigen account; de vermeldingen daaronder komen uit de centrale opslag van toegangsgegevens (Instellingen → Toegangsgegevens).`,aiu_capClaude:`Abonnement — meld u eenmalig aan met uw Claude-account`,aiu_capChatgpt:`Abonnement — meld u eenmalig aan met uw ChatGPT-account`,aiu_capGemini:`Abonnement — meld u eenmalig aan met uw Google-account`,aiu_storedKey:`Opgeslagen sleutel`,aiu_provider:`Aanbieder`,aiu_warnAt:`Waarschuwen vanaf %`,aiu_signedIn:`Aangemeld`,aiu_signOut:`Afmelden`,aiu_startSignIn:`Aanmelden`,aiu_claudeStep1:`1. Open de aanmeldpagina en log in met uw Claude-account.`,aiu_claudeStep2:`2. Kopieer de getoonde code en wissel die hier in.`,aiu_codeLabel:`Code`,aiu_googleStep1:`1. Open de aanmeldpagina en log in met uw Google-account.`,aiu_googleStep2:`2. Kopieer het VOLLEDIGE adres uit de adresbalk en plak het hier.`,aiu_googleErrorPageHint:`Uw browser toont daarna een foutpagina («site kan niet worden bereikt») — dat hoort zo. Google stuurt het resultaat naar uw eigen computer, waar niets luistert. In de adresbalk staat het resultaat wél — en dat is wat hier nodig is.`,aiu_addressLabel:`Adres uit de browser`,aiu_deviceStep1:`1. Open de OpenAI-pagina en typ deze code in:`,aiu_copyCode:`Code kopiëren`,aiu_openPage:`Pagina openen`,aiu_deviceWaiting:`Wachten op uw bevestiging — deze kaart werkt zichzelf bij.`,aiu_openSignIn:`Aanmeldpagina openen`,aiu_copyLink:`Link kopiëren`,aiu_redeem:`Inwisselen`,aiu_saveFirst:`Sla eerst de instellingen op — daarna kan het aanmelden starten.`,aiu_instanceNotRunning:`De instantie draait niet — start deze om aan te melden.`,aiu_noAnswer:`de instantie antwoordde niet`,aiu_noCredentials:`Nog geen AI-toegangsgegevens opgeslagen — voeg ze toe via Instellingen → Toegangsgegevens.`,aiu_adminKeyHint:`Dit account heeft een BEHEER-sleutel van uw organisatie nodig, niet die van de AI-assistent. Persoonlijke accounts zonder organisatie leveren geen verbruiksrapporten.`,aiu_stOnline:`Online`,aiu_stLimited:`Beperkt`,aiu_stOffline:`Offline`},g={aiu_storedTitle:`Comptes IA`,aiu_storedHint:`Activez ce que vous voulez surveiller. Les abonnements se connectent avec votre propre compte ; les entrées ci-dessous viennent du stockage central des identifiants (Réglages → Identifiants).`,aiu_capClaude:`Abonnement — connectez-vous une fois avec votre compte Claude`,aiu_capChatgpt:`Abonnement — connectez-vous une fois avec votre compte ChatGPT`,aiu_capGemini:`Abonnement — connectez-vous une fois avec votre compte Google`,aiu_storedKey:`Clé enregistrée`,aiu_provider:`Fournisseur`,aiu_warnAt:`Avertir à partir de %`,aiu_signedIn:`Connecté`,aiu_signOut:`Se déconnecter`,aiu_startSignIn:`Se connecter`,aiu_claudeStep1:`1. Ouvrez la page de connexion et connectez-vous avec votre compte Claude.`,aiu_claudeStep2:`2. Copiez le code affiché et validez-le ici.`,aiu_codeLabel:`Code`,aiu_googleStep1:`1. Ouvrez la page de connexion et connectez-vous avec votre compte Google.`,aiu_googleStep2:`2. Copiez l'adresse COMPLÈTE depuis la barre d'adresse et collez-la ici.`,aiu_googleErrorPageHint:`Votre navigateur affichera ensuite une page d'erreur (« site inaccessible ») — c'est normal. Google envoie le résultat vers votre propre ordinateur, où rien n'écoute. L'adresse contient malgré tout le résultat — et c'est précisément ce qu'il faut ici.`,aiu_addressLabel:`Adresse du navigateur`,aiu_deviceStep1:`1. Ouvrez la page OpenAI et saisissez ce code :`,aiu_copyCode:`Copier le code`,aiu_openPage:`Ouvrir la page`,aiu_deviceWaiting:`En attente de votre confirmation — cette carte se met à jour toute seule.`,aiu_openSignIn:`Ouvrir la page de connexion`,aiu_copyLink:`Copier le lien`,aiu_redeem:`Valider`,aiu_saveFirst:`Enregistrez d'abord les réglages — la connexion pourra ensuite démarrer.`,aiu_instanceNotRunning:`L'instance ne tourne pas — démarrez-la pour vous connecter.`,aiu_noAnswer:`l'instance n'a pas répondu`,aiu_noCredentials:`Aucun identifiant IA enregistré — ajoutez-les via Réglages → Identifiants.`,aiu_adminKeyHint:`Ce compte nécessite une clé d'ADMINISTRATION de votre organisation, pas celle de l'assistant IA. Les comptes personnels sans organisation ne fournissent pas de rapports d'utilisation.`,aiu_stOnline:`En ligne`,aiu_stLimited:`Limité`,aiu_stOffline:`Hors ligne`},_={aiu_storedTitle:`Account IA`,aiu_storedHint:`Attiva ciò che vuoi monitorare. Gli abbonamenti accedono con il tuo account; le voci sotto provengono dall'archivio centrale delle credenziali (Impostazioni → Credenziali).`,aiu_capClaude:`Abbonamento — accedi una volta con il tuo account Claude`,aiu_capChatgpt:`Abbonamento — accedi una volta con il tuo account ChatGPT`,aiu_capGemini:`Abbonamento — accedi una volta con il tuo account Google`,aiu_storedKey:`Chiave salvata`,aiu_provider:`Fornitore`,aiu_warnAt:`Avvisa da %`,aiu_signedIn:`Accesso effettuato`,aiu_signOut:`Disconnetti`,aiu_startSignIn:`Accedi`,aiu_claudeStep1:`1. Apri la pagina di accesso ed entra con il tuo account Claude.`,aiu_claudeStep2:`2. Copia il codice mostrato e riscattalo qui.`,aiu_codeLabel:`Codice`,aiu_googleStep1:`1. Apri la pagina di accesso ed entra con il tuo account Google.`,aiu_googleStep2:`2. Copia l'INTERO indirizzo dalla barra degli indirizzi e incollalo qui.`,aiu_googleErrorPageHint:`Il browser mostrerà poi una pagina di errore («sito non raggiungibile») — è corretto così. Google invia il risultato al tuo computer, dove nessuno è in ascolto. Nell'indirizzo il risultato c'è comunque — ed è proprio quello che serve qui.`,aiu_addressLabel:`Indirizzo dal browser`,aiu_deviceStep1:`1. Apri la pagina OpenAI e digita questo codice:`,aiu_copyCode:`Copia codice`,aiu_openPage:`Apri pagina`,aiu_deviceWaiting:`In attesa della tua conferma — questa scheda si aggiorna da sola.`,aiu_openSignIn:`Apri pagina di accesso`,aiu_copyLink:`Copia link`,aiu_redeem:`Riscatta`,aiu_saveFirst:`Salva prima le impostazioni — poi l'accesso può iniziare.`,aiu_instanceNotRunning:`L'istanza non è in esecuzione — avviala per accedere.`,aiu_noAnswer:`l'istanza non ha risposto`,aiu_noCredentials:`Nessuna credenziale IA salvata — aggiungila in Impostazioni → Credenziali.`,aiu_adminKeyHint:`Questo account richiede una chiave di AMMINISTRAZIONE della tua organizzazione, non quella dell'assistente IA. Gli account personali senza organizzazione non forniscono rapporti di utilizzo.`,aiu_stOnline:`Online`,aiu_stLimited:`Limitato`,aiu_stOffline:`Offline`},v={aiu_storedTitle:`Cuentas de IA`,aiu_storedHint:`Active lo que quiera supervisar. Las suscripciones inician sesión con su propia cuenta; las entradas de abajo provienen del almacén central de credenciales (Ajustes → Credenciales).`,aiu_capClaude:`Suscripción — inicie sesión una vez con su cuenta Claude`,aiu_capChatgpt:`Suscripción — inicie sesión una vez con su cuenta ChatGPT`,aiu_capGemini:`Suscripción — inicie sesión una vez con su cuenta Google`,aiu_storedKey:`Clave guardada`,aiu_provider:`Proveedor`,aiu_warnAt:`Avisar desde %`,aiu_signedIn:`Sesión iniciada`,aiu_signOut:`Cerrar sesión`,aiu_startSignIn:`Iniciar sesión`,aiu_claudeStep1:`1. Abra la página de inicio de sesión y entre con su cuenta Claude.`,aiu_claudeStep2:`2. Copie el código mostrado y canjéelo aquí.`,aiu_codeLabel:`Código`,aiu_googleStep1:`1. Abra la página de inicio de sesión y entre con su cuenta Google.`,aiu_googleStep2:`2. Copie la dirección COMPLETA de la barra de direcciones y péguela aquí.`,aiu_googleErrorPageHint:`Su navegador mostrará después una página de error («no se puede acceder al sitio») — es lo esperado. Google envía el resultado a su propio ordenador, donde nada está escuchando. La dirección contiene el resultado igualmente — y eso es lo que hace falta aquí.`,aiu_addressLabel:`Dirección del navegador`,aiu_deviceStep1:`1. Abra la página de OpenAI y escriba este código:`,aiu_copyCode:`Copiar código`,aiu_openPage:`Abrir página`,aiu_deviceWaiting:`Esperando su confirmación — esta tarjeta se actualiza sola.`,aiu_openSignIn:`Abrir página de inicio de sesión`,aiu_copyLink:`Copiar enlace`,aiu_redeem:`Canjear`,aiu_saveFirst:`Guarde primero la configuración — después puede comenzar el inicio de sesión.`,aiu_instanceNotRunning:`La instancia no está en marcha — iníciela para entrar.`,aiu_noAnswer:`la instancia no respondió`,aiu_noCredentials:`Aún no hay credenciales de IA guardadas — añádalas en Ajustes → Credenciales.`,aiu_adminKeyHint:`Esta cuenta necesita una clave de ADMINISTRACIÓN de su organización, no la del asistente de IA. Las cuentas personales sin organización no ofrecen informes de uso.`,aiu_stOnline:`En línea`,aiu_stLimited:`Limitado`,aiu_stOffline:`Sin conexión`},y={aiu_storedTitle:`Konta AI`,aiu_storedHint:`Włącz to, co ma być monitorowane. Subskrypcje logują się Twoim kontem; pozycje poniżej pochodzą z centralnego magazynu danych dostępu (Ustawienia → Dane dostępu).`,aiu_capClaude:`Subskrypcja — zaloguj się raz kontem Claude`,aiu_capChatgpt:`Subskrypcja — zaloguj się raz kontem ChatGPT`,aiu_capGemini:`Subskrypcja — zaloguj się raz kontem Google`,aiu_storedKey:`Zapisany klucz`,aiu_provider:`Dostawca`,aiu_warnAt:`Ostrzegaj od %`,aiu_signedIn:`Zalogowano`,aiu_signOut:`Wyloguj`,aiu_startSignIn:`Zaloguj`,aiu_claudeStep1:`1. Otwórz stronę logowania i zaloguj się kontem Claude.`,aiu_claudeStep2:`2. Skopiuj pokazany kod i zrealizuj go tutaj.`,aiu_codeLabel:`Kod`,aiu_googleStep1:`1. Otwórz stronę logowania i zaloguj się kontem Google.`,aiu_googleStep2:`2. Skopiuj CAŁY adres z paska adresu i wklej go tutaj.`,aiu_googleErrorPageHint:`Przeglądarka pokaże potem stronę błędu («nie można otworzyć strony») — tak ma być. Google wysyła wynik na Twój komputer, gdzie nic nie nasłuchuje. W pasku adresu wynik jednak jest — i właśnie on jest tu potrzebny.`,aiu_addressLabel:`Adres z przeglądarki`,aiu_deviceStep1:`1. Otwórz stronę OpenAI i wpisz ten kod:`,aiu_copyCode:`Kopiuj kod`,aiu_openPage:`Otwórz stronę`,aiu_deviceWaiting:`Czekam na potwierdzenie — ta karta odświeży się sama.`,aiu_openSignIn:`Otwórz stronę logowania`,aiu_copyLink:`Kopiuj link`,aiu_redeem:`Zrealizuj`,aiu_saveFirst:`Najpierw zapisz ustawienia — potem można rozpocząć logowanie.`,aiu_instanceNotRunning:`Instancja nie działa — uruchom ją, aby się zalogować.`,aiu_noAnswer:`instancja nie odpowiedziała`,aiu_noCredentials:`Brak zapisanych danych dostępu AI — dodaj je w Ustawienia → Dane dostępu.`,aiu_adminKeyHint:`To konto wymaga klucza ADMINISTRACYJNEGO organizacji, a nie klucza asystenta AI. Konta osobiste bez organizacji nie dostarczają raportów zużycia.`,aiu_stOnline:`Online`,aiu_stLimited:`Ograniczone`,aiu_stOffline:`Offline`},b={aiu_storedTitle:`Акаунти ШІ`,aiu_storedHint:`Увімкніть те, що треба відстежувати. Підписки входять під вашим акаунтом; записи нижче — з центрального сховища даних доступу (Налаштування → Дані доступу).`,aiu_capClaude:`Підписка — увійдіть один раз акаунтом Claude`,aiu_capChatgpt:`Підписка — увійдіть один раз акаунтом ChatGPT`,aiu_capGemini:`Підписка — увійдіть один раз акаунтом Google`,aiu_storedKey:`Збережений ключ`,aiu_provider:`Постачальник`,aiu_warnAt:`Попереджати від %`,aiu_signedIn:`Вхід виконано`,aiu_signOut:`Вийти`,aiu_startSignIn:`Увійти`,aiu_claudeStep1:`1. Відкрийте сторінку входу та увійдіть акаунтом Claude.`,aiu_claudeStep2:`2. Скопіюйте показаний код і активуйте його тут.`,aiu_codeLabel:`Код`,aiu_googleStep1:`1. Відкрийте сторінку входу та увійдіть акаунтом Google.`,aiu_googleStep2:`2. Скопіюйте ВЕСЬ адрес з адресного рядка та вставте сюди.`,aiu_googleErrorPageHint:`Далі браузер покаже сторінку помилки («сайт недоступний») — так і має бути. Google надсилає результат на ваш комп'ютер, де ніхто не слухає. В адресному рядку результат усе одно є — саме він тут і потрібен.`,aiu_addressLabel:`Адреса з браузера`,aiu_deviceStep1:`1. Відкрийте сторінку OpenAI та введіть цей код:`,aiu_copyCode:`Копіювати код`,aiu_openPage:`Відкрити сторінку`,aiu_deviceWaiting:`Очікую підтвердження — картка оновиться сама.`,aiu_openSignIn:`Відкрити сторінку входу`,aiu_copyLink:`Копіювати посилання`,aiu_redeem:`Активувати`,aiu_saveFirst:`Спочатку збережіть налаштування — потім можна починати вхід.`,aiu_instanceNotRunning:`Екземпляр не запущено — запустіть його для входу.`,aiu_noAnswer:`екземпляр не відповів`,aiu_noCredentials:`Ще немає збережених даних доступу ШІ — додайте їх у Налаштування → Дані доступу.`,aiu_adminKeyHint:`Цей акаунт потребує АДМІНІСТРАТИВНОГО ключа організації, а не ключа ШІ-асистента. Особисті акаунти без організації не дають звітів про використання.`,aiu_stOnline:`У мережі`,aiu_stLimited:`Обмежено`,aiu_stOffline:`Не в мережі`},x={aiu_storedTitle:`AI 账户`,aiu_storedHint:`开启需要监控的项目。订阅使用您自己的账户登录;下方条目来自管理界面的中央凭据存储(设置 → 凭据)。`,aiu_capClaude:`订阅——使用您的 Claude 账户登录一次`,aiu_capChatgpt:`订阅——使用您的 ChatGPT 账户登录一次`,aiu_capGemini:`订阅——使用您的 Google 账户登录一次`,aiu_storedKey:`已保存的密钥`,aiu_provider:`提供商`,aiu_warnAt:`警告阈值 %`,aiu_signedIn:`已登录`,aiu_signOut:`退出登录`,aiu_startSignIn:`登录`,aiu_claudeStep1:`1. 打开登录页面并使用您的 Claude 账户登录。`,aiu_claudeStep2:`2. 复制页面显示的代码并在此兑换。`,aiu_codeLabel:`代码`,aiu_googleStep1:`1. 打开登录页面并使用您的 Google 账户登录。`,aiu_googleStep2:`2. 复制浏览器地址栏中的完整地址并粘贴到这里。`,aiu_googleErrorPageHint:`随后浏览器会显示错误页面(无法访问此网站)——这是正常的。Google 把结果发送到您自己的电脑,而那里没有程序在接收。地址栏中仍然包含登录结果——这正是此处需要的。`,aiu_addressLabel:`来自浏览器的地址`,aiu_deviceStep1:`1. 打开 OpenAI 页面并输入此代码:`,aiu_copyCode:`复制代码`,aiu_openPage:`打开页面`,aiu_deviceWaiting:`正在等待您的确认——完成后此卡片会自动更新。`,aiu_openSignIn:`打开登录页面`,aiu_copyLink:`复制链接`,aiu_redeem:`兑换`,aiu_saveFirst:`请先保存设置——之后即可开始登录。`,aiu_instanceNotRunning:`实例未运行——请启动后再登录。`,aiu_noAnswer:`实例没有响应`,aiu_noCredentials:`尚未保存任何 AI 凭据——请在 设置 → 凭据 中添加。`,aiu_adminKeyHint:`此账户需要您所在组织的管理密钥,而不是管理界面 AI 助手使用的密钥。没有组织的个人账户无法提供用量报告。`,aiu_stOnline:`在线`,aiu_stLimited:`受限`,aiu_stOffline:`离线`},S=class extends t{constructor(e){super(e,{...e}),this.state={...this.state,data:{accounts:[]},originalData:{accounts:[]},theme:this.createTheme()};let t={en:d,de:f,ru:p,pt:m,nl:h,fr:g,it:_,es:v,pl:y,uk:b,"zh-cn":x};for(let[e,n]of Object.entries(t))o.extendTranslations(n,e);let n=(navigator.language||navigator.userLanguage||`en`).toLowerCase();o.setLanguage(n.startsWith(`zh`)?`zh-cn`:n.substring(0,2))}render(){return this.state.loaded?r(e,{injectFirst:!0,children:r(n,{theme:this.state.theme,children:r(s,{sx:{height:`100%`,bgcolor:`background.default`,color:`text.primary`},children:r(`div`,{style:{padding:50,width:760},children:r(u,{oContext:{adapterName:`ai-usage`,socket:this.socket,instance:0,themeType:this.state.theme.palette.mode,isFloatComma:!0,dateFormat:``,forceUpdate:()=>{},systemConfig:{},theme:this.state.theme,_themeName:this.state.themeName,onCommandRunning:()=>{}},alive:!0,changed:!1,themeName:this.state.theme.palette.mode,common:{},attr:`_accounts`,data:this.state.data,originalData:this.state.originalData,onError:()=>{},schema:{url:`custom/customComponents.js`,i18n:!0,name:`AiUsageComponentSet/Components/ConfigPanel`,type:`custom`},onChange:e=>this.setState({data:e})})})})})}):r(e,{injectFirst:!0,children:r(n,{theme:this.state.theme,children:r(c,{themeType:this.state.themeType})})})}};a(),window.adapterName=`ai-usage`;var C=document.getElementById(`root`);C&&l(C).render(r(i.StrictMode,{children:r(S,{socket:{port:8081}})}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{t as e}from"./virtual_mf-exposes___mfe_internal__AiUsageComponentSet__customComponents_js-DAX-5uLT.js";export{e as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{t as e}from"./vite-preload-helper-B7qeedMF.js";import{a as t,r as n}from"./_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare___mf_0_iobroker_mf_1_json_mf_2_config__loadShare__.js-DEdJInQG.js";({...t.global});var r=t.share;t.utils,typeof __VUE_HMR_RUNTIME__>`u`&&(globalThis.__VUE_HMR_RUNTIME__={createRecord(){},rerender(){},reload(){}});var i=`__mf_init__virtual:mf:__mfe_internal__AiUsageComponentSet__mf_owner__1__mf_v__runtimeInit__mf_v__.js__`,a=globalThis[i];if(!a){let e,t,n=new Promise((n,r)=>{e=n,t=r});a=globalThis[i]={initPromise:n,initResolve:e,initReject:t}}var o=a.initResolve,s=`__mf_module_cache__`;globalThis[s]||={share:{},remote:{}},globalThis[s].share||={},globalThis[s].remote||={};var c=globalThis[s];for(let e of Object.keys(c.share))if(e.startsWith(`default:`)){let t=e.slice(8);c.share[t]===void 0&&(c.share[t]=c.share[e])}else if(!e.includes(`:`)){let t=`default:`+e;c.share[t]===void 0&&(c.share[t]=c.share[e])}var l={},u=Array.isArray(`default`)?`default`:[`default`],d=`default`,f=`AiUsageComponentSet`,p,m;async function h(e){for(let t=0;;t++)try{return await e()}catch(e){throw e}}var g=Symbol(`mf.originalSharedProvider`),_={emit:e=>e},v=(e,t)=>{if(t!==`version-first`)return e;let n={};for(let[t,r]of Object.entries(e))n[t]=Object.assign({},r,{[g]:r});return n},y=(e,t)=>{if(!t)return;let n=Object.entries(e||{}),r=n.find(([,e])=>e===t);if(r)return{version:r[0],provider:t,registered:!0};if(typeof t.version==`string`&&t.version)return{version:t.version,provider:t,registered:!1};let i=n.filter(([,e])=>e===t||!!(t.from&&(e==null?void 0:e.from)===t.from));if(i.length===1)return{version:i[0][0],provider:t,registered:!1}},b=(e,t,n,i)=>{var a;if(!e||!n)return;let o=Array.isArray(n.scope)?n.scope:[n.scope||`default`],s=v(e,i),c={};for(let e of o)c[e||`default`]={[t]:s};let l=(a=r.getRegisteredShare(c,t,{...n,scope:o,strategy:i},_))==null?void 0:a.shared;return(l==null?void 0:l[g])||l},x=(e,t,n,r)=>{var i;let a=e=>S(e,n),o=Object.fromEntries(Object.entries(e||{}).filter(([,e])=>!a(e)));n!=null&&n.version&&((i=n.shareConfig)==null?void 0:i.import)!==!1&&(o[n.version]||(o[n.version]=n));let s=b(o,t,n,r);return a(s)?void 0:s},S=(e,t)=>e===t||!!(t!=null&&t.from&&(e==null?void 0:e.from)===t.from),C=(e,t,n,r,i,a,o,s,c)=>{var l,u;if(c!==`version-first`||!s)return;let d=t==null||(l=t.options)==null||(l=l.shared)==null?void 0:l[i],f=Array.isArray(d)?d.find(e=>(e==null?void 0:e.version)===a):void 0,p=((s==null?void 0:s.from)===(t==null||(u=t.options)==null?void 0:u.name)?s:void 0)||(S(f,s)?f:t?void 0:s);if(p)return e.some(e=>{var i,a;return e!==t&&(e==null||(i=e.options)==null?void 0:i.name)===(o==null?void 0:o.from)&&(e==null||(a=e.shareScopeMap)==null?void 0:a[r])===n})?p:void 0},w=(e,t,n,r,i,a,o,s,c)=>{let l=a.registered?C(e,t,n,r,i,a.version,a.provider,s,c):void 0,u=l||o;if(u&&!(a.registered&&!S(u,s)))return{provider:u,scopeRootProvider:l}};async function T(){return p||=h(()=>e(()=>import(`./_virtual_mf-localSharedImportMap___mfe_internal__AiUsageComponentSet__mf_owner__1-DKcsNxEw.js`),[],import.meta.url)).catch(e=>{throw p=void 0,e}),p}async function E(){return m||=h(()=>e(()=>import(`./virtualExposes-CdmVMFlA.js`),[],import.meta.url)).then(e=>e.default??e).catch(e=>{throw m=void 0,e}),m}async function D(e={},t=[]){var r,i;let a=(e,t,n,r)=>{let i=(Array.isArray(r)?r[0]:r)||`default`,a=t||!n?e:e+`@`+n,o={canonical:i+`:`+a};return i==="default"&&(o.aliases=[a]),o},s=(e,t)=>{let n=e[t.canonical];if(n!==void 0)return n;let r=t.aliases||[];for(let n of r){if(!Object.prototype.hasOwnProperty.call(e,n))continue;let r=e[n];if(r!==void 0)return e[t.canonical]=r,r}},p=Symbol.for(`module-federation.shared-cache-listeners`),m=Symbol.for(`module-federation.shared-cache-owners`),g=e=>{let t=e[m];return t===void 0&&(t=Object.create(null),Object.defineProperty(e,m,{value:t,enumerable:!1,configurable:!1,writable:!1})),t},_=(e,t)=>{var n;return(n=e[m])==null?void 0:n[t.canonical]},v=(e,t,n,r)=>{var i;e[t.canonical]=n;let a=t.aliases||[];for(let t of a)Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0});let o=e[m];r===void 0?o&&delete o[t.canonical]:g(e)[t.canonical]=r;let s=(i=e[p])==null?void 0:i[t.canonical];if(s)for(let e of s)e(n);return n},C=Symbol.for(`module-federation.tree-shaking-shared-cache`),E=e=>{let t=e[C];return t===void 0&&(t=Object.create(null),Object.defineProperty(e,C,{value:t,enumerable:!1,configurable:!1,writable:!1})),t},D=(e,t,n,r)=>{if(!Array.isArray(n))return r;let i=[...new Set(n)].sort(),a=E(e),o=a[t.canonical]||=[],s=o.find(e=>e.providedExports.length===i.length&&e.providedExports.every((e,t)=>e===i[t]));return s?s.value=r:o.push({providedExports:i,value:r}),r},O=Symbol.for(`module-federation.tree-shaking-shared-selection-cache`),k=e=>{let t=e[O];return t===void 0&&(t=Object.create(null),Object.defineProperty(e,O,{value:t,enumerable:!1,configurable:!1,writable:!1})),t},A=(e,t,n)=>{var r;let i=s(e,t);return i===void 0?(r=e[O])==null||(r=r[t.canonical])==null?void 0:r[n]:i},j=(e,t,n,r)=>{let i=k(e),a=i[t.canonical]||=Object.create(null);return a[n]=r,r},M=((r=globalThis.__FEDERATION__)==null?void 0:r.__INSTANCES__)||[],N=(i=t.find(e=>e==null?void 0:e.from))==null?void 0:i.from,ee=M.find(t=>{var n,r;return(t==null||(n=t.options)==null?void 0:n.name)===N&&(t==null||(r=t.shareScopeMap)==null?void 0:r.default)===e})||M.find(t=>{var n,r;return(t==null||(n=t.options)==null?void 0:n.name)!==f&&(t==null||(r=t.shareScopeMap)==null?void 0:r.default)===e}),P=Object.create(null);for(let[t,n]of Object.entries(e)){let e=P[t]=Object.create(null);for(let[t,r]of Object.entries(n))e[t]=Object.assign({},r)}let{usedShared:F,usedRemotes:I}=await T();var L=l[d];if(L||=l[d]={from:f},t.indexOf(L)>=0)return;t.push(L);let R=e=>{let t=e;for(let e=0;e<5;e++){let e=t==null?void 0:t.default;if(!e||typeof e!=`object`||Object.keys(e).length===0)break;let n=Object.keys(t).filter(e=>e!=="default").map(e=>t[e]);if(n.length>0&&n.some(e=>e!==void 0))break;t=e}return t},z=[],B=globalThis.window===void 0?await Promise.all([]):[],V=`__mf_vite_runtime_share_load_id__`,te=0,H=new Map,U=new Map,W=n({name:f,remotes:I,shared:F,plugins:[ne(),...z,...B],shareStrategy:`version-first`});W.initShareScopeMap(`default`,e);function ne(){return{name:`vite-share-pin-lifecycle-plugin`,resolveShare(e){var t;let n=(t=e.shareInfo)==null?void 0:t[V],r=n===void 0?void 0:U.get(n);if(!r)return e;let i=e.resolver;return e.resolver=(...e)=>(r.pinned.reapply(),i(...e)),r.pinned.reveal(),e}}}let re=W.sharedHandler.hooks.lifecycle.resolveShare,G=new WeakMap;re.on(e=>{var t;let n=(t=e.shareInfo)==null?void 0:t[V],r=e.resolver;return typeof r==`function`&&(e.resolver=(...e)=>{let t=r(...e),i=t==null?void 0:t.shared;return i&&(typeof i==`object`||typeof i==`function`)&&!G.has(i)&&G.set(i,{from:i.from}),n!==void 0&&i&&H.set(n,i),t}),e});let ie=(e,t,n,r)=>{if(!e||e[t]!==n)return;let i=Object.assign({},r,{version:r.version??t,scope:r.scope??(n==null?void 0:n.scope)??[`default`],strategy:`loaded-first`}),a=r.from;e[t]=i;let o=()=>n===void 0?e[t]===void 0:e[t]===n;return{provider:i,reveal(){return e[t]===i&&(n===void 0?delete e[t]:e[t]=n,!0)},reapply(){return o()?(e[t]=i,!0):!1},release(s,c=!0){return r.from=a,e[t]===i?c?s?(i.from=a,s&&i.lib&&(i.loaded=!0),r.strategy===void 0?delete i.strategy:i.strategy=r.strategy,!0):(n===void 0?delete e[t]:e[t]=n,!1):(n===void 0?delete e[t]:e[t]=n,!0):!c&&o()}}},ae=e=>Object.entries(e||{}).map(([e,t])=>({provider:t,version:e,from:t.from,registered:!0})),oe=(e,t)=>{if(t===void 0)return;let n;for(let r of e){let e=r.provider,i=e.treeShaking||e;if(r.loadedFactory===t||e.lib===t||i.lib===t){if(n)return;n=r}}return n},se=async(e,t,n)=>{let r=++te;U.set(r,{pinned:n});try{let n=await W.loadShare(e,{customShareInfo:{shareConfig:t,[V]:r}});return{factory:n===!1?void 0:n,selectedProvider:H.get(r)}}finally{H.delete(r),U.delete(r)}},K=async(e,t,n,r,i,a,o=!0)=>{let s=a.from,c=ie(n,r,i,a);if(!c)return;let l;try{l=await se(e,t,c)}catch(e){throw c.release(!1),e}let u=l==null?void 0:l.factory;if(u===void 0){c.release(!1);return}let d=ae(n),f=c.provider.treeShaking||c.provider,p=c.provider.lib===u||f.lib===u;if(!o&&p){let e=d.findIndex(e=>e.provider===c.provider);e!==-1&&d.splice(e,1)}!o&&!d.some(e=>e.provider===a)&&d.push({provider:a,version:r,from:s,registered:!1,loadedFactory:p?u:void 0});let m=!o&&l.selectedProvider===c.provider&&p?a:l.selectedProvider;if(m&&!d.some(e=>e.provider===m)){let e=G.get(m),t=typeof m.version==`string`&&m.version?m.version:r;d.push({provider:m,version:t,from:e?e.from:m.from,registered:(n==null?void 0:n[t])===m,loadedFactory:u})}let h=d.find(e=>e.provider===m)??oe(d,u);if(!c.release(!0,(h==null?void 0:h.provider)===c.provider)||!h)return;let g=G.get(h.provider);h.from=h.provider===a?s:g?g.from:h.provider.from,g&&(h.provider.from=g.from);let _=typeof u==`function`?u():u,v=await Promise.resolve(_);if(!(h.registered&&(n==null?void 0:n[h.version])!==h.provider))return{provider:h.provider,selection:h,resolved:v}},q=new Set,J=new Map,ce=async(t,n,r)=>{var i;let o=!!((i=n.shareConfig)!=null&&i.singleton);if(!o&&n.canLiveRebind!==!1)try{let i=x(r,t,n,`version-first`),l=y(r,i);if(!l)return;let{version:u}=l;if(!o&&u!==n.version||!i.lib&&!i.loading&&!(i.loaded&&typeof i.get==`function`))return;let d=a(t,o,n.version,n.scope);if(s(c.share,d)!==void 0)return;let f=e[t],p=f==null?void 0:f[u];if(l.registered&&!S(p,i))return;let m,h=i.lib;if(!h&&i.loading&&(h=await i.loading),!h&&i.loaded&&typeof i.get==`function`&&(h=await i.get()),!h)return;let g=typeof h==`function`?h():h,_=await Promise.resolve(g),b=l.registered?p:i;m={provider:b,selection:{provider:b,version:u,from:i.from,registered:l.registered},resolved:_};let C=m==null?void 0:m.provider,w=m==null?void 0:m.selection;if(!w)return;let T=m==null?void 0:m.resolved;if(T===void 0||s(c.share,d)!==void 0||w.registered&&(f==null?void 0:f[w.version])!==C)return;v(c.share,d,R(T),w.from),q.add(C)}catch(e){console.error(`[Module Federation] Failed to bridge materialized shared module "`+t+`"`,e)}},Y=async(t,n,r,i,o)=>{try{var l,u;let d=a(t,(l=n.shareConfig)==null?void 0:l.singleton,n.version,n.scope),p=s(c.share,d),m=_(c.share,d),h=x(r,t,n,`version-first`),g=h||b(r,t,n,`version-first`)||n,C=y(r,g);if(!C)return;let T=S(g,n),{version:E}=C,D=i==null?void 0:i[E],O=w(M,ee,e,`default`,t,C,h,D,`version-first`);if(!O&&!T)return;let{provider:k,scopeRootProvider:A}=O||{provider:g,scopeRootProvider:void 0};if(!((u=n.shareConfig)!=null&&u.singleton)||n.canLiveRebind===!1||p!==void 0&&m!==f)return;let j=e[t],N=j==null?void 0:j[E];if(C.registered&&!A&&!S(N,k))return;let P=await K(t,n.shareConfig,j,E,N,k,C.registered&&!T),F=P==null?void 0:P.provider,I=P==null?void 0:P.selection;if(!I||S(F,n)||o&&(o.version!==I.version||!S({from:I.from},o.provider))||q.has(F))return;let L=P==null?void 0:P.resolved;if(L===void 0)return;let z=s(c.share,d),B=_(c.share,d);if(z!==void 0&&B!==f||I.registered&&(j==null?void 0:j[I.version])!==F)return;o||J.set(t,{version:I.version,provider:{from:I.from}}),q.add(F);let V=R(L);v(c.share,d,V,I.from)}catch(e){console.error(`[Module Federation] Failed to bridge external shared module "`+t+`"`,e)}};for(let[e,t]of Object.entries(F))t.treeShaking||await ce(e,t,P[e]);for(let[e,t]of Object.entries(F)){var X;if(t.treeShaking)continue;let n=a(e,(X=t.shareConfig)==null?void 0:X.singleton,t.version,t.scope);if(s(c.share,n)!==void 0)continue;let r=a(e,!0,t.version,t.scope),i=s(c.share,r);i!==void 0&&v(c.share,n,i,_(c.share,r))}let Z=[`react`,`react/jsx-runtime`,`react-dom`,`react-dom/client`,`@mui/material`,`@mui/icons-material`,`@iobroker/gui-components`,`@iobroker/json-config`].filter(e=>F[e]!==void 0),Q=e=>e.startsWith(`@`)?e.split(`/`).slice(0,2).join(`/`):e.split(`/`)[0];for(let e of Object.keys(F)){if(Z.includes(e))continue;let t=Q(e),n=Z.indexOf(t);if(n===-1){Z.push(e);continue}let r=n;for(;r>0&&Q(Z[r-1])===t&&Z[r-1]!==t;)r--;Z.splice(r,0,e)}var le=async e=>{for(let r of e){var t,n;let e=F[r],i=a(r,(t=e.shareConfig)==null?void 0:t.singleton,e.version,e.scope);if(((n=e.shareConfig)==null?void 0:n.import)===!1||e.treeShaking||s(c.share,i)!==void 0)continue;let o=a(r,!0,e.version,e.scope),l=s(c.share,o);if(l!==void 0){v(c.share,i,l,_(c.share,o));continue}let u=await e.get(),d=typeof u==`function`?u():u,p=await Promise.resolve(d),m=(e=>{let t=e;for(let e=0;e<5;e++){let e=t==null?void 0:t.default;if(!e||typeof e!=`object`||Object.keys(e).length===0)break;let n=Object.keys(t).filter(e=>e!=="default").map(e=>t[e]);if(n.length>0&&n.some(e=>e!==void 0))break;t=e}return t})(p),h=m===p?{...p}:m;h.__esModule!==!0&&Object.defineProperty(h,"__esModule",{value:!0,enumerable:!1}),v(c.share,i,h,f)}};let ue=e=>{var t,n;let r=F[e];if(!r.treeShaking&&((t=r.shareConfig)==null?void 0:t.import)!==!1)return!1;let i=a(e,(n=r.shareConfig)==null?void 0:n.singleton,r.version,r.scope);return r.treeShaking?A(c.share,i,f)===void 0&&s(c.share,i)===void 0:s(c.share,i)===void 0},$=Z.findIndex(e=>{var t,n,r;let i=F[e],o=a(e,(t=i.shareConfig)==null?void 0:t.singleton,i.version,i.scope);return(i.treeShaking?A(c.share,o,f)??s(c.share,o):s(c.share,o))===void 0?i.treeShaking||((n=i.shareConfig)==null?void 0:n.import)===!1?!0:!((r=i.shareConfig)!=null&&r.singleton)||typeof x!=`function`?!1:!!x(P[e],e,i,`version-first`):!1}),de=$===-1?Z:Z.slice(0,$);var fe=$===-1?[]:Z.slice($);await le(de);try{await h(async()=>{await Promise.all(await W.initializeSharing(`default`,{strategy:`version-first`,from:`build`,initScope:t}))})}catch(e){console.error(`[Module Federation]`,e)}for(let[t,n]of Object.entries(F))n.treeShaking||await Y(t,n,e[t],P[t],void 0);try{var pe;let e=(pe=globalThis.__FEDERATION__)==null?void 0:pe.__SHARE__,t=Object.create(null);if(e)for(let[,n]of Object.entries(e))for(let e of u){let r=n==null?void 0:n[e];if(r)for(let[e,n]of Object.entries(r)){let r=F==null?void 0:F[e],i=P[e],a=J.get(e);if(!r||!i||!a||r.treeShaking)continue;let o=t[e]||(t[e]=Object.create(null));for(let[e,t]of Object.entries(n)){if(!t.lib||a.version!==e||!S(t,a.provider))continue;let n=i[e];(t===n||n!=null&&n.from&&t.from===n.from)&&(t===r||r.from&&t.from===r.from||o[e]===void 0&&(o[e]=t))}}}for(let[e,n]of Object.entries(t))await Y(e,F[e],n,P[e],J.get(e))}catch(e){console.error(`[Module Federation] Failed to bridge external shared modules`,e)}let me=async()=>{},he=async(t,n)=>{var r,i;let o=a(t,(r=n.shareConfig)==null?void 0:r.singleton,n.version,n.scope),l=n.treeShaking?A(c.share,o,f):s(c.share,o);if(((i=n.shareConfig)==null?void 0:i.import)!==!1||l!==void 0)return;let u=e=>{let t=e;for(let e=0;e<5;e++){let e=t==null?void 0:t.default;if(!e||typeof e!=`object`||Object.keys(e).length===0)break;let n=Object.keys(t).filter(e=>e!=="default").map(e=>t[e]);if(n.length>0&&n.some(e=>e!==void 0))break;t=e}return t},d=e==null?void 0:e[t],p=b(d,t,n,`version-first`)||n,m=y(d,p);if(!m)return;let{version:h}=m,g=d==null?void 0:d[h],_=await K(t,n.shareConfig,d,h,g,p,m.registered&&!S(p,n)),x=_==null?void 0:_.selection,C=_==null?void 0:_.provider,w=_==null?void 0:_.resolved;if(!x||S(C,n)||w===void 0||(n.treeShaking?A(c.share,o,f):s(c.share,o))!==void 0||x.registered&&(d==null?void 0:d[x.version])!==C)return;let T=u(w);if(n.treeShaking){let e=n.treeShaking.providedExports??n.treeShaking.usedExports??[];D(c.share,o,e,T),j(c.share,o,f,T)}else v(c.share,o,T,x.from)};for(let e of fe){let t=F[e];if(ue(e)){var ge;t.treeShaking?await me(e,t):((ge=t.shareConfig)==null?void 0:ge.import)===!1&&await he(e,t)}if(ue(e))break;await le([e])}return o(W),W}async function O(e){let t=await E();if(!(e in t))throw Error(`[Module Federation] Module ${e} does not exist in container.`);return c.pendingShareLoads&&await Promise.all(c.pendingShareLoads),t[e]().then(e=>()=>e)}export{D as n,O as t};
|
|
1
|
+
import{t as e}from"./vite-preload-helper-B7qeedMF.js";import{a as t,r as n}from"./_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare___mf_0_iobroker_mf_1_json_mf_2_config__loadShare__.js-DEdJInQG.js";({...t.global});var r=t.share;t.utils,typeof __VUE_HMR_RUNTIME__>`u`&&(globalThis.__VUE_HMR_RUNTIME__={createRecord(){},rerender(){},reload(){}});var i=`__mf_init__virtual:mf:__mfe_internal__AiUsageComponentSet__mf_owner__1__mf_v__runtimeInit__mf_v__.js__`,a=globalThis[i];if(!a){let e,t,n=new Promise((n,r)=>{e=n,t=r});a=globalThis[i]={initPromise:n,initResolve:e,initReject:t}}var o=a.initResolve,s=`__mf_module_cache__`;globalThis[s]||={share:{},remote:{}},globalThis[s].share||={},globalThis[s].remote||={};var c=globalThis[s];for(let e of Object.keys(c.share))if(e.startsWith(`default:`)){let t=e.slice(8);c.share[t]===void 0&&(c.share[t]=c.share[e])}else if(!e.includes(`:`)){let t=`default:`+e;c.share[t]===void 0&&(c.share[t]=c.share[e])}var l={},u=Array.isArray(`default`)?`default`:[`default`],d=`default`,f=`AiUsageComponentSet`,p,m;async function h(e){for(let t=0;;t++)try{return await e()}catch(e){throw e}}var g=Symbol(`mf.originalSharedProvider`),_={emit:e=>e},v=(e,t)=>{if(t!==`version-first`)return e;let n={};for(let[t,r]of Object.entries(e))n[t]=Object.assign({},r,{[g]:r});return n},y=(e,t)=>{if(!t)return;let n=Object.entries(e||{}),r=n.find(([,e])=>e===t);if(r)return{version:r[0],provider:t,registered:!0};if(typeof t.version==`string`&&t.version)return{version:t.version,provider:t,registered:!1};let i=n.filter(([,e])=>e===t||!!(t.from&&(e==null?void 0:e.from)===t.from));if(i.length===1)return{version:i[0][0],provider:t,registered:!1}},b=(e,t,n,i)=>{var a;if(!e||!n)return;let o=Array.isArray(n.scope)?n.scope:[n.scope||`default`],s=v(e,i),c={};for(let e of o)c[e||`default`]={[t]:s};let l=(a=r.getRegisteredShare(c,t,{...n,scope:o,strategy:i},_))==null?void 0:a.shared;return(l==null?void 0:l[g])||l},x=(e,t,n,r)=>{var i;let a=e=>S(e,n),o=Object.fromEntries(Object.entries(e||{}).filter(([,e])=>!a(e)));n!=null&&n.version&&((i=n.shareConfig)==null?void 0:i.import)!==!1&&(o[n.version]||(o[n.version]=n));let s=b(o,t,n,r);return a(s)?void 0:s},S=(e,t)=>e===t||!!(t!=null&&t.from&&(e==null?void 0:e.from)===t.from),C=(e,t,n,r,i,a,o,s,c)=>{var l,u;if(c!==`version-first`||!s)return;let d=t==null||(l=t.options)==null||(l=l.shared)==null?void 0:l[i],f=Array.isArray(d)?d.find(e=>(e==null?void 0:e.version)===a):void 0,p=((s==null?void 0:s.from)===(t==null||(u=t.options)==null?void 0:u.name)?s:void 0)||(S(f,s)?f:t?void 0:s);if(p)return e.some(e=>{var i,a;return e!==t&&(e==null||(i=e.options)==null?void 0:i.name)===(o==null?void 0:o.from)&&(e==null||(a=e.shareScopeMap)==null?void 0:a[r])===n})?p:void 0},w=(e,t,n,r,i,a,o,s,c)=>{let l=a.registered?C(e,t,n,r,i,a.version,a.provider,s,c):void 0,u=l||o;if(u&&!(a.registered&&!S(u,s)))return{provider:u,scopeRootProvider:l}};async function T(){return p||=h(()=>e(()=>import(`./_virtual_mf-localSharedImportMap___mfe_internal__AiUsageComponentSet__mf_owner__1-DKcsNxEw.js`),[],import.meta.url)).catch(e=>{throw p=void 0,e}),p}async function E(){return m||=h(()=>e(()=>import(`./virtualExposes--n4JvPAZ.js`),[],import.meta.url)).then(e=>e.default??e).catch(e=>{throw m=void 0,e}),m}async function D(e={},t=[]){var r,i;let a=(e,t,n,r)=>{let i=(Array.isArray(r)?r[0]:r)||`default`,a=t||!n?e:e+`@`+n,o={canonical:i+`:`+a};return i==="default"&&(o.aliases=[a]),o},s=(e,t)=>{let n=e[t.canonical];if(n!==void 0)return n;let r=t.aliases||[];for(let n of r){if(!Object.prototype.hasOwnProperty.call(e,n))continue;let r=e[n];if(r!==void 0)return e[t.canonical]=r,r}},p=Symbol.for(`module-federation.shared-cache-listeners`),m=Symbol.for(`module-federation.shared-cache-owners`),g=e=>{let t=e[m];return t===void 0&&(t=Object.create(null),Object.defineProperty(e,m,{value:t,enumerable:!1,configurable:!1,writable:!1})),t},_=(e,t)=>{var n;return(n=e[m])==null?void 0:n[t.canonical]},v=(e,t,n,r)=>{var i;e[t.canonical]=n;let a=t.aliases||[];for(let t of a)Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0});let o=e[m];r===void 0?o&&delete o[t.canonical]:g(e)[t.canonical]=r;let s=(i=e[p])==null?void 0:i[t.canonical];if(s)for(let e of s)e(n);return n},C=Symbol.for(`module-federation.tree-shaking-shared-cache`),E=e=>{let t=e[C];return t===void 0&&(t=Object.create(null),Object.defineProperty(e,C,{value:t,enumerable:!1,configurable:!1,writable:!1})),t},D=(e,t,n,r)=>{if(!Array.isArray(n))return r;let i=[...new Set(n)].sort(),a=E(e),o=a[t.canonical]||=[],s=o.find(e=>e.providedExports.length===i.length&&e.providedExports.every((e,t)=>e===i[t]));return s?s.value=r:o.push({providedExports:i,value:r}),r},O=Symbol.for(`module-federation.tree-shaking-shared-selection-cache`),k=e=>{let t=e[O];return t===void 0&&(t=Object.create(null),Object.defineProperty(e,O,{value:t,enumerable:!1,configurable:!1,writable:!1})),t},A=(e,t,n)=>{var r;let i=s(e,t);return i===void 0?(r=e[O])==null||(r=r[t.canonical])==null?void 0:r[n]:i},j=(e,t,n,r)=>{let i=k(e),a=i[t.canonical]||=Object.create(null);return a[n]=r,r},M=((r=globalThis.__FEDERATION__)==null?void 0:r.__INSTANCES__)||[],N=(i=t.find(e=>e==null?void 0:e.from))==null?void 0:i.from,ee=M.find(t=>{var n,r;return(t==null||(n=t.options)==null?void 0:n.name)===N&&(t==null||(r=t.shareScopeMap)==null?void 0:r.default)===e})||M.find(t=>{var n,r;return(t==null||(n=t.options)==null?void 0:n.name)!==f&&(t==null||(r=t.shareScopeMap)==null?void 0:r.default)===e}),P=Object.create(null);for(let[t,n]of Object.entries(e)){let e=P[t]=Object.create(null);for(let[t,r]of Object.entries(n))e[t]=Object.assign({},r)}let{usedShared:F,usedRemotes:I}=await T();var L=l[d];if(L||=l[d]={from:f},t.indexOf(L)>=0)return;t.push(L);let R=e=>{let t=e;for(let e=0;e<5;e++){let e=t==null?void 0:t.default;if(!e||typeof e!=`object`||Object.keys(e).length===0)break;let n=Object.keys(t).filter(e=>e!=="default").map(e=>t[e]);if(n.length>0&&n.some(e=>e!==void 0))break;t=e}return t},z=[],B=globalThis.window===void 0?await Promise.all([]):[],V=`__mf_vite_runtime_share_load_id__`,te=0,H=new Map,U=new Map,W=n({name:f,remotes:I,shared:F,plugins:[ne(),...z,...B],shareStrategy:`version-first`});W.initShareScopeMap(`default`,e);function ne(){return{name:`vite-share-pin-lifecycle-plugin`,resolveShare(e){var t;let n=(t=e.shareInfo)==null?void 0:t[V],r=n===void 0?void 0:U.get(n);if(!r)return e;let i=e.resolver;return e.resolver=(...e)=>(r.pinned.reapply(),i(...e)),r.pinned.reveal(),e}}}let re=W.sharedHandler.hooks.lifecycle.resolveShare,G=new WeakMap;re.on(e=>{var t;let n=(t=e.shareInfo)==null?void 0:t[V],r=e.resolver;return typeof r==`function`&&(e.resolver=(...e)=>{let t=r(...e),i=t==null?void 0:t.shared;return i&&(typeof i==`object`||typeof i==`function`)&&!G.has(i)&&G.set(i,{from:i.from}),n!==void 0&&i&&H.set(n,i),t}),e});let ie=(e,t,n,r)=>{if(!e||e[t]!==n)return;let i=Object.assign({},r,{version:r.version??t,scope:r.scope??(n==null?void 0:n.scope)??[`default`],strategy:`loaded-first`}),a=r.from;e[t]=i;let o=()=>n===void 0?e[t]===void 0:e[t]===n;return{provider:i,reveal(){return e[t]===i&&(n===void 0?delete e[t]:e[t]=n,!0)},reapply(){return o()?(e[t]=i,!0):!1},release(s,c=!0){return r.from=a,e[t]===i?c?s?(i.from=a,s&&i.lib&&(i.loaded=!0),r.strategy===void 0?delete i.strategy:i.strategy=r.strategy,!0):(n===void 0?delete e[t]:e[t]=n,!1):(n===void 0?delete e[t]:e[t]=n,!0):!c&&o()}}},ae=e=>Object.entries(e||{}).map(([e,t])=>({provider:t,version:e,from:t.from,registered:!0})),oe=(e,t)=>{if(t===void 0)return;let n;for(let r of e){let e=r.provider,i=e.treeShaking||e;if(r.loadedFactory===t||e.lib===t||i.lib===t){if(n)return;n=r}}return n},se=async(e,t,n)=>{let r=++te;U.set(r,{pinned:n});try{let n=await W.loadShare(e,{customShareInfo:{shareConfig:t,[V]:r}});return{factory:n===!1?void 0:n,selectedProvider:H.get(r)}}finally{H.delete(r),U.delete(r)}},K=async(e,t,n,r,i,a,o=!0)=>{let s=a.from,c=ie(n,r,i,a);if(!c)return;let l;try{l=await se(e,t,c)}catch(e){throw c.release(!1),e}let u=l==null?void 0:l.factory;if(u===void 0){c.release(!1);return}let d=ae(n),f=c.provider.treeShaking||c.provider,p=c.provider.lib===u||f.lib===u;if(!o&&p){let e=d.findIndex(e=>e.provider===c.provider);e!==-1&&d.splice(e,1)}!o&&!d.some(e=>e.provider===a)&&d.push({provider:a,version:r,from:s,registered:!1,loadedFactory:p?u:void 0});let m=!o&&l.selectedProvider===c.provider&&p?a:l.selectedProvider;if(m&&!d.some(e=>e.provider===m)){let e=G.get(m),t=typeof m.version==`string`&&m.version?m.version:r;d.push({provider:m,version:t,from:e?e.from:m.from,registered:(n==null?void 0:n[t])===m,loadedFactory:u})}let h=d.find(e=>e.provider===m)??oe(d,u);if(!c.release(!0,(h==null?void 0:h.provider)===c.provider)||!h)return;let g=G.get(h.provider);h.from=h.provider===a?s:g?g.from:h.provider.from,g&&(h.provider.from=g.from);let _=typeof u==`function`?u():u,v=await Promise.resolve(_);if(!(h.registered&&(n==null?void 0:n[h.version])!==h.provider))return{provider:h.provider,selection:h,resolved:v}},q=new Set,J=new Map,ce=async(t,n,r)=>{var i;let o=!!((i=n.shareConfig)!=null&&i.singleton);if(!o&&n.canLiveRebind!==!1)try{let i=x(r,t,n,`version-first`),l=y(r,i);if(!l)return;let{version:u}=l;if(!o&&u!==n.version||!i.lib&&!i.loading&&!(i.loaded&&typeof i.get==`function`))return;let d=a(t,o,n.version,n.scope);if(s(c.share,d)!==void 0)return;let f=e[t],p=f==null?void 0:f[u];if(l.registered&&!S(p,i))return;let m,h=i.lib;if(!h&&i.loading&&(h=await i.loading),!h&&i.loaded&&typeof i.get==`function`&&(h=await i.get()),!h)return;let g=typeof h==`function`?h():h,_=await Promise.resolve(g),b=l.registered?p:i;m={provider:b,selection:{provider:b,version:u,from:i.from,registered:l.registered},resolved:_};let C=m==null?void 0:m.provider,w=m==null?void 0:m.selection;if(!w)return;let T=m==null?void 0:m.resolved;if(T===void 0||s(c.share,d)!==void 0||w.registered&&(f==null?void 0:f[w.version])!==C)return;v(c.share,d,R(T),w.from),q.add(C)}catch(e){console.error(`[Module Federation] Failed to bridge materialized shared module "`+t+`"`,e)}},Y=async(t,n,r,i,o)=>{try{var l,u;let d=a(t,(l=n.shareConfig)==null?void 0:l.singleton,n.version,n.scope),p=s(c.share,d),m=_(c.share,d),h=x(r,t,n,`version-first`),g=h||b(r,t,n,`version-first`)||n,C=y(r,g);if(!C)return;let T=S(g,n),{version:E}=C,D=i==null?void 0:i[E],O=w(M,ee,e,`default`,t,C,h,D,`version-first`);if(!O&&!T)return;let{provider:k,scopeRootProvider:A}=O||{provider:g,scopeRootProvider:void 0};if(!((u=n.shareConfig)!=null&&u.singleton)||n.canLiveRebind===!1||p!==void 0&&m!==f)return;let j=e[t],N=j==null?void 0:j[E];if(C.registered&&!A&&!S(N,k))return;let P=await K(t,n.shareConfig,j,E,N,k,C.registered&&!T),F=P==null?void 0:P.provider,I=P==null?void 0:P.selection;if(!I||S(F,n)||o&&(o.version!==I.version||!S({from:I.from},o.provider))||q.has(F))return;let L=P==null?void 0:P.resolved;if(L===void 0)return;let z=s(c.share,d),B=_(c.share,d);if(z!==void 0&&B!==f||I.registered&&(j==null?void 0:j[I.version])!==F)return;o||J.set(t,{version:I.version,provider:{from:I.from}}),q.add(F);let V=R(L);v(c.share,d,V,I.from)}catch(e){console.error(`[Module Federation] Failed to bridge external shared module "`+t+`"`,e)}};for(let[e,t]of Object.entries(F))t.treeShaking||await ce(e,t,P[e]);for(let[e,t]of Object.entries(F)){var X;if(t.treeShaking)continue;let n=a(e,(X=t.shareConfig)==null?void 0:X.singleton,t.version,t.scope);if(s(c.share,n)!==void 0)continue;let r=a(e,!0,t.version,t.scope),i=s(c.share,r);i!==void 0&&v(c.share,n,i,_(c.share,r))}let Z=[`react`,`react/jsx-runtime`,`react-dom`,`react-dom/client`,`@mui/material`,`@mui/icons-material`,`@iobroker/gui-components`,`@iobroker/json-config`].filter(e=>F[e]!==void 0),Q=e=>e.startsWith(`@`)?e.split(`/`).slice(0,2).join(`/`):e.split(`/`)[0];for(let e of Object.keys(F)){if(Z.includes(e))continue;let t=Q(e),n=Z.indexOf(t);if(n===-1){Z.push(e);continue}let r=n;for(;r>0&&Q(Z[r-1])===t&&Z[r-1]!==t;)r--;Z.splice(r,0,e)}var le=async e=>{for(let r of e){var t,n;let e=F[r],i=a(r,(t=e.shareConfig)==null?void 0:t.singleton,e.version,e.scope);if(((n=e.shareConfig)==null?void 0:n.import)===!1||e.treeShaking||s(c.share,i)!==void 0)continue;let o=a(r,!0,e.version,e.scope),l=s(c.share,o);if(l!==void 0){v(c.share,i,l,_(c.share,o));continue}let u=await e.get(),d=typeof u==`function`?u():u,p=await Promise.resolve(d),m=(e=>{let t=e;for(let e=0;e<5;e++){let e=t==null?void 0:t.default;if(!e||typeof e!=`object`||Object.keys(e).length===0)break;let n=Object.keys(t).filter(e=>e!=="default").map(e=>t[e]);if(n.length>0&&n.some(e=>e!==void 0))break;t=e}return t})(p),h=m===p?{...p}:m;h.__esModule!==!0&&Object.defineProperty(h,"__esModule",{value:!0,enumerable:!1}),v(c.share,i,h,f)}};let ue=e=>{var t,n;let r=F[e];if(!r.treeShaking&&((t=r.shareConfig)==null?void 0:t.import)!==!1)return!1;let i=a(e,(n=r.shareConfig)==null?void 0:n.singleton,r.version,r.scope);return r.treeShaking?A(c.share,i,f)===void 0&&s(c.share,i)===void 0:s(c.share,i)===void 0},$=Z.findIndex(e=>{var t,n,r;let i=F[e],o=a(e,(t=i.shareConfig)==null?void 0:t.singleton,i.version,i.scope);return(i.treeShaking?A(c.share,o,f)??s(c.share,o):s(c.share,o))===void 0?i.treeShaking||((n=i.shareConfig)==null?void 0:n.import)===!1?!0:!((r=i.shareConfig)!=null&&r.singleton)||typeof x!=`function`?!1:!!x(P[e],e,i,`version-first`):!1}),de=$===-1?Z:Z.slice(0,$);var fe=$===-1?[]:Z.slice($);await le(de);try{await h(async()=>{await Promise.all(await W.initializeSharing(`default`,{strategy:`version-first`,from:`build`,initScope:t}))})}catch(e){console.error(`[Module Federation]`,e)}for(let[t,n]of Object.entries(F))n.treeShaking||await Y(t,n,e[t],P[t],void 0);try{var pe;let e=(pe=globalThis.__FEDERATION__)==null?void 0:pe.__SHARE__,t=Object.create(null);if(e)for(let[,n]of Object.entries(e))for(let e of u){let r=n==null?void 0:n[e];if(r)for(let[e,n]of Object.entries(r)){let r=F==null?void 0:F[e],i=P[e],a=J.get(e);if(!r||!i||!a||r.treeShaking)continue;let o=t[e]||(t[e]=Object.create(null));for(let[e,t]of Object.entries(n)){if(!t.lib||a.version!==e||!S(t,a.provider))continue;let n=i[e];(t===n||n!=null&&n.from&&t.from===n.from)&&(t===r||r.from&&t.from===r.from||o[e]===void 0&&(o[e]=t))}}}for(let[e,n]of Object.entries(t))await Y(e,F[e],n,P[e],J.get(e))}catch(e){console.error(`[Module Federation] Failed to bridge external shared modules`,e)}let me=async()=>{},he=async(t,n)=>{var r,i;let o=a(t,(r=n.shareConfig)==null?void 0:r.singleton,n.version,n.scope),l=n.treeShaking?A(c.share,o,f):s(c.share,o);if(((i=n.shareConfig)==null?void 0:i.import)!==!1||l!==void 0)return;let u=e=>{let t=e;for(let e=0;e<5;e++){let e=t==null?void 0:t.default;if(!e||typeof e!=`object`||Object.keys(e).length===0)break;let n=Object.keys(t).filter(e=>e!=="default").map(e=>t[e]);if(n.length>0&&n.some(e=>e!==void 0))break;t=e}return t},d=e==null?void 0:e[t],p=b(d,t,n,`version-first`)||n,m=y(d,p);if(!m)return;let{version:h}=m,g=d==null?void 0:d[h],_=await K(t,n.shareConfig,d,h,g,p,m.registered&&!S(p,n)),x=_==null?void 0:_.selection,C=_==null?void 0:_.provider,w=_==null?void 0:_.resolved;if(!x||S(C,n)||w===void 0||(n.treeShaking?A(c.share,o,f):s(c.share,o))!==void 0||x.registered&&(d==null?void 0:d[x.version])!==C)return;let T=u(w);if(n.treeShaking){let e=n.treeShaking.providedExports??n.treeShaking.usedExports??[];D(c.share,o,e,T),j(c.share,o,f,T)}else v(c.share,o,T,x.from)};for(let e of fe){let t=F[e];if(ue(e)){var ge;t.treeShaking?await me(e,t):((ge=t.shareConfig)==null?void 0:ge.import)===!1&&await he(e,t)}if(ue(e))break;await le([e])}return o(W),W}async function O(e){let t=await E();if(!(e in t))throw Error(`[Module Federation] Module ${e} does not exist in container.`);return c.pendingShareLoads&&await Promise.all(c.pendingShareLoads),t[e]().then(e=>()=>e)}export{D as n,O as t};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./Components-SGigWPbz.js","./ConfigPanel-7wtMdF7R.js"])))=>i.map(i=>d[i]);
|
|
2
|
+
import{t as e}from"./vite-preload-helper-B7qeedMF.js";var t={"./Components":async()=>{let t=await e(()=>import(`./Components-SGigWPbz.js`),__vite__mapDeps([0,1]),import.meta.url),n={};return Object.assign(n,t),Object.defineProperty(n,"__esModule",{value:!0,enumerable:!1}),n}};export{t as default};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./Components-
|
|
2
|
-
import{t as e}from"./vite-preload-helper-B7qeedMF.js";var t={},n=new Set,r=Promise.resolve();async function i(e){let t=r.then(e,e);return r=t.then(()=>void 0,()=>void 0),t}async function a(e){if(typeof document>`u`)return;let r=t[e]||[];await Promise.all(r.map(e=>{let t=new URL(e,import.meta.url).href;return n.has(t)||(n.add(t),document.querySelector(`link[rel="stylesheet"][href="${t}"]`))?Promise.resolve():new Promise((e,n)=>{let r=document.createElement(`link`);r.rel=`stylesheet`,r.href=t,r.onload=()=>e(),r.onerror=()=>n(Error(`[Module Federation] Failed to load CSS asset: ${t}`)),document.head.appendChild(r)})}))}var o={"./Components":async()=>{await a(`./Components`),await Promise.all([]);let t=await i(()=>e(()=>import(`./Components-
|
|
1
|
+
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./Components-SGigWPbz.js","./ConfigPanel-7wtMdF7R.js"])))=>i.map(i=>d[i]);
|
|
2
|
+
import{t as e}from"./vite-preload-helper-B7qeedMF.js";var t={},n=new Set,r=Promise.resolve();async function i(e){let t=r.then(e,e);return r=t.then(()=>void 0,()=>void 0),t}async function a(e){if(typeof document>`u`)return;let r=t[e]||[];await Promise.all(r.map(e=>{let t=new URL(e,import.meta.url).href;return n.has(t)||(n.add(t),document.querySelector(`link[rel="stylesheet"][href="${t}"]`))?Promise.resolve():new Promise((e,n)=>{let r=document.createElement(`link`);r.rel=`stylesheet`,r.href=t,r.onload=()=>e(),r.onerror=()=>n(Error(`[Module Federation] Failed to load CSS asset: ${t}`)),document.head.appendChild(r)})}))}var o={"./Components":async()=>{await a(`./Components`),await Promise.all([]);let t=await i(()=>e(()=>import(`./Components-SGigWPbz.js`),__vite__mapDeps([0,1]),import.meta.url)),n=t&&t.__mf_remote_dependency_pending;n&&typeof n.then==`function`&&await n;let r={};return Object.assign(r,t),Object.defineProperty(r,"__esModule",{value:!0,enumerable:!1}),r}};export{o as t};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e,t}from"./assets/virtual_mf-REMOTE_ENTRY_ID___mfe_internal__AiUsageComponentSet__customComponents_js-
|
|
1
|
+
import{n as e,t}from"./assets/virtual_mf-REMOTE_ENTRY_ID___mfe_internal__AiUsageComponentSet__customComponents_js-haafVBNm.js";export{t as get,e as init};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"id":"AiUsageComponentSet","name":"AiUsageComponentSet","metaData":{"name":"AiUsageComponentSet","type":"app","buildInfo":{"buildVersion":"1.0.0","buildName":"AiUsageComponentSet"},"remoteEntry":{"name":"customComponents.js","path":"","type":"module"},"ssrRemoteEntry":{"name":"customComponents.ssr.js","path":"","type":"module"},"types":{"path":"","name":"","zip":"@mf-types.zip","api":"@mf-types.d.ts"},"globalName":"AiUsageComponentSet","pluginVersion":"0.2.5","publicPath":"auto"},"shared":[{"id":"AiUsageComponentSet:@iobroker/gui-components","name":"@iobroker/gui-components","version":"10.1.0","singleton":true,"requiredVersion":"*","assets":{"js":{"async":[],"sync":["assets/_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__prebuild___mf_0_iobroker_mf_1_gui_mf_2_components__prebuild__-DeLUTSLT.js"]},"css":{"async":[],"sync":[]}}},{"id":"AiUsageComponentSet:@iobroker/json-config","name":"@iobroker/json-config","version":"9.0.22","singleton":true,"requiredVersion":"*","assets":{"js":{"async":["assets/lib-MY71LxhX.js"],"sync":["assets/_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare___mf_0_iobroker_mf_1_json_mf_2_config__loadShare__.js-DEdJInQG.js"]},"css":{"async":[],"sync":[]}}},{"id":"AiUsageComponentSet:@mui/icons-material","name":"@mui/icons-material","version":"9.3.1","singleton":true,"requiredVersion":"*","assets":{"js":{"async":[],"sync":["assets/_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-DzPBJWNQ.js"]},"css":{"async":[],"sync":[]}}},{"id":"AiUsageComponentSet:@mui/material","name":"@mui/material","version":"9.3.1","singleton":true,"requiredVersion":"*","assets":{"js":{"async":[],"sync":["assets/_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-DzPBJWNQ.js"]},"css":{"async":[],"sync":[]}}},{"id":"AiUsageComponentSet:react","name":"react","version":"19.2.8","singleton":true,"requiredVersion":"*","assets":{"js":{"async":[],"sync":["assets/_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-DzPBJWNQ.js"]},"css":{"async":[],"sync":[]}}},{"id":"AiUsageComponentSet:react-dom","name":"react-dom","version":"19.2.8","singleton":true,"requiredVersion":"*","assets":{"js":{"async":[],"sync":["assets/_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-DzPBJWNQ.js"]},"css":{"async":[],"sync":[]}}},{"id":"AiUsageComponentSet:react/jsx-runtime","name":"react/jsx-runtime","version":"19.2.8","singleton":true,"requiredVersion":"*","assets":{"js":{"async":[],"sync":["assets/_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-DzPBJWNQ.js"]},"css":{"async":[],"sync":[]}}},{"id":"AiUsageComponentSet:react-dom/client","name":"react-dom/client","version":"19.2.8","singleton":true,"requiredVersion":"*","assets":{"js":{"async":[],"sync":["assets/_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-CFn_lBXc.js"]},"css":{"async":[],"sync":[]}}}],"remotes":[],"exposes":[{"id":"AiUsageComponentSet:Components","name":"Components","assets":{"js":{"async":["assets/lib-MY71LxhX.js"],"sync":["assets/Components-
|
|
1
|
+
{"id":"AiUsageComponentSet","name":"AiUsageComponentSet","metaData":{"name":"AiUsageComponentSet","type":"app","buildInfo":{"buildVersion":"1.0.0","buildName":"AiUsageComponentSet"},"remoteEntry":{"name":"customComponents.js","path":"","type":"module"},"ssrRemoteEntry":{"name":"customComponents.ssr.js","path":"","type":"module"},"types":{"path":"","name":"","zip":"@mf-types.zip","api":"@mf-types.d.ts"},"globalName":"AiUsageComponentSet","pluginVersion":"0.2.5","publicPath":"auto"},"shared":[{"id":"AiUsageComponentSet:@iobroker/gui-components","name":"@iobroker/gui-components","version":"10.1.0","singleton":true,"requiredVersion":"*","assets":{"js":{"async":[],"sync":["assets/_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__prebuild___mf_0_iobroker_mf_1_gui_mf_2_components__prebuild__-DeLUTSLT.js"]},"css":{"async":[],"sync":[]}}},{"id":"AiUsageComponentSet:@iobroker/json-config","name":"@iobroker/json-config","version":"9.0.22","singleton":true,"requiredVersion":"*","assets":{"js":{"async":["assets/lib-MY71LxhX.js"],"sync":["assets/_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare___mf_0_iobroker_mf_1_json_mf_2_config__loadShare__.js-DEdJInQG.js"]},"css":{"async":[],"sync":[]}}},{"id":"AiUsageComponentSet:@mui/icons-material","name":"@mui/icons-material","version":"9.3.1","singleton":true,"requiredVersion":"*","assets":{"js":{"async":[],"sync":["assets/_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-DzPBJWNQ.js"]},"css":{"async":[],"sync":[]}}},{"id":"AiUsageComponentSet:@mui/material","name":"@mui/material","version":"9.3.1","singleton":true,"requiredVersion":"*","assets":{"js":{"async":[],"sync":["assets/_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-DzPBJWNQ.js"]},"css":{"async":[],"sync":[]}}},{"id":"AiUsageComponentSet:react","name":"react","version":"19.2.8","singleton":true,"requiredVersion":"*","assets":{"js":{"async":[],"sync":["assets/_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-DzPBJWNQ.js"]},"css":{"async":[],"sync":[]}}},{"id":"AiUsageComponentSet:react-dom","name":"react-dom","version":"19.2.8","singleton":true,"requiredVersion":"*","assets":{"js":{"async":[],"sync":["assets/_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-DzPBJWNQ.js"]},"css":{"async":[],"sync":[]}}},{"id":"AiUsageComponentSet:react/jsx-runtime","name":"react/jsx-runtime","version":"19.2.8","singleton":true,"requiredVersion":"*","assets":{"js":{"async":[],"sync":["assets/_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-DzPBJWNQ.js"]},"css":{"async":[],"sync":[]}}},{"id":"AiUsageComponentSet:react-dom/client","name":"react-dom/client","version":"19.2.8","singleton":true,"requiredVersion":"*","assets":{"js":{"async":[],"sync":["assets/_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-CFn_lBXc.js"]},"css":{"async":[],"sync":[]}}}],"remotes":[],"exposes":[{"id":"AiUsageComponentSet:Components","name":"Components","assets":{"js":{"async":["assets/lib-MY71LxhX.js"],"sync":["assets/Components-SGigWPbz.js"]},"css":{"async":[],"sync":[]}},"path":"./Components"}]}
|
package/build/lib/poll-engine.js
CHANGED
|
@@ -119,16 +119,16 @@ class PollEngine {
|
|
|
119
119
|
* and the badge in the settings, and on its last value it stays green for as long
|
|
120
120
|
* as the instance is switched off.
|
|
121
121
|
*
|
|
122
|
-
* `info.error`
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
122
|
+
* `info.error` goes back to {@link REASON_UNKNOWN}: while the instance is off the
|
|
123
|
+
* adapter has nothing to report, and the fleet-wide word for that is the single
|
|
124
|
+
* word "Unknown" (krobi 2026-08-27) — never a stale provider message, never an
|
|
125
|
+
* invented sentence about our own operating state.
|
|
126
126
|
*
|
|
127
127
|
* The returned promise is what makes this WORK. Measured on the live server
|
|
128
128
|
* 2026-08-27: issued fire-and-forget and followed by an immediate `callback()`,
|
|
129
129
|
* not one of these writes ever reached the database — the process was gone first.
|
|
130
|
-
* The caller has to wait for this
|
|
131
|
-
*
|
|
130
|
+
* The caller has to wait for this before telling the host it is done — the host's
|
|
131
|
+
* own deadline is the only limit there is.
|
|
132
132
|
*
|
|
133
133
|
* @returns resolves once every write has been acknowledged
|
|
134
134
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/poll-engine.ts"],
|
|
4
|
-
"sourcesContent": ["import type { AccountConfig } from \"./pure-helpers\";\nimport { FetchError, type UsageProvider, type UsageSnapshot } from \"./provider\";\nimport { limitingWindow, mapSnapshot, orphanObjectIds, type ObjectDef } from \"./snapshot-tree\";\nimport { computeTotals, type AccountStatus } from \"./totals\";\n\n/**\n * What the adapter currently knows about one account, in one word.\n *\n * `ok` and `rate-limited` mean the AI service is up and talking to us,\n * `unauthorized` means it is up but rejects our sign-in, `service-down` means the\n * service itself answered with a fault, `no-connection` means we never reached it.\n */\nexport type AccountState = \"ok\" | \"unauthorized\" | \"rate-limited\" | \"service-down\" | \"no-connection\";\n\n/** Consecutive network failures after which an account is judged unreachable. */\nconst MAX_NETWORK_FAILURES = 3;\n/** First backoff after a rate-limit answer (ms); doubles per repeat. */\nconst BACKOFF_START_MS = 10 * 60 * 1000;\n/** Backoff ceiling (ms). */\nconst BACKOFF_MAX_MS = 60 * 60 * 1000;\n/** Stagger between the accounts' first polls (ms) so they never fire in one burst. */\nconst STAGGER_MS = 3000;\n\n/**\n * What `info.error` says while the adapter itself has nothing to report: switched\n * off, or started and not asked yet.\n *\n * ONE wording for the whole fleet (krobi 2026-08-27) \u2014 the datapoint otherwise ends\n * up saying something different in every adapter. It stays a single word: the field\n * names the reason, it does not explain itself.\n */\nconst REASON_UNKNOWN = \"Unknown\";\n\n/** The adapter callbacks the engine drives \u2014 narrow, so tests need no adapter mock. */\nexport interface EngineDeps {\n /** Create or update an object. */\n upsertObject(def: ObjectDef): Promise<void>;\n /** Delete an object and everything below it. */\n deleteObject(id: string): Promise<void>;\n /** Every state id that currently exists below `prefix` (relative to the instance). */\n listStateIds(prefix: string): Promise<string[]>;\n /** Write a state value with ack \u2014 for MEASUREMENTS, where every cycle carries information. */\n setState(id: string, value: boolean | number | string): void;\n /**\n * Write a state only when the value differs from what the database holds \u2014 for\n * INDICATORS. js-controller does the comparison (`setStateChangedAsync`), which is\n * what the rest of the fleet uses; a hand-rolled cache would only know what this\n * process wrote and would still write blindly after a restart.\n *\n * Returns a promise so the shutdown path can WAIT for its writes; everywhere else\n * it is deliberately ignored \u2014 a poll cycle must not be held up by the database.\n */\n setStateChanged(id: string, value: boolean | number | string): Promise<void>;\n /** Schedule a repeating callback; returns a cancel handle. */\n schedule(cb: () => void, ms: number): unknown;\n /** Schedule a one-shot callback; returns a cancel handle. */\n scheduleOnce(cb: () => void, ms: number): unknown;\n /** Cancel a handle from schedule/scheduleOnce. */\n cancel(handle: unknown): void;\n /** Current time (ms since epoch) \u2014 injected for tests. */\n now(): number;\n /** Adapter log. */\n log: { debug(m: string): void; info(m: string): void; warn(m: string): void; error(m: string): void };\n /** Raise a user-facing notification (threshold crossing, broken credentials). */\n notify?(accountName: string, message: string): void;\n /**\n * Called once, when every account has finished its FIRST poll.\n *\n * The first round is staggered on purpose, so the adapter cannot report what the\n * object tree gained until the last account has been through. A config change\n * restarts the instance, so this is also the only moment a user needs the report.\n */\n afterFirstRound?(): void;\n}\n\n/** One account's runtime state inside the engine. */\ninterface AccountRuntime {\n config: AccountConfig;\n provider: UsageProvider;\n status: AccountStatus;\n /** Consecutive network failures. */\n failCount: number;\n /** Skip polls until this time (rate-limit backoff). */\n skipUntil: number;\n /** Current backoff length (ms). */\n backoffMs: number;\n /** Whether the auth-broken notification has been raised (reset on success). */\n authNotified: boolean;\n /** Whether the AI service itself answered on the last attempt. */\n serviceOnline: boolean;\n /** The account's one-word state. */\n state: AccountState;\n /** Plain-text reason shown in `info.error`; empty while everything works. */\n error: string;\n /** Object ids already created for this account (create-once cache). */\n createdObjects: Set<string>;\n /** Whether this account has been through its first poll. */\n firstPollDone: boolean;\n /** True while a poll of this account is in flight \u2014 a second one must not overlap. */\n polling: boolean;\n /** Set when a poll was requested while one was running; runs once the current one ends. */\n pollAgain: boolean;\n /**\n * The dynamic state ids the last snapshot delivered, or null until the first\n * reconcile \u2014 which reads the database, so a datapoint that vanished while the\n * adapter was stopped is caught too.\n */\n deliveredIds: string[] | null;\n /** The skeleton's own state ids \u2014 they never expire. */\n staticIds: string[];\n}\n\n/**\n * Drives the polling of all configured accounts: staggered starts, one independent\n * cycle per account, typed failure handling (auth = immediate + one notification,\n * rate-limit = backoff keeping last values, service = reported down at once,\n * network = tolerated three times), warn-threshold transitions on the PLAN-WIDE\n * windows only, and the adapter-wide totals. Pure orchestration \u2014 all IO is injected.\n */\nexport class PollEngine {\n private readonly runtimes: AccountRuntime[] = [];\n private readonly handles: unknown[] = [];\n private stopped = false;\n private firstRoundReported = false;\n /**\n * How many accounts the user switched on \u2014 including those the adapter cannot\n * poll because their credential is missing. `total.accounts` is what the user\n * configured, not what happened to work out.\n */\n private readonly configuredAccounts: number;\n\n /**\n * @param accounts the validated account configs\n * @param providers each account id's provider (accounts without one are skipped)\n * @param intervalSec the poll interval in seconds\n * @param deps the injected adapter callbacks\n */\n public constructor(\n accounts: readonly AccountConfig[],\n providers: ReadonlyMap<string, UsageProvider>,\n private readonly intervalSec: number,\n private readonly deps: EngineDeps,\n ) {\n this.configuredAccounts = accounts.length;\n for (const config of accounts) {\n const provider = providers.get(config.id);\n if (!provider) {\n deps.log.warn(`${config.name}: provider \"${config.provider}\" is not available \u2014 account skipped`);\n continue;\n }\n this.runtimes.push({\n config,\n provider,\n status: { reachable: false, warning: false },\n failCount: 0,\n skipUntil: 0,\n backoffMs: BACKOFF_START_MS,\n authNotified: false,\n serviceOnline: false,\n state: \"no-connection\",\n // Nothing known until the service says something; the skeleton writes\n // REASON_UNKNOWN, and the first answer replaces it.\n error: REASON_UNKNOWN,\n createdObjects: new Set(),\n firstPollDone: false,\n polling: false,\n pollAgain: false,\n deliveredIds: null,\n staticIds: [],\n });\n }\n }\n\n /** Create the static per-account and totals objects, then arm the poll cycles. */\n public async start(): Promise<void> {\n for (const runtime of this.runtimes) {\n await this.createAccountSkeleton(runtime);\n }\n await this.createTotalsSkeleton();\n this.writeTotals();\n if (this.runtimes.length === 0) {\n // Nothing will ever poll \u2014 report right away, the cleanup may still have changed something.\n this.reportFirstRoundOnce();\n return;\n }\n this.runtimes.forEach((runtime, index) => {\n // The repeating timer is armed INSIDE the staggered first poll, not next to\n // it: armed here it would start counting for every account in the same\n // instant, and from the second round on all of them would fire together \u2014\n // exactly the burst the stagger exists to prevent, against providers that\n // answer a burst by locking the whole account for a day.\n this.handles.push(\n this.deps.scheduleOnce(() => {\n if (this.stopped) {\n return;\n }\n this.handles.push(this.deps.schedule(() => void this.pollAccount(runtime), this.intervalSec * 1000));\n void this.pollAccount(runtime);\n }, index * STAGGER_MS),\n );\n });\n }\n\n /** Cancel every timer. Synchronous \u2014 safe from onUnload. */\n public stop(): void {\n this.stopped = true;\n for (const handle of this.handles) {\n this.deps.cancel(handle);\n }\n this.handles.length = 0;\n }\n\n /**\n * Say that no account is delivering any more \u2014 for shutdown.\n *\n * A stopped adapter reads nothing, so it must not leave every account claiming to\n * be online: `info.unreach` is what colours the account in the admin's object tree\n * and the badge in the settings, and on its last value it stays green for as long\n * as the instance is switched off.\n *\n * `info.error` is deliberately NOT touched. That datapoint answers \"what did the AI\n * service say\", and the adapter being switched off is not something the service\n * said \u2014 a sentence about our own operating state in there reads as if the provider\n * had reported it. Whatever the last real reason was stays readable.\n *\n * The returned promise is what makes this WORK. Measured on the live server\n * 2026-08-27: issued fire-and-forget and followed by an immediate `callback()`,\n * not one of these writes ever reached the database \u2014 the process was gone first.\n * The caller has to wait for this (with its own time limit) before saying it is\n * done.\n *\n * @returns resolves once every write has been acknowledged\n */\n public async markAllOffline(): Promise<void> {\n const writes = this.runtimes.flatMap(runtime => [\n this.deps.setStateChanged(`${runtime.config.id}.info.unreach`, true),\n this.deps.setStateChanged(`${runtime.config.id}.info.error`, REASON_UNKNOWN),\n ]);\n // The same lie one level up: \"accounts currently delivering data\" is zero.\n writes.push(this.deps.setStateChanged(\"total.accountsReachable\", 0));\n writes.push(this.deps.setStateChanged(\"info.connection\", false));\n await Promise.all(writes);\n }\n\n /**\n * Poll one account immediately, by id. Used after a successful sign-in: waiting\n * up to a full interval there reads as \"the sign-in did not work\".\n *\n * @param accountId the account's object id\n */\n public async pollNow(accountId: string): Promise<void> {\n const runtime = this.runtimes.find(entry => entry.config.id === accountId);\n if (runtime) {\n // A fresh sign-in clears a previous auth failure and any backoff.\n runtime.authNotified = false;\n runtime.skipUntil = 0;\n await this.pollAccount(runtime);\n }\n }\n\n /**\n * Poll one account now (also used by the staggered first run).\n *\n * Never two at once for the same account: a sign-in triggers an immediate poll,\n * which can land on top of a scheduled one \u2014 and two token refreshes in parallel\n * on a rotating refresh token sign each other out. A request that arrives while\n * one is running is remembered and runs right after, so nothing is lost.\n *\n * @param runtime the account's runtime\n */\n private async pollAccount(runtime: AccountRuntime): Promise<void> {\n if (this.stopped) {\n return;\n }\n if (runtime.polling) {\n runtime.pollAgain = true;\n return;\n }\n runtime.polling = true;\n try {\n await this.pollOnce(runtime);\n } finally {\n runtime.polling = false;\n }\n if (runtime.pollAgain && !this.stopped) {\n runtime.pollAgain = false;\n await this.pollAccount(runtime);\n }\n }\n\n /**\n * One poll of one account: fetch, classify, write.\n *\n * @param runtime the account's runtime\n */\n private async pollOnce(runtime: AccountRuntime): Promise<void> {\n const { config } = runtime;\n if (this.deps.now() < runtime.skipUntil) {\n this.deps.log.debug(`${config.name}: in rate-limit backoff \u2014 poll skipped`);\n // Still counts as \"been through\": otherwise an account that starts inside a\n // backoff would hold the first-round report back forever.\n runtime.firstPollDone = true;\n this.reportFirstRoundOnce();\n return;\n }\n try {\n const snapshot = await runtime.provider.fetch();\n runtime.failCount = 0;\n runtime.backoffMs = BACKOFF_START_MS;\n runtime.authNotified = false;\n runtime.status.snapshot = snapshot;\n runtime.status.reachable = true;\n runtime.serviceOnline = true;\n runtime.state = \"ok\";\n runtime.error = \"\";\n await this.applySnapshot(runtime, snapshot);\n } catch (e) {\n this.handleFailure(runtime, e);\n }\n this.writeAccountInfo(runtime);\n this.writeTotals();\n runtime.firstPollDone = true;\n this.reportFirstRoundOnce();\n }\n\n /** Fire the first-round hook exactly once, when no account is still pending. */\n private reportFirstRoundOnce(): void {\n if (this.firstRoundReported || this.runtimes.some(runtime => !runtime.firstPollDone)) {\n return;\n }\n this.firstRoundReported = true;\n this.deps.afterFirstRound?.();\n }\n\n /**\n * Write a successful snapshot: upsert new objects (create-once cache), write the\n * values, and run the warn-threshold transition.\n *\n * @param runtime the account's runtime\n * @param snapshot the fetched snapshot\n */\n private async applySnapshot(runtime: AccountRuntime, snapshot: UsageSnapshot): Promise<void> {\n const { config } = runtime;\n const { objects, writes } = mapSnapshot(config.id, snapshot);\n for (const object of objects) {\n if (!runtime.createdObjects.has(object.id)) {\n await this.deps.upsertObject(object);\n runtime.createdObjects.add(object.id);\n }\n }\n for (const write of writes) {\n this.deps.setState(write.id, write.value);\n }\n await this.removeVanished(\n runtime,\n writes.map(write => write.id),\n );\n // Only PLAN-WIDE windows speak for the account \u2014 a per-model bucket at 100 %\n // must not read as \"this AI is full\" (krobi 2026-08-26).\n const driver = limitingWindow(snapshot);\n const percent = driver?.percent ?? 0;\n const wasWarning = runtime.status.warning;\n runtime.status.warning = percent >= config.warnThreshold;\n // Indicators go through the changed-write, measurements through the normal one.\n void this.deps.setStateChanged(`${config.id}.warning`, runtime.status.warning);\n void this.deps.setStateChanged(`${config.id}.limitReached`, percent >= 100);\n if (runtime.status.warning && !wasWarning) {\n // Always name the window: \"usage at 100 %\" without it was misleading whenever\n // several windows existed.\n const window = driver ? `${driver.label} ` : \"\";\n const message = `${config.name}: ${window}at ${Math.round(percent)} % (threshold ${config.warnThreshold} %)`;\n this.deps.log.warn(message);\n this.deps.notify?.(config.name, message);\n }\n }\n\n /**\n * Delete what this account no longer delivers.\n *\n * The first round after a start compares against the DATABASE, so a window or\n * model that disappeared while the adapter was stopped is caught as well; every\n * round after that compares against the previous snapshot, which costs nothing.\n *\n * @param runtime the account's runtime\n * @param delivered the state ids this snapshot wrote\n */\n private async removeVanished(runtime: AccountRuntime, delivered: string[]): Promise<void> {\n const known = runtime.deliveredIds ?? (await this.deps.listStateIds(runtime.config.id));\n for (const id of orphanObjectIds(known, delivered, runtime.staticIds)) {\n await this.deps.deleteObject(id);\n runtime.createdObjects.delete(id);\n this.deps.log.info(`${runtime.config.name}: removed \"${id}\" \u2014 the provider no longer reports it`);\n }\n runtime.deliveredIds = delivered;\n }\n\n /**\n * Classify a fetch failure.\n *\n * The split matters for the online indicator: with `auth` and `rate-limit` the AI\n * service ANSWERED \u2014 it is online, it just said no \u2014 so only our own access is\n * broken. `service` means the service answered with a fault of its own and is\n * reported as down at once (it told us, that is not a flake). A `network` failure\n * is tolerated MAX_NETWORK_FAILURES times before we call the connection gone, so\n * a single hiccup does not make the indicator flap.\n *\n * @param runtime the account's runtime\n * @param error the thrown error\n */\n private handleFailure(runtime: AccountRuntime, error: unknown): void {\n const { config } = runtime;\n const message = error instanceof Error ? error.message : String(error);\n if (error instanceof FetchError && error.kind === \"auth\") {\n runtime.status.reachable = false;\n runtime.serviceOnline = true;\n runtime.state = \"unauthorized\";\n runtime.error = `Sign-in rejected \u2014 ${message}`;\n if (!runtime.authNotified) {\n runtime.authNotified = true;\n const text = `${config.name}: credentials rejected \u2014 ${message}`;\n this.deps.log.warn(text);\n this.deps.notify?.(config.name, text);\n }\n return;\n }\n if (error instanceof FetchError && error.kind === \"rate-limit\") {\n runtime.serviceOnline = true;\n runtime.state = \"rate-limited\";\n runtime.error = `Throttled by the provider \u2014 retrying in ${Math.round(runtime.backoffMs / 60000)} min, last values kept`;\n runtime.skipUntil = this.deps.now() + runtime.backoffMs;\n this.deps.log.warn(\n `${config.name}: rate-limited \u2014 backing off for ${Math.round(runtime.backoffMs / 60000)} min, keeping last values`,\n );\n runtime.backoffMs = Math.min(BACKOFF_MAX_MS, runtime.backoffMs * 2);\n return;\n }\n if (error instanceof FetchError && error.kind === \"service\") {\n runtime.status.reachable = false;\n runtime.failCount = 0;\n if (runtime.serviceOnline) {\n this.deps.log.warn(`${config.name}: the service reports a fault (${message}) \u2014 values kept`);\n }\n runtime.serviceOnline = false;\n runtime.state = \"service-down\";\n runtime.error = `The AI service reports a fault \u2014 ${message}`;\n return;\n }\n runtime.failCount++;\n this.deps.log.debug(`${config.name}: fetch failed (${message}), attempt ${runtime.failCount}`);\n if (runtime.failCount >= MAX_NETWORK_FAILURES) {\n runtime.status.reachable = false;\n if (runtime.serviceOnline) {\n this.deps.log.warn(`${config.name}: not reachable after ${runtime.failCount} attempts (${message})`);\n }\n runtime.serviceOnline = false;\n runtime.state = \"no-connection\";\n runtime.error = `Not reachable after ${runtime.failCount} attempts \u2014 ${message}`;\n }\n }\n\n /**\n * Write one account's info states (offline marker, error text, last update).\n *\n * @param runtime the account's runtime\n */\n private writeAccountInfo(runtime: AccountRuntime): void {\n const { config } = runtime;\n void this.writeAccountStatus(runtime);\n if (runtime.status.reachable) {\n this.deps.setState(`${config.id}.info.lastUpdate`, new Date(this.deps.now()).toISOString());\n }\n }\n\n /**\n * Write the two status states.\n *\n * `unreach` drives the connection icon the admin draws next to the account, so it\n * has to mean what a user reads into that icon: green while the account delivers.\n * A throttle keeps the last values and the service is fine, so it stays green and\n * only fills the error text; a dead sign-in, a broken service or no connection at\n * all turn it off. Both go through the changed-write: an indicator rewritten every\n * cycle floods the history and hides the real transition.\n *\n * @param runtime the account's runtime\n */\n private async writeAccountStatus(runtime: AccountRuntime): Promise<void> {\n const { config } = runtime;\n const delivering = runtime.state === \"ok\" || runtime.state === \"rate-limited\";\n await Promise.all([\n this.deps.setStateChanged(`${config.id}.info.unreach`, !delivering),\n this.deps.setStateChanged(`${config.id}.info.error`, runtime.error),\n ]);\n }\n\n /** Recompute and write the totals + info.connection. */\n private writeTotals(): void {\n const totals = computeTotals(\n this.runtimes.map(runtime => runtime.status),\n this.configuredAccounts,\n );\n this.deps.setState(\"total.costs.today\", totals.costsToday);\n this.deps.setState(\"total.costs.month\", totals.costsMonth);\n this.deps.setState(\"total.costs.projectedMonth\", totals.costsProjectedMonth);\n this.deps.setState(\"total.maxLimitPercent\", totals.maxLimitPercent);\n this.deps.setState(\"total.warningsActive\", totals.warningsActive);\n void this.deps.setStateChanged(\"total.limitReached\", totals.limitReached);\n this.deps.setState(\"total.accountsReachable\", totals.accountsReachable);\n // The configured count only ever changes with the configuration, which restarts\n // the instance \u2014 rewriting it every cycle would be pure noise in a recording.\n void this.deps.setStateChanged(\"total.accounts\", totals.accounts);\n void this.deps.setStateChanged(\"info.connection\", totals.accountsReachable > 0);\n }\n\n /**\n * The static per-account objects that exist regardless of what the source delivers.\n *\n * @param runtime the account's runtime\n */\n private async createAccountSkeleton(runtime: AccountRuntime): Promise<void> {\n const { config } = runtime;\n const defs: ObjectDef[] = [\n {\n id: config.id,\n type: \"device\",\n common: {\n name: `${config.name} (${config.provider})`,\n // The admin's object tree draws its connection icon from this link and\n // from nothing else \u2014 govee, beszel, homewizard and nut2 all do the same.\n statusStates: { offlineId: \"info.unreach\" },\n },\n },\n { id: `${config.id}.info`, type: \"channel\", common: { name: \"Info\" } },\n {\n // The two slots ioBroker itself provides for this \u2014 measured against\n // @iobroker/type-detector 6.0.0: `unreach` is the offline marker every\n // device type carries (`indicator.reachable` is deprecated there), and\n // `indicator.error` is the standard home for the reason.\n id: `${config.id}.info.unreach`,\n type: \"state\",\n common: {\n name: \"AI service not reachable\",\n type: \"boolean\",\n role: \"indicator.maintenance.unreach\",\n read: true,\n write: false,\n },\n },\n {\n // NOT `indicator.error`: the two official sources disagree \u2014 the type-detector\n // lists that role as a String, the repochecker's validity whitelist allows\n // boolean only (E1009). Validity wins, so the message rides on `text`.\n id: `${config.id}.info.error`,\n type: \"state\",\n common: { name: \"Last error\", type: \"string\", role: \"text\", read: true, write: false },\n },\n {\n id: `${config.id}.info.lastUpdate`,\n type: \"state\",\n common: { name: \"Last successful update\", type: \"string\", role: \"date\", read: true, write: false },\n },\n {\n id: `${config.id}.warning`,\n type: \"state\",\n common: { name: \"Above warn threshold\", type: \"boolean\", role: \"indicator\", read: true, write: false },\n },\n {\n id: `${config.id}.limitReached`,\n type: \"state\",\n common: {\n name: \"A plan-wide limit window is full\",\n type: \"boolean\",\n role: \"indicator\",\n read: true,\n write: false,\n },\n },\n ];\n for (const def of defs) {\n await this.deps.upsertObject(def);\n runtime.createdObjects.add(def.id);\n }\n runtime.staticIds = defs.filter(def => def.type === \"state\").map(def => def.id);\n // Mark it as not delivering right away, before anything has been asked.\n //\n // This looks pessimistic and is the honest state: nothing has been read yet. It\n // also carries the whole weight of \"the instance was off\". Whatever the previous\n // run left behind stands until someone overwrites it \u2014 after a hard kill, after\n // a crash, after an unclean shutdown the account would otherwise sit there green\n // and claim to deliver while no process exists at all. nut2 marks its devices\n // unreachable on start for exactly this reason.\n //\n // The window is short: the first poll follows within seconds and writes the real\n // state, success or failure. Only the marker \u2014 `info.error` belongs to the AI\n // service, and \"we have not asked yet\" is not something it said.\n void this.deps.setStateChanged(`${config.id}.info.unreach`, true);\n void this.deps.setStateChanged(`${config.id}.info.error`, REASON_UNKNOWN);\n }\n\n /** The totals skeleton (channel + states). */\n private async createTotalsSkeleton(): Promise<void> {\n const defs: ObjectDef[] = [\n { id: \"total.costs\", type: \"channel\", common: { name: \"Costs (USD accounts)\" } },\n {\n id: \"total.costs.today\",\n type: \"state\",\n common: {\n name: \"Costs today (all accounts)\",\n type: \"number\",\n role: \"value\",\n read: true,\n write: false,\n unit: \"USD\",\n },\n },\n {\n id: \"total.costs.month\",\n type: \"state\",\n common: {\n name: \"Costs this month (all accounts)\",\n type: \"number\",\n role: \"value\",\n read: true,\n write: false,\n unit: \"USD\",\n },\n },\n {\n id: \"total.costs.projectedMonth\",\n type: \"state\",\n common: {\n name: \"Costs projected month-end (computed)\",\n type: \"number\",\n role: \"value\",\n read: true,\n write: false,\n unit: \"USD\",\n },\n },\n {\n id: \"total.maxLimitPercent\",\n type: \"state\",\n common: {\n name: \"Highest plan-wide utilisation of any account\",\n type: \"number\",\n role: \"value\",\n read: true,\n write: false,\n unit: \"%\",\n },\n },\n {\n id: \"total.warningsActive\",\n type: \"state\",\n common: {\n name: \"Accounts above their warn threshold\",\n type: \"number\",\n role: \"value\",\n read: true,\n write: false,\n },\n },\n {\n id: \"total.limitReached\",\n type: \"state\",\n common: {\n name: \"Any plan-wide limit window full\",\n type: \"boolean\",\n role: \"indicator\",\n read: true,\n write: false,\n },\n },\n {\n id: \"total.accountsReachable\",\n type: \"state\",\n common: { name: \"Reachable accounts\", type: \"number\", role: \"value\", read: true, write: false },\n },\n {\n id: \"total.accounts\",\n type: \"state\",\n common: { name: \"Configured accounts\", type: \"number\", role: \"value\", read: true, write: false },\n },\n ];\n for (const def of defs) {\n await this.deps.upsertObject(def);\n }\n }\n}\n"],
|
|
4
|
+
"sourcesContent": ["import type { AccountConfig } from \"./pure-helpers\";\nimport { FetchError, type UsageProvider, type UsageSnapshot } from \"./provider\";\nimport { limitingWindow, mapSnapshot, orphanObjectIds, type ObjectDef } from \"./snapshot-tree\";\nimport { computeTotals, type AccountStatus } from \"./totals\";\n\n/**\n * What the adapter currently knows about one account, in one word.\n *\n * `ok` and `rate-limited` mean the AI service is up and talking to us,\n * `unauthorized` means it is up but rejects our sign-in, `service-down` means the\n * service itself answered with a fault, `no-connection` means we never reached it.\n */\nexport type AccountState = \"ok\" | \"unauthorized\" | \"rate-limited\" | \"service-down\" | \"no-connection\";\n\n/** Consecutive network failures after which an account is judged unreachable. */\nconst MAX_NETWORK_FAILURES = 3;\n/** First backoff after a rate-limit answer (ms); doubles per repeat. */\nconst BACKOFF_START_MS = 10 * 60 * 1000;\n/** Backoff ceiling (ms). */\nconst BACKOFF_MAX_MS = 60 * 60 * 1000;\n/** Stagger between the accounts' first polls (ms) so they never fire in one burst. */\nconst STAGGER_MS = 3000;\n\n/**\n * What `info.error` says while the adapter itself has nothing to report: switched\n * off, or started and not asked yet.\n *\n * ONE wording for the whole fleet (krobi 2026-08-27) \u2014 the datapoint otherwise ends\n * up saying something different in every adapter. It stays a single word: the field\n * names the reason, it does not explain itself.\n */\nconst REASON_UNKNOWN = \"Unknown\";\n\n/** The adapter callbacks the engine drives \u2014 narrow, so tests need no adapter mock. */\nexport interface EngineDeps {\n /** Create or update an object. */\n upsertObject(def: ObjectDef): Promise<void>;\n /** Delete an object and everything below it. */\n deleteObject(id: string): Promise<void>;\n /** Every state id that currently exists below `prefix` (relative to the instance). */\n listStateIds(prefix: string): Promise<string[]>;\n /** Write a state value with ack \u2014 for MEASUREMENTS, where every cycle carries information. */\n setState(id: string, value: boolean | number | string): void;\n /**\n * Write a state only when the value differs from what the database holds \u2014 for\n * INDICATORS. js-controller does the comparison (`setStateChangedAsync`), which is\n * what the rest of the fleet uses; a hand-rolled cache would only know what this\n * process wrote and would still write blindly after a restart.\n *\n * Returns a promise so the shutdown path can WAIT for its writes; everywhere else\n * it is deliberately ignored \u2014 a poll cycle must not be held up by the database.\n */\n setStateChanged(id: string, value: boolean | number | string): Promise<void>;\n /** Schedule a repeating callback; returns a cancel handle. */\n schedule(cb: () => void, ms: number): unknown;\n /** Schedule a one-shot callback; returns a cancel handle. */\n scheduleOnce(cb: () => void, ms: number): unknown;\n /** Cancel a handle from schedule/scheduleOnce. */\n cancel(handle: unknown): void;\n /** Current time (ms since epoch) \u2014 injected for tests. */\n now(): number;\n /** Adapter log. */\n log: { debug(m: string): void; info(m: string): void; warn(m: string): void; error(m: string): void };\n /** Raise a user-facing notification (threshold crossing, broken credentials). */\n notify?(accountName: string, message: string): void;\n /**\n * Called once, when every account has finished its FIRST poll.\n *\n * The first round is staggered on purpose, so the adapter cannot report what the\n * object tree gained until the last account has been through. A config change\n * restarts the instance, so this is also the only moment a user needs the report.\n */\n afterFirstRound?(): void;\n}\n\n/** One account's runtime state inside the engine. */\ninterface AccountRuntime {\n config: AccountConfig;\n provider: UsageProvider;\n status: AccountStatus;\n /** Consecutive network failures. */\n failCount: number;\n /** Skip polls until this time (rate-limit backoff). */\n skipUntil: number;\n /** Current backoff length (ms). */\n backoffMs: number;\n /** Whether the auth-broken notification has been raised (reset on success). */\n authNotified: boolean;\n /** Whether the AI service itself answered on the last attempt. */\n serviceOnline: boolean;\n /** The account's one-word state. */\n state: AccountState;\n /** Plain-text reason shown in `info.error`; empty while everything works. */\n error: string;\n /** Object ids already created for this account (create-once cache). */\n createdObjects: Set<string>;\n /** Whether this account has been through its first poll. */\n firstPollDone: boolean;\n /** True while a poll of this account is in flight \u2014 a second one must not overlap. */\n polling: boolean;\n /** Set when a poll was requested while one was running; runs once the current one ends. */\n pollAgain: boolean;\n /**\n * The dynamic state ids the last snapshot delivered, or null until the first\n * reconcile \u2014 which reads the database, so a datapoint that vanished while the\n * adapter was stopped is caught too.\n */\n deliveredIds: string[] | null;\n /** The skeleton's own state ids \u2014 they never expire. */\n staticIds: string[];\n}\n\n/**\n * Drives the polling of all configured accounts: staggered starts, one independent\n * cycle per account, typed failure handling (auth = immediate + one notification,\n * rate-limit = backoff keeping last values, service = reported down at once,\n * network = tolerated three times), warn-threshold transitions on the PLAN-WIDE\n * windows only, and the adapter-wide totals. Pure orchestration \u2014 all IO is injected.\n */\nexport class PollEngine {\n private readonly runtimes: AccountRuntime[] = [];\n private readonly handles: unknown[] = [];\n private stopped = false;\n private firstRoundReported = false;\n /**\n * How many accounts the user switched on \u2014 including those the adapter cannot\n * poll because their credential is missing. `total.accounts` is what the user\n * configured, not what happened to work out.\n */\n private readonly configuredAccounts: number;\n\n /**\n * @param accounts the validated account configs\n * @param providers each account id's provider (accounts without one are skipped)\n * @param intervalSec the poll interval in seconds\n * @param deps the injected adapter callbacks\n */\n public constructor(\n accounts: readonly AccountConfig[],\n providers: ReadonlyMap<string, UsageProvider>,\n private readonly intervalSec: number,\n private readonly deps: EngineDeps,\n ) {\n this.configuredAccounts = accounts.length;\n for (const config of accounts) {\n const provider = providers.get(config.id);\n if (!provider) {\n deps.log.warn(`${config.name}: provider \"${config.provider}\" is not available \u2014 account skipped`);\n continue;\n }\n this.runtimes.push({\n config,\n provider,\n status: { reachable: false, warning: false },\n failCount: 0,\n skipUntil: 0,\n backoffMs: BACKOFF_START_MS,\n authNotified: false,\n serviceOnline: false,\n state: \"no-connection\",\n // Nothing known until the service says something; the skeleton writes\n // REASON_UNKNOWN, and the first answer replaces it.\n error: REASON_UNKNOWN,\n createdObjects: new Set(),\n firstPollDone: false,\n polling: false,\n pollAgain: false,\n deliveredIds: null,\n staticIds: [],\n });\n }\n }\n\n /** Create the static per-account and totals objects, then arm the poll cycles. */\n public async start(): Promise<void> {\n for (const runtime of this.runtimes) {\n await this.createAccountSkeleton(runtime);\n }\n await this.createTotalsSkeleton();\n this.writeTotals();\n if (this.runtimes.length === 0) {\n // Nothing will ever poll \u2014 report right away, the cleanup may still have changed something.\n this.reportFirstRoundOnce();\n return;\n }\n this.runtimes.forEach((runtime, index) => {\n // The repeating timer is armed INSIDE the staggered first poll, not next to\n // it: armed here it would start counting for every account in the same\n // instant, and from the second round on all of them would fire together \u2014\n // exactly the burst the stagger exists to prevent, against providers that\n // answer a burst by locking the whole account for a day.\n this.handles.push(\n this.deps.scheduleOnce(() => {\n if (this.stopped) {\n return;\n }\n this.handles.push(this.deps.schedule(() => void this.pollAccount(runtime), this.intervalSec * 1000));\n void this.pollAccount(runtime);\n }, index * STAGGER_MS),\n );\n });\n }\n\n /** Cancel every timer. Synchronous \u2014 safe from onUnload. */\n public stop(): void {\n this.stopped = true;\n for (const handle of this.handles) {\n this.deps.cancel(handle);\n }\n this.handles.length = 0;\n }\n\n /**\n * Say that no account is delivering any more \u2014 for shutdown.\n *\n * A stopped adapter reads nothing, so it must not leave every account claiming to\n * be online: `info.unreach` is what colours the account in the admin's object tree\n * and the badge in the settings, and on its last value it stays green for as long\n * as the instance is switched off.\n *\n * `info.error` goes back to {@link REASON_UNKNOWN}: while the instance is off the\n * adapter has nothing to report, and the fleet-wide word for that is the single\n * word \"Unknown\" (krobi 2026-08-27) \u2014 never a stale provider message, never an\n * invented sentence about our own operating state.\n *\n * The returned promise is what makes this WORK. Measured on the live server\n * 2026-08-27: issued fire-and-forget and followed by an immediate `callback()`,\n * not one of these writes ever reached the database \u2014 the process was gone first.\n * The caller has to wait for this before telling the host it is done \u2014 the host's\n * own deadline is the only limit there is.\n *\n * @returns resolves once every write has been acknowledged\n */\n public async markAllOffline(): Promise<void> {\n const writes = this.runtimes.flatMap(runtime => [\n this.deps.setStateChanged(`${runtime.config.id}.info.unreach`, true),\n this.deps.setStateChanged(`${runtime.config.id}.info.error`, REASON_UNKNOWN),\n ]);\n // The same lie one level up: \"accounts currently delivering data\" is zero.\n writes.push(this.deps.setStateChanged(\"total.accountsReachable\", 0));\n writes.push(this.deps.setStateChanged(\"info.connection\", false));\n await Promise.all(writes);\n }\n\n /**\n * Poll one account immediately, by id. Used after a successful sign-in: waiting\n * up to a full interval there reads as \"the sign-in did not work\".\n *\n * @param accountId the account's object id\n */\n public async pollNow(accountId: string): Promise<void> {\n const runtime = this.runtimes.find(entry => entry.config.id === accountId);\n if (runtime) {\n // A fresh sign-in clears a previous auth failure and any backoff.\n runtime.authNotified = false;\n runtime.skipUntil = 0;\n await this.pollAccount(runtime);\n }\n }\n\n /**\n * Poll one account now (also used by the staggered first run).\n *\n * Never two at once for the same account: a sign-in triggers an immediate poll,\n * which can land on top of a scheduled one \u2014 and two token refreshes in parallel\n * on a rotating refresh token sign each other out. A request that arrives while\n * one is running is remembered and runs right after, so nothing is lost.\n *\n * @param runtime the account's runtime\n */\n private async pollAccount(runtime: AccountRuntime): Promise<void> {\n if (this.stopped) {\n return;\n }\n if (runtime.polling) {\n runtime.pollAgain = true;\n return;\n }\n runtime.polling = true;\n try {\n await this.pollOnce(runtime);\n } finally {\n runtime.polling = false;\n }\n if (runtime.pollAgain && !this.stopped) {\n runtime.pollAgain = false;\n await this.pollAccount(runtime);\n }\n }\n\n /**\n * One poll of one account: fetch, classify, write.\n *\n * @param runtime the account's runtime\n */\n private async pollOnce(runtime: AccountRuntime): Promise<void> {\n const { config } = runtime;\n if (this.deps.now() < runtime.skipUntil) {\n this.deps.log.debug(`${config.name}: in rate-limit backoff \u2014 poll skipped`);\n // Still counts as \"been through\": otherwise an account that starts inside a\n // backoff would hold the first-round report back forever.\n runtime.firstPollDone = true;\n this.reportFirstRoundOnce();\n return;\n }\n try {\n const snapshot = await runtime.provider.fetch();\n runtime.failCount = 0;\n runtime.backoffMs = BACKOFF_START_MS;\n runtime.authNotified = false;\n runtime.status.snapshot = snapshot;\n runtime.status.reachable = true;\n runtime.serviceOnline = true;\n runtime.state = \"ok\";\n runtime.error = \"\";\n await this.applySnapshot(runtime, snapshot);\n } catch (e) {\n this.handleFailure(runtime, e);\n }\n this.writeAccountInfo(runtime);\n this.writeTotals();\n runtime.firstPollDone = true;\n this.reportFirstRoundOnce();\n }\n\n /** Fire the first-round hook exactly once, when no account is still pending. */\n private reportFirstRoundOnce(): void {\n if (this.firstRoundReported || this.runtimes.some(runtime => !runtime.firstPollDone)) {\n return;\n }\n this.firstRoundReported = true;\n this.deps.afterFirstRound?.();\n }\n\n /**\n * Write a successful snapshot: upsert new objects (create-once cache), write the\n * values, and run the warn-threshold transition.\n *\n * @param runtime the account's runtime\n * @param snapshot the fetched snapshot\n */\n private async applySnapshot(runtime: AccountRuntime, snapshot: UsageSnapshot): Promise<void> {\n const { config } = runtime;\n const { objects, writes } = mapSnapshot(config.id, snapshot);\n for (const object of objects) {\n if (!runtime.createdObjects.has(object.id)) {\n await this.deps.upsertObject(object);\n runtime.createdObjects.add(object.id);\n }\n }\n for (const write of writes) {\n this.deps.setState(write.id, write.value);\n }\n await this.removeVanished(\n runtime,\n writes.map(write => write.id),\n );\n // Only PLAN-WIDE windows speak for the account \u2014 a per-model bucket at 100 %\n // must not read as \"this AI is full\" (krobi 2026-08-26).\n const driver = limitingWindow(snapshot);\n const percent = driver?.percent ?? 0;\n const wasWarning = runtime.status.warning;\n runtime.status.warning = percent >= config.warnThreshold;\n // Indicators go through the changed-write, measurements through the normal one.\n void this.deps.setStateChanged(`${config.id}.warning`, runtime.status.warning);\n void this.deps.setStateChanged(`${config.id}.limitReached`, percent >= 100);\n if (runtime.status.warning && !wasWarning) {\n // Always name the window: \"usage at 100 %\" without it was misleading whenever\n // several windows existed.\n const window = driver ? `${driver.label} ` : \"\";\n const message = `${config.name}: ${window}at ${Math.round(percent)} % (threshold ${config.warnThreshold} %)`;\n this.deps.log.warn(message);\n this.deps.notify?.(config.name, message);\n }\n }\n\n /**\n * Delete what this account no longer delivers.\n *\n * The first round after a start compares against the DATABASE, so a window or\n * model that disappeared while the adapter was stopped is caught as well; every\n * round after that compares against the previous snapshot, which costs nothing.\n *\n * @param runtime the account's runtime\n * @param delivered the state ids this snapshot wrote\n */\n private async removeVanished(runtime: AccountRuntime, delivered: string[]): Promise<void> {\n const known = runtime.deliveredIds ?? (await this.deps.listStateIds(runtime.config.id));\n for (const id of orphanObjectIds(known, delivered, runtime.staticIds)) {\n await this.deps.deleteObject(id);\n runtime.createdObjects.delete(id);\n this.deps.log.info(`${runtime.config.name}: removed \"${id}\" \u2014 the provider no longer reports it`);\n }\n runtime.deliveredIds = delivered;\n }\n\n /**\n * Classify a fetch failure.\n *\n * The split matters for the online indicator: with `auth` and `rate-limit` the AI\n * service ANSWERED \u2014 it is online, it just said no \u2014 so only our own access is\n * broken. `service` means the service answered with a fault of its own and is\n * reported as down at once (it told us, that is not a flake). A `network` failure\n * is tolerated MAX_NETWORK_FAILURES times before we call the connection gone, so\n * a single hiccup does not make the indicator flap.\n *\n * @param runtime the account's runtime\n * @param error the thrown error\n */\n private handleFailure(runtime: AccountRuntime, error: unknown): void {\n const { config } = runtime;\n const message = error instanceof Error ? error.message : String(error);\n if (error instanceof FetchError && error.kind === \"auth\") {\n runtime.status.reachable = false;\n runtime.serviceOnline = true;\n runtime.state = \"unauthorized\";\n runtime.error = `Sign-in rejected \u2014 ${message}`;\n if (!runtime.authNotified) {\n runtime.authNotified = true;\n const text = `${config.name}: credentials rejected \u2014 ${message}`;\n this.deps.log.warn(text);\n this.deps.notify?.(config.name, text);\n }\n return;\n }\n if (error instanceof FetchError && error.kind === \"rate-limit\") {\n runtime.serviceOnline = true;\n runtime.state = \"rate-limited\";\n runtime.error = `Throttled by the provider \u2014 retrying in ${Math.round(runtime.backoffMs / 60000)} min, last values kept`;\n runtime.skipUntil = this.deps.now() + runtime.backoffMs;\n this.deps.log.warn(\n `${config.name}: rate-limited \u2014 backing off for ${Math.round(runtime.backoffMs / 60000)} min, keeping last values`,\n );\n runtime.backoffMs = Math.min(BACKOFF_MAX_MS, runtime.backoffMs * 2);\n return;\n }\n if (error instanceof FetchError && error.kind === \"service\") {\n runtime.status.reachable = false;\n runtime.failCount = 0;\n if (runtime.serviceOnline) {\n this.deps.log.warn(`${config.name}: the service reports a fault (${message}) \u2014 values kept`);\n }\n runtime.serviceOnline = false;\n runtime.state = \"service-down\";\n runtime.error = `The AI service reports a fault \u2014 ${message}`;\n return;\n }\n runtime.failCount++;\n this.deps.log.debug(`${config.name}: fetch failed (${message}), attempt ${runtime.failCount}`);\n if (runtime.failCount >= MAX_NETWORK_FAILURES) {\n runtime.status.reachable = false;\n if (runtime.serviceOnline) {\n this.deps.log.warn(`${config.name}: not reachable after ${runtime.failCount} attempts (${message})`);\n }\n runtime.serviceOnline = false;\n runtime.state = \"no-connection\";\n runtime.error = `Not reachable after ${runtime.failCount} attempts \u2014 ${message}`;\n }\n }\n\n /**\n * Write one account's info states (offline marker, error text, last update).\n *\n * @param runtime the account's runtime\n */\n private writeAccountInfo(runtime: AccountRuntime): void {\n const { config } = runtime;\n void this.writeAccountStatus(runtime);\n if (runtime.status.reachable) {\n this.deps.setState(`${config.id}.info.lastUpdate`, new Date(this.deps.now()).toISOString());\n }\n }\n\n /**\n * Write the two status states.\n *\n * `unreach` drives the connection icon the admin draws next to the account, so it\n * has to mean what a user reads into that icon: green while the account delivers.\n * A throttle keeps the last values and the service is fine, so it stays green and\n * only fills the error text; a dead sign-in, a broken service or no connection at\n * all turn it off. Both go through the changed-write: an indicator rewritten every\n * cycle floods the history and hides the real transition.\n *\n * @param runtime the account's runtime\n */\n private async writeAccountStatus(runtime: AccountRuntime): Promise<void> {\n const { config } = runtime;\n const delivering = runtime.state === \"ok\" || runtime.state === \"rate-limited\";\n await Promise.all([\n this.deps.setStateChanged(`${config.id}.info.unreach`, !delivering),\n this.deps.setStateChanged(`${config.id}.info.error`, runtime.error),\n ]);\n }\n\n /** Recompute and write the totals + info.connection. */\n private writeTotals(): void {\n const totals = computeTotals(\n this.runtimes.map(runtime => runtime.status),\n this.configuredAccounts,\n );\n this.deps.setState(\"total.costs.today\", totals.costsToday);\n this.deps.setState(\"total.costs.month\", totals.costsMonth);\n this.deps.setState(\"total.costs.projectedMonth\", totals.costsProjectedMonth);\n this.deps.setState(\"total.maxLimitPercent\", totals.maxLimitPercent);\n this.deps.setState(\"total.warningsActive\", totals.warningsActive);\n void this.deps.setStateChanged(\"total.limitReached\", totals.limitReached);\n this.deps.setState(\"total.accountsReachable\", totals.accountsReachable);\n // The configured count only ever changes with the configuration, which restarts\n // the instance \u2014 rewriting it every cycle would be pure noise in a recording.\n void this.deps.setStateChanged(\"total.accounts\", totals.accounts);\n void this.deps.setStateChanged(\"info.connection\", totals.accountsReachable > 0);\n }\n\n /**\n * The static per-account objects that exist regardless of what the source delivers.\n *\n * @param runtime the account's runtime\n */\n private async createAccountSkeleton(runtime: AccountRuntime): Promise<void> {\n const { config } = runtime;\n const defs: ObjectDef[] = [\n {\n id: config.id,\n type: \"device\",\n common: {\n name: `${config.name} (${config.provider})`,\n // The admin's object tree draws its connection icon from this link and\n // from nothing else \u2014 govee, beszel, homewizard and nut2 all do the same.\n statusStates: { offlineId: \"info.unreach\" },\n },\n },\n { id: `${config.id}.info`, type: \"channel\", common: { name: \"Info\" } },\n {\n // The two slots ioBroker itself provides for this \u2014 measured against\n // @iobroker/type-detector 6.0.0: `unreach` is the offline marker every\n // device type carries (`indicator.reachable` is deprecated there), and\n // `indicator.error` is the standard home for the reason.\n id: `${config.id}.info.unreach`,\n type: \"state\",\n common: {\n name: \"AI service not reachable\",\n type: \"boolean\",\n role: \"indicator.maintenance.unreach\",\n read: true,\n write: false,\n },\n },\n {\n // NOT `indicator.error`: the two official sources disagree \u2014 the type-detector\n // lists that role as a String, the repochecker's validity whitelist allows\n // boolean only (E1009). Validity wins, so the message rides on `text`.\n id: `${config.id}.info.error`,\n type: \"state\",\n common: { name: \"Last error\", type: \"string\", role: \"text\", read: true, write: false },\n },\n {\n id: `${config.id}.info.lastUpdate`,\n type: \"state\",\n common: { name: \"Last successful update\", type: \"string\", role: \"date\", read: true, write: false },\n },\n {\n id: `${config.id}.warning`,\n type: \"state\",\n common: { name: \"Above warn threshold\", type: \"boolean\", role: \"indicator\", read: true, write: false },\n },\n {\n id: `${config.id}.limitReached`,\n type: \"state\",\n common: {\n name: \"A plan-wide limit window is full\",\n type: \"boolean\",\n role: \"indicator\",\n read: true,\n write: false,\n },\n },\n ];\n for (const def of defs) {\n await this.deps.upsertObject(def);\n runtime.createdObjects.add(def.id);\n }\n runtime.staticIds = defs.filter(def => def.type === \"state\").map(def => def.id);\n // Mark it as not delivering right away, before anything has been asked.\n //\n // This looks pessimistic and is the honest state: nothing has been read yet. It\n // also carries the whole weight of \"the instance was off\". Whatever the previous\n // run left behind stands until someone overwrites it \u2014 after a hard kill, after\n // a crash, after an unclean shutdown the account would otherwise sit there green\n // and claim to deliver while no process exists at all. nut2 marks its devices\n // unreachable on start for exactly this reason.\n //\n // The window is short: the first poll follows within seconds and writes the real\n // state, success or failure. Only the marker \u2014 `info.error` belongs to the AI\n // service, and \"we have not asked yet\" is not something it said.\n void this.deps.setStateChanged(`${config.id}.info.unreach`, true);\n void this.deps.setStateChanged(`${config.id}.info.error`, REASON_UNKNOWN);\n }\n\n /** The totals skeleton (channel + states). */\n private async createTotalsSkeleton(): Promise<void> {\n const defs: ObjectDef[] = [\n { id: \"total.costs\", type: \"channel\", common: { name: \"Costs (USD accounts)\" } },\n {\n id: \"total.costs.today\",\n type: \"state\",\n common: {\n name: \"Costs today (all accounts)\",\n type: \"number\",\n role: \"value\",\n read: true,\n write: false,\n unit: \"USD\",\n },\n },\n {\n id: \"total.costs.month\",\n type: \"state\",\n common: {\n name: \"Costs this month (all accounts)\",\n type: \"number\",\n role: \"value\",\n read: true,\n write: false,\n unit: \"USD\",\n },\n },\n {\n id: \"total.costs.projectedMonth\",\n type: \"state\",\n common: {\n name: \"Costs projected month-end (computed)\",\n type: \"number\",\n role: \"value\",\n read: true,\n write: false,\n unit: \"USD\",\n },\n },\n {\n id: \"total.maxLimitPercent\",\n type: \"state\",\n common: {\n name: \"Highest plan-wide utilisation of any account\",\n type: \"number\",\n role: \"value\",\n read: true,\n write: false,\n unit: \"%\",\n },\n },\n {\n id: \"total.warningsActive\",\n type: \"state\",\n common: {\n name: \"Accounts above their warn threshold\",\n type: \"number\",\n role: \"value\",\n read: true,\n write: false,\n },\n },\n {\n id: \"total.limitReached\",\n type: \"state\",\n common: {\n name: \"Any plan-wide limit window full\",\n type: \"boolean\",\n role: \"indicator\",\n read: true,\n write: false,\n },\n },\n {\n id: \"total.accountsReachable\",\n type: \"state\",\n common: { name: \"Reachable accounts\", type: \"number\", role: \"value\", read: true, write: false },\n },\n {\n id: \"total.accounts\",\n type: \"state\",\n common: { name: \"Configured accounts\", type: \"number\", role: \"value\", read: true, write: false },\n },\n ];\n for (const def of defs) {\n await this.deps.upsertObject(def);\n }\n }\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|