Spaces:
Sleeping
Sleeping
Update src/App.jsx
Browse files- src/App.jsx +86 -9
src/App.jsx
CHANGED
|
@@ -158,14 +158,14 @@ function isTableField(field) {
|
|
| 158 |
const API_BASE = import.meta.env.VITE_API_BASE || "http://localhost:8000";
|
| 159 |
|
| 160 |
export default function App() {
|
| 161 |
-
// PDF comes from pdf_id in URL
|
| 162 |
const [pdfId, setPdfId] = useState(null);
|
| 163 |
const [pdfUrl, setPdfUrl] = useState(null);
|
| 164 |
const [pdfName, setPdfName] = useState(null);
|
| 165 |
|
| 166 |
-
//
|
| 167 |
const [templateId, setTemplateId] = useState("");
|
| 168 |
-
//
|
| 169 |
const [templateName, setTemplateName] = useState("");
|
| 170 |
const [templateVersion, setTemplateVersion] = useState(1);
|
| 171 |
const [matchKeywordsAny, setMatchKeywordsAny] = useState("");
|
|
@@ -193,6 +193,9 @@ export default function App() {
|
|
| 193 |
const dragStartRef = useRef(null);
|
| 194 |
const overlayRef = useRef(null);
|
| 195 |
|
|
|
|
|
|
|
|
|
|
| 196 |
const [showPreview, setShowPreview] = useState(true);
|
| 197 |
|
| 198 |
const [sendStatus, setSendStatus] = useState({ state: "idle", msg: "" }); // idle|sending|ok|err
|
|
@@ -228,14 +231,22 @@ export default function App() {
|
|
| 228 |
|
| 229 |
// Spacebar handling for pan
|
| 230 |
useEffect(() => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
function onKeyDown(e) {
|
| 232 |
if (e.code === "Space") {
|
|
|
|
| 233 |
e.preventDefault();
|
| 234 |
setSpaceDown(true);
|
| 235 |
}
|
| 236 |
}
|
| 237 |
function onKeyUp(e) {
|
| 238 |
if (e.code === "Space") {
|
|
|
|
| 239 |
e.preventDefault();
|
| 240 |
setSpaceDown(false);
|
| 241 |
setIsPanning(false);
|
|
@@ -254,15 +265,12 @@ export default function App() {
|
|
| 254 |
useEffect(() => {
|
| 255 |
const sp = new URLSearchParams(window.location.search);
|
| 256 |
|
| 257 |
-
console.log("URL", window.location.href);
|
| 258 |
-
console.log("query", Object.fromEntries(sp.entries()));
|
| 259 |
const id = sp.get("pdf_id") || sp.get("id") || sp.get("pdfId") || sp.get("pdf");
|
| 260 |
setPdfId(id || null);
|
| 261 |
|
| 262 |
const tid = sp.get("template_id");
|
| 263 |
setTemplateId((tid || "").trim());
|
| 264 |
|
| 265 |
-
// Optional metadata via URL
|
| 266 |
const tname = sp.get("template_name");
|
| 267 |
if (tname) setTemplateName(tname.trim());
|
| 268 |
|
|
@@ -305,6 +313,53 @@ export default function App() {
|
|
| 305 |
};
|
| 306 |
}, []);
|
| 307 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 308 |
const selectedField = useMemo(
|
| 309 |
() => FIELDS.find((f) => f.fieldId === selectedFieldId),
|
| 310 |
[selectedFieldId]
|
|
@@ -633,7 +688,7 @@ export default function App() {
|
|
| 633 |
|
| 634 |
async function saveConfig() {
|
| 635 |
if (!pdfId) {
|
| 636 |
-
setSendStatus({ state: "err", msg: "Missing pdf_id
|
| 637 |
return;
|
| 638 |
}
|
| 639 |
const tid = (templateId || "").trim();
|
|
@@ -709,11 +764,33 @@ export default function App() {
|
|
| 709 |
</div>
|
| 710 |
{!pdfId && (
|
| 711 |
<div style={{ marginTop: 10, fontSize: 12, color: "#ffb4b4" }}>
|
| 712 |
-
|
| 713 |
</div>
|
| 714 |
)}
|
| 715 |
</div>
|
| 716 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 717 |
{/* Template ID */}
|
| 718 |
<div style={{ marginBottom: 14 }}>
|
| 719 |
<div style={UI.label}>Template ID (required)</div>
|
|
@@ -1049,7 +1126,7 @@ export default function App() {
|
|
| 1049 |
<div style={{ color: "#999", lineHeight: 1.4 }}>
|
| 1050 |
No PDF loaded.
|
| 1051 |
<br />
|
| 1052 |
-
|
| 1053 |
<br />
|
| 1054 |
<code style={{ color: "#ddd" }}>...?pdf_id=xxxx&template_id=T1_...</code>
|
| 1055 |
</div>
|
|
|
|
| 158 |
const API_BASE = import.meta.env.VITE_API_BASE || "http://localhost:8000";
|
| 159 |
|
| 160 |
export default function App() {
|
| 161 |
+
// PDF comes from pdf_id in URL OR a local upload
|
| 162 |
const [pdfId, setPdfId] = useState(null);
|
| 163 |
const [pdfUrl, setPdfUrl] = useState(null);
|
| 164 |
const [pdfName, setPdfName] = useState(null);
|
| 165 |
|
| 166 |
+
// template id comes from URL OR manual input
|
| 167 |
const [templateId, setTemplateId] = useState("");
|
| 168 |
+
// template metadata so the exported config matches the registry template format
|
| 169 |
const [templateName, setTemplateName] = useState("");
|
| 170 |
const [templateVersion, setTemplateVersion] = useState(1);
|
| 171 |
const [matchKeywordsAny, setMatchKeywordsAny] = useState("");
|
|
|
|
| 193 |
const dragStartRef = useRef(null);
|
| 194 |
const overlayRef = useRef(null);
|
| 195 |
|
| 196 |
+
const fileInputRef = useRef(null);
|
| 197 |
+
const localUrlRef = useRef(null);
|
| 198 |
+
|
| 199 |
const [showPreview, setShowPreview] = useState(true);
|
| 200 |
|
| 201 |
const [sendStatus, setSendStatus] = useState({ state: "idle", msg: "" }); // idle|sending|ok|err
|
|
|
|
| 231 |
|
| 232 |
// Spacebar handling for pan
|
| 233 |
useEffect(() => {
|
| 234 |
+
// Don't hijack the spacebar while the user is typing in a field.
|
| 235 |
+
function isTypingTarget(e) {
|
| 236 |
+
const t = e.target;
|
| 237 |
+
const tag = t && t.tagName ? t.tagName.toLowerCase() : "";
|
| 238 |
+
return tag === "input" || tag === "textarea" || tag === "select" || (t && t.isContentEditable);
|
| 239 |
+
}
|
| 240 |
function onKeyDown(e) {
|
| 241 |
if (e.code === "Space") {
|
| 242 |
+
if (isTypingTarget(e)) return; // allow typing spaces in inputs
|
| 243 |
e.preventDefault();
|
| 244 |
setSpaceDown(true);
|
| 245 |
}
|
| 246 |
}
|
| 247 |
function onKeyUp(e) {
|
| 248 |
if (e.code === "Space") {
|
| 249 |
+
if (isTypingTarget(e)) return;
|
| 250 |
e.preventDefault();
|
| 251 |
setSpaceDown(false);
|
| 252 |
setIsPanning(false);
|
|
|
|
| 265 |
useEffect(() => {
|
| 266 |
const sp = new URLSearchParams(window.location.search);
|
| 267 |
|
|
|
|
|
|
|
| 268 |
const id = sp.get("pdf_id") || sp.get("id") || sp.get("pdfId") || sp.get("pdf");
|
| 269 |
setPdfId(id || null);
|
| 270 |
|
| 271 |
const tid = sp.get("template_id");
|
| 272 |
setTemplateId((tid || "").trim());
|
| 273 |
|
|
|
|
| 274 |
const tname = sp.get("template_name");
|
| 275 |
if (tname) setTemplateName(tname.trim());
|
| 276 |
|
|
|
|
| 313 |
};
|
| 314 |
}, []);
|
| 315 |
|
| 316 |
+
// ---- Load a local PDF directly (no email link / no pdf_id needed) ----
|
| 317 |
+
async function onPickLocalPdf(e) {
|
| 318 |
+
const file = e.target.files && e.target.files[0];
|
| 319 |
+
if (!file) return;
|
| 320 |
+
|
| 321 |
+
// revoke previous local URL
|
| 322 |
+
if (localUrlRef.current) {
|
| 323 |
+
try { URL.revokeObjectURL(localUrlRef.current); } catch (_) {}
|
| 324 |
+
localUrlRef.current = null;
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
const objectUrl = URL.createObjectURL(file);
|
| 328 |
+
localUrlRef.current = objectUrl;
|
| 329 |
+
|
| 330 |
+
// show it + reset view/measurements
|
| 331 |
+
setPdfUrl(objectUrl);
|
| 332 |
+
setPdfName(file.name);
|
| 333 |
+
setPageSize({ w: 0, h: 0 });
|
| 334 |
+
setScale(1);
|
| 335 |
+
setPan({ x: 0, y: 0 });
|
| 336 |
+
|
| 337 |
+
// give it an id so Save Config / form_id work
|
| 338 |
+
const localId = `local_${Date.now()}`;
|
| 339 |
+
setPdfId(localId);
|
| 340 |
+
|
| 341 |
+
setSendStatus({ state: "ok", msg: `Loaded ${file.name}. (Download Template JSON works offline.)` });
|
| 342 |
+
|
| 343 |
+
// best-effort: also upload to the API so "Save Config" can attach the PDF server-side.
|
| 344 |
+
try {
|
| 345 |
+
const fd = new FormData();
|
| 346 |
+
fd.append("file", file, file.name);
|
| 347 |
+
fd.append("pdf_name", file.name);
|
| 348 |
+
const resp = await fetch(`${API_BASE}/api/pdf/${encodeURIComponent(localId)}`, {
|
| 349 |
+
method: "POST",
|
| 350 |
+
body: fd,
|
| 351 |
+
});
|
| 352 |
+
if (resp.ok) {
|
| 353 |
+
setSendStatus({ state: "ok", msg: `Loaded ${file.name} and uploaded to API as ${localId}.` });
|
| 354 |
+
}
|
| 355 |
+
} catch (_) {
|
| 356 |
+
// offline / CORS — local preview + Download still work fine
|
| 357 |
+
}
|
| 358 |
+
|
| 359 |
+
// allow re-selecting the same file later
|
| 360 |
+
if (fileInputRef.current) fileInputRef.current.value = "";
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
const selectedField = useMemo(
|
| 364 |
() => FIELDS.find((f) => f.fieldId === selectedFieldId),
|
| 365 |
[selectedFieldId]
|
|
|
|
| 688 |
|
| 689 |
async function saveConfig() {
|
| 690 |
if (!pdfId) {
|
| 691 |
+
setSendStatus({ state: "err", msg: "Missing pdf_id. Upload a PDF or open via the pipeline link." });
|
| 692 |
return;
|
| 693 |
}
|
| 694 |
const tid = (templateId || "").trim();
|
|
|
|
| 764 |
</div>
|
| 765 |
{!pdfId && (
|
| 766 |
<div style={{ marginTop: 10, fontSize: 12, color: "#ffb4b4" }}>
|
| 767 |
+
No PDF yet. Upload one below, or open this page using the pipeline link from the email.
|
| 768 |
</div>
|
| 769 |
)}
|
| 770 |
</div>
|
| 771 |
|
| 772 |
+
{/* Upload a local PDF directly */}
|
| 773 |
+
<div style={{ marginBottom: 14 }}>
|
| 774 |
+
<div style={UI.label}>Upload PDF (no link needed)</div>
|
| 775 |
+
<input
|
| 776 |
+
ref={fileInputRef}
|
| 777 |
+
type="file"
|
| 778 |
+
accept="application/pdf,.pdf"
|
| 779 |
+
onChange={onPickLocalPdf}
|
| 780 |
+
style={{ display: "none" }}
|
| 781 |
+
/>
|
| 782 |
+
<button
|
| 783 |
+
type="button"
|
| 784 |
+
onClick={() => fileInputRef.current && fileInputRef.current.click()}
|
| 785 |
+
style={{ ...UI.btnBase, ...UI.blue }}
|
| 786 |
+
>
|
| 787 |
+
Upload PDF
|
| 788 |
+
</button>
|
| 789 |
+
<div style={{ fontSize: 12, color: UI.subtle, marginTop: 6 }}>
|
| 790 |
+
Loads a PDF from your computer to train on. No email link required.
|
| 791 |
+
</div>
|
| 792 |
+
</div>
|
| 793 |
+
|
| 794 |
{/* Template ID */}
|
| 795 |
<div style={{ marginBottom: 14 }}>
|
| 796 |
<div style={UI.label}>Template ID (required)</div>
|
|
|
|
| 1126 |
<div style={{ color: "#999", lineHeight: 1.4 }}>
|
| 1127 |
No PDF loaded.
|
| 1128 |
<br />
|
| 1129 |
+
Click <b>Upload PDF</b> on the left, or open using a pipeline link:
|
| 1130 |
<br />
|
| 1131 |
<code style={{ color: "#ddd" }}>...?pdf_id=xxxx&template_id=T1_...</code>
|
| 1132 |
</div>
|