import { closeIcon, eyeIcon, fileIcon, trashIcon } from './constants.js'; const inputElement = document.getElementById('file-input'); const metadataElement = document.getElementById('metadata-input'); const textInputElement = document.getElementById('text-input'); const outputElement = document.getElementById('output-area'); const btnClear = document.getElementById('btn-clear-face-check'); const btnSubmit = document.getElementById('btn-submit-face-check'); const btnSave = document.getElementById('btn-save-face-check'); const wrapperFilesElement = document.getElementById('wrapper-files-face-check'); const wrapperMetadataElement = document.getElementById( 'wrapper-metadata-face-check' ); const wrapperUploadElement = document.getElementById( 'wrapper-upload-face-check' ); const wrapperModalElement = document.getElementById( 'wrapper-modal-1760410479496' ); const contentModalElement = document.getElementById('model-1760410479496'); const dropZones = document.getElementsByClassName('drop-zone'); const fileRemoves = document.getElementsByClassName('file-remove'); const statusFaceCheckElement = document.getElementById('status-face-check'); const tabMenuElement = document.getElementById('menu-1762855257376'); const tabItems = tabMenuElement.getElementsByClassName('item'); const livechat = document.getElementById('live-chat-1765253515534'); export let output; const REQUIRED_METADATA_FIELDS = [ 'title', 'location', 'category', 'violence level', 'description', 'social media link', ]; export const clientId = Date.now().toString(36) + Math.random().toString(36).substring(2, 8); export const apiBaseUrl = ''; export let estTimeStep1 = 0; export let estTimeStep2 = 0; let currentPreviewURL = null; const mediaPreviewUrls = new Map(); const renderer = new marked.Renderer(); renderer.link = function ({ href, text }) { return `${text}`; }; let isReplaced = true; let isReplacedBlock = true; renderer.text = function ({ text }) { const hiddenTexts = []; const noReplaces = [ '**Tags:**', '**Filename:**', '**Authenticity Assessment:**', '**Verification Tools & Methods:**', '**Synthetic Type (if applicable):**', '**Other Artifacts:**', '**Supporting Sources:**', '**Cross-Checking Information:**', '**Other Info:**', ]; isReplaced = !noReplaces.some((item) => text.includes(item)); if (isReplacedBlock) { isReplacedBlock = !text.includes('**Supporting Sources:**'); } const normalizedText = text.trim().replace(/^[-*]\s*/, ''); if (hiddenTexts.some((item) => normalizedText === item)) { return ''; } if (!isReplaced) { const inlineHtml = typeof marked.parseInline === 'function' ? marked.parseInline(text) : marked.Renderer.prototype.text.call(renderer, text); return inlineHtml; } const replaced = text.replace( /(?:\b([\w-]+)\s*)?(?:\[((?:Image\s+[^\]]+|Source\s+[^\]]+|URL\s+[^\]]+|[^,\]]+(?:\s*,\s*[^,\]]+)*))\]|\((Sources?\s+[^)]+)\))/gi, (match, beforeWord, insideSquare, insideSource) => { const beforeWordLower = (beforeWord || '').toLowerCase(); const insideLower = (insideSquare || '').toLowerCase(); const isImages = ['image', 'images', 'photo', 'photos']; if ( isImages.includes(beforeWordLower) || isImages.some((item) => insideLower.includes(item)) ) { const listImage = insideSquare .split(',') .map( (item) => `${item}` ); return `${ beforeWord ? beforeWord + ' ' : '' } [${listImage.join(', ')}]`; } const listUrl = (insideSquare || insideSource) .split(',') .map( (item) => `${item}` ); return `${beforeWord ? beforeWord + ' ' : ''} \n[${listUrl.join( ', ' )}]`; } ); return replaced; }; marked.setOptions({ renderer, breaks: true, }); let files = {}; Object.defineProperty(files, 'value', { set(newValue) { const activeIds = new Set( newValue.map((file) => `${file.name}|${file.size}|${file.lastModified}`) ); mediaPreviewUrls.forEach((url, id) => { if (!activeIds.has(id)) { URL.revokeObjectURL(url); mediaPreviewUrls.delete(id); } }); if (newValue.length > 0) { const displayNames = newValue .map((file) => { const fileId = `${file.name}|${file.size}|${file.lastModified}`; if (!mediaPreviewUrls.has(fileId)) { mediaPreviewUrls.set(fileId, URL.createObjectURL(file)); } const previewUrl = mediaPreviewUrls.get(fileId); const previewMarkup = file.type.startsWith('video/') ? `
` : `${file.name}`; return `
${previewMarkup} ${file.name}
${eyeIcon}
${trashIcon}
`; }) .join(''); wrapperFilesElement.innerHTML = displayNames; wrapperFilesElement.classList.remove('display-none'); // Add tooltips only for truncated text setTimeout(() => { document.querySelectorAll('.file-name').forEach((span) => { const fullName = span.getAttribute('data-full-name'); if (fullName && span.scrollWidth > span.clientWidth) { span.setAttribute('title', fullName); } }); }, 0); } else { wrapperFilesElement.innerHTML = ''; wrapperFilesElement.classList.add('display-none'); } this._value = newValue; }, get() { return this._value; }, }); files.value = []; function validateMetadataJson(file) { return new Promise((resolve, reject) => { if (!file) { reject('Please provide a metadata file.'); return; } const reader = new FileReader(); reader.onload = (event) => { const text = event.target?.result; if (typeof text !== 'string' || text.trim().length === 0) { reject('Metadata file must contain valid JSON.'); return; } try { const parsed = JSON.parse(text); if ( typeof parsed !== 'object' || parsed === null || Array.isArray(parsed) ) { reject('Metadata file must be a JSON object.'); return; } const keys = Object.keys(parsed); const missingFields = REQUIRED_METADATA_FIELDS.filter( (field) => !keys.includes(field) ); if (missingFields.length > 0) { reject( `Metadata file is missing required fields: ${missingFields.join( ', ' )}` ); return; } const extraFields = keys.filter( (key) => !REQUIRED_METADATA_FIELDS.includes(key) ); if (extraFields.length > 0) { reject( `Metadata file contains unsupported fields: ${extraFields.join( ', ' )}` ); return; } resolve(); } catch (error) { reject('Metadata file must contain valid JSON.'); } }; reader.onerror = () => { reject('Unable to read the metadata file.'); }; reader.readAsText(file); }); } let metadata = {}; Object.defineProperty(metadata, 'value', { set(newValue) { if (newValue.length > 0) { const displayNames = newValue .map((meta) => { const fileId = `${meta.name}|${meta.size}|${meta.lastModified}`; return `
${fileIcon}
${meta.name}
${eyeIcon}
${trashIcon}
`; }) .join(''); wrapperMetadataElement.innerHTML = displayNames; wrapperMetadataElement.classList.remove('display-none'); // Add tooltips only for truncated text setTimeout(() => { document.querySelectorAll('.file-name').forEach((span) => { const fullName = span.getAttribute('data-full-name'); if (fullName && span.scrollWidth > span.clientWidth) { span.setAttribute('title', fullName); } }); }, 0); } else { wrapperMetadataElement.innerHTML = ''; wrapperMetadataElement.classList.add('display-none'); } this._value = newValue; }, get() { return this._value; }, }); metadata.value = []; let data = {}; Object.defineProperty(data, 'value', { set(newValue) { if (newValue) { btnSave.classList.add('active'); console.log(newValue); output = newValue; // livechat.classList.remove('display-none'); } else { btnSave.classList.remove('active'); output = ''; // livechat.classList.add('display-none'); } this._value = newValue; }, get() { return this._value; }, }); // data.value = {} let selectedTab = {}; Object.defineProperty(selectedTab, 'value', { set(newValue) { if (data.value) { const menuItems = newValue === 'verified_evidence' ? `` : ''; outputElement.innerHTML = menuItems + marked.parse( data.value.readme_content[newValue]?.content || data.value.readme_content[newValue]?.source_details?.content || '' ); } isReplacedBlock = true; this._value = newValue; }, get() { return this._value; }, }); selectedTab.value = 'case_summary'; let selectedTabChildren = {}; Object.defineProperty(selectedTabChildren, 'value', { set(newValue) { if (data.value) { const menuItems = ``; outputElement.innerHTML = menuItems + marked.parse( data.value.readme_content[selectedTab.value]?.[newValue]?.content || '' ); } isReplacedBlock = true; this._value = newValue; }, get() { return this._value; }, }); selectedTab.value = 'case_summary'; let isLoading = {}; Object.defineProperty(isLoading, 'value', { set(newValue) { if (newValue) { btnClear.disabled = true; btnSave.disabled = true; btnSubmit.disabled = true; inputElement.disabled = true; metadataElement.disabled = true; Array.from(dropZones).forEach((dropZone) => { dropZone.disabled = true; dropZone.classList.add('btn-disabled'); }); Array.from(fileRemoves).forEach((fileRemove) => { fileRemove.disabled = true; fileRemove.classList.add('btn-disabled'); }); btnClear.classList.add('btn-disabled'); btnSave.classList.add('btn-disabled'); btnSubmit.classList.add('btn-disabled'); Array.from(tabItems).forEach((tabItem) => { tabItem.disabled = true; tabItem.classList.add('btn-disabled', 'no-hover'); }); } else { btnClear.disabled = false; btnSave.disabled = false; btnSubmit.disabled = false; inputElement.disabled = false; metadataElement.disabled = false; Array.from(dropZones).forEach((dropZone) => { dropZone.disabled = false; dropZone.classList.remove('btn-disabled'); }); Array.from(fileRemoves).forEach((fileRemove) => { fileRemove.disabled = false; fileRemove.classList.remove('btn-disabled'); }); btnClear.classList.remove('btn-disabled'); btnSave.classList.remove('btn-disabled'); btnSubmit.classList.remove('btn-disabled'); btnSubmit.innerText = 'Submit'; Array.from(tabItems).forEach((tabItem) => { tabItem.disabled = false; tabItem.classList.remove('btn-disabled', 'no-hover'); }); } this._value = newValue; }, get() { return this._value; }, }); inputElement.addEventListener('change', (event) => { const allowedExtensions = [ '.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.mov', '.mp4', ]; const selectedFiles = Array.from(event.target.files); const invalidFiles = selectedFiles.filter((file) => { return !allowedExtensions.some((ext) => file.name.toLowerCase().endsWith(ext) ); }); if (invalidFiles.length > 0) { createTemplateModal( 'Only the following formats are accepted: ' + allowedExtensions.join(', ') ); inputElement.value = ''; return; } for (const file of selectedFiles) { if ( !files.value.some((f) => f.name === file.name && f.size === file.size) ) { files.value = [...files.value, file]; } } inputElement.value = ''; }); metadataElement.addEventListener('change', (event) => { const allowedExtensions = ['.json']; const selectedFiles = Array.from(event.target.files || []); if (selectedFiles.length === 0) { metadataElement.value = ''; return; } const invalidFiles = selectedFiles.filter((file) => { return !allowedExtensions.some((ext) => file.name.toLowerCase().endsWith(ext) ); }); if (invalidFiles.length > 0) { createTemplateModal( 'Only the following formats are accepted: ' + allowedExtensions.join(', ') ); metadataElement.value = ''; return; } const file = selectedFiles[0]; validateMetadataJson(file) .then(() => { metadata.value = [file]; }) .catch((message) => { createTemplateModal(`
The content of the JSON file is invalid. Please follow the format below.
{ "title": "", "location": "", "category": "", "violence level": "", "description": "", "social media link": "" }
`); }) .finally(() => { metadataElement.value = ''; }); }); // Drag and drop for metadata (JSON files) const metadataDropZone = document.querySelector('label[for="metadata-input"]'); metadataDropZone.addEventListener('dragover', (e) => { e.preventDefault(); metadataDropZone.classList.add('drag-over'); }); metadataDropZone.addEventListener('dragleave', (e) => { e.preventDefault(); metadataDropZone.classList.remove('drag-over'); }); metadataDropZone.addEventListener('drop', (e) => { e.preventDefault(); metadataDropZone.classList.remove('drag-over'); const droppedFiles = Array.from(e.dataTransfer.files); const allowedExtensions = ['.json']; const invalidFiles = droppedFiles.filter((file) => { return !allowedExtensions.some((ext) => file.name.toLowerCase().endsWith(ext) ); }); if (invalidFiles.length > 0) { createTemplateModal( 'Only the following formats are accepted: ' + allowedExtensions.join(', ') ); return; } if (droppedFiles.length > 0) { const file = droppedFiles[0]; // Only allow one JSON file validateMetadataJson(file) .then(() => { metadata.value = [file]; }) .catch((message) => { createTemplateModal( `
The content of the JSON file is invalid. Please follow the format below.
{ "title": "", "location": "", "category": "", "violence level": "", "description": "", "social media link": "" }
` ); }); } }); // Drag and drop for media files const mediaDropZone = document.querySelector('label[for="file-input"]'); mediaDropZone.addEventListener('dragover', (e) => { e.preventDefault(); mediaDropZone.classList.add('drag-over'); }); mediaDropZone.addEventListener('dragleave', (e) => { e.preventDefault(); mediaDropZone.classList.remove('drag-over'); }); mediaDropZone.addEventListener('drop', (e) => { e.preventDefault(); mediaDropZone.classList.remove('drag-over'); const droppedFiles = Array.from(e.dataTransfer.files); const allowedExtensions = [ '.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.mov', '.mp4', ]; const invalidFiles = droppedFiles.filter((file) => { return !allowedExtensions.some((ext) => file.name.toLowerCase().endsWith(ext) ); }); if (invalidFiles.length > 0) { createTemplateModal( 'Only the following formats are accepted: ' + allowedExtensions.join(', ') ); return; } for (const file of droppedFiles) { if ( !files.value.some((f) => f.name === file.name && f.size === file.size) ) { files.value = [...files.value, file]; } } }); btnSubmit.addEventListener('click', () => handleSubmit()); btnClear.addEventListener('click', () => reset()); btnSave.addEventListener('click', () => handleDownload('report.md')); function reset() { inputElement.value = ''; metadataElement.value = ''; if (textInputElement) textInputElement.value = ''; outputElement.innerHTML = '

Verification result comes here...

'; files.value = []; metadata.value = []; data.value = null; } const pollIntervalMs = 5000; const maxPolls = 1000; async function runFaceCheckPolling(formData, clientId) { try { const start = await axios.post( apiBaseUrl + 'v1/face_check/polling', formData, { headers: { 'Content-Type': 'multipart/form-data', 'X-Client-ID': clientId, }, } ); const { job_id, status } = start.data; if (!job_id) return { error: 'Missing job_id from polling start' }; if (status === 'failed') return { error: 'Job failed immediately' }; let attempts = 0; while (attempts < maxPolls) { await new Promise((r) => setTimeout(r, pollIntervalMs)); attempts += 1; const res = await axios.get( apiBaseUrl + `v1/face_check/polling/${job_id}` ); if (res.data.status === 'succeeded') return { result: res.data.result }; if (res.data.status === 'failed') { return { error: res.data.error || 'Job failed' }; } } return { error: 'Polling timeout' }; } catch (error) { console.error('Face check polling failed', error); statusFaceCheckElement.classList.add('display-none'); return { error: error?.message || 'Job failed' }; } } function createDefaultMetadataFile() { const randomSuffix = Math.random().toString(36).slice(2, 10); const fileName = `metadata-${Date.now()}-${randomSuffix}.json`; const defaultContent = `{ "location": "", "violence level": "", "title": "", "social media link": "", "description": "", "category": "" }`; return new File([defaultContent], fileName, { type: 'application/json' }); } async function handleSubmit() { if (isLoading.value) return; data.value = null; outputElement.innerHTML = '

Verification result comes here...

'; if (files.value.length === 0) { createTemplateModal('Please upload media files'); return; } isLoading.value = true; btnSubmit.innerText = 'Processing...'; statusFaceCheckElement.classList.remove('display-none'); const metadataFile = metadata.value[0] || createDefaultMetadataFile(); const formData = new FormData(); formData.append('metadata_file', metadataFile); formData.append('additional_text', (textInputElement?.value || '').trim()); const mediainfo = await new Promise((resolve) => { MediaInfo.mediaInfoFactory({ format: 'JSON' }, resolve); }); let isGetTimed = false; for (const [index, file] of files.value.entries()) { const readChunk = async (chunkSize, offset) => new Uint8Array( await file.slice(offset, offset + chunkSize).arrayBuffer() ); try { if (file.type.startsWith('video/') && !isGetTimed) { const result = await mediainfo.analyzeData(file.size, readChunk); const data = JSON.parse(result); const video = data.media.track.find((t) => t['@type'] === 'Video') || {}; const duration = +video.Duration || 0; const frameRate = +video.FrameRate || 0; const heightV = +video.Height || 0; const widthV = +video.Width || 0; const calcEstimatedTimeCode = Math.ceil( (duration * frameRate * heightV * widthV * 3.17e-7) / 60 ); estTimeStep1 = calcEstimatedTimeCode; isGetTimed = true; } formData.append('files', file); } catch (error) { console.error('Error:', error); } } estTimeStep1 = estTimeStep1 + 5; estTimeStep2 = 5; try { const { result, error } = await runFaceCheckPolling(formData, clientId); if (error || !result) { outputElement.innerText = 'Error: An error occurred while processing your request. Please try again later.'; isLoading.value = false; return; } data.value = result; const menuItems = selectedTab.value === 'verified_evidence' ? `` : ''; outputElement.innerHTML = menuItems + marked.parse( result.readme_content[selectedTab.value]?.content || result.readme_content[selectedTab.value]?.source_details?.content || '' ); estTimeStep1 = 0; estTimeStep2 = 0; isLoading.value = false; } catch (error) { outputElement.innerText = 'Error: An error occurred while processing your request. Please try again later.'; isLoading.value = false; } } function handleHyperLinkImages(text) { return text.replace(/[\[\(]Image\s+([\d,\s]+)[\]\)]/g, (match, numbers) => { const list = numbers .split(',') .map((n) => n.trim()) .filter((n) => n !== ''); const result = list.map((n) => { return `[![Image ${n}]](./media/image-${n}.png)`; }); return result.join(''); }); } async function handleDownload(filename) { if (!data.value) { createTemplateModal('No data to save.'); return; } const dataReport = handleHyperLinkImages(`${data.value.readme_content.case_summary.title}${data.value.readme_content.case_summary.content}${data.value.readme_content.content_classification.title}${data.value.readme_content.content_classification.content}${data.value.readme_content.verified_evidence.title} ${data.value.readme_content.verified_evidence.source_details.title}${data.value.readme_content.verified_evidence.source_details.content}${data.value.readme_content.verified_evidence.where.title}${data.value.readme_content.verified_evidence.where.content}${data.value.readme_content.verified_evidence.when.title}${data.value.readme_content.verified_evidence.when.content}${data.value.readme_content.verified_evidence.who.title}${data.value.readme_content.verified_evidence.who.content}${data.value.readme_content.verified_evidence.why.title}${data.value.readme_content.verified_evidence.why.content}${data.value.readme_content.forensic_analysis.title}${data.value.readme_content.forensic_analysis.content}${data.value.readme_content.other_evidence.title}${data.value.readme_content.other_evidence.content} `); const mediaItems = Array.isArray(data.value.readme_content.media) ? data.value.readme_content.media : []; if (mediaItems.length === 0) { const blob = new Blob([dataReport], { type: 'text/markdown', }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); return; } if (typeof JSZip === 'undefined') { createTemplateModal( 'Unable to prepare the download. Please check your connection and try again.' ); return; } const zip = new JSZip(); zip.file(filename, dataReport); // Package each available media asset alongside the markdown report mediaItems.forEach((raw, index) => { if (typeof raw !== 'string' || !raw.trim()) { return; } const base64Data = raw.replace(/\s+/g, ''); zip.file(`media/image-${index + 1}.png`, base64Data, { base64: true }); }); try { const archiveBlob = await zip.generateAsync({ type: 'blob' }); const baseName = filename ? filename.replace(/\.[^/.]+$/, '') : 'report'; const archiveName = `${baseName || 'report'}-assets.zip`; const url = URL.createObjectURL(archiveBlob); const a = document.createElement('a'); a.href = url; a.download = archiveName; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } catch (error) { console.error('Failed to generate download archive', error); createTemplateModal( 'Unable to prepare the download. Please try again later.' ); } } window.goToEvidence = function () { if (selectedTab.value === 'other_evidence') return; document.querySelectorAll('.menu .item').forEach((item) => { item.classList.remove('active'); }); document .getElementById('other_evidence-1763023210526') .classList.add('active'); selectedTab.value = 'other_evidence'; }; window.previewImage = function (imageRaw) { const match = imageRaw.match(/\d+/); if (!match) return; const index = Number(match[0]) - 1; const image = data.value.readme_content.media[index]; if (!image) return; createTemplateModal( `Image Preview`, `Image ${index + 1}` ); }; window.changeTabChildren = function (tab, element) { if (isLoading.value) return; document.querySelectorAll('.menu-children .item').forEach((item) => { item.classList.remove('active'); }); element.classList.add('active'); selectedTabChildren.value = tab; }; window.changeTab = function (tab, element) { if (isLoading.value) return; document.querySelectorAll('.menu .item').forEach((item) => { item.classList.remove('active'); }); element.classList.add('active'); selectedTab.value = tab; }; window.removeFile = function (event, fileId, fileType) { event.preventDefault(); event.stopPropagation(); if (isLoading.value) return; const [fileName, fileSize, fileLastModified] = fileId.split('|'); if (fileType === 'metadata') { metadata.value = metadata.value.filter( (file) => !( file.name === fileName && file.size.toString() === fileSize && file.lastModified.toString() === fileLastModified ) ); return; } files.value = files.value.filter( (file) => !( file.name === fileName && file.size.toString() === fileSize && file.lastModified.toString() === fileLastModified ) ); }; function createTemplateModal(content, title = 'Notification') { const modal = `
${title}
${closeIcon}
${content}
`; openModal(modal); } window.previewMetadata = function (event, fileId) { event.preventDefault(); event.stopPropagation(); const [fileName, fileSize, fileLastModified] = fileId.split('|'); const file = metadata.value.find( (f) => f.name === fileName && f.size.toString() === fileSize && f.lastModified.toString() === fileLastModified ); if (!file) return; const reader = new FileReader(); reader.onload = function (e) { const content = `
${e.target.result}
`; const modal = createTemplateModal(content, file.name); }; reader.readAsText(file); }; window.previewFile = function (event, fileId) { event.preventDefault(); event.stopPropagation(); const [fileName, fileSize, fileLastModified] = fileId.split('|'); const file = files.value.find( (f) => f.name === fileName && f.size.toString() === fileSize && f.lastModified.toString() === fileLastModified ); if (!file) return; const fileURL = URL.createObjectURL(file); currentPreviewURL = fileURL; let content = ''; if (file.type === 'video/quicktime') { content = 'The system does not support preview for .mov files.'; } else if (file.type.startsWith('image/')) { content = `${file.name}`; } else if (file.type.startsWith('video/')) { content = ``; } else { content = 'Preview not available for this file type.'; } createTemplateModal(content, file.name); }; function openModal(content) { contentModalElement.innerHTML = content; wrapperModalElement.style.display = 'flex'; } window.closeModal = function () { wrapperModalElement.style.display = 'none'; const video = contentModalElement.querySelector('video'); if (video) { video.pause(); video.currentTime = 0; } if (currentPreviewURL) { URL.revokeObjectURL(currentPreviewURL); currentPreviewURL = null; } };