kingkont 0.6.2 → 0.7.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/index.html +590 -108
- package/main.js +219 -4
- package/package.json +3 -1
- package/preload.js +13 -0
- package/server.js +492 -45
- package/settings.html +299 -0
package/settings.html
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="ru">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Настройки</title>
|
|
7
|
+
<style>
|
|
8
|
+
/* Тёмная тема под основное окно. */
|
|
9
|
+
:root {
|
|
10
|
+
--bg: #1a1a1a;
|
|
11
|
+
--card: #232323;
|
|
12
|
+
--border: #333;
|
|
13
|
+
--text: #e0e0e0;
|
|
14
|
+
--muted: #888;
|
|
15
|
+
--accent: #7c3aed;
|
|
16
|
+
--accent-hover: #6d28d9;
|
|
17
|
+
--danger: #dc2626;
|
|
18
|
+
--ok: #16a34a;
|
|
19
|
+
}
|
|
20
|
+
* { box-sizing: border-box; }
|
|
21
|
+
html, body { height: 100%; }
|
|
22
|
+
body {
|
|
23
|
+
margin: 0;
|
|
24
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
25
|
+
background: var(--bg);
|
|
26
|
+
color: var(--text);
|
|
27
|
+
font-size: 13px;
|
|
28
|
+
-webkit-app-region: drag;
|
|
29
|
+
}
|
|
30
|
+
.wrap { padding: 20px; max-height: 100vh; overflow: auto; -webkit-app-region: no-drag; }
|
|
31
|
+
h1 { margin: 0 0 16px 0; font-size: 18px; font-weight: 600; }
|
|
32
|
+
h2 { margin: 24px 0 8px 0; font-size: 13px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--muted); font-weight: 600; }
|
|
33
|
+
.card { background: var(--card); border: 1px solid var(--border); border-radius: 10px; padding: 16px; margin-bottom: 12px; transition: opacity 0.15s; }
|
|
34
|
+
.card.disabled { opacity: 0.55; }
|
|
35
|
+
|
|
36
|
+
label { display: block; margin-bottom: 6px; font-size: 12px; color: var(--muted); }
|
|
37
|
+
input[type="password"], input[type="text"] {
|
|
38
|
+
width: 100%; padding: 8px 10px; font-size: 13px;
|
|
39
|
+
background: #161616; color: var(--text); border: 1px solid var(--border);
|
|
40
|
+
border-radius: 6px; outline: none; font-family: ui-monospace, monospace;
|
|
41
|
+
}
|
|
42
|
+
input:focus { border-color: var(--accent); }
|
|
43
|
+
.row { display: flex; gap: 8px; align-items: center; margin-bottom: 12px; }
|
|
44
|
+
.row:last-child { margin-bottom: 0; }
|
|
45
|
+
.row > div { flex: 1; }
|
|
46
|
+
|
|
47
|
+
button {
|
|
48
|
+
padding: 7px 14px; font-size: 13px; font-weight: 500;
|
|
49
|
+
background: var(--accent); color: #fff; border: 0; border-radius: 6px;
|
|
50
|
+
cursor: pointer; transition: background 0.15s;
|
|
51
|
+
}
|
|
52
|
+
button:hover { background: var(--accent-hover); }
|
|
53
|
+
button.secondary { background: #333; color: var(--text); }
|
|
54
|
+
button.secondary:hover { background: #444; }
|
|
55
|
+
button.danger { background: var(--danger); }
|
|
56
|
+
button.danger:hover { background: #b91c1c; }
|
|
57
|
+
button:disabled { opacity: 0.5; cursor: wait; }
|
|
58
|
+
|
|
59
|
+
.actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--border); }
|
|
60
|
+
.hint { font-size: 11px; color: var(--muted); margin-top: 6px; line-height: 1.5; }
|
|
61
|
+
|
|
62
|
+
/* «Использовать» — toggle над блоком */
|
|
63
|
+
.use-toggle {
|
|
64
|
+
display: flex; align-items: center; gap: 10px; padding: 10px 12px;
|
|
65
|
+
background: #1c1c1c; border: 1px solid var(--border); border-radius: 8px;
|
|
66
|
+
margin-bottom: 12px; cursor: pointer; user-select: none;
|
|
67
|
+
}
|
|
68
|
+
.use-toggle input[type="checkbox"] {
|
|
69
|
+
width: 16px; height: 16px; accent-color: var(--accent); cursor: pointer; margin: 0;
|
|
70
|
+
}
|
|
71
|
+
.use-toggle .label-text { font-size: 13px; color: var(--text); font-weight: 500; }
|
|
72
|
+
.use-toggle .sub { color: var(--muted); font-weight: 400; }
|
|
73
|
+
|
|
74
|
+
/* Provider-row: компактный заголовок коннектора с галочкой */
|
|
75
|
+
.provider-head {
|
|
76
|
+
display: flex; align-items: center; gap: 10px; margin-bottom: 6px; cursor: pointer;
|
|
77
|
+
user-select: none; padding: 0; font-weight: 500; color: var(--text); font-size: 13px;
|
|
78
|
+
}
|
|
79
|
+
.provider-head input[type="checkbox"] {
|
|
80
|
+
width: 14px; height: 14px; accent-color: var(--accent); cursor: pointer; margin: 0;
|
|
81
|
+
}
|
|
82
|
+
.provider-head .badge { color: var(--muted); font-weight: 400; font-size: 11px; }
|
|
83
|
+
.provider-block { transition: opacity 0.15s; }
|
|
84
|
+
.provider-block.disabled { opacity: 0.45; pointer-events: none; }
|
|
85
|
+
|
|
86
|
+
/* Chatium card */
|
|
87
|
+
.chatium-state { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
|
|
88
|
+
.dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
|
89
|
+
.dot.on { background: var(--ok); box-shadow: 0 0 0 3px rgba(22,163,74,0.18); }
|
|
90
|
+
.dot.off { background: #555; }
|
|
91
|
+
.chatium-state .who { flex: 1; }
|
|
92
|
+
.chatium-state .who b { display: block; font-size: 13px; color: var(--text); }
|
|
93
|
+
.chatium-state .who small { font-size: 11px; color: var(--muted); font-family: ui-monospace, monospace; }
|
|
94
|
+
.error { color: var(--danger); font-size: 12px; margin-top: 8px; }
|
|
95
|
+
|
|
96
|
+
.saved { color: var(--ok); font-size: 12px; opacity: 0; transition: opacity 0.3s; }
|
|
97
|
+
.saved.show { opacity: 1; }
|
|
98
|
+
</style>
|
|
99
|
+
</head>
|
|
100
|
+
<body>
|
|
101
|
+
<div class="wrap">
|
|
102
|
+
<h1>Настройки</h1>
|
|
103
|
+
|
|
104
|
+
<h2>Chatium</h2>
|
|
105
|
+
<div class="card" id="chatiumCard">
|
|
106
|
+
<label class="use-toggle" for="useChatium">
|
|
107
|
+
<input type="checkbox" id="useChatium" />
|
|
108
|
+
<span class="label-text">Использовать Chatium <span class="sub">— текст / аудио / видео через kingkont.ru</span></span>
|
|
109
|
+
</label>
|
|
110
|
+
<div class="chatium-state">
|
|
111
|
+
<div class="dot off" id="chatiumDot"></div>
|
|
112
|
+
<div class="who" id="chatiumWho">
|
|
113
|
+
<b>Не подключено</b>
|
|
114
|
+
<small>Авторизация нужна для генерации текста, аудио и видео через Chatium.</small>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
<div class="row">
|
|
118
|
+
<button id="chatiumLogin">Войти в Chatium</button>
|
|
119
|
+
<button id="chatiumLogout" class="danger" style="display:none;">Выйти</button>
|
|
120
|
+
<button id="chatiumRefresh" class="secondary">Проверить</button>
|
|
121
|
+
</div>
|
|
122
|
+
<div class="error" id="chatiumError" style="display:none;"></div>
|
|
123
|
+
<div class="hint">Откроется браузер для входа в Chatium. После подтверждения авторизация сохранится локально и будет использоваться приложением для запросов к нейросетям.</div>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
<h2>Прямые коннекторы</h2>
|
|
127
|
+
<div class="card">
|
|
128
|
+
<div class="provider-block" id="openrouterBlock">
|
|
129
|
+
<label class="provider-head" for="useOpenrouter">
|
|
130
|
+
<input type="checkbox" id="useOpenrouter" />
|
|
131
|
+
<span>OpenRouter</span>
|
|
132
|
+
<span class="badge">— текст</span>
|
|
133
|
+
</label>
|
|
134
|
+
<input type="password" id="openrouterKey" placeholder="sk-or-v1-..." autocomplete="off" />
|
|
135
|
+
<div class="hint">openrouter.ai → Keys</div>
|
|
136
|
+
</div>
|
|
137
|
+
<div class="provider-block" id="elevenBlock" style="margin-top:18px;">
|
|
138
|
+
<label class="provider-head" for="useElevenlabs">
|
|
139
|
+
<input type="checkbox" id="useElevenlabs" />
|
|
140
|
+
<span>ElevenLabs</span>
|
|
141
|
+
<span class="badge">— голос / SFX / музыка</span>
|
|
142
|
+
</label>
|
|
143
|
+
<input type="password" id="elevenKey" placeholder="sk_..." autocomplete="off" />
|
|
144
|
+
<div class="hint">elevenlabs.io → Profile → API Keys</div>
|
|
145
|
+
</div>
|
|
146
|
+
<div class="provider-block" id="kieBlock" style="margin-top:18px;">
|
|
147
|
+
<label class="provider-head" for="useKie">
|
|
148
|
+
<input type="checkbox" id="useKie" />
|
|
149
|
+
<span>KIE</span>
|
|
150
|
+
<span class="badge">— картинки / видео</span>
|
|
151
|
+
</label>
|
|
152
|
+
<input type="password" id="kieKey" placeholder="..." autocomplete="off" />
|
|
153
|
+
<div class="hint">api.kie.ai</div>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
<div class="actions">
|
|
158
|
+
<span class="saved" id="saved">Сохранено</span>
|
|
159
|
+
<button class="secondary" id="cancel">Закрыть</button>
|
|
160
|
+
<button id="save">Сохранить</button>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
|
|
164
|
+
<script>
|
|
165
|
+
const $ = (id) => document.getElementById(id);
|
|
166
|
+
|
|
167
|
+
// Дефолт для use*-флагов: если в settings нет — true (back-compat для старых
|
|
168
|
+
// users, которые до этой версии просто пользовались всеми коннекторами).
|
|
169
|
+
const useDefault = (v) => (v === undefined || v === null) ? true : !!v;
|
|
170
|
+
|
|
171
|
+
// ---------- load ----------
|
|
172
|
+
async function loadSettings() {
|
|
173
|
+
const s = await window.appSettings.get();
|
|
174
|
+
|
|
175
|
+
$('openrouterKey').value = s.openrouterKey || '';
|
|
176
|
+
$('elevenKey').value = s.elevenKey || '';
|
|
177
|
+
$('kieKey').value = s.kieKey || '';
|
|
178
|
+
|
|
179
|
+
$('useChatium').checked = useDefault(s.useChatium);
|
|
180
|
+
$('useOpenrouter').checked = useDefault(s.useOpenrouter);
|
|
181
|
+
$('useElevenlabs').checked = useDefault(s.useElevenlabs);
|
|
182
|
+
$('useKie').checked = useDefault(s.useKie);
|
|
183
|
+
|
|
184
|
+
// Применяем visual-disabled к блокам с выключенным toggle
|
|
185
|
+
syncProviderVisuals();
|
|
186
|
+
|
|
187
|
+
renderChatiumState(s.chatium || null);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function syncProviderVisuals() {
|
|
191
|
+
$('chatiumCard').classList.toggle('disabled', !$('useChatium').checked);
|
|
192
|
+
$('openrouterBlock').classList.toggle('disabled', !$('useOpenrouter').checked);
|
|
193
|
+
$('elevenBlock').classList.toggle('disabled', !$('useElevenlabs').checked);
|
|
194
|
+
$('kieBlock').classList.toggle('disabled', !$('useKie').checked);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Перерендериваем visual каждый раз когда чекбокс меняется. Само сохранение —
|
|
198
|
+
// по кнопке «Сохранить», как и текстовые поля.
|
|
199
|
+
['useChatium','useOpenrouter','useElevenlabs','useKie'].forEach(id => {
|
|
200
|
+
$(id).addEventListener('change', syncProviderVisuals);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
$('save').addEventListener('click', async () => {
|
|
204
|
+
await window.appSettings.save({
|
|
205
|
+
openrouterKey: $('openrouterKey').value.trim() || null,
|
|
206
|
+
elevenKey: $('elevenKey').value.trim() || null,
|
|
207
|
+
kieKey: $('kieKey').value.trim() || null,
|
|
208
|
+
useChatium: $('useChatium').checked,
|
|
209
|
+
useOpenrouter: $('useOpenrouter').checked,
|
|
210
|
+
useElevenlabs: $('useElevenlabs').checked,
|
|
211
|
+
useKie: $('useKie').checked,
|
|
212
|
+
});
|
|
213
|
+
const saved = $('saved');
|
|
214
|
+
saved.classList.add('show');
|
|
215
|
+
setTimeout(() => saved.classList.remove('show'), 1500);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
$('cancel').addEventListener('click', () => window.appSettings.closeSettingsWindow());
|
|
219
|
+
|
|
220
|
+
// ---------- Chatium ----------
|
|
221
|
+
function renderChatiumState(chatium) {
|
|
222
|
+
const dot = $('chatiumDot');
|
|
223
|
+
const who = $('chatiumWho');
|
|
224
|
+
const loginBtn = $('chatiumLogin');
|
|
225
|
+
const logoutBtn = $('chatiumLogout');
|
|
226
|
+
|
|
227
|
+
if (chatium && chatium.token) {
|
|
228
|
+
dot.classList.remove('off'); dot.classList.add('on');
|
|
229
|
+
who.innerHTML = `<b>Подключено</b><small>${escapeHtml(chatium.userId || '')} · ${escapeHtml(chatium.base || '')}</small>`;
|
|
230
|
+
loginBtn.style.display = 'none';
|
|
231
|
+
logoutBtn.style.display = '';
|
|
232
|
+
} else {
|
|
233
|
+
dot.classList.remove('on'); dot.classList.add('off');
|
|
234
|
+
who.innerHTML = `<b>Не подключено</b><small>Авторизация нужна для генерации текста, аудио и видео через Chatium.</small>`;
|
|
235
|
+
loginBtn.style.display = '';
|
|
236
|
+
logoutBtn.style.display = 'none';
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function setError(msg) {
|
|
241
|
+
const el = $('chatiumError');
|
|
242
|
+
if (!msg) { el.style.display = 'none'; el.textContent = ''; return; }
|
|
243
|
+
el.style.display = ''; el.textContent = msg;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function setBusy(busy) {
|
|
247
|
+
['chatiumLogin','chatiumLogout','chatiumRefresh'].forEach(id => $(id).disabled = busy);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
$('chatiumLogin').addEventListener('click', async () => {
|
|
251
|
+
setError(''); setBusy(true);
|
|
252
|
+
try {
|
|
253
|
+
await window.appChatium.login();
|
|
254
|
+
const s = await window.appSettings.get();
|
|
255
|
+
renderChatiumState(s.chatium || null);
|
|
256
|
+
} catch (e) {
|
|
257
|
+
setError(e?.message || String(e));
|
|
258
|
+
} finally {
|
|
259
|
+
setBusy(false);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
$('chatiumLogout').addEventListener('click', async () => {
|
|
264
|
+
setError(''); setBusy(true);
|
|
265
|
+
try {
|
|
266
|
+
await window.appChatium.logout();
|
|
267
|
+
const s = await window.appSettings.get();
|
|
268
|
+
renderChatiumState(s.chatium || null);
|
|
269
|
+
} catch (e) {
|
|
270
|
+
setError(e?.message || String(e));
|
|
271
|
+
} finally {
|
|
272
|
+
setBusy(false);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
$('chatiumRefresh').addEventListener('click', async () => {
|
|
277
|
+
setError(''); setBusy(true);
|
|
278
|
+
try {
|
|
279
|
+
const r = await window.appChatium.status();
|
|
280
|
+
if (!r.connected && r.reason === 'expired') {
|
|
281
|
+
setError('Токен отозван или истёк. Войдите снова.');
|
|
282
|
+
} else if (!r.connected && r.reason) {
|
|
283
|
+
setError('Не удалось проверить: ' + r.reason);
|
|
284
|
+
}
|
|
285
|
+
const s = await window.appSettings.get();
|
|
286
|
+
renderChatiumState(s.chatium || null);
|
|
287
|
+
} finally {
|
|
288
|
+
setBusy(false);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
function escapeHtml(s) {
|
|
293
|
+
return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
loadSettings();
|
|
297
|
+
</script>
|
|
298
|
+
</body>
|
|
299
|
+
</html>
|