@akropolys/kiku 1.6.2 → 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
  ] });
@@ -1167,6 +1174,25 @@ var ShoppingBagIcon = () => /* @__PURE__ */ jsxs6("svg", { width: "13", height:
1167
1174
  /* @__PURE__ */ jsx7("line", { x1: "3", y1: "6", x2: "21", y2: "6" }),
1168
1175
  /* @__PURE__ */ jsx7("path", { d: "M16 10a4 4 0 0 1-8 0" })
1169
1176
  ] });
1177
+ var SecureShieldIcon = ({ className, size = 18 }) => /* @__PURE__ */ jsxs6(
1178
+ "svg",
1179
+ {
1180
+ className,
1181
+ width: size,
1182
+ height: size,
1183
+ viewBox: "0 0 24 24",
1184
+ fill: "none",
1185
+ stroke: "currentColor",
1186
+ strokeWidth: "2.5",
1187
+ strokeLinecap: "round",
1188
+ strokeLinejoin: "round",
1189
+ children: [
1190
+ /* @__PURE__ */ jsx7("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" }),
1191
+ /* @__PURE__ */ jsx7("path", { d: "M12 11a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z" }),
1192
+ /* @__PURE__ */ jsx7("path", { d: "M12 11v4" })
1193
+ ]
1194
+ }
1195
+ );
1170
1196
  var PaperclipIcon = () => /* @__PURE__ */ jsx7("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx7("path", { d: "m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48" }) });
1171
1197
  var MicIcon2 = () => /* @__PURE__ */ jsxs6("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1172
1198
  /* @__PURE__ */ jsx7("path", { d: "M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3z" }),
@@ -1189,6 +1215,173 @@ var DEFAULT_CHIPS = [
1189
1215
  ];
1190
1216
  var SESSIONS_KEY = "akropolys_chat_sessions";
1191
1217
  var MAX_SESSIONS = 20;
1218
+ function parseAtKiku(raw) {
1219
+ const trimmed = raw.trim();
1220
+ if (!/^@kiku\b/i.test(trimmed)) return null;
1221
+ const rest = trimmed.slice(5).trim();
1222
+ if (rest === "" || /^(capture|save)\b/i.test(rest)) {
1223
+ return {
1224
+ intent: "capture",
1225
+ cleanQuery: rest.replace(/^(capture|save)\s*/i, "").trim() || trimmed
1226
+ };
1227
+ }
1228
+ if (/^(history|what have you|show my|my items|what did you|saved|captures|recall)\b/i.test(rest)) {
1229
+ return { intent: "view_history", cleanQuery: "show my saved items" };
1230
+ }
1231
+ if (/^(delete|forget|remove|unsave)\b/i.test(rest)) {
1232
+ return {
1233
+ intent: "delete",
1234
+ cleanQuery: rest.replace(/^(delete|forget|remove|unsave)\s*/i, "").trim() || trimmed
1235
+ };
1236
+ }
1237
+ return { intent: "capture", cleanQuery: rest || trimmed };
1238
+ }
1239
+ function KikuPickerMenu({
1240
+ sources,
1241
+ referencedIds,
1242
+ defaultCurrency,
1243
+ onCapture,
1244
+ onCaptureAll,
1245
+ onViewHistory,
1246
+ onDelete,
1247
+ onDismiss
1248
+ }) {
1249
+ const discussed = sources.filter((s) => s.id && referencedIds.includes(s.id));
1250
+ return /* @__PURE__ */ jsxs6(
1251
+ "div",
1252
+ {
1253
+ className: "hsk-kiku-picker",
1254
+ role: "menu",
1255
+ "aria-label": "@kiku commands",
1256
+ onMouseDown: (e) => e.preventDefault(),
1257
+ children: [
1258
+ /* @__PURE__ */ jsxs6("div", { className: "hsk-kiku-picker-header", children: [
1259
+ /* @__PURE__ */ jsx7("span", { className: "hsk-kiku-picker-at", children: "@kiku" }),
1260
+ /* @__PURE__ */ jsx7("span", { className: "hsk-kiku-picker-hint", children: "What would you like to do?" }),
1261
+ /* @__PURE__ */ jsx7("button", { className: "hsk-kiku-picker-close", onClick: onDismiss, "aria-label": "Close", children: /* @__PURE__ */ jsx7(CloseIcon, {}) })
1262
+ ] }),
1263
+ discussed.length > 0 && /* @__PURE__ */ jsxs6(Fragment3, { children: [
1264
+ /* @__PURE__ */ jsx7("div", { className: "hsk-kiku-picker-section", children: "Discussed in this chat" }),
1265
+ discussed.map((src, i) => /* @__PURE__ */ jsxs6(
1266
+ "button",
1267
+ {
1268
+ className: "hsk-kiku-picker-item",
1269
+ role: "menuitem",
1270
+ onClick: () => {
1271
+ onCapture(src);
1272
+ onDismiss();
1273
+ },
1274
+ children: [
1275
+ src.image ? /* @__PURE__ */ jsx7("img", { src: src.image, alt: src.name, className: "hsk-kiku-picker-img" }) : /* @__PURE__ */ jsx7("span", { className: "hsk-kiku-picker-img-placeholder", children: /* @__PURE__ */ jsx7(SecureShieldIcon, { size: 14 }) }),
1276
+ /* @__PURE__ */ jsxs6("span", { className: "hsk-kiku-picker-item-body", children: [
1277
+ /* @__PURE__ */ jsxs6("span", { className: "hsk-kiku-picker-item-name", children: [
1278
+ "\u{1F4CC} ",
1279
+ src.name
1280
+ ] }),
1281
+ src.price && /* @__PURE__ */ jsxs6("span", { className: "hsk-kiku-picker-item-price", children: [
1282
+ src.currency ?? defaultCurrency,
1283
+ " ",
1284
+ parseFloat(String(src.price).replace(/[^0-9.]/g, "") || "0").toLocaleString()
1285
+ ] })
1286
+ ] })
1287
+ ]
1288
+ },
1289
+ src.id ?? i
1290
+ )),
1291
+ discussed.length > 1 && /* @__PURE__ */ jsx7(
1292
+ "button",
1293
+ {
1294
+ className: "hsk-kiku-picker-item hsk-kiku-picker-item--all",
1295
+ role: "menuitem",
1296
+ onClick: () => {
1297
+ onCaptureAll(discussed);
1298
+ onDismiss();
1299
+ },
1300
+ children: /* @__PURE__ */ jsx7("span", { className: "hsk-kiku-picker-item-body", children: /* @__PURE__ */ jsxs6("span", { className: "hsk-kiku-picker-item-name", children: [
1301
+ "\u{1F4CC} Capture all (",
1302
+ discussed.length,
1303
+ " items)"
1304
+ ] }) })
1305
+ }
1306
+ ),
1307
+ /* @__PURE__ */ jsx7("div", { className: "hsk-kiku-picker-divider" })
1308
+ ] }),
1309
+ discussed.length === 0 && /* @__PURE__ */ jsxs6(Fragment3, { children: [
1310
+ /* @__PURE__ */ jsx7(
1311
+ "button",
1312
+ {
1313
+ className: "hsk-kiku-picker-item",
1314
+ role: "menuitem",
1315
+ onClick: () => {
1316
+ onCapture({ name: "current page", id: void 0 });
1317
+ onDismiss();
1318
+ },
1319
+ children: /* @__PURE__ */ jsx7("span", { className: "hsk-kiku-picker-item-body", children: /* @__PURE__ */ jsx7("span", { className: "hsk-kiku-picker-item-name", children: "\u{1F4CC} Capture current page" }) })
1320
+ }
1321
+ ),
1322
+ /* @__PURE__ */ jsx7("div", { className: "hsk-kiku-picker-divider" })
1323
+ ] }),
1324
+ /* @__PURE__ */ jsx7(
1325
+ "button",
1326
+ {
1327
+ className: "hsk-kiku-picker-item",
1328
+ role: "menuitem",
1329
+ onClick: () => {
1330
+ onViewHistory();
1331
+ onDismiss();
1332
+ },
1333
+ children: /* @__PURE__ */ jsx7("span", { className: "hsk-kiku-picker-item-body", children: /* @__PURE__ */ jsx7("span", { className: "hsk-kiku-picker-item-name", children: "\u{1F4CB} What have you saved?" }) })
1334
+ }
1335
+ ),
1336
+ /* @__PURE__ */ jsx7(
1337
+ "button",
1338
+ {
1339
+ className: "hsk-kiku-picker-item",
1340
+ role: "menuitem",
1341
+ onClick: () => {
1342
+ onDelete();
1343
+ onDismiss();
1344
+ },
1345
+ children: /* @__PURE__ */ jsx7("span", { className: "hsk-kiku-picker-item-body", children: /* @__PURE__ */ jsx7("span", { className: "hsk-kiku-picker-item-name", children: "\u{1F5D1}\uFE0F Delete this" }) })
1346
+ }
1347
+ )
1348
+ ]
1349
+ }
1350
+ );
1351
+ }
1352
+ function AtPickerMenu({ onSelect, onDismiss }) {
1353
+ return /* @__PURE__ */ jsxs6(
1354
+ "div",
1355
+ {
1356
+ className: "hsk-kiku-picker",
1357
+ role: "menu",
1358
+ "aria-label": "Extensions",
1359
+ onMouseDown: (e) => e.preventDefault(),
1360
+ children: [
1361
+ /* @__PURE__ */ jsxs6("div", { className: "hsk-kiku-picker-header", children: [
1362
+ /* @__PURE__ */ jsx7("span", { className: "hsk-kiku-picker-at", children: "@" }),
1363
+ /* @__PURE__ */ jsx7("span", { className: "hsk-kiku-picker-hint", children: "Use an extension" }),
1364
+ /* @__PURE__ */ jsx7("button", { className: "hsk-kiku-picker-close", onClick: onDismiss, "aria-label": "Close", children: /* @__PURE__ */ jsx7(CloseIcon, {}) })
1365
+ ] }),
1366
+ /* @__PURE__ */ jsxs6(
1367
+ "button",
1368
+ {
1369
+ className: "hsk-kiku-picker-item",
1370
+ role: "menuitem",
1371
+ onClick: () => onSelect("@kiku"),
1372
+ children: [
1373
+ /* @__PURE__ */ jsx7("span", { className: "hsk-kiku-picker-img-placeholder", style: { background: "color-mix(in srgb, var(--hsk-primary, #7c5cfc) 15%, transparent)", color: "var(--hsk-primary, #7c5cfc)" }, children: /* @__PURE__ */ jsx7(SparkleIcon2, {}) }),
1374
+ /* @__PURE__ */ jsxs6("span", { className: "hsk-kiku-picker-item-body", children: [
1375
+ /* @__PURE__ */ jsx7("span", { className: "hsk-kiku-picker-item-name", children: "@kiku" }),
1376
+ /* @__PURE__ */ jsx7("span", { className: "hsk-kiku-picker-item-price", style: { fontSize: "11px", whiteSpace: "normal", lineHeight: "1.3" }, children: "Ask Kiku to capture pages, remember items, or show your history" })
1377
+ ] })
1378
+ ]
1379
+ }
1380
+ )
1381
+ ]
1382
+ }
1383
+ );
1384
+ }
1192
1385
  function loadSessions() {
1193
1386
  try {
1194
1387
  if (typeof window === "undefined") return [];
@@ -1509,18 +1702,7 @@ var getFriendlyError = (err) => {
1509
1702
  return str;
1510
1703
  }
1511
1704
  };
1512
- var MK_ADJ = ["flying", "silent", "golden", "lucky", "brave", "swift", "royal", "cosmic"];
1513
- var MK_NOUN = ["thunder", "falcon", "river", "ember", "tiger", "comet", "willow", "onyx"];
1514
- function generateMagicWord() {
1515
- return MK_ADJ[Math.floor(Math.random() * MK_ADJ.length)] + MK_NOUN[Math.floor(Math.random() * MK_NOUN.length)];
1516
- }
1517
- function parseMemoryKey(raw) {
1518
- const cleaned = raw.trim().replace(/\s+/g, "");
1519
- const m = cleaned.match(/^(\d{6,})([a-zA-Z].*)?$/);
1520
- const phone = m?.[1] || cleaned.replace(/\D/g, "");
1521
- const word = (m?.[2] || "").toLowerCase() || generateMagicWord();
1522
- return { phone, word };
1523
- }
1705
+ var KIKU_KEY_REVEAL_SECONDS = 60;
1524
1706
  function parseThinking(text) {
1525
1707
  const openMatch = text.match(/<\s*thinking\s*>/i);
1526
1708
  if (!openMatch) {
@@ -1628,6 +1810,7 @@ function ChatModal({
1628
1810
  theme,
1629
1811
  classNames = {},
1630
1812
  enableVoice = false,
1813
+ voiceLang,
1631
1814
  enableVision = false,
1632
1815
  visionCategoryHint
1633
1816
  }) {
@@ -1661,7 +1844,7 @@ function ChatModal({
1661
1844
  if (!hasSpeechAPI || voiceState !== "idle") return;
1662
1845
  const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
1663
1846
  const recognition = new SR();
1664
- recognition.lang = "en-US";
1847
+ recognition.lang = voiceLang || document.documentElement.lang || navigator.language || "en-US";
1665
1848
  recognition.interimResults = true;
1666
1849
  recognition.maxAlternatives = 1;
1667
1850
  recognitionRef.current = recognition;
@@ -1694,7 +1877,7 @@ function ChatModal({
1694
1877
  setVoiceState((prev) => prev === "listening" ? "idle" : prev);
1695
1878
  };
1696
1879
  recognition.start();
1697
- }, [hasSpeechAPI, voiceState]);
1880
+ }, [hasSpeechAPI, voiceState, voiceLang]);
1698
1881
  const stopVoice = useCallback2(() => {
1699
1882
  recognitionRef.current?.stop();
1700
1883
  setVoiceState("idle");
@@ -1709,23 +1892,37 @@ function ChatModal({
1709
1892
  const [selectedProduct, setSelectedProduct] = useState5(null);
1710
1893
  const bottomRef = useRef5(null);
1711
1894
  const textareaRef = useRef5(null);
1712
- const [phoneInput, setPhoneInput] = useState5(() => {
1713
- if (typeof window !== "undefined") {
1714
- return localStorage.getItem("akropolys_user_phone") || "";
1715
- }
1716
- return "";
1717
- });
1718
- const [paymentPhase, setPaymentPhase] = useState5("idle");
1719
- 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);
1900
+ const [showKikuPicker, setShowKikuPicker] = useState5(false);
1901
+ const [showAtPicker, setShowAtPicker] = useState5(false);
1720
1902
  const [sessions, setSessions] = useState5(() => loadSessions());
1721
1903
  const [sidebarOpen, setSidebarOpen] = useState5(false);
1722
1904
  const [replayMessages, setReplayMessages] = useState5(null);
1723
1905
  useEffect3(() => {
1724
1906
  if (!lastAction) return;
1725
- if (lastAction.type === "request_phone") {
1726
- setPaymentPhase("prompt_phone");
1907
+ if (lastAction.type === "request_kiku_key") {
1908
+ setKeyPhase("prompt_key");
1727
1909
  }
1728
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]);
1729
1926
  const isStringTheme = typeof theme === "string";
1730
1927
  const hskThemeAttr = isStringTheme ? theme : void 0;
1731
1928
  const customStyles = !isStringTheme && theme ? {
@@ -1735,16 +1932,30 @@ function ChatModal({
1735
1932
  ...theme?.fontFamily && { "--hsk-font": theme.fontFamily },
1736
1933
  ...theme?.borderRadius && { "--hsk-border-radius": theme.borderRadius }
1737
1934
  } : void 0;
1738
- const handlePhoneSubmit = async () => {
1739
- if (!phoneInput.trim()) return;
1740
- const { phone, word } = parseMemoryKey(phoneInput);
1741
- if (!phone) return;
1742
- if (typeof window !== "undefined") localStorage.setItem("akropolys_user_phone", phone);
1743
- client.setShopperIdentity(phone, word);
1744
- setMemoryKey(phone + word);
1745
- setPaymentPhase("idle");
1935
+ const retryLastMessage = async () => {
1746
1936
  const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
1747
- 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
+ }
1748
1959
  };
1749
1960
  const msgsContainerRef = useRef5(null);
1750
1961
  useEffect3(() => {
@@ -1779,7 +1990,7 @@ function ChatModal({
1779
1990
  saveCurrentSession();
1780
1991
  reset();
1781
1992
  setReplayMessages(null);
1782
- setPaymentPhase("idle");
1993
+ setKeyPhase("idle");
1783
1994
  }, [reset, saveCurrentSession]);
1784
1995
  const handleSourceClick = (src) => {
1785
1996
  setSelectedProduct(src);
@@ -1787,27 +1998,97 @@ function ChatModal({
1787
1998
  const q = `Tell me more about the ${src.name}${src.price ? ` (${src.currency ?? defaultCurrency} ${src.price})` : ""} \u2014 what are its key specs, who is it best for, and is it worth buying?`;
1788
1999
  send(q);
1789
2000
  };
1790
- const handleSend = async (text, extraAttachments) => {
1791
- const q = (text ?? input).trim();
1792
- if (!q || loading) return;
2001
+ const handleSend = async (text, extraAttachments, forcedIntent, captureTargets) => {
2002
+ const raw = (text ?? input).trim();
2003
+ if (!raw || loading) return;
2004
+ const kiku = parseAtKiku(raw);
2005
+ const q = kiku ? kiku.cleanQuery : raw;
2006
+ const resolvedForcedIntent = forcedIntent ?? kiku?.intent;
1793
2007
  setReplayMessages(null);
1794
2008
  setSelectedProduct(null);
2009
+ setShowKikuPicker(false);
2010
+ setShowAtPicker(false);
1795
2011
  setInput("");
1796
2012
  if (textareaRef.current) {
1797
2013
  textareaRef.current.style.height = "auto";
1798
2014
  }
1799
2015
  const toSend = extraAttachments ?? attachments;
1800
2016
  setAttachments([]);
1801
- await send(q, void 0, toSend.length > 0 ? toSend : void 0);
2017
+ await send(q, raw, toSend.length > 0 ? toSend : void 0, resolvedForcedIntent, captureTargets);
1802
2018
  };
2019
+ const handleSelectExtension = (ext) => {
2020
+ setInput(ext + " ");
2021
+ setShowAtPicker(false);
2022
+ setShowKikuPicker(true);
2023
+ if (textareaRef.current) {
2024
+ textareaRef.current.focus();
2025
+ }
2026
+ };
2027
+ const handleKikuCapture = useCallback2((product) => {
2028
+ const name = product.name || "";
2029
+ const display = `@kiku capture${name ? " " + name : ""}`;
2030
+ const q = name || "capture current page";
2031
+ setInput("");
2032
+ setReplayMessages(null);
2033
+ setSelectedProduct(null);
2034
+ const toSend = attachments;
2035
+ setAttachments([]);
2036
+ send(q, display, toSend.length > 0 ? toSend : void 0, "capture");
2037
+ }, [attachments, send]);
2038
+ const handleKikuCaptureAll = useCallback2((products) => {
2039
+ const targets = products.filter((p) => p.id).map((p) => ({
2040
+ name: p.name || "",
2041
+ url: p.url || "",
2042
+ image: p.image || "",
2043
+ price: p.price ? String(p.price) : "",
2044
+ currency: p.currency || defaultCurrency
2045
+ }));
2046
+ const names = products.map((p) => p.name).filter(Boolean).join(", ");
2047
+ const display = `@kiku capture all (${products.length} items)`;
2048
+ setInput("");
2049
+ setReplayMessages(null);
2050
+ setSelectedProduct(null);
2051
+ setAttachments([]);
2052
+ send(names || "capture all", display, void 0, "capture_all", targets);
2053
+ }, [defaultCurrency, send]);
2054
+ const handleKikuViewHistory = useCallback2(() => {
2055
+ const display = "@kiku what have you saved?";
2056
+ setInput("");
2057
+ setReplayMessages(null);
2058
+ setSelectedProduct(null);
2059
+ setAttachments([]);
2060
+ send("show my saved items", display, void 0, "view_history");
2061
+ }, [send]);
2062
+ const handleKikuDelete = useCallback2(() => {
2063
+ const display = "@kiku delete this";
2064
+ setInput("");
2065
+ setReplayMessages(null);
2066
+ setSelectedProduct(null);
2067
+ setAttachments([]);
2068
+ send("delete this", display, void 0, "delete");
2069
+ }, [send]);
1803
2070
  const handleKeyDown = (e) => {
2071
+ if (e.key === "Escape" && showKikuPicker) {
2072
+ e.preventDefault();
2073
+ setShowKikuPicker(false);
2074
+ return;
2075
+ }
2076
+ if (e.key === "Escape" && showAtPicker) {
2077
+ e.preventDefault();
2078
+ setShowAtPicker(false);
2079
+ return;
2080
+ }
1804
2081
  if (e.key === "Enter" && !e.shiftKey) {
1805
2082
  e.preventDefault();
1806
2083
  handleSend();
1807
2084
  }
1808
2085
  };
1809
2086
  const handleInput = (e) => {
1810
- setInput(e.target.value);
2087
+ const val = e.target.value;
2088
+ setInput(val);
2089
+ const trimmed = val.trim();
2090
+ setShowAtPicker(trimmed === "@");
2091
+ setShowKikuPicker(/^@kiku\b/i.test(trimmed));
1811
2092
  const t = e.target;
1812
2093
  t.style.height = "auto";
1813
2094
  t.style.height = `${Math.min(t.scrollHeight, 140)}px`;
@@ -1944,7 +2225,10 @@ function ChatModal({
1944
2225
  const displayContent = !isUser && isLast && lastIntent === "compare" && sources.length >= 2 && !replayMessages ? stripMarkdownTables(msg.content) : msg.content;
1945
2226
  return /* @__PURE__ */ jsx7("div", { className: "hsk-cb-msg-group", children: isUser ? /* @__PURE__ */ jsxs6("div", { className: "hsk-cb-user-msg", children: [
1946
2227
  msg.images && msg.images.length > 0 && /* @__PURE__ */ jsx7("div", { className: "hsk-cb-user-imgs", children: msg.images.map((img, i) => /* @__PURE__ */ jsx7("img", { src: img, alt: `attachment ${i + 1}`, className: "hsk-cb-user-img-thumb" }, i)) }),
1947
- msg.content && /* @__PURE__ */ jsx7("div", { className: "hsk-cb-user-bubble", children: msg.content })
2228
+ msg.content && /* @__PURE__ */ jsxs6("div", { className: "hsk-cb-user-bubble", children: [
2229
+ /^@kiku\b/i.test(msg.content) && /* @__PURE__ */ jsx7("span", { className: "hsk-kiku-badge", children: "@kiku" }),
2230
+ msg.content
2231
+ ] })
1948
2232
  ] }) : /* @__PURE__ */ jsxs6("div", { className: "hsk-cb-ai-msg", children: [
1949
2233
  /* @__PURE__ */ jsx7("div", { className: "hsk-cb-ai-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx7(SparkleIcon2, {}) }),
1950
2234
  /* @__PURE__ */ jsxs6("div", { className: "hsk-cb-ai-body", children: [
@@ -1959,7 +2243,7 @@ function ChatModal({
1959
2243
  ] });
1960
2244
  })(),
1961
2245
  isLast && lastIntent === "compare" && sources.length >= 2 && !replayMessages && /* @__PURE__ */ jsx7(ComparisonMatrix, { sources, defaultCurrency }),
1962
- isLast && referencedIds.length > 0 && lastIntent !== "compare" && lastAction?.type !== "request_phone" && lastAction?.type !== "awaiting_payment" && lastAction?.type !== "checkout" && !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(
1963
2247
  SourcesCarousel,
1964
2248
  {
1965
2249
  sources,
@@ -2032,44 +2316,71 @@ function ChatModal({
2032
2316
  ] })
2033
2317
  ] }),
2034
2318
  error && /* @__PURE__ */ jsx7("div", { className: "hsk-cb-error", children: getFriendlyError(error) }),
2035
- !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: [
2036
2320
  /* @__PURE__ */ jsx7("div", { className: "hsk-cb-ai-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx7(SparkleIcon2, {}) }),
2037
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: [
2038
- /* @__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" }),
2039
2323
  /* @__PURE__ */ jsx7(
2040
2324
  "input",
2041
2325
  {
2042
2326
  type: "text",
2043
2327
  className: "hsk-cb-phone-input",
2044
- placeholder: "0712345678 or 0712345678flyingthunder",
2045
- value: phoneInput,
2046
- onChange: (e) => setPhoneInput(e.target.value.replace(/[^0-9a-zA-Z]/g, "")),
2047
- 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(),
2048
2332
  autoFocus: true
2049
2333
  }
2050
2334
  ),
2051
- /* @__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
+ ] })
2052
2339
  ] }) }) })
2053
2340
  ] }),
2054
- !replayMessages && memoryKey && /* @__PURE__ */ jsxs6("div", { className: "hsk-cb-ai-msg", children: [
2341
+ !replayMessages && mintedKey && /* @__PURE__ */ jsxs6("div", { className: "hsk-cb-ai-msg", children: [
2055
2342
  /* @__PURE__ */ jsx7("div", { className: "hsk-cb-ai-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx7(SparkleIcon2, {}) }),
2056
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: [
2057
- /* @__PURE__ */ jsx7("div", { style: { fontSize: 12, fontWeight: 600, marginBottom: 4 }, children: "\u{1F511} Your memory key" }),
2058
- /* @__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 }),
2059
2346
  /* @__PURE__ */ jsxs6("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
2060
2347
  /* @__PURE__ */ jsx7("button", { className: "hsk-cb-phone-submit", style: { padding: "4px 10px" }, onClick: () => {
2061
2348
  try {
2062
- navigator.clipboard?.writeText(memoryKey);
2349
+ navigator.clipboard?.writeText(mintedKey);
2063
2350
  } catch {
2064
2351
  }
2065
2352
  }, children: "Copy" }),
2066
- /* @__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
+ ] })
2067
2358
  ] })
2068
2359
  ] }) }) })
2069
2360
  ] }),
2070
2361
  /* @__PURE__ */ jsx7("div", { ref: bottomRef, style: { height: 1 } })
2071
2362
  ] }),
2072
2363
  !replayMessages && /* @__PURE__ */ jsxs6("div", { className: "hsk-cb-input-wrap", children: [
2364
+ showAtPicker && /* @__PURE__ */ jsx7(
2365
+ AtPickerMenu,
2366
+ {
2367
+ onSelect: handleSelectExtension,
2368
+ onDismiss: () => setShowAtPicker(false)
2369
+ }
2370
+ ),
2371
+ showKikuPicker && /* @__PURE__ */ jsx7(
2372
+ KikuPickerMenu,
2373
+ {
2374
+ sources,
2375
+ referencedIds,
2376
+ defaultCurrency,
2377
+ onCapture: handleKikuCapture,
2378
+ onCaptureAll: handleKikuCaptureAll,
2379
+ onViewHistory: handleKikuViewHistory,
2380
+ onDelete: handleKikuDelete,
2381
+ onDismiss: () => setShowKikuPicker(false)
2382
+ }
2383
+ ),
2073
2384
  attachments.length > 0 && /* @__PURE__ */ jsx7("div", { className: "hsk-cb-img-strip", children: attachments.map((att, i) => /* @__PURE__ */ jsxs6("div", { className: "hsk-cb-img-thumb-wrap", children: [
2074
2385
  /* @__PURE__ */ jsx7("img", { src: att.data, alt: `attachment ${i + 1}`, className: "hsk-cb-img-thumb" }),
2075
2386
  /* @__PURE__ */ jsx7(
@@ -2168,6 +2479,7 @@ function KikuButton({
2168
2479
  theme,
2169
2480
  classNames = {},
2170
2481
  enableVoice = false,
2482
+ voiceLang,
2171
2483
  enableVision = false,
2172
2484
  visionCategoryHint
2173
2485
  }) {
@@ -2237,6 +2549,7 @@ function KikuButton({
2237
2549
  theme,
2238
2550
  classNames,
2239
2551
  enableVoice,
2552
+ voiceLang,
2240
2553
  enableVision,
2241
2554
  visionCategoryHint
2242
2555
  }