🌉 AI Document Bridge
Drop trade documents → AI extracts PI, invoice, contract fields → Translate Chinese → Export Excel
📋
Drag and drop trade documents here
or click to browse · Supports PDF / DOCX / XLSX / JPEG / PNG
🌐 Translate Chinese fields to English
📊 0 documents extracted
| # | Thumbnail | Document Type | Supplier | Buyer |
Order No. | Date | Products | Total Amount | |
📬 No documents yet — Please drop files above and click extract |
' + esc(f.name) + '';
} else {
var icon = '📄';
if(f.type.indexOf('word') > -1 || f.name.match(/\.docx$/i)) icon = '📝';
else if(f.type.indexOf('sheet') > -1 || f.name.match(/\.xlsx$/i)) icon = '📊';
h += '' + icon + '
' + esc(f.name) + ' ';
}
}
previewRow.innerHTML = h;
}
// ── Remove file ──
function removeFile(i){
files.splice(i, 1);
documents = documents.filter(function(d){ return d.fileIndex !== i; });
for(var j = 0; j < documents.length; j++){
if(documents[j].fileIndex > i) documents[j].fileIndex--;
}
renderPreviews();
renderTable();
if(files.length === 0) extractBtn.disabled = true;
}
// ── Escape HTML ──
function esc(s){
return String(s || '').replace(/&/g,'&').replace(/"/g,'"').replace(//g,'>');
}
// ── Status helpers ──
function showStatus(type, msg){
statusEl.className = 'status show ' + type;
statusEl.textContent = msg;
}
function hideStatus(){
statusEl.className = 'status';
}
// ── AI Extraction prompt ──
var EXTRACT_PROMPT = 'You are a trade document parser. Analyze this document image/PDF. Extract ALL trade-related fields. Rules:\n' +
'1. Identify document type: PI (Proforma Invoice), Commercial Invoice, Purchase Order, Contract, Spec Sheet, or other\n' +
'2. Supplier/manufacturer name (company providing goods)\n' +
'3. Buyer/client name (company purchasing)\n' +
'4. PO number if present\n' +
'5. Document date (YYYY-MM-DD format)\n' +
'6. Products as array: [{name, qty (number), unit_price (number), total (number)}]\n' +
'7. Payment terms (e.g. T/T 30%, L/C, Net 30)\n' +
'8. Shipping terms (e.g. FOB Shenzhen, CIF Hamburg, EXW)\n' +
'9. Total amount (number only, no currency symbol)\n' +
'10. Currency (ISO code: USD, RMB, EUR, HKD, etc.)\n' +
'Return ONLY valid JSON, no markdown, no explanation, no code fences:\n' +
'{"document_type":"...","supplier_name":"...","buyer_name":"...","po_number":"...","date":"...","products":[...],"payment_terms":"...","shipping_terms":"...","total_amount":...,"currency":"..."}';
// ── Translation prompt ──
var TRANSLATE_PROMPT = 'You are a trade document translator. The following JSON was extracted from a trade document that may contain Chinese text. Translate ALL Chinese values to English. Keep the JSON structure exactly the same. Do not change numbers. Return ONLY valid JSON, no markdown, no explanation:\n\n';
// ── Call Gemini API for extraction ──
async function callGeminiExtract(fileObj){
var b64 = fileObj.data;
if(!b64) throw new Error('Unable to read file data');
var mimeType = fileObj.type;
if(!mimeType || mimeType === 'application/octet-stream'){
var ext = fileObj.name.split('.').pop().toLowerCase();
if(ext === 'pdf') mimeType = 'application/pdf';
else if(ext === 'docx') mimeType = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
else if(ext === 'xlsx') mimeType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
else if(ext === 'jpg' || ext === 'jpeg') mimeType = 'image/jpeg';
else if(ext === 'png') mimeType = 'image/png';
else if(ext === 'webp') mimeType = 'image/webp';
else mimeType = 'image/jpeg';
}
var parts = [{text: EXTRACT_PROMPT}];
if(mimeType.startsWith('image/') || mimeType === 'application/pdf'){
parts.push({inlineData: {mimeType: mimeType, data: b64}});
} else {
throw new Error('File type ' + mimeType + ' is not directly supported by Gemini inlineData. Please convert to PDF first for best results.');
}
var imageB64 = null;
for(var pi = 0; pi < parts.length; pi++){
if(parts[pi].inlineData){ imageB64 = parts[pi].inlineData.data; break; }
}
var promptText = '';
for(var pi = 0; pi < parts.length; pi++){
if(parts[pi].text) promptText += parts[pi].text + '\n';
}
var text = await callAI(promptText, imageB64, 0.1, 1024);
text = text.replace(/```json\s*/gi, '').replace(/```/g, '').trim();
// Find first { to last }
var start = text.indexOf('{');
var end = text.lastIndexOf('}');
if(start >= 0 && end > start) text = text.substring(start, end + 1);
return text;
}
// ── Call Gemini for translation ──
async function callGeminiTranslate(jsonStr){
var text = await callAI(TRANSLATE_PROMPT + jsonStr, null, 0.1, 1024);
text = text.replace(/```json\s*/gi, '').replace(/```/g, '').trim();
var start = text.indexOf('{');
var end = text.lastIndexOf('}');
if(start >= 0 && end > start) text = text.substring(start, end + 1);
return text;
}
// ── Extract & Translate ──
extractBtn.addEventListener('click', async function(){
var btn = this;
btn.disabled = true;
btn.textContent = '⏳ Extracting...';
var unscanned = [];
for(var i = 0; i < files.length; i++){
if(!files[i].scanned) unscanned.push(i);
}
if(unscanned.length === 0){
btn.textContent = '🔍 AI Extract & Translate';
btn.disabled = false;
showStatus('success', '✅ All files scanned.');
setTimeout(hideStatus, 3000);
return;
}
for(var k = 0; k < unscanned.length; k++){
var idx = unscanned[k];
var f = files[idx];
showStatus('scanning', '🤖 AI is parsing document ' + (k+1) + '/' + unscanned.length + ': ' + f.name);
try{
// Step 1: Extract
var jsonText = await callGeminiExtract(f);
var doc = JSON.parse(jsonText);
// Step 2: Translate if enabled
if(translateEnabled){
try{
var transText = await callGeminiTranslate(jsonText);
var transDoc = JSON.parse(transText);
// Merge translated fields back
doc = transDoc;
}catch(te){
console.warn('Translation failed, using original extraction results: ', te.message);
// Continue with original extraction
}
}
doc.fileIndex = idx;
doc.thumbnail = f.data;
doc.fileName = f.name;
doc.fileType = f.type;
doc.translated = translateEnabled;
documents.push(doc);
f.scanned = true;
}catch(e){
showStatus('error', '❌ '+f.name+' processing failed: ' + e.message);
documents.push({
fileIndex: idx, thumbnail: f.data, fileName: f.name, fileType: f.type,
document_type: 'Parsing Error', supplier_name: '', buyer_name: '',
po_number: '', date: '', products: [],
payment_terms: e.message, shipping_terms: '', total_amount: 0, currency: 'USD',
translated: false, error: true
});
f.scanned = true;
}
}
renderTable();
btn.textContent = '🔍 AI Extract & Translate';
btn.disabled = false;
if(documents.length > 0) exportBtn.disabled = false;
var errors = documents.filter(function(d){ return d.error; });
if(errors.length > 0){
showStatus('error', '⚠️ ' + documents.length + ' processed, ' + errors.length + ' with errors.');
} else if(unscanned.length > 0){
showStatus('success', '✅ Successfully extracted ' + unscanned.length + ' documents.');
}
setTimeout(hideStatus, 5000);
});
// ── Demo ──
demoBtn.addEventListener('click', function(){
documents = [
{
document_type: 'PI (Proforma Invoice)',
supplier_name: 'Guangzhou Xinsheng Electronics Co., Ltd.',
buyer_name: 'TechGlobal Imports GmbH',
po_number: 'PO-2025-08842',
date: '2025-06-20',
products: [
{name: 'Smart Thermostat Module XS-TH200', qty: 5000, unit_price: 12.80, total: 64000},
{name: 'PCB Assembly Board 4-Layer FR4', qty: 2000, unit_price: 8.50, total: 17000},
{name: 'Capacitor Kit 100pcs', qty: 300, unit_price: 2.40, total: 720},
{name: 'USB-C Connector SMT Type', qty: 10000, unit_price: 0.35, total: 3500}
],
payment_terms: 'T/T 30% deposit, 70% against copy of B/L',
shipping_terms: 'FOB Shenzhen',
total_amount: 85220,
currency: 'USD',
fileIndex: -1,
thumbnail: 'data:image/svg+xml,' + encodeURIComponent(''),
fileName: 'PI_Xinsheng_TechGlobal.pdf',
fileType: 'application/pdf',
translated: false
},
{
document_type: 'Purchase Order',
supplier_name: 'Shenzhen Huaxing Precision Co., Ltd.',
buyer_name: 'NordicAuto Parts AB',
po_number: 'NA-2025-3341',
date: '2025-05-28',
products: [
{name: 'CNC Machined Aluminum Alloy Bracket AM-42', qty: 800, unit_price: 3.25, total: 2600},
{name: 'Stainless Steel Fastener Set M6', qty: 5000, unit_price: 0.18, total: 900}
],
payment_terms: 'L/C at sight',
shipping_terms: 'CIF Gothenburg',
total_amount: 3500,
currency: 'EUR',
fileIndex: -1,
thumbnail: 'data:image/svg+xml,' + encodeURIComponent(''),
fileName: 'PO_NordicAuto_May2025.xlsx',
fileType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
translated: false
}
];
renderTable();
exportBtn.disabled = false;
showStatus('success', '✅ Demo data loaded — 2 sample trade documents (containing Chinese content). Try the translation toggle!');
setTimeout(hideStatus, 4000);
});
// ── Clear all ──
clearBtn.addEventListener('click', function(){
files = [];
documents = [];
renderPreviews();
renderTable();
extractBtn.disabled = true;
exportBtn.disabled = true;
hideStatus();
});
// ── Render table ──
function renderTable(){
// Remove existing rows except empty state
var rows = tableBody.querySelectorAll('tr:not(#emptyRow)');
for(var r = 0; r < rows.length; r++){ rows[r].remove(); }
emptyRow.style.display = documents.length > 0 ? 'none' : '';
for(var i = 0; i < documents.length; i++){
var d = documents[i];
var thumbHtml = '';
if(d.thumbnail && d.fileType && d.fileType.startsWith('image/')){
thumbHtml = '
';
} else {
var icon = '📄';
if(d.fileType && d.fileType.indexOf('word') > -1) icon = '📝';
else if(d.fileType && d.fileType.indexOf('sheet') > -1) icon = '📊';
thumbHtml = '' + icon + '';
}
var prodCount = (d.products && d.products.length) ? d.products.length : 0;
var prodBadge = prodCount > 0
? '' + prodCount + ' items'
: 'None';
var totalDisplay = (d.currency || 'USD') + ' ' + (d.total_amount || 0).toLocaleString('en-US', {minimumFractionDigits: 2});
var errorClass = d.error ? ' style="opacity:0.6"' : '';
var mainRow = document.createElement('tr');
mainRow.innerHTML =
'' + (i+1) + ' | ' +
'' + thumbHtml + ' | ' +
'' + esc(d.document_type || '') + ' | ' +
'' + esc(d.supplier_name || '') + ' | ' +
'' + esc(d.buyer_name || '') + ' | ' +
'' + esc(d.po_number || '') + ' | ' +
'' + esc(d.date || '') + ' | ' +
'' + prodBadge + ' | ' +
'' + totalDisplay + ' | ' +
' | ';
tableBody.appendChild(mainRow);
// Product sub-table
if(prodCount > 0){
var subRow = document.createElement('tr');
subRow.className = 'prod-sub';
subRow.id = 'prodSub' + i;
var prodHtml = '' +
'| # | Product Name | Quantity | Unit Price | Total | ' +
' ';
for(var p = 0; p < d.products.length; p++){
var pr = d.products[p];
prodHtml += '' +
'| ' + (p+1) + ' | ' +
'' + esc(pr.name || '') + ' | ' +
'' + (pr.qty || 0).toLocaleString() + ' | ' +
'' + (d.currency || '') + ' ' + ((pr.unit_price || 0).toFixed(2)) + ' | ' +
'' + (d.currency || '') + ' ' + ((pr.total || 0).toLocaleString('en-US', {minimumFractionDigits: 2})) + ' | ' +
' ';
}
prodHtml += ' ' +
'_
' +
'📋 Payment: ' + esc(d.payment_terms || '—') +
' | 🚢 Shipping: ' + esc(d.shipping_terms || '—') +
' | ';
subRow.innerHTML = prodHtml;
tableBody.appendChild(subRow);
}
}
docCountEl.textContent = documents.length;
if(documents.length === 0){
exportBtn.disabled = true;
}
}
// ── Toggle product sub-table ──
function toggleProducts(i){
var sub = document.getElementById('prodSub' + i);
if(!sub) return;
if(sub.classList.contains('open')){
sub.classList.remove('open');
} else {
sub.classList.add('open');
}
}
// ── Remove document ──
function removeDocument(i){
documents.splice(i, 1);
renderTable();
if(documents.length === 0){
exportBtn.disabled = true;
hideStatus();
}
}
// ── Export Excel (CSV) ──
function exportExcel(){
if(documents.length === 0) return;
var csv = '\uFEFF#,Document Type,Supplier,Buyer,Order Number,Date,Products,Total Amount,Currency,Payment Terms,Shipping Terms\n';
for(var i = 0; i < documents.length; i++){
var d = documents[i];
var prodSummary = '';
if(d.products && d.products.length){
prodSummary = d.products.map(function(p){
return p.name + ' (x' + p.qty + ' @ ' + (p.unit_price || 0).toFixed(2) + ')';
}).join('; ');
}
csv += (i+1) + ',' +
'"' + (d.document_type || '').replace(/"/g,'""') + '",' +
'"' + (d.supplier_name || '').replace(/"/g,'""') + '",' +
'"' + (d.buyer_name || '').replace(/"/g,'""') + '",' +
'"' + (d.po_number || '').replace(/"/g,'""') + '",' +
'"' + (d.date || '').replace(/"/g,'""') + '",' +
'"' + prodSummary.replace(/"/g,'""') + '",' +
'"' + (d.total_amount || 0) + '",' +
'"' + (d.currency || '').replace(/"/g,'""') + '",' +
'"' + (d.payment_terms || '').replace(/"/g,'""') + '",' +
'"' + (d.shipping_terms || '').replace(/"/g,'""') + '"\n';
}
var blob = new Blob([csv], {type: 'text/csv;charset=utf-8'});
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = 'trade_documents_export.csv';
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
showStatus('success', '📥 Exported ' + documents.length + ' documents as CSV.');
setTimeout(hideStatus, 3000);
}
exportBtn.addEventListener('click', exportExcel);