@sequent-org/moodboard 1.4.45 → 1.4.48
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 +1 -1
- package/src/core/events/Events.js +1 -0
- package/src/core/flows/ClipboardFlow.js +29 -1
- package/src/core/flows/Model3dFlow.js +15 -0
- package/src/core/index.js +4 -2
- package/src/moodboard/bootstrap/MoodBoardUiFactory.js +2 -0
- package/src/objects/Model3dScreenshotImageObject.js +8 -0
- package/src/objects/ObjectFactory.js +2 -0
- package/src/services/ai/AiClient.js +181 -2
- package/src/services/ai/Model3dSessionController.js +238 -0
- package/src/services/ai/VideoSessionController.js +175 -0
- package/src/services/ai/imageModelCapabilities.js +130 -0
- package/src/services/ai/videoModelCapabilities.js +120 -0
- package/src/ui/ImagePropertiesPanel.js +8 -0
- package/src/ui/chat/ChatComposer.js +50 -3
- package/src/ui/chat/ChatSettingsPopup.js +210 -44
- package/src/ui/chat/ChatVideoToolbarPills.js +284 -0
- package/src/ui/chat/ChatWindow.js +919 -92
- package/src/ui/chat/ChatWindowRenderer.js +29 -1
- package/src/ui/chat/Model3dBoardSkeleton.js +93 -0
- package/src/ui/chat/Model3dProgressOverlay.js +114 -0
- package/src/ui/chat/icons.js +27 -6
- package/src/ui/handles/HandlesDomRenderer.js +30 -0
- package/src/ui/styles/chat.css +235 -0
- package/src/ui/styles/panels.css +264 -0
|
@@ -97,7 +97,7 @@ function buildActionsRow(collect) {
|
|
|
97
97
|
contentTypeWrapper.pill.title = 'Тип генерируемого контента';
|
|
98
98
|
contentTypeWrapper.pill.setAttribute('aria-label', 'Тип генерируемого контента');
|
|
99
99
|
|
|
100
|
-
const modelWrapper = pillWithMenu('
|
|
100
|
+
const modelWrapper = pillWithMenu('Nano Banana Pro', ICONS.model, 'chat-menu-model');
|
|
101
101
|
modelWrapper.pill.title = 'Модель ИИ';
|
|
102
102
|
modelWrapper.pill.setAttribute('aria-label', 'Модель ИИ');
|
|
103
103
|
|
|
@@ -106,6 +106,10 @@ function buildActionsRow(collect) {
|
|
|
106
106
|
formatWrapper.pill.setAttribute('aria-label', 'Формат изображения');
|
|
107
107
|
formatWrapper.menu.classList.add('moodboard-chat__menu--grid');
|
|
108
108
|
|
|
109
|
+
const resolutionWrapper = pillWithMenu('Разрешение', ICONS.ratio, 'chat-menu-resolution');
|
|
110
|
+
resolutionWrapper.pill.title = 'Разрешение изображения';
|
|
111
|
+
resolutionWrapper.pill.setAttribute('aria-label', 'Разрешение изображения');
|
|
112
|
+
|
|
109
113
|
const countWrapper = pillWithMenu('Авто', ICONS.count, 'chat-menu-count');
|
|
110
114
|
countWrapper.pill.title = 'Количество изображений';
|
|
111
115
|
countWrapper.pill.setAttribute('aria-label', 'Количество изображений');
|
|
@@ -113,6 +117,7 @@ function buildActionsRow(collect) {
|
|
|
113
117
|
pills.appendChild(contentTypeWrapper.wrapper);
|
|
114
118
|
pills.appendChild(modelWrapper.wrapper);
|
|
115
119
|
pills.appendChild(formatWrapper.wrapper);
|
|
120
|
+
pills.appendChild(resolutionWrapper.wrapper);
|
|
116
121
|
pills.appendChild(countWrapper.wrapper);
|
|
117
122
|
|
|
118
123
|
const sendRow = createDiv('moodboard-chat__send-row');
|
|
@@ -132,6 +137,22 @@ function buildActionsRow(collect) {
|
|
|
132
137
|
fileInput.setAttribute('aria-hidden', 'true');
|
|
133
138
|
fileInput.setAttribute('tabindex', '-1');
|
|
134
139
|
|
|
140
|
+
// Обёртка настроек — позиционированный контейнер для попапа
|
|
141
|
+
const settingsWrapper = createDiv('moodboard-chat__settings-wrapper');
|
|
142
|
+
settingsWrapper.style.cssText = 'position:relative;display:inline-flex;align-items:center;';
|
|
143
|
+
|
|
144
|
+
const settingsTrigger = document.createElement('button');
|
|
145
|
+
settingsTrigger.type = 'button';
|
|
146
|
+
settingsTrigger.className = 'moodboard-chat__attach';
|
|
147
|
+
settingsTrigger.title = 'Настройки';
|
|
148
|
+
settingsTrigger.setAttribute('aria-label', 'Настройки генерации');
|
|
149
|
+
settingsTrigger.innerHTML = ICONS.sliders;
|
|
150
|
+
|
|
151
|
+
const settingsPopup = createDiv('moodboard-chat__settings-popup');
|
|
152
|
+
|
|
153
|
+
settingsWrapper.appendChild(settingsTrigger);
|
|
154
|
+
settingsWrapper.appendChild(settingsPopup);
|
|
155
|
+
|
|
135
156
|
const send = document.createElement('button');
|
|
136
157
|
send.type = 'button';
|
|
137
158
|
send.className = 'moodboard-chat__send';
|
|
@@ -141,6 +162,7 @@ function buildActionsRow(collect) {
|
|
|
141
162
|
|
|
142
163
|
sendRow.appendChild(attach);
|
|
143
164
|
sendRow.appendChild(fileInput);
|
|
165
|
+
sendRow.appendChild(settingsWrapper);
|
|
144
166
|
sendRow.appendChild(send);
|
|
145
167
|
|
|
146
168
|
row.appendChild(pills);
|
|
@@ -158,12 +180,18 @@ function buildActionsRow(collect) {
|
|
|
158
180
|
formatPill: formatWrapper.pill,
|
|
159
181
|
formatMenu: formatWrapper.menu,
|
|
160
182
|
formatLabel: formatWrapper.labelEl,
|
|
183
|
+
resolutionPill: resolutionWrapper.pill,
|
|
184
|
+
resolutionMenu: resolutionWrapper.menu,
|
|
185
|
+
resolutionLabel: resolutionWrapper.labelEl,
|
|
186
|
+
resolutionWrapper: resolutionWrapper.wrapper,
|
|
161
187
|
countPill: countWrapper.pill,
|
|
162
188
|
countMenu: countWrapper.menu,
|
|
163
189
|
countLabel: countWrapper.labelEl,
|
|
164
190
|
countIcon: countWrapper.iconEl,
|
|
165
191
|
attach,
|
|
166
192
|
fileInput,
|
|
193
|
+
settingsTrigger,
|
|
194
|
+
settingsPopup,
|
|
167
195
|
send
|
|
168
196
|
});
|
|
169
197
|
return row;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM-скелетон 3D-модели на полотне доски.
|
|
3
|
+
*
|
|
4
|
+
* Одна ответственность: показывать анимированную заглушку в том месте,
|
|
5
|
+
* где появится сгенерированная 3D-модель. Не знает про сессию и вьюпорт —
|
|
6
|
+
* экранный прямоугольник ему задаёт ChatWindow (он же держит мировые координаты
|
|
7
|
+
* и пересчитывает позицию при pan/zoom).
|
|
8
|
+
*
|
|
9
|
+
* Lifecycle: attach(container) → setRect(rect)* → enter() → detach() → destroy()
|
|
10
|
+
* attach/detach идемпотентны.
|
|
11
|
+
*/
|
|
12
|
+
export class Model3dBoardSkeleton {
|
|
13
|
+
/**
|
|
14
|
+
* @param {object} [opts]
|
|
15
|
+
* @param {string} [opts.iconSvg] HTML-строка иконки (например ICONS.cube)
|
|
16
|
+
*/
|
|
17
|
+
constructor({ iconSvg = '' } = {}) {
|
|
18
|
+
this._el = null;
|
|
19
|
+
this._iconSvg = iconSvg;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {HTMLElement} container
|
|
24
|
+
*/
|
|
25
|
+
attach(container) {
|
|
26
|
+
if (this._el) return;
|
|
27
|
+
|
|
28
|
+
const el = document.createElement('div');
|
|
29
|
+
el.className = 'moodboard-chat__3d-skeleton moodboard-chat__3d-skeleton--enter';
|
|
30
|
+
|
|
31
|
+
if (this._iconSvg) {
|
|
32
|
+
const icon = document.createElement('span');
|
|
33
|
+
icon.className = 'moodboard-chat__3d-skeleton-icon';
|
|
34
|
+
icon.innerHTML = this._iconSvg;
|
|
35
|
+
el.appendChild(icon);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this._el = el;
|
|
39
|
+
(container ?? document.body).appendChild(el);
|
|
40
|
+
|
|
41
|
+
// Принудительный reflow фиксирует стартовое состояние (--enter) до enter(),
|
|
42
|
+
// иначе браузер смерджит кадры и transition не запустится.
|
|
43
|
+
void el.offsetWidth;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Задаёт экранный прямоугольник (CSS-пиксели вьюпорта, position: fixed).
|
|
48
|
+
* @param {{ left:number, top:number, width:number, height:number, radius?:number }} rect
|
|
49
|
+
* @param {{ animate?: boolean }} [opts]
|
|
50
|
+
*/
|
|
51
|
+
setRect({ left, top, width, height, radius }, { animate = false } = {}) {
|
|
52
|
+
if (!this._el) return;
|
|
53
|
+
const el = this._el;
|
|
54
|
+
|
|
55
|
+
if (!animate) el.style.transition = 'none';
|
|
56
|
+
|
|
57
|
+
el.style.left = `${Math.round(left)}px`;
|
|
58
|
+
el.style.top = `${Math.round(top)}px`;
|
|
59
|
+
el.style.width = `${Math.round(width)}px`;
|
|
60
|
+
el.style.height = `${Math.round(height)}px`;
|
|
61
|
+
if (typeof radius === 'number') {
|
|
62
|
+
el.style.borderRadius = `${Math.max(2, Math.round(radius))}px`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!animate) {
|
|
66
|
+
void el.offsetWidth;
|
|
67
|
+
el.style.removeProperty('transition');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Запускает анимацию появления. */
|
|
72
|
+
enter() {
|
|
73
|
+
if (!this._el) return;
|
|
74
|
+
this._el.classList.remove('moodboard-chat__3d-skeleton--enter');
|
|
75
|
+
this._el.classList.add('moodboard-chat__3d-skeleton--entered');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** @returns {boolean} */
|
|
79
|
+
isAttached() {
|
|
80
|
+
return !!this._el;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
detach() {
|
|
84
|
+
if (this._el && this._el.parentNode) {
|
|
85
|
+
this._el.parentNode.removeChild(this._el);
|
|
86
|
+
}
|
|
87
|
+
this._el = null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
destroy() {
|
|
91
|
+
this.detach();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM-заглушка прогресса генерации 3D-модели.
|
|
3
|
+
*
|
|
4
|
+
* Одна ответственность: показывать стадии (геометрия / текстура) и прогресс-бар
|
|
5
|
+
* поверх composer-контейнера. Не знает ни про сессию, ни про доску.
|
|
6
|
+
*
|
|
7
|
+
* Lifecycle: attach(container) → update(state)* → detach() → destroy()
|
|
8
|
+
* attach/detach идемпотентны. Listeners не дублируются.
|
|
9
|
+
*/
|
|
10
|
+
export class Model3dProgressOverlay {
|
|
11
|
+
constructor() {
|
|
12
|
+
this._el = null;
|
|
13
|
+
this._barFill = null;
|
|
14
|
+
this._stageLabel = null;
|
|
15
|
+
this._percentLabel = null;
|
|
16
|
+
this._container = null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Добавляет оверлей в container.
|
|
21
|
+
* @param {HTMLElement} container
|
|
22
|
+
*/
|
|
23
|
+
attach(container) {
|
|
24
|
+
if (this._el) return;
|
|
25
|
+
this._container = container;
|
|
26
|
+
|
|
27
|
+
const el = document.createElement('div');
|
|
28
|
+
el.className = 'moodboard-chat__3d-overlay';
|
|
29
|
+
|
|
30
|
+
const inner = document.createElement('div');
|
|
31
|
+
inner.className = 'moodboard-chat__3d-overlay-inner';
|
|
32
|
+
|
|
33
|
+
const stageLabel = document.createElement('span');
|
|
34
|
+
stageLabel.className = 'moodboard-chat__3d-overlay-stage';
|
|
35
|
+
stageLabel.textContent = 'Генерация геометрии…';
|
|
36
|
+
|
|
37
|
+
const barWrap = document.createElement('div');
|
|
38
|
+
barWrap.className = 'moodboard-chat__3d-overlay-bar-wrap';
|
|
39
|
+
|
|
40
|
+
const barFill = document.createElement('div');
|
|
41
|
+
barFill.className = 'moodboard-chat__3d-overlay-bar-fill';
|
|
42
|
+
barFill.style.width = '0%';
|
|
43
|
+
|
|
44
|
+
const percentLabel = document.createElement('span');
|
|
45
|
+
percentLabel.className = 'moodboard-chat__3d-overlay-percent';
|
|
46
|
+
percentLabel.textContent = '0%';
|
|
47
|
+
|
|
48
|
+
barWrap.appendChild(barFill);
|
|
49
|
+
inner.appendChild(stageLabel);
|
|
50
|
+
inner.appendChild(barWrap);
|
|
51
|
+
inner.appendChild(percentLabel);
|
|
52
|
+
el.appendChild(inner);
|
|
53
|
+
|
|
54
|
+
this._el = el;
|
|
55
|
+
this._barFill = barFill;
|
|
56
|
+
this._stageLabel = stageLabel;
|
|
57
|
+
this._percentLabel = percentLabel;
|
|
58
|
+
|
|
59
|
+
container.appendChild(el);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Обновляет отображение по состоянию Model3dSessionController.
|
|
64
|
+
* @param {{ status: string, progress: number, stage: string|null, error: string|null }} state
|
|
65
|
+
*/
|
|
66
|
+
update(state) {
|
|
67
|
+
if (!this._el) return;
|
|
68
|
+
|
|
69
|
+
const { status, progress, stage, error } = state;
|
|
70
|
+
|
|
71
|
+
if (status === 'error') {
|
|
72
|
+
this._el.classList.add('is-error');
|
|
73
|
+
this._stageLabel.textContent = error ? `Ошибка: ${error}` : 'Ошибка генерации';
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
this._el.classList.remove('is-error');
|
|
78
|
+
|
|
79
|
+
const pct = Math.round(Math.min(100, Math.max(0, progress ?? 0)));
|
|
80
|
+
|
|
81
|
+
// Пока прогресс равен нулю и API ещё не вернул данные (submitting / начало polling),
|
|
82
|
+
// показываем indeterminate-полоску — пользователь видит активную работу, а не «0% = завис».
|
|
83
|
+
const isIndeterminate = pct === 0;
|
|
84
|
+
this._barFill.classList.toggle('is-indeterminate', isIndeterminate);
|
|
85
|
+
if (!isIndeterminate) {
|
|
86
|
+
this._barFill.style.width = `${pct}%`;
|
|
87
|
+
}
|
|
88
|
+
this._percentLabel.textContent = `${pct}%`;
|
|
89
|
+
|
|
90
|
+
if (stage === 'texture') {
|
|
91
|
+
this._stageLabel.textContent = 'Генерация текстуры…';
|
|
92
|
+
} else if (status === 'submitting') {
|
|
93
|
+
this._stageLabel.textContent = 'Отправка запроса…';
|
|
94
|
+
} else {
|
|
95
|
+
this._stageLabel.textContent = 'Генерация геометрии…';
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Убирает DOM-элемент из контейнера, не разрушая объект. */
|
|
100
|
+
detach() {
|
|
101
|
+
if (this._el && this._el.parentNode) {
|
|
102
|
+
this._el.parentNode.removeChild(this._el);
|
|
103
|
+
}
|
|
104
|
+
this._el = null;
|
|
105
|
+
this._barFill = null;
|
|
106
|
+
this._stageLabel = null;
|
|
107
|
+
this._percentLabel = null;
|
|
108
|
+
this._container = null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
destroy() {
|
|
112
|
+
this.detach();
|
|
113
|
+
}
|
|
114
|
+
}
|
package/src/ui/chat/icons.js
CHANGED
|
@@ -45,20 +45,27 @@ const EXTEND_PROMPT_FIELD_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width=
|
|
|
45
45
|
/** public/icons/google.svg — цветной логотип Google 36×36 */
|
|
46
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
47
|
|
|
48
|
-
/** public/icons/gpt.svg — логотип
|
|
49
|
-
const MODEL_GPT_ICON = `<
|
|
48
|
+
/** public/icons/gpt.svg — логотип OpenAI 36×36 (currentColor) */
|
|
49
|
+
const MODEL_GPT_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" fill="currentColor" fill-rule="evenodd" viewBox="0 0 24 24" aria-hidden="true"><path d="M9.205 8.658v-2.26c0-.19.072-.333.238-.428l4.543-2.616c.619-.357 1.356-.523 2.117-.523 2.854 0 4.662 2.212 4.662 4.566 0 .167 0 .357-.024.547l-4.71-2.759a.797.797 0 00-.856 0l-5.97 3.473zm10.609 8.8V12.06c0-.333-.143-.57-.429-.737l-5.97-3.473 1.95-1.118a.433.433 0 01.476 0l4.543 2.617c1.309.76 2.189 2.378 2.189 3.948 0 1.808-1.07 3.473-2.76 4.163zM7.802 12.703l-1.95-1.142c-.167-.095-.239-.238-.239-.428V5.899c0-2.545 1.95-4.472 4.591-4.472 1 0 1.927.333 2.712.928L8.23 5.067c-.285.166-.428.404-.428.737v6.898zM12 15.128l-2.795-1.57v-3.33L12 8.658l2.795 1.57v3.33L12 15.128zm1.796 7.23c-1 0-1.927-.332-2.712-.927l4.686-2.712c.285-.166.428-.404.428-.737v-6.898l1.974 1.142c.167.095.238.238.238.428v5.233c0 2.545-1.974 4.472-4.614 4.472zm-5.637-5.303l-4.544-2.617c-1.308-.761-2.188-2.378-2.188-3.948A4.482 4.482 0 014.21 6.327v5.423c0 .333.143.571.428.738l5.947 3.449-1.95 1.118a.432.432 0 01-.476 0zm-.262 3.9c-2.688 0-4.662-2.021-4.662-4.519 0-.19.024-.38.047-.57l4.686 2.71c.286.167.571.167.856 0l5.97-3.448v2.26c0 .19-.07.333-.237.428l-4.543 2.616c-.619.357-1.356.523-2.117.523zm5.899 2.83a5.947 5.947 0 005.827-4.756C22.287 18.339 24 15.84 24 13.296c0-1.665-.713-3.282-1.998-4.448.119-.5.19-.999.19-1.498 0-3.401-2.759-5.947-5.946-5.947-.642 0-1.26.095-1.88.31A5.962 5.962 0 0010.205 0a5.947 5.947 0 00-5.827 4.757C1.713 5.447 0 7.945 0 10.49c0 1.666.713 3.283 1.998 4.448-.119.5-.19 1-.19 1.499 0 3.401 2.759 5.946 5.946 5.946.642 0 1.26-.095 1.88-.309a5.96 5.96 0 004.162 1.713z"/></svg>`;
|
|
50
50
|
|
|
51
|
-
/**
|
|
52
|
-
const MODEL_QWEN_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0
|
|
51
|
+
/** public/icons/qwen.svg — логотип Alibaba Qwen 36×36 (фирменный градиент) */
|
|
52
|
+
const MODEL_QWEN_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24" aria-hidden="true"><path d="M12.604 1.34c.393.69.784 1.382 1.174 2.075a.18.18 0 00.157.091h5.552c.174 0 .322.11.446.327l1.454 2.57c.19.337.24.478.024.837-.26.43-.513.864-.76 1.3l-.367.658c-.106.196-.223.28-.04.512l2.652 4.637c.172.301.111.494-.043.77-.437.785-.882 1.564-1.335 2.34-.159.272-.352.375-.68.37-.777-.016-1.552-.01-2.327.016a.099.099 0 00-.081.05 575.097 575.097 0 01-2.705 4.74c-.169.293-.38.363-.725.364-.997.003-2.002.004-3.017.002a.537.537 0 01-.465-.271l-1.335-2.323a.09.09 0 00-.083-.049H4.982c-.285.03-.553-.001-.805-.092l-1.603-2.77a.543.543 0 01-.002-.54l1.207-2.12a.198.198 0 000-.197 550.951 550.951 0 01-1.875-3.272l-.79-1.395c-.16-.31-.173-.496.095-.965.465-.813.927-1.625 1.387-2.436.132-.234.304-.334.584-.335a338.3 338.3 0 012.589-.001.124.124 0 00.107-.063l2.806-4.895a.488.488 0 01.422-.246c.524-.001 1.053 0 1.583-.006L11.704 1c.341-.003.724.032.9.34zm-3.432.403a.06.06 0 00-.052.03L6.254 6.788a.157.157 0 01-.135.078H3.253c-.056 0-.07.025-.041.074l5.81 10.156c.025.042.013.062-.034.063l-2.795.015a.218.218 0 00-.2.116l-1.32 2.31c-.044.078-.021.118.068.118l5.716.008c.046 0 .08.02.104.061l1.403 2.454c.046.081.092.082.139 0l5.006-8.76.783-1.382a.055.055 0 01.096 0l1.424 2.53a.122.122 0 00.107.062l2.763-.02a.04.04 0 00.035-.02.041.041 0 000-.04l-2.9-5.086a.108.108 0 010-.113l.293-.507 1.12-1.977c.024-.041.012-.062-.035-.062H9.2c-.059 0-.073-.026-.043-.077l1.434-2.505a.107.107 0 000-.114L9.225 1.774a.06.06 0 00-.053-.031zm6.29 8.02c.046 0 .058.02.034.06l-.832 1.465-2.613 4.585a.056.056 0 01-.05.029.058.058 0 01-.05-.029L8.498 9.841c-.02-.034-.01-.052.028-.054l.216-.012 6.722-.012z" fill="url(#mb-icon-qwen-fill)" fill-rule="nonzero"/><defs><linearGradient id="mb-icon-qwen-fill" x1="0%" x2="100%" y1="0%" y2="0%"><stop offset="0%" stop-color="#6336E7" stop-opacity=".84"/><stop offset="100%" stop-color="#6F69F7" stop-opacity=".84"/></linearGradient></defs></svg>`;
|
|
53
53
|
|
|
54
|
-
/**
|
|
55
|
-
const MODEL_ALICE_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36"
|
|
54
|
+
/** public/icons/yandex.svg — логотип Yandex 36×36 (фирменный красный) */
|
|
55
|
+
const MODEL_ALICE_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" fill="#FC3F1D" fill-rule="evenodd" viewBox="0 0 24 24" aria-hidden="true"><path d="M16.376 12.644L21 2h-3.842l-4.624 10.644h3.842zM13.915 24v-3.733c0-2.822-.352-3.64-1.407-5.988L6.933 2H3l7.124 15.709V24h3.79z"/></svg>`;
|
|
56
|
+
|
|
57
|
+
/** public/icons/kling.svg — логотип Kling (Kuaishou) 36×36 (фирменный градиент) */
|
|
58
|
+
const MODEL_KLING_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24" aria-hidden="true"><path d="M5.412 13.775A23.193 23.193 0 017.41 9.32c3.17-5.492 7.795-8.757 10.33-7.294C12.038-1.266 4.598.944 1.122 6.964A13.378 13.378 0 00.085 9.22c-.259.739.092 1.534.77 1.926l4.557 2.63z" fill="url(#mb-icon-kling-0)"/><path d="M18.588 10.164a23.188 23.188 0 01-1.999 4.455c-3.17 5.492-7.795 8.758-10.33 7.294 5.703 3.293 13.143 1.082 16.619-4.938a13.392 13.392 0 001.037-2.255c.259-.738-.092-1.534-.77-1.925l-4.557-2.63z" fill="url(#mb-icon-kling-1)"/><path d="M16.59 14.62c3.17-5.492 3.686-11.13 1.15-12.594C15.207.563 10.582 3.83 7.41 9.32c2.074-3.59 5.809-5.315 8.344-3.852 2.534 1.464 2.908 5.56.835 9.151z" fill="url(#mb-icon-kling-2)"/><path d="M7.41 9.32c-3.17 5.492-3.686 11.13-1.15 12.593 2.534 1.464 7.159-1.802 10.33-7.294-2.074 3.591-5.809 5.316-8.344 3.852-2.534-1.463-2.908-5.56-.835-9.15z" fill="url(#mb-icon-kling-3)"/><defs><radialGradient cx="0" cy="0" gradientTransform="matrix(7.47772 -12.51022 17.14368 10.24728 5.173 13.637)" gradientUnits="userSpaceOnUse" id="mb-icon-kling-0" r="1"><stop offset=".095" stop-color="#FFF959"/><stop offset=".326" stop-color="#0DF35E"/><stop offset=".64" stop-color="#0BF2F9"/><stop offset="1" stop-color="#04A6F0"/></radialGradient><radialGradient cx="0" cy="0" gradientTransform="rotate(120.868 6.491 10.491) scale(14.5747 19.9728)" gradientUnits="userSpaceOnUse" id="mb-icon-kling-1" r="1"><stop offset=".095" stop-color="#FFF959"/><stop offset=".326" stop-color="#0DF35E"/><stop offset=".64" stop-color="#0BF2F9"/><stop offset="1" stop-color="#04A6F0"/></radialGradient><linearGradient gradientUnits="userSpaceOnUse" id="mb-icon-kling-2" x1="15.578" x2="18.062" y1="1.798" y2="9.861"><stop stop-color="#003EFF"/><stop offset="1" stop-color="#0BFFE7"/></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="mb-icon-kling-3" x1="8.422" x2="5.938" y1="22.142" y2="14.079"><stop stop-color="#003EFF"/><stop offset="1" stop-color="#0BFFE7"/></linearGradient></defs></svg>`;
|
|
59
|
+
|
|
60
|
+
/** public/icons/seedance.svg — логотип ByteDance (Seedance) 36×36 (фирменные цвета) */
|
|
61
|
+
const MODEL_SEEDANCE_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24" aria-hidden="true"><path d="M14.944 18.587l-1.704-.445V10.01l1.824-.462c1-.254 1.84-.461 1.88-.453.032 0 .056 2.235.056 4.972v4.973l-.176-.008c-.104 0-.952-.207-1.88-.446z" fill="#00C8D2" fill-rule="nonzero"/><path d="M7 16.542c0-2.736.024-4.98.064-4.98.032-.008.872.2 1.88.454l1.816.461-.016 4.05-.024 4.049-1.632.422c-.896.23-1.736.445-1.856.469L7 21.523v-4.98z" fill="#3C8CFF" fill-rule="nonzero"/><path d="M19.24 12.477c0-9.03.008-9.515.144-9.475.072.024.784.207 1.576.406.792.207 1.576.405 1.744.445l.296.08-.016 8.56-.024 8.568-1.624.414c-.888.23-1.728.437-1.856.47l-.24.055v-9.523z" fill="#78E6DC" fill-rule="nonzero"/><path d="M1 12.509c0-4.678.024-8.505.064-8.505.032 0 .872.207 1.872.454l1.824.461v7.582c0 4.16-.016 7.574-.032 7.574-.024 0-.872.215-1.88.47L1 21.013v-8.505z" fill="#325AB4"/></svg>`;
|
|
56
62
|
|
|
57
63
|
/** Иконка «Автоматический режим» — робот, 36×36, outline */
|
|
58
64
|
const MODEL_BOT_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 8V4H8"/><rect width="16" height="12" x="4" y="8" rx="2"/><path d="M2 14h2"/><path d="M20 14h2"/><path d="M15 13v2"/><path d="M9 13v2"/></svg>`;
|
|
59
65
|
|
|
60
66
|
export const ICONS = {
|
|
61
67
|
bot: svg('<path d="M12 8V4H8"/><rect width="16" height="12" x="4" y="8" rx="2"/><path d="M2 14h2"/><path d="M20 14h2"/><path d="M15 13v2"/><path d="M9 13v2"/>'),
|
|
68
|
+
cube: svg('<path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/>'),
|
|
62
69
|
modelBot: MODEL_BOT_ICON,
|
|
63
70
|
image: IMAGE_ICON,
|
|
64
71
|
video: VIDEO_ICON,
|
|
@@ -73,6 +80,13 @@ export const ICONS = {
|
|
|
73
80
|
send: SEND_ICON,
|
|
74
81
|
extendPromptField: EXTEND_PROMPT_FIELD_ICON,
|
|
75
82
|
sliders: svg('<path d="M4 7h10M18 7h2M4 17h2M10 17h10"/><circle cx="16" cy="7" r="2"/><circle cx="8" cy="17" r="2"/>'),
|
|
83
|
+
audio: svg('<path d="M11 5 6 9H2v6h4l5 4z"/><path d="M15.5 8.5a5 5 0 0 1 0 7"/><path d="M19 5a9 9 0 0 1 0 14"/>'),
|
|
84
|
+
audioOff: svg('<path d="M11 5 6 9H2v6h4l5 4z"/><path d="M22 9l-6 6M16 9l6 6"/>'),
|
|
85
|
+
watermark: svg('<path d="M12 2.7 6.7 9.2a6.5 6.5 0 1 0 10.6 0z"/>'),
|
|
86
|
+
users: svg('<circle cx="9" cy="8" r="3.2"/><path d="M3 20c0-3.3 2.7-5 6-5s6 1.7 6 5"/><path d="M16 5.4a3.2 3.2 0 0 1 0 6"/><path d="M21 20c0-2.6-1.6-4.3-4-4.8"/>'),
|
|
87
|
+
seed: svg('<path d="M9 4 7 20M17 4l-2 16M4 9h16M3 15h16"/>'),
|
|
88
|
+
gauge: svg('<path d="M5 18a8 8 0 1 1 14 0"/><path d="M12 14l4-3"/>'),
|
|
89
|
+
negative: svg('<circle cx="12" cy="12" r="9"/><path d="M8 12h8"/>'),
|
|
76
90
|
chevronDown: svg('<path d="M6 9l6 6 6-6"/>'),
|
|
77
91
|
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"/>'),
|
|
78
92
|
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>`,
|
|
@@ -81,6 +95,8 @@ export const ICONS = {
|
|
|
81
95
|
modelGpt: MODEL_GPT_ICON,
|
|
82
96
|
modelQwen: MODEL_QWEN_ICON,
|
|
83
97
|
modelAlice: MODEL_ALICE_ICON,
|
|
98
|
+
modelKling: MODEL_KLING_ICON,
|
|
99
|
+
modelSeedance: MODEL_SEEDANCE_ICON,
|
|
84
100
|
};
|
|
85
101
|
|
|
86
102
|
/**
|
|
@@ -119,6 +135,8 @@ export const RATIO_ICONS = {
|
|
|
119
135
|
'2:3': ratioSvgIcon(6.437, false, false, 11.983),
|
|
120
136
|
'9:16': ratioSvgIcon(7.907, false, false, 9.9),
|
|
121
137
|
'1:2': ratioSvgIcon(7.961, false, false, 8.65),
|
|
138
|
+
'1:4': ratioSvgIcon(10.17, false, false, 4.66),
|
|
139
|
+
'1:8': ratioSvgIcon(11.34, false, false, 2.33),
|
|
122
140
|
/* авто + альбомные (повёрнутые портреты) */
|
|
123
141
|
'auto': ratioSvgIcon(3.389, false, true, 18.65),
|
|
124
142
|
'5:4': ratioSvgIcon(4.818, true),
|
|
@@ -127,4 +145,7 @@ export const RATIO_ICONS = {
|
|
|
127
145
|
'3:2': ratioSvgIcon(6.437, true, false, 11.983),
|
|
128
146
|
'16:9': ratioSvgIcon(7.907, true, false, 9.9),
|
|
129
147
|
'2:1': ratioSvgIcon(7.961, true, false, 8.65),
|
|
148
|
+
'21:9': ratioSvgIcon(8.5, true, false, 8.0),
|
|
149
|
+
'4:1': ratioSvgIcon(10.17, true, false, 4.66),
|
|
150
|
+
'8:1': ratioSvgIcon(11.34, true, false, 2.33),
|
|
130
151
|
};
|
|
@@ -10,6 +10,7 @@ import { MindmapStatePatchCommand } from '../../core/commands/MindmapStatePatchC
|
|
|
10
10
|
|
|
11
11
|
const HANDLES_ACCENT_COLOR = '#80D8FF';
|
|
12
12
|
const REVIT_SHOW_IN_MODEL_ICON_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" aria-hidden="true" focusable="false"><path d="M384 64C366.3 64 352 78.3 352 96C352 113.7 366.3 128 384 128L466.7 128L265.3 329.4C252.8 341.9 252.8 362.2 265.3 374.7C277.8 387.2 298.1 387.2 310.6 374.7L512 173.3L512 256C512 273.7 526.3 288 544 288C561.7 288 576 273.7 576 256L576 96C576 78.3 561.7 64 544 64L384 64zM144 160C99.8 160 64 195.8 64 240L64 496C64 540.2 99.8 576 144 576L400 576C444.2 576 480 540.2 480 496L480 416C480 398.3 465.7 384 448 384C430.3 384 416 398.3 416 416L416 496C416 504.8 408.8 512 400 512L144 512C135.2 512 128 504.8 128 496L128 240C128 231.2 135.2 224 144 224L224 224C241.7 224 256 209.7 256 192C256 174.3 241.7 160 224 160L144 160z"/></svg>';
|
|
13
|
+
const MODEL3D_SHOW_IN_VIEWER_ICON_SVG = REVIT_SHOW_IN_MODEL_ICON_SVG;
|
|
13
14
|
const MINDMAP_CHILD_WIDTH_FACTOR = 0.9;
|
|
14
15
|
const MINDMAP_CHILD_HEIGHT_FACTOR = 0.8;
|
|
15
16
|
const MINDMAP_CHILD_PADDING_FACTOR = 0.5;
|
|
@@ -1158,6 +1159,9 @@ export class HandlesDomRenderer {
|
|
|
1158
1159
|
let isMindmapOnlyGroupTarget = false;
|
|
1159
1160
|
let isRevitScreenshotTarget = false;
|
|
1160
1161
|
let revitViewPayload = null;
|
|
1162
|
+
let isModel3dScreenshotTarget = false;
|
|
1163
|
+
let model3dModelUrl = null;
|
|
1164
|
+
let model3dFormat = null;
|
|
1161
1165
|
let sourceMindmapProperties = null;
|
|
1162
1166
|
const occupiedOutgoingSides = new Set();
|
|
1163
1167
|
const hiddenIncomingSide = { value: null };
|
|
@@ -1170,6 +1174,9 @@ export class HandlesDomRenderer {
|
|
|
1170
1174
|
isMindmapTarget = mbType === 'mindmap';
|
|
1171
1175
|
isRevitScreenshotTarget = mbType === 'revit-screenshot-img';
|
|
1172
1176
|
revitViewPayload = req.pixiObject?._mb?.properties?.view || null;
|
|
1177
|
+
isModel3dScreenshotTarget = mbType === 'model3d-screenshot-img';
|
|
1178
|
+
model3dModelUrl = req.pixiObject?._mb?.properties?.modelUrl || null;
|
|
1179
|
+
model3dFormat = req.pixiObject?._mb?.properties?.format || null;
|
|
1173
1180
|
if (isMindmapTarget) {
|
|
1174
1181
|
sourceMindmapProperties = req.pixiObject?._mb?.properties || null;
|
|
1175
1182
|
const allObjects = this.host.core?.state?.state?.objects || [];
|
|
@@ -1667,6 +1674,29 @@ export class HandlesDomRenderer {
|
|
|
1667
1674
|
}
|
|
1668
1675
|
}
|
|
1669
1676
|
|
|
1677
|
+
if (isModel3dScreenshotTarget && typeof model3dModelUrl === 'string' && model3dModelUrl.length > 0) {
|
|
1678
|
+
const showInViewerButton = document.createElement('button');
|
|
1679
|
+
showInViewerButton.type = 'button';
|
|
1680
|
+
showInViewerButton.className = 'mb-revit-show-in-model';
|
|
1681
|
+
showInViewerButton.innerHTML = `${MODEL3D_SHOW_IN_VIEWER_ICON_SVG}<span>Показать в модели</span>`;
|
|
1682
|
+
showInViewerButton.style.left = `${Math.round(left + width / 2)}px`;
|
|
1683
|
+
showInViewerButton.style.top = `${Math.round(top - 34)}px`;
|
|
1684
|
+
showInViewerButton.addEventListener('pointerdown', (evt) => {
|
|
1685
|
+
evt.preventDefault();
|
|
1686
|
+
evt.stopPropagation();
|
|
1687
|
+
});
|
|
1688
|
+
showInViewerButton.addEventListener('click', (evt) => {
|
|
1689
|
+
evt.preventDefault();
|
|
1690
|
+
evt.stopPropagation();
|
|
1691
|
+
this.host.eventBus.emit(Events.UI.Model3dShowInViewer, {
|
|
1692
|
+
objectId: id,
|
|
1693
|
+
modelUrl: model3dModelUrl,
|
|
1694
|
+
format: model3dFormat,
|
|
1695
|
+
});
|
|
1696
|
+
});
|
|
1697
|
+
this.host.layer.appendChild(showInViewerButton);
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1670
1700
|
if (isRevitScreenshotTarget && typeof revitViewPayload === 'string' && revitViewPayload.length > 0) {
|
|
1671
1701
|
const showInModelButton = document.createElement('button');
|
|
1672
1702
|
showInModelButton.type = 'button';
|