@akropolys/kiku 1.6.3 → 1.6.4

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
@@ -20,7 +20,7 @@ var SearchIcon = () => /* @__PURE__ */ jsxs("svg", { width: "15", height: "15",
20
20
  function SearchBar({
21
21
  placeholder = "Search products\u2026",
22
22
  limit = 10,
23
- debounceMs = 300,
23
+ debounceMs = 150,
24
24
  onSelect,
25
25
  className,
26
26
  inputClassName,
@@ -31,10 +31,8 @@ function SearchBar({
31
31
  }) {
32
32
  const [query, setQuery] = useState("");
33
33
  const [open, setOpen] = useState(false);
34
- const [isDebouncing, setIsDebouncing] = useState(false);
35
- const { results, loading, search, clear } = useSearch();
34
+ const { results, loading, search, clear } = useSearch({ debounceMs });
36
35
  const client = useAkropolysContext();
37
- const timer = useRef();
38
36
  const wrap = useRef(null);
39
37
  const ignoreNextQueryChange = useRef(false);
40
38
  useEffect(() => {
@@ -42,20 +40,13 @@ function SearchBar({
42
40
  ignoreNextQueryChange.current = false;
43
41
  return;
44
42
  }
45
- clearTimeout(timer.current);
46
43
  if (!query.trim()) {
47
44
  clear();
48
45
  setOpen(false);
49
- setIsDebouncing(false);
50
46
  return;
51
47
  }
52
48
  setOpen(true);
53
- setIsDebouncing(true);
54
- timer.current = setTimeout(() => {
55
- setIsDebouncing(false);
56
- search(query, limit);
57
- }, debounceMs);
58
- return () => clearTimeout(timer.current);
49
+ search(query, limit);
59
50
  }, [query]);
60
51
  useEffect(() => {
61
52
  const h = (e) => {
@@ -111,8 +102,8 @@ function SearchBar({
111
102
  }
112
103
  ),
113
104
  showDrop && /* @__PURE__ */ jsxs("div", { className: cn("hsk-sb-drop", classNames.dropdown, dropdownClassName), style: { position: "absolute" }, children: [
114
- (loading || isDebouncing) && /* @__PURE__ */ jsx("div", { className: "hsk-sb-loading-bar" }),
115
- (loading || isDebouncing) && results.length === 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
105
+ loading && /* @__PURE__ */ jsx("div", { className: "hsk-sb-loading-bar" }),
106
+ loading && results.length === 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
116
107
  /* @__PURE__ */ jsxs("div", { className: "hsk-sb-skeleton-row", children: [
117
108
  /* @__PURE__ */ jsx("span", { className: "hsk-sb-skeleton-icon" }),
118
109
  /* @__PURE__ */ jsxs("div", { className: "hsk-sb-row-body", children: [
@@ -128,36 +119,52 @@ function SearchBar({
128
119
  ] })
129
120
  ] })
130
121
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
131
- results.length === 0 && !loading && !isDebouncing && /* @__PURE__ */ jsxs("div", { className: "hsk-sb-empty", children: [
122
+ results.length === 0 && !loading && /* @__PURE__ */ jsxs("div", { className: "hsk-sb-empty", children: [
132
123
  "No results for \u201C",
133
124
  query,
134
125
  "\u201D"
135
126
  ] }),
136
- results.map((r, i) => renderResult ? /* @__PURE__ */ jsx(
137
- "div",
138
- {
139
- onClick: () => handleSelect(r),
140
- className: "hsk-sb-fade",
141
- style: { animationDelay: `${i * 18}ms` },
142
- children: renderResult(r)
143
- },
144
- r.id
145
- ) : /* @__PURE__ */ jsxs(
146
- "div",
147
- {
148
- className: cn("hsk-sb-row hsk-sb-fade", classNames.row),
149
- style: { animationDelay: `${i * 18}ms` },
150
- onClick: () => handleSelect(r),
151
- children: [
152
- /* @__PURE__ */ jsx("span", { className: "hsk-sb-row-icon", children: /* @__PURE__ */ jsx(SearchIcon, {}) }),
153
- /* @__PURE__ */ jsxs("div", { className: "hsk-sb-row-body", children: [
154
- /* @__PURE__ */ jsx("div", { className: "hsk-sb-row-title", children: r.entity.title ?? r.entity.name }),
155
- (r.entity.category || r.entity.brand) && /* @__PURE__ */ jsx("div", { className: "hsk-sb-row-sub", children: r.entity.category ?? r.entity.brand })
156
- ] })
157
- ]
158
- },
159
- r.id
160
- ))
127
+ results.map((r, i) => {
128
+ if (renderResult) {
129
+ return /* @__PURE__ */ jsx(
130
+ "div",
131
+ {
132
+ onClick: () => handleSelect(r),
133
+ className: "hsk-sb-fade",
134
+ style: { animationDelay: `${i * 18}ms` },
135
+ children: renderResult(r)
136
+ },
137
+ r.id
138
+ );
139
+ }
140
+ const thumb = r.entity.image ?? r.entity.thumbnail ?? r.entity.images?.[0];
141
+ return /* @__PURE__ */ jsxs(
142
+ "div",
143
+ {
144
+ className: cn("hsk-sb-row hsk-sb-fade", classNames.row),
145
+ style: { animationDelay: `${i * 18}ms` },
146
+ onClick: () => handleSelect(r),
147
+ children: [
148
+ /* @__PURE__ */ jsx("span", { className: "hsk-sb-row-thumb", children: thumb ? /* @__PURE__ */ jsx(
149
+ "img",
150
+ {
151
+ src: thumb,
152
+ alt: "",
153
+ loading: "lazy",
154
+ onError: (e) => {
155
+ e.currentTarget.style.display = "none";
156
+ }
157
+ }
158
+ ) : /* @__PURE__ */ jsx(SearchIcon, {}) }),
159
+ /* @__PURE__ */ jsxs("div", { className: "hsk-sb-row-body", children: [
160
+ /* @__PURE__ */ jsx("div", { className: "hsk-sb-row-title", children: r.entity.title ?? r.entity.name }),
161
+ (r.entity.category || r.entity.brand) && /* @__PURE__ */ jsx("div", { className: "hsk-sb-row-sub", children: r.entity.category ?? r.entity.brand })
162
+ ] })
163
+ ]
164
+ },
165
+ r.id
166
+ );
167
+ })
161
168
  ] })
162
169
  ] })
163
170
  ] });
@@ -1695,18 +1702,7 @@ var getFriendlyError = (err) => {
1695
1702
  return str;
1696
1703
  }
1697
1704
  };
1698
- var MK_ADJ = ["flying", "silent", "golden", "lucky", "brave", "swift", "royal", "cosmic"];
1699
- var MK_NOUN = ["thunder", "falcon", "river", "ember", "tiger", "comet", "willow", "onyx"];
1700
- function generateMagicWord() {
1701
- return MK_ADJ[Math.floor(Math.random() * MK_ADJ.length)] + MK_NOUN[Math.floor(Math.random() * MK_NOUN.length)];
1702
- }
1703
- function parseMemoryKey(raw) {
1704
- const cleaned = raw.trim().replace(/\s+/g, "");
1705
- const m = cleaned.match(/^(\d{6,})([a-zA-Z].*)?$/);
1706
- const phone = m?.[1] || cleaned.replace(/\D/g, "");
1707
- const word = (m?.[2] || "").toLowerCase() || generateMagicWord();
1708
- return { phone, word };
1709
- }
1705
+ var KIKU_KEY_REVEAL_SECONDS = 60;
1710
1706
  function parseThinking(text) {
1711
1707
  const openMatch = text.match(/<\s*thinking\s*>/i);
1712
1708
  if (!openMatch) {
@@ -1814,6 +1810,7 @@ function ChatModal({
1814
1810
  theme,
1815
1811
  classNames = {},
1816
1812
  enableVoice = false,
1813
+ voiceLang,
1817
1814
  enableVision = false,
1818
1815
  visionCategoryHint
1819
1816
  }) {
@@ -1847,7 +1844,7 @@ function ChatModal({
1847
1844
  if (!hasSpeechAPI || voiceState !== "idle") return;
1848
1845
  const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
1849
1846
  const recognition = new SR();
1850
- recognition.lang = "en-US";
1847
+ recognition.lang = voiceLang || document.documentElement.lang || navigator.language || "en-US";
1851
1848
  recognition.interimResults = true;
1852
1849
  recognition.maxAlternatives = 1;
1853
1850
  recognitionRef.current = recognition;
@@ -1880,7 +1877,7 @@ function ChatModal({
1880
1877
  setVoiceState((prev) => prev === "listening" ? "idle" : prev);
1881
1878
  };
1882
1879
  recognition.start();
1883
- }, [hasSpeechAPI, voiceState]);
1880
+ }, [hasSpeechAPI, voiceState, voiceLang]);
1884
1881
  const stopVoice = useCallback2(() => {
1885
1882
  recognitionRef.current?.stop();
1886
1883
  setVoiceState("idle");
@@ -1895,14 +1892,11 @@ function ChatModal({
1895
1892
  const [selectedProduct, setSelectedProduct] = useState5(null);
1896
1893
  const bottomRef = useRef5(null);
1897
1894
  const textareaRef = useRef5(null);
1898
- const [phoneInput, setPhoneInput] = useState5(() => {
1899
- if (typeof window !== "undefined") {
1900
- return localStorage.getItem("akropolys_user_phone") || "";
1901
- }
1902
- return "";
1903
- });
1904
- const [paymentPhase, setPaymentPhase] = useState5("idle");
1905
- const [memoryKey, setMemoryKey] = useState5(null);
1895
+ const [keyInput, setKeyInput] = useState5("");
1896
+ const [keyPhase, setKeyPhase] = useState5("idle");
1897
+ const [mintedKey, setMintedKey] = useState5(null);
1898
+ const [keyCountdown, setKeyCountdown] = useState5(KIKU_KEY_REVEAL_SECONDS);
1899
+ const [minting, setMinting] = useState5(false);
1906
1900
  const [showKikuPicker, setShowKikuPicker] = useState5(false);
1907
1901
  const [showAtPicker, setShowAtPicker] = useState5(false);
1908
1902
  const [sessions, setSessions] = useState5(() => loadSessions());
@@ -1910,10 +1904,25 @@ function ChatModal({
1910
1904
  const [replayMessages, setReplayMessages] = useState5(null);
1911
1905
  useEffect3(() => {
1912
1906
  if (!lastAction) return;
1913
- if (lastAction.type === "request_phone") {
1914
- setPaymentPhase("prompt_phone");
1907
+ if (lastAction.type === "request_kiku_key") {
1908
+ setKeyPhase("prompt_key");
1915
1909
  }
1916
1910
  }, [lastAction]);
1911
+ useEffect3(() => {
1912
+ if (!mintedKey) return;
1913
+ setKeyCountdown(KIKU_KEY_REVEAL_SECONDS);
1914
+ const t = setInterval(() => {
1915
+ setKeyCountdown((s) => {
1916
+ if (s <= 1) {
1917
+ clearInterval(t);
1918
+ setMintedKey(null);
1919
+ return 0;
1920
+ }
1921
+ return s - 1;
1922
+ });
1923
+ }, 1e3);
1924
+ return () => clearInterval(t);
1925
+ }, [mintedKey]);
1917
1926
  const isStringTheme = typeof theme === "string";
1918
1927
  const hskThemeAttr = isStringTheme ? theme : void 0;
1919
1928
  const customStyles = !isStringTheme && theme ? {
@@ -1923,16 +1932,30 @@ function ChatModal({
1923
1932
  ...theme?.fontFamily && { "--hsk-font": theme.fontFamily },
1924
1933
  ...theme?.borderRadius && { "--hsk-border-radius": theme.borderRadius }
1925
1934
  } : void 0;
1926
- const handlePhoneSubmit = async () => {
1927
- if (!phoneInput.trim()) return;
1928
- const { phone, word } = parseMemoryKey(phoneInput);
1929
- if (!phone) return;
1930
- if (typeof window !== "undefined") localStorage.setItem("akropolys_user_phone", phone);
1931
- client.setShopperIdentity(phone, word);
1932
- setMemoryKey(phone + word);
1933
- setPaymentPhase("idle");
1935
+ const retryLastMessage = async () => {
1934
1936
  const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
1935
- if (lastUserMsg) await send(lastUserMsg.content);
1937
+ if (lastUserMsg) await handleSend(lastUserMsg.content);
1938
+ };
1939
+ const handleUseExistingKey = async () => {
1940
+ const key = keyInput.trim();
1941
+ if (!key) return;
1942
+ client.setKikuKey(key);
1943
+ setKeyInput("");
1944
+ setKeyPhase("idle");
1945
+ await retryLastMessage();
1946
+ };
1947
+ const handleCreateKey = async () => {
1948
+ if (minting) return;
1949
+ setMinting(true);
1950
+ try {
1951
+ const key = await client.mintKikuKey();
1952
+ setMintedKey(key);
1953
+ setKeyPhase("idle");
1954
+ await retryLastMessage();
1955
+ } catch {
1956
+ } finally {
1957
+ setMinting(false);
1958
+ }
1936
1959
  };
1937
1960
  const msgsContainerRef = useRef5(null);
1938
1961
  useEffect3(() => {
@@ -1967,7 +1990,7 @@ function ChatModal({
1967
1990
  saveCurrentSession();
1968
1991
  reset();
1969
1992
  setReplayMessages(null);
1970
- setPaymentPhase("idle");
1993
+ setKeyPhase("idle");
1971
1994
  }, [reset, saveCurrentSession]);
1972
1995
  const handleSourceClick = (src) => {
1973
1996
  setSelectedProduct(src);
@@ -2220,7 +2243,7 @@ function ChatModal({
2220
2243
  ] });
2221
2244
  })(),
2222
2245
  isLast && lastIntent === "compare" && sources.length >= 2 && !replayMessages && /* @__PURE__ */ jsx7(ComparisonMatrix, { sources, defaultCurrency }),
2223
- isLast && referencedIds.length > 0 && lastIntent !== "compare" && lastAction?.type !== "request_phone" && lastAction?.type !== "awaiting_payment" && lastAction?.type !== "checkout" && lastAction?.type !== "capture" && lastAction?.type !== "delete" && lastAction?.type !== "view_history" && !msg.cartSnapshot && /* @__PURE__ */ jsx7(
2246
+ isLast && referencedIds.length > 0 && lastIntent !== "compare" && lastAction?.type !== "request_kiku_key" && lastAction?.type !== "awaiting_payment" && lastAction?.type !== "checkout" && lastAction?.type !== "capture" && lastAction?.type !== "delete" && lastAction?.type !== "view_history" && !msg.cartSnapshot && /* @__PURE__ */ jsx7(
2224
2247
  SourcesCarousel,
2225
2248
  {
2226
2249
  sources,
@@ -2293,38 +2316,45 @@ function ChatModal({
2293
2316
  ] })
2294
2317
  ] }),
2295
2318
  error && /* @__PURE__ */ jsx7("div", { className: "hsk-cb-error", children: getFriendlyError(error) }),
2296
- !replayMessages && paymentPhase === "prompt_phone" && /* @__PURE__ */ jsxs6("div", { className: "hsk-cb-ai-msg", children: [
2319
+ !replayMessages && keyPhase === "prompt_key" && /* @__PURE__ */ jsxs6("div", { className: "hsk-cb-ai-msg", children: [
2297
2320
  /* @__PURE__ */ jsx7("div", { className: "hsk-cb-ai-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx7(SparkleIcon2, {}) }),
2298
2321
  /* @__PURE__ */ jsx7("div", { className: "hsk-cb-ai-body", children: /* @__PURE__ */ jsx7("div", { className: "hsk-cb-ai-text", children: /* @__PURE__ */ jsxs6("div", { className: "hsk-cb-phone-form", children: [
2299
- /* @__PURE__ */ jsx7("label", { className: "hsk-cb-phone-label", children: "Your phone number \u2014 or paste your memory key" }),
2322
+ /* @__PURE__ */ jsx7("label", { className: "hsk-cb-phone-label", children: "Paste your kiku key \u2014 or create one" }),
2300
2323
  /* @__PURE__ */ jsx7(
2301
2324
  "input",
2302
2325
  {
2303
2326
  type: "text",
2304
2327
  className: "hsk-cb-phone-input",
2305
- placeholder: "0712345678 or 0712345678flyingthunder",
2306
- value: phoneInput,
2307
- onChange: (e) => setPhoneInput(e.target.value.replace(/[^0-9a-zA-Z]/g, "")),
2308
- onKeyDown: (e) => e.key === "Enter" && handlePhoneSubmit(),
2328
+ placeholder: "kiku_\u2026",
2329
+ value: keyInput,
2330
+ onChange: (e) => setKeyInput(e.target.value),
2331
+ onKeyDown: (e) => e.key === "Enter" && handleUseExistingKey(),
2309
2332
  autoFocus: true
2310
2333
  }
2311
2334
  ),
2312
- /* @__PURE__ */ jsx7("button", { className: "hsk-cb-phone-submit", onClick: handlePhoneSubmit, children: "Save & sync my items" })
2335
+ /* @__PURE__ */ jsxs6("div", { style: { display: "flex", gap: 8 }, children: [
2336
+ /* @__PURE__ */ jsx7("button", { className: "hsk-cb-phone-submit", onClick: handleUseExistingKey, disabled: !keyInput.trim(), children: "Use my key" }),
2337
+ /* @__PURE__ */ jsx7("button", { className: "hsk-cb-phone-submit", onClick: handleCreateKey, disabled: minting, children: minting ? "Creating\u2026" : "I'm new \u2014 create my key" })
2338
+ ] })
2313
2339
  ] }) }) })
2314
2340
  ] }),
2315
- !replayMessages && memoryKey && /* @__PURE__ */ jsxs6("div", { className: "hsk-cb-ai-msg", children: [
2341
+ !replayMessages && mintedKey && /* @__PURE__ */ jsxs6("div", { className: "hsk-cb-ai-msg", children: [
2316
2342
  /* @__PURE__ */ jsx7("div", { className: "hsk-cb-ai-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx7(SparkleIcon2, {}) }),
2317
2343
  /* @__PURE__ */ jsx7("div", { className: "hsk-cb-ai-body", children: /* @__PURE__ */ jsx7("div", { className: "hsk-cb-ai-text", children: /* @__PURE__ */ jsxs6("div", { style: { padding: "10px 12px", border: "1px solid var(--hsk-border, #e5e5e5)", borderRadius: 8 }, children: [
2318
- /* @__PURE__ */ jsx7("div", { style: { fontSize: 12, fontWeight: 600, marginBottom: 4 }, children: "\u{1F511} Your memory key" }),
2319
- /* @__PURE__ */ jsx7("code", { style: { display: "block", fontSize: 14, fontWeight: 700, marginBottom: 6, wordBreak: "break-all" }, children: memoryKey }),
2344
+ /* @__PURE__ */ jsx7("div", { style: { fontSize: 12, fontWeight: 600, marginBottom: 4 }, children: "\u{1F511} Your kiku key \u2014 shown only once" }),
2345
+ /* @__PURE__ */ jsx7("code", { style: { display: "block", fontSize: 14, fontWeight: 700, marginBottom: 6, wordBreak: "break-all" }, children: mintedKey }),
2320
2346
  /* @__PURE__ */ jsxs6("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
2321
2347
  /* @__PURE__ */ jsx7("button", { className: "hsk-cb-phone-submit", style: { padding: "4px 10px" }, onClick: () => {
2322
2348
  try {
2323
- navigator.clipboard?.writeText(memoryKey);
2349
+ navigator.clipboard?.writeText(mintedKey);
2324
2350
  } catch {
2325
2351
  }
2326
2352
  }, children: "Copy" }),
2327
- /* @__PURE__ */ jsx7("span", { style: { fontSize: 11, opacity: 0.7 }, children: "Save it \u2014 enter it on any site to get your items back." })
2353
+ /* @__PURE__ */ jsxs6("span", { style: { fontSize: 11, opacity: 0.7 }, children: [
2354
+ "Disappears in ",
2355
+ keyCountdown,
2356
+ "s. Save it like a password \u2014 if lost, the memory it opens is lost with it."
2357
+ ] })
2328
2358
  ] })
2329
2359
  ] }) }) })
2330
2360
  ] }),
@@ -2449,6 +2479,7 @@ function KikuButton({
2449
2479
  theme,
2450
2480
  classNames = {},
2451
2481
  enableVoice = false,
2482
+ voiceLang,
2452
2483
  enableVision = false,
2453
2484
  visionCategoryHint
2454
2485
  }) {
@@ -2518,6 +2549,7 @@ function KikuButton({
2518
2549
  theme,
2519
2550
  classNames,
2520
2551
  enableVoice,
2552
+ voiceLang,
2521
2553
  enableVision,
2522
2554
  visionCategoryHint
2523
2555
  }