Empty search help; Biography wording
OCR enrich UX; handwriting / vision flag
Line 2,275: Line 2,275:
$panel.append($banner);
$panel.append($banner);
} else {
} else {
var engineBit = job.ocr_engine ? (' · Engine: ' + job.ocr_engine) : '';
$panel.append($('<p>', { 'class': 'ocr-fact-meta' }).text(
$panel.append($('<p>', { 'class': 'ocr-fact-meta' }).text(
(job.doc_type_label || 'Document') +
(job.doc_type_label || 'Document') +
' · OCR quality ' + (job.ocr_quality || 0) + '%' +
' · OCR quality ' + (job.ocr_quality || 0) + '%' +
engineBit +
(job.error ? (' · Error: ' + job.error) : '')
(job.error ? (' · Error: ' + job.error) : '')
));
));
if (job.ocr_engine_notes && job.ocr_engine_notes.length) {
$panel.append($('<p>', { 'class': 'ocr-fact-meta' }).text(
job.ocr_engine_notes.slice(0, 3).join(' · ')
));
}
}
}


Line 2,462: Line 2,469:
if (isMultiPersonJob(job) && !job.bound_title) {
if (isMultiPersonJob(job) && !job.bound_title) {
$forceMulti.hide();
$forceMulti.hide();
}
var $forceVision = $('<label>', { 'class': 'ocr-force-vision' }).append(
$('<input>', { type: 'checkbox', checked: mwApiBool(job.force_vision) }),
' Handwriting / hard scan (LLM vision; rebuild re-runs OCR)'
);
if (!cfg.llmVision) {
$forceVision.append(document.createTextNode(' — needs RITCHIE_LLM_API_KEY on OCR service'));
}
}
var $forceId = $('<label>', { 'class': 'ocr-force-identity' }).append(
var $forceId = $('<label>', { 'class': 'ocr-force-identity' }).append(
Line 2,476: Line 2,490:
$applyNotes, $('<br>'),
$applyNotes, $('<br>'),
$forceMulti, $('<br>'),
$forceMulti, $('<br>'),
$forceVision, $('<br>'),
$forceId
$forceId
);
);


var $save = $('<button>', { type: 'button', text: 'Save draft' });
var $save = $('<button>', { type: 'button', text: 'Save draft' });
var $rebuild = $('<button>', { type: 'button', text: 'Rebuild from pages / roster mode' });
var $rebuild = $('<button>', { type: 'button', text: 'Rebuild facts / re-OCR if handwriting checked' });
if (pages.length < 2 && !job.bound_title) {
$rebuild.hide();
}
$advBody.append($('<div>', { 'class': 'ocr-advanced-actions' }).append($save, $rebuild));
$advBody.append($('<div>', { 'class': 'ocr-advanced-actions' }).append($save, $rebuild));
$adv.append($advBody);
$adv.append($advBody);
Line 2,567: Line 2,579:
force_identity: $forceId.find('input').prop('checked'),
force_identity: $forceId.find('input').prop('checked'),
force_multi: $forceMulti.find('input').prop('checked'),
force_multi: $forceMulti.find('input').prop('checked'),
force_vision: $forceVision.find('input').prop('checked'),
apply_notes: $applyNotes.find('input').prop('checked')
apply_notes: $applyNotes.find('input').prop('checked')
};
};
Line 2,769: Line 2,782:
bound_title: boundTitle || '',
bound_title: boundTitle || '',
force_multi: opts.forceMulti ? true : false,
force_multi: opts.forceMulti ? true : false,
force_vision: opts.forceVision ? true : false,
file: file,
file: file,
format: 'json'
format: 'json'
Line 2,815: Line 2,829:
var $section = $('<div>', { 'class': 'soldier-contrib-section soldier-contrib-ocr' });
var $section = $('<div>', { 'class': 'soldier-contrib-section soldier-contrib-ocr' });
$section.append($('<p>', { 'class': 'soldier-contrib-heading' }).append(
$section.append($('<p>', { 'class': 'soldier-contrib-heading' }).append(
$('<strong>').text('OCR a document')
$('<strong>').text('OCR a document to enrich this profile')
));
));
$section.append($('<p>', { 'class': 'soldier-contrib-help' }).text(
$section.append($('<p>', { 'class': 'soldier-contrib-help' }).text(
'Scan or photo of a form, letter, or roster. Proposed facts appear here for review — nothing is written until Apply.'
'Scan a form, letter, certificate, clipping, or roster. Review proposes infobox facts and a short biography draft — nothing is written until Apply. Team rosters can update many soldiers at once.'
));
));
var $file = $('<input>', {
var $file = $('<input>', {
Line 2,828: Line 2,842:
$('<input>', { type: 'checkbox' }),
$('<input>', { type: 'checkbox' }),
' This is a multi-person roster (not only this soldier)'
' This is a multi-person roster (not only this soldier)'
);
var $visionMode = $('<label>', { 'class': 'ocr-force-multi-upload' }).append(
$('<input>', { type: 'checkbox' }),
' Handwriting / hard scan (needs LLM vision key)'
);
);
var $btn = $('<button>', { type: 'button', text: 'Upload & OCR' });
var $btn = $('<button>', { type: 'button', text: 'Upload & OCR' });
var $host = $('<div>', { 'class': 'ocr-review-host' });
var $host = $('<div>', { 'class': 'ocr-review-host' });
$section.append($file, $rosterMode, $btn, $host);
$section.append($file, $rosterMode, $visionMode, $btn, $host);
$btn.on('click', function () {
$btn.on('click', function () {
startUpload($file, pageTitle, $host, cfg, {
startUpload($file, pageTitle, $host, cfg, {
forceMulti: $rosterMode.find('input').prop('checked')
forceMulti: $rosterMode.find('input').prop('checked'),
forceVision: $visionMode.find('input').prop('checked')
});
});
});
});
Line 2,857: Line 2,876:
$up.append($('<h3>').text('Upload a document'));
$up.append($('<h3>').text('Upload a document'));
$up.append($('<p>').text(
$up.append($('<p>').text(
'For a roster, review is a table: who is on the scan, which wiki page, and what would be written on that profile (usually Team). Rank, role, and ASN are matching cues, not automatic edits.'
'Enrich soldier profiles from scans: propose infobox facts, a short biography draft, and Sources. For multi-person IPW rosters, review is a table (usually Team membership). Rank, role, and ASN are matching cues until you approve.'
));
));
var $file2 = $('<input>', { type: 'file', accept: '.jpg,.jpeg,.png,.gif,.webp,.tif,.tiff,.pdf' });
var $file2 = $('<input>', { type: 'file', accept: '.jpg,.jpeg,.png,.gif,.webp,.tif,.tiff,.pdf' });
var $vision2 = $('<label>', { 'class': 'ocr-force-multi-upload' }).append(
$('<input>', { type: 'checkbox' }),
' Handwriting / hard scan (LLM vision)'
);
var $btn2 = $('<button>', { type: 'button', text: 'Upload & OCR' });
var $btn2 = $('<button>', { type: 'button', text: 'Upload & OCR' });
var $host2 = $('<div>', { 'class': 'ocr-review-host' });
var $host2 = $('<div>', { 'class': 'ocr-review-host' });
$up.append($file2, ' ', $btn2, $host2);
$up.append($file2, $vision2, ' ', $btn2, $host2);
$btn2.on('click', function () {
$btn2.on('click', function () {
startUpload($file2, '', $host2, cfg);
startUpload($file2, '', $host2, cfg, {
forceVision: $vision2.find('input').prop('checked')
});
});
});
$slot.append($up);
$slot.append($up);