kingkont 0.18.1 → 0.18.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/lib/providers.js +14 -1
- package/package.json +1 -1
- package/renderer/cloudFs.js +23 -2
- package/renderer/generate.js +42 -7
- package/renderer/state.js +5 -1
package/lib/providers.js
CHANGED
|
@@ -515,7 +515,20 @@ async function generateText({ prompt, messages: msgsIn, model = 'anthropic/claud
|
|
|
515
515
|
});
|
|
516
516
|
const text = await r.text();
|
|
517
517
|
let data; try { data = JSON.parse(text); } catch { data = { raw: text }; }
|
|
518
|
-
if (!r.ok)
|
|
518
|
+
if (!r.ok) {
|
|
519
|
+
// OpenRouter заворачивает upstream-ошибки в {error:{message, metadata:{provider_name, raw}}}.
|
|
520
|
+
// Без metadata.raw юзер видит generic «Provider returned error» — бесполезно.
|
|
521
|
+
const baseMsg = data?.error?.message || data?.raw || `HTTP ${r.status}`;
|
|
522
|
+
const meta = data?.error?.metadata || {};
|
|
523
|
+
const provName = meta.provider_name ? ` [${meta.provider_name}]` : '';
|
|
524
|
+
let extra = '';
|
|
525
|
+
if (meta.raw) {
|
|
526
|
+
const rawStr = typeof meta.raw === 'string' ? meta.raw : JSON.stringify(meta.raw);
|
|
527
|
+
extra = ` — ${rawStr.slice(0, 400)}`;
|
|
528
|
+
}
|
|
529
|
+
console.error('[OpenRouter error]', JSON.stringify(data, null, 2).slice(0, 2000));
|
|
530
|
+
throw new Error(`${baseMsg}${provName}${extra}`);
|
|
531
|
+
}
|
|
519
532
|
return {
|
|
520
533
|
text: data?.choices?.[0]?.message?.content || '',
|
|
521
534
|
model: data?.model || model,
|
package/package.json
CHANGED
package/renderer/cloudFs.js
CHANGED
|
@@ -53,6 +53,24 @@
|
|
|
53
53
|
const i = n.lastIndexOf('/');
|
|
54
54
|
return i < 0 ? { dir: '', name: n } : { dir: n.slice(0, i), name: n.slice(i + 1) };
|
|
55
55
|
}
|
|
56
|
+
// MIME-detection по расширению. Нужен в getFile() — нативный FSAH ставит
|
|
57
|
+
// type из расширения, наш shim строил Blob без type → readAsDataURL давал
|
|
58
|
+
// битый `data:base64,...` → Anthropic/OpenRouter reject image_url.
|
|
59
|
+
const _MIME_MAP = {
|
|
60
|
+
jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png', webp: 'image/webp',
|
|
61
|
+
gif: 'image/gif', avif: 'image/avif', heic: 'image/heic', heif: 'image/heif',
|
|
62
|
+
bmp: 'image/bmp', svg: 'image/svg+xml',
|
|
63
|
+
mp4: 'video/mp4', mov: 'video/quicktime', webm: 'video/webm', mkv: 'video/x-matroska',
|
|
64
|
+
mp3: 'audio/mpeg', wav: 'audio/wav', ogg: 'audio/ogg', m4a: 'audio/mp4',
|
|
65
|
+
flac: 'audio/flac', aac: 'audio/aac',
|
|
66
|
+
json: 'application/json', md: 'text/markdown', txt: 'text/plain',
|
|
67
|
+
pdf: 'application/pdf',
|
|
68
|
+
};
|
|
69
|
+
function _mimeFromName(name) {
|
|
70
|
+
const m = String(name || '').match(/\.([a-zA-Z0-9]+)$/);
|
|
71
|
+
if (!m) return '';
|
|
72
|
+
return _MIME_MAP[m[1].toLowerCase()] || '';
|
|
73
|
+
}
|
|
56
74
|
|
|
57
75
|
// === Backend factories. =====================================================
|
|
58
76
|
// Возвращает объект с минимальным API над хранилищем (диск или память).
|
|
@@ -209,8 +227,11 @@
|
|
|
209
227
|
const buf = await backend.read(relPath);
|
|
210
228
|
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf);
|
|
211
229
|
const stat = await backend.stat(relPath);
|
|
212
|
-
//
|
|
213
|
-
|
|
230
|
+
// КРИТИЧНО: Blob БЕЗ type → file.type='' → FileReader.readAsDataURL
|
|
231
|
+
// даёт `data:base64,...` без MIME → Anthropic/OpenRouter reject
|
|
232
|
+
// image_url. Нативный FSAH ставит type из расширения, мимикрируем.
|
|
233
|
+
const type = _mimeFromName(_name);
|
|
234
|
+
const blob = new Blob([u8], type ? { type } : undefined);
|
|
214
235
|
return Object.assign(blob, {
|
|
215
236
|
name: _name,
|
|
216
237
|
lastModified: stat?.mtimeMs ?? Date.now(),
|
package/renderer/generate.js
CHANGED
|
@@ -492,6 +492,20 @@ $('textGenSubmit').addEventListener('click', async () => {
|
|
|
492
492
|
runTextJob(node, resolvedPrompt, model, state.currentBoard.handle, state.currentBoard.key, imageRefs);
|
|
493
493
|
});
|
|
494
494
|
|
|
495
|
+
// Лёгкий fallback MIME-detector по расширению — на случай когда
|
|
496
|
+
// Blob-обёртка пришла без `type` (некоторые пути могут потерять).
|
|
497
|
+
// Anthropic/OpenRouter rejects data URL без MIME → обязательный fix.
|
|
498
|
+
function _mimeFromFilename(name) {
|
|
499
|
+
const m = String(name || '').match(/\.([a-zA-Z0-9]+)$/);
|
|
500
|
+
if (!m) return 'image/jpeg'; // дефолт для картинок без расширения
|
|
501
|
+
const ext = m[1].toLowerCase();
|
|
502
|
+
return ({
|
|
503
|
+
jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png',
|
|
504
|
+
webp: 'image/webp', gif: 'image/gif', avif: 'image/avif',
|
|
505
|
+
heic: 'image/heic', bmp: 'image/bmp',
|
|
506
|
+
})[ext] || 'image/jpeg';
|
|
507
|
+
}
|
|
508
|
+
|
|
495
509
|
async function _imageRefToDataUrl(ref) {
|
|
496
510
|
// Возвращает {url, size, mime} или {error}. Раньше тихо возвращал null
|
|
497
511
|
// на любую ошибку → дебаг был невозможен (юзер видел «картинка не
|
|
@@ -503,13 +517,34 @@ async function _imageRefToDataUrl(ref) {
|
|
|
503
517
|
}
|
|
504
518
|
const fh = await resolveBoardFile(ref.boardHandle, ref.file);
|
|
505
519
|
const file = await fh.getFile();
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
520
|
+
// Гарантируем правильный MIME — иначе data URL получается как
|
|
521
|
+
// `data:base64,...` (без MIME) → Anthropic возвращает 502 «Provider
|
|
522
|
+
// returned error» через OpenRouter. Bug был в cloudFs shim'е (Blob
|
|
523
|
+
// без type), но защищаемся здесь тоже на случай других source'ов.
|
|
524
|
+
const mime = file.type && file.type !== 'application/octet-stream'
|
|
525
|
+
? file.type
|
|
526
|
+
: _mimeFromFilename(ref.file);
|
|
527
|
+
let url;
|
|
528
|
+
if (file.type === mime) {
|
|
529
|
+
// type уже правильный — FileReader даст правильный data URL.
|
|
530
|
+
url = await new Promise((res, rej) => {
|
|
531
|
+
const r = new FileReader();
|
|
532
|
+
r.onload = () => res(r.result);
|
|
533
|
+
r.onerror = () => rej(r.error);
|
|
534
|
+
r.readAsDataURL(file);
|
|
535
|
+
});
|
|
536
|
+
} else {
|
|
537
|
+
// type пустой/неправильный — пересобираем Blob с явным type.
|
|
538
|
+
const buf = await file.arrayBuffer();
|
|
539
|
+
const blob = new Blob([buf], { type: mime });
|
|
540
|
+
url = await new Promise((res, rej) => {
|
|
541
|
+
const r = new FileReader();
|
|
542
|
+
r.onload = () => res(r.result);
|
|
543
|
+
r.onerror = () => rej(r.error);
|
|
544
|
+
r.readAsDataURL(blob);
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
return { url, size: file.size, mime };
|
|
513
548
|
} catch (e) {
|
|
514
549
|
return { error: e?.message || String(e) };
|
|
515
550
|
}
|
package/renderer/state.js
CHANGED
|
@@ -669,10 +669,14 @@ async function plannedProvider(kind) {
|
|
|
669
669
|
let s;
|
|
670
670
|
try { s = await window.appSettings.get(); } catch { s = {}; }
|
|
671
671
|
const hasChatium = !!(s.useChatium && s.chatium?.token && s.chatium?.base);
|
|
672
|
+
const hasOpenrouter = !!(s.useOpenrouter && s.openrouterKey);
|
|
672
673
|
switch (kind) {
|
|
673
674
|
case 'text':
|
|
675
|
+
// Зеркалит логику в lib/providers.js:generateText — directOpenrouter
|
|
676
|
+
// имеет приоритет над Chatium (юзер явно включил → значит хочет
|
|
677
|
+
// не тратить kingkont-кредиты).
|
|
678
|
+
if (hasOpenrouter) return 'openrouter';
|
|
674
679
|
if (hasChatium) return 'kingkont';
|
|
675
|
-
if (s.useOpenrouter === true) return 'openrouter';
|
|
676
680
|
return 'none';
|
|
677
681
|
case 'audio': case 'tts': case 'sfx': case 'music':
|
|
678
682
|
// Если ElevenLabs включён С ключом — приоритет прямого ElevenLabs
|