@productmaker/mcp 2.8.3 → 2.9.1
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/README.md +14 -8
- package/dist/http.js +801 -721
- package/dist/stdio.js +801 -721
- package/dist/ui/meta-upload-app.html +973 -436
- package/package.json +3 -3
|
@@ -91,120 +91,102 @@
|
|
|
91
91
|
.app {
|
|
92
92
|
width: min(100%, 760px);
|
|
93
93
|
margin: 0 auto;
|
|
94
|
-
padding:
|
|
94
|
+
padding: 12px;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
.card {
|
|
98
98
|
overflow: hidden;
|
|
99
99
|
border: 1px solid var(--pm-line);
|
|
100
|
-
border-radius:
|
|
100
|
+
border-radius: 26px;
|
|
101
101
|
background: var(--pm-card);
|
|
102
102
|
box-shadow: var(--pm-shadow);
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
.hero,
|
|
106
|
-
.section {
|
|
107
|
-
padding: 22px;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
105
|
.hero {
|
|
111
|
-
|
|
106
|
+
padding: 19px 22px 14px;
|
|
107
|
+
border-bottom: 1px solid
|
|
108
|
+
color-mix(in srgb, var(--pm-line) 65%, transparent);
|
|
112
109
|
background:
|
|
113
110
|
radial-gradient(
|
|
114
111
|
circle at 100% 0%,
|
|
115
112
|
var(--pm-primary-soft),
|
|
116
|
-
transparent
|
|
113
|
+
transparent 48%
|
|
117
114
|
),
|
|
118
115
|
var(--pm-card);
|
|
119
116
|
}
|
|
120
117
|
|
|
121
|
-
.
|
|
122
|
-
display:
|
|
118
|
+
.upload-header {
|
|
119
|
+
display: flex;
|
|
123
120
|
align-items: center;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
color: var(--pm-primary-strong);
|
|
127
|
-
font-size: 12px;
|
|
128
|
-
font-weight: 800;
|
|
129
|
-
letter-spacing: 0.08em;
|
|
130
|
-
text-transform: uppercase;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
.eyebrow-dot {
|
|
134
|
-
width: 8px;
|
|
135
|
-
height: 8px;
|
|
136
|
-
border-radius: 999px;
|
|
137
|
-
background: var(--pm-primary);
|
|
138
|
-
box-shadow: 0 0 0 5px var(--pm-primary-soft);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
h1,
|
|
142
|
-
h2,
|
|
143
|
-
p {
|
|
144
|
-
margin-top: 0;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
h1 {
|
|
148
|
-
margin-bottom: 8px;
|
|
149
|
-
font-size: clamp(23px, 5vw, 34px);
|
|
150
|
-
line-height: 1.12;
|
|
151
|
-
letter-spacing: -0.03em;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
h2 {
|
|
155
|
-
margin-bottom: 6px;
|
|
156
|
-
font-size: 18px;
|
|
121
|
+
justify-content: space-between;
|
|
122
|
+
gap: 12px;
|
|
157
123
|
}
|
|
158
124
|
|
|
159
|
-
.
|
|
160
|
-
|
|
125
|
+
.upload-title {
|
|
126
|
+
min-width: 0;
|
|
127
|
+
margin: 0;
|
|
128
|
+
overflow: hidden;
|
|
129
|
+
font-size: clamp(17px, 3vw, 24px);
|
|
130
|
+
font-weight: 760;
|
|
131
|
+
letter-spacing: -0.025em;
|
|
132
|
+
text-overflow: ellipsis;
|
|
133
|
+
white-space: nowrap;
|
|
161
134
|
}
|
|
162
135
|
|
|
163
|
-
.
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
136
|
+
.brand-mark {
|
|
137
|
+
display: grid;
|
|
138
|
+
width: 34px;
|
|
139
|
+
height: 34px;
|
|
140
|
+
flex: 0 0 auto;
|
|
141
|
+
place-items: center;
|
|
142
|
+
border: 1px solid
|
|
143
|
+
color-mix(in srgb, var(--pm-primary) 20%, var(--pm-line));
|
|
144
|
+
border-radius: 50%;
|
|
145
|
+
background: color-mix(
|
|
146
|
+
in srgb,
|
|
147
|
+
var(--pm-primary-soft) 70%,
|
|
148
|
+
var(--pm-card)
|
|
149
|
+
);
|
|
150
|
+
color: var(--pm-primary-strong);
|
|
151
|
+
font-size: 20px;
|
|
152
|
+
font-weight: 800;
|
|
153
|
+
line-height: 1;
|
|
168
154
|
}
|
|
169
155
|
|
|
170
156
|
.session-row {
|
|
171
157
|
display: flex;
|
|
172
|
-
|
|
158
|
+
min-height: 0;
|
|
173
159
|
align-items: center;
|
|
174
|
-
gap:
|
|
175
|
-
margin-top:
|
|
160
|
+
gap: 10px;
|
|
161
|
+
margin-top: 8px;
|
|
176
162
|
}
|
|
177
163
|
|
|
178
164
|
.session-id {
|
|
179
165
|
overflow: hidden;
|
|
180
166
|
max-width: 100%;
|
|
181
|
-
padding: 7px 10px;
|
|
182
|
-
border-radius: 9px;
|
|
183
|
-
background: var(--pm-card-muted);
|
|
184
167
|
color: var(--pm-muted);
|
|
185
168
|
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
186
|
-
font-size:
|
|
169
|
+
font-size: 10px;
|
|
187
170
|
text-overflow: ellipsis;
|
|
188
171
|
white-space: nowrap;
|
|
189
172
|
}
|
|
190
173
|
|
|
191
174
|
.fallback-link {
|
|
192
175
|
color: var(--pm-primary-strong);
|
|
193
|
-
font-size:
|
|
194
|
-
font-weight:
|
|
176
|
+
font-size: 12px;
|
|
177
|
+
font-weight: 760;
|
|
195
178
|
text-decoration-thickness: 1px;
|
|
196
179
|
text-underline-offset: 3px;
|
|
197
180
|
}
|
|
198
181
|
|
|
199
182
|
.status {
|
|
200
183
|
display: none;
|
|
201
|
-
margin:
|
|
202
|
-
padding:
|
|
203
|
-
border-radius:
|
|
204
|
-
background: var(--pm-card-muted);
|
|
184
|
+
margin: 10px 0 0;
|
|
185
|
+
padding: 9px 11px;
|
|
186
|
+
border-radius: 11px;
|
|
205
187
|
color: var(--pm-muted);
|
|
206
|
-
font-size:
|
|
207
|
-
line-height: 1.
|
|
188
|
+
font-size: 12px;
|
|
189
|
+
line-height: 1.4;
|
|
208
190
|
}
|
|
209
191
|
|
|
210
192
|
.status[data-tone="success"] {
|
|
@@ -226,7 +208,11 @@
|
|
|
226
208
|
}
|
|
227
209
|
|
|
228
210
|
.status[data-tone="neutral"] {
|
|
229
|
-
display:
|
|
211
|
+
display: none;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.section {
|
|
215
|
+
padding: 12px 22px 22px;
|
|
230
216
|
}
|
|
231
217
|
|
|
232
218
|
.section + .section {
|
|
@@ -235,12 +221,16 @@
|
|
|
235
221
|
|
|
236
222
|
.dropzone {
|
|
237
223
|
display: grid;
|
|
238
|
-
min-height:
|
|
224
|
+
min-height: 260px;
|
|
239
225
|
place-items: center;
|
|
240
|
-
padding:
|
|
226
|
+
padding: 20px;
|
|
241
227
|
border: 2px dashed var(--pm-line);
|
|
242
|
-
border-radius:
|
|
243
|
-
background:
|
|
228
|
+
border-radius: 21px;
|
|
229
|
+
background: color-mix(
|
|
230
|
+
in srgb,
|
|
231
|
+
var(--pm-card-muted) 62%,
|
|
232
|
+
var(--pm-card)
|
|
233
|
+
);
|
|
244
234
|
text-align: center;
|
|
245
235
|
transition:
|
|
246
236
|
border-color 160ms ease,
|
|
@@ -259,31 +249,54 @@
|
|
|
259
249
|
opacity: 0.65;
|
|
260
250
|
}
|
|
261
251
|
|
|
252
|
+
.drop-trigger {
|
|
253
|
+
display: grid;
|
|
254
|
+
width: min(100%, 280px);
|
|
255
|
+
min-height: 190px;
|
|
256
|
+
place-items: center;
|
|
257
|
+
align-content: center;
|
|
258
|
+
gap: 16px;
|
|
259
|
+
border: 0;
|
|
260
|
+
border-radius: 18px;
|
|
261
|
+
background: transparent;
|
|
262
|
+
color: var(--pm-text);
|
|
263
|
+
cursor: pointer;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.drop-trigger:disabled {
|
|
267
|
+
cursor: not-allowed;
|
|
268
|
+
}
|
|
269
|
+
|
|
262
270
|
.drop-icon {
|
|
263
271
|
display: grid;
|
|
264
|
-
width:
|
|
265
|
-
height:
|
|
266
|
-
margin: 0 auto 12px;
|
|
272
|
+
width: 58px;
|
|
273
|
+
height: 58px;
|
|
267
274
|
place-items: center;
|
|
268
|
-
border
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
275
|
+
border: 1px solid var(--pm-line);
|
|
276
|
+
border-radius: 50%;
|
|
277
|
+
background: var(--pm-card);
|
|
278
|
+
box-shadow: 0 7px 20px rgb(27 36 66 / 8%);
|
|
279
|
+
color: var(--pm-text);
|
|
280
|
+
font-size: 31px;
|
|
281
|
+
font-weight: 350;
|
|
282
|
+
line-height: 1;
|
|
273
283
|
}
|
|
274
284
|
|
|
275
285
|
.drop-title {
|
|
276
|
-
margin
|
|
277
|
-
font-size:
|
|
278
|
-
font-weight:
|
|
286
|
+
margin: 0;
|
|
287
|
+
font-size: 18px;
|
|
288
|
+
font-weight: 520;
|
|
289
|
+
letter-spacing: -0.02em;
|
|
279
290
|
}
|
|
280
291
|
|
|
281
292
|
.drop-help {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
293
|
+
position: absolute;
|
|
294
|
+
width: 1px;
|
|
295
|
+
height: 1px;
|
|
296
|
+
overflow: hidden;
|
|
297
|
+
clip: rect(0 0 0 0);
|
|
298
|
+
clip-path: inset(50%);
|
|
299
|
+
white-space: nowrap;
|
|
287
300
|
}
|
|
288
301
|
|
|
289
302
|
.native-input {
|
|
@@ -337,17 +350,11 @@
|
|
|
337
350
|
background: var(--pm-card-muted);
|
|
338
351
|
}
|
|
339
352
|
|
|
340
|
-
.button.danger {
|
|
341
|
-
border-color: color-mix(in srgb, var(--pm-danger) 25%, var(--pm-line));
|
|
342
|
-
background: transparent;
|
|
343
|
-
color: var(--pm-danger);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
353
|
.batch-error {
|
|
347
354
|
display: none;
|
|
348
|
-
margin:
|
|
355
|
+
margin: 10px 0 0;
|
|
349
356
|
color: var(--pm-danger);
|
|
350
|
-
font-size:
|
|
357
|
+
font-size: 12px;
|
|
351
358
|
}
|
|
352
359
|
|
|
353
360
|
.batch-error:not(:empty) {
|
|
@@ -356,85 +363,134 @@
|
|
|
356
363
|
|
|
357
364
|
.file-list {
|
|
358
365
|
display: grid;
|
|
359
|
-
|
|
360
|
-
|
|
366
|
+
grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
|
|
367
|
+
gap: 12px;
|
|
368
|
+
margin-top: 14px;
|
|
361
369
|
}
|
|
362
370
|
|
|
363
371
|
.file-card {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
border-radius: 15px;
|
|
367
|
-
background: var(--pm-card);
|
|
372
|
+
min-width: 0;
|
|
373
|
+
background: transparent;
|
|
368
374
|
}
|
|
369
375
|
|
|
370
|
-
.file-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
376
|
+
.file-preview {
|
|
377
|
+
position: relative;
|
|
378
|
+
aspect-ratio: 1;
|
|
379
|
+
overflow: hidden;
|
|
380
|
+
border: 1px solid var(--pm-line);
|
|
381
|
+
border-radius: 16px;
|
|
382
|
+
background: var(--pm-card-muted);
|
|
375
383
|
}
|
|
376
384
|
|
|
377
|
-
.file-
|
|
378
|
-
|
|
379
|
-
|
|
385
|
+
.file-preview img,
|
|
386
|
+
.video-preview {
|
|
387
|
+
display: block;
|
|
388
|
+
width: 100%;
|
|
389
|
+
height: 100%;
|
|
390
|
+
object-fit: cover;
|
|
380
391
|
}
|
|
381
392
|
|
|
382
|
-
.
|
|
383
|
-
|
|
384
|
-
|
|
393
|
+
.video-preview {
|
|
394
|
+
display: grid;
|
|
395
|
+
place-items: center;
|
|
396
|
+
background: linear-gradient(145deg, #242a3d, #515c78);
|
|
397
|
+
color: #ffffff;
|
|
398
|
+
font-size: 37px;
|
|
385
399
|
}
|
|
386
400
|
|
|
387
401
|
.file-kind {
|
|
388
402
|
display: grid;
|
|
389
|
-
width:
|
|
390
|
-
height:
|
|
391
|
-
flex: 0 0 auto;
|
|
403
|
+
width: 100%;
|
|
404
|
+
height: 100%;
|
|
392
405
|
place-items: center;
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
406
|
+
color: var(--pm-muted);
|
|
407
|
+
font-size: 34px;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.file-head {
|
|
411
|
+
min-width: 0;
|
|
412
|
+
margin-top: 7px;
|
|
397
413
|
}
|
|
398
414
|
|
|
399
415
|
.file-label {
|
|
400
416
|
margin: 0;
|
|
401
|
-
|
|
402
|
-
font-
|
|
417
|
+
overflow: hidden;
|
|
418
|
+
font-size: 12px;
|
|
419
|
+
font-weight: 700;
|
|
420
|
+
text-overflow: ellipsis;
|
|
421
|
+
white-space: nowrap;
|
|
403
422
|
}
|
|
404
423
|
|
|
405
424
|
.file-state {
|
|
406
425
|
margin: 3px 0 0;
|
|
426
|
+
overflow: hidden;
|
|
407
427
|
color: var(--pm-muted);
|
|
408
428
|
font-size: 11px;
|
|
429
|
+
line-height: 1.35;
|
|
430
|
+
text-overflow: ellipsis;
|
|
431
|
+
white-space: nowrap;
|
|
409
432
|
}
|
|
410
433
|
|
|
411
434
|
.file-state.error {
|
|
412
435
|
color: var(--pm-danger);
|
|
436
|
+
white-space: normal;
|
|
413
437
|
}
|
|
414
438
|
|
|
415
|
-
.
|
|
416
|
-
|
|
417
|
-
|
|
439
|
+
.overlay-button {
|
|
440
|
+
position: absolute;
|
|
441
|
+
z-index: 1;
|
|
442
|
+
border: 0;
|
|
443
|
+
cursor: pointer;
|
|
418
444
|
}
|
|
419
445
|
|
|
420
|
-
.
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
446
|
+
.remove-overlay {
|
|
447
|
+
top: 9px;
|
|
448
|
+
right: 9px;
|
|
449
|
+
display: grid;
|
|
450
|
+
width: 30px;
|
|
451
|
+
height: 30px;
|
|
452
|
+
place-items: center;
|
|
453
|
+
border: 1px solid rgb(23 32 51 / 12%);
|
|
454
|
+
border-radius: 50%;
|
|
455
|
+
background: rgb(255 255 255 / 90%);
|
|
456
|
+
color: #172033;
|
|
457
|
+
font-size: 23px;
|
|
458
|
+
font-weight: 350;
|
|
459
|
+
line-height: 1;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.remove-overlay:disabled {
|
|
463
|
+
cursor: not-allowed;
|
|
464
|
+
opacity: 0.72;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.retry-overlay {
|
|
468
|
+
top: 50%;
|
|
469
|
+
left: 50%;
|
|
470
|
+
padding: 10px 14px;
|
|
471
|
+
border: 1px solid rgb(255 255 255 / 76%);
|
|
472
|
+
border-radius: 11px;
|
|
473
|
+
background: rgb(255 255 255 / 94%);
|
|
474
|
+
box-shadow: 0 8px 24px rgb(27 36 66 / 16%);
|
|
426
475
|
color: var(--pm-text);
|
|
427
|
-
|
|
428
|
-
font-
|
|
429
|
-
|
|
476
|
+
font-size: 12px;
|
|
477
|
+
font-weight: 780;
|
|
478
|
+
transform: translate(-50%, -50%);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.retry-overlay:hover {
|
|
482
|
+
background: var(--pm-primary-soft);
|
|
430
483
|
}
|
|
431
484
|
|
|
432
485
|
.progress {
|
|
486
|
+
position: absolute;
|
|
487
|
+
right: 10px;
|
|
488
|
+
bottom: 10px;
|
|
489
|
+
left: 10px;
|
|
433
490
|
height: 6px;
|
|
434
|
-
margin-top: 11px;
|
|
435
491
|
overflow: hidden;
|
|
436
492
|
border-radius: 999px;
|
|
437
|
-
background:
|
|
493
|
+
background: rgb(255 255 255 / 75%);
|
|
438
494
|
}
|
|
439
495
|
|
|
440
496
|
.progress-bar {
|
|
@@ -448,20 +504,11 @@
|
|
|
448
504
|
.destination-grid {
|
|
449
505
|
display: grid;
|
|
450
506
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
451
|
-
gap:
|
|
452
|
-
margin-top: 16px;
|
|
507
|
+
gap: 9px;
|
|
453
508
|
}
|
|
454
509
|
|
|
455
510
|
.field {
|
|
456
511
|
display: grid;
|
|
457
|
-
gap: 6px;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
.field label {
|
|
461
|
-
color: var(--pm-muted);
|
|
462
|
-
font-size: 11px;
|
|
463
|
-
font-weight: 760;
|
|
464
|
-
letter-spacing: 0.02em;
|
|
465
512
|
}
|
|
466
513
|
|
|
467
514
|
select,
|
|
@@ -474,8 +521,9 @@
|
|
|
474
521
|
}
|
|
475
522
|
|
|
476
523
|
select {
|
|
477
|
-
min-height:
|
|
478
|
-
padding:
|
|
524
|
+
min-height: 42px;
|
|
525
|
+
padding: 8px 30px 8px 10px;
|
|
526
|
+
font-size: 12px;
|
|
479
527
|
}
|
|
480
528
|
|
|
481
529
|
textarea {
|
|
@@ -492,19 +540,22 @@
|
|
|
492
540
|
display: flex;
|
|
493
541
|
flex-wrap: wrap;
|
|
494
542
|
gap: 10px;
|
|
495
|
-
margin-top:
|
|
543
|
+
margin-top: 12px;
|
|
496
544
|
}
|
|
497
545
|
|
|
498
|
-
.
|
|
499
|
-
display:
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
546
|
+
.compatibility-control {
|
|
547
|
+
display: none !important;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
.sr-only {
|
|
551
|
+
position: absolute;
|
|
552
|
+
width: 1px;
|
|
553
|
+
height: 1px;
|
|
554
|
+
padding: 0;
|
|
555
|
+
overflow: hidden;
|
|
556
|
+
clip: rect(0, 0, 0, 0);
|
|
557
|
+
white-space: nowrap;
|
|
558
|
+
border: 0;
|
|
508
559
|
}
|
|
509
560
|
|
|
510
561
|
[hidden] {
|
|
@@ -513,23 +564,29 @@
|
|
|
513
564
|
|
|
514
565
|
@media (max-width: 640px) {
|
|
515
566
|
.app {
|
|
516
|
-
padding:
|
|
567
|
+
padding: 8px;
|
|
517
568
|
}
|
|
518
569
|
|
|
519
570
|
.card {
|
|
520
|
-
border-radius:
|
|
571
|
+
border-radius: 20px;
|
|
521
572
|
}
|
|
522
573
|
|
|
523
574
|
.hero,
|
|
524
575
|
.section {
|
|
525
|
-
padding:
|
|
576
|
+
padding-right: 14px;
|
|
577
|
+
padding-left: 14px;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
.dropzone {
|
|
581
|
+
min-height: 230px;
|
|
526
582
|
}
|
|
527
583
|
|
|
528
584
|
.destination-grid {
|
|
529
585
|
grid-template-columns: 1fr;
|
|
530
586
|
}
|
|
531
587
|
|
|
532
|
-
.button
|
|
588
|
+
.destination-actions .button,
|
|
589
|
+
.handoff-actions .button {
|
|
533
590
|
width: 100%;
|
|
534
591
|
}
|
|
535
592
|
}
|
|
@@ -548,16 +605,16 @@
|
|
|
548
605
|
<main class="app" data-testid="meta-upload-root">
|
|
549
606
|
<article class="card">
|
|
550
607
|
<header class="hero">
|
|
551
|
-
<
|
|
552
|
-
<
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
</
|
|
608
|
+
<div class="upload-header">
|
|
609
|
+
<p class="upload-title" data-testid="meta-upload-title">
|
|
610
|
+
Upload image
|
|
611
|
+
</p>
|
|
612
|
+
<span class="brand-mark" aria-label="ProductMaker" role="img"
|
|
613
|
+
>↗</span
|
|
614
|
+
>
|
|
615
|
+
</div>
|
|
559
616
|
<div class="session-row">
|
|
560
|
-
<code class="session-id" data-testid="meta-upload-session-id"
|
|
617
|
+
<code class="session-id" data-testid="meta-upload-session-id" hidden
|
|
561
618
|
>Esperando sesión…</code
|
|
562
619
|
>
|
|
563
620
|
<a
|
|
@@ -577,52 +634,46 @@
|
|
|
577
634
|
></p>
|
|
578
635
|
</header>
|
|
579
636
|
|
|
580
|
-
<section class="section" aria-
|
|
581
|
-
<h2 id="upload-heading">1. Selecciona tus archivos</h2>
|
|
582
|
-
<p class="section-copy muted">
|
|
583
|
-
Hasta 10 archivos · 350 MB por archivo · videos de máximo 180
|
|
584
|
-
segundos · mínimo 600 × 600 px.
|
|
585
|
-
</p>
|
|
637
|
+
<section class="section" aria-label="Upload media">
|
|
586
638
|
<div
|
|
587
639
|
class="dropzone"
|
|
588
640
|
data-testid="meta-upload-dropzone"
|
|
589
641
|
data-dragging="false"
|
|
590
642
|
aria-disabled="true"
|
|
643
|
+
aria-describedby="meta-upload-constraints"
|
|
591
644
|
>
|
|
592
|
-
<
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
<
|
|
600
|
-
|
|
601
|
-
type="button"
|
|
602
|
-
data-testid="meta-upload-picker-btn"
|
|
603
|
-
disabled
|
|
604
|
-
>
|
|
605
|
-
Elegir archivos
|
|
606
|
-
</button>
|
|
607
|
-
<button
|
|
608
|
-
class="button secondary"
|
|
609
|
-
type="button"
|
|
610
|
-
data-testid="meta-upload-library-btn"
|
|
611
|
-
hidden
|
|
612
|
-
disabled
|
|
645
|
+
<button
|
|
646
|
+
class="drop-trigger"
|
|
647
|
+
type="button"
|
|
648
|
+
data-testid="meta-upload-picker-btn"
|
|
649
|
+
disabled
|
|
650
|
+
>
|
|
651
|
+
<span class="drop-icon" aria-hidden="true">+</span>
|
|
652
|
+
<span class="drop-title" data-testid="meta-upload-drop-title"
|
|
653
|
+
>Upload image</span
|
|
613
654
|
>
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
655
|
+
</button>
|
|
656
|
+
<button
|
|
657
|
+
class="compatibility-control"
|
|
658
|
+
type="button"
|
|
659
|
+
data-testid="meta-upload-library-btn"
|
|
660
|
+
hidden
|
|
661
|
+
disabled
|
|
662
|
+
aria-label="Choose from ChatGPT library"
|
|
663
|
+
></button>
|
|
664
|
+
<input
|
|
665
|
+
class="native-input"
|
|
666
|
+
data-testid="meta-upload-input"
|
|
667
|
+
type="file"
|
|
668
|
+
accept="image/jpeg,image/png,image/webp,video/mp4,video/quicktime,video/webm"
|
|
669
|
+
multiple
|
|
670
|
+
disabled
|
|
671
|
+
/>
|
|
625
672
|
</div>
|
|
673
|
+
<p class="drop-help" id="meta-upload-constraints">
|
|
674
|
+
JPEG, PNG, WebP, MP4, MOV o WebM. Hasta 10 archivos de 350 MB; las
|
|
675
|
+
imágenes grandes se comprimen y los videos no se transcodifican.
|
|
676
|
+
</p>
|
|
626
677
|
<p
|
|
627
678
|
class="batch-error"
|
|
628
679
|
data-testid="meta-upload-batch-error"
|
|
@@ -635,57 +686,44 @@
|
|
|
635
686
|
aria-live="polite"
|
|
636
687
|
></div>
|
|
637
688
|
<button
|
|
638
|
-
class="
|
|
689
|
+
class="compatibility-control"
|
|
639
690
|
type="button"
|
|
640
691
|
data-testid="meta-upload-pending-refresh"
|
|
641
692
|
hidden
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
</button>
|
|
645
|
-
<p class="privacy-note">
|
|
646
|
-
<span aria-hidden="true">✓</span>
|
|
647
|
-
<span
|
|
648
|
-
>No enviamos bytes, enlaces temporales, nombres de archivo ni
|
|
649
|
-
tokens al modelo.</span
|
|
650
|
-
>
|
|
651
|
-
</p>
|
|
693
|
+
aria-label="Refresh upload status"
|
|
694
|
+
></button>
|
|
652
695
|
</section>
|
|
653
696
|
|
|
654
697
|
<section
|
|
655
698
|
class="section"
|
|
656
699
|
data-testid="meta-upload-destination-section"
|
|
657
|
-
aria-
|
|
700
|
+
aria-label="Meta destination"
|
|
658
701
|
hidden
|
|
659
702
|
>
|
|
660
|
-
<h2 id="destination-heading">2. Elige el destino de Meta</h2>
|
|
661
|
-
<p class="section-copy muted">
|
|
662
|
-
La selección es obligatoria. Las cuentas se filtran por el portfolio
|
|
663
|
-
elegido.
|
|
664
|
-
</p>
|
|
665
703
|
<div class="destination-grid">
|
|
666
704
|
<div class="field">
|
|
667
|
-
<label for="portfolio-select">Portfolio</label>
|
|
705
|
+
<label class="sr-only" for="portfolio-select">Portfolio</label>
|
|
668
706
|
<select
|
|
669
707
|
id="portfolio-select"
|
|
670
708
|
data-testid="meta-upload-portfolio-select"
|
|
671
709
|
>
|
|
672
|
-
<option value="">
|
|
710
|
+
<option value="">Portfolio</option>
|
|
673
711
|
</select>
|
|
674
712
|
</div>
|
|
675
713
|
<div class="field">
|
|
676
|
-
<label for="ad-account-select">
|
|
714
|
+
<label class="sr-only" for="ad-account-select">Ad account</label>
|
|
677
715
|
<select
|
|
678
716
|
id="ad-account-select"
|
|
679
717
|
data-testid="meta-upload-ad-account-select"
|
|
680
718
|
disabled
|
|
681
719
|
>
|
|
682
|
-
<option value="">
|
|
720
|
+
<option value="">Ad account</option>
|
|
683
721
|
</select>
|
|
684
722
|
</div>
|
|
685
723
|
<div class="field">
|
|
686
|
-
<label for="page-select">
|
|
724
|
+
<label class="sr-only" for="page-select">Facebook Page</label>
|
|
687
725
|
<select id="page-select" data-testid="meta-upload-page-select">
|
|
688
|
-
<option value="">
|
|
726
|
+
<option value="">Facebook Page</option>
|
|
689
727
|
</select>
|
|
690
728
|
</div>
|
|
691
729
|
</div>
|
|
@@ -696,15 +734,15 @@
|
|
|
696
734
|
data-testid="meta-upload-submit-destination"
|
|
697
735
|
disabled
|
|
698
736
|
>
|
|
699
|
-
|
|
737
|
+
Continue
|
|
700
738
|
</button>
|
|
701
739
|
<button
|
|
702
|
-
class="
|
|
740
|
+
class="compatibility-control"
|
|
703
741
|
type="button"
|
|
704
742
|
data-testid="meta-upload-check-status"
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
743
|
+
hidden
|
|
744
|
+
aria-label="Refresh upload status"
|
|
745
|
+
></button>
|
|
708
746
|
</div>
|
|
709
747
|
</section>
|
|
710
748
|
|
|
@@ -714,10 +752,8 @@
|
|
|
714
752
|
aria-labelledby="handoff-heading"
|
|
715
753
|
hidden
|
|
716
754
|
>
|
|
717
|
-
<
|
|
718
|
-
|
|
719
|
-
Este host no puede devolver el mensaje automáticamente. Copia este
|
|
720
|
-
JSON exacto en el chat.
|
|
755
|
+
<p id="handoff-heading" class="section-copy muted">
|
|
756
|
+
Copy this handoff into the chat.
|
|
721
757
|
</p>
|
|
722
758
|
<textarea data-testid="meta-upload-handoff-copy" readonly></textarea>
|
|
723
759
|
<div class="handoff-actions">
|
|
@@ -726,7 +762,7 @@
|
|
|
726
762
|
type="button"
|
|
727
763
|
data-testid="meta-upload-copy-btn"
|
|
728
764
|
>
|
|
729
|
-
|
|
765
|
+
Copy handoff
|
|
730
766
|
</button>
|
|
731
767
|
</div>
|
|
732
768
|
</section>
|
|
@@ -765,6 +801,7 @@
|
|
|
765
801
|
|
|
766
802
|
const elements = {
|
|
767
803
|
root: document.querySelector('[data-testid="meta-upload-root"]'),
|
|
804
|
+
title: document.querySelector('[data-testid="meta-upload-title"]'),
|
|
768
805
|
sessionId: document.querySelector(
|
|
769
806
|
'[data-testid="meta-upload-session-id"]',
|
|
770
807
|
),
|
|
@@ -777,6 +814,9 @@
|
|
|
777
814
|
dropzone: document.querySelector(
|
|
778
815
|
'[data-testid="meta-upload-dropzone"]',
|
|
779
816
|
),
|
|
817
|
+
dropTitle: document.querySelector(
|
|
818
|
+
'[data-testid="meta-upload-drop-title"]',
|
|
819
|
+
),
|
|
780
820
|
pickerButton: document.querySelector(
|
|
781
821
|
'[data-testid="meta-upload-picker-btn"]',
|
|
782
822
|
),
|
|
@@ -823,12 +863,17 @@
|
|
|
823
863
|
};
|
|
824
864
|
|
|
825
865
|
const state = {
|
|
866
|
+
disposed: false,
|
|
826
867
|
initialized: false,
|
|
827
868
|
standardHostReady: false,
|
|
828
869
|
hostCanMessage: false,
|
|
829
870
|
hostCanServerTools: false,
|
|
871
|
+
hostContainerDimensions: null,
|
|
830
872
|
rpcId: 0,
|
|
831
873
|
pendingRpc: new Map(),
|
|
874
|
+
pendingSizeReport: false,
|
|
875
|
+
forceSizeReport: false,
|
|
876
|
+
lastReportedHeight: 0,
|
|
832
877
|
session: null,
|
|
833
878
|
toolInput: {},
|
|
834
879
|
items: [],
|
|
@@ -836,7 +881,18 @@
|
|
|
836
881
|
destinations: { portfolios: [], adAccounts: [], pages: [] },
|
|
837
882
|
consumedBridgeIds: new Set(),
|
|
838
883
|
consumingBridgeFiles: false,
|
|
884
|
+
bridgeConsumptionQueued: false,
|
|
885
|
+
ingestionQueue: Promise.resolve(),
|
|
839
886
|
busyBatch: false,
|
|
887
|
+
uploadBlocked: false,
|
|
888
|
+
fallbackRequired: false,
|
|
889
|
+
workCancelled: false,
|
|
890
|
+
abortController: null,
|
|
891
|
+
activeXhrs: new Set(),
|
|
892
|
+
resizeObserver: null,
|
|
893
|
+
mutationObserver: null,
|
|
894
|
+
resizeHandler: null,
|
|
895
|
+
hostMessageHandler: null,
|
|
840
896
|
};
|
|
841
897
|
|
|
842
898
|
function isRecord(value) {
|
|
@@ -870,6 +926,29 @@
|
|
|
870
926
|
return null;
|
|
871
927
|
}
|
|
872
928
|
|
|
929
|
+
function defaultUploadTitle() {
|
|
930
|
+
return state.session?.mediaKind === "video"
|
|
931
|
+
? "Upload video"
|
|
932
|
+
: "Upload image";
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
function updateUploadTitle() {
|
|
936
|
+
const namedItems = state.items.filter(
|
|
937
|
+
(item) =>
|
|
938
|
+
(item.file && typeof item.file.name === "string") ||
|
|
939
|
+
(typeof item.displayName === "string" &&
|
|
940
|
+
item.displayName.length > 0),
|
|
941
|
+
);
|
|
942
|
+
const title =
|
|
943
|
+
namedItems.length === 1
|
|
944
|
+
? namedItems[0].file?.name || namedItems[0].displayName
|
|
945
|
+
: namedItems.length > 1
|
|
946
|
+
? `${namedItems.length} files`
|
|
947
|
+
: defaultUploadTitle();
|
|
948
|
+
elements.title.textContent = title;
|
|
949
|
+
elements.dropTitle.textContent = defaultUploadTitle();
|
|
950
|
+
}
|
|
951
|
+
|
|
873
952
|
function safeSessionLimit() {
|
|
874
953
|
const proposed =
|
|
875
954
|
state.session && Number.isInteger(state.session.maxFiles)
|
|
@@ -887,16 +966,232 @@
|
|
|
887
966
|
function setSessionStatus(message, tone = "neutral") {
|
|
888
967
|
elements.sessionStatus.textContent = message;
|
|
889
968
|
elements.sessionStatus.dataset.tone = tone;
|
|
969
|
+
scheduleIntrinsicSizeReport(true);
|
|
890
970
|
}
|
|
891
971
|
|
|
892
972
|
function setBatchError(message) {
|
|
893
973
|
elements.batchError.textContent = message;
|
|
974
|
+
scheduleIntrinsicSizeReport(true);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
function parseFinitePositiveNumber(value) {
|
|
978
|
+
return typeof value === "number" &&
|
|
979
|
+
Number.isFinite(value) &&
|
|
980
|
+
value > 0
|
|
981
|
+
? value
|
|
982
|
+
: null;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
function parseHostContainerDimensions(value) {
|
|
986
|
+
if (!isRecord(value)) return null;
|
|
987
|
+
const height = parseFinitePositiveNumber(value.height);
|
|
988
|
+
const maxHeight = parseFinitePositiveNumber(value.maxHeight);
|
|
989
|
+
if (!height && !maxHeight) return null;
|
|
990
|
+
return { height, maxHeight };
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
function boundedPositiveHeight(value) {
|
|
994
|
+
const parsed = parseFinitePositiveNumber(value);
|
|
995
|
+
return parsed ? Math.max(1, Math.round(parsed)) : null;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
function intrinsicHeightLimit() {
|
|
999
|
+
const dimensions = state.hostContainerDimensions;
|
|
1000
|
+
if (!dimensions) return null;
|
|
1001
|
+
return boundedPositiveHeight(
|
|
1002
|
+
dimensions.height || dimensions.maxHeight,
|
|
1003
|
+
);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
function measureIntrinsicWidth() {
|
|
1007
|
+
const candidates = [
|
|
1008
|
+
elements.root && elements.root.scrollWidth,
|
|
1009
|
+
document.body && document.body.scrollWidth,
|
|
1010
|
+
document.documentElement && document.documentElement.scrollWidth,
|
|
1011
|
+
];
|
|
1012
|
+
for (const candidate of candidates) {
|
|
1013
|
+
const parsed = boundedPositiveHeight(candidate);
|
|
1014
|
+
if (parsed) return parsed;
|
|
1015
|
+
}
|
|
1016
|
+
return 1;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
function applyHostContainerDimensions() {
|
|
1020
|
+
const dimensions = state.hostContainerDimensions;
|
|
1021
|
+
if (!dimensions) return;
|
|
1022
|
+
if (dimensions.height) {
|
|
1023
|
+
document.documentElement.style.height = "100vh";
|
|
1024
|
+
} else {
|
|
1025
|
+
document.documentElement.style.removeProperty("height");
|
|
1026
|
+
}
|
|
1027
|
+
if (dimensions.maxHeight) {
|
|
1028
|
+
document.documentElement.style.maxHeight = `${boundedPositiveHeight(dimensions.maxHeight)}px`;
|
|
1029
|
+
} else {
|
|
1030
|
+
document.documentElement.style.removeProperty("max-height");
|
|
1031
|
+
}
|
|
1032
|
+
document.documentElement.style.overflowY = "auto";
|
|
1033
|
+
scheduleIntrinsicSizeReport(true);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
function measureIntrinsicHeight() {
|
|
1037
|
+
const candidates = [
|
|
1038
|
+
elements.root && elements.root.scrollHeight,
|
|
1039
|
+
document.body && document.body.scrollHeight,
|
|
1040
|
+
document.documentElement && document.documentElement.scrollHeight,
|
|
1041
|
+
];
|
|
1042
|
+
let measured = 1;
|
|
1043
|
+
for (const candidate of candidates) {
|
|
1044
|
+
const parsed = boundedPositiveHeight(candidate);
|
|
1045
|
+
if (parsed) {
|
|
1046
|
+
measured = parsed;
|
|
1047
|
+
break;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
const limit = intrinsicHeightLimit();
|
|
1051
|
+
return limit ? Math.min(measured, limit) : measured;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
function reportIntrinsicHeight(force = false) {
|
|
1055
|
+
state.pendingSizeReport = false;
|
|
1056
|
+
state.forceSizeReport = false;
|
|
1057
|
+
const height = measureIntrinsicHeight();
|
|
1058
|
+
if (!force && height === state.lastReportedHeight) return;
|
|
1059
|
+
state.lastReportedHeight = height;
|
|
1060
|
+
if (state.standardHostReady) {
|
|
1061
|
+
postNotification("ui/notifications/size-changed", {
|
|
1062
|
+
width: measureIntrinsicWidth(),
|
|
1063
|
+
height,
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
const bridge = openAiBridge();
|
|
1067
|
+
if (bridge && typeof bridge.notifyIntrinsicHeight === "function") {
|
|
1068
|
+
void Promise.resolve(bridge.notifyIntrinsicHeight(height)).catch(
|
|
1069
|
+
() => {},
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
function scheduleIntrinsicSizeReport(force = false) {
|
|
1075
|
+
state.forceSizeReport = state.forceSizeReport || force;
|
|
1076
|
+
if (state.pendingSizeReport) return;
|
|
1077
|
+
state.pendingSizeReport = true;
|
|
1078
|
+
const flush = () => reportIntrinsicHeight(state.forceSizeReport);
|
|
1079
|
+
if (typeof window.requestAnimationFrame === "function") {
|
|
1080
|
+
window.requestAnimationFrame(() => flush());
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
window.setTimeout(flush, 0);
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
function installIntrinsicSizeObserver() {
|
|
1087
|
+
if (
|
|
1088
|
+
state.resizeObserver ||
|
|
1089
|
+
state.mutationObserver ||
|
|
1090
|
+
state.resizeHandler
|
|
1091
|
+
)
|
|
1092
|
+
return;
|
|
1093
|
+
if (typeof ResizeObserver === "function" && elements.root) {
|
|
1094
|
+
state.resizeObserver = new ResizeObserver(() =>
|
|
1095
|
+
scheduleIntrinsicSizeReport(),
|
|
1096
|
+
);
|
|
1097
|
+
state.resizeObserver.observe(elements.root);
|
|
1098
|
+
} else if (typeof MutationObserver === "function" && elements.root) {
|
|
1099
|
+
state.mutationObserver = new MutationObserver(() =>
|
|
1100
|
+
scheduleIntrinsicSizeReport(),
|
|
1101
|
+
);
|
|
1102
|
+
state.mutationObserver.observe(elements.root, {
|
|
1103
|
+
attributes: true,
|
|
1104
|
+
childList: true,
|
|
1105
|
+
subtree: true,
|
|
1106
|
+
characterData: true,
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
state.resizeHandler = () => scheduleIntrinsicSizeReport();
|
|
1110
|
+
window.addEventListener("resize", state.resizeHandler);
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
function setFallbackVisibility(visible) {
|
|
1114
|
+
elements.fallbackLink.hidden = !visible;
|
|
1115
|
+
scheduleIntrinsicSizeReport(true);
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
function requireFallback(
|
|
1119
|
+
message,
|
|
1120
|
+
tone = "warning",
|
|
1121
|
+
blockPicker = false,
|
|
1122
|
+
) {
|
|
1123
|
+
state.fallbackRequired = true;
|
|
1124
|
+
state.uploadBlocked = state.uploadBlocked || blockPicker;
|
|
1125
|
+
setFallbackVisibility(true);
|
|
1126
|
+
if (message) setSessionStatus(message, tone);
|
|
1127
|
+
setPickerAvailability();
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
function enqueueIngestion(task) {
|
|
1131
|
+
const next = state.ingestionQueue.then(task, task);
|
|
1132
|
+
state.ingestionQueue = next.catch(() => {});
|
|
1133
|
+
return next;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
function activeAbortSignal() {
|
|
1137
|
+
if (!state.abortController || state.abortController.signal.aborted) {
|
|
1138
|
+
state.abortController =
|
|
1139
|
+
typeof AbortController === "function"
|
|
1140
|
+
? new AbortController()
|
|
1141
|
+
: null;
|
|
1142
|
+
}
|
|
1143
|
+
return state.abortController?.signal;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
function abortActiveWork() {
|
|
1147
|
+
state.abortController?.abort();
|
|
1148
|
+
for (const request of state.activeXhrs) request.abort();
|
|
1149
|
+
state.activeXhrs.clear();
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
function cleanup() {
|
|
1153
|
+
if (state.disposed) return;
|
|
1154
|
+
state.disposed = true;
|
|
1155
|
+
abortActiveWork();
|
|
1156
|
+
state.resizeObserver?.disconnect();
|
|
1157
|
+
state.mutationObserver?.disconnect();
|
|
1158
|
+
if (state.resizeHandler)
|
|
1159
|
+
window.removeEventListener("resize", state.resizeHandler);
|
|
1160
|
+
if (state.hostMessageHandler)
|
|
1161
|
+
window.removeEventListener("message", state.hostMessageHandler);
|
|
1162
|
+
for (const item of state.items) revokePreviewUrl(item);
|
|
1163
|
+
for (const [id, pending] of state.pendingRpc) {
|
|
1164
|
+
window.clearTimeout(pending.timeout);
|
|
1165
|
+
pending.reject(new Error("resource_teardown"));
|
|
1166
|
+
state.pendingRpc.delete(id);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
function normalizedBridgeReferenceId(reference) {
|
|
1171
|
+
return isRecord(reference)
|
|
1172
|
+
? boundedString(
|
|
1173
|
+
reference.file_id || reference.fileId || reference.id,
|
|
1174
|
+
300,
|
|
1175
|
+
)
|
|
1176
|
+
: "";
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
function normalizedBridgeDownloadUrl(result) {
|
|
1180
|
+
return typeof result === "string"
|
|
1181
|
+
? result
|
|
1182
|
+
: isRecord(result)
|
|
1183
|
+
? boundedString(
|
|
1184
|
+
result.download_url || result.downloadUrl || result.url,
|
|
1185
|
+
3000,
|
|
1186
|
+
)
|
|
1187
|
+
: "";
|
|
894
1188
|
}
|
|
895
1189
|
|
|
896
1190
|
function setPickerAvailability() {
|
|
897
1191
|
const disabled =
|
|
898
1192
|
!state.session ||
|
|
899
1193
|
sessionExpired() ||
|
|
1194
|
+
state.uploadBlocked ||
|
|
900
1195
|
state.busyBatch ||
|
|
901
1196
|
state.items.length >= safeSessionLimit();
|
|
902
1197
|
elements.input.disabled = disabled;
|
|
@@ -925,10 +1220,12 @@
|
|
|
925
1220
|
}
|
|
926
1221
|
|
|
927
1222
|
function initialize() {
|
|
928
|
-
if (state.initialized) return;
|
|
1223
|
+
if (state.initialized || state.disposed) return;
|
|
929
1224
|
state.initialized = true;
|
|
930
1225
|
bindUi();
|
|
1226
|
+
installIntrinsicSizeObserver();
|
|
931
1227
|
setPickerAvailability();
|
|
1228
|
+
scheduleIntrinsicSizeReport(true);
|
|
932
1229
|
void initializeStandardHost();
|
|
933
1230
|
const bridge = openAiBridge();
|
|
934
1231
|
elements.libraryPickerButton.hidden = !(
|
|
@@ -1010,14 +1307,17 @@
|
|
|
1010
1307
|
() => void copyHandoff(),
|
|
1011
1308
|
);
|
|
1012
1309
|
|
|
1013
|
-
|
|
1310
|
+
state.hostMessageHandler = handleHostMessage;
|
|
1311
|
+
window.addEventListener("message", state.hostMessageHandler);
|
|
1014
1312
|
}
|
|
1015
1313
|
|
|
1016
1314
|
async function initializeStandardHost() {
|
|
1017
1315
|
try {
|
|
1018
1316
|
const result = await rpcRequest("ui/initialize", {
|
|
1019
1317
|
appInfo: { name: "ProductMaker Meta upload", version: "1.0.0" },
|
|
1020
|
-
appCapabilities: {
|
|
1318
|
+
appCapabilities: {
|
|
1319
|
+
availableDisplayModes: ["inline", "fullscreen"],
|
|
1320
|
+
},
|
|
1021
1321
|
protocolVersion: APP_PROTOCOL_VERSION,
|
|
1022
1322
|
});
|
|
1023
1323
|
state.standardHostReady = true;
|
|
@@ -1025,11 +1325,20 @@
|
|
|
1025
1325
|
isRecord(result) && isRecord(result.hostCapabilities)
|
|
1026
1326
|
? result.hostCapabilities
|
|
1027
1327
|
: {};
|
|
1328
|
+
const hostContext =
|
|
1329
|
+
isRecord(result) && isRecord(result.hostContext)
|
|
1330
|
+
? result.hostContext
|
|
1331
|
+
: {};
|
|
1332
|
+
state.hostContainerDimensions = parseHostContainerDimensions(
|
|
1333
|
+
hostContext.containerDimensions,
|
|
1334
|
+
);
|
|
1335
|
+
applyHostContainerDimensions();
|
|
1028
1336
|
state.hostCanMessage = capabilityEnabled(capabilities.message);
|
|
1029
1337
|
state.hostCanServerTools = capabilityEnabled(
|
|
1030
1338
|
capabilities.serverTools,
|
|
1031
1339
|
);
|
|
1032
1340
|
postNotification("ui/notifications/initialized", {});
|
|
1341
|
+
scheduleIntrinsicSizeReport(true);
|
|
1033
1342
|
} catch {
|
|
1034
1343
|
state.standardHostReady = false;
|
|
1035
1344
|
state.hostCanServerTools = false;
|
|
@@ -1038,6 +1347,10 @@
|
|
|
1038
1347
|
|
|
1039
1348
|
function rpcRequest(method, params) {
|
|
1040
1349
|
return new Promise((resolve, reject) => {
|
|
1350
|
+
if (state.disposed) {
|
|
1351
|
+
reject(new Error("resource_teardown"));
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1041
1354
|
state.rpcId += 1;
|
|
1042
1355
|
const id = state.rpcId;
|
|
1043
1356
|
const timeout = window.setTimeout(() => {
|
|
@@ -1053,12 +1366,44 @@
|
|
|
1053
1366
|
}
|
|
1054
1367
|
|
|
1055
1368
|
function postNotification(method, params) {
|
|
1369
|
+
if (state.disposed) return;
|
|
1056
1370
|
window.parent.postMessage({ jsonrpc: "2.0", method, params }, "*");
|
|
1057
1371
|
}
|
|
1058
1372
|
|
|
1059
1373
|
function handleHostMessage(event) {
|
|
1060
1374
|
if (event.source !== window.parent || !isRecord(event.data)) return;
|
|
1061
1375
|
const message = event.data;
|
|
1376
|
+
if (message.method === "ui/resource-teardown") {
|
|
1377
|
+
cleanup();
|
|
1378
|
+
if (Object.prototype.hasOwnProperty.call(message, "id")) {
|
|
1379
|
+
window.parent.postMessage(
|
|
1380
|
+
{ jsonrpc: "2.0", id: message.id, result: {} },
|
|
1381
|
+
"*",
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
return;
|
|
1385
|
+
}
|
|
1386
|
+
if (message.method === "ui/notifications/tool-cancelled") {
|
|
1387
|
+
state.workCancelled = true;
|
|
1388
|
+
abortActiveWork();
|
|
1389
|
+
requireFallback(
|
|
1390
|
+
"La carga fue cancelada por el host. Puedes continuar desde el enlace seguro.",
|
|
1391
|
+
"warning",
|
|
1392
|
+
true,
|
|
1393
|
+
);
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
if (message.method === "ui/notifications/host-context-changed") {
|
|
1397
|
+
const params = isRecord(message.params) ? message.params : {};
|
|
1398
|
+
const hostContext = isRecord(params.hostContext)
|
|
1399
|
+
? params.hostContext
|
|
1400
|
+
: params;
|
|
1401
|
+
state.hostContainerDimensions = parseHostContainerDimensions(
|
|
1402
|
+
hostContext.containerDimensions,
|
|
1403
|
+
);
|
|
1404
|
+
applyHostContainerDimensions();
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1062
1407
|
if (Object.prototype.hasOwnProperty.call(message, "id")) {
|
|
1063
1408
|
const pending = state.pendingRpc.get(message.id);
|
|
1064
1409
|
if (!pending) return;
|
|
@@ -1090,22 +1435,35 @@
|
|
|
1090
1435
|
if (!candidate) return;
|
|
1091
1436
|
const session = parseSession(candidate);
|
|
1092
1437
|
if (!session) return;
|
|
1438
|
+
const sessionChanged =
|
|
1439
|
+
!state.session || state.session.sessionId !== session.sessionId;
|
|
1440
|
+
if (sessionChanged) {
|
|
1441
|
+
state.uploadBlocked = false;
|
|
1442
|
+
state.fallbackRequired = false;
|
|
1443
|
+
state.workCancelled = false;
|
|
1444
|
+
if (state.abortController?.signal.aborted) {
|
|
1445
|
+
state.abortController = null;
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1093
1448
|
state.session = session;
|
|
1094
1449
|
elements.sessionId.textContent = session.sessionId;
|
|
1095
1450
|
elements.fallbackLink.href = session.uploadPageUrl;
|
|
1096
|
-
|
|
1451
|
+
updateUploadTitle();
|
|
1097
1452
|
if (sessionExpired()) {
|
|
1098
|
-
|
|
1453
|
+
requireFallback(
|
|
1099
1454
|
"La sesión está expirada. Abre el enlace seguro para solicitar una nueva.",
|
|
1100
1455
|
"danger",
|
|
1456
|
+
true,
|
|
1101
1457
|
);
|
|
1102
1458
|
elements.pendingRefresh.hidden = true;
|
|
1103
1459
|
} else {
|
|
1460
|
+
setFallbackVisibility(state.fallbackRequired);
|
|
1104
1461
|
setSessionStatus("Sesión lista para recibir archivos.", "neutral");
|
|
1105
|
-
elements.pendingRefresh.hidden =
|
|
1462
|
+
elements.pendingRefresh.hidden = true;
|
|
1106
1463
|
}
|
|
1107
1464
|
setPickerAvailability();
|
|
1108
1465
|
void consumePendingBridgeFiles();
|
|
1466
|
+
scheduleIntrinsicSizeReport(true);
|
|
1109
1467
|
}
|
|
1110
1468
|
|
|
1111
1469
|
function parseSession(candidate) {
|
|
@@ -1139,8 +1497,26 @@
|
|
|
1139
1497
|
void consumePendingBridgeFiles();
|
|
1140
1498
|
}
|
|
1141
1499
|
|
|
1142
|
-
|
|
1500
|
+
function consumePendingBridgeFiles() {
|
|
1143
1501
|
if (
|
|
1502
|
+
state.disposed ||
|
|
1503
|
+
state.bridgeConsumptionQueued ||
|
|
1504
|
+
!state.session ||
|
|
1505
|
+
!Array.isArray(state.toolInput.files)
|
|
1506
|
+
)
|
|
1507
|
+
return;
|
|
1508
|
+
state.bridgeConsumptionQueued = true;
|
|
1509
|
+
void enqueueIngestion(async () => {
|
|
1510
|
+
state.bridgeConsumptionQueued = false;
|
|
1511
|
+
await consumePendingBridgeFilesNow();
|
|
1512
|
+
}).catch(() => {
|
|
1513
|
+
state.bridgeConsumptionQueued = false;
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
async function consumePendingBridgeFilesNow() {
|
|
1518
|
+
if (
|
|
1519
|
+
state.disposed ||
|
|
1144
1520
|
!state.session ||
|
|
1145
1521
|
!Array.isArray(state.toolInput.files) ||
|
|
1146
1522
|
state.consumingBridgeFiles
|
|
@@ -1149,14 +1525,12 @@
|
|
|
1149
1525
|
state.consumingBridgeFiles = true;
|
|
1150
1526
|
try {
|
|
1151
1527
|
const pending = state.toolInput.files.filter((reference) => {
|
|
1152
|
-
const id =
|
|
1153
|
-
? boundedString(reference.file_id, 300)
|
|
1154
|
-
: "";
|
|
1528
|
+
const id = normalizedBridgeReferenceId(reference);
|
|
1155
1529
|
return id && !state.consumedBridgeIds.has(id);
|
|
1156
1530
|
});
|
|
1157
1531
|
if (!pending.length) return;
|
|
1158
1532
|
if (!hasOpenAiFunction("getFileDownloadUrl")) {
|
|
1159
|
-
|
|
1533
|
+
requireFallback(
|
|
1160
1534
|
"Este host no expone el puente de archivos. Usa el selector o el enlace seguro.",
|
|
1161
1535
|
"warning",
|
|
1162
1536
|
);
|
|
@@ -1168,11 +1542,13 @@
|
|
|
1168
1542
|
);
|
|
1169
1543
|
const files = [];
|
|
1170
1544
|
for (const reference of pending.slice(0, availableSlots)) {
|
|
1171
|
-
const id =
|
|
1172
|
-
state.consumedBridgeIds.add(id);
|
|
1545
|
+
const id = normalizedBridgeReferenceId(reference);
|
|
1173
1546
|
try {
|
|
1174
|
-
|
|
1547
|
+
const file = await downloadBridgeReference(reference);
|
|
1548
|
+
state.consumedBridgeIds.add(id);
|
|
1549
|
+
files.push(file);
|
|
1175
1550
|
} catch {
|
|
1551
|
+
state.consumedBridgeIds.add(id);
|
|
1176
1552
|
addBridgeFailure(reference);
|
|
1177
1553
|
}
|
|
1178
1554
|
}
|
|
@@ -1182,9 +1558,7 @@
|
|
|
1182
1558
|
const hasUnconsumed =
|
|
1183
1559
|
Array.isArray(state.toolInput.files) &&
|
|
1184
1560
|
state.toolInput.files.some((reference) => {
|
|
1185
|
-
const id =
|
|
1186
|
-
? boundedString(reference.file_id, 300)
|
|
1187
|
-
: "";
|
|
1561
|
+
const id = normalizedBridgeReferenceId(reference);
|
|
1188
1562
|
return id && !state.consumedBridgeIds.has(id);
|
|
1189
1563
|
});
|
|
1190
1564
|
if (
|
|
@@ -1197,82 +1571,75 @@
|
|
|
1197
1571
|
}
|
|
1198
1572
|
}
|
|
1199
1573
|
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
safeSessionLimit() - state.items.length,
|
|
1214
|
-
);
|
|
1215
|
-
const normalized = [];
|
|
1216
|
-
for (const file of files.slice(0, availableSlots)) {
|
|
1217
|
-
try {
|
|
1218
|
-
const fileId = boundedString(
|
|
1219
|
-
await bridge.uploadFile(file, { library: false }),
|
|
1220
|
-
300,
|
|
1221
|
-
);
|
|
1222
|
-
if (!fileId) throw new Error("missing_bridge_file_id");
|
|
1223
|
-
normalized.push(
|
|
1224
|
-
await downloadBridgeReference({
|
|
1225
|
-
fileId,
|
|
1226
|
-
fileName: file.name,
|
|
1227
|
-
mimeType: file.type,
|
|
1228
|
-
}),
|
|
1574
|
+
function processLocalFiles(files) {
|
|
1575
|
+
return enqueueIngestion(async () => {
|
|
1576
|
+
if (
|
|
1577
|
+
state.disposed ||
|
|
1578
|
+
!state.session ||
|
|
1579
|
+
sessionExpired() ||
|
|
1580
|
+
state.uploadBlocked
|
|
1581
|
+
)
|
|
1582
|
+
return;
|
|
1583
|
+
state.workCancelled = false;
|
|
1584
|
+
if (files.length > safeSessionLimit() - state.items.length) {
|
|
1585
|
+
setBatchError(
|
|
1586
|
+
`Puedes subir hasta ${safeSessionLimit()} archivos por sesión.`,
|
|
1229
1587
|
);
|
|
1230
|
-
} catch {
|
|
1231
|
-
addBridgeFailure({ fileName: file.name, mimeType: file.type });
|
|
1232
1588
|
}
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
setBatchError(
|
|
1236
|
-
`Puedes subir hasta ${safeSessionLimit()} archivos por sesión.`,
|
|
1237
|
-
);
|
|
1238
|
-
}
|
|
1239
|
-
await processFiles(normalized);
|
|
1589
|
+
await processFiles(files);
|
|
1590
|
+
});
|
|
1240
1591
|
}
|
|
1241
1592
|
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
const
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
files
|
|
1266
|
-
|
|
1593
|
+
function chooseLibraryFiles() {
|
|
1594
|
+
return enqueueIngestion(async () => {
|
|
1595
|
+
if (
|
|
1596
|
+
state.disposed ||
|
|
1597
|
+
!state.session ||
|
|
1598
|
+
sessionExpired() ||
|
|
1599
|
+
state.uploadBlocked
|
|
1600
|
+
)
|
|
1601
|
+
return;
|
|
1602
|
+
const bridge = openAiBridge();
|
|
1603
|
+
if (
|
|
1604
|
+
!bridge ||
|
|
1605
|
+
typeof bridge.selectFiles !== "function" ||
|
|
1606
|
+
typeof bridge.getFileDownloadUrl !== "function"
|
|
1607
|
+
)
|
|
1608
|
+
return;
|
|
1609
|
+
try {
|
|
1610
|
+
const result = await bridge.selectFiles();
|
|
1611
|
+
const selected = Array.isArray(result) ? result : [];
|
|
1612
|
+
const availableSlots = Math.max(
|
|
1613
|
+
0,
|
|
1614
|
+
safeSessionLimit() - state.items.length,
|
|
1615
|
+
);
|
|
1616
|
+
const files = [];
|
|
1617
|
+
for (const candidate of selected.slice(0, availableSlots)) {
|
|
1618
|
+
if (!isRecord(candidate)) continue;
|
|
1619
|
+
const fileId = boundedString(candidate.fileId, 300);
|
|
1620
|
+
const fileName = boundedString(candidate.fileName, 240);
|
|
1621
|
+
const mimeType = boundedString(candidate.mimeType, 100);
|
|
1622
|
+
if (!fileId || !fileName || !mimeType) continue;
|
|
1623
|
+
try {
|
|
1624
|
+
files.push(
|
|
1625
|
+
await downloadBridgeReference({
|
|
1626
|
+
fileId,
|
|
1627
|
+
fileName,
|
|
1628
|
+
mimeType,
|
|
1629
|
+
}),
|
|
1630
|
+
);
|
|
1631
|
+
} catch {
|
|
1632
|
+
addBridgeFailure({ fileId, fileName, mimeType });
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
await processFiles(files);
|
|
1636
|
+
} catch {
|
|
1637
|
+
setSessionStatus(
|
|
1638
|
+
"No se pudo abrir el selector del host. Usa el selector del navegador.",
|
|
1639
|
+
"warning",
|
|
1267
1640
|
);
|
|
1268
1641
|
}
|
|
1269
|
-
|
|
1270
|
-
} catch {
|
|
1271
|
-
setSessionStatus(
|
|
1272
|
-
"No se pudo abrir el selector del host. Usa el selector del navegador.",
|
|
1273
|
-
"warning",
|
|
1274
|
-
);
|
|
1275
|
-
}
|
|
1642
|
+
});
|
|
1276
1643
|
}
|
|
1277
1644
|
|
|
1278
1645
|
function isBrowserFile(value) {
|
|
@@ -1290,31 +1657,21 @@
|
|
|
1290
1657
|
if (!bridge || typeof bridge.getFileDownloadUrl !== "function") {
|
|
1291
1658
|
throw new Error("bridge_download_unavailable");
|
|
1292
1659
|
}
|
|
1293
|
-
const fileId =
|
|
1294
|
-
reference.file_id || reference.fileId || reference.id,
|
|
1295
|
-
300,
|
|
1296
|
-
);
|
|
1660
|
+
const fileId = normalizedBridgeReferenceId(reference);
|
|
1297
1661
|
if (!fileId) throw new Error("invalid_bridge_file");
|
|
1298
1662
|
const resolved = await bridge.getFileDownloadUrl({ fileId });
|
|
1299
|
-
const temporaryUrl =
|
|
1300
|
-
typeof resolved === "string"
|
|
1301
|
-
? resolved
|
|
1302
|
-
: isRecord(resolved)
|
|
1303
|
-
? boundedString(
|
|
1304
|
-
resolved.download_url ||
|
|
1305
|
-
resolved.downloadUrl ||
|
|
1306
|
-
resolved.url,
|
|
1307
|
-
3000,
|
|
1308
|
-
)
|
|
1309
|
-
: "";
|
|
1663
|
+
const temporaryUrl = normalizedBridgeDownloadUrl(resolved);
|
|
1310
1664
|
const parsedUrl = new URL(temporaryUrl);
|
|
1311
|
-
if (!
|
|
1665
|
+
if (!safeBridgeDownloadUrl(parsedUrl))
|
|
1666
|
+
throw new Error("unsafe_bridge_url");
|
|
1312
1667
|
const response = await fetch(parsedUrl.toString(), {
|
|
1313
1668
|
method: "GET",
|
|
1314
1669
|
credentials: "omit",
|
|
1670
|
+
redirect: "error",
|
|
1671
|
+
signal: activeAbortSignal(),
|
|
1315
1672
|
});
|
|
1316
1673
|
if (!response.ok) throw new Error("bridge_download_failed");
|
|
1317
|
-
const blob = await response
|
|
1674
|
+
const blob = await readBoundedBridgeBlob(response);
|
|
1318
1675
|
const preferredType = boundedString(
|
|
1319
1676
|
reference.mime_type || reference.mimeType || reference.type,
|
|
1320
1677
|
100,
|
|
@@ -1338,6 +1695,48 @@
|
|
|
1338
1695
|
});
|
|
1339
1696
|
}
|
|
1340
1697
|
|
|
1698
|
+
async function readBoundedBridgeBlob(response) {
|
|
1699
|
+
const declaredLength = Number(
|
|
1700
|
+
response.headers.get("content-length") || "",
|
|
1701
|
+
);
|
|
1702
|
+
if (
|
|
1703
|
+
Number.isFinite(declaredLength) &&
|
|
1704
|
+
declaredLength > MAX_FILE_SIZE_BYTES
|
|
1705
|
+
) {
|
|
1706
|
+
throw new Error("bridge_file_too_large");
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
if (!response.body || typeof response.body.getReader !== "function") {
|
|
1710
|
+
const blob = await response.blob();
|
|
1711
|
+
if (blob.size > MAX_FILE_SIZE_BYTES) {
|
|
1712
|
+
throw new Error("bridge_file_too_large");
|
|
1713
|
+
}
|
|
1714
|
+
return blob;
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
const reader = response.body.getReader();
|
|
1718
|
+
const chunks = [];
|
|
1719
|
+
let total = 0;
|
|
1720
|
+
try {
|
|
1721
|
+
while (true) {
|
|
1722
|
+
const next = await reader.read();
|
|
1723
|
+
if (next.done) break;
|
|
1724
|
+
const chunk = next.value;
|
|
1725
|
+
total += chunk.byteLength;
|
|
1726
|
+
if (total > MAX_FILE_SIZE_BYTES) {
|
|
1727
|
+
await reader.cancel();
|
|
1728
|
+
throw new Error("bridge_file_too_large");
|
|
1729
|
+
}
|
|
1730
|
+
chunks.push(chunk);
|
|
1731
|
+
}
|
|
1732
|
+
} finally {
|
|
1733
|
+
reader.releaseLock?.();
|
|
1734
|
+
}
|
|
1735
|
+
return new Blob(chunks, {
|
|
1736
|
+
type: response.headers.get("content-type") || "",
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1341
1740
|
function addBridgeFailure(reference) {
|
|
1342
1741
|
const type = boundedString(
|
|
1343
1742
|
reference.mime_type || reference.mimeType || reference.type,
|
|
@@ -1345,11 +1744,19 @@
|
|
|
1345
1744
|
);
|
|
1346
1745
|
const mediaKind = mediaKindForType(type) || "image";
|
|
1347
1746
|
const item = createItem(null, mediaKind);
|
|
1747
|
+
item.displayName = boundedString(
|
|
1748
|
+
reference.file_name || reference.fileName || reference.name,
|
|
1749
|
+
300,
|
|
1750
|
+
);
|
|
1348
1751
|
item.status = "failed";
|
|
1349
1752
|
item.error =
|
|
1350
1753
|
"No se pudo leer el archivo temporal del host. Usa el selector o el enlace seguro.";
|
|
1351
1754
|
item.failureStage = "bridge";
|
|
1352
1755
|
state.items.push(item);
|
|
1756
|
+
requireFallback(
|
|
1757
|
+
"No se pudo leer el archivo temporal del host. Usa el selector o el enlace seguro.",
|
|
1758
|
+
"warning",
|
|
1759
|
+
);
|
|
1353
1760
|
renderItems();
|
|
1354
1761
|
emitAnalytics("ads_meta_remote_upload_error", mediaKind, {
|
|
1355
1762
|
failureStage: "bridge",
|
|
@@ -1358,7 +1765,14 @@
|
|
|
1358
1765
|
}
|
|
1359
1766
|
|
|
1360
1767
|
async function processFiles(fileCandidates) {
|
|
1361
|
-
if (
|
|
1768
|
+
if (
|
|
1769
|
+
state.disposed ||
|
|
1770
|
+
!state.session ||
|
|
1771
|
+
sessionExpired() ||
|
|
1772
|
+
state.uploadBlocked ||
|
|
1773
|
+
state.busyBatch
|
|
1774
|
+
)
|
|
1775
|
+
return;
|
|
1362
1776
|
const files = fileCandidates.filter(isBrowserFile);
|
|
1363
1777
|
if (!files.length) return;
|
|
1364
1778
|
setBatchError("");
|
|
@@ -1395,6 +1809,7 @@
|
|
|
1395
1809
|
id: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`,
|
|
1396
1810
|
uploadId: createClientUuid(),
|
|
1397
1811
|
file,
|
|
1812
|
+
displayName: file?.name || "",
|
|
1398
1813
|
uploadFile: file,
|
|
1399
1814
|
mediaKind,
|
|
1400
1815
|
status: "pending",
|
|
@@ -1403,6 +1818,7 @@
|
|
|
1403
1818
|
error: "",
|
|
1404
1819
|
failureStage: "",
|
|
1405
1820
|
stagingAssetId: "",
|
|
1821
|
+
previewUrl: "",
|
|
1406
1822
|
};
|
|
1407
1823
|
}
|
|
1408
1824
|
|
|
@@ -1435,6 +1851,18 @@
|
|
|
1435
1851
|
failureStage: failure.stage,
|
|
1436
1852
|
errorType: failure.code,
|
|
1437
1853
|
});
|
|
1854
|
+
if (failure.code === "session_expired") {
|
|
1855
|
+
requireFallback(
|
|
1856
|
+
"La sesión expiró. Abre el enlace seguro para crear otra.",
|
|
1857
|
+
"danger",
|
|
1858
|
+
true,
|
|
1859
|
+
);
|
|
1860
|
+
} else if (failure.stage !== "validation") {
|
|
1861
|
+
requireFallback(
|
|
1862
|
+
"La carga no terminó. Puedes reintentarlo o continuar desde el enlace seguro.",
|
|
1863
|
+
"warning",
|
|
1864
|
+
);
|
|
1865
|
+
}
|
|
1438
1866
|
}
|
|
1439
1867
|
renderItems();
|
|
1440
1868
|
}
|
|
@@ -1606,6 +2034,12 @@
|
|
|
1606
2034
|
alpha: file.type !== "image/jpeg",
|
|
1607
2035
|
});
|
|
1608
2036
|
if (!context) throw new Error("canvas_unavailable");
|
|
2037
|
+
if (file.type === "image/png") {
|
|
2038
|
+
context.save();
|
|
2039
|
+
context.fillStyle = "#ffffff";
|
|
2040
|
+
context.fillRect(0, 0, canvas.width, canvas.height);
|
|
2041
|
+
context.restore();
|
|
2042
|
+
}
|
|
1609
2043
|
context.drawImage(
|
|
1610
2044
|
inspection.image,
|
|
1611
2045
|
0,
|
|
@@ -1613,16 +2047,13 @@
|
|
|
1613
2047
|
canvas.width,
|
|
1614
2048
|
canvas.height,
|
|
1615
2049
|
);
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
0.85,
|
|
1624
|
-
);
|
|
1625
|
-
});
|
|
2050
|
+
let blob = await canvasBlob(canvas, file.type);
|
|
2051
|
+
if (
|
|
2052
|
+
file.type === "image/png" &&
|
|
2053
|
+
blob.size > IMAGE_COMPRESSION_MAX_BYTES
|
|
2054
|
+
) {
|
|
2055
|
+
blob = await canvasBlob(canvas, "image/jpeg");
|
|
2056
|
+
}
|
|
1626
2057
|
const compressed = new File([blob], file.name, {
|
|
1627
2058
|
type: blob.type || file.type,
|
|
1628
2059
|
lastModified: file.lastModified,
|
|
@@ -1651,6 +2082,19 @@
|
|
|
1651
2082
|
}
|
|
1652
2083
|
}
|
|
1653
2084
|
|
|
2085
|
+
function canvasBlob(canvas, type) {
|
|
2086
|
+
return new Promise((resolve, reject) => {
|
|
2087
|
+
canvas.toBlob(
|
|
2088
|
+
(result) =>
|
|
2089
|
+
result
|
|
2090
|
+
? resolve(result)
|
|
2091
|
+
: reject(new Error("compression_failed")),
|
|
2092
|
+
type,
|
|
2093
|
+
0.85,
|
|
2094
|
+
);
|
|
2095
|
+
});
|
|
2096
|
+
}
|
|
2097
|
+
|
|
1654
2098
|
async function uploadWithRetries(item) {
|
|
1655
2099
|
let lastError = null;
|
|
1656
2100
|
for (let attempt = 1; attempt <= MAX_UPLOAD_ATTEMPTS; attempt += 1) {
|
|
@@ -1675,6 +2119,7 @@
|
|
|
1675
2119
|
return;
|
|
1676
2120
|
} catch (error) {
|
|
1677
2121
|
if (error && error.message === "session_expired") throw error;
|
|
2122
|
+
if (state.disposed || state.workCancelled) throw error;
|
|
1678
2123
|
lastError = error;
|
|
1679
2124
|
}
|
|
1680
2125
|
}
|
|
@@ -1712,6 +2157,7 @@
|
|
|
1712
2157
|
fileSize: file.size,
|
|
1713
2158
|
}),
|
|
1714
2159
|
credentials: "omit",
|
|
2160
|
+
signal: activeAbortSignal(),
|
|
1715
2161
|
});
|
|
1716
2162
|
if (!response.ok) {
|
|
1717
2163
|
const error = new Error(
|
|
@@ -1761,6 +2207,21 @@
|
|
|
1761
2207
|
);
|
|
1762
2208
|
}
|
|
1763
2209
|
|
|
2210
|
+
function safeBridgeDownloadUrl(url) {
|
|
2211
|
+
if (
|
|
2212
|
+
url.protocol !== "https:" ||
|
|
2213
|
+
url.username ||
|
|
2214
|
+
url.password ||
|
|
2215
|
+
url.port
|
|
2216
|
+
)
|
|
2217
|
+
return false;
|
|
2218
|
+
const hostname = url.hostname.toLowerCase();
|
|
2219
|
+
return (
|
|
2220
|
+
hostname === "files.oaiusercontent.com" ||
|
|
2221
|
+
hostname.endsWith(".oaiusercontent.com")
|
|
2222
|
+
);
|
|
2223
|
+
}
|
|
2224
|
+
|
|
1764
2225
|
function safeUploadPageUrl(url) {
|
|
1765
2226
|
if (url.username || url.password) return false;
|
|
1766
2227
|
if (
|
|
@@ -1779,23 +2240,32 @@
|
|
|
1779
2240
|
function putFileWithProgress(url, file, onProgress) {
|
|
1780
2241
|
return new Promise((resolve, reject) => {
|
|
1781
2242
|
const request = new XMLHttpRequest();
|
|
2243
|
+
let settled = false;
|
|
2244
|
+
const finish = (callback) => {
|
|
2245
|
+
if (settled) return;
|
|
2246
|
+
settled = true;
|
|
2247
|
+
state.activeXhrs.delete(request);
|
|
2248
|
+
callback();
|
|
2249
|
+
};
|
|
1782
2250
|
request.open("PUT", url);
|
|
1783
2251
|
request.timeout = 5 * 60 * 1000;
|
|
1784
2252
|
request.setRequestHeader("Content-Type", file.type);
|
|
2253
|
+
state.activeXhrs.add(request);
|
|
1785
2254
|
request.upload.addEventListener("progress", (event) => {
|
|
1786
2255
|
if (event.lengthComputable && event.total > 0) {
|
|
1787
2256
|
onProgress(Math.round((event.loaded / event.total) * 100));
|
|
1788
2257
|
}
|
|
1789
2258
|
});
|
|
1790
2259
|
request.addEventListener("load", () => {
|
|
1791
|
-
if (request.status >= 200 && request.status < 300)
|
|
1792
|
-
|
|
2260
|
+
if (request.status >= 200 && request.status < 300)
|
|
2261
|
+
finish(resolve);
|
|
2262
|
+
else finish(() => reject(uploadTransportError("put_failed")));
|
|
1793
2263
|
});
|
|
1794
2264
|
request.addEventListener("error", () =>
|
|
1795
|
-
reject(uploadTransportError("network_error")),
|
|
2265
|
+
finish(() => reject(uploadTransportError("network_error"))),
|
|
1796
2266
|
);
|
|
1797
2267
|
request.addEventListener("timeout", () =>
|
|
1798
|
-
reject(uploadTransportError("upload_timeout")),
|
|
2268
|
+
finish(() => reject(uploadTransportError("upload_timeout"))),
|
|
1799
2269
|
);
|
|
1800
2270
|
request.send(file);
|
|
1801
2271
|
});
|
|
@@ -1846,11 +2316,38 @@
|
|
|
1846
2316
|
|
|
1847
2317
|
function removeItem(index) {
|
|
1848
2318
|
if (state.busyBatch) return;
|
|
1849
|
-
state.items.splice(index, 1);
|
|
2319
|
+
const [item] = state.items.splice(index, 1);
|
|
2320
|
+
if (item) revokePreviewUrl(item);
|
|
1850
2321
|
renderItems();
|
|
1851
2322
|
setPickerAvailability();
|
|
1852
2323
|
}
|
|
1853
2324
|
|
|
2325
|
+
function revokePreviewUrl(item) {
|
|
2326
|
+
if (
|
|
2327
|
+
item.previewUrl &&
|
|
2328
|
+
typeof window.URL.revokeObjectURL === "function"
|
|
2329
|
+
) {
|
|
2330
|
+
window.URL.revokeObjectURL(item.previewUrl);
|
|
2331
|
+
item.previewUrl = "";
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
function previewUrlFor(item) {
|
|
2336
|
+
if (
|
|
2337
|
+
!item.file ||
|
|
2338
|
+
item.mediaKind !== "image" ||
|
|
2339
|
+
!IMAGE_TYPES.has(item.file.type)
|
|
2340
|
+
)
|
|
2341
|
+
return "";
|
|
2342
|
+
if (
|
|
2343
|
+
!item.previewUrl &&
|
|
2344
|
+
typeof window.URL.createObjectURL === "function"
|
|
2345
|
+
) {
|
|
2346
|
+
item.previewUrl = window.URL.createObjectURL(item.file);
|
|
2347
|
+
}
|
|
2348
|
+
return item.previewUrl;
|
|
2349
|
+
}
|
|
2350
|
+
|
|
1854
2351
|
function renderItems() {
|
|
1855
2352
|
const fragment = document.createDocumentFragment();
|
|
1856
2353
|
state.items.forEach((item, index) => {
|
|
@@ -1858,33 +2355,46 @@
|
|
|
1858
2355
|
card.className = "file-card";
|
|
1859
2356
|
card.dataset.testid = `meta-upload-file-${index}`;
|
|
1860
2357
|
|
|
1861
|
-
const
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
2358
|
+
const preview = document.createElement("div");
|
|
2359
|
+
preview.className = "file-preview";
|
|
2360
|
+
preview.dataset.testid = `meta-upload-file-preview-${index}`;
|
|
2361
|
+
const previewUrl = previewUrlFor(item);
|
|
2362
|
+
if (previewUrl) {
|
|
2363
|
+
const image = document.createElement("img");
|
|
2364
|
+
image.src = previewUrl;
|
|
2365
|
+
image.alt =
|
|
2366
|
+
item.file?.name || item.displayName || "Uploaded image";
|
|
2367
|
+
image.dataset.testid = `meta-upload-preview-${index}`;
|
|
2368
|
+
preview.append(image);
|
|
2369
|
+
} else if (item.mediaKind === "video") {
|
|
2370
|
+
const video = document.createElement("span");
|
|
2371
|
+
video.className = "video-preview";
|
|
2372
|
+
video.setAttribute("aria-hidden", "true");
|
|
2373
|
+
video.textContent = "▶";
|
|
2374
|
+
preview.append(video);
|
|
2375
|
+
} else {
|
|
2376
|
+
const placeholder = document.createElement("span");
|
|
2377
|
+
placeholder.className = "file-kind";
|
|
2378
|
+
placeholder.setAttribute("aria-hidden", "true");
|
|
2379
|
+
placeholder.textContent = "▧";
|
|
2380
|
+
preview.append(placeholder);
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
if (item.status === "uploading" || item.status === "uploaded") {
|
|
2384
|
+
const progress = document.createElement("div");
|
|
2385
|
+
progress.className = "progress";
|
|
2386
|
+
progress.setAttribute("role", "progressbar");
|
|
2387
|
+
progress.setAttribute("aria-valuemin", "0");
|
|
2388
|
+
progress.setAttribute("aria-valuemax", "100");
|
|
2389
|
+
progress.setAttribute("aria-valuenow", String(item.progress));
|
|
2390
|
+
progress.dataset.testid = `meta-upload-progress-${index}`;
|
|
2391
|
+
const bar = document.createElement("div");
|
|
2392
|
+
bar.className = "progress-bar";
|
|
2393
|
+
bar.style.width = `${item.progress}%`;
|
|
2394
|
+
progress.append(bar);
|
|
2395
|
+
preview.append(progress);
|
|
2396
|
+
}
|
|
1885
2397
|
|
|
1886
|
-
const actions = document.createElement("div");
|
|
1887
|
-
actions.className = "file-actions";
|
|
1888
2398
|
if (
|
|
1889
2399
|
item.status === "failed" &&
|
|
1890
2400
|
item.failureStage !== "validation" &&
|
|
@@ -1892,42 +2402,50 @@
|
|
|
1892
2402
|
) {
|
|
1893
2403
|
const retry = document.createElement("button");
|
|
1894
2404
|
retry.type = "button";
|
|
1895
|
-
retry.className = "
|
|
2405
|
+
retry.className = "overlay-button retry-overlay";
|
|
1896
2406
|
retry.dataset.testid = `meta-upload-retry-${index}`;
|
|
1897
|
-
retry.
|
|
2407
|
+
retry.setAttribute("aria-label", `Retry ${item.file.name}`);
|
|
2408
|
+
retry.textContent = "Retry";
|
|
1898
2409
|
retry.addEventListener("click", () => void retryItem(index));
|
|
1899
|
-
|
|
2410
|
+
preview.append(retry);
|
|
1900
2411
|
}
|
|
2412
|
+
|
|
1901
2413
|
const remove = document.createElement("button");
|
|
1902
2414
|
remove.type = "button";
|
|
1903
|
-
remove.className = "
|
|
2415
|
+
remove.className = "overlay-button remove-overlay";
|
|
1904
2416
|
remove.dataset.testid = `meta-upload-remove-${index}`;
|
|
1905
|
-
remove.
|
|
2417
|
+
remove.setAttribute(
|
|
2418
|
+
"aria-label",
|
|
2419
|
+
`Remove ${item.file?.name || item.displayName || `file ${index + 1}`}`,
|
|
2420
|
+
);
|
|
2421
|
+
remove.textContent = "×";
|
|
1906
2422
|
remove.disabled = ["uploading", "validating", "uploaded"].includes(
|
|
1907
2423
|
item.status,
|
|
1908
2424
|
);
|
|
1909
2425
|
remove.addEventListener("click", () => removeItem(index));
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
card.append(head);
|
|
2426
|
+
preview.append(remove);
|
|
2427
|
+
card.append(preview);
|
|
1913
2428
|
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
2429
|
+
const head = document.createElement("div");
|
|
2430
|
+
head.className = "file-head";
|
|
2431
|
+
const label = document.createElement("p");
|
|
2432
|
+
label.className = "file-label";
|
|
2433
|
+
label.textContent =
|
|
2434
|
+
item.file?.name || item.displayName || `File ${index + 1}`;
|
|
2435
|
+
const status = document.createElement("p");
|
|
2436
|
+
status.className = `file-state${item.status === "failed" ? " error" : ""}`;
|
|
2437
|
+
status.dataset.testid =
|
|
2438
|
+
item.status === "failed"
|
|
2439
|
+
? `meta-upload-file-error-${index}`
|
|
2440
|
+
: `meta-upload-file-status-${index}`;
|
|
2441
|
+
status.textContent = itemStatusText(item);
|
|
2442
|
+
head.append(label, status);
|
|
2443
|
+
card.append(head);
|
|
1928
2444
|
fragment.append(card);
|
|
1929
2445
|
});
|
|
1930
2446
|
elements.fileList.replaceChildren(fragment);
|
|
2447
|
+
updateUploadTitle();
|
|
2448
|
+
scheduleIntrinsicSizeReport(true);
|
|
1931
2449
|
}
|
|
1932
2450
|
|
|
1933
2451
|
function itemStatusText(item) {
|
|
@@ -1950,24 +2468,36 @@
|
|
|
1950
2468
|
const status = parseStatusResult(result);
|
|
1951
2469
|
state.assets = status.assets;
|
|
1952
2470
|
if (!status.assets.length) {
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
2471
|
+
if (status.legacyKindAmbiguous) {
|
|
2472
|
+
requireFallback(
|
|
2473
|
+
"El tipo del asset legacy es ambiguo. Vuelve a comprobar o usa el enlace seguro.",
|
|
2474
|
+
"warning",
|
|
2475
|
+
);
|
|
2476
|
+
} else {
|
|
2477
|
+
setFallbackVisibility(state.fallbackRequired);
|
|
2478
|
+
setSessionStatus(
|
|
2479
|
+
"La sesión sigue pendiente. Termina la carga o vuelve a comprobar.",
|
|
2480
|
+
"warning",
|
|
2481
|
+
);
|
|
2482
|
+
}
|
|
1959
2483
|
elements.destinationSection.hidden = true;
|
|
1960
|
-
elements.pendingRefresh.hidden =
|
|
2484
|
+
elements.pendingRefresh.hidden = true;
|
|
1961
2485
|
return;
|
|
1962
2486
|
}
|
|
1963
2487
|
elements.pendingRefresh.hidden = true;
|
|
1964
2488
|
|
|
1965
|
-
const locallyUploaded = state.items.filter(
|
|
1966
|
-
(item) => item.status === "uploaded",
|
|
1967
|
-
).length;
|
|
1968
2489
|
const partial =
|
|
1969
2490
|
status.status === "pending" ||
|
|
1970
|
-
|
|
2491
|
+
(state.items.length > 0 &&
|
|
2492
|
+
status.assets.length < state.items.length);
|
|
2493
|
+
if (partial) {
|
|
2494
|
+
requireFallback(
|
|
2495
|
+
"Carga parcial verificada. Puedes continuar desde el enlace seguro si algún archivo no terminó.",
|
|
2496
|
+
"warning",
|
|
2497
|
+
);
|
|
2498
|
+
} else {
|
|
2499
|
+
setFallbackVisibility(state.fallbackRequired);
|
|
2500
|
+
}
|
|
1971
2501
|
setSessionStatus(
|
|
1972
2502
|
partial
|
|
1973
2503
|
? "Carga parcial verificada. Continuaremos solo con los assets listos."
|
|
@@ -1986,17 +2516,16 @@
|
|
|
1986
2516
|
(error && /expired|expirad/i.test(String(error.message)));
|
|
1987
2517
|
const toolsUnavailable =
|
|
1988
2518
|
error && error.message === "host_tools_unavailable";
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
:
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
);
|
|
1997
|
-
elements.pendingRefresh.hidden = expired;
|
|
2519
|
+
const message = expired
|
|
2520
|
+
? "La sesión está expirada. Abre el enlace seguro para solicitar una nueva."
|
|
2521
|
+
: toolsUnavailable
|
|
2522
|
+
? "host_tools_unavailable: este host no permite comprobar el staging desde la App. Usa el enlace seguro."
|
|
2523
|
+
: "No se pudo comprobar la sesión. Conserva el enlace y vuelve a intentarlo.";
|
|
2524
|
+
requireFallback(message, "danger", expired);
|
|
2525
|
+
elements.pendingRefresh.hidden = true;
|
|
1998
2526
|
} finally {
|
|
1999
2527
|
elements.checkStatus.disabled = false;
|
|
2528
|
+
elements.pendingRefresh.hidden = true;
|
|
2000
2529
|
elements.pendingRefresh.disabled = false;
|
|
2001
2530
|
}
|
|
2002
2531
|
}
|
|
@@ -2012,7 +2541,15 @@
|
|
|
2012
2541
|
const bridge = openAiBridge();
|
|
2013
2542
|
let result;
|
|
2014
2543
|
if (bridge && typeof bridge.callTool === "function") {
|
|
2015
|
-
|
|
2544
|
+
try {
|
|
2545
|
+
result = await bridge.callTool(name, args);
|
|
2546
|
+
} catch (error) {
|
|
2547
|
+
if (!state.hostCanServerTools) throw error;
|
|
2548
|
+
result = await rpcRequest("tools/call", {
|
|
2549
|
+
name,
|
|
2550
|
+
arguments: args,
|
|
2551
|
+
});
|
|
2552
|
+
}
|
|
2016
2553
|
} else if (state.hostCanServerTools) {
|
|
2017
2554
|
result = await rpcRequest("tools/call", { name, arguments: args });
|
|
2018
2555
|
} else {
|
|
@@ -2125,18 +2662,19 @@
|
|
|
2125
2662
|
fillSelect(
|
|
2126
2663
|
elements.portfolio,
|
|
2127
2664
|
state.destinations.portfolios,
|
|
2128
|
-
"
|
|
2665
|
+
"Portfolio",
|
|
2129
2666
|
(entry) => entry.name,
|
|
2130
2667
|
);
|
|
2131
2668
|
fillSelect(
|
|
2132
2669
|
elements.page,
|
|
2133
2670
|
state.destinations.pages,
|
|
2134
|
-
"
|
|
2671
|
+
"Facebook Page",
|
|
2135
2672
|
(entry) => entry.name,
|
|
2136
2673
|
);
|
|
2137
2674
|
renderAdAccounts();
|
|
2138
2675
|
elements.destinationSection.hidden = false;
|
|
2139
2676
|
updateDestinationSubmit();
|
|
2677
|
+
scheduleIntrinsicSizeReport(true);
|
|
2140
2678
|
}
|
|
2141
2679
|
|
|
2142
2680
|
function renderAdAccounts() {
|
|
@@ -2149,9 +2687,7 @@
|
|
|
2149
2687
|
fillSelect(
|
|
2150
2688
|
elements.adAccount,
|
|
2151
2689
|
accounts,
|
|
2152
|
-
portfolioId
|
|
2153
|
-
? "Selecciona una cuenta"
|
|
2154
|
-
: "Elige primero un portfolio",
|
|
2690
|
+
portfolioId ? "Ad account" : "Ad account",
|
|
2155
2691
|
(entry) =>
|
|
2156
2692
|
`${entry.name}${entry.currency ? ` · ${entry.currency}` : ""}`,
|
|
2157
2693
|
);
|
|
@@ -2236,6 +2772,7 @@
|
|
|
2236
2772
|
"Copia el handoff exacto y pégalo en el chat.",
|
|
2237
2773
|
"warning",
|
|
2238
2774
|
);
|
|
2775
|
+
scheduleIntrinsicSizeReport(true);
|
|
2239
2776
|
}
|
|
2240
2777
|
|
|
2241
2778
|
async function copyHandoff() {
|