@akropolys/kiku 1.5.7 → 1.5.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -768,61 +768,46 @@ import { usePaymentPolling } from "@akropolys/sdk";
768
768
  import { useAkropolysContext as useAkropolysContext3 } from "@akropolys/sdk";
769
769
 
770
770
  // src/components/ComparisonMatrix.tsx
771
+ import { resolveDisplayFields } from "@akropolys/sdk";
771
772
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
772
- function extractSize(p) {
773
- const name = p.name;
774
- const cat = (p.category || "").toLowerCase();
775
- const mExplicit = name.match(/(\d+(?:\.\d+)?)\s*(?:inch(?:es)?|["'″])/i);
776
- if (mExplicit) {
777
- return `${mExplicit[1]} inches`;
773
+ function normalizeKey(key) {
774
+ let s = key.replace(/[_-]+/g, " ");
775
+ s = s.replace(/([a-z])([A-Z])/g, "$1 $2");
776
+ return s.split(" ").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
777
+ }
778
+ function getBaseGroup(normalized) {
779
+ const norm = normalized.toLowerCase().trim();
780
+ if (norm.startsWith("salary") || norm.startsWith("pay") || norm.startsWith("wage")) {
781
+ return "Salary";
778
782
  }
779
- if (cat.includes("tv") || cat.includes("audio")) {
780
- const mTv = name.match(/\b(\d{2})(?:[a-zA-Z]|\b)/);
781
- if (mTv) {
782
- const size = parseInt(mTv[1], 10);
783
- if (size >= 24 && size <= 120) {
784
- return `${size} inches`;
785
- }
786
- }
783
+ if (norm.startsWith("price") || norm.startsWith("cost") || norm.startsWith("rate")) {
784
+ return "Price";
787
785
  }
788
- return null;
789
- }
790
- function extractResolution(name) {
791
- if (/\b4K\b/i.test(name)) return "4K Ultra HD (2160p)";
792
- if (/\bUHD\b/i.test(name)) return "Ultra HD (2160p)";
793
- if (/\b(?:Full HD|FHD|1080p)\b/i.test(name)) return "Full HD (1080p)";
794
- if (/\b(?:HD|720p)\b/i.test(name)) return "HD (720p)";
795
- return null;
796
- }
797
- function extractStorage(name) {
798
- const m = name.match(/(\d+)\s*GB(?!\s*RAM)/i);
799
- return m ? `${m[1]} GB` : null;
800
- }
801
- function extractRAM(name) {
802
- const m = name.match(/(\d+)\s*GB\s*RAM/i);
803
- return m ? `${m[1]} GB` : null;
804
- }
805
- function extractCamera(name) {
806
- const m = name.match(/(\d+)\s*MP/i);
807
- return m ? `${m[1]} MP` : null;
808
- }
809
- function extractBattery(name) {
810
- const m = name.match(/(\d{3,5})\s*mAh/i);
811
- return m ? `${m[1]} mAh` : null;
786
+ if (norm.startsWith("location") || norm.startsWith("address") || norm.startsWith("city")) {
787
+ return "Location";
788
+ }
789
+ if (norm.startsWith("image") || norm.startsWith("photo") || norm.startsWith("pic") || norm.startsWith("thumb")) {
790
+ return "Image";
791
+ }
792
+ if (norm.startsWith("title") || norm.startsWith("name") || norm.startsWith("label") || norm.startsWith("heading")) {
793
+ return "Title";
794
+ }
795
+ return normalized;
812
796
  }
813
- function buildRows(products, currency) {
797
+ function buildRows(products, displayConfig, defaultCurrency = "KES") {
814
798
  const rows = [];
799
+ const resolved = products.map((p) => resolveDisplayFields(p.fields || p, displayConfig));
815
800
  rows.push({
816
801
  label: "Product Preview",
817
- values: products.map((s) => s.image || null),
802
+ values: resolved.map((r) => r.image || null),
818
803
  type: "image"
819
804
  });
820
- const prices = products.map((s) => {
821
- const n = parseFloat(String(s.price ?? "").replace(/[^0-9.]/g, ""));
805
+ const prices = resolved.map((r) => {
806
+ const n = parseFloat(String(r.price ?? "").replace(/[^0-9.]/g, ""));
822
807
  return isNaN(n) ? null : n;
823
808
  });
824
- const priceLabels = products.map((s, i) => {
825
- const c = s.currency || currency;
809
+ const priceLabels = products.map((p, i) => {
810
+ const c = p.fields?.currency || p.currency || defaultCurrency;
826
811
  const n = prices[i];
827
812
  return n !== null ? `${c} ${n.toLocaleString("en-KE", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` : null;
828
813
  });
@@ -834,38 +819,101 @@ function buildRows(products, currency) {
834
819
  type: "price",
835
820
  bestIdx: minPrice !== null ? prices.indexOf(minPrice) : void 0
836
821
  });
837
- const brands = products.map((s) => s.brand || null);
838
- if (brands.some(Boolean)) rows.push({ label: "Brand", values: brands });
839
- const specDefs = [
840
- { label: "Display Size", fn: extractSize },
841
- { label: "Resolution", fn: (p) => extractResolution(p.name), higherIsBetter: true },
842
- { label: "Storage", fn: (p) => extractStorage(p.name), higherIsBetter: true },
843
- { label: "RAM", fn: (p) => extractRAM(p.name), higherIsBetter: true },
844
- { label: "Camera", fn: (p) => extractCamera(p.name), higherIsBetter: true },
845
- { label: "Battery", fn: (p) => extractBattery(p.name), higherIsBetter: true }
822
+ const keysToExclude = /* @__PURE__ */ new Set([
823
+ "url",
824
+ "fields",
825
+ "id",
826
+ "score",
827
+ "currency",
828
+ "status",
829
+ "indexed_at",
830
+ "indexedAt"
831
+ ]);
832
+ if (displayConfig) {
833
+ Object.values(displayConfig).forEach((v) => {
834
+ if (v) keysToExclude.add(v);
835
+ });
836
+ }
837
+ const commonKeys = [
838
+ "title",
839
+ "name",
840
+ "label",
841
+ "headline",
842
+ "subject",
843
+ "job_title",
844
+ "listing_title",
845
+ "common_name",
846
+ "product_name",
847
+ "image",
848
+ "images",
849
+ "thumbnail",
850
+ "photo",
851
+ "cover",
852
+ "featured_image",
853
+ "hero_image",
854
+ "listing_image",
855
+ "logo",
856
+ "price",
857
+ "cost",
858
+ "listingPrice",
859
+ "rate",
860
+ "fee",
861
+ "startingFrom",
862
+ "brand",
863
+ "category",
864
+ "location",
865
+ "type",
866
+ "variety",
867
+ "make"
846
868
  ];
847
- const resOrder = ["4K Ultra HD (2160p)", "Ultra HD (2160p)", "Full HD (1080p)", "HD (720p)"];
848
- for (const { label, fn, higherIsBetter } of specDefs) {
849
- const vals = products.map((s) => fn(s));
850
- if (!vals.some(Boolean)) continue;
851
- let bestIdx;
852
- if (higherIsBetter && vals.filter(Boolean).length > 1) {
853
- if (label === "Resolution") {
854
- bestIdx = vals.reduce((best, v, i) => {
855
- const rank = resOrder.indexOf(v ?? "");
856
- const bestRank = resOrder.indexOf(vals[best] ?? "");
857
- return rank !== -1 && (bestRank === -1 || rank < bestRank) ? i : best;
858
- }, 0);
859
- } else {
860
- const nums = vals.map((v) => parseFloat((v ?? "").replace(/[^0-9.]/g, "")));
861
- const max = Math.max(...nums.filter((n) => !isNaN(n)));
862
- bestIdx = nums.indexOf(max);
869
+ commonKeys.forEach((k) => keysToExclude.add(k));
870
+ const allFieldKeys = /* @__PURE__ */ new Set();
871
+ products.forEach((p) => {
872
+ const f = p.fields || p;
873
+ if (f) {
874
+ Object.keys(f).forEach((k) => {
875
+ if (!keysToExclude.has(k)) {
876
+ allFieldKeys.add(k);
877
+ }
878
+ });
879
+ }
880
+ });
881
+ const groupedKeys = /* @__PURE__ */ new Map();
882
+ allFieldKeys.forEach((k) => {
883
+ const norm = normalizeKey(k);
884
+ const base = getBaseGroup(norm);
885
+ if (!groupedKeys.has(base)) {
886
+ groupedKeys.set(base, []);
887
+ }
888
+ groupedKeys.get(base).push(k);
889
+ });
890
+ const sortedGroups = Array.from(groupedKeys.keys()).sort();
891
+ sortedGroups.forEach((group) => {
892
+ const originalKeys = groupedKeys.get(group);
893
+ const values = products.map((p) => {
894
+ const f = p.fields || p;
895
+ if (!f) return null;
896
+ for (const k of originalKeys) {
897
+ if (f[k] !== void 0 && f[k] !== null) {
898
+ if (typeof f[k] === "object") {
899
+ return JSON.stringify(f[k]);
900
+ }
901
+ return String(f[k]);
902
+ }
863
903
  }
904
+ return null;
905
+ });
906
+ if (values.some((v) => v !== null)) {
907
+ rows.push({
908
+ label: group,
909
+ values,
910
+ type: "text"
911
+ });
864
912
  }
865
- rows.push({ label, values: vals, bestIdx });
866
- }
913
+ });
867
914
  const avail = products.map((s) => {
868
- const a = s.availability ?? "";
915
+ const f = s.fields || s;
916
+ const a = f.availability || "";
869
917
  if (!a) return null;
870
918
  if (/in.?stock/i.test(a)) return "In-Stock";
871
919
  if (/out.?of.?stock/i.test(a)) return "Out of Stock";
@@ -874,7 +922,10 @@ function buildRows(products, currency) {
874
922
  if (avail.some(Boolean)) {
875
923
  rows.push({ label: "Availability", values: avail, type: "availability" });
876
924
  }
877
- const cats = products.map((s) => s.category || null);
925
+ const cats = products.map((s) => {
926
+ const f = s.fields || s;
927
+ return f.category || null;
928
+ });
878
929
  if (cats.some(Boolean)) rows.push({ label: "Category", values: cats });
879
930
  return rows;
880
931
  }
@@ -914,16 +965,15 @@ function AvailabilityCell({ value }) {
914
965
  value
915
966
  ] });
916
967
  }
917
- function ComparisonMatrix({ sources, defaultCurrency = "KES" }) {
918
- if (sources.length < 2) return null;
968
+ function ComparisonMatrix({ sources, defaultCurrency = "KES", displayConfig }) {
969
+ if (!sources || sources.length < 2) return null;
919
970
  const products = sources.slice(0, 3);
920
- const rows = buildRows(products, defaultCurrency);
971
+ const rows = buildRows(products, displayConfig, defaultCurrency);
921
972
  const colTemplate = `140px repeat(${products.length}, 1fr)`;
922
973
  const labelStyle = {
923
974
  padding: "10px 12px",
924
975
  fontSize: 11,
925
976
  fontWeight: 700,
926
- // Solid dark fallback — never inherit a muted ancestor color
927
977
  color: "var(--hsk-text-muted, #4b5563)",
928
978
  textTransform: "uppercase",
929
979
  letterSpacing: "0.05em",
@@ -936,7 +986,6 @@ function ComparisonMatrix({ sources, defaultCurrency = "KES" }) {
936
986
  const cellBase = {
937
987
  padding: "10px 14px",
938
988
  fontSize: 13,
939
- // Explicit color so cells are always readable regardless of parent theme
940
989
  color: "var(--hsk-text, #111827)",
941
990
  borderBottom: "1px solid var(--hsk-border, rgba(0,0,0,0.07))",
942
991
  verticalAlign: "middle",
@@ -958,27 +1007,30 @@ function ComparisonMatrix({ sources, defaultCurrency = "KES" }) {
958
1007
  children: [
959
1008
  /* @__PURE__ */ jsxs5("div", { style: { display: "grid", gridTemplateColumns: colTemplate, background: "var(--hsk-surface2, #f9fafb)", borderBottom: "2px solid var(--hsk-border, rgba(0,0,0,0.09))" }, children: [
960
1009
  /* @__PURE__ */ jsx6("div", { style: { ...labelStyle, borderBottom: "none", color: "var(--hsk-text, #111)", fontSize: 12 }, children: "Feature" }),
961
- products.map((p, i) => /* @__PURE__ */ jsx6(
962
- "a",
963
- {
964
- href: p.url || "#",
965
- target: "_blank",
966
- rel: "noopener noreferrer",
967
- style: {
968
- display: "flex",
969
- alignItems: "center",
970
- padding: "10px 14px",
971
- fontSize: 12,
972
- fontWeight: 700,
973
- color: "var(--hsk-primary, #16a34a)",
974
- textDecoration: "none",
975
- lineHeight: 1.3,
976
- borderLeft: i > 0 ? "1px solid var(--hsk-border, rgba(0,0,0,0.07))" : "none"
1010
+ products.map((p, i) => {
1011
+ const { title } = resolveDisplayFields(p.fields || p, displayConfig);
1012
+ return /* @__PURE__ */ jsx6(
1013
+ "a",
1014
+ {
1015
+ href: p.url || "#",
1016
+ target: "_blank",
1017
+ rel: "noopener noreferrer",
1018
+ style: {
1019
+ display: "flex",
1020
+ alignItems: "center",
1021
+ padding: "10px 14px",
1022
+ fontSize: 12,
1023
+ fontWeight: 700,
1024
+ color: "var(--hsk-primary, #16a34a)",
1025
+ textDecoration: "none",
1026
+ lineHeight: 1.3,
1027
+ borderLeft: i > 0 ? "1px solid var(--hsk-border, rgba(0,0,0,0.07))" : "none"
1028
+ },
1029
+ children: title
977
1030
  },
978
- children: p.name
979
- },
980
- i
981
- ))
1031
+ i
1032
+ );
1033
+ })
982
1034
  ] }),
983
1035
  rows.map((row, rowIdx) => /* @__PURE__ */ jsxs5(
984
1036
  "div",
@@ -993,8 +1045,9 @@ function ComparisonMatrix({ sources, defaultCurrency = "KES" }) {
993
1045
  products.map((p, i) => {
994
1046
  const val = row.values[i];
995
1047
  const isBest = row.bestIdx === i && row.values.filter(Boolean).length > 1;
1048
+ const { title } = resolveDisplayFields(p.fields || p, displayConfig);
996
1049
  if (row.type === "image") {
997
- return /* @__PURE__ */ jsx6("div", { style: { ...cellBase, justifyContent: "center", padding: "12px", borderLeft: i > 0 ? "1px solid var(--hsk-border, rgba(0,0,0,0.07))" : "none" }, children: /* @__PURE__ */ jsx6(ImageCell, { value: val, name: p.name }) }, i);
1050
+ return /* @__PURE__ */ jsx6("div", { style: { ...cellBase, justifyContent: "center", padding: "12px", borderLeft: i > 0 ? "1px solid var(--hsk-border, rgba(0,0,0,0.07))" : "none" }, children: /* @__PURE__ */ jsx6(ImageCell, { value: val, name: title }) }, i);
998
1051
  }
999
1052
  if (row.type === "availability") {
1000
1053
  return /* @__PURE__ */ jsx6("div", { style: { ...cellBase, borderLeft: i > 0 ? "1px solid var(--hsk-border, rgba(0,0,0,0.07))" : "none" }, children: /* @__PURE__ */ jsx6(AvailabilityCell, { value: val }) }, i);
@@ -1005,7 +1058,6 @@ function ComparisonMatrix({ sources, defaultCurrency = "KES" }) {
1005
1058
  style: {
1006
1059
  ...cellBase,
1007
1060
  fontWeight: isBest ? 700 : 400,
1008
- // Always use a solid dark fallback — never 'inherit' which can be muted/invisible
1009
1061
  color: isBest ? "var(--hsk-primary, #ea580c)" : row.type === "price" ? "var(--hsk-text, #374151)" : "var(--hsk-text, #111827)",
1010
1062
  borderLeft: i > 0 ? "1px solid var(--hsk-border, rgba(0,0,0,0.07))" : "none"
1011
1063
  },
@@ -1454,20 +1506,28 @@ var getFriendlyError = (err) => {
1454
1506
  }
1455
1507
  };
1456
1508
  function parseThinking(text) {
1457
- const openTag = "<thinking>";
1458
- const closeTag = "</thinking>";
1459
- const openIdx = text.indexOf(openTag);
1460
- if (openIdx === -1) {
1509
+ const openMatch = text.match(/<\s*thinking\s*>/i);
1510
+ if (!openMatch) {
1461
1511
  return { thinking: "", content: text, isComplete: true };
1462
1512
  }
1463
- const start = openIdx + openTag.length;
1464
- const closeIdx = text.indexOf(closeTag, start);
1465
- if (closeIdx === -1) {
1466
- return { thinking: text.slice(start), content: "", isComplete: false };
1513
+ const openIdx = openMatch.index ?? 0;
1514
+ const openTagLength = openMatch[0].length;
1515
+ const start = openIdx + openTagLength;
1516
+ const contentBefore = text.slice(0, openIdx);
1517
+ const textAfterOpen = text.slice(start);
1518
+ const closeMatch = textAfterOpen.match(/<\/\s*thinking\s*>/i);
1519
+ if (!closeMatch) {
1520
+ return {
1521
+ thinking: textAfterOpen,
1522
+ content: contentBefore,
1523
+ isComplete: false
1524
+ };
1467
1525
  }
1526
+ const closeIdx = closeMatch.index ?? 0;
1527
+ const closeTagLength = closeMatch[0].length;
1468
1528
  return {
1469
- thinking: text.slice(start, closeIdx),
1470
- content: text.slice(closeIdx + closeTag.length),
1529
+ thinking: textAfterOpen.slice(0, closeIdx),
1530
+ content: contentBefore + textAfterOpen.slice(closeIdx + closeTagLength),
1471
1531
  isComplete: true
1472
1532
  };
1473
1533
  }