@akropolys/kiku 1.5.15 → 1.5.18

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.js CHANGED
@@ -1542,6 +1542,18 @@ var getFriendlyError = (err) => {
1542
1542
  return str;
1543
1543
  }
1544
1544
  };
1545
+ var MK_ADJ = ["flying", "silent", "golden", "lucky", "brave", "swift", "royal", "cosmic"];
1546
+ var MK_NOUN = ["thunder", "falcon", "river", "ember", "tiger", "comet", "willow", "onyx"];
1547
+ function generateMagicWord() {
1548
+ return MK_ADJ[Math.floor(Math.random() * MK_ADJ.length)] + MK_NOUN[Math.floor(Math.random() * MK_NOUN.length)];
1549
+ }
1550
+ function parseMemoryKey(raw) {
1551
+ const cleaned = raw.trim().replace(/\s+/g, "");
1552
+ const m = cleaned.match(/^(\d{6,})([a-zA-Z].*)?$/);
1553
+ const phone = m?.[1] || cleaned.replace(/\D/g, "");
1554
+ const word = (m?.[2] || "").toLowerCase() || generateMagicWord();
1555
+ return { phone, word };
1556
+ }
1545
1557
  function parseThinking(text) {
1546
1558
  const openMatch = text.match(/<\s*thinking\s*>/i);
1547
1559
  if (!openMatch) {
@@ -1683,17 +1695,34 @@ function ChatModal({
1683
1695
  const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
1684
1696
  const recognition = new SR();
1685
1697
  recognition.lang = "en-US";
1686
- recognition.interimResults = false;
1698
+ recognition.interimResults = true;
1687
1699
  recognition.maxAlternatives = 1;
1688
1700
  recognitionRef.current = recognition;
1689
1701
  recognition.onstart = () => setVoiceState("listening");
1690
1702
  recognition.onresult = (event) => {
1691
- const transcript = event.results[0][0].transcript;
1692
- setInput(transcript);
1693
- pendingVoiceRef.current = transcript;
1694
- setVoiceState("processing");
1703
+ let finalText = "";
1704
+ let interimText = "";
1705
+ for (let i = 0; i < event.results.length; i++) {
1706
+ const seg = event.results[i][0].transcript;
1707
+ if (event.results[i].isFinal) finalText += seg;
1708
+ else interimText += seg;
1709
+ }
1710
+ const live = (finalText + " " + interimText).trim();
1711
+ if (live) setInput(live);
1712
+ if (finalText.trim()) {
1713
+ pendingVoiceRef.current = finalText.trim();
1714
+ setVoiceState("processing");
1715
+ }
1716
+ };
1717
+ recognition.onerror = (event) => {
1718
+ const err = event?.error;
1719
+ if (err === "not-allowed" || err === "service-not-allowed") {
1720
+ setInput("Microphone access was blocked \u2014 enable it in your browser to use voice.");
1721
+ } else if (err === "no-speech") {
1722
+ setInput("Didn't catch that \u2014 tap the mic and try again.");
1723
+ }
1724
+ setVoiceState("idle");
1695
1725
  };
1696
- recognition.onerror = () => setVoiceState("idle");
1697
1726
  recognition.onend = () => {
1698
1727
  setVoiceState((prev) => prev === "listening" ? "idle" : prev);
1699
1728
  };
@@ -1721,6 +1750,7 @@ function ChatModal({
1721
1750
  });
1722
1751
  const [merchantRef, setMerchantRef] = (0, import_react5.useState)(null);
1723
1752
  const [paymentPhase, setPaymentPhase] = (0, import_react5.useState)("idle");
1753
+ const [memoryKey, setMemoryKey] = (0, import_react5.useState)(null);
1724
1754
  const [sessions, setSessions] = (0, import_react5.useState)(() => loadSessions());
1725
1755
  const [sidebarOpen, setSidebarOpen] = (0, import_react5.useState)(false);
1726
1756
  const [replayMessages, setReplayMessages] = (0, import_react5.useState)(null);
@@ -1756,26 +1786,14 @@ function ChatModal({
1756
1786
  } : void 0;
1757
1787
  const handlePhoneSubmit = async () => {
1758
1788
  if (!phoneInput.trim()) return;
1759
- try {
1760
- if (typeof window !== "undefined") {
1761
- localStorage.setItem("akropolys_user_phone", phoneInput.trim());
1762
- }
1763
- const isHistoryIntent = lastIntent === "capture" || lastIntent === "delete" || lastIntent === "view_history";
1764
- if (isHistoryIntent) {
1765
- setPaymentPhase("idle");
1766
- const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
1767
- if (lastUserMsg) {
1768
- await send(lastUserMsg.content);
1769
- }
1770
- return;
1771
- }
1772
- const res = await client.api.initiatePayment(phoneInput.trim());
1773
- setMerchantRef(res.merchantReference);
1774
- setPaymentPhase("awaiting");
1775
- } catch (e) {
1776
- console.error("[Akropolys] initiatePayment error", e);
1777
- setPaymentPhase("failed");
1778
- }
1789
+ const { phone, word } = parseMemoryKey(phoneInput);
1790
+ if (!phone) return;
1791
+ if (typeof window !== "undefined") localStorage.setItem("akropolys_user_phone", phone);
1792
+ client.setShopperIdentity(phone, word);
1793
+ setMemoryKey(phone + word);
1794
+ setPaymentPhase("idle");
1795
+ const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
1796
+ if (lastUserMsg) await send(lastUserMsg.content);
1779
1797
  };
1780
1798
  const msgsContainerRef = (0, import_react5.useRef)(null);
1781
1799
  (0, import_react5.useEffect)(() => {
@@ -2064,31 +2082,41 @@ function ChatModal({
2064
2082
  ] })
2065
2083
  ] }),
2066
2084
  error && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-error", children: getFriendlyError(error) }),
2067
- !replayMessages && paymentPhase === "prompt_phone" && (() => {
2068
- const isHistoryIntent = lastIntent === "capture" || lastIntent === "delete" || lastIntent === "view_history";
2069
- if (isHistoryIntent) {
2070
- return null;
2071
- }
2072
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hsk-cb-ai-msg", children: [
2073
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-ai-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SparkleIcon2, {}) }),
2074
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-ai-body", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-ai-text", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hsk-cb-phone-form", children: [
2075
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("label", { className: "hsk-cb-phone-label", children: "Enter your M-Pesa number to pay" }),
2076
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2077
- "input",
2078
- {
2079
- type: "tel",
2080
- className: "hsk-cb-phone-input",
2081
- placeholder: "0712 345 678",
2082
- value: phoneInput,
2083
- onChange: (e) => setPhoneInput(e.target.value.replace(/\D/g, "")),
2084
- onKeyDown: (e) => e.key === "Enter" && handlePhoneSubmit(),
2085
- autoFocus: true
2085
+ !replayMessages && paymentPhase === "prompt_phone" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hsk-cb-ai-msg", children: [
2086
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-ai-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SparkleIcon2, {}) }),
2087
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-ai-body", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-ai-text", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hsk-cb-phone-form", children: [
2088
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("label", { className: "hsk-cb-phone-label", children: "Your phone number \u2014 or paste your memory key" }),
2089
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2090
+ "input",
2091
+ {
2092
+ type: "text",
2093
+ className: "hsk-cb-phone-input",
2094
+ placeholder: "0712345678 or 0712345678flyingthunder",
2095
+ value: phoneInput,
2096
+ onChange: (e) => setPhoneInput(e.target.value.replace(/[^0-9a-zA-Z]/g, "")),
2097
+ onKeyDown: (e) => e.key === "Enter" && handlePhoneSubmit(),
2098
+ autoFocus: true
2099
+ }
2100
+ ),
2101
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { className: "hsk-cb-phone-submit", onClick: handlePhoneSubmit, children: "Save & sync my items" })
2102
+ ] }) }) })
2103
+ ] }),
2104
+ !replayMessages && memoryKey && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hsk-cb-ai-msg", children: [
2105
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-ai-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SparkleIcon2, {}) }),
2106
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-ai-body", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-ai-text", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { padding: "10px 12px", border: "1px solid var(--hsk-border, #e5e5e5)", borderRadius: 8 }, children: [
2107
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { fontSize: 12, fontWeight: 600, marginBottom: 4 }, children: "\u{1F511} Your memory key" }),
2108
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("code", { style: { display: "block", fontSize: 14, fontWeight: 700, marginBottom: 6, wordBreak: "break-all" }, children: memoryKey }),
2109
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
2110
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { className: "hsk-cb-phone-submit", style: { padding: "4px 10px" }, onClick: () => {
2111
+ try {
2112
+ navigator.clipboard?.writeText(memoryKey);
2113
+ } catch {
2086
2114
  }
2087
- ),
2088
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { className: "hsk-cb-phone-submit", onClick: handlePhoneSubmit, children: "Send STK push" })
2089
- ] }) }) })
2090
- ] });
2091
- })(),
2115
+ }, children: "Copy" }),
2116
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: { fontSize: 11, opacity: 0.7 }, children: "Save it \u2014 enter it on any site to get your items back." })
2117
+ ] })
2118
+ ] }) }) })
2119
+ ] }),
2092
2120
  !replayMessages && paymentPhase === "awaiting" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hsk-cb-payment-prompt hsk-cb-payment-prompt--awaiting", children: [
2093
2121
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-payment-pulse-ring" }),
2094
2122
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-payment-icon-wrap", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: { fontSize: "2rem" }, children: "\u{1F4F1}" }) }),
@@ -2185,7 +2213,7 @@ function ChatModal({
2185
2213
  value: input,
2186
2214
  onChange: handleInput,
2187
2215
  onKeyDown: handleKeyDown,
2188
- placeholder: activePlaceholder,
2216
+ placeholder: voiceState === "listening" ? "\u{1F399}\uFE0F Listening\u2026 tap the mic to stop" : voiceState === "processing" ? "Got it \u2014 sending\u2026" : activePlaceholder,
2189
2217
  rows: 1,
2190
2218
  disabled: loading,
2191
2219
  autoFocus: true