@productmaker/mcp 2.8.3 → 2.9.2
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 +962 -464
- 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,23 @@
|
|
|
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
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
546
|
+
.compatibility-control {
|
|
547
|
+
/* Retained for older hosts; recovery is visible only through the secure fallback. */
|
|
548
|
+
display: none !important;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
.sr-only {
|
|
552
|
+
position: absolute;
|
|
553
|
+
width: 1px;
|
|
554
|
+
height: 1px;
|
|
555
|
+
padding: 0;
|
|
556
|
+
overflow: hidden;
|
|
557
|
+
clip: rect(0, 0, 0, 0);
|
|
558
|
+
white-space: nowrap;
|
|
559
|
+
border: 0;
|
|
508
560
|
}
|
|
509
561
|
|
|
510
562
|
[hidden] {
|
|
@@ -513,23 +565,29 @@
|
|
|
513
565
|
|
|
514
566
|
@media (max-width: 640px) {
|
|
515
567
|
.app {
|
|
516
|
-
padding:
|
|
568
|
+
padding: 8px;
|
|
517
569
|
}
|
|
518
570
|
|
|
519
571
|
.card {
|
|
520
|
-
border-radius:
|
|
572
|
+
border-radius: 20px;
|
|
521
573
|
}
|
|
522
574
|
|
|
523
575
|
.hero,
|
|
524
576
|
.section {
|
|
525
|
-
padding:
|
|
577
|
+
padding-right: 14px;
|
|
578
|
+
padding-left: 14px;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
.dropzone {
|
|
582
|
+
min-height: 230px;
|
|
526
583
|
}
|
|
527
584
|
|
|
528
585
|
.destination-grid {
|
|
529
586
|
grid-template-columns: 1fr;
|
|
530
587
|
}
|
|
531
588
|
|
|
532
|
-
.button
|
|
589
|
+
.destination-actions .button,
|
|
590
|
+
.handoff-actions .button {
|
|
533
591
|
width: 100%;
|
|
534
592
|
}
|
|
535
593
|
}
|
|
@@ -548,16 +606,16 @@
|
|
|
548
606
|
<main class="app" data-testid="meta-upload-root">
|
|
549
607
|
<article class="card">
|
|
550
608
|
<header class="hero">
|
|
551
|
-
<
|
|
552
|
-
<
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
</
|
|
609
|
+
<div class="upload-header">
|
|
610
|
+
<p class="upload-title" data-testid="meta-upload-title">
|
|
611
|
+
Upload image
|
|
612
|
+
</p>
|
|
613
|
+
<span class="brand-mark" aria-label="ProductMaker" role="img"
|
|
614
|
+
>↗</span
|
|
615
|
+
>
|
|
616
|
+
</div>
|
|
559
617
|
<div class="session-row">
|
|
560
|
-
<code class="session-id" data-testid="meta-upload-session-id"
|
|
618
|
+
<code class="session-id" data-testid="meta-upload-session-id" hidden
|
|
561
619
|
>Esperando sesión…</code
|
|
562
620
|
>
|
|
563
621
|
<a
|
|
@@ -577,52 +635,46 @@
|
|
|
577
635
|
></p>
|
|
578
636
|
</header>
|
|
579
637
|
|
|
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>
|
|
638
|
+
<section class="section" aria-label="Upload media">
|
|
586
639
|
<div
|
|
587
640
|
class="dropzone"
|
|
588
641
|
data-testid="meta-upload-dropzone"
|
|
589
642
|
data-dragging="false"
|
|
590
643
|
aria-disabled="true"
|
|
644
|
+
aria-describedby="meta-upload-constraints"
|
|
591
645
|
>
|
|
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
|
|
646
|
+
<button
|
|
647
|
+
class="drop-trigger"
|
|
648
|
+
type="button"
|
|
649
|
+
data-testid="meta-upload-picker-btn"
|
|
650
|
+
disabled
|
|
651
|
+
>
|
|
652
|
+
<span class="drop-icon" aria-hidden="true">+</span>
|
|
653
|
+
<span class="drop-title" data-testid="meta-upload-drop-title"
|
|
654
|
+
>Upload image</span
|
|
613
655
|
>
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
656
|
+
</button>
|
|
657
|
+
<button
|
|
658
|
+
class="compatibility-control"
|
|
659
|
+
type="button"
|
|
660
|
+
data-testid="meta-upload-library-btn"
|
|
661
|
+
hidden
|
|
662
|
+
disabled
|
|
663
|
+
aria-label="Choose from ChatGPT library"
|
|
664
|
+
></button>
|
|
665
|
+
<input
|
|
666
|
+
class="native-input"
|
|
667
|
+
data-testid="meta-upload-input"
|
|
668
|
+
type="file"
|
|
669
|
+
accept="image/jpeg,image/png,image/webp,video/mp4,video/quicktime,video/webm"
|
|
670
|
+
multiple
|
|
671
|
+
disabled
|
|
672
|
+
/>
|
|
625
673
|
</div>
|
|
674
|
+
<p class="drop-help" id="meta-upload-constraints">
|
|
675
|
+
JPEG, PNG, WebP, MP4, MOV o WebM. Hasta 10 archivos de 350 MB; las
|
|
676
|
+
imágenes grandes se comprimen y los videos no se transcodifican.
|
|
677
|
+
</p>
|
|
626
678
|
<p
|
|
627
679
|
class="batch-error"
|
|
628
680
|
data-testid="meta-upload-batch-error"
|
|
@@ -634,58 +686,38 @@
|
|
|
634
686
|
data-testid="meta-upload-file-list"
|
|
635
687
|
aria-live="polite"
|
|
636
688
|
></div>
|
|
637
|
-
<button
|
|
638
|
-
class="button secondary"
|
|
639
|
-
type="button"
|
|
640
|
-
data-testid="meta-upload-pending-refresh"
|
|
641
|
-
hidden
|
|
642
|
-
>
|
|
643
|
-
Actualizar estado del staging
|
|
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>
|
|
652
689
|
</section>
|
|
653
690
|
|
|
654
691
|
<section
|
|
655
692
|
class="section"
|
|
656
693
|
data-testid="meta-upload-destination-section"
|
|
657
|
-
aria-
|
|
694
|
+
aria-label="Meta destination"
|
|
658
695
|
hidden
|
|
659
696
|
>
|
|
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
697
|
<div class="destination-grid">
|
|
666
698
|
<div class="field">
|
|
667
|
-
<label for="portfolio-select">Portfolio</label>
|
|
699
|
+
<label class="sr-only" for="portfolio-select">Portfolio</label>
|
|
668
700
|
<select
|
|
669
701
|
id="portfolio-select"
|
|
670
702
|
data-testid="meta-upload-portfolio-select"
|
|
671
703
|
>
|
|
672
|
-
<option value="">
|
|
704
|
+
<option value="">Portfolio</option>
|
|
673
705
|
</select>
|
|
674
706
|
</div>
|
|
675
707
|
<div class="field">
|
|
676
|
-
<label for="ad-account-select">
|
|
708
|
+
<label class="sr-only" for="ad-account-select">Ad account</label>
|
|
677
709
|
<select
|
|
678
710
|
id="ad-account-select"
|
|
679
711
|
data-testid="meta-upload-ad-account-select"
|
|
680
712
|
disabled
|
|
681
713
|
>
|
|
682
|
-
<option value="">
|
|
714
|
+
<option value="">Ad account</option>
|
|
683
715
|
</select>
|
|
684
716
|
</div>
|
|
685
717
|
<div class="field">
|
|
686
|
-
<label for="page-select">
|
|
718
|
+
<label class="sr-only" for="page-select">Facebook Page</label>
|
|
687
719
|
<select id="page-select" data-testid="meta-upload-page-select">
|
|
688
|
-
<option value="">
|
|
720
|
+
<option value="">Facebook Page</option>
|
|
689
721
|
</select>
|
|
690
722
|
</div>
|
|
691
723
|
</div>
|
|
@@ -696,14 +728,7 @@
|
|
|
696
728
|
data-testid="meta-upload-submit-destination"
|
|
697
729
|
disabled
|
|
698
730
|
>
|
|
699
|
-
|
|
700
|
-
</button>
|
|
701
|
-
<button
|
|
702
|
-
class="button secondary"
|
|
703
|
-
type="button"
|
|
704
|
-
data-testid="meta-upload-check-status"
|
|
705
|
-
>
|
|
706
|
-
Actualizar estado
|
|
731
|
+
Continue
|
|
707
732
|
</button>
|
|
708
733
|
</div>
|
|
709
734
|
</section>
|
|
@@ -714,10 +739,8 @@
|
|
|
714
739
|
aria-labelledby="handoff-heading"
|
|
715
740
|
hidden
|
|
716
741
|
>
|
|
717
|
-
<
|
|
718
|
-
|
|
719
|
-
Este host no puede devolver el mensaje automáticamente. Copia este
|
|
720
|
-
JSON exacto en el chat.
|
|
742
|
+
<p id="handoff-heading" class="section-copy muted">
|
|
743
|
+
Copy this handoff into the chat.
|
|
721
744
|
</p>
|
|
722
745
|
<textarea data-testid="meta-upload-handoff-copy" readonly></textarea>
|
|
723
746
|
<div class="handoff-actions">
|
|
@@ -726,7 +749,7 @@
|
|
|
726
749
|
type="button"
|
|
727
750
|
data-testid="meta-upload-copy-btn"
|
|
728
751
|
>
|
|
729
|
-
|
|
752
|
+
Copy handoff
|
|
730
753
|
</button>
|
|
731
754
|
</div>
|
|
732
755
|
</section>
|
|
@@ -765,6 +788,7 @@
|
|
|
765
788
|
|
|
766
789
|
const elements = {
|
|
767
790
|
root: document.querySelector('[data-testid="meta-upload-root"]'),
|
|
791
|
+
title: document.querySelector('[data-testid="meta-upload-title"]'),
|
|
768
792
|
sessionId: document.querySelector(
|
|
769
793
|
'[data-testid="meta-upload-session-id"]',
|
|
770
794
|
),
|
|
@@ -777,6 +801,9 @@
|
|
|
777
801
|
dropzone: document.querySelector(
|
|
778
802
|
'[data-testid="meta-upload-dropzone"]',
|
|
779
803
|
),
|
|
804
|
+
dropTitle: document.querySelector(
|
|
805
|
+
'[data-testid="meta-upload-drop-title"]',
|
|
806
|
+
),
|
|
780
807
|
pickerButton: document.querySelector(
|
|
781
808
|
'[data-testid="meta-upload-picker-btn"]',
|
|
782
809
|
),
|
|
@@ -805,12 +832,6 @@
|
|
|
805
832
|
submitDestination: document.querySelector(
|
|
806
833
|
'[data-testid="meta-upload-submit-destination"]',
|
|
807
834
|
),
|
|
808
|
-
checkStatus: document.querySelector(
|
|
809
|
-
'[data-testid="meta-upload-check-status"]',
|
|
810
|
-
),
|
|
811
|
-
pendingRefresh: document.querySelector(
|
|
812
|
-
'[data-testid="meta-upload-pending-refresh"]',
|
|
813
|
-
),
|
|
814
835
|
handoffSection: document.querySelector(
|
|
815
836
|
'[data-testid="meta-upload-handoff-section"]',
|
|
816
837
|
),
|
|
@@ -823,12 +844,17 @@
|
|
|
823
844
|
};
|
|
824
845
|
|
|
825
846
|
const state = {
|
|
847
|
+
disposed: false,
|
|
826
848
|
initialized: false,
|
|
827
849
|
standardHostReady: false,
|
|
828
850
|
hostCanMessage: false,
|
|
829
851
|
hostCanServerTools: false,
|
|
852
|
+
hostContainerDimensions: null,
|
|
830
853
|
rpcId: 0,
|
|
831
854
|
pendingRpc: new Map(),
|
|
855
|
+
pendingSizeReport: false,
|
|
856
|
+
forceSizeReport: false,
|
|
857
|
+
lastReportedHeight: 0,
|
|
832
858
|
session: null,
|
|
833
859
|
toolInput: {},
|
|
834
860
|
items: [],
|
|
@@ -836,7 +862,18 @@
|
|
|
836
862
|
destinations: { portfolios: [], adAccounts: [], pages: [] },
|
|
837
863
|
consumedBridgeIds: new Set(),
|
|
838
864
|
consumingBridgeFiles: false,
|
|
865
|
+
bridgeConsumptionQueued: false,
|
|
866
|
+
ingestionQueue: Promise.resolve(),
|
|
839
867
|
busyBatch: false,
|
|
868
|
+
uploadBlocked: false,
|
|
869
|
+
fallbackRequired: false,
|
|
870
|
+
workCancelled: false,
|
|
871
|
+
abortController: null,
|
|
872
|
+
activeXhrs: new Set(),
|
|
873
|
+
resizeObserver: null,
|
|
874
|
+
mutationObserver: null,
|
|
875
|
+
resizeHandler: null,
|
|
876
|
+
hostMessageHandler: null,
|
|
840
877
|
};
|
|
841
878
|
|
|
842
879
|
function isRecord(value) {
|
|
@@ -870,6 +907,29 @@
|
|
|
870
907
|
return null;
|
|
871
908
|
}
|
|
872
909
|
|
|
910
|
+
function defaultUploadTitle() {
|
|
911
|
+
return state.session?.mediaKind === "video"
|
|
912
|
+
? "Upload video"
|
|
913
|
+
: "Upload image";
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
function updateUploadTitle() {
|
|
917
|
+
const namedItems = state.items.filter(
|
|
918
|
+
(item) =>
|
|
919
|
+
(item.file && typeof item.file.name === "string") ||
|
|
920
|
+
(typeof item.displayName === "string" &&
|
|
921
|
+
item.displayName.length > 0),
|
|
922
|
+
);
|
|
923
|
+
const title =
|
|
924
|
+
namedItems.length === 1
|
|
925
|
+
? namedItems[0].file?.name || namedItems[0].displayName
|
|
926
|
+
: namedItems.length > 1
|
|
927
|
+
? `${namedItems.length} files`
|
|
928
|
+
: defaultUploadTitle();
|
|
929
|
+
elements.title.textContent = title;
|
|
930
|
+
elements.dropTitle.textContent = defaultUploadTitle();
|
|
931
|
+
}
|
|
932
|
+
|
|
873
933
|
function safeSessionLimit() {
|
|
874
934
|
const proposed =
|
|
875
935
|
state.session && Number.isInteger(state.session.maxFiles)
|
|
@@ -887,16 +947,232 @@
|
|
|
887
947
|
function setSessionStatus(message, tone = "neutral") {
|
|
888
948
|
elements.sessionStatus.textContent = message;
|
|
889
949
|
elements.sessionStatus.dataset.tone = tone;
|
|
950
|
+
scheduleIntrinsicSizeReport(true);
|
|
890
951
|
}
|
|
891
952
|
|
|
892
953
|
function setBatchError(message) {
|
|
893
954
|
elements.batchError.textContent = message;
|
|
955
|
+
scheduleIntrinsicSizeReport(true);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
function parseFinitePositiveNumber(value) {
|
|
959
|
+
return typeof value === "number" &&
|
|
960
|
+
Number.isFinite(value) &&
|
|
961
|
+
value > 0
|
|
962
|
+
? value
|
|
963
|
+
: null;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
function parseHostContainerDimensions(value) {
|
|
967
|
+
if (!isRecord(value)) return null;
|
|
968
|
+
const height = parseFinitePositiveNumber(value.height);
|
|
969
|
+
const maxHeight = parseFinitePositiveNumber(value.maxHeight);
|
|
970
|
+
if (!height && !maxHeight) return null;
|
|
971
|
+
return { height, maxHeight };
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
function boundedPositiveHeight(value) {
|
|
975
|
+
const parsed = parseFinitePositiveNumber(value);
|
|
976
|
+
return parsed ? Math.max(1, Math.round(parsed)) : null;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
function intrinsicHeightLimit() {
|
|
980
|
+
const dimensions = state.hostContainerDimensions;
|
|
981
|
+
if (!dimensions) return null;
|
|
982
|
+
return boundedPositiveHeight(
|
|
983
|
+
dimensions.height || dimensions.maxHeight,
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
function measureIntrinsicWidth() {
|
|
988
|
+
const candidates = [
|
|
989
|
+
elements.root && elements.root.scrollWidth,
|
|
990
|
+
document.body && document.body.scrollWidth,
|
|
991
|
+
document.documentElement && document.documentElement.scrollWidth,
|
|
992
|
+
];
|
|
993
|
+
for (const candidate of candidates) {
|
|
994
|
+
const parsed = boundedPositiveHeight(candidate);
|
|
995
|
+
if (parsed) return parsed;
|
|
996
|
+
}
|
|
997
|
+
return 1;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
function applyHostContainerDimensions() {
|
|
1001
|
+
const dimensions = state.hostContainerDimensions;
|
|
1002
|
+
if (!dimensions) return;
|
|
1003
|
+
if (dimensions.height) {
|
|
1004
|
+
document.documentElement.style.height = "100vh";
|
|
1005
|
+
} else {
|
|
1006
|
+
document.documentElement.style.removeProperty("height");
|
|
1007
|
+
}
|
|
1008
|
+
if (dimensions.maxHeight) {
|
|
1009
|
+
document.documentElement.style.maxHeight = `${boundedPositiveHeight(dimensions.maxHeight)}px`;
|
|
1010
|
+
} else {
|
|
1011
|
+
document.documentElement.style.removeProperty("max-height");
|
|
1012
|
+
}
|
|
1013
|
+
document.documentElement.style.overflowY = "auto";
|
|
1014
|
+
scheduleIntrinsicSizeReport(true);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
function measureIntrinsicHeight() {
|
|
1018
|
+
const candidates = [
|
|
1019
|
+
elements.root && elements.root.scrollHeight,
|
|
1020
|
+
document.body && document.body.scrollHeight,
|
|
1021
|
+
document.documentElement && document.documentElement.scrollHeight,
|
|
1022
|
+
];
|
|
1023
|
+
let measured = 1;
|
|
1024
|
+
for (const candidate of candidates) {
|
|
1025
|
+
const parsed = boundedPositiveHeight(candidate);
|
|
1026
|
+
if (parsed) {
|
|
1027
|
+
measured = parsed;
|
|
1028
|
+
break;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
const limit = intrinsicHeightLimit();
|
|
1032
|
+
return limit ? Math.min(measured, limit) : measured;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
function reportIntrinsicHeight(force = false) {
|
|
1036
|
+
state.pendingSizeReport = false;
|
|
1037
|
+
state.forceSizeReport = false;
|
|
1038
|
+
const height = measureIntrinsicHeight();
|
|
1039
|
+
if (!force && height === state.lastReportedHeight) return;
|
|
1040
|
+
state.lastReportedHeight = height;
|
|
1041
|
+
if (state.standardHostReady) {
|
|
1042
|
+
postNotification("ui/notifications/size-changed", {
|
|
1043
|
+
width: measureIntrinsicWidth(),
|
|
1044
|
+
height,
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
const bridge = openAiBridge();
|
|
1048
|
+
if (bridge && typeof bridge.notifyIntrinsicHeight === "function") {
|
|
1049
|
+
void Promise.resolve(bridge.notifyIntrinsicHeight(height)).catch(
|
|
1050
|
+
() => {},
|
|
1051
|
+
);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
function scheduleIntrinsicSizeReport(force = false) {
|
|
1056
|
+
state.forceSizeReport = state.forceSizeReport || force;
|
|
1057
|
+
if (state.pendingSizeReport) return;
|
|
1058
|
+
state.pendingSizeReport = true;
|
|
1059
|
+
const flush = () => reportIntrinsicHeight(state.forceSizeReport);
|
|
1060
|
+
if (typeof window.requestAnimationFrame === "function") {
|
|
1061
|
+
window.requestAnimationFrame(() => flush());
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
window.setTimeout(flush, 0);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
function installIntrinsicSizeObserver() {
|
|
1068
|
+
if (
|
|
1069
|
+
state.resizeObserver ||
|
|
1070
|
+
state.mutationObserver ||
|
|
1071
|
+
state.resizeHandler
|
|
1072
|
+
)
|
|
1073
|
+
return;
|
|
1074
|
+
if (typeof ResizeObserver === "function" && elements.root) {
|
|
1075
|
+
state.resizeObserver = new ResizeObserver(() =>
|
|
1076
|
+
scheduleIntrinsicSizeReport(),
|
|
1077
|
+
);
|
|
1078
|
+
state.resizeObserver.observe(elements.root);
|
|
1079
|
+
} else if (typeof MutationObserver === "function" && elements.root) {
|
|
1080
|
+
state.mutationObserver = new MutationObserver(() =>
|
|
1081
|
+
scheduleIntrinsicSizeReport(),
|
|
1082
|
+
);
|
|
1083
|
+
state.mutationObserver.observe(elements.root, {
|
|
1084
|
+
attributes: true,
|
|
1085
|
+
childList: true,
|
|
1086
|
+
subtree: true,
|
|
1087
|
+
characterData: true,
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
state.resizeHandler = () => scheduleIntrinsicSizeReport();
|
|
1091
|
+
window.addEventListener("resize", state.resizeHandler);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
function setFallbackVisibility(visible) {
|
|
1095
|
+
elements.fallbackLink.hidden = !visible;
|
|
1096
|
+
scheduleIntrinsicSizeReport(true);
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
function requireFallback(
|
|
1100
|
+
message,
|
|
1101
|
+
tone = "warning",
|
|
1102
|
+
blockPicker = false,
|
|
1103
|
+
) {
|
|
1104
|
+
state.fallbackRequired = true;
|
|
1105
|
+
state.uploadBlocked = state.uploadBlocked || blockPicker;
|
|
1106
|
+
setFallbackVisibility(true);
|
|
1107
|
+
if (message) setSessionStatus(message, tone);
|
|
1108
|
+
setPickerAvailability();
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
function enqueueIngestion(task) {
|
|
1112
|
+
const next = state.ingestionQueue.then(task, task);
|
|
1113
|
+
state.ingestionQueue = next.catch(() => {});
|
|
1114
|
+
return next;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
function activeAbortSignal() {
|
|
1118
|
+
if (!state.abortController || state.abortController.signal.aborted) {
|
|
1119
|
+
state.abortController =
|
|
1120
|
+
typeof AbortController === "function"
|
|
1121
|
+
? new AbortController()
|
|
1122
|
+
: null;
|
|
1123
|
+
}
|
|
1124
|
+
return state.abortController?.signal;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
function abortActiveWork() {
|
|
1128
|
+
state.abortController?.abort();
|
|
1129
|
+
for (const request of state.activeXhrs) request.abort();
|
|
1130
|
+
state.activeXhrs.clear();
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
function cleanup() {
|
|
1134
|
+
if (state.disposed) return;
|
|
1135
|
+
state.disposed = true;
|
|
1136
|
+
abortActiveWork();
|
|
1137
|
+
state.resizeObserver?.disconnect();
|
|
1138
|
+
state.mutationObserver?.disconnect();
|
|
1139
|
+
if (state.resizeHandler)
|
|
1140
|
+
window.removeEventListener("resize", state.resizeHandler);
|
|
1141
|
+
if (state.hostMessageHandler)
|
|
1142
|
+
window.removeEventListener("message", state.hostMessageHandler);
|
|
1143
|
+
for (const item of state.items) revokePreviewUrl(item);
|
|
1144
|
+
for (const [id, pending] of state.pendingRpc) {
|
|
1145
|
+
window.clearTimeout(pending.timeout);
|
|
1146
|
+
pending.reject(new Error("resource_teardown"));
|
|
1147
|
+
state.pendingRpc.delete(id);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
function normalizedBridgeReferenceId(reference) {
|
|
1152
|
+
return isRecord(reference)
|
|
1153
|
+
? boundedString(
|
|
1154
|
+
reference.file_id || reference.fileId || reference.id,
|
|
1155
|
+
300,
|
|
1156
|
+
)
|
|
1157
|
+
: "";
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
function normalizedBridgeDownloadUrl(result) {
|
|
1161
|
+
return typeof result === "string"
|
|
1162
|
+
? result
|
|
1163
|
+
: isRecord(result)
|
|
1164
|
+
? boundedString(
|
|
1165
|
+
result.download_url || result.downloadUrl || result.url,
|
|
1166
|
+
3000,
|
|
1167
|
+
)
|
|
1168
|
+
: "";
|
|
894
1169
|
}
|
|
895
1170
|
|
|
896
1171
|
function setPickerAvailability() {
|
|
897
1172
|
const disabled =
|
|
898
1173
|
!state.session ||
|
|
899
1174
|
sessionExpired() ||
|
|
1175
|
+
state.uploadBlocked ||
|
|
900
1176
|
state.busyBatch ||
|
|
901
1177
|
state.items.length >= safeSessionLimit();
|
|
902
1178
|
elements.input.disabled = disabled;
|
|
@@ -925,10 +1201,12 @@
|
|
|
925
1201
|
}
|
|
926
1202
|
|
|
927
1203
|
function initialize() {
|
|
928
|
-
if (state.initialized) return;
|
|
1204
|
+
if (state.initialized || state.disposed) return;
|
|
929
1205
|
state.initialized = true;
|
|
930
1206
|
bindUi();
|
|
1207
|
+
installIntrinsicSizeObserver();
|
|
931
1208
|
setPickerAvailability();
|
|
1209
|
+
scheduleIntrinsicSizeReport(true);
|
|
932
1210
|
void initializeStandardHost();
|
|
933
1211
|
const bridge = openAiBridge();
|
|
934
1212
|
elements.libraryPickerButton.hidden = !(
|
|
@@ -997,27 +1275,22 @@
|
|
|
997
1275
|
"click",
|
|
998
1276
|
() => void submitDestination(),
|
|
999
1277
|
);
|
|
1000
|
-
elements.checkStatus.addEventListener(
|
|
1001
|
-
"click",
|
|
1002
|
-
() => void refreshSessionStatus(),
|
|
1003
|
-
);
|
|
1004
|
-
elements.pendingRefresh.addEventListener(
|
|
1005
|
-
"click",
|
|
1006
|
-
() => void refreshSessionStatus(),
|
|
1007
|
-
);
|
|
1008
1278
|
elements.copyButton.addEventListener(
|
|
1009
1279
|
"click",
|
|
1010
1280
|
() => void copyHandoff(),
|
|
1011
1281
|
);
|
|
1012
1282
|
|
|
1013
|
-
|
|
1283
|
+
state.hostMessageHandler = handleHostMessage;
|
|
1284
|
+
window.addEventListener("message", state.hostMessageHandler);
|
|
1014
1285
|
}
|
|
1015
1286
|
|
|
1016
1287
|
async function initializeStandardHost() {
|
|
1017
1288
|
try {
|
|
1018
1289
|
const result = await rpcRequest("ui/initialize", {
|
|
1019
1290
|
appInfo: { name: "ProductMaker Meta upload", version: "1.0.0" },
|
|
1020
|
-
appCapabilities: {
|
|
1291
|
+
appCapabilities: {
|
|
1292
|
+
availableDisplayModes: ["inline", "fullscreen"],
|
|
1293
|
+
},
|
|
1021
1294
|
protocolVersion: APP_PROTOCOL_VERSION,
|
|
1022
1295
|
});
|
|
1023
1296
|
state.standardHostReady = true;
|
|
@@ -1025,11 +1298,20 @@
|
|
|
1025
1298
|
isRecord(result) && isRecord(result.hostCapabilities)
|
|
1026
1299
|
? result.hostCapabilities
|
|
1027
1300
|
: {};
|
|
1301
|
+
const hostContext =
|
|
1302
|
+
isRecord(result) && isRecord(result.hostContext)
|
|
1303
|
+
? result.hostContext
|
|
1304
|
+
: {};
|
|
1305
|
+
state.hostContainerDimensions = parseHostContainerDimensions(
|
|
1306
|
+
hostContext.containerDimensions,
|
|
1307
|
+
);
|
|
1308
|
+
applyHostContainerDimensions();
|
|
1028
1309
|
state.hostCanMessage = capabilityEnabled(capabilities.message);
|
|
1029
1310
|
state.hostCanServerTools = capabilityEnabled(
|
|
1030
1311
|
capabilities.serverTools,
|
|
1031
1312
|
);
|
|
1032
1313
|
postNotification("ui/notifications/initialized", {});
|
|
1314
|
+
scheduleIntrinsicSizeReport(true);
|
|
1033
1315
|
} catch {
|
|
1034
1316
|
state.standardHostReady = false;
|
|
1035
1317
|
state.hostCanServerTools = false;
|
|
@@ -1038,6 +1320,10 @@
|
|
|
1038
1320
|
|
|
1039
1321
|
function rpcRequest(method, params) {
|
|
1040
1322
|
return new Promise((resolve, reject) => {
|
|
1323
|
+
if (state.disposed) {
|
|
1324
|
+
reject(new Error("resource_teardown"));
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1041
1327
|
state.rpcId += 1;
|
|
1042
1328
|
const id = state.rpcId;
|
|
1043
1329
|
const timeout = window.setTimeout(() => {
|
|
@@ -1053,12 +1339,44 @@
|
|
|
1053
1339
|
}
|
|
1054
1340
|
|
|
1055
1341
|
function postNotification(method, params) {
|
|
1342
|
+
if (state.disposed) return;
|
|
1056
1343
|
window.parent.postMessage({ jsonrpc: "2.0", method, params }, "*");
|
|
1057
1344
|
}
|
|
1058
1345
|
|
|
1059
1346
|
function handleHostMessage(event) {
|
|
1060
1347
|
if (event.source !== window.parent || !isRecord(event.data)) return;
|
|
1061
1348
|
const message = event.data;
|
|
1349
|
+
if (message.method === "ui/resource-teardown") {
|
|
1350
|
+
cleanup();
|
|
1351
|
+
if (Object.prototype.hasOwnProperty.call(message, "id")) {
|
|
1352
|
+
window.parent.postMessage(
|
|
1353
|
+
{ jsonrpc: "2.0", id: message.id, result: {} },
|
|
1354
|
+
"*",
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
if (message.method === "ui/notifications/tool-cancelled") {
|
|
1360
|
+
state.workCancelled = true;
|
|
1361
|
+
abortActiveWork();
|
|
1362
|
+
requireFallback(
|
|
1363
|
+
"La carga fue cancelada por el host. Puedes continuar desde el enlace seguro.",
|
|
1364
|
+
"warning",
|
|
1365
|
+
true,
|
|
1366
|
+
);
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
if (message.method === "ui/notifications/host-context-changed") {
|
|
1370
|
+
const params = isRecord(message.params) ? message.params : {};
|
|
1371
|
+
const hostContext = isRecord(params.hostContext)
|
|
1372
|
+
? params.hostContext
|
|
1373
|
+
: params;
|
|
1374
|
+
state.hostContainerDimensions = parseHostContainerDimensions(
|
|
1375
|
+
hostContext.containerDimensions,
|
|
1376
|
+
);
|
|
1377
|
+
applyHostContainerDimensions();
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1062
1380
|
if (Object.prototype.hasOwnProperty.call(message, "id")) {
|
|
1063
1381
|
const pending = state.pendingRpc.get(message.id);
|
|
1064
1382
|
if (!pending) return;
|
|
@@ -1090,22 +1408,33 @@
|
|
|
1090
1408
|
if (!candidate) return;
|
|
1091
1409
|
const session = parseSession(candidate);
|
|
1092
1410
|
if (!session) return;
|
|
1411
|
+
const sessionChanged =
|
|
1412
|
+
!state.session || state.session.sessionId !== session.sessionId;
|
|
1413
|
+
if (sessionChanged) {
|
|
1414
|
+
state.uploadBlocked = false;
|
|
1415
|
+
state.fallbackRequired = false;
|
|
1416
|
+
state.workCancelled = false;
|
|
1417
|
+
if (state.abortController?.signal.aborted) {
|
|
1418
|
+
state.abortController = null;
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1093
1421
|
state.session = session;
|
|
1094
1422
|
elements.sessionId.textContent = session.sessionId;
|
|
1095
1423
|
elements.fallbackLink.href = session.uploadPageUrl;
|
|
1096
|
-
|
|
1424
|
+
updateUploadTitle();
|
|
1097
1425
|
if (sessionExpired()) {
|
|
1098
|
-
|
|
1426
|
+
requireFallback(
|
|
1099
1427
|
"La sesión está expirada. Abre el enlace seguro para solicitar una nueva.",
|
|
1100
1428
|
"danger",
|
|
1429
|
+
true,
|
|
1101
1430
|
);
|
|
1102
|
-
elements.pendingRefresh.hidden = true;
|
|
1103
1431
|
} else {
|
|
1432
|
+
setFallbackVisibility(state.fallbackRequired);
|
|
1104
1433
|
setSessionStatus("Sesión lista para recibir archivos.", "neutral");
|
|
1105
|
-
elements.pendingRefresh.hidden = false;
|
|
1106
1434
|
}
|
|
1107
1435
|
setPickerAvailability();
|
|
1108
1436
|
void consumePendingBridgeFiles();
|
|
1437
|
+
scheduleIntrinsicSizeReport(true);
|
|
1109
1438
|
}
|
|
1110
1439
|
|
|
1111
1440
|
function parseSession(candidate) {
|
|
@@ -1139,8 +1468,26 @@
|
|
|
1139
1468
|
void consumePendingBridgeFiles();
|
|
1140
1469
|
}
|
|
1141
1470
|
|
|
1142
|
-
|
|
1471
|
+
function consumePendingBridgeFiles() {
|
|
1472
|
+
if (
|
|
1473
|
+
state.disposed ||
|
|
1474
|
+
state.bridgeConsumptionQueued ||
|
|
1475
|
+
!state.session ||
|
|
1476
|
+
!Array.isArray(state.toolInput.files)
|
|
1477
|
+
)
|
|
1478
|
+
return;
|
|
1479
|
+
state.bridgeConsumptionQueued = true;
|
|
1480
|
+
void enqueueIngestion(async () => {
|
|
1481
|
+
state.bridgeConsumptionQueued = false;
|
|
1482
|
+
await consumePendingBridgeFilesNow();
|
|
1483
|
+
}).catch(() => {
|
|
1484
|
+
state.bridgeConsumptionQueued = false;
|
|
1485
|
+
});
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
async function consumePendingBridgeFilesNow() {
|
|
1143
1489
|
if (
|
|
1490
|
+
state.disposed ||
|
|
1144
1491
|
!state.session ||
|
|
1145
1492
|
!Array.isArray(state.toolInput.files) ||
|
|
1146
1493
|
state.consumingBridgeFiles
|
|
@@ -1149,14 +1496,12 @@
|
|
|
1149
1496
|
state.consumingBridgeFiles = true;
|
|
1150
1497
|
try {
|
|
1151
1498
|
const pending = state.toolInput.files.filter((reference) => {
|
|
1152
|
-
const id =
|
|
1153
|
-
? boundedString(reference.file_id, 300)
|
|
1154
|
-
: "";
|
|
1499
|
+
const id = normalizedBridgeReferenceId(reference);
|
|
1155
1500
|
return id && !state.consumedBridgeIds.has(id);
|
|
1156
1501
|
});
|
|
1157
1502
|
if (!pending.length) return;
|
|
1158
1503
|
if (!hasOpenAiFunction("getFileDownloadUrl")) {
|
|
1159
|
-
|
|
1504
|
+
requireFallback(
|
|
1160
1505
|
"Este host no expone el puente de archivos. Usa el selector o el enlace seguro.",
|
|
1161
1506
|
"warning",
|
|
1162
1507
|
);
|
|
@@ -1168,11 +1513,13 @@
|
|
|
1168
1513
|
);
|
|
1169
1514
|
const files = [];
|
|
1170
1515
|
for (const reference of pending.slice(0, availableSlots)) {
|
|
1171
|
-
const id =
|
|
1172
|
-
state.consumedBridgeIds.add(id);
|
|
1516
|
+
const id = normalizedBridgeReferenceId(reference);
|
|
1173
1517
|
try {
|
|
1174
|
-
|
|
1518
|
+
const file = await downloadBridgeReference(reference);
|
|
1519
|
+
state.consumedBridgeIds.add(id);
|
|
1520
|
+
files.push(file);
|
|
1175
1521
|
} catch {
|
|
1522
|
+
state.consumedBridgeIds.add(id);
|
|
1176
1523
|
addBridgeFailure(reference);
|
|
1177
1524
|
}
|
|
1178
1525
|
}
|
|
@@ -1182,9 +1529,7 @@
|
|
|
1182
1529
|
const hasUnconsumed =
|
|
1183
1530
|
Array.isArray(state.toolInput.files) &&
|
|
1184
1531
|
state.toolInput.files.some((reference) => {
|
|
1185
|
-
const id =
|
|
1186
|
-
? boundedString(reference.file_id, 300)
|
|
1187
|
-
: "";
|
|
1532
|
+
const id = normalizedBridgeReferenceId(reference);
|
|
1188
1533
|
return id && !state.consumedBridgeIds.has(id);
|
|
1189
1534
|
});
|
|
1190
1535
|
if (
|
|
@@ -1197,82 +1542,75 @@
|
|
|
1197
1542
|
}
|
|
1198
1543
|
}
|
|
1199
1544
|
|
|
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
|
-
}),
|
|
1545
|
+
function processLocalFiles(files) {
|
|
1546
|
+
return enqueueIngestion(async () => {
|
|
1547
|
+
if (
|
|
1548
|
+
state.disposed ||
|
|
1549
|
+
!state.session ||
|
|
1550
|
+
sessionExpired() ||
|
|
1551
|
+
state.uploadBlocked
|
|
1552
|
+
)
|
|
1553
|
+
return;
|
|
1554
|
+
state.workCancelled = false;
|
|
1555
|
+
if (files.length > safeSessionLimit() - state.items.length) {
|
|
1556
|
+
setBatchError(
|
|
1557
|
+
`Puedes subir hasta ${safeSessionLimit()} archivos por sesión.`,
|
|
1229
1558
|
);
|
|
1230
|
-
} catch {
|
|
1231
|
-
addBridgeFailure({ fileName: file.name, mimeType: file.type });
|
|
1232
1559
|
}
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
setBatchError(
|
|
1236
|
-
`Puedes subir hasta ${safeSessionLimit()} archivos por sesión.`,
|
|
1237
|
-
);
|
|
1238
|
-
}
|
|
1239
|
-
await processFiles(normalized);
|
|
1560
|
+
await processFiles(files);
|
|
1561
|
+
});
|
|
1240
1562
|
}
|
|
1241
1563
|
|
|
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
|
-
|
|
1564
|
+
function chooseLibraryFiles() {
|
|
1565
|
+
return enqueueIngestion(async () => {
|
|
1566
|
+
if (
|
|
1567
|
+
state.disposed ||
|
|
1568
|
+
!state.session ||
|
|
1569
|
+
sessionExpired() ||
|
|
1570
|
+
state.uploadBlocked
|
|
1571
|
+
)
|
|
1572
|
+
return;
|
|
1573
|
+
const bridge = openAiBridge();
|
|
1574
|
+
if (
|
|
1575
|
+
!bridge ||
|
|
1576
|
+
typeof bridge.selectFiles !== "function" ||
|
|
1577
|
+
typeof bridge.getFileDownloadUrl !== "function"
|
|
1578
|
+
)
|
|
1579
|
+
return;
|
|
1580
|
+
try {
|
|
1581
|
+
const result = await bridge.selectFiles();
|
|
1582
|
+
const selected = Array.isArray(result) ? result : [];
|
|
1583
|
+
const availableSlots = Math.max(
|
|
1584
|
+
0,
|
|
1585
|
+
safeSessionLimit() - state.items.length,
|
|
1586
|
+
);
|
|
1587
|
+
const files = [];
|
|
1588
|
+
for (const candidate of selected.slice(0, availableSlots)) {
|
|
1589
|
+
if (!isRecord(candidate)) continue;
|
|
1590
|
+
const fileId = boundedString(candidate.fileId, 300);
|
|
1591
|
+
const fileName = boundedString(candidate.fileName, 240);
|
|
1592
|
+
const mimeType = boundedString(candidate.mimeType, 100);
|
|
1593
|
+
if (!fileId || !fileName || !mimeType) continue;
|
|
1594
|
+
try {
|
|
1595
|
+
files.push(
|
|
1596
|
+
await downloadBridgeReference({
|
|
1597
|
+
fileId,
|
|
1598
|
+
fileName,
|
|
1599
|
+
mimeType,
|
|
1600
|
+
}),
|
|
1601
|
+
);
|
|
1602
|
+
} catch {
|
|
1603
|
+
addBridgeFailure({ fileId, fileName, mimeType });
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
await processFiles(files);
|
|
1607
|
+
} catch {
|
|
1608
|
+
setSessionStatus(
|
|
1609
|
+
"No se pudo abrir el selector del host. Usa el selector del navegador.",
|
|
1610
|
+
"warning",
|
|
1267
1611
|
);
|
|
1268
1612
|
}
|
|
1269
|
-
|
|
1270
|
-
} catch {
|
|
1271
|
-
setSessionStatus(
|
|
1272
|
-
"No se pudo abrir el selector del host. Usa el selector del navegador.",
|
|
1273
|
-
"warning",
|
|
1274
|
-
);
|
|
1275
|
-
}
|
|
1613
|
+
});
|
|
1276
1614
|
}
|
|
1277
1615
|
|
|
1278
1616
|
function isBrowserFile(value) {
|
|
@@ -1290,31 +1628,21 @@
|
|
|
1290
1628
|
if (!bridge || typeof bridge.getFileDownloadUrl !== "function") {
|
|
1291
1629
|
throw new Error("bridge_download_unavailable");
|
|
1292
1630
|
}
|
|
1293
|
-
const fileId =
|
|
1294
|
-
reference.file_id || reference.fileId || reference.id,
|
|
1295
|
-
300,
|
|
1296
|
-
);
|
|
1631
|
+
const fileId = normalizedBridgeReferenceId(reference);
|
|
1297
1632
|
if (!fileId) throw new Error("invalid_bridge_file");
|
|
1298
1633
|
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
|
-
: "";
|
|
1634
|
+
const temporaryUrl = normalizedBridgeDownloadUrl(resolved);
|
|
1310
1635
|
const parsedUrl = new URL(temporaryUrl);
|
|
1311
|
-
if (!
|
|
1636
|
+
if (!safeBridgeDownloadUrl(parsedUrl))
|
|
1637
|
+
throw new Error("unsafe_bridge_url");
|
|
1312
1638
|
const response = await fetch(parsedUrl.toString(), {
|
|
1313
1639
|
method: "GET",
|
|
1314
1640
|
credentials: "omit",
|
|
1641
|
+
redirect: "error",
|
|
1642
|
+
signal: activeAbortSignal(),
|
|
1315
1643
|
});
|
|
1316
1644
|
if (!response.ok) throw new Error("bridge_download_failed");
|
|
1317
|
-
const blob = await response
|
|
1645
|
+
const blob = await readBoundedBridgeBlob(response);
|
|
1318
1646
|
const preferredType = boundedString(
|
|
1319
1647
|
reference.mime_type || reference.mimeType || reference.type,
|
|
1320
1648
|
100,
|
|
@@ -1338,6 +1666,48 @@
|
|
|
1338
1666
|
});
|
|
1339
1667
|
}
|
|
1340
1668
|
|
|
1669
|
+
async function readBoundedBridgeBlob(response) {
|
|
1670
|
+
const declaredLength = Number(
|
|
1671
|
+
response.headers.get("content-length") || "",
|
|
1672
|
+
);
|
|
1673
|
+
if (
|
|
1674
|
+
Number.isFinite(declaredLength) &&
|
|
1675
|
+
declaredLength > MAX_FILE_SIZE_BYTES
|
|
1676
|
+
) {
|
|
1677
|
+
throw new Error("bridge_file_too_large");
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
if (!response.body || typeof response.body.getReader !== "function") {
|
|
1681
|
+
const blob = await response.blob();
|
|
1682
|
+
if (blob.size > MAX_FILE_SIZE_BYTES) {
|
|
1683
|
+
throw new Error("bridge_file_too_large");
|
|
1684
|
+
}
|
|
1685
|
+
return blob;
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
const reader = response.body.getReader();
|
|
1689
|
+
const chunks = [];
|
|
1690
|
+
let total = 0;
|
|
1691
|
+
try {
|
|
1692
|
+
while (true) {
|
|
1693
|
+
const next = await reader.read();
|
|
1694
|
+
if (next.done) break;
|
|
1695
|
+
const chunk = next.value;
|
|
1696
|
+
total += chunk.byteLength;
|
|
1697
|
+
if (total > MAX_FILE_SIZE_BYTES) {
|
|
1698
|
+
await reader.cancel();
|
|
1699
|
+
throw new Error("bridge_file_too_large");
|
|
1700
|
+
}
|
|
1701
|
+
chunks.push(chunk);
|
|
1702
|
+
}
|
|
1703
|
+
} finally {
|
|
1704
|
+
reader.releaseLock?.();
|
|
1705
|
+
}
|
|
1706
|
+
return new Blob(chunks, {
|
|
1707
|
+
type: response.headers.get("content-type") || "",
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1341
1711
|
function addBridgeFailure(reference) {
|
|
1342
1712
|
const type = boundedString(
|
|
1343
1713
|
reference.mime_type || reference.mimeType || reference.type,
|
|
@@ -1345,11 +1715,19 @@
|
|
|
1345
1715
|
);
|
|
1346
1716
|
const mediaKind = mediaKindForType(type) || "image";
|
|
1347
1717
|
const item = createItem(null, mediaKind);
|
|
1718
|
+
item.displayName = boundedString(
|
|
1719
|
+
reference.file_name || reference.fileName || reference.name,
|
|
1720
|
+
300,
|
|
1721
|
+
);
|
|
1348
1722
|
item.status = "failed";
|
|
1349
1723
|
item.error =
|
|
1350
1724
|
"No se pudo leer el archivo temporal del host. Usa el selector o el enlace seguro.";
|
|
1351
1725
|
item.failureStage = "bridge";
|
|
1352
1726
|
state.items.push(item);
|
|
1727
|
+
requireFallback(
|
|
1728
|
+
"No se pudo leer el archivo temporal del host. Usa el selector o el enlace seguro.",
|
|
1729
|
+
"warning",
|
|
1730
|
+
);
|
|
1353
1731
|
renderItems();
|
|
1354
1732
|
emitAnalytics("ads_meta_remote_upload_error", mediaKind, {
|
|
1355
1733
|
failureStage: "bridge",
|
|
@@ -1358,7 +1736,14 @@
|
|
|
1358
1736
|
}
|
|
1359
1737
|
|
|
1360
1738
|
async function processFiles(fileCandidates) {
|
|
1361
|
-
if (
|
|
1739
|
+
if (
|
|
1740
|
+
state.disposed ||
|
|
1741
|
+
!state.session ||
|
|
1742
|
+
sessionExpired() ||
|
|
1743
|
+
state.uploadBlocked ||
|
|
1744
|
+
state.busyBatch
|
|
1745
|
+
)
|
|
1746
|
+
return;
|
|
1362
1747
|
const files = fileCandidates.filter(isBrowserFile);
|
|
1363
1748
|
if (!files.length) return;
|
|
1364
1749
|
setBatchError("");
|
|
@@ -1395,6 +1780,7 @@
|
|
|
1395
1780
|
id: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`,
|
|
1396
1781
|
uploadId: createClientUuid(),
|
|
1397
1782
|
file,
|
|
1783
|
+
displayName: file?.name || "",
|
|
1398
1784
|
uploadFile: file,
|
|
1399
1785
|
mediaKind,
|
|
1400
1786
|
status: "pending",
|
|
@@ -1403,6 +1789,7 @@
|
|
|
1403
1789
|
error: "",
|
|
1404
1790
|
failureStage: "",
|
|
1405
1791
|
stagingAssetId: "",
|
|
1792
|
+
previewUrl: "",
|
|
1406
1793
|
};
|
|
1407
1794
|
}
|
|
1408
1795
|
|
|
@@ -1435,6 +1822,18 @@
|
|
|
1435
1822
|
failureStage: failure.stage,
|
|
1436
1823
|
errorType: failure.code,
|
|
1437
1824
|
});
|
|
1825
|
+
if (failure.code === "session_expired") {
|
|
1826
|
+
requireFallback(
|
|
1827
|
+
"La sesión expiró. Abre el enlace seguro para crear otra.",
|
|
1828
|
+
"danger",
|
|
1829
|
+
true,
|
|
1830
|
+
);
|
|
1831
|
+
} else if (failure.stage !== "validation") {
|
|
1832
|
+
requireFallback(
|
|
1833
|
+
"La carga no terminó. Puedes reintentarlo o continuar desde el enlace seguro.",
|
|
1834
|
+
"warning",
|
|
1835
|
+
);
|
|
1836
|
+
}
|
|
1438
1837
|
}
|
|
1439
1838
|
renderItems();
|
|
1440
1839
|
}
|
|
@@ -1606,6 +2005,12 @@
|
|
|
1606
2005
|
alpha: file.type !== "image/jpeg",
|
|
1607
2006
|
});
|
|
1608
2007
|
if (!context) throw new Error("canvas_unavailable");
|
|
2008
|
+
if (file.type === "image/png") {
|
|
2009
|
+
context.save();
|
|
2010
|
+
context.fillStyle = "#ffffff";
|
|
2011
|
+
context.fillRect(0, 0, canvas.width, canvas.height);
|
|
2012
|
+
context.restore();
|
|
2013
|
+
}
|
|
1609
2014
|
context.drawImage(
|
|
1610
2015
|
inspection.image,
|
|
1611
2016
|
0,
|
|
@@ -1613,16 +2018,13 @@
|
|
|
1613
2018
|
canvas.width,
|
|
1614
2019
|
canvas.height,
|
|
1615
2020
|
);
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
0.85,
|
|
1624
|
-
);
|
|
1625
|
-
});
|
|
2021
|
+
let blob = await canvasBlob(canvas, file.type);
|
|
2022
|
+
if (
|
|
2023
|
+
file.type === "image/png" &&
|
|
2024
|
+
blob.size > IMAGE_COMPRESSION_MAX_BYTES
|
|
2025
|
+
) {
|
|
2026
|
+
blob = await canvasBlob(canvas, "image/jpeg");
|
|
2027
|
+
}
|
|
1626
2028
|
const compressed = new File([blob], file.name, {
|
|
1627
2029
|
type: blob.type || file.type,
|
|
1628
2030
|
lastModified: file.lastModified,
|
|
@@ -1651,6 +2053,19 @@
|
|
|
1651
2053
|
}
|
|
1652
2054
|
}
|
|
1653
2055
|
|
|
2056
|
+
function canvasBlob(canvas, type) {
|
|
2057
|
+
return new Promise((resolve, reject) => {
|
|
2058
|
+
canvas.toBlob(
|
|
2059
|
+
(result) =>
|
|
2060
|
+
result
|
|
2061
|
+
? resolve(result)
|
|
2062
|
+
: reject(new Error("compression_failed")),
|
|
2063
|
+
type,
|
|
2064
|
+
0.85,
|
|
2065
|
+
);
|
|
2066
|
+
});
|
|
2067
|
+
}
|
|
2068
|
+
|
|
1654
2069
|
async function uploadWithRetries(item) {
|
|
1655
2070
|
let lastError = null;
|
|
1656
2071
|
for (let attempt = 1; attempt <= MAX_UPLOAD_ATTEMPTS; attempt += 1) {
|
|
@@ -1675,6 +2090,7 @@
|
|
|
1675
2090
|
return;
|
|
1676
2091
|
} catch (error) {
|
|
1677
2092
|
if (error && error.message === "session_expired") throw error;
|
|
2093
|
+
if (state.disposed || state.workCancelled) throw error;
|
|
1678
2094
|
lastError = error;
|
|
1679
2095
|
}
|
|
1680
2096
|
}
|
|
@@ -1712,6 +2128,7 @@
|
|
|
1712
2128
|
fileSize: file.size,
|
|
1713
2129
|
}),
|
|
1714
2130
|
credentials: "omit",
|
|
2131
|
+
signal: activeAbortSignal(),
|
|
1715
2132
|
});
|
|
1716
2133
|
if (!response.ok) {
|
|
1717
2134
|
const error = new Error(
|
|
@@ -1761,6 +2178,21 @@
|
|
|
1761
2178
|
);
|
|
1762
2179
|
}
|
|
1763
2180
|
|
|
2181
|
+
function safeBridgeDownloadUrl(url) {
|
|
2182
|
+
if (
|
|
2183
|
+
url.protocol !== "https:" ||
|
|
2184
|
+
url.username ||
|
|
2185
|
+
url.password ||
|
|
2186
|
+
url.port
|
|
2187
|
+
)
|
|
2188
|
+
return false;
|
|
2189
|
+
const hostname = url.hostname.toLowerCase();
|
|
2190
|
+
return (
|
|
2191
|
+
hostname === "files.oaiusercontent.com" ||
|
|
2192
|
+
hostname.endsWith(".oaiusercontent.com")
|
|
2193
|
+
);
|
|
2194
|
+
}
|
|
2195
|
+
|
|
1764
2196
|
function safeUploadPageUrl(url) {
|
|
1765
2197
|
if (url.username || url.password) return false;
|
|
1766
2198
|
if (
|
|
@@ -1779,23 +2211,32 @@
|
|
|
1779
2211
|
function putFileWithProgress(url, file, onProgress) {
|
|
1780
2212
|
return new Promise((resolve, reject) => {
|
|
1781
2213
|
const request = new XMLHttpRequest();
|
|
2214
|
+
let settled = false;
|
|
2215
|
+
const finish = (callback) => {
|
|
2216
|
+
if (settled) return;
|
|
2217
|
+
settled = true;
|
|
2218
|
+
state.activeXhrs.delete(request);
|
|
2219
|
+
callback();
|
|
2220
|
+
};
|
|
1782
2221
|
request.open("PUT", url);
|
|
1783
2222
|
request.timeout = 5 * 60 * 1000;
|
|
1784
2223
|
request.setRequestHeader("Content-Type", file.type);
|
|
2224
|
+
state.activeXhrs.add(request);
|
|
1785
2225
|
request.upload.addEventListener("progress", (event) => {
|
|
1786
2226
|
if (event.lengthComputable && event.total > 0) {
|
|
1787
2227
|
onProgress(Math.round((event.loaded / event.total) * 100));
|
|
1788
2228
|
}
|
|
1789
2229
|
});
|
|
1790
2230
|
request.addEventListener("load", () => {
|
|
1791
|
-
if (request.status >= 200 && request.status < 300)
|
|
1792
|
-
|
|
2231
|
+
if (request.status >= 200 && request.status < 300)
|
|
2232
|
+
finish(resolve);
|
|
2233
|
+
else finish(() => reject(uploadTransportError("put_failed")));
|
|
1793
2234
|
});
|
|
1794
2235
|
request.addEventListener("error", () =>
|
|
1795
|
-
reject(uploadTransportError("network_error")),
|
|
2236
|
+
finish(() => reject(uploadTransportError("network_error"))),
|
|
1796
2237
|
);
|
|
1797
2238
|
request.addEventListener("timeout", () =>
|
|
1798
|
-
reject(uploadTransportError("upload_timeout")),
|
|
2239
|
+
finish(() => reject(uploadTransportError("upload_timeout"))),
|
|
1799
2240
|
);
|
|
1800
2241
|
request.send(file);
|
|
1801
2242
|
});
|
|
@@ -1846,11 +2287,38 @@
|
|
|
1846
2287
|
|
|
1847
2288
|
function removeItem(index) {
|
|
1848
2289
|
if (state.busyBatch) return;
|
|
1849
|
-
state.items.splice(index, 1);
|
|
2290
|
+
const [item] = state.items.splice(index, 1);
|
|
2291
|
+
if (item) revokePreviewUrl(item);
|
|
1850
2292
|
renderItems();
|
|
1851
2293
|
setPickerAvailability();
|
|
1852
2294
|
}
|
|
1853
2295
|
|
|
2296
|
+
function revokePreviewUrl(item) {
|
|
2297
|
+
if (
|
|
2298
|
+
item.previewUrl &&
|
|
2299
|
+
typeof window.URL.revokeObjectURL === "function"
|
|
2300
|
+
) {
|
|
2301
|
+
window.URL.revokeObjectURL(item.previewUrl);
|
|
2302
|
+
item.previewUrl = "";
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
function previewUrlFor(item) {
|
|
2307
|
+
if (
|
|
2308
|
+
!item.file ||
|
|
2309
|
+
item.mediaKind !== "image" ||
|
|
2310
|
+
!IMAGE_TYPES.has(item.file.type)
|
|
2311
|
+
)
|
|
2312
|
+
return "";
|
|
2313
|
+
if (
|
|
2314
|
+
!item.previewUrl &&
|
|
2315
|
+
typeof window.URL.createObjectURL === "function"
|
|
2316
|
+
) {
|
|
2317
|
+
item.previewUrl = window.URL.createObjectURL(item.file);
|
|
2318
|
+
}
|
|
2319
|
+
return item.previewUrl;
|
|
2320
|
+
}
|
|
2321
|
+
|
|
1854
2322
|
function renderItems() {
|
|
1855
2323
|
const fragment = document.createDocumentFragment();
|
|
1856
2324
|
state.items.forEach((item, index) => {
|
|
@@ -1858,33 +2326,46 @@
|
|
|
1858
2326
|
card.className = "file-card";
|
|
1859
2327
|
card.dataset.testid = `meta-upload-file-${index}`;
|
|
1860
2328
|
|
|
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
|
-
|
|
2329
|
+
const preview = document.createElement("div");
|
|
2330
|
+
preview.className = "file-preview";
|
|
2331
|
+
preview.dataset.testid = `meta-upload-file-preview-${index}`;
|
|
2332
|
+
const previewUrl = previewUrlFor(item);
|
|
2333
|
+
if (previewUrl) {
|
|
2334
|
+
const image = document.createElement("img");
|
|
2335
|
+
image.src = previewUrl;
|
|
2336
|
+
image.alt =
|
|
2337
|
+
item.file?.name || item.displayName || "Uploaded image";
|
|
2338
|
+
image.dataset.testid = `meta-upload-preview-${index}`;
|
|
2339
|
+
preview.append(image);
|
|
2340
|
+
} else if (item.mediaKind === "video") {
|
|
2341
|
+
const video = document.createElement("span");
|
|
2342
|
+
video.className = "video-preview";
|
|
2343
|
+
video.setAttribute("aria-hidden", "true");
|
|
2344
|
+
video.textContent = "▶";
|
|
2345
|
+
preview.append(video);
|
|
2346
|
+
} else {
|
|
2347
|
+
const placeholder = document.createElement("span");
|
|
2348
|
+
placeholder.className = "file-kind";
|
|
2349
|
+
placeholder.setAttribute("aria-hidden", "true");
|
|
2350
|
+
placeholder.textContent = "▧";
|
|
2351
|
+
preview.append(placeholder);
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
if (item.status === "uploading" || item.status === "uploaded") {
|
|
2355
|
+
const progress = document.createElement("div");
|
|
2356
|
+
progress.className = "progress";
|
|
2357
|
+
progress.setAttribute("role", "progressbar");
|
|
2358
|
+
progress.setAttribute("aria-valuemin", "0");
|
|
2359
|
+
progress.setAttribute("aria-valuemax", "100");
|
|
2360
|
+
progress.setAttribute("aria-valuenow", String(item.progress));
|
|
2361
|
+
progress.dataset.testid = `meta-upload-progress-${index}`;
|
|
2362
|
+
const bar = document.createElement("div");
|
|
2363
|
+
bar.className = "progress-bar";
|
|
2364
|
+
bar.style.width = `${item.progress}%`;
|
|
2365
|
+
progress.append(bar);
|
|
2366
|
+
preview.append(progress);
|
|
2367
|
+
}
|
|
1885
2368
|
|
|
1886
|
-
const actions = document.createElement("div");
|
|
1887
|
-
actions.className = "file-actions";
|
|
1888
2369
|
if (
|
|
1889
2370
|
item.status === "failed" &&
|
|
1890
2371
|
item.failureStage !== "validation" &&
|
|
@@ -1892,42 +2373,50 @@
|
|
|
1892
2373
|
) {
|
|
1893
2374
|
const retry = document.createElement("button");
|
|
1894
2375
|
retry.type = "button";
|
|
1895
|
-
retry.className = "
|
|
2376
|
+
retry.className = "overlay-button retry-overlay";
|
|
1896
2377
|
retry.dataset.testid = `meta-upload-retry-${index}`;
|
|
1897
|
-
retry.
|
|
2378
|
+
retry.setAttribute("aria-label", `Retry ${item.file.name}`);
|
|
2379
|
+
retry.textContent = "Retry";
|
|
1898
2380
|
retry.addEventListener("click", () => void retryItem(index));
|
|
1899
|
-
|
|
2381
|
+
preview.append(retry);
|
|
1900
2382
|
}
|
|
2383
|
+
|
|
1901
2384
|
const remove = document.createElement("button");
|
|
1902
2385
|
remove.type = "button";
|
|
1903
|
-
remove.className = "
|
|
2386
|
+
remove.className = "overlay-button remove-overlay";
|
|
1904
2387
|
remove.dataset.testid = `meta-upload-remove-${index}`;
|
|
1905
|
-
remove.
|
|
2388
|
+
remove.setAttribute(
|
|
2389
|
+
"aria-label",
|
|
2390
|
+
`Remove ${item.file?.name || item.displayName || `file ${index + 1}`}`,
|
|
2391
|
+
);
|
|
2392
|
+
remove.textContent = "×";
|
|
1906
2393
|
remove.disabled = ["uploading", "validating", "uploaded"].includes(
|
|
1907
2394
|
item.status,
|
|
1908
2395
|
);
|
|
1909
2396
|
remove.addEventListener("click", () => removeItem(index));
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
card.append(head);
|
|
2397
|
+
preview.append(remove);
|
|
2398
|
+
card.append(preview);
|
|
1913
2399
|
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
2400
|
+
const head = document.createElement("div");
|
|
2401
|
+
head.className = "file-head";
|
|
2402
|
+
const label = document.createElement("p");
|
|
2403
|
+
label.className = "file-label";
|
|
2404
|
+
label.textContent =
|
|
2405
|
+
item.file?.name || item.displayName || `File ${index + 1}`;
|
|
2406
|
+
const status = document.createElement("p");
|
|
2407
|
+
status.className = `file-state${item.status === "failed" ? " error" : ""}`;
|
|
2408
|
+
status.dataset.testid =
|
|
2409
|
+
item.status === "failed"
|
|
2410
|
+
? `meta-upload-file-error-${index}`
|
|
2411
|
+
: `meta-upload-file-status-${index}`;
|
|
2412
|
+
status.textContent = itemStatusText(item);
|
|
2413
|
+
head.append(label, status);
|
|
2414
|
+
card.append(head);
|
|
1928
2415
|
fragment.append(card);
|
|
1929
2416
|
});
|
|
1930
2417
|
elements.fileList.replaceChildren(fragment);
|
|
2418
|
+
updateUploadTitle();
|
|
2419
|
+
scheduleIntrinsicSizeReport(true);
|
|
1931
2420
|
}
|
|
1932
2421
|
|
|
1933
2422
|
function itemStatusText(item) {
|
|
@@ -1940,8 +2429,6 @@
|
|
|
1940
2429
|
|
|
1941
2430
|
async function refreshSessionStatus() {
|
|
1942
2431
|
if (!state.session) return;
|
|
1943
|
-
elements.checkStatus.disabled = true;
|
|
1944
|
-
elements.pendingRefresh.disabled = true;
|
|
1945
2432
|
setSessionStatus("Comprobando el staging privado…", "neutral");
|
|
1946
2433
|
try {
|
|
1947
2434
|
const result = await callReadOnlyTool("check_meta_media_upload", {
|
|
@@ -1950,24 +2437,33 @@
|
|
|
1950
2437
|
const status = parseStatusResult(result);
|
|
1951
2438
|
state.assets = status.assets;
|
|
1952
2439
|
if (!status.assets.length) {
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
2440
|
+
if (status.legacyKindAmbiguous) {
|
|
2441
|
+
requireFallback(
|
|
2442
|
+
"El tipo del asset legacy es ambiguo. Vuelve a comprobar o usa el enlace seguro.",
|
|
2443
|
+
"warning",
|
|
2444
|
+
);
|
|
2445
|
+
} else {
|
|
2446
|
+
requireFallback(
|
|
2447
|
+
"La sesión sigue pendiente. Continúa desde el enlace seguro para terminar la carga.",
|
|
2448
|
+
"warning",
|
|
2449
|
+
);
|
|
2450
|
+
}
|
|
1959
2451
|
elements.destinationSection.hidden = true;
|
|
1960
|
-
elements.pendingRefresh.hidden = false;
|
|
1961
2452
|
return;
|
|
1962
2453
|
}
|
|
1963
|
-
elements.pendingRefresh.hidden = true;
|
|
1964
2454
|
|
|
1965
|
-
const locallyUploaded = state.items.filter(
|
|
1966
|
-
(item) => item.status === "uploaded",
|
|
1967
|
-
).length;
|
|
1968
2455
|
const partial =
|
|
1969
2456
|
status.status === "pending" ||
|
|
1970
|
-
|
|
2457
|
+
(state.items.length > 0 &&
|
|
2458
|
+
status.assets.length < state.items.length);
|
|
2459
|
+
if (partial) {
|
|
2460
|
+
requireFallback(
|
|
2461
|
+
"Carga parcial verificada. Puedes continuar desde el enlace seguro si algún archivo no terminó.",
|
|
2462
|
+
"warning",
|
|
2463
|
+
);
|
|
2464
|
+
} else {
|
|
2465
|
+
setFallbackVisibility(state.fallbackRequired);
|
|
2466
|
+
}
|
|
1971
2467
|
setSessionStatus(
|
|
1972
2468
|
partial
|
|
1973
2469
|
? "Carga parcial verificada. Continuaremos solo con los assets listos."
|
|
@@ -1986,18 +2482,12 @@
|
|
|
1986
2482
|
(error && /expired|expirad/i.test(String(error.message)));
|
|
1987
2483
|
const toolsUnavailable =
|
|
1988
2484
|
error && error.message === "host_tools_unavailable";
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
:
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
"danger",
|
|
1996
|
-
);
|
|
1997
|
-
elements.pendingRefresh.hidden = expired;
|
|
1998
|
-
} finally {
|
|
1999
|
-
elements.checkStatus.disabled = false;
|
|
2000
|
-
elements.pendingRefresh.disabled = false;
|
|
2485
|
+
const message = expired
|
|
2486
|
+
? "La sesión está expirada. Abre el enlace seguro para solicitar una nueva."
|
|
2487
|
+
: toolsUnavailable
|
|
2488
|
+
? "host_tools_unavailable: este host no permite comprobar el staging desde la App. Usa el enlace seguro."
|
|
2489
|
+
: "No se pudo comprobar la sesión. Conserva el enlace y vuelve a intentarlo.";
|
|
2490
|
+
requireFallback(message, "danger", expired);
|
|
2001
2491
|
}
|
|
2002
2492
|
}
|
|
2003
2493
|
|
|
@@ -2012,7 +2502,15 @@
|
|
|
2012
2502
|
const bridge = openAiBridge();
|
|
2013
2503
|
let result;
|
|
2014
2504
|
if (bridge && typeof bridge.callTool === "function") {
|
|
2015
|
-
|
|
2505
|
+
try {
|
|
2506
|
+
result = await bridge.callTool(name, args);
|
|
2507
|
+
} catch (error) {
|
|
2508
|
+
if (!state.hostCanServerTools) throw error;
|
|
2509
|
+
result = await rpcRequest("tools/call", {
|
|
2510
|
+
name,
|
|
2511
|
+
arguments: args,
|
|
2512
|
+
});
|
|
2513
|
+
}
|
|
2016
2514
|
} else if (state.hostCanServerTools) {
|
|
2017
2515
|
result = await rpcRequest("tools/call", { name, arguments: args });
|
|
2018
2516
|
} else {
|
|
@@ -2125,18 +2623,19 @@
|
|
|
2125
2623
|
fillSelect(
|
|
2126
2624
|
elements.portfolio,
|
|
2127
2625
|
state.destinations.portfolios,
|
|
2128
|
-
"
|
|
2626
|
+
"Portfolio",
|
|
2129
2627
|
(entry) => entry.name,
|
|
2130
2628
|
);
|
|
2131
2629
|
fillSelect(
|
|
2132
2630
|
elements.page,
|
|
2133
2631
|
state.destinations.pages,
|
|
2134
|
-
"
|
|
2632
|
+
"Facebook Page",
|
|
2135
2633
|
(entry) => entry.name,
|
|
2136
2634
|
);
|
|
2137
2635
|
renderAdAccounts();
|
|
2138
2636
|
elements.destinationSection.hidden = false;
|
|
2139
2637
|
updateDestinationSubmit();
|
|
2638
|
+
scheduleIntrinsicSizeReport(true);
|
|
2140
2639
|
}
|
|
2141
2640
|
|
|
2142
2641
|
function renderAdAccounts() {
|
|
@@ -2149,9 +2648,7 @@
|
|
|
2149
2648
|
fillSelect(
|
|
2150
2649
|
elements.adAccount,
|
|
2151
2650
|
accounts,
|
|
2152
|
-
portfolioId
|
|
2153
|
-
? "Selecciona una cuenta"
|
|
2154
|
-
: "Elige primero un portfolio",
|
|
2651
|
+
portfolioId ? "Ad account" : "Ad account",
|
|
2155
2652
|
(entry) =>
|
|
2156
2653
|
`${entry.name}${entry.currency ? ` · ${entry.currency}` : ""}`,
|
|
2157
2654
|
);
|
|
@@ -2236,6 +2733,7 @@
|
|
|
2236
2733
|
"Copia el handoff exacto y pégalo en el chat.",
|
|
2237
2734
|
"warning",
|
|
2238
2735
|
);
|
|
2736
|
+
scheduleIntrinsicSizeReport(true);
|
|
2239
2737
|
}
|
|
2240
2738
|
|
|
2241
2739
|
async function copyHandoff() {
|