kingkont 0.9.1 → 0.9.2
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/index.html +7 -0
- package/package.json +1 -1
- package/renderer/board.js +74 -18
- package/renderer/styles.css +34 -0
package/index.html
CHANGED
|
@@ -70,6 +70,13 @@
|
|
|
70
70
|
|
|
71
71
|
<!-- Welcome-экран: виден только когда body.no-project -->
|
|
72
72
|
<div class="welcome" id="welcome">
|
|
73
|
+
<!-- Топ-правый угол: identity + балансы. Позиционирование absolute,
|
|
74
|
+
чтобы не ехало с центрированной шапкой. Заполняется renderWelcomeStatus
|
|
75
|
+
(board.js) при каждом показе welcome И при auth-changed/balance-tick. -->
|
|
76
|
+
<div class="welcome-status" id="welcomeStatus" style="-webkit-app-region: no-drag;">
|
|
77
|
+
<div class="welcome-status-balances" id="welcomeStatusBalances"></div>
|
|
78
|
+
<div class="welcome-status-identity" id="welcomeStatusIdentity"></div>
|
|
79
|
+
</div>
|
|
73
80
|
<div class="welcome-inner">
|
|
74
81
|
<div class="welcome-header">
|
|
75
82
|
<img class="welcome-logo" src="assets/icon.png" alt="" draggable="false" id="welcomeLogo" title="Дабл-клик — настройки" style="cursor:pointer;">
|
package/package.json
CHANGED
package/renderer/board.js
CHANGED
|
@@ -50,8 +50,11 @@ window.addEventListener('DOMContentLoaded', async () => {
|
|
|
50
50
|
// Сбрасываем кэш — он содержал данные старого юзера.
|
|
51
51
|
localStorage.removeItem('cloudProjectsCache');
|
|
52
52
|
localStorage.removeItem('cloudProjectsLastOpened');
|
|
53
|
-
//
|
|
54
|
-
//
|
|
53
|
+
// Welcome top-right identity-pill + balances — перерисовываем сразу
|
|
54
|
+
// (даже если открыт проект и welcome скрыт — обновится для следующего показа).
|
|
55
|
+
renderWelcomeIdentity().catch(() => {});
|
|
56
|
+
refreshBalance().catch(() => {});
|
|
57
|
+
// Если открыт welcome (нет проекта) — перерисовать grid.
|
|
55
58
|
if (!state || !window.cloudProjects) return;
|
|
56
59
|
if (window.cloudProjects.setVisibility) window.cloudProjects.setVisibility();
|
|
57
60
|
if (!document.body.classList.contains('no-project')) return;
|
|
@@ -162,14 +165,11 @@ window.addEventListener('DOMContentLoaded', async () => {
|
|
|
162
165
|
// red (≤0). Также экспортирована глобально как window.refreshBalance —
|
|
163
166
|
// чтобы settings-окно могло триггерить обновление после login/logout.
|
|
164
167
|
async function refreshBalance() {
|
|
165
|
-
const wrap = document.getElementById('balancesAll');
|
|
166
|
-
if (!wrap) return;
|
|
167
168
|
let data = {};
|
|
168
169
|
try {
|
|
169
170
|
const r = await fetch('/api/balance/all');
|
|
170
171
|
if (r.ok) data = await r.json();
|
|
171
172
|
} catch {}
|
|
172
|
-
wrap.innerHTML = '';
|
|
173
173
|
// Один pill на провайдер. Если у провайдера нет данных (выключен или
|
|
174
174
|
// API не дал баланс) — pill не рендерим.
|
|
175
175
|
const pills = [
|
|
@@ -178,24 +178,78 @@ async function refreshBalance() {
|
|
|
178
178
|
{ key: 'openrouter', label: 'OpenRouter', low: 0.5, fmt: (a) => `<b>$${a.toFixed(2)}</b>` },
|
|
179
179
|
{ key: 'elevenlabs', label: 'ElevenLabs', low: 1000, fmt: (a) => `<b>${a.toLocaleString('ru-RU')}</b> chars` },
|
|
180
180
|
];
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
pill.
|
|
192
|
-
pill.
|
|
181
|
+
function renderInto(wrap) {
|
|
182
|
+
if (!wrap) return;
|
|
183
|
+
wrap.innerHTML = '';
|
|
184
|
+
for (const p of pills) {
|
|
185
|
+
const d = data[p.key];
|
|
186
|
+
if (!d || typeof d.amount !== 'number') continue;
|
|
187
|
+
const pill = document.createElement('span');
|
|
188
|
+
pill.className = 'balance-info';
|
|
189
|
+
pill.title = `Баланс ${p.label}` + (p.onClick ? ' · клик — лог списаний' : '');
|
|
190
|
+
if (d.amount > 0 && d.amount < (p.low || 0)) pill.classList.add('low');
|
|
191
|
+
if (d.amount <= 0) pill.classList.add('empty');
|
|
192
|
+
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>`;
|
|
193
|
+
if (p.onClick) {
|
|
194
|
+
pill.style.cursor = 'pointer';
|
|
195
|
+
pill.addEventListener('click', p.onClick);
|
|
196
|
+
}
|
|
197
|
+
wrap.appendChild(pill);
|
|
193
198
|
}
|
|
194
|
-
wrap.appendChild(pill);
|
|
195
199
|
}
|
|
200
|
+
// Sidebar-footer (когда открыт проект).
|
|
201
|
+
renderInto(document.getElementById('balancesAll'));
|
|
202
|
+
// Welcome top-right (когда no-project).
|
|
203
|
+
renderInto(document.getElementById('welcomeStatusBalances'));
|
|
196
204
|
}
|
|
197
205
|
window.refreshBalance = refreshBalance;
|
|
198
206
|
|
|
207
|
+
// Identity-pill в правом верхнем углу welcome-экрана.
|
|
208
|
+
// Показывает кто залогинен в KingKont (или предлагает войти).
|
|
209
|
+
async function renderWelcomeIdentity() {
|
|
210
|
+
const wrap = document.getElementById('welcomeStatusIdentity');
|
|
211
|
+
if (!wrap) return;
|
|
212
|
+
wrap.innerHTML = '';
|
|
213
|
+
let status = null;
|
|
214
|
+
try { status = await window.appChatium?.status?.(); } catch {}
|
|
215
|
+
if (status?.connected) {
|
|
216
|
+
// Имя: предпочитаем display-name (login/name из auth~me), fallback — userId.
|
|
217
|
+
const name = status.login || status.name || status.userId || 'KingKont';
|
|
218
|
+
const sub = status.userId && status.userId !== name ? `· ${status.userId.slice(0, 8)}` : '';
|
|
219
|
+
wrap.innerHTML = `
|
|
220
|
+
<span style="color:#5c5; font-size:13px; line-height:1;">●</span>
|
|
221
|
+
<span class="who">${escapeHtml(name)}</span>
|
|
222
|
+
<span class="who-sub">${escapeHtml(sub)}</span>
|
|
223
|
+
`;
|
|
224
|
+
const logoutBtn = document.createElement('button');
|
|
225
|
+
logoutBtn.textContent = 'Выйти';
|
|
226
|
+
logoutBtn.title = 'Logout из KingKont';
|
|
227
|
+
logoutBtn.addEventListener('click', async () => {
|
|
228
|
+
if (!confirm('Выйти из KingKont?')) return;
|
|
229
|
+
try { await window.appChatium?.logout?.(); } catch {}
|
|
230
|
+
});
|
|
231
|
+
wrap.appendChild(logoutBtn);
|
|
232
|
+
} else {
|
|
233
|
+
wrap.innerHTML = `
|
|
234
|
+
<span style="color:#888; font-size:13px; line-height:1;">●</span>
|
|
235
|
+
<span class="who" style="color:#aaa;">Не залогинен</span>
|
|
236
|
+
`;
|
|
237
|
+
const loginBtn = document.createElement('button');
|
|
238
|
+
loginBtn.textContent = 'Войти';
|
|
239
|
+
loginBtn.title = 'Войти в KingKont';
|
|
240
|
+
loginBtn.addEventListener('click', async () => {
|
|
241
|
+
try { await window.appChatium?.login?.(); } catch (e) { alert('Login failed: ' + (e?.message || e)); }
|
|
242
|
+
});
|
|
243
|
+
wrap.appendChild(loginBtn);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
window.renderWelcomeIdentity = renderWelcomeIdentity;
|
|
247
|
+
function escapeHtml(s) {
|
|
248
|
+
return String(s || '').replace(/[&<>"']/g, c => ({
|
|
249
|
+
'&':'&','<':'<','>':'>','"':'"',"'":''',
|
|
250
|
+
}[c]));
|
|
251
|
+
}
|
|
252
|
+
|
|
199
253
|
// === Лог списаний (модал с историей кредитов) ===
|
|
200
254
|
async function openTxLog() {
|
|
201
255
|
const modal = document.getElementById('txLogModal');
|
|
@@ -397,6 +451,8 @@ async function renderWelcomeRecents() {
|
|
|
397
451
|
if (!grid || !wrap) return;
|
|
398
452
|
grid.innerHTML = '';
|
|
399
453
|
wrap.style.display = '';
|
|
454
|
+
// Identity-pill в правом верхнем — обновляем при каждом показе welcome.
|
|
455
|
+
renderWelcomeIdentity().catch(() => {});
|
|
400
456
|
const list = await getRecents();
|
|
401
457
|
|
|
402
458
|
// Первой картой — «Открыть проект». Кликается → дёргает скрытый
|
package/renderer/styles.css
CHANGED
|
@@ -213,6 +213,40 @@
|
|
|
213
213
|
vertical-align: middle; margin-left: 6px;
|
|
214
214
|
}
|
|
215
215
|
.welcome-sub { font-size: 12px; color: #888; letter-spacing: 0.5px; text-transform: uppercase; }
|
|
216
|
+
/* Топ-правый блок welcome-экрана: identity + balances. Положение fixed,
|
|
217
|
+
чтобы не зависеть от центрированной .welcome-inner колонки. */
|
|
218
|
+
.welcome-status {
|
|
219
|
+
position: fixed; top: 16px; right: 24px; z-index: 60;
|
|
220
|
+
display: flex; flex-direction: column; align-items: flex-end; gap: 6px;
|
|
221
|
+
pointer-events: none; /* контейнер прозрачен, дочерние сами включают pointer-events */
|
|
222
|
+
}
|
|
223
|
+
.welcome-status-identity {
|
|
224
|
+
pointer-events: auto;
|
|
225
|
+
display: flex; align-items: center; gap: 8px;
|
|
226
|
+
background: rgba(30, 32, 40, 0.7); border: 1px solid #333;
|
|
227
|
+
border-radius: 999px; padding: 4px 10px;
|
|
228
|
+
font-size: 12px; color: #ccc;
|
|
229
|
+
backdrop-filter: blur(6px);
|
|
230
|
+
}
|
|
231
|
+
.welcome-status-identity .who {
|
|
232
|
+
color: #e0e0e0; font-weight: 500; max-width: 240px;
|
|
233
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
234
|
+
}
|
|
235
|
+
.welcome-status-identity .who-sub {
|
|
236
|
+
color: #888; font-size: 10px;
|
|
237
|
+
font-family: ui-monospace, 'SF Mono', monospace;
|
|
238
|
+
}
|
|
239
|
+
.welcome-status-identity button {
|
|
240
|
+
background: transparent; border: none; color: #9ab;
|
|
241
|
+
font-size: 11px; cursor: pointer; padding: 2px 6px; border-radius: 4px;
|
|
242
|
+
}
|
|
243
|
+
.welcome-status-identity button:hover { background: rgba(255,255,255,0.06); color: #cde; }
|
|
244
|
+
.welcome-status-balances {
|
|
245
|
+
pointer-events: auto;
|
|
246
|
+
display: flex; flex-direction: row; gap: 6px; flex-wrap: wrap;
|
|
247
|
+
justify-content: flex-end;
|
|
248
|
+
}
|
|
249
|
+
/* Используем те же .balance-info pill'ы что и в sidebar-footer'е. */
|
|
216
250
|
.welcome-open {
|
|
217
251
|
margin-top: 16px;
|
|
218
252
|
padding: 12px 28px; font-size: 15px; font-weight: 600;
|