@sequent-org/moodboard 1.4.30 → 1.4.32
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 +3 -1
- package/src/core/PixiEngine.js +34 -5
- package/src/core/bootstrap/CoreInitializer.js +4 -0
- package/src/core/commands/CreateConnectorCommand.js +25 -0
- package/src/core/commands/GroupMoveCommand.js +2 -2
- package/src/core/commands/MoveObjectCommand.js +1 -1
- package/src/core/commands/UpdateConnectorCommand.js +38 -0
- package/src/core/events/Events.js +1 -0
- package/src/mindmap/MindmapCompoundContract.js +1 -0
- package/src/moodboard/bootstrap/MoodBoardUiFactory.js +14 -0
- package/src/moodboard/lifecycle/MoodBoardDestroyer.js +18 -0
- package/src/objects/ConnectorObject.js +85 -0
- package/src/objects/DrawingObject.js +47 -0
- package/src/objects/MindmapObject.js +21 -3
- package/src/objects/NoteObject.js +16 -8
- package/src/objects/ObjectFactory.js +3 -1
- package/src/objects/ShapeObject.js +1 -1
- package/src/services/ConnectorBindingResolver.js +204 -0
- package/src/services/ai/AiClient.js +30 -2
- package/src/services/ai/ChatSessionController.js +1 -0
- package/src/tools/ToolManager.js +3 -0
- package/src/tools/manager/PointerGestureController.js +206 -0
- package/src/tools/manager/ToolEventRouter.js +10 -0
- package/src/tools/manager/ToolManagerGuards.js +3 -1
- package/src/tools/manager/ToolManagerLifecycle.js +70 -58
- package/src/tools/object-tools/ConnectorTool.js +147 -0
- package/src/tools/object-tools/PlacementTool.js +2 -2
- package/src/tools/object-tools/connector/ConnectorDragController.js +296 -0
- package/src/tools/object-tools/connector/connectorGesture.js +108 -0
- package/src/tools/object-tools/placement/GhostController.js +4 -4
- package/src/tools/object-tools/placement/PlacementEventsBridge.js +2 -2
- package/src/tools/object-tools/placement/PlacementInputRouter.js +5 -5
- package/src/tools/object-tools/selection/MindmapInlineEditorController.js +11 -2
- package/src/tools/object-tools/selection/SelectInputRouter.js +33 -4
- package/src/tools/object-tools/selection/SelectToolLifecycleController.js +12 -0
- package/src/tools/object-tools/selection/SelectToolSetup.js +3 -0
- package/src/tools/object-tools/selection/TextEditorDomFactory.js +1 -2
- package/src/tools/object-tools/selection/TextEditorSyncService.js +4 -4
- package/src/tools/object-tools/selection/TextInlineEditorController.js +21 -3
- package/src/tools/object-tools/selection/TransformInteractionController.js +4 -6
- package/src/ui/HtmlTextLayer.js +212 -5
- package/src/ui/animation/HoverLiftController.js +395 -0
- package/src/ui/chat/ChatComposer.js +1 -10
- package/src/ui/chat/ChatExtendedPromptModal.js +1 -12
- package/src/ui/chat/ChatWindow.js +167 -36
- package/src/ui/chat/ChatWindowRenderer.js +1 -8
- package/src/ui/chat/icons.js +17 -5
- package/src/ui/connectors/ConnectionAnchorsLayer.js +231 -0
- package/src/ui/connectors/ConnectorLayer.js +251 -0
- package/src/ui/handles/HandlesDomRenderer.js +11 -7
- package/src/ui/handles/HandlesInteractionController.js +65 -34
- package/src/ui/handles/HandlesPositioningService.js +41 -6
- package/src/ui/mindmap/MindmapCollapseGraph.js +169 -0
- package/src/ui/mindmap/MindmapCollapseLayer.js +380 -0
- package/src/ui/mindmap/MindmapConnectionLayer.js +50 -25
- package/src/ui/mindmap/MindmapHtmlTextLayer.js +223 -2
- package/src/ui/mindmap/MindmapLayoutConfig.js +12 -0
- package/src/ui/styles/chat.css +2 -37
- package/src/ui/styles/toolbar.css +6 -0
- package/src/ui/styles/workspace.css +83 -21
- package/src/ui/toolbar/ToolbarPopupsController.js +1 -1
|
@@ -13,7 +13,7 @@ const CLOSE_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12
|
|
|
13
13
|
|
|
14
14
|
export class ChatComposer {
|
|
15
15
|
/**
|
|
16
|
-
* @param {{ textarea: HTMLTextAreaElement, send: HTMLButtonElement, attach: HTMLButtonElement, fileInput: HTMLInputElement, attachmentsPreview: HTMLElement,
|
|
16
|
+
* @param {{ textarea: HTMLTextAreaElement, send: HTMLButtonElement, attach: HTMLButtonElement, fileInput: HTMLInputElement, attachmentsPreview: HTMLElement, statusBar?: HTMLElement }} refs
|
|
17
17
|
* @param {{ onSubmit: (text: string, attachments: File[]) => void, onAbort: () => void }} handlers
|
|
18
18
|
*/
|
|
19
19
|
constructor(refs, handlers) {
|
|
@@ -22,7 +22,6 @@ export class ChatComposer {
|
|
|
22
22
|
this._attach = refs.attach ?? null;
|
|
23
23
|
this._fileInput = refs.fileInput ?? null;
|
|
24
24
|
this._attachmentsPreview = refs.attachmentsPreview ?? null;
|
|
25
|
-
this._enhancePrompt = refs.enhancePrompt ?? null;
|
|
26
25
|
this._statusBar = refs.statusBar ?? null;
|
|
27
26
|
this._handlers = handlers;
|
|
28
27
|
this._listeners = [];
|
|
@@ -97,11 +96,6 @@ export class ChatComposer {
|
|
|
97
96
|
if (!trimmed && !hasAttachments) return;
|
|
98
97
|
if (this._send.dataset.state === 'streaming') return;
|
|
99
98
|
const attachments = [...this._attachments];
|
|
100
|
-
this._attachments = [];
|
|
101
|
-
this._textarea.value = '';
|
|
102
|
-
this._resizeTextarea();
|
|
103
|
-
this._renderAttachmentsPreview();
|
|
104
|
-
this._refreshSendState();
|
|
105
99
|
this._handlers.onSubmit?.(trimmed, attachments);
|
|
106
100
|
}
|
|
107
101
|
|
|
@@ -110,9 +104,6 @@ export class ChatComposer {
|
|
|
110
104
|
const hasAttachments = this._attachments.length > 0;
|
|
111
105
|
this._send.dataset.state = (hasText || hasAttachments) ? 'ready' : 'idle';
|
|
112
106
|
this._send.disabled = false;
|
|
113
|
-
if (this._enhancePrompt) {
|
|
114
|
-
this._enhancePrompt.dataset.empty = hasText ? 'false' : 'true';
|
|
115
|
-
}
|
|
116
107
|
}
|
|
117
108
|
|
|
118
109
|
_handleFileChange() {
|
|
@@ -36,11 +36,6 @@ export class ChatExtendedPromptModal {
|
|
|
36
36
|
this._sourceTextarea.value = '';
|
|
37
37
|
this._sourceTextarea.dispatchEvent(new Event('input', { bubbles: true }));
|
|
38
38
|
});
|
|
39
|
-
|
|
40
|
-
this._on(this._refs.enhanceBtn, 'click', () => {
|
|
41
|
-
const originalEnhance = this._sourceTextarea.parentElement?.querySelector('.moodboard-chat__input-icon-btn--enhance-prompt');
|
|
42
|
-
if (originalEnhance) originalEnhance.click();
|
|
43
|
-
});
|
|
44
39
|
}
|
|
45
40
|
|
|
46
41
|
show() {
|
|
@@ -111,13 +106,7 @@ export class ChatExtendedPromptModal {
|
|
|
111
106
|
clearBtn.className = 'moodboard-chat__extended-clear';
|
|
112
107
|
clearBtn.textContent = 'Очистить';
|
|
113
108
|
|
|
114
|
-
const enhanceBtn = document.createElement('button');
|
|
115
|
-
enhanceBtn.type = 'button';
|
|
116
|
-
enhanceBtn.className = 'moodboard-chat__extended-enhance';
|
|
117
|
-
enhanceBtn.innerHTML = `<span class="moodboard-chat__extended-enhance-icon">${ICONS.enhancePrompt}</span> Улучшить`;
|
|
118
|
-
|
|
119
109
|
actions.appendChild(clearBtn);
|
|
120
|
-
actions.appendChild(enhanceBtn);
|
|
121
110
|
|
|
122
111
|
body.appendChild(textarea);
|
|
123
112
|
body.appendChild(actions);
|
|
@@ -126,6 +115,6 @@ export class ChatExtendedPromptModal {
|
|
|
126
115
|
modal.appendChild(body);
|
|
127
116
|
overlay.appendChild(modal);
|
|
128
117
|
|
|
129
|
-
return { overlay, modal, header, title, closeBtn, body, textarea, clearBtn
|
|
118
|
+
return { overlay, modal, header, title, closeBtn, body, textarea, clearBtn };
|
|
130
119
|
}
|
|
131
120
|
}
|
|
@@ -56,6 +56,10 @@ const BOARD_IMAGE_STEP = 320;
|
|
|
56
56
|
const BOARD_IMAGE_GAP = BOARD_IMAGE_STEP - BOARD_IMAGE_WIDTH;
|
|
57
57
|
// Скорость перестановки AI-изображений на доске и въезда заглушек регулируется здесь.
|
|
58
58
|
const BOARD_IMAGE_REARRANGE_MS = 520;
|
|
59
|
+
// На сколько колонок «справа» появляется блок-заглушка перед въездом в финальную позицию.
|
|
60
|
+
const BOARD_IMAGE_PENDING_ENTER_FACTOR = 1.6;
|
|
61
|
+
// Каскад между блоками одного батча (мс): пользователь видит, что они приезжают друг за другом.
|
|
62
|
+
const BOARD_IMAGE_PENDING_STAGGER_MS = 90;
|
|
59
63
|
const REFERENCE_DRAG_PREVIEW_SIZE = 96;
|
|
60
64
|
|
|
61
65
|
const MODEL_OPTIONS = [
|
|
@@ -68,25 +72,25 @@ const MODEL_OPTIONS = [
|
|
|
68
72
|
{
|
|
69
73
|
id: 'yandex',
|
|
70
74
|
label: 'Алиса',
|
|
71
|
-
icon:
|
|
75
|
+
icon: ICONS.modelAlice,
|
|
72
76
|
description: 'YandexGPT'
|
|
73
77
|
},
|
|
74
78
|
{
|
|
75
79
|
id: 'gpt',
|
|
76
80
|
label: 'GPT',
|
|
77
|
-
icon:
|
|
81
|
+
icon: ICONS.modelGpt,
|
|
78
82
|
description: 'OpenAI'
|
|
79
83
|
},
|
|
80
84
|
{
|
|
81
85
|
id: 'google',
|
|
82
86
|
label: 'Google',
|
|
83
|
-
icon:
|
|
87
|
+
icon: ICONS.modelGoogle,
|
|
84
88
|
description: 'Gemini'
|
|
85
89
|
},
|
|
86
90
|
{
|
|
87
91
|
id: 'qwen',
|
|
88
92
|
label: 'Qwen',
|
|
89
|
-
icon:
|
|
93
|
+
icon: ICONS.modelQwen,
|
|
90
94
|
description: 'Alibaba'
|
|
91
95
|
}
|
|
92
96
|
];
|
|
@@ -137,14 +141,16 @@ export class ChatWindow {
|
|
|
137
141
|
this._attached = false;
|
|
138
142
|
this._boardImageMessageIds = new Set();
|
|
139
143
|
this._shiftedForImageBatchKeys = new Set();
|
|
140
|
-
this.
|
|
141
|
-
this.
|
|
144
|
+
this._boardImageShiftHistory = new Map();
|
|
145
|
+
this._pendingOverlays = new Map();
|
|
146
|
+
this._pendingOverlayTimers = new Map();
|
|
142
147
|
this._boardImageShiftAnimations = new Map();
|
|
143
148
|
this._boardCursor = null;
|
|
144
149
|
this._draggedReferenceObject = null;
|
|
145
150
|
this._draggedReferenceStartPosition = null;
|
|
146
151
|
this._referenceDragPreview = null;
|
|
147
152
|
this._referenceDragHandlers = null;
|
|
153
|
+
this._clearSelectionOnSendClick = null;
|
|
148
154
|
}
|
|
149
155
|
|
|
150
156
|
attach() {
|
|
@@ -161,14 +167,18 @@ export class ChatWindow {
|
|
|
161
167
|
attach: this._refs.attach,
|
|
162
168
|
fileInput: this._refs.fileInput,
|
|
163
169
|
attachmentsPreview: this._refs.attachmentsPreview,
|
|
164
|
-
enhancePrompt: this._refs.enhancePrompt,
|
|
165
170
|
statusBar: this._refs.statusBar
|
|
166
171
|
},
|
|
167
172
|
{
|
|
168
|
-
onSubmit: (text, attachments) =>
|
|
173
|
+
onSubmit: (text, attachments) => {
|
|
174
|
+
this._clearBoardSelection();
|
|
175
|
+
return this._session.send(text, { ...this._getImageRequestOptions(), referenceImages: attachments });
|
|
176
|
+
},
|
|
169
177
|
onAbort: () => this._session.abort()
|
|
170
178
|
}
|
|
171
179
|
);
|
|
180
|
+
this._clearSelectionOnSendClick = () => this._clearBoardSelection();
|
|
181
|
+
this._refs.send.addEventListener('click', this._clearSelectionOnSendClick);
|
|
172
182
|
this._composer.attach();
|
|
173
183
|
this._attachReferenceDragEvents();
|
|
174
184
|
|
|
@@ -255,9 +265,13 @@ export class ChatWindow {
|
|
|
255
265
|
this._cancelBoardImageShiftAnimations();
|
|
256
266
|
this._clearReferenceDragState();
|
|
257
267
|
if (this._unsubscribe) { this._unsubscribe(); this._unsubscribe = null; }
|
|
268
|
+
if (this._clearSelectionOnSendClick && this._refs?.send) {
|
|
269
|
+
this._refs.send.removeEventListener('click', this._clearSelectionOnSendClick);
|
|
270
|
+
this._clearSelectionOnSendClick = null;
|
|
271
|
+
}
|
|
258
272
|
this._detachReferenceDragEvents();
|
|
259
273
|
this._shiftedForImageBatchKeys.clear();
|
|
260
|
-
this.
|
|
274
|
+
this._boardImageShiftHistory.clear();
|
|
261
275
|
this._composer?.destroy();
|
|
262
276
|
this._extendedPromptModal?.destroy();
|
|
263
277
|
this._contentTypeMenu?.destroy();
|
|
@@ -282,6 +296,15 @@ export class ChatWindow {
|
|
|
282
296
|
this.detach();
|
|
283
297
|
}
|
|
284
298
|
|
|
299
|
+
_clearBoardSelection() {
|
|
300
|
+
if (typeof this._boardCore?.selectTool?.clearSelection === 'function') {
|
|
301
|
+
this._boardCore.selectTool.clearSelection();
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
this._boardCore?.eventBus?.emit(Events.Tool.SelectionClear);
|
|
306
|
+
}
|
|
307
|
+
|
|
285
308
|
_updateCountPillIcon() {
|
|
286
309
|
const active = COUNT_OPTIONS.find((o) => o.id === this._countId);
|
|
287
310
|
if (!active) return;
|
|
@@ -314,6 +337,9 @@ export class ChatWindow {
|
|
|
314
337
|
_render(state) {
|
|
315
338
|
if (!this._attached && !this._refs) return;
|
|
316
339
|
this._syncGeneratedImagesToBoard(state.messages);
|
|
340
|
+
if (state.status !== 'streaming') {
|
|
341
|
+
this._revertFailedBatchShifts(state.messages);
|
|
342
|
+
}
|
|
317
343
|
this._messageList.render(state.messages);
|
|
318
344
|
this._contentTypeMenu.refresh();
|
|
319
345
|
this._modelMenu.refresh();
|
|
@@ -327,9 +353,17 @@ export class ChatWindow {
|
|
|
327
353
|
}
|
|
328
354
|
|
|
329
355
|
_updatePendingImages(messages) {
|
|
330
|
-
this._clearPendingOverlays();
|
|
331
|
-
|
|
332
356
|
const pending = (messages || []).filter((m) => m.pending && m.kind === 'image');
|
|
357
|
+
const activeIds = new Set(pending.map((m) => m.id));
|
|
358
|
+
|
|
359
|
+
for (const [id, record] of this._pendingOverlays) {
|
|
360
|
+
if (!activeIds.has(id)) {
|
|
361
|
+
record.el.remove();
|
|
362
|
+
this._pendingOverlays.delete(id);
|
|
363
|
+
this._cancelPendingOverlayTimer(id);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
333
367
|
if (pending.length === 0) return;
|
|
334
368
|
|
|
335
369
|
const world = this._boardCore?.pixi?.worldLayer || this._boardCore?.pixi?.app?.stage;
|
|
@@ -341,27 +375,31 @@ export class ChatWindow {
|
|
|
341
375
|
const ratio = wr / hr;
|
|
342
376
|
const wScreen = Math.round(BOARD_IMAGE_WIDTH * s);
|
|
343
377
|
const hScreen = Math.round(wScreen / ratio);
|
|
378
|
+
const enterDistance = Math.round(BOARD_IMAGE_STEP * s * BOARD_IMAGE_PENDING_ENTER_FACTOR);
|
|
344
379
|
|
|
345
|
-
|
|
380
|
+
let newIndex = 0;
|
|
381
|
+
pending.forEach((message) => {
|
|
346
382
|
const slot = this._getImageBatchSlot(messages, message.id, s);
|
|
347
383
|
const left = Math.round(slot.x - wScreen / 2);
|
|
348
384
|
const top = Math.round(slot.y - hScreen / 2);
|
|
349
385
|
|
|
386
|
+
const existing = this._pendingOverlays.get(message.id);
|
|
387
|
+
if (existing) {
|
|
388
|
+
const el = existing.el;
|
|
389
|
+
el.style.left = `${left}px`;
|
|
390
|
+
el.style.top = `${top}px`;
|
|
391
|
+
el.style.width = `${wScreen}px`;
|
|
392
|
+
el.style.height = `${hScreen}px`;
|
|
393
|
+
el.style.setProperty('--moodboard-chat-board-animation-ms', `${BOARD_IMAGE_REARRANGE_MS}ms`);
|
|
394
|
+
el.style.setProperty('--moodboard-chat-pending-enter-x', `${enterDistance}px`);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
350
398
|
const overlay = document.createElement('div');
|
|
351
|
-
overlay.className = 'moodboard-chat__pending-overlay';
|
|
399
|
+
overlay.className = 'moodboard-chat__pending-overlay moodboard-chat__pending-overlay--enter';
|
|
352
400
|
overlay.style.cssText = `left:${left}px;top:${top}px;width:${wScreen}px;height:${hScreen}px`;
|
|
353
401
|
overlay.style.setProperty('--moodboard-chat-board-animation-ms', `${BOARD_IMAGE_REARRANGE_MS}ms`);
|
|
354
|
-
overlay.style.setProperty('--moodboard-chat-pending-enter-x', `${
|
|
355
|
-
|
|
356
|
-
if (!this._pendingOverlayMessageIds.has(message.id)) {
|
|
357
|
-
overlay.classList.add('moodboard-chat__pending-overlay--enter');
|
|
358
|
-
this._pendingOverlayMessageIds.add(message.id);
|
|
359
|
-
this._scheduleAnimationFrame(() => {
|
|
360
|
-
if (overlay.isConnected) {
|
|
361
|
-
overlay.classList.add('moodboard-chat__pending-overlay--entered');
|
|
362
|
-
}
|
|
363
|
-
});
|
|
364
|
-
}
|
|
402
|
+
overlay.style.setProperty('--moodboard-chat-pending-enter-x', `${enterDistance}px`);
|
|
365
403
|
|
|
366
404
|
const label = document.createElement('span');
|
|
367
405
|
label.className = 'moodboard-chat__pending-image-label';
|
|
@@ -369,15 +407,52 @@ export class ChatWindow {
|
|
|
369
407
|
overlay.appendChild(label);
|
|
370
408
|
|
|
371
409
|
document.body.appendChild(overlay);
|
|
372
|
-
|
|
410
|
+
|
|
411
|
+
// Принудительный reflow: фиксируем стартовое состояние (translateX справа + opacity 0)
|
|
412
|
+
// в layout до переключения класса. Без этого браузер может смерджить два состояния
|
|
413
|
+
// в один кадр и transition не запустится — заглушка появится мгновенно.
|
|
414
|
+
void overlay.offsetWidth;
|
|
415
|
+
|
|
416
|
+
this._pendingOverlays.set(message.id, { el: overlay });
|
|
417
|
+
|
|
418
|
+
const stagger = newIndex * BOARD_IMAGE_PENDING_STAGGER_MS;
|
|
419
|
+
newIndex += 1;
|
|
420
|
+
|
|
421
|
+
const trigger = () => {
|
|
422
|
+
if (!overlay.isConnected) return;
|
|
423
|
+
overlay.classList.remove('moodboard-chat__pending-overlay--enter');
|
|
424
|
+
overlay.classList.add('moodboard-chat__pending-overlay--entered');
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
if (stagger > 0) {
|
|
428
|
+
const timer = setTimeout(() => {
|
|
429
|
+
this._pendingOverlayTimers.delete(message.id);
|
|
430
|
+
this._scheduleAnimationFrame(trigger);
|
|
431
|
+
}, stagger);
|
|
432
|
+
this._pendingOverlayTimers.set(message.id, timer);
|
|
433
|
+
} else {
|
|
434
|
+
this._scheduleAnimationFrame(trigger);
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
_cancelPendingOverlayTimer(id) {
|
|
440
|
+
const timer = this._pendingOverlayTimers.get(id);
|
|
441
|
+
if (timer !== undefined) {
|
|
442
|
+
clearTimeout(timer);
|
|
443
|
+
this._pendingOverlayTimers.delete(id);
|
|
373
444
|
}
|
|
374
445
|
}
|
|
375
446
|
|
|
376
447
|
_clearPendingOverlays() {
|
|
377
|
-
for (const
|
|
378
|
-
el.remove();
|
|
448
|
+
for (const record of this._pendingOverlays.values()) {
|
|
449
|
+
record.el.remove();
|
|
450
|
+
}
|
|
451
|
+
this._pendingOverlays.clear();
|
|
452
|
+
for (const timer of this._pendingOverlayTimers.values()) {
|
|
453
|
+
clearTimeout(timer);
|
|
379
454
|
}
|
|
380
|
-
this.
|
|
455
|
+
this._pendingOverlayTimers.clear();
|
|
381
456
|
}
|
|
382
457
|
|
|
383
458
|
_getImageRequestOptions() {
|
|
@@ -406,11 +481,15 @@ export class ChatWindow {
|
|
|
406
481
|
const onDragEnd = (data) => {
|
|
407
482
|
void this._handleReferenceDragEnd(data);
|
|
408
483
|
};
|
|
484
|
+
const onSelectionAdd = (data) => {
|
|
485
|
+
void this._handleSelectionAdd(data);
|
|
486
|
+
};
|
|
409
487
|
|
|
410
|
-
this._referenceDragHandlers = { onCursorMove, onDragStart, onDragEnd };
|
|
488
|
+
this._referenceDragHandlers = { onCursorMove, onDragStart, onDragEnd, onSelectionAdd };
|
|
411
489
|
eventBus.on(Events.UI.CursorMove, onCursorMove);
|
|
412
490
|
eventBus.on(Events.Tool.DragStart, onDragStart);
|
|
413
491
|
eventBus.on(Events.Tool.DragEnd, onDragEnd);
|
|
492
|
+
eventBus.on(Events.Tool.SelectionAdd, onSelectionAdd);
|
|
414
493
|
}
|
|
415
494
|
|
|
416
495
|
_detachReferenceDragEvents() {
|
|
@@ -421,9 +500,19 @@ export class ChatWindow {
|
|
|
421
500
|
eventBus.off(Events.UI.CursorMove, handlers.onCursorMove);
|
|
422
501
|
eventBus.off(Events.Tool.DragStart, handlers.onDragStart);
|
|
423
502
|
eventBus.off(Events.Tool.DragEnd, handlers.onDragEnd);
|
|
503
|
+
eventBus.off(Events.Tool.SelectionAdd, handlers.onSelectionAdd);
|
|
424
504
|
this._referenceDragHandlers = null;
|
|
425
505
|
}
|
|
426
506
|
|
|
507
|
+
async _handleSelectionAdd(data = {}) {
|
|
508
|
+
const objectId = data?.object;
|
|
509
|
+
const object = this._boardCore?.state?.state?.objects?.find((item) => item?.id === objectId);
|
|
510
|
+
|
|
511
|
+
if (!isReferenceImageObject(object)) return;
|
|
512
|
+
|
|
513
|
+
await this._addImageObjectAsReference(object);
|
|
514
|
+
}
|
|
515
|
+
|
|
427
516
|
_handleReferenceDragStart(data = {}) {
|
|
428
517
|
const objectId = data?.object;
|
|
429
518
|
const object = this._boardCore?.state?.state?.objects?.find((item) => item?.id === objectId);
|
|
@@ -591,7 +680,7 @@ export class ChatWindow {
|
|
|
591
680
|
if (this._shiftedForImageBatchKeys.has(batchKey)) return;
|
|
592
681
|
|
|
593
682
|
this._shiftedForImageBatchKeys.add(batchKey);
|
|
594
|
-
this._shiftBoardAiImagesLeft(this._getImageBatchWorldBounds(messages, messageId, scale));
|
|
683
|
+
this._shiftBoardAiImagesLeft(this._getImageBatchWorldBounds(messages, messageId, scale), batchKey);
|
|
595
684
|
}
|
|
596
685
|
|
|
597
686
|
_getImageBatchWorldBounds(messages, messageId, scale = 1) {
|
|
@@ -611,7 +700,7 @@ export class ChatWindow {
|
|
|
611
700
|
};
|
|
612
701
|
}
|
|
613
702
|
|
|
614
|
-
_shiftBoardAiImagesLeft(nextBatchBounds) {
|
|
703
|
+
_shiftBoardAiImagesLeft(nextBatchBounds, batchKey) {
|
|
615
704
|
const aiObjects = this._getBoardAiImageObjects();
|
|
616
705
|
if (aiObjects.length === 0 || !nextBatchBounds) return;
|
|
617
706
|
|
|
@@ -621,14 +710,56 @@ export class ChatWindow {
|
|
|
621
710
|
|
|
622
711
|
const ids = new Set(aiObjects.map((object) => object.id));
|
|
623
712
|
const objects = this._boardCore?.state?.state?.objects;
|
|
713
|
+
const shiftRecord = [];
|
|
624
714
|
for (const id of ids) {
|
|
625
715
|
const obj = objects?.find((item) => item.id === id);
|
|
626
716
|
if (obj?.position) {
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
717
|
+
const from = { x: obj.position.x, y: obj.position.y };
|
|
718
|
+
const to = { x: Math.round(obj.position.x - shift), y: obj.position.y };
|
|
719
|
+
shiftRecord.push({ id, from });
|
|
720
|
+
this._animateBoardImageToPosition(id, from, to);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
if (batchKey && shiftRecord.length > 0) {
|
|
725
|
+
this._boardImageShiftHistory.set(batchKey, shiftRecord);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
_revertBoardImageShiftForBatch(batchKey) {
|
|
730
|
+
const record = this._boardImageShiftHistory.get(batchKey);
|
|
731
|
+
if (!record) return;
|
|
732
|
+
|
|
733
|
+
const objects = this._boardCore?.state?.state?.objects;
|
|
734
|
+
for (const { id, from } of record) {
|
|
735
|
+
const obj = objects?.find((item) => item.id === id);
|
|
736
|
+
if (obj?.position) {
|
|
737
|
+
this._animateBoardImageToPosition(id, obj.position, from);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
this._boardImageShiftHistory.delete(batchKey);
|
|
742
|
+
this._shiftedForImageBatchKeys.delete(batchKey);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
_revertFailedBatchShifts(messages) {
|
|
746
|
+
if (this._boardImageShiftHistory.size === 0) return;
|
|
747
|
+
|
|
748
|
+
for (const batchKey of [...this._boardImageShiftHistory.keys()]) {
|
|
749
|
+
if (batchKey === 'unknown') continue;
|
|
750
|
+
|
|
751
|
+
const messageIds = batchKey.split('|');
|
|
752
|
+
const batchMessages = messageIds
|
|
753
|
+
.map((id) => messages?.find((m) => m.id === id))
|
|
754
|
+
.filter(Boolean);
|
|
755
|
+
|
|
756
|
+
if (batchMessages.length === 0) continue;
|
|
757
|
+
|
|
758
|
+
const allResolved = batchMessages.every((m) => !m.pending);
|
|
759
|
+
const anyImage = batchMessages.some((m) => Boolean(m.imageBase64));
|
|
760
|
+
|
|
761
|
+
if (allResolved && !anyImage) {
|
|
762
|
+
this._revertBoardImageShiftForBatch(batchKey);
|
|
632
763
|
}
|
|
633
764
|
}
|
|
634
765
|
}
|
|
@@ -67,26 +67,19 @@ function buildInputRow(collect) {
|
|
|
67
67
|
const promptActionsWrapper = document.createElement('div');
|
|
68
68
|
promptActionsWrapper.className = 'moodboard-chat__pill-wrapper';
|
|
69
69
|
|
|
70
|
-
const enhancePrompt = createInputIconButton(
|
|
71
|
-
'enhance-prompt',
|
|
72
|
-
'Улучшить промпт',
|
|
73
|
-
ICONS.enhancePrompt
|
|
74
|
-
);
|
|
75
|
-
enhancePrompt.dataset.empty = 'true';
|
|
76
70
|
const extendPromptField = createInputIconButton(
|
|
77
71
|
'extend-promt-field',
|
|
78
72
|
'Развернуть поле ввода',
|
|
79
73
|
ICONS.extendPromptField
|
|
80
74
|
);
|
|
81
75
|
|
|
82
|
-
promptActionsWrapper.appendChild(enhancePrompt);
|
|
83
76
|
promptActionsWrapper.appendChild(extendPromptField);
|
|
84
77
|
textareaRow.appendChild(textarea);
|
|
85
78
|
textareaRow.appendChild(promptActionsWrapper);
|
|
86
79
|
row.appendChild(attachmentsPreview);
|
|
87
80
|
row.appendChild(textareaRow);
|
|
88
81
|
|
|
89
|
-
collect({ textarea,
|
|
82
|
+
collect({ textarea, extendPromptField, attachmentsPreview });
|
|
90
83
|
return row;
|
|
91
84
|
}
|
|
92
85
|
|
package/src/ui/chat/icons.js
CHANGED
|
@@ -39,12 +39,21 @@ const ATTACHMENTS_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="16" hei
|
|
|
39
39
|
/** Кнопка отправки в composer */
|
|
40
40
|
const SEND_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16" aria-hidden="true"><path fill="currentColor" d="M7.453 1.204a.87.87 0 0 1 1.093 0l.066.06 4.962 4.961a.6.6 0 0 1-.849.849L8.6 2.949v11.45a.6.6 0 0 1-1.199 0V2.946L3.273 7.074a.6.6 0 1 1-.847-.849l4.96-4.962z"/></svg>`;
|
|
41
41
|
|
|
42
|
-
/** public/icons/enhance-prompt.svg */
|
|
43
|
-
const ENHANCE_PROMPT_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16" aria-hidden="true"><path fill="currentColor" d="M8 11.771a.6.6 0 1 1 0 1.2L1.6 13a.6.6 0 0 1 0-1.2zm2-4.37A.6.6 0 1 1 10 8.6H1.6a.6.6 0 1 1 0-1.2zM14.4 3a.6.6 0 1 1 0 1.2H1.6a.6.6 0 1 1 0-1.2zm-1.101 5.5a.346.346 0 0 0-.643 0l-.174.417a2.98 2.98 0 0 1-1.538 1.59l-.489.218a.362.362 0 0 0 0 .658l.519.231c.675.3 1.216.85 1.516 1.538l.168.387a.347.347 0 0 0 .639 0l.168-.387c.3-.689.841-1.237 1.516-1.538l.519-.231a.362.362 0 0 0 0-.658l-.49-.218a3 3 0 0 1-1.538-1.59z"/></svg>`;
|
|
44
|
-
|
|
45
42
|
/** public/icons/extend-promt-field.svg */
|
|
46
43
|
const EXTEND_PROMPT_FIELD_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16" aria-hidden="true"><path fill="currentColor" d="M9.696 7.151a.599.599 0 1 1-.847-.847L12.55 2.6H9.272a.6.6 0 0 1 0-1.2H14a.6.6 0 0 1 .6.599v4.728a.6.6 0 1 1-1.2 0V3.449zM1.4 9.272a.6.6 0 1 1 1.2 0v3.28l3.704-3.703a.599.599 0 1 1 .847.847L3.45 13.4h3.279a.6.6 0 0 1 0 1.2H2A.6.6 0 0 1 1.4 14z"/></svg>`;
|
|
47
44
|
|
|
45
|
+
/** public/icons/google.svg — цветной логотип Google 36×36 */
|
|
46
|
+
const MODEL_GOOGLE_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" fill="none" viewBox="0 0 36 36"><path fill="#4285F4" d="M29.251 18.49c0-.813-.073-1.596-.209-2.347H18.23v4.445h6.179c-.272 1.43-1.086 2.64-2.307 3.455v2.89h3.726c2.17-2.003 3.423-4.946 3.423-8.442"/><path fill="#34A853" d="M18.229 29.71c3.1 0 5.698-1.023 7.597-2.776l-3.725-2.89c-1.023.688-2.328 1.105-3.872 1.105-2.985 0-5.52-2.014-6.429-4.727H7.98v2.964c1.89 3.746 5.761 6.324 10.249 6.324"/><path fill="#FBBC05" d="M11.801 20.412a6.9 6.9 0 0 1-.365-2.181c0-.762.136-1.492.365-2.181v-2.964h-3.82A11.34 11.34 0 0 0 6.75 18.23c0 1.858.449 3.6 1.231 5.145l2.975-2.317z"/><path fill="#EA4335" d="M18.229 11.321c1.69 0 3.193.585 4.393 1.712l3.288-3.288c-1.994-1.857-4.582-2.995-7.681-2.995-4.488 0-8.36 2.578-10.249 6.335l3.82 2.964c.908-2.714 3.444-4.728 6.429-4.728"/></svg>`;
|
|
47
|
+
|
|
48
|
+
/** Placeholder OpenAI GPT — буква G в круге, 36×36 */
|
|
49
|
+
const MODEL_GPT_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36" fill="none" aria-hidden="true"><circle cx="18" cy="18" r="16" fill="#10a37f"/><text x="18" y="23" text-anchor="middle" font-size="16" font-family="Arial,sans-serif" fill="#fff" font-weight="bold">G</text></svg>`;
|
|
50
|
+
|
|
51
|
+
/** Placeholder Alibaba Qwen — буква Q в круге, 36×36 */
|
|
52
|
+
const MODEL_QWEN_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36" fill="none" aria-hidden="true"><circle cx="18" cy="18" r="16" fill="#6e42ca"/><text x="18" y="23" text-anchor="middle" font-size="16" font-family="Arial,sans-serif" fill="#fff" font-weight="bold">Q</text></svg>`;
|
|
53
|
+
|
|
54
|
+
/** Placeholder Yandex Alice — буква А в круге, 36×36 */
|
|
55
|
+
const MODEL_ALICE_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36" fill="none" aria-hidden="true"><circle cx="18" cy="18" r="16" fill="#fc3f1d"/><text x="18" y="23" text-anchor="middle" font-size="16" font-family="Arial,sans-serif" fill="#fff" font-weight="bold">А</text></svg>`;
|
|
56
|
+
|
|
48
57
|
export const ICONS = {
|
|
49
58
|
image: IMAGE_ICON,
|
|
50
59
|
video: VIDEO_ICON,
|
|
@@ -57,13 +66,16 @@ export const ICONS = {
|
|
|
57
66
|
palette: svg('<path d="M12 3a9 9 0 0 0 0 18c1.7 0 2-1 2-2 0-1.5 1-2 2-2h2a3 3 0 0 0 3-3c0-5-4-9-9-9z"/><circle cx="7.5" cy="11" r="1"/><circle cx="11" cy="7" r="1"/><circle cx="15" cy="7" r="1"/><circle cx="17.5" cy="11" r="1"/>'),
|
|
58
67
|
attach: ATTACHMENTS_ICON,
|
|
59
68
|
send: SEND_ICON,
|
|
60
|
-
enhancePrompt: ENHANCE_PROMPT_ICON,
|
|
61
69
|
extendPromptField: EXTEND_PROMPT_FIELD_ICON,
|
|
62
70
|
sliders: svg('<path d="M4 7h10M18 7h2M4 17h2M10 17h10"/><circle cx="16" cy="7" r="2"/><circle cx="8" cy="17" r="2"/>'),
|
|
63
71
|
chevronDown: svg('<path d="M6 9l6 6 6-6"/>'),
|
|
64
72
|
trash: svg('<path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M6 6l1 14a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-14"/>'),
|
|
65
73
|
close: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"><path fill="currentColor" d="M13.575 12.726a.6.6 0 0 1-.849.849zm-.849-10.3a.6.6 0 0 1 .849.848L8.848 7.999l4.727 4.727-.425.424-.424.425-4.727-4.727-4.725 4.727a.6.6 0 0 1-.848-.849l4.726-4.727-4.726-4.725a.599.599 0 1 1 .848-.848l4.725 4.726z"></path></svg>`,
|
|
66
|
-
sparkles: svg('<path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/><path d="M20 2v4"/><path d="M22 4h-4"/><circle cx="4" cy="20" r="2"/>')
|
|
74
|
+
sparkles: svg('<path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/><path d="M20 2v4"/><path d="M22 4h-4"/><circle cx="4" cy="20" r="2"/>'),
|
|
75
|
+
modelGoogle: MODEL_GOOGLE_ICON,
|
|
76
|
+
modelGpt: MODEL_GPT_ICON,
|
|
77
|
+
modelQwen: MODEL_QWEN_ICON,
|
|
78
|
+
modelAlice: MODEL_ALICE_ICON,
|
|
67
79
|
};
|
|
68
80
|
|
|
69
81
|
/**
|