/* global React, PaperCard, Button, Chip, WaxSeal, StageChip, ProgressBar, LogLine, Icons,
deriveStages, stageStatuses, overallProgress, chipFor */
// Palimpsest — Mobile workshop view (real API)
const { useState: useStateM } = React;
function MobileWorkshop(props) {
const {
state, page, total, currentStage, log, jobId, file, pageCount,
cfg, setCfg, cost, wsConnected, hasPdf, errorMsg,
onPick, onClear, onStart, flash, canStart, uploading,
} = props;
const [sheetOpen, setSheetOpen] = useStateM(false);
const inputRef = React.useRef(null);
const sizeMB = file ? (file.size / 1048576).toFixed(1) : null;
return (
Drop your old scan, get a clean scientific document back.
¶
palimpsest · a manuscript page scraped clean and written
over, with traces of the original still showing through.
read more ↗
{/* Dropzone */}
!file && inputRef.current?.click()}
style={{ cursor: file ? 'default' : 'pointer' }}
>
e.target.files[0] && onPick(e.target.files[0])}
/>
{file ? 'staged · ready' : 'tap to browse a PDF'}
{file ? 'tap start below' : 'scanned · max 60 MB'}
{file && (
✓
{file.name}
{pageCount ? `${pageCount} p · ` : ''}{sizeMB} MB
staged
)}
{/* Config summary -> bottom sheet */}
setSheetOpen(true)}>
SPECIMEN
{(window.COSTS[cfg.model]?.label || cfg.model)}
{cfg.ocr}
{cost != null ? `≈ $${cost.toFixed(2)}` : '— · pick a PDF'}
tap ›
{/* Inline terminal section */}
{state !== 'idle' && (
palimpsest@workshop ~/jobs/{jobId || '—'}
{file?.name || '(resumed)'}
{jobId &&
#{jobId}}
{chipFor(state, page, total)}
{(() => {
const s = deriveStages(state, currentStage);
const t = stageStatuses(state, currentStage, page, total);
return ['preprocess', 'ocr', 'rewrite', 'compile'].map((n, i) => (
));
})()}
{(() => {
const p = overallProgress(state, currentStage, page, total);
return (
// progress
{p.label} · {p.sub}
);
})()}
# pipeline.log
{state === 'processing' ? 'live' : state === 'done' ? 'complete' : 'halted'}
{log.slice(-12).map((l, i) => )}
{state === 'done' &&
}
{state === 'processing' && (
↳ outputs unlock at compile
)}
{state === 'done' && (
<>
{hasPdf && (
download pdf
)}
.tex
Overleaf
>
)}
{state === 'error' && (
<>
{jobId && (
.tex if any
)}
↳ {errorMsg || 'halted'}
>
)}
)}
{/* Sticky bottom CTA — only when idle and no in-flight */}
{!sheetOpen && state === 'idle' && (
)}
{/* Bottom sheet — specimen config */}
{sheetOpen && (
<>
setSheetOpen(false)} />
Specimen · pipeline settings
MODEL
OCR ENGINE
{['vision','mathpix'].map(o => (
))}
SOURCE
{['preserve images','text only'].map(o => (
))}
ESTIMATED COST
≈ {cost != null ? `$${cost.toFixed(2)}` : '—'}
per run
>
)}
);
}
Object.assign(window, { MobileWorkshop });