kingkont 0.14.5 → 0.14.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kingkont",
3
- "version": "0.14.5",
3
+ "version": "0.14.7",
4
4
  "description": "KingKont · Chatium — нод-редактор сцен с AI-генерацией (картинки/видео/голос/SFX/музыка/текст)",
5
5
  "main": "main.js",
6
6
  "bin": {
package/renderer/chat.js CHANGED
@@ -99,9 +99,9 @@
99
99
  },
100
100
 
101
101
  add_node: {
102
- description: 'Добавить ноду на текущую доску. Для image/video/audio с prompt нода будет draft (юзер запустит generation отдельно или используй generate_node). Для audio: subKind="music"|"sfx"|"voice" (default: voice/TTS). aspectRatio: "16:9"|"9:16"|"1:1"|"3:4"|"4:3"|"21:9" — переопределяет дефолтный для текущей сцены. ВАЖНО: если юзер сказал "горизонтальная"16:9, "вертикальная"9:16, "квадрат" 1:1.',
103
- params: '{"type":"image|video|audio|text","subKind":"music|sfx|voice (only for audio)","name":"<optional>","prompt":"<optional>","x":<optional>,"y":<optional>,"text":"<for-type=text>","modelKey":"<optional>","durationMs":<for-music-or-sfx>,"aspectRatio":"<optional, for image/video>"}',
104
- async handler({ type, subKind, name, prompt, x, y, text, modelKey, durationMs, aspectRatio }) {
102
+ description: 'Добавить ноду на текущую доску. Типы: image, video, audio, text, label.\n - image/video/audio с prompt draft, юзер потом запустит generation (или используй generate_node).\n - audio: subKind="music"|"sfx"|"voice" (дефолт voice/TTS).\n - aspectRatio: "16:9"|"9:16"|"1:1"|"3:4"|"4:3"|"21:9" — переопределяет дефолт сцены. Если юзер сказал "горизонтальная"16:9, "вертикальная"9:16, "квадрат"→1:1.\n - text: длинный markdown-блок (несколько абзацев). Передай text="...".\n - label: КОРОТКАЯ подпись поверх холста (заголовок, метка). Передай text="..." (1-3 слова обычно). Опционально textStyle={fontSize, fontFamily, italic}.',
103
+ params: '{"type":"image|video|audio|text|label","subKind":"music|sfx|voice","name":"<optional>","prompt":"<for image/video/audio>","x":<optional>,"y":<optional>,"text":"<for text/label>","textStyle":{"fontSize":<12|14|18|24|32|48|64|96>,"italic":<bool>,"fontFamily":"default|pencil|handwritten|brush|marker|display|elegant|rounded|serif|mono"},"modelKey":"<optional>","durationMs":<for music/sfx>,"aspectRatio":"<optional>"}',
104
+ async handler({ type, subKind, name, prompt, x, y, text, textStyle, modelKey, durationMs, aspectRatio }) {
105
105
  if (!state.currentBoard) throw new Error('доска не выбрана');
106
106
  if (!['image','video','audio','text','label'].includes(type)) throw new Error(`unknown type: ${type}`);
107
107
  // Поиск незанятого места: пытаемся справа от последней ноды,
@@ -149,6 +149,14 @@
149
149
  node.text = text;
150
150
  // .md создастся при saveBoardMetadata.
151
151
  }
152
+ if (type === 'label') {
153
+ node.text = (typeof text === 'string' && text.trim()) ? text : 'Подпись';
154
+ // textStyle: дефолт совпадает с UI-добавленной подписью.
155
+ // Юзерские поля переопределяют по одному (не replace целиком).
156
+ const defaultStyle = { fontSize: 32, italic: false, fontFamily: 'pencil' };
157
+ node.textStyle = { ...defaultStyle, ...(textStyle || {}) };
158
+ // Label — auto-size по тексту, width/height не задаём.
159
+ }
152
160
  if (prompt && type !== 'text' && type !== 'label') {
153
161
  node.status = 'draft';
154
162
  node.generated = { rawPrompt: prompt, prompt, modelKey: modelKey || undefined };
@@ -816,7 +824,8 @@
816
824
  const parts = [];
817
825
  if (ctx.scene) parts.push({ key: 'scene', label: `🎬 ${ctx.scene.name}`, removable: false });
818
826
  // Группируем выделенные ноды по type. Если 1 — показываем имя/id;
819
- // если 2+ — «N картинок» с локализацией.
827
+ // если 2+ — «N картинок» с локализацией. Клик по чипу — снимает
828
+ // выделение с этой ноды (или со всех нод этого type для группы).
820
829
  const byType = {};
821
830
  for (const sel of ctx.selected) {
822
831
  (byType[sel.type] = byType[sel.type] || []).push(sel);
@@ -825,10 +834,28 @@
825
834
  const icon = type === 'image' ? '🖼' : type === 'video' ? '🎬' : type === 'audio' ? '🎙' : type === 'text' ? '📝' : '◉';
826
835
  if (items.length === 1) {
827
836
  const s = items[0];
828
- parts.push({ key: 'sel:' + s.id, label: `${icon} ${s.name || s.id.slice(0, 6)}`, removable: false });
837
+ parts.push({
838
+ key: 'sel:' + s.id,
839
+ label: `${icon} ${s.name || s.id.slice(0, 6)}`,
840
+ removable: true,
841
+ onRemove: () => {
842
+ state.selectedNodeIds.delete(s.id);
843
+ if (typeof renderSelection === 'function') renderSelection();
844
+ renderContextRow();
845
+ },
846
+ });
829
847
  } else {
830
848
  const noun = _typeForms[type] || ['нода','ноды','нод'];
831
- parts.push({ key: 'sel-grp:' + type, label: `${icon} ${items.length} ${_ru_plural(items.length, noun)}`, removable: false });
849
+ parts.push({
850
+ key: 'sel-grp:' + type,
851
+ label: `${icon} ${items.length} ${_ru_plural(items.length, noun)}`,
852
+ removable: true,
853
+ onRemove: () => {
854
+ for (const it of items) state.selectedNodeIds.delete(it.id);
855
+ if (typeof renderSelection === 'function') renderSelection();
856
+ renderContextRow();
857
+ },
858
+ });
832
859
  }
833
860
  }
834
861
  for (const a of ctx.attachments) {
@@ -260,6 +260,10 @@
260
260
  box-shadow: -8px 0 32px rgba(0,0,0,0.4);
261
261
  /* --chat-font задаётся inline в chat.js (Cmd+/Cmd-). Дефолт 13px. */
262
262
  font-size: var(--chat-font, 13px);
263
+ /* Защита от horizontal-overflow: дочерние flex-элементы (длинные
264
+ имена чипов, tool-args в pre) могли push'ать ширину — fixed
265
+ position спасал, но без этого max-width дочерних не работал. */
266
+ overflow-x: hidden; box-sizing: border-box;
263
267
  }
264
268
  .chat-panel.chat-drag::before {
265
269
  content: '📎 Отпусти, чтобы прикрепить файл к чату';
@@ -274,6 +278,11 @@
274
278
  display: flex; flex-wrap: wrap; gap: 4px;
275
279
  padding: 6px 10px;
276
280
  background: #1a1a1a; border-top: 1px solid #2a2a2a;
281
+ /* min-width:0 — без этого flex-children не «сжимаются», и длинная
282
+ строка чипов раздвигает родителя (chat-panel получает горизонтальный
283
+ overflow). С flex-wrap:wrap + min-width:0 чипы корректно
284
+ переносятся на следующую строку. */
285
+ min-width: 0; max-width: 100%; box-sizing: border-box;
277
286
  }
278
287
  .chat-context-row:empty { display: none; }
279
288
  .chat-chip {
@@ -281,7 +290,12 @@
281
290
  background: #232a36; border: 1px solid #2c3848;
282
291
  color: #aac; font-size: 11px;
283
292
  padding: 2px 8px; border-radius: 999px;
284
- max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
293
+ max-width: 100%; /* был 200px длинные имена ужимались, но
294
+ если width контейнера меньше 200px, чип всё
295
+ ещё мог push'нуть строку. 100% относительно
296
+ .chat-context-row → гарантированно влезает. */
297
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
298
+ min-width: 0; /* чтобы flex-shrink работал */
285
299
  }
286
300
  .chat-chip-x {
287
301
  background: transparent; border: none; color: #889;