kingkont 0.20.48 → 0.20.50

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 CHANGED
@@ -1091,16 +1091,19 @@ function _extFromFilename(name) {
1091
1091
  /**
1092
1092
  * @returns {Promise<{ url, fileName, size, hash?: string, provider }>}
1093
1093
  */
1094
- async function uploadFile({ buffer, filename = 'upload.bin', mime = 'application/octet-stream', settings: s }) {
1094
+ async function uploadFile({ buffer, filename = 'upload.bin', mime = 'application/octet-stream', kind, settings: s }) {
1095
1095
  if (!buffer || !buffer.length) throw new Error('пустой файл');
1096
1096
  if (buffer.length > 50 * 1024 * 1024) {
1097
1097
  throw new Error(`файл слишком большой (${(buffer.length/1024/1024).toFixed(1)} MB), лимит 50MB`);
1098
1098
  }
1099
-
1100
- // Явный override: «Использовать Cloudflare для загрузки» каждый файл сразу
1101
- // идёт в R2, без chatium-race. Чекбокс в settings.html, юзер сам решает.
1102
- if (s.useCloudflareR2 && r2Upload.isConfigured()) {
1103
- console.log('[uploadFile] useCloudflareR2=true → R2 directly');
1099
+ // kind: 'ref' (передача референса в gen) | 'project' (default — сохранение
1100
+ // проекта). Юзер: «Cloudflare нельзя использовать для выкладки проектов,
1101
+ // только для передачи reference». Project-save'ы НИКОГДА не идут в R2.
1102
+ const isRef = kind === 'ref';
1103
+
1104
+ // Только для REF-аплоада: явный override useCloudflareR2 → R2 directly.
1105
+ if (isRef && s.useCloudflareR2 && r2Upload.isConfigured()) {
1106
+ console.log('[uploadFile ref] useCloudflareR2=true → R2 directly');
1104
1107
  const r2 = await r2Upload.uploadToR2(buffer, mime, _extFromFilename(filename));
1105
1108
  return {
1106
1109
  url: r2.url, fileName: filename, size: buffer.length, hash: r2.key, provider: 'cloudflare-r2',
@@ -1108,7 +1111,15 @@ async function uploadFile({ buffer, filename = 'upload.bin', mime = 'application
1108
1111
  }
1109
1112
 
1110
1113
  if (s.useChatium && s.chatium?.token && s.chatium?.base) {
1111
- return await _uploadViaChatiumWithR2Fallback({ buffer, filename, mime, s });
1114
+ // Project-save: ВСЕГДА только chatium, без R2-fallback (даже если
1115
+ // chatium тормозит). Юзер хочет чтобы вся проектная storage была
1116
+ // в chatium-storage, иначе R2-URL'ы попадают в manifest и при
1117
+ // следующем open project возвращаем мусор.
1118
+ // Ref: race chatium с R2-fallback по 5s timeout (для устойчивости).
1119
+ if (isRef) {
1120
+ return await _uploadViaChatiumWithR2Fallback({ buffer, filename, mime, s });
1121
+ }
1122
+ return await _chatiumUploadOnly({ buffer, filename, mime, s });
1112
1123
  }
1113
1124
 
1114
1125
  if (!s.useKie) throw new Error('Войдите в KingKont или KIE для загрузки файлов.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kingkont",
3
- "version": "0.20.48",
3
+ "version": "0.20.50",
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/renderer/board.js CHANGED
@@ -1671,14 +1671,7 @@ async function openShareModal(p) {
1671
1671
  // там приложение живёт на kingkont.ru. В web используем origin.
1672
1672
  const isLocal = /^https?:\/\/(localhost|127\.0\.0\.1)/.test(location.origin);
1673
1673
  const base = isLocal ? 'https://kingkont.ru' : location.origin;
1674
- // Раньше: /static/web.html но chatium-edge кэширует этот путь
1675
- // НАВЕЧНО, и юзеры получали старую версию HTML со старыми хешами
1676
- // скриптов. Старые cloudProjects.js не имеют R2 pub→worker rewrite
1677
- // → картинки 503 → «Файл не найден».
1678
- // Сейчас: /app/spaces/client/ — auth-aware stub, который JS'ом
1679
- // редиректит на свежий /static/web-<sha>.html (новое имя файла =
1680
- // свежий cache-entry на каждом deploy).
1681
- return base + '/app/spaces/client/#template=' + proj.id;
1674
+ return base + '/static/web.html#template=' + proj.id;
1682
1675
  }
1683
1676
  function render() {
1684
1677
  const isPub = !!proj.isPublic;
@@ -82,16 +82,23 @@
82
82
  // openShareModal живёт в board.js (window.openShareModal). В Electron
83
83
  // share-API'ы (/api/proj/setPublic|share|unshare|shares) проксируются
84
84
  // через server.js → kingkont.ru/proj_*; в web — через web-shim.
85
+ // ↗ Перейти — рядом, только когда проект публичный (state.cloudIsPublic).
86
+ // Юзер: «на проекте если он публичный сделай кнопку перейти (с иконкой,
87
+ // без надписи) справа от кнопки Расшарить».
85
88
  {
86
- let shareBtn = $('shareProjectBtn');
89
+ let row = $('shareProjectRow');
87
90
  const wantShow = logged && state.cloudProjectId && state.cloudMine !== false;
88
- if (wantShow && !shareBtn && saveBtn?.parentNode) {
89
- shareBtn = document.createElement('button');
91
+ if (wantShow && !row && saveBtn?.parentNode) {
92
+ row = document.createElement('div');
93
+ row.id = 'shareProjectRow';
94
+ row.style.cssText = 'display:flex; gap:6px; margin-top:6px; align-items:stretch;';
95
+
96
+ const shareBtn = document.createElement('button');
90
97
  shareBtn.id = 'shareProjectBtn';
91
98
  shareBtn.className = 'kk-edit-only';
92
99
  shareBtn.textContent = '🤝 Расшарить';
93
100
  shareBtn.title = 'Расшарить проект другому юзеру или сделать публичным';
94
- shareBtn.style.cssText = 'margin-top:6px; font-size:12px;';
101
+ shareBtn.style.cssText = 'flex:1; font-size:12px;';
95
102
  shareBtn.addEventListener('click', () => {
96
103
  if (typeof window.openShareModal === 'function') {
97
104
  window.openShareModal({
@@ -102,9 +109,31 @@
102
109
  });
103
110
  }
104
111
  });
105
- saveBtn.parentNode.insertBefore(shareBtn, saveBtn.nextSibling);
112
+ row.appendChild(shareBtn);
113
+
114
+ // ↗ Перейти — иконка, открывает share-URL в новой вкладке.
115
+ // Видна только если проект публичный (по private-проекту ссылка
116
+ // не работает у незалогиненных, и тут смысла нет).
117
+ const gotoBtn = document.createElement('button');
118
+ gotoBtn.id = 'gotoPublicBtn';
119
+ gotoBtn.className = 'kk-edit-only';
120
+ gotoBtn.textContent = '↗';
121
+ gotoBtn.title = 'Открыть публичную страницу проекта в новой вкладке';
122
+ gotoBtn.style.cssText = 'font-size:14px; padding:0 10px;';
123
+ gotoBtn.addEventListener('click', () => {
124
+ const isLocal = /^https?:\/\/(localhost|127\.0\.0\.1)/.test(location.origin);
125
+ const base = isLocal ? 'https://kingkont.ru' : location.origin;
126
+ const url = base + '/static/web.html#template=' + encodeURIComponent(state.cloudProjectId);
127
+ window.open(url, '_blank', 'noopener');
128
+ });
129
+ row.appendChild(gotoBtn);
130
+
131
+ saveBtn.parentNode.insertBefore(row, saveBtn.nextSibling);
106
132
  }
107
- if (shareBtn) shareBtn.style.display = wantShow ? '' : 'none';
133
+ if (row) row.style.display = wantShow ? 'flex' : 'none';
134
+ // Видимость ↗ Перейти зависит от isPublic — обновляем каждый тик.
135
+ const gotoBtn = $('gotoPublicBtn');
136
+ if (gotoBtn) gotoBtn.style.display = (wantShow && state.cloudIsPublic) ? '' : 'none';
108
137
  }
109
138
  }).catch(() => {});
110
139
  }
@@ -935,8 +964,17 @@
935
964
  // Dedup: сначала быстрая проверка по size, потом content-hash.
936
965
  // Mtime НЕ используем — Date.now() в JS не совпадает с OS-stamped
937
966
  // mtime, после download/write валидные файлы выглядели как «новые».
967
+ // Юзер: «Cloudflare нельзя использовать для выкладки проектов».
968
+ // Старые R2-URL'ы (pub-…r2.dev / kingkont-r2-upload…workers.dev) в
969
+ // кэше — это легаси от прежней архитектуры. Не переиспользуем их —
970
+ // форсим re-upload в chatium-storage. Иначе manifest проекта так и
971
+ // останется с R2-URL'ами навсегда.
972
+ const _isCachedR2 = cached?.url && (
973
+ cached.url.startsWith('https://pub-cd4114af9f7d44c9bf8c9442bc7dddc2.r2.dev') ||
974
+ cached.url.startsWith('https://kingkont-r2-upload.timur-dd5.workers.dev')
975
+ );
938
976
  let fhash = null;
939
- if (cached && cached.size === file.size) {
977
+ if (cached && !_isCachedR2 && cached.size === file.size) {
940
978
  fhash = await _quickFileHash(file);
941
979
  if (cached.fhash === fhash) {
942
980
  boardFiles[f.relPath] = cached.url;
@@ -949,6 +987,9 @@
949
987
  continue;
950
988
  }
951
989
  }
990
+ if (_isCachedR2) {
991
+ console.log(`[cloudProjects] re-upload (R2→chatium migration) ${f.relPath}`);
992
+ }
952
993
  PROGRESS.update(uploaded, total,
953
994
  `[${board.kind}/${board.name}] ${f.relPath} (${uploaded + 1}/${total})`);
954
995
  const r = await fetch('/api/upload', {
@@ -620,7 +620,11 @@ async function _imageRefToDataUrl(ref) {
620
620
  const filename = ref.file.split('/').pop() || 'ref.jpg';
621
621
  const r = await fetch('/api/upload', {
622
622
  method: 'POST',
623
- headers: { 'Content-Type': mime, 'X-File-Name': encodeURIComponent(filename) },
623
+ headers: {
624
+ 'Content-Type': mime,
625
+ 'X-File-Name': encodeURIComponent(filename),
626
+ 'X-Upload-Kind': 'ref',
627
+ },
624
628
  body: buf,
625
629
  });
626
630
  if (!r.ok) {
package/renderer/media.js CHANGED
@@ -1384,11 +1384,16 @@ async function uploadBoardFile(boardHandle, boardKey, filename) {
1384
1384
  return cached.url;
1385
1385
  }
1386
1386
  console.log('[uploadBoardFile] cache MISS — uploading', { file: filename, size: file.size, mime: file.type });
1387
+ // X-Upload-Kind: ref — этот upload идёт для передачи референса в gen.
1388
+ // Только ref-аплоады могут уйти в Cloudflare R2 (если useCloudflareR2).
1389
+ // Project-save'ы (cloudProjects.js) не передают этот header → chatium-only.
1390
+ // Юзер: «Cloudflare нельзя использовать для выкладки проектов».
1387
1391
  const r = await fetch('/api/upload', {
1388
1392
  method: 'POST',
1389
1393
  headers: {
1390
1394
  'Content-Type': file.type || 'application/octet-stream',
1391
1395
  'X-File-Name': encodeURIComponent(file.name),
1396
+ 'X-Upload-Kind': 'ref',
1392
1397
  },
1393
1398
  body: file,
1394
1399
  });
package/server.js CHANGED
@@ -125,9 +125,14 @@ async function handlePoll(res, url) {
125
125
  async function handleUpload(req, res) {
126
126
  const filename = decodeURIComponent(req.headers['x-file-name'] || 'upload.bin');
127
127
  const mime = (req.headers['content-type'] || 'application/octet-stream').split(';')[0].trim();
128
+ // X-Upload-Kind: 'ref' (для передачи референсов в gen) | 'project' (default).
129
+ // Только 'ref' может уйти в R2 (если включено) — project-save'ы ВСЕГДА
130
+ // в chatium-storage. Юзер: «Cloudflare нельзя использовать для выкладки
131
+ // проектов, только для передачи reference».
132
+ const kind = (req.headers['x-upload-kind'] === 'ref') ? 'ref' : 'project';
128
133
  const buffer = await readBody(req);
129
134
  try {
130
- const r = await providers.uploadFile({ buffer, filename, mime, settings: getSettings() });
135
+ const r = await providers.uploadFile({ buffer, filename, mime, kind, settings: getSettings() });
131
136
  send(res, 200, { url: r.url, fileName: r.fileName, size: r.size, hash: r.hash }, { 'X-Provider': r.provider });
132
137
  } catch (e) { sendError(res, e, 502); }
133
138
  }
package/settings.html CHANGED
@@ -174,15 +174,20 @@
174
174
  <label class="use-toggle" for="useCloudflareR2">
175
175
  <input type="checkbox" id="useCloudflareR2" />
176
176
  <span class="label-text">
177
- Использовать Cloudflare для загрузки
178
- <span class="sub">— upload референсов идёт в R2-бакет KingKont вместо chatium-storage</span>
177
+ Использовать Cloudflare для референсов
178
+ <span class="sub">— только для передачи refs в gen; проекты ВСЕГДА в chatium-storage</span>
179
179
  </span>
180
180
  </label>
181
181
  <div class="hint">
182
- По умолчанию (выключено) — upload идёт в <code>fs.chatium.ru</code>, и
182
+ По умолчанию (выключено) — реф идёт в <code>fs.chatium.ru</code>, и
183
183
  если он медленнее 5 секунд, автоматически уходит в Cloudflare R2.
184
- Если включить — chatium-storage не используется вообще, каждый
185
- файл сразу идёт в R2 (<code>pub-cd4114…r2.dev</code>).
184
+ Если включить — chatium-storage для рефов не используется, каждый
185
+ реф сразу в R2 (<code>worker/get/&lt;uuid&gt;</code>).
186
+ <br><br>
187
+ <strong>Важно</strong>: настройка касается ТОЛЬКО передачи референсов
188
+ в генерацию. Сохранение проектов (☁ Сохранить на сервер) всегда
189
+ пишет файлы в chatium-storage — иначе R2-URL'ы попадают в manifest
190
+ и нарушают переносимость проекта.
186
191
  </div>
187
192
  </div>
188
193