Avinashnalla7 commited on
Commit
884f006
·
verified ·
1 Parent(s): de765ce

Update src/App.jsx

Browse files
Files changed (1) hide show
  1. src/App.jsx +119 -6
src/App.jsx CHANGED
@@ -28,12 +28,21 @@ const TABLE_ANCHORS = [
28
  { key: "qty", expected_text: "Qty" },
29
  ];
30
 
 
 
31
  const TABLE_COLUMNS = [
32
  { key: "item_number", label: "Item Number" },
 
33
  { key: "description", label: "Description" },
34
  { key: "qty", label: "Qty" },
 
 
 
35
  ];
36
 
 
 
 
37
  /** ---------- normalization helpers ---------- */
38
 
39
  function normBBox(b, w, h) {
@@ -156,6 +165,10 @@ export default function App() {
156
 
157
  // NEW: template id comes from URL OR manual input
158
  const [templateId, setTemplateId] = useState("");
 
 
 
 
159
 
160
  const [pageSize, setPageSize] = useState({ w: 0, h: 0 });
161
  const [selectedFieldId, setSelectedFieldId] = useState(FIELDS[0].fieldId);
@@ -249,6 +262,10 @@ export default function App() {
249
  const tid = sp.get("template_id");
250
  setTemplateId((tid || "").trim());
251
 
 
 
 
 
252
  if (!id) {
253
  setPdfUrl(null);
254
  setPdfName(null);
@@ -471,12 +488,13 @@ export default function App() {
471
  return applyOffset(st.anchor, st.valueOffsetPx);
472
  }
473
 
474
- function buildConfig() {
 
475
  if (!pageSize.w || !pageSize.h) return null;
476
 
477
  return {
478
  form_id: pdfId ? `trainer_${pdfId}` : "trainer_form_v1",
479
- version: 3,
480
  page: 1,
481
  scalar_value_region_mode: offsetMode ? "offset_from_anchor_v1" : "absolute_bbox_v1",
482
  fields: FIELDS.map((f) => {
@@ -564,11 +582,55 @@ export default function App() {
564
  };
565
  }
566
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
567
  const previewConfig = useMemo(
568
  () => buildConfig(),
569
- [pageSize.w, pageSize.h, boxes, offsetMode, rowHeightHintPx, pdfId]
 
570
  );
571
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
572
  async function saveConfig() {
573
  if (!pdfId) {
574
  setSendStatus({ state: "err", msg: "Missing pdf_id in URL." });
@@ -652,7 +714,7 @@ export default function App() {
652
  )}
653
  </div>
654
 
655
- {/* NEW: Template ID */}
656
  <div style={{ marginBottom: 14 }}>
657
  <div style={UI.label}>Template ID (required)</div>
658
  <input
@@ -666,6 +728,44 @@ export default function App() {
666
  </div>
667
  </div>
668
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
669
  {/* Zoom / Pan controls */}
670
  <div style={{ marginBottom: 14 }}>
671
  <div style={UI.label}>View (Zoom / Pan)</div>
@@ -830,7 +930,7 @@ export default function App() {
830
  ))}
831
  </select>
832
  <div style={{ fontSize: 12, color: UI.subtle, marginTop: 6 }}>
833
- Columns should be drawn inside the table region.
834
  </div>
835
  </div>
836
  )}
@@ -881,6 +981,19 @@ export default function App() {
881
  {sendStatus.state === "sending" ? "Saving..." : "Save Config"}
882
  </button>
883
 
 
 
 
 
 
 
 
 
 
 
 
 
 
884
  {sendStatus.state !== "idle" && (
885
  <div
886
  style={{
@@ -1088,7 +1201,7 @@ export default function App() {
1088
  >
1089
  <h3 style={{ margin: "0 0 10px", fontSize: 18 }}>Config Preview</h3>
1090
  <div style={{ fontSize: 12, color: "#9aa4b2", marginBottom: 10 }}>
1091
- Live preview of what will be saved.
1092
  </div>
1093
 
1094
  <pre
 
28
  { key: "qty", expected_text: "Qty" },
29
  ];
30
 
31
+ // Canonical line-item column set. Keep this in sync with the backend
32
+ // (prompts.py + generate_template_schema_skeletons.py).
33
  const TABLE_COLUMNS = [
34
  { key: "item_number", label: "Item Number" },
35
+ { key: "lot_number", label: "Lot Number" },
36
  { key: "description", label: "Description" },
37
  { key: "qty", label: "Qty" },
38
+ { key: "price", label: "Price" },
39
+ { key: "total", label: "Total" },
40
+ { key: "bundle", label: "Bundle" }, // NEW: bundle column
41
  ];
42
 
43
+ // Schema version (matches T10 "version_schema")
44
+ const SCHEMA_VERSION = 3;
45
+
46
  /** ---------- normalization helpers ---------- */
47
 
48
  function normBBox(b, w, h) {
 
165
 
166
  // NEW: template id comes from URL OR manual input
167
  const [templateId, setTemplateId] = useState("");
168
+ // NEW: 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("");
172
 
173
  const [pageSize, setPageSize] = useState({ w: 0, h: 0 });
174
  const [selectedFieldId, setSelectedFieldId] = useState(FIELDS[0].fieldId);
 
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
+
269
  if (!id) {
270
  setPdfUrl(null);
271
  setPdfName(null);
 
488
  return applyOffset(st.anchor, st.valueOffsetPx);
489
  }
490
 
491
+ // Build ONLY the schema portion (flat) — fields + table mapping.
492
+ function buildSchema() {
493
  if (!pageSize.w || !pageSize.h) return null;
494
 
495
  return {
496
  form_id: pdfId ? `trainer_${pdfId}` : "trainer_form_v1",
497
+ version_schema: SCHEMA_VERSION,
498
  page: 1,
499
  scalar_value_region_mode: offsetMode ? "offset_from_anchor_v1" : "absolute_bbox_v1",
500
  fields: FIELDS.map((f) => {
 
582
  };
583
  }
584
 
585
+ // Build the FULL template object in the registry format (matches T10_*.json).
586
+ function buildConfig() {
587
+ const schema = buildSchema();
588
+ if (!schema) return null;
589
+
590
+ const tid = (templateId || "").trim();
591
+ const kwAny = (matchKeywordsAny || "")
592
+ .split(",")
593
+ .map((s) => s.trim())
594
+ .filter(Boolean);
595
+
596
+ return {
597
+ template_id: tid || "UNSET_TEMPLATE_ID",
598
+ name: (templateName || "").trim() || tid || "Untitled Template",
599
+ status: "active",
600
+ version: Number(templateVersion) || 1,
601
+ match: {
602
+ keywords_all: [],
603
+ keywords_any: kwAny,
604
+ },
605
+ // flat schema (same level as T10)
606
+ ...schema,
607
+ };
608
+ }
609
+
610
  const previewConfig = useMemo(
611
  () => buildConfig(),
612
+ // eslint-disable-next-line react-hooks/exhaustive-deps
613
+ [pageSize.w, pageSize.h, boxes, offsetMode, rowHeightHintPx, pdfId, templateId, templateName, templateVersion, matchKeywordsAny]
614
  );
615
 
616
+ function downloadTemplateJson() {
617
+ const cfg = buildConfig();
618
+ if (!cfg) {
619
+ setSendStatus({ state: "err", msg: "Config not ready. Load a PDF first." });
620
+ return;
621
+ }
622
+ const tid = (templateId || "").trim() || "template";
623
+ const blob = new Blob([JSON.stringify(cfg, null, 2)], { type: "application/json" });
624
+ const url = URL.createObjectURL(blob);
625
+ const a = document.createElement("a");
626
+ a.href = url;
627
+ a.download = `${tid}.json`;
628
+ document.body.appendChild(a);
629
+ a.click();
630
+ document.body.removeChild(a);
631
+ URL.revokeObjectURL(url);
632
+ }
633
+
634
  async function saveConfig() {
635
  if (!pdfId) {
636
  setSendStatus({ state: "err", msg: "Missing pdf_id in URL." });
 
714
  )}
715
  </div>
716
 
717
+ {/* Template ID */}
718
  <div style={{ marginBottom: 14 }}>
719
  <div style={UI.label}>Template ID (required)</div>
720
  <input
 
728
  </div>
729
  </div>
730
 
731
+ {/* Template Name */}
732
+ <div style={{ marginBottom: 14 }}>
733
+ <div style={UI.label}>Template Name</div>
734
+ <input
735
+ style={UI.input}
736
+ value={templateName}
737
+ onChange={(e) => setTemplateName(e.target.value)}
738
+ placeholder="e.g. Zavation Sales Order Form"
739
+ />
740
+ </div>
741
+
742
+ {/* Match keywords */}
743
+ <div style={{ marginBottom: 14 }}>
744
+ <div style={UI.label}>Match keywords (comma separated)</div>
745
+ <input
746
+ style={UI.input}
747
+ value={matchKeywordsAny}
748
+ onChange={(e) => setMatchKeywordsAny(e.target.value)}
749
+ placeholder="e.g. zavation, sales order, purchase order"
750
+ />
751
+ <div style={{ fontSize: 12, color: UI.subtle, marginTop: 6 }}>
752
+ Stored as <code>match.keywords_any</code> for classification.
753
+ </div>
754
+ </div>
755
+
756
+ {/* Template version */}
757
+ <div style={{ marginBottom: 14 }}>
758
+ <div style={UI.label}>Template Version</div>
759
+ <input
760
+ style={UI.input}
761
+ type="number"
762
+ min="1"
763
+ step="1"
764
+ value={templateVersion}
765
+ onChange={(e) => setTemplateVersion(Number(e.target.value || 1))}
766
+ />
767
+ </div>
768
+
769
  {/* Zoom / Pan controls */}
770
  <div style={{ marginBottom: 14 }}>
771
  <div style={UI.label}>View (Zoom / Pan)</div>
 
930
  ))}
931
  </select>
932
  <div style={{ fontSize: 12, color: UI.subtle, marginTop: 6 }}>
933
+ Columns should be drawn inside the table region. (Includes <b>Bundle</b>.)
934
  </div>
935
  </div>
936
  )}
 
981
  {sendStatus.state === "sending" ? "Saving..." : "Save Config"}
982
  </button>
983
 
984
+ {/* DOWNLOAD as <template_id>.json in registry format */}
985
+ <button
986
+ onClick={downloadTemplateJson}
987
+ disabled={!pdfUrl}
988
+ style={{
989
+ ...UI.btnBase,
990
+ ...(pdfUrl ? UI.blue : { background: "rgba(255,255,255,0.10)", color: "#aaa" }),
991
+ marginTop: 10,
992
+ }}
993
+ >
994
+ Download Template JSON
995
+ </button>
996
+
997
  {sendStatus.state !== "idle" && (
998
  <div
999
  style={{
 
1201
  >
1202
  <h3 style={{ margin: "0 0 10px", fontSize: 18 }}>Config Preview</h3>
1203
  <div style={{ fontSize: 12, color: "#9aa4b2", marginBottom: 10 }}>
1204
+ Live preview of what will be saved (registry template format).
1205
  </div>
1206
 
1207
  <pre