Update app.js
Browse files
app.js
CHANGED
|
@@ -1100,7 +1100,12 @@ const API_BASE = window.location.origin;
|
|
| 1100 |
/** When Blob is enabled, try Blob before direct POST (0 = any non-empty file). */
|
| 1101 |
// Avoid forcing tiny image uploads through Blob flow (can fail on some hosts/browsers).
|
| 1102 |
const NEXUS_BLOB_THRESHOLD_BYTES = 6 * 1024 * 1024;
|
| 1103 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1104 |
|
| 1105 |
const NEXUS_COMPOSER_ATTACH_PREFIX = "nexus_composer_attach_v1:";
|
| 1106 |
|
|
@@ -2919,6 +2924,16 @@ function isVideoFilename(name) {
|
|
| 2919 |
return ["mp4", "mov", "webm", "m4v", "avi", "mkv", "mpeg", "mpg", "3gp", "wmv", "ogv", "f4v"].includes(ext);
|
| 2920 |
}
|
| 2921 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2922 |
function applyThinkingLabel(statusEl, payload) {
|
| 2923 |
if (!statusEl) return;
|
| 2924 |
const p = payload || {};
|
|
@@ -2927,7 +2942,12 @@ function applyThinkingLabel(statusEl, payload) {
|
|
| 2927 |
const lab0 = String(p.label || "").trim();
|
| 2928 |
if (
|
| 2929 |
lab0 &&
|
| 2930 |
-
(parseMode === "video" ||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2931 |
) {
|
| 2932 |
statusEl.textContent = lab0;
|
| 2933 |
const rootV = statusEl.closest(".ai-thinking-standalone");
|
|
@@ -3612,6 +3632,7 @@ async function checkAuth() {
|
|
| 3612 |
}
|
| 3613 |
clearTimeout(to);
|
| 3614 |
const data = await res.json().catch(() => ({}));
|
|
|
|
| 3615 |
if (res.ok && data.logged_in === true) {
|
| 3616 |
state.isLoggedIn = true;
|
| 3617 |
state.userEmail = data.email || "";
|
|
@@ -5440,10 +5461,12 @@ async function sendChatMessage(message, attachedFiles) {
|
|
| 5440 |
thinkingNode.appendChild(typingBubble);
|
| 5441 |
const statusSr = document.createElement("span");
|
| 5442 |
statusSr.className = "ai-thinking-status-sr visually-hidden";
|
| 5443 |
-
const
|
| 5444 |
-
|
| 5445 |
-
|
| 5446 |
-
|
|
|
|
|
|
|
| 5447 |
thinkingNode.appendChild(statusSr);
|
| 5448 |
thinkingNode.setAttribute("aria-label", statusSr.textContent);
|
| 5449 |
chatMessages.appendChild(thinkingNode);
|
|
@@ -5727,7 +5750,7 @@ function showUploadLimitPopup() {
|
|
| 5727 |
pop.className = "upload-limit-popup";
|
| 5728 |
document.body.appendChild(pop);
|
| 5729 |
}
|
| 5730 |
-
pop.textContent =
|
| 5731 |
pop.classList.add("visible");
|
| 5732 |
if (uploadLimitPopupTimer) clearTimeout(uploadLimitPopupTimer);
|
| 5733 |
uploadLimitPopupTimer = setTimeout(() => {
|
|
@@ -5773,6 +5796,20 @@ function renderFileChips() {
|
|
| 5773 |
</div>
|
| 5774 |
<button type="button" class="file-chip-remove" aria-label="Remove">×</button>
|
| 5775 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5776 |
`;
|
| 5777 |
} else {
|
| 5778 |
const previewUrl = fileData.preview_data_url || (
|
|
@@ -5975,7 +6012,9 @@ async function uploadSelectedFile(file, fromLanding) {
|
|
| 5975 |
if (file.size > MAX_UPLOAD_BYTES) {
|
| 5976 |
showUploadLimitPopup();
|
| 5977 |
if (typeof appendAssistantBubble === "function") {
|
| 5978 |
-
appendAssistantBubble(
|
|
|
|
|
|
|
| 5979 |
}
|
| 5980 |
if (fileInput) fileInput.value = "";
|
| 5981 |
if (fromLanding && landingFileInput) landingFileInput.value = "";
|
|
@@ -6488,7 +6527,11 @@ function bindEvents() {
|
|
| 6488 |
}
|
| 6489 |
});
|
| 6490 |
|
| 6491 |
-
const ALLOWED_EXTENSIONS = [
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6492 |
function isAllowedFile(file) {
|
| 6493 |
if (!file?.name) return false;
|
| 6494 |
const ext = file.name.split(".").pop()?.toLowerCase() || "";
|
|
|
|
| 1100 |
/** When Blob is enabled, try Blob before direct POST (0 = any non-empty file). */
|
| 1101 |
// Avoid forcing tiny image uploads through Blob flow (can fail on some hosts/browsers).
|
| 1102 |
const NEXUS_BLOB_THRESHOLD_BYTES = 6 * 1024 * 1024;
|
| 1103 |
+
let MAX_UPLOAD_BYTES = 100 * 1024 * 1024;
|
| 1104 |
+
|
| 1105 |
+
function nexusApplyMaxUploadFromMe(data) {
|
| 1106 |
+
const mb = Number(data && data.max_upload_mb);
|
| 1107 |
+
if (Number.isFinite(mb) && mb > 0) MAX_UPLOAD_BYTES = Math.floor(mb * 1024 * 1024);
|
| 1108 |
+
}
|
| 1109 |
|
| 1110 |
const NEXUS_COMPOSER_ATTACH_PREFIX = "nexus_composer_attach_v1:";
|
| 1111 |
|
|
|
|
| 2924 |
return ["mp4", "mov", "webm", "m4v", "avi", "mkv", "mpeg", "mpg", "3gp", "wmv", "ogv", "f4v"].includes(ext);
|
| 2925 |
}
|
| 2926 |
|
| 2927 |
+
function isAudioFilename(name) {
|
| 2928 |
+
if (!name) return false;
|
| 2929 |
+
const ext = String(name).split(".").pop()?.toLowerCase() || "";
|
| 2930 |
+
return ext === "mp3";
|
| 2931 |
+
}
|
| 2932 |
+
|
| 2933 |
+
function isLongMediaAttachment(fileMeta) {
|
| 2934 |
+
return !!(fileMeta && (isVideoFilename(fileMeta.filename) || isAudioFilename(fileMeta.filename)));
|
| 2935 |
+
}
|
| 2936 |
+
|
| 2937 |
function applyThinkingLabel(statusEl, payload) {
|
| 2938 |
if (!statusEl) return;
|
| 2939 |
const p = payload || {};
|
|
|
|
| 2942 |
const lab0 = String(p.label || "").trim();
|
| 2943 |
if (
|
| 2944 |
lab0 &&
|
| 2945 |
+
(parseMode === "video" ||
|
| 2946 |
+
parseMode === "audio" ||
|
| 2947 |
+
fs === "Video" ||
|
| 2948 |
+
fs === "Audio" ||
|
| 2949 |
+
/uploading and analyzing video/i.test(lab0) ||
|
| 2950 |
+
/uploading and analyzing audio/i.test(lab0))
|
| 2951 |
) {
|
| 2952 |
statusEl.textContent = lab0;
|
| 2953 |
const rootV = statusEl.closest(".ai-thinking-standalone");
|
|
|
|
| 3632 |
}
|
| 3633 |
clearTimeout(to);
|
| 3634 |
const data = await res.json().catch(() => ({}));
|
| 3635 |
+
nexusApplyMaxUploadFromMe(data);
|
| 3636 |
if (res.ok && data.logged_in === true) {
|
| 3637 |
state.isLoggedIn = true;
|
| 3638 |
state.userEmail = data.email || "";
|
|
|
|
| 5461 |
thinkingNode.appendChild(typingBubble);
|
| 5462 |
const statusSr = document.createElement("span");
|
| 5463 |
statusSr.className = "ai-thinking-status-sr visually-hidden";
|
| 5464 |
+
const hasVid = (filesToSend || []).some((f) => isVideoFilename(f && f.filename));
|
| 5465 |
+
const hasAud = (filesToSend || []).some((f) => isAudioFilename(f && f.filename));
|
| 5466 |
+
let srLabel = "Thinking…";
|
| 5467 |
+
if (hasVid) srLabel = "Uploading and analyzing video... this may take up to 30 seconds";
|
| 5468 |
+
else if (hasAud) srLabel = "Uploading and analyzing audio... this may take up to 30 seconds";
|
| 5469 |
+
statusSr.textContent = srLabel;
|
| 5470 |
thinkingNode.appendChild(statusSr);
|
| 5471 |
thinkingNode.setAttribute("aria-label", statusSr.textContent);
|
| 5472 |
chatMessages.appendChild(thinkingNode);
|
|
|
|
| 5750 |
pop.className = "upload-limit-popup";
|
| 5751 |
document.body.appendChild(pop);
|
| 5752 |
}
|
| 5753 |
+
pop.textContent = `Max file size is ${Math.max(1, Math.round(MAX_UPLOAD_BYTES / (1024 * 1024)))}MB.`;
|
| 5754 |
pop.classList.add("visible");
|
| 5755 |
if (uploadLimitPopupTimer) clearTimeout(uploadLimitPopupTimer);
|
| 5756 |
uploadLimitPopupTimer = setTimeout(() => {
|
|
|
|
| 5796 |
</div>
|
| 5797 |
<button type="button" class="file-chip-remove" aria-label="Remove">×</button>
|
| 5798 |
</div>
|
| 5799 |
+
`;
|
| 5800 |
+
} else if (isAudioFilename(fileData.filename)) {
|
| 5801 |
+
chip.className = "file-chip file-chip-pdf";
|
| 5802 |
+
chip.innerHTML = `
|
| 5803 |
+
<div class="file-chip-inner">
|
| 5804 |
+
<div class="file-chip-name">${escapeHtml(baseName)}</div>
|
| 5805 |
+
<div class="file-chip-row">
|
| 5806 |
+
<span class="file-chip-preview">
|
| 5807 |
+
<span class="file-chip-preview-fallback" style="display:flex">♪</span>
|
| 5808 |
+
</span>
|
| 5809 |
+
<span class="file-chip-type">AUDIO</span>
|
| 5810 |
+
</div>
|
| 5811 |
+
<button type="button" class="file-chip-remove" aria-label="Remove">×</button>
|
| 5812 |
+
</div>
|
| 5813 |
`;
|
| 5814 |
} else {
|
| 5815 |
const previewUrl = fileData.preview_data_url || (
|
|
|
|
| 6012 |
if (file.size > MAX_UPLOAD_BYTES) {
|
| 6013 |
showUploadLimitPopup();
|
| 6014 |
if (typeof appendAssistantBubble === "function") {
|
| 6015 |
+
appendAssistantBubble(
|
| 6016 |
+
`Upload failed: Max file size is ${Math.max(1, Math.round(MAX_UPLOAD_BYTES / (1024 * 1024)))}MB.`
|
| 6017 |
+
);
|
| 6018 |
}
|
| 6019 |
if (fileInput) fileInput.value = "";
|
| 6020 |
if (fromLanding && landingFileInput) landingFileInput.value = "";
|
|
|
|
| 6527 |
}
|
| 6528 |
});
|
| 6529 |
|
| 6530 |
+
const ALLOWED_EXTENSIONS = [
|
| 6531 |
+
"pdf", "png", "jpg", "jpeg", "gif", "webp", "heic", "heif",
|
| 6532 |
+
"mp3",
|
| 6533 |
+
"mp4", "mov", "webm", "m4v", "avi", "mkv", "mpeg", "mpg", "3gp", "wmv", "ogv", "f4v",
|
| 6534 |
+
];
|
| 6535 |
function isAllowedFile(file) {
|
| 6536 |
if (!file?.name) return false;
|
| 6537 |
const ext = file.name.split(".").pop()?.toLowerCase() || "";
|