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/') ? `
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( `${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 = `