kingkont 0.7.38 → 0.7.39

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.
Files changed (3) hide show
  1. package/index.html +29 -29
  2. package/package.json +1 -1
  3. package/server.js +50 -1
package/index.html CHANGED
@@ -1037,10 +1037,7 @@
1037
1037
  <div class="sidebar-list" id="episodeList"></div>
1038
1038
  </div>
1039
1039
  <div class="sidebar-footer">
1040
- <span id="balanceInfo" class="balance-info" style="display:none;" title="Баланс KingKont-аккаунта · клик — лог списаний" onclick="window.openTxLog && window.openTxLog()">
1041
- <span class="dot"></span>
1042
- <span id="balanceValue">— credits</span>
1043
- </span>
1040
+ <div id="balancesAll" style="display:flex; flex-direction:column; gap:4px;"></div>
1044
1041
  <span id="jobsInfo" class="jobs-info" style="display:none;"></span>
1045
1042
  <span class="hint">Перетаскивай файлы на холст · @имя для ссылок</span>
1046
1043
  </div>
@@ -2116,32 +2113,35 @@ window.addEventListener('DOMContentLoaded', async () => {
2116
2113
  // red (≤0). Также экспортирована глобально как window.refreshBalance —
2117
2114
  // чтобы settings-окно могло триггерить обновление после login/logout.
2118
2115
  async function refreshBalance() {
2119
- const pill = document.getElementById('balanceInfo');
2120
- const valueEl = document.getElementById('balanceValue');
2121
- if (!pill || !valueEl) return;
2116
+ const wrap = document.getElementById('balancesAll');
2117
+ if (!wrap) return;
2118
+ let data = {};
2122
2119
  try {
2123
- const s = await window.appSettings.get();
2124
- if (!s.useChatium || !s.chatium?.token) {
2125
- pill.style.display = 'none';
2126
- return;
2127
- }
2128
- const r = await fetch('/api/balance');
2129
- if (!r.ok) {
2130
- pill.style.display = '';
2131
- pill.classList.remove('low'); pill.classList.add('empty');
2132
- valueEl.textContent = `— credits (HTTP ${r.status})`;
2133
- return;
2134
- }
2135
- const d = await r.json();
2136
- const balance = Number(d.balance) || 0;
2137
- pill.style.display = '';
2138
- pill.classList.toggle('low', balance > 0 && balance < 100);
2139
- pill.classList.toggle('empty', balance <= 0);
2140
- valueEl.innerHTML = `<b>${balance.toLocaleString('ru-RU')}</b>&nbsp;credits`;
2141
- } catch (e) {
2142
- pill.style.display = '';
2143
- pill.classList.remove('low'); pill.classList.add('empty');
2144
- valueEl.textContent = `— credits (offline)`;
2120
+ const r = await fetch('/api/balance/all');
2121
+ if (r.ok) data = await r.json();
2122
+ } catch {}
2123
+ wrap.innerHTML = '';
2124
+ // Один pill на провайдер. Если у провайдера нет данных (выключен или
2125
+ // API не дал баланс) — pill не рендерим.
2126
+ const pills = [
2127
+ { key: 'kingkont', label: 'KingKont', onClick: () => window.openTxLog?.(), low: 100, fmt: (a) => `<b>${a.toLocaleString('ru-RU')}</b>&nbsp;credits` },
2128
+ { key: 'openrouter', label: 'OpenRouter', low: 0.5, fmt: (a) => `<b>$${a.toFixed(2)}</b>` },
2129
+ { key: 'elevenlabs', label: 'ElevenLabs', low: 1000, fmt: (a) => `<b>${a.toLocaleString('ru-RU')}</b>&nbsp;chars` },
2130
+ ];
2131
+ for (const p of pills) {
2132
+ const d = data[p.key];
2133
+ if (!d || typeof d.amount !== 'number') continue;
2134
+ const pill = document.createElement('span');
2135
+ pill.className = 'balance-info';
2136
+ pill.title = `Баланс ${p.label}` + (p.onClick ? ' · клик — лог списаний' : '');
2137
+ if (d.amount > 0 && d.amount < (p.low || 0)) pill.classList.add('low');
2138
+ if (d.amount <= 0) pill.classList.add('empty');
2139
+ pill.innerHTML = `<span class="dot"></span><span style="color:#888;font-size:10px;margin-right:4px;">${p.label}</span><span>${p.fmt(d.amount)}</span>`;
2140
+ if (p.onClick) {
2141
+ pill.style.cursor = 'pointer';
2142
+ pill.addEventListener('click', p.onClick);
2143
+ }
2144
+ wrap.appendChild(pill);
2145
2145
  }
2146
2146
  }
2147
2147
  window.refreshBalance = refreshBalance;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kingkont",
3
- "version": "0.7.38",
3
+ "version": "0.7.39",
4
4
  "description": "KingKont \u00b7 Chatium \u2014 \u043d\u043e\u0434-\u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440 \u0441\u0446\u0435\u043d \u0441 AI-\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0435\u0439 (\u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438/\u0432\u0438\u0434\u0435\u043e/\u0433\u043e\u043b\u043e\u0441/SFX/\u043c\u0443\u0437\u044b\u043a\u0430/\u0442\u0435\u043a\u0441\u0442)",
5
5
  "main": "main.js",
6
6
  "bin": {
package/server.js CHANGED
@@ -657,7 +657,7 @@ async function handleTransactions(req, res) {
657
657
  }
658
658
  }
659
659
 
660
- // ---------- /api/balance (только Chatium) ----------
660
+ // ---------- /api/balance (KingKont — legacy single-balance endpoint) ----------
661
661
  async function handleBalance(req, res) {
662
662
  const s = getSettings();
663
663
  if (!s.useChatium || !s.chatium?.token || !s.chatium?.base) {
@@ -682,6 +682,54 @@ async function handleBalance(req, res) {
682
682
  }
683
683
  }
684
684
 
685
+ // ---------- /api/balance/all (агрегированные балансы всех включённых провайдеров) ----------
686
+ async function handleBalanceAll(req, res) {
687
+ const s = getSettings();
688
+ const out = {};
689
+ // KingKont — credits.
690
+ if (s.useChatium && s.chatium?.token && s.chatium?.base) {
691
+ try {
692
+ const url = s.chatium.base.replace(/\/$/, '') + CHATIUM_PATHS.balance;
693
+ const r = await fetch(url, { headers: { 'Authorization': `Bearer ${s.chatium.token}` } });
694
+ const d = await r.json().catch(() => ({}));
695
+ if (r.ok && typeof d.balance === 'number') out.kingkont = { unit: 'credits', amount: d.balance };
696
+ } catch {}
697
+ }
698
+ // ElevenLabs — characters_left = limit - used.
699
+ if (s.useElevenlabs && process.env.ELEVENLABS_API_KEY) {
700
+ try {
701
+ const r = await fetch(`${ELEVEN_BASE}/v1/user/subscription`, {
702
+ headers: { 'xi-api-key': process.env.ELEVENLABS_API_KEY },
703
+ });
704
+ const d = await r.json().catch(() => ({}));
705
+ if (r.ok && typeof d.character_count === 'number' && typeof d.character_limit === 'number') {
706
+ out.elevenlabs = {
707
+ unit: 'chars',
708
+ amount: Math.max(0, d.character_limit - d.character_count),
709
+ limit: d.character_limit,
710
+ };
711
+ }
712
+ } catch {}
713
+ }
714
+ // OpenRouter — credits в USD.
715
+ if (s.useOpenrouter && process.env.OPENROUTER_API_KEY) {
716
+ try {
717
+ const r = await fetch('https://openrouter.ai/api/v1/credits', {
718
+ headers: { 'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}` },
719
+ });
720
+ const d = await r.json().catch(() => ({}));
721
+ if (r.ok && d.data && typeof d.data.total_credits === 'number') {
722
+ out.openrouter = {
723
+ unit: 'usd',
724
+ amount: Math.max(0, (d.data.total_credits || 0) - (d.data.total_usage || 0)),
725
+ };
726
+ }
727
+ } catch {}
728
+ }
729
+ // KIE — нет публичного balance-endpoint в их API; пропускаем.
730
+ send(res, 200, out);
731
+ }
732
+
685
733
  // ---------- /api/sfx (Chatium ИЛИ ElevenLabs Sound Effects) ----------
686
734
  async function handleSfx(req, res) {
687
735
  const { text, durationSeconds, promptInfluence = 0.3 } = await readJson(req);
@@ -885,6 +933,7 @@ const server = createServer(async (req, res) => {
885
933
  if (req.method === 'POST' && url.pathname === '/api/music') return handleMusic(req, res);
886
934
  if (req.method === 'POST' && url.pathname === '/api/text') return handleText(req, res);
887
935
  if (req.method === 'GET' && url.pathname === '/api/balance') return handleBalance(req, res);
936
+ if (req.method === 'GET' && url.pathname === '/api/balance/all') return handleBalanceAll(req, res);
888
937
  if (req.method === 'GET' && url.pathname === '/api/transactions') return handleTransactions(req, res);
889
938
  if (req.method === 'GET') return serveStatic(res, url);
890
939
  send(res, 404, 'not found');