@akropolys/kiku 1.5.0 → 1.5.3

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
@@ -205,7 +205,7 @@ var import_sdk3 = require("@akropolys/sdk");
205
205
  // src/utils/markdown.tsx
206
206
  var import_jsx_runtime2 = require("react/jsx-runtime");
207
207
  var parseInline = (text, keyPrefix) => {
208
- const tokenRegex = /(\[[^\]]+\]\([^)]+\)|\*\*[^*]+\*\*|`[^`]+`)/g;
208
+ const tokenRegex = /(!\[[^\]]*\]\([^)]+\)|\[[^\]]+\]\([^)]+\)|\*\*[^*]+\*\*|`[^`]+`)/g;
209
209
  const parts = text.split(tokenRegex);
210
210
  return parts.map((part, index) => {
211
211
  if (!part) return null;
@@ -216,6 +216,28 @@ var parseInline = (text, keyPrefix) => {
216
216
  if (part.startsWith("**") && part.endsWith("**")) {
217
217
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: parseInline(part.slice(2, -2), key) }, key);
218
218
  }
219
+ const imageMatch = part.match(/^!\[([^\]]*)\]\(([^)]+)\)$/);
220
+ if (imageMatch) {
221
+ const alt = imageMatch[1];
222
+ const url = imageMatch[2];
223
+ const isSafeUrl = /^(https?|data:image):/i.test(url);
224
+ if (isSafeUrl) {
225
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
226
+ "img",
227
+ {
228
+ src: url,
229
+ alt: alt || "Product image",
230
+ className: "hsk-markdown-img",
231
+ loading: "lazy",
232
+ onError: (e) => {
233
+ e.target.style.display = "none";
234
+ }
235
+ },
236
+ key
237
+ );
238
+ }
239
+ return null;
240
+ }
219
241
  const linkMatch = part.match(/^\[([^\]]+)\]\(([^)]+)\)$/);
220
242
  if (linkMatch) {
221
243
  const url = linkMatch[2];
@@ -239,6 +261,30 @@ function renderMarkdown(content) {
239
261
  i++;
240
262
  continue;
241
263
  }
264
+ const standaloneImageMatch = line.trim().match(/^!\[([^\]]*)\]\(([^)]+)\)$/);
265
+ if (standaloneImageMatch) {
266
+ const alt = standaloneImageMatch[1];
267
+ const url = standaloneImageMatch[2];
268
+ const isSafeUrl = /^(https?|data:image):/i.test(url);
269
+ if (isSafeUrl) {
270
+ elements.push(
271
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-markdown-img-block", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
272
+ "img",
273
+ {
274
+ src: url,
275
+ alt: alt || "Product image",
276
+ className: "hsk-markdown-img",
277
+ loading: "lazy",
278
+ onError: (e) => {
279
+ e.target.style.display = "none";
280
+ }
281
+ }
282
+ ) }, key)
283
+ );
284
+ }
285
+ i++;
286
+ continue;
287
+ }
242
288
  const headerMatch = line.match(/^(#{1,3})\s+(.*)/);
243
289
  if (headerMatch) {
244
290
  const level = headerMatch[1].length;
@@ -426,18 +472,31 @@ var ArrowUpIcon = () => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { w
426
472
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "m5 12 7-7 7 7" }),
427
473
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12 19V5" })
428
474
  ] });
429
- function SourceCard({ source, defaultCurrency, onSelect }) {
430
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-source-card", onClick: () => onSelect?.(source), children: [
431
- source.image && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("img", { src: source.image, alt: source.name, className: "hsk-source-img" }),
432
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
433
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-source-name", children: source.name }),
434
- source.price && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-source-price", children: [
435
- source.currency ?? defaultCurrency,
436
- " ",
437
- source.price
438
- ] })
439
- ] })
440
- ] });
475
+ function SourceCard({
476
+ source,
477
+ defaultCurrency,
478
+ onSelect,
479
+ isReferenced
480
+ }) {
481
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
482
+ "div",
483
+ {
484
+ className: cn("hsk-source-card", isReferenced && "hsk-source-card--referenced"),
485
+ onClick: () => onSelect?.(source),
486
+ children: [
487
+ source.image && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("img", { src: source.image, alt: source.name, className: "hsk-source-img" }),
488
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { flex: 1, minWidth: 0, position: "relative" }, children: [
489
+ isReferenced && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-source-ref-badge", title: "Featured in response", style: { top: "0", right: "0" }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SparkleIcon, {}) }),
490
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-source-name", style: { paddingRight: isReferenced ? "20px" : void 0 }, children: source.name }),
491
+ source.price && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-source-price", children: [
492
+ source.currency ?? defaultCurrency,
493
+ " ",
494
+ source.price
495
+ ] })
496
+ ] })
497
+ ]
498
+ }
499
+ );
441
500
  }
442
501
  function ChatWidget({
443
502
  title = "AI Shopping Assistant",
@@ -453,7 +512,7 @@ function ChatWidget({
453
512
  enableVision = false,
454
513
  visionCategoryHint
455
514
  }) {
456
- const { messages, sources, loading: chatLoading, error, send, reset } = (0, import_sdk3.useKiku)();
515
+ const { messages, sources, referencedIds, loading: chatLoading, streaming, error, send, reset } = (0, import_sdk3.useKiku)();
457
516
  const [input, setInput] = (0, import_react4.useState)("");
458
517
  const [visualLoading, setVisualLoading] = (0, import_react4.useState)(false);
459
518
  const bottomRef = (0, import_react4.useRef)(null);
@@ -611,7 +670,54 @@ I couldn't find any matching products in the store.`;
611
670
  ] })
612
671
  ] }),
613
672
  msg.role === "assistant" && msg.visualSources && msg.visualSources.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-sources-container", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-sources", children: msg.visualSources.map((src, si) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SourceCard, { source: src, defaultCurrency, onSelect: onSelectSource }, si)) }) }),
614
- msg.role === "assistant" && idx === chatHistory.length - 1 && !msg.visualSources && sources.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-sources-container", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-sources", children: sources.map((src, si) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SourceCard, { source: src, defaultCurrency, onSelect: onSelectSource }, si)) }) })
673
+ msg.role === "assistant" && idx === chatHistory.length - 1 && !msg.visualSources && sources.length > 0 && (() => {
674
+ const isStreamingActive = chatLoading || streaming;
675
+ if (isStreamingActive) {
676
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-sources-container", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-sources", children: sources.map((src, si) => {
677
+ const isReferenced = !!(src.id && referencedIds.includes(src.id));
678
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
679
+ SourceCard,
680
+ {
681
+ source: src,
682
+ defaultCurrency,
683
+ onSelect: onSelectSource,
684
+ isReferenced
685
+ },
686
+ si
687
+ );
688
+ }) }) });
689
+ }
690
+ const featured = sources.filter((src) => src.id && referencedIds.includes(src.id));
691
+ const general = sources.filter((src) => !src.id || !referencedIds.includes(src.id));
692
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-sources-container", children: [
693
+ featured.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-sources-group", style: { marginBottom: "10px" }, children: [
694
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-sources-group-title", children: "\u2B50 Featured in response" }),
695
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-sources", children: featured.map((src, si) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
696
+ SourceCard,
697
+ {
698
+ source: src,
699
+ defaultCurrency,
700
+ onSelect: onSelectSource,
701
+ isReferenced: true
702
+ },
703
+ `feat-${si}`
704
+ )) })
705
+ ] }),
706
+ general.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-sources-group", children: [
707
+ featured.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-sources-group-title", children: "All matches" }),
708
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-sources", children: general.map((src, si) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
709
+ SourceCard,
710
+ {
711
+ source: src,
712
+ defaultCurrency,
713
+ onSelect: onSelectSource,
714
+ isReferenced: false
715
+ },
716
+ `gen-${si}`
717
+ )) })
718
+ ] })
719
+ ] });
720
+ })()
615
721
  ] }, idx)),
616
722
  loading && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-msg-row", children: [
617
723
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-msg-avatar ai", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SparkleIcon, {}) }),
@@ -1141,8 +1247,10 @@ function ActionPills({ cart, actionType, onSend, loading }) {
1141
1247
  const pills = [];
1142
1248
  if (hasItems && lastItem) {
1143
1249
  if (actionType === "add_to_cart") {
1144
- pills.push({ emoji: "\u2795", label: "Add 1 more", query: `Add one more ${lastItem.name} to my cart` });
1145
- pills.push({ emoji: "\u{1F5D1}\uFE0F", label: `Remove ${lastItem.name.split(" ")[0]}`, query: `Remove the ${lastItem.name} from my cart` });
1250
+ const name = lastItem.name || "item";
1251
+ const shortName = name.split(" ")[0] || "item";
1252
+ pills.push({ emoji: "\u2795", label: "Add 1 more", query: `Add one more ${name} to my cart` });
1253
+ pills.push({ emoji: "\u{1F5D1}\uFE0F", label: `Remove ${shortName}`, query: `Remove the ${name} from my cart` });
1146
1254
  }
1147
1255
  if (cart.items.length > 1) {
1148
1256
  pills.push({ emoji: "\u{1F6D2}", label: "View cart", query: "Show me my cart" });
@@ -1173,7 +1281,7 @@ function ActionPills({ cart, actionType, onSend, loading }) {
1173
1281
  pill.query
1174
1282
  )) });
1175
1283
  }
1176
- function SourcesCarousel({ sources, defaultCurrency, onSelectSource }) {
1284
+ function SourcesCarousel({ sources, defaultCurrency, onSelectSource, referencedIds = [] }) {
1177
1285
  const client = (0, import_sdk6.useAkropolysContext)();
1178
1286
  const isProperty = client?.vertical === "property";
1179
1287
  const railRef = (0, import_react5.useRef)(null);
@@ -1200,44 +1308,51 @@ function SourcesCarousel({ sources, defaultCurrency, onSelectSource }) {
1200
1308
  railRef.current?.scrollBy({ left: 170, behavior: "smooth" });
1201
1309
  };
1202
1310
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hsk-cb-sources-wrap", children: [
1203
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-sources", ref: railRef, children: sources.map((src, si) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1204
- "div",
1205
- {
1206
- className: "hsk-cb-source",
1207
- style: { animationDelay: `${si * 50}ms` },
1208
- onClick: () => onSelectSource?.(src),
1209
- children: [
1210
- src.image ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hsk-cb-src-imgwrap", style: { position: "relative" }, children: [
1211
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("img", { src: src.image, alt: src.name, loading: "lazy" }),
1212
- isProperty && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: {
1213
- position: "absolute",
1214
- top: "6px",
1215
- right: "6px",
1216
- background: "rgba(14, 14, 15, 0.75)",
1217
- backdropFilter: "blur(4px)",
1218
- borderRadius: "50%",
1219
- width: "24px",
1220
- height: "24px",
1221
- display: "flex",
1222
- alignItems: "center",
1223
- justifyContent: "center",
1224
- color: "#fbbf24",
1225
- // Gold sparkle badge
1226
- boxShadow: "0 2px 4px rgba(0,0,0,0.2)"
1227
- }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SparkleIcon2, { size: 12 }) })
1228
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-src-imgwrap-empty", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SparkleIcon2, {}) }),
1229
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hsk-cb-src-info", children: [
1230
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-src-name", children: src.name }),
1231
- src.price && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hsk-cb-src-price", children: [
1232
- src.currency ?? defaultCurrency,
1233
- " ",
1234
- parseFloat(String(src.price).replace(/[^0-9.]/g, "") || "0").toLocaleString()
1311
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-sources", ref: railRef, children: sources.map((src, si) => {
1312
+ const isReferenced = !!(src.id && referencedIds.includes(src.id));
1313
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1314
+ "div",
1315
+ {
1316
+ className: cn("hsk-cb-source", isReferenced && "hsk-cb-source--referenced"),
1317
+ style: { animationDelay: `${si * 50}ms` },
1318
+ onClick: () => onSelectSource?.(src),
1319
+ children: [
1320
+ src.image ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hsk-cb-src-imgwrap", style: { position: "relative" }, children: [
1321
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("img", { src: src.image, alt: src.name, loading: "lazy" }),
1322
+ isReferenced && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-source-ref-badge", title: "Featured in response", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SparkleIcon2, { size: 10 }) }),
1323
+ isProperty && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: {
1324
+ position: "absolute",
1325
+ top: "6px",
1326
+ right: "6px",
1327
+ background: "rgba(14, 14, 15, 0.75)",
1328
+ backdropFilter: "blur(4px)",
1329
+ borderRadius: "50%",
1330
+ width: "24px",
1331
+ height: "24px",
1332
+ display: "flex",
1333
+ alignItems: "center",
1334
+ justifyContent: "center",
1335
+ color: "#fbbf24",
1336
+ // Gold sparkle badge
1337
+ boxShadow: "0 2px 4px rgba(0,0,0,0.2)"
1338
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SparkleIcon2, { size: 12 }) })
1339
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hsk-cb-src-imgwrap-empty", style: { position: "relative" }, children: [
1340
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SparkleIcon2, {}),
1341
+ isReferenced && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-source-ref-badge", title: "Featured in response", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SparkleIcon2, { size: 10 }) })
1342
+ ] }),
1343
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hsk-cb-src-info", children: [
1344
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-src-name", children: src.name }),
1345
+ src.price && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hsk-cb-src-price", children: [
1346
+ src.currency ?? defaultCurrency,
1347
+ " ",
1348
+ parseFloat(String(src.price).replace(/[^0-9.]/g, "") || "0").toLocaleString()
1349
+ ] })
1235
1350
  ] })
1236
- ] })
1237
- ]
1238
- },
1239
- si
1240
- )) }),
1351
+ ]
1352
+ },
1353
+ si
1354
+ );
1355
+ }) }),
1241
1356
  showNext && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
1242
1357
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1243
1358
  "div",
@@ -1285,11 +1400,12 @@ function SmartContextPills({
1285
1400
  });
1286
1401
  }
1287
1402
  if (cheapest && !isProperty) {
1288
- const short = cheapest.name.split(" ").slice(0, 3).join(" ");
1403
+ const name = cheapest.name || "";
1404
+ const short = name.split(" ").slice(0, 3).join(" ");
1289
1405
  pills.push({
1290
1406
  emoji: "\u{1F6D2}",
1291
1407
  label: `Add ${short}`,
1292
- query: `Add the ${cheapest.name} to my cart`
1408
+ query: `Add the ${name} to my cart`
1293
1409
  });
1294
1410
  }
1295
1411
  if (isProperty) {
@@ -1299,11 +1415,12 @@ function SmartContextPills({
1299
1415
  }
1300
1416
  } else if (intent === "compare" && sources.length > 0) {
1301
1417
  if (cheapest && !isProperty) {
1302
- const short = cheapest.name.split(" ").slice(0, 3).join(" ");
1418
+ const name = cheapest.name || "";
1419
+ const short = name.split(" ").slice(0, 3).join(" ");
1303
1420
  pills.push({
1304
1421
  emoji: "\u{1F6D2}",
1305
1422
  label: `Add ${short}`,
1306
- query: `Add the ${cheapest.name} to my cart`
1423
+ query: `Add the ${name} to my cart`
1307
1424
  });
1308
1425
  }
1309
1426
  if (firstName) {
@@ -1406,7 +1523,7 @@ function ChatModal({
1406
1523
  visionCategoryHint
1407
1524
  }) {
1408
1525
  const client = (0, import_sdk6.useAkropolysContext)();
1409
- const { messages, sources, loading, streaming, error, lastAction, lastIntent, send, reset } = (0, import_sdk4.useKiku)();
1526
+ const { messages, sources, loading, streaming, error, lastAction, lastIntent, send, reset, referencedIds } = (0, import_sdk4.useKiku)();
1410
1527
  const [input, setInput] = (0, import_react5.useState)("");
1411
1528
  const [loadingMessageIndex, setLoadingMessageIndex] = (0, import_react5.useState)(0);
1412
1529
  const [attachments, setAttachments] = (0, import_react5.useState)([]);
@@ -1751,7 +1868,8 @@ function ChatModal({
1751
1868
  {
1752
1869
  sources,
1753
1870
  defaultCurrency,
1754
- onSelectSource: handleSourceClick
1871
+ onSelectSource: handleSourceClick,
1872
+ referencedIds
1755
1873
  }
1756
1874
  ),
1757
1875
  msg.cartSnapshot && msg.cartSnapshot.items?.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(