diff --git a/test/core/workflow/workflow-acrobat/action-binder.test.js b/test/core/workflow/workflow-acrobat/action-binder.test.js
index 534ca68f9..20b68ab56 100644
--- a/test/core/workflow/workflow-acrobat/action-binder.test.js
+++ b/test/core/workflow/workflow-acrobat/action-binder.test.js
@@ -1537,6 +1537,23 @@ describe('ActionBinder', () => {
it('should return false for direct upload verbs without file size', () => {
expect(actionBinder.isDirectUploadVerb()).to.be.false;
});
+
+ it('should use the extended max size for verbs in the extended list', () => {
+ actionBinder.workflowCfg.enabledFeatures = ['word-to-pdf'];
+ actionBinder.workflowCfg.targetCfg.directUploadMaxSizeExtended = 3145728;
+ actionBinder.workflowCfg.targetCfg.directUploadMaxSizeExtendedVerbs = ['word-to-pdf'];
+ expect(actionBinder.isDirectUploadVerb(3145728)).to.be.true;
+ expect(actionBinder.isDirectUploadVerb(1048577)).to.be.true;
+ expect(actionBinder.isDirectUploadVerb(3145729)).to.be.false;
+ });
+
+ it('should fall back to the global max size for verbs not in the extended list', () => {
+ actionBinder.workflowCfg.enabledFeatures = ['compress-pdf'];
+ actionBinder.workflowCfg.targetCfg.directUploadMaxSizeExtended = 3145728;
+ actionBinder.workflowCfg.targetCfg.directUploadMaxSizeExtendedVerbs = ['word-to-pdf'];
+ expect(actionBinder.isDirectUploadVerb(1048576)).to.be.true;
+ expect(actionBinder.isDirectUploadVerb(1048577)).to.be.false;
+ });
});
describe('continueInApp', () => {
diff --git a/unitylibs/core/widgets/prompt-upload/prompt-upload.css b/unitylibs/core/widgets/prompt-upload/prompt-upload.css
index a4cc2f1f1..fa7d4660c 100644
--- a/unitylibs/core/widgets/prompt-upload/prompt-upload.css
+++ b/unitylibs/core/widgets/prompt-upload/prompt-upload.css
@@ -20,35 +20,43 @@
}
.pu-error-toast {
+ cursor: default;
+ background: #D31510;
+ max-width: 435px;
+ color: white;
position: absolute;
- top: 90px;
+ top: 109px;
+ transform: translate(-50%, -50%);
left: 50%;
- transform: translateX(-50%);
- z-index: 100;
+ font-size: 14px;
+ font-weight: 400;
display: flex;
+ justify-content: center;
align-items: center;
- gap: 12px;
- width: max-content;
- max-width: min(560px, 92%);
- box-sizing: border-box;
- padding: 14px 16px;
- border-radius: 8px;
- background: #d31510;
- box-shadow: 0 3px 12px rgb(0 0 0 / 15%);
- color: #fff;
- font-size: 15px;
- line-height: 20px;
+ padding: 8px;
}
.pu-error-toast.hide { display: none; }
/* stylelint-disable selector-class-pattern -- verb-error* mirrors the acrobat error-toast markup */
-.pu-error-toast .verb-errorIcon,
+.pu-error-toast .verb-errorIcon {
+ align-self: self-start;
+ width: 18px;
+ height: 18px;
+ margin-left: 8px;
+ margin-right: 4px;
+ margin-top: 7px;
+
+}
.pu-error-toast .verb-errorBtn {
display: inline-flex;
- align-items: center;
- flex-shrink: 0;
- color: #fff;
+ align-self: self-start;
+ width: 32px;
+ height: 32px;
+ cursor: pointer;
+ background: none;
+ border: none;
+ outline: none;
}
.pu-error-toast .verb-errorIcon svg {
@@ -57,8 +65,14 @@
}
.pu-error-toast .verb-errorText {
- flex: 1;
+ border-right: 1px solid rgb(255 255 255 / 20%);
+ padding: 0;
margin: 0;
+ margin-right: 8px;
+ padding-right: 16px;
+ max-width: 324px;
+ margin-left: 8px;
+ min-width: 100px;
}
.pu-error-toast .verb-errorBtn {
@@ -69,8 +83,8 @@
.pu-error-toast .verb-errorBtn:hover { opacity: 1; }
.pu-error-toast .verb-errorBtn svg {
- width: 18px;
- height: 18px;
+ width: 32px;
+ height: 32px;
}
/* stylelint-enable selector-class-pattern */
@@ -102,7 +116,7 @@
.pu-dropdown-label {
font-size: 14px;
- color: #6e6e6e;
+ color: #505050;
white-space: nowrap;
}
@@ -170,16 +184,20 @@
.pu-input-helper {
font-size: 12px;
- color: #6e6e6e;
+ color: #505050;
}
.pu-search-field .inp-field {
min-height: 24px;
font-size: 16px;
padding: 0;
+ color: #686868;
}
-.pu-search-field .inp-field::placeholder { color: #9a9a9a; }
+.pu-search-field .inp-field::placeholder {
+ color: #686868;
+ opacity: 1;
+}
.pu-search-field.pu-pill {
gap: 6px;
@@ -217,6 +235,7 @@
align-items: center;
gap: 16px;
width: 100%;
+ justify-content: space-between;
}
.pu-footer-left {
@@ -225,7 +244,7 @@
gap: 20px;
}
-.pu-footer-container .act-wrap { margin-inline-start: auto; }
+/* .pu-footer-container .act-wrap { margin-inline-start: auto; } */
.pu-secondary-link {
align-self: flex-start;
@@ -291,6 +310,10 @@
}
@media (max-width: 700px) {
+
+ /* stylelint-disable-next-line selector-class-pattern */
+ .pu-error-toast .verb-errorText { min-width: 120px; }
+
.pu-header {
flex-direction: column;
align-items: flex-start;
@@ -301,16 +324,18 @@
flex-direction: column;
align-items: flex-start;
gap: 10px;
+ padding: 12px;
}
.pu-search-field.pu-pill .pu-input-wrap { width: 100%; }
.pu-footer-container {
- flex-direction: column;
align-items: flex-start;
+ justify-content: space-between;
+ flex-direction: column;
}
- .pu-footer-container .act-wrap,
+
.pu-secondary-link {
margin-inline-start: 0;
align-self: flex-start;
diff --git a/unitylibs/core/widgets/prompt-upload/prompt-upload.js b/unitylibs/core/widgets/prompt-upload/prompt-upload.js
index 397f3f8ec..412e3665a 100644
--- a/unitylibs/core/widgets/prompt-upload/prompt-upload.js
+++ b/unitylibs/core/widgets/prompt-upload/prompt-upload.js
@@ -135,6 +135,9 @@ export default class PromptUploadWidget {
dragText: placeholderText(this.el, 'icon-dropzone-drag-text'),
showIcon: this.hasFlag('icon-upload-icon'),
});
+ const verb = this.workflowCfg?.enabledFeatures?.[0] || '';
+ const verbLabel = verb.split('-')[0].replace(/^./, (c) => c.toUpperCase());
+ refs.dropZone.setAttribute('daa-ll', verbLabel ? `add-sources--${verbLabel}` : 'add-sources');
const slot = createTag('div', { class: 'pu-upload-slot' });
slot.append(refs.wrap);
const subtext = placeholderText(this.el, 'icon-dropzone-subtext');
@@ -233,6 +236,8 @@ export default class PromptUploadWidget {
if (this.selectedOption) this.setSelectedOption(this.selectedOption);
if (this.optionPayloadKey) this.widgetWrap?.setAttribute('data-selected-option-key', this.optionPayloadKey);
if (this.ctaStaticLink) this.widgetWrap?.setAttribute('data-cta-static-link', this.ctaStaticLink);
+ const promptPrefix = placeholderText(this.el, 'icon-prompt-prefix')
+ if (promptPrefix) this.widgetWrap?.setAttribute('data-prompt-prefix', promptPrefix);
return this.cfg.actionMap;
}
}
diff --git a/unitylibs/core/workflow/workflow-acrobat/action-binder.js b/unitylibs/core/workflow/workflow-acrobat/action-binder.js
index 71b4d9e57..e1375c2fb 100644
--- a/unitylibs/core/workflow/workflow-acrobat/action-binder.js
+++ b/unitylibs/core/workflow/workflow-acrobat/action-binder.js
@@ -777,7 +777,9 @@ export default class ActionBinder {
}
isDirectUploadVerb(fileSize) {
- const directUploadMaxSize = this.workflowCfg.targetCfg.directUploadMaxSize || 0;
+ const { targetCfg, enabledFeatures } = this.workflowCfg;
+ const directUploadMaxSize = (targetCfg.directUploadMaxSizeExtendedVerbs?.includes(enabledFeatures?.[0])
+ ? targetCfg.directUploadMaxSizeExtended : targetCfg.directUploadMaxSize) || 0;
return fileSize != null && fileSize <= directUploadMaxSize;
}
diff --git a/unitylibs/core/workflow/workflow-acrobat/target-config.json b/unitylibs/core/workflow/workflow-acrobat/target-config.json
index 795acec1e..53c49d6b4 100644
--- a/unitylibs/core/workflow/workflow-acrobat/target-config.json
+++ b/unitylibs/core/workflow/workflow-acrobat/target-config.json
@@ -16,6 +16,8 @@
"verbsWithoutMfuToSfuFallback": ["compress-pdf"],
"awaitFinalizeVerbs": ["word-to-pdf", "ppt-to-pdf", "excel-to-pdf"],
"directUploadMaxSize": 1048576,
+ "directUploadMaxSizeExtended": 3145728,
+ "directUploadMaxSizeExtendedVerbs": ["word-to-pdf", "ppt-to-pdf", "excel-to-pdf", "jpg-to-pdf", "png-to-pdf", "heic-to-pdf"],
"nonpdfMfuFeedbackScreenTypeNonpdf": ["combine-pdf"],
"nonpdfSfuProductScreen": ["word-to-pdf", "jpg-to-pdf", "ppt-to-pdf", "excel-to-pdf", "png-to-pdf", "createpdf", "chat-pdf", "chat-pdf-student", "summarize-pdf", "pdf-ai", "heic-to-pdf", "image-to-pdf", "bmp-to-pdf", "gif-to-pdf", "tiff-to-pdf", "indd-to-pdf", "psd-to-pdf", "ai-to-pdf", "quiz-maker", "flashcard-maker", "mindmap-maker", "resume-builder"],
"mfuUploadAllowed": ["combine-pdf", "rotate-pages", "chat-pdf", "chat-pdf-student", "summarize-pdf", "pdf-ai", "quiz-maker", "flashcard-maker", "mindmap-maker", "heic-to-pdf"],
diff --git a/unitylibs/core/workflow/workflow-prompt-upload/action-binder.js b/unitylibs/core/workflow/workflow-prompt-upload/action-binder.js
index 8d9037c79..55bfeec2f 100644
--- a/unitylibs/core/workflow/workflow-prompt-upload/action-binder.js
+++ b/unitylibs/core/workflow/workflow-prompt-upload/action-binder.js
@@ -28,7 +28,7 @@ const ENDING_SPACE_PERIOD_REGEX = /[ .]+$/;
const STARTING_SPACE_PERIOD_REGEX = /^[ .]+/;
const ERROR_WARNING_ICON = '';
-const ERROR_CLOSE_ICON = '';
+const ERROR_CLOSE_ICON = '';
export default class ActionBinder {
static SINGLE_FILE_ERROR_MESSAGES = {
@@ -149,6 +149,7 @@ export default class ActionBinder {
this.pendingFiles = [];
this.query = '';
this.optionValue = '';
+ this.promptPrefix = '';
this.optionKey = '';
this.selectedStyleName = '';
this.analyticsModule = null;
@@ -384,17 +385,30 @@ export default class ActionBinder {
if (existing) return existing;
const host = this.block || this.canvasArea;
if (!host) return null;
- const toast = createTag('div', { class: 'error verb-error pu-error-toast hide' });
+ const toast = createTag('div', { class: 'error verb-error pu-error-toast hide', role: 'alert', tabindex: '-1' });
const icon = createTag('div', { class: 'verb-errorIcon' });
icon.innerHTML = ERROR_WARNING_ICON;
const closeBtn = createTag('div', { class: 'verb-errorBtn', role: 'button', tabindex: '0', 'aria-label': 'Close error' });
closeBtn.innerHTML = ERROR_CLOSE_ICON;
toast.append(icon, createTag('p', { class: 'verb-errorText' }), closeBtn);
- if (getComputedStyle(host).position === 'static') host.style.position = 'relative';
host.append(toast);
return toast;
}
+ getStickyTopOffset() {
+ const selectors = ['header.global-navigation', '.feds-localnav', '.global-navigation'];
+ let bottom = 0;
+ selectors.forEach((sel) => {
+ const el = document.querySelector(sel);
+ if (!el) return;
+ const pos = getComputedStyle(el).position;
+ if (pos !== 'fixed' && pos !== 'sticky') return;
+ const rect = el.getBoundingClientRect();
+ if (rect.top <= 1 && rect.bottom > bottom) bottom = rect.bottom;
+ });
+ return bottom;
+ }
+
showErrorToastMessage(message) {
const toast = this.findOrCreateErrorToast();
if (!toast) {
@@ -402,9 +416,11 @@ export default class ActionBinder {
return;
}
const textEl = toast.querySelector('.verb-errorText') || toast;
- textEl.textContent = message;
toast.classList.remove('hide');
toast.classList.add('verb-error');
+ const stickyOffset = this.getStickyTopOffset();
+ if (stickyOffset) toast.style.top = `${stickyOffset + 12}px`;
+ textEl.textContent = message;
const closeBtn = toast.querySelector('.verb-errorBtn');
if (closeBtn && !closeBtn.dataset.puBound) {
closeBtn.dataset.puBound = 'true';
@@ -414,6 +430,7 @@ export default class ActionBinder {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); hide(); }
});
}
+ toast.focus();
}
isMixedFileTypes(files) {
@@ -461,35 +478,61 @@ export default class ActionBinder {
const errorMessages = files.length === 1
? ActionBinder.SINGLE_FILE_ERROR_MESSAGES
: ActionBinder.MULTI_FILE_ERROR_MESSAGES;
+
const validFiles = [];
+ const errorTypes = new Set();
let allFilesFailed = true;
-
+
if (this.limits.maxNumFiles && files.length > this.limits.maxNumFiles) {
- await this.dispatchErrorToast('validation_error_max_num_files', null, `Maximum ${this.limits.maxNumFiles} files allowed`, false, true, {
- code: 'validation_error_validate_files',
- subCode: 'validation_error_max_num_files',
- });
+ await this.dispatchErrorToast(
+ 'validation_error_max_num_files',
+ null,
+ `Maximum ${this.limits.maxNumFiles} files allowed`,
+ false,
+ true,
+ {
+ code: 'validation_error_validate_files',
+ subCode: 'validation_error_max_num_files',
+ },
+ );
return { isValid: false, validFiles };
}
-
+
for (const file of files) {
let fail = false;
+
if (this.limits.allowedFileTypes && !this.limits.allowedFileTypes.includes(file.type)) {
- await this.dispatchErrorToast(errorMessages.UNSUPPORTED_TYPE, null, `File type: ${file.type}`, false, true, { code: 'validation_error_validate_files', subCode: errorMessages.UNSUPPORTED_TYPE });
fail = true;
+ errorTypes.add(errorMessages.UNSUPPORTED_TYPE);
} else if (!file.size) {
- await this.dispatchErrorToast(errorMessages.EMPTY_FILE, null, null, false, true, { code: 'validation_error_validate_files', subCode: errorMessages.EMPTY_FILE });
fail = true;
+ errorTypes.add(errorMessages.EMPTY_FILE);
} else if (this.limits.maxFileSize && file.size > this.limits.maxFileSize) {
- await this.dispatchErrorToast(errorMessages.FILE_TOO_LARGE, null, `File too large: ${file.size}`, false, true, { code: 'validation_error_validate_files', subCode: errorMessages.FILE_TOO_LARGE });
fail = true;
+ errorTypes.add(errorMessages.FILE_TOO_LARGE);
}
+
if (!fail) {
allFilesFailed = false;
validFiles.push(file);
}
}
- return { isValid: !allFilesFailed, validFiles };
+ if (allFilesFailed) {
+ const firstErrorType = Array.from(errorTypes)[0];
+ await this.dispatchErrorToast(
+ firstErrorType,
+ null,
+ null,
+ false,
+ true,
+ {
+ code: 'validation_error_validate_files',
+ subCode: firstErrorType,
+ },
+ );
+ return { isValid: false, validFiles };
+ }
+ return { isValid: true, validFiles };
}
getComputedRedirectParams(queryString) {
@@ -546,7 +589,11 @@ export default class ActionBinder {
}
async handleRedirect(cOpts, filesData) {
- if (this.query) cOpts.query = this.query;
+ if (this.query) {
+ cOpts.query = this.promptPrefix
+ ? `${this.promptPrefix} ${this.query}`
+ : this.query;
+ }
if (this.optionValue && this.optionKey) cOpts.payload[this.optionKey] = this.optionValue;
[cOpts.payload.referrer] = this.workflowCfg.enabledFeatures;
try {
@@ -744,32 +791,33 @@ export default class ActionBinder {
return searchRoot?.querySelector?.('.ex-unity-wrap') || searchRoot;
}
- bindWidgetDropTarget() {
- const card = this.getWidgetWrap()?.querySelector('.interactive-area') || this.getWidgetWrap();
- if (!card || card.dataset.puDropBound) return;
- card.dataset.puDropBound = 'true';
+ bindMarqueeDropTarget() {
+ const dropTarget = this.block;
+ const highlightEl = this.getWidgetWrap()?.querySelector('.interactive-area') || this.getWidgetWrap();
+ if (!dropTarget || dropTarget.dataset.puDropBound) return;
+ dropTarget.dataset.puDropBound = 'true';
let dragDepth = 0;
const hasFilePayload = (e) => !!e?.dataTransfer?.types && [...e.dataTransfer.types].includes('Files');
- const setHighlight = (on) => card.classList.toggle('drag-over', !!on);
- card.addEventListener('dragenter', (e) => {
- if (!hasFilePayload(e)) return;
+ const setHighlight = (on) => highlightEl?.classList.toggle('drag-over', !!on);
+ dropTarget.addEventListener('dragenter', (e) => {
e.preventDefault();
+ if (!hasFilePayload(e)) return;
dragDepth += 1;
setHighlight(true);
});
- card.addEventListener('dragover', (e) => {
- if (!hasFilePayload(e)) return;
+ dropTarget.addEventListener('dragover', (e) => {
e.preventDefault();
+ if (!hasFilePayload(e)) return;
if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy';
setHighlight(true);
});
- card.addEventListener('dragleave', (e) => {
+ dropTarget.addEventListener('dragleave', (e) => {
if (!hasFilePayload(e)) return;
e.preventDefault();
dragDepth = Math.max(0, dragDepth - 1);
if (dragDepth === 0) setHighlight(false);
});
- card.addEventListener('drop', async (e) => {
+ dropTarget.addEventListener('drop', async (e) => {
if (!hasFilePayload(e)) return;
e.preventDefault();
dragDepth = 0;
@@ -795,6 +843,7 @@ export default class ActionBinder {
const input = searchRoot?.querySelector?.('#pbuPromptInput') || searchRoot?.querySelector?.('.inp-field');
this.query = input?.value?.trim() || '';
this.optionValue = wrap?.getAttribute('data-selected-option-value') || '';
+ this.promptPrefix = wrap?.getAttribute('data-prompt-prefix') || '';
this.optionKey = wrap?.getAttribute('data-selected-option-key') || '';
this.selectedStyleName = wrap?.querySelector('.selected-model .model-name')?.textContent?.trim() || '';
this.ctaStaticLink = wrap?.getAttribute('data-cta-static-link') || '';
@@ -924,6 +973,11 @@ export default class ActionBinder {
el.addEventListener('click', async (e) => {
if (value === 'interrupt') { e.preventDefault(); await this.cancelOperation(); } else if (value === 'generate') { e.preventDefault(); await this.handleGenerate(); }
});
+ if (el.nodeName === 'A') {
+ el.addEventListener('keydown', (e) => {
+ if (e.key === ' ' || e.key === 'Spacebar') { e.preventDefault(); el.click(); }
+ });
+ }
break;
case 'DIV':
el.addEventListener('dragover', (e) => { e.preventDefault(); el.classList.add('drag-over'); });
@@ -993,7 +1047,7 @@ export default class ActionBinder {
searchRoot.addEventListener('pu:style-open', () => this.dispatchAnalyticsEvent('style-open'));
}
if (b === this.block) {
- this.bindWidgetDropTarget();
+ this.bindMarqueeDropTarget();
const preloadTransitionScreen = () => this.loadTransitionScreen();
if ('requestIdleCallback' in window) requestIdleCallback(preloadTransitionScreen, { timeout: 3000 });
else setTimeout(preloadTransitionScreen, 2000);
diff --git a/unitylibs/core/workflow/workflow-prompt-upload/sprite.svg b/unitylibs/core/workflow/workflow-prompt-upload/sprite.svg
index 766ddc1fc..cdf2e5310 100644
--- a/unitylibs/core/workflow/workflow-prompt-upload/sprite.svg
+++ b/unitylibs/core/workflow/workflow-prompt-upload/sprite.svg
@@ -30,16 +30,14 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/unitylibs/core/workflow/workflow-prompt-upload/target-config.json b/unitylibs/core/workflow/workflow-prompt-upload/target-config.json
index 50c85bf30..a6ebd486b 100644
--- a/unitylibs/core/workflow/workflow-prompt-upload/target-config.json
+++ b/unitylibs/core/workflow/workflow-prompt-upload/target-config.json
@@ -18,8 +18,8 @@
"verbsWithoutMfuToSfuFallback": [],
"awaitFinalizeVerbs": [],
"nonpdfMfuFeedbackScreenTypeNonpdf": [],
- "nonpdfSfuProductScreen": ["citation-generator"],
- "mfuUploadAllowed": ["citation-generator"],
+ "nonpdfSfuProductScreen": ["citation-generator", "flashcard-maker", "quiz-maker"],
+ "mfuUploadAllowed": ["citation-generator", "flashcard-maker", "quiz-maker"],
"mfuUploadOnlyPdfAllowed": [],
"experimentationOn": [],
"limits": {