@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.mjs CHANGED
@@ -170,7 +170,7 @@ import { useKiku } from "@akropolys/sdk";
170
170
  // src/utils/markdown.tsx
171
171
  import { Fragment as Fragment2, jsx as jsx2 } from "react/jsx-runtime";
172
172
  var parseInline = (text, keyPrefix) => {
173
- const tokenRegex = /(\[[^\]]+\]\([^)]+\)|\*\*[^*]+\*\*|`[^`]+`)/g;
173
+ const tokenRegex = /(!\[[^\]]*\]\([^)]+\)|\[[^\]]+\]\([^)]+\)|\*\*[^*]+\*\*|`[^`]+`)/g;
174
174
  const parts = text.split(tokenRegex);
175
175
  return parts.map((part, index) => {
176
176
  if (!part) return null;
@@ -181,6 +181,28 @@ var parseInline = (text, keyPrefix) => {
181
181
  if (part.startsWith("**") && part.endsWith("**")) {
182
182
  return /* @__PURE__ */ jsx2("strong", { children: parseInline(part.slice(2, -2), key) }, key);
183
183
  }
184
+ const imageMatch = part.match(/^!\[([^\]]*)\]\(([^)]+)\)$/);
185
+ if (imageMatch) {
186
+ const alt = imageMatch[1];
187
+ const url = imageMatch[2];
188
+ const isSafeUrl = /^(https?|data:image):/i.test(url);
189
+ if (isSafeUrl) {
190
+ return /* @__PURE__ */ jsx2(
191
+ "img",
192
+ {
193
+ src: url,
194
+ alt: alt || "Product image",
195
+ className: "hsk-markdown-img",
196
+ loading: "lazy",
197
+ onError: (e) => {
198
+ e.target.style.display = "none";
199
+ }
200
+ },
201
+ key
202
+ );
203
+ }
204
+ return null;
205
+ }
184
206
  const linkMatch = part.match(/^\[([^\]]+)\]\(([^)]+)\)$/);
185
207
  if (linkMatch) {
186
208
  const url = linkMatch[2];
@@ -204,6 +226,30 @@ function renderMarkdown(content) {
204
226
  i++;
205
227
  continue;
206
228
  }
229
+ const standaloneImageMatch = line.trim().match(/^!\[([^\]]*)\]\(([^)]+)\)$/);
230
+ if (standaloneImageMatch) {
231
+ const alt = standaloneImageMatch[1];
232
+ const url = standaloneImageMatch[2];
233
+ const isSafeUrl = /^(https?|data:image):/i.test(url);
234
+ if (isSafeUrl) {
235
+ elements.push(
236
+ /* @__PURE__ */ jsx2("div", { className: "hsk-markdown-img-block", children: /* @__PURE__ */ jsx2(
237
+ "img",
238
+ {
239
+ src: url,
240
+ alt: alt || "Product image",
241
+ className: "hsk-markdown-img",
242
+ loading: "lazy",
243
+ onError: (e) => {
244
+ e.target.style.display = "none";
245
+ }
246
+ }
247
+ ) }, key)
248
+ );
249
+ }
250
+ i++;
251
+ continue;
252
+ }
207
253
  const headerMatch = line.match(/^(#{1,3})\s+(.*)/);
208
254
  if (headerMatch) {
209
255
  const level = headerMatch[1].length;
@@ -391,18 +437,31 @@ var ArrowUpIcon = () => /* @__PURE__ */ jsxs4("svg", { width: "18", height: "18"
391
437
  /* @__PURE__ */ jsx5("path", { d: "m5 12 7-7 7 7" }),
392
438
  /* @__PURE__ */ jsx5("path", { d: "M12 19V5" })
393
439
  ] });
394
- function SourceCard({ source, defaultCurrency, onSelect }) {
395
- return /* @__PURE__ */ jsxs4("div", { className: "hsk-source-card", onClick: () => onSelect?.(source), children: [
396
- source.image && /* @__PURE__ */ jsx5("img", { src: source.image, alt: source.name, className: "hsk-source-img" }),
397
- /* @__PURE__ */ jsxs4("div", { style: { flex: 1, minWidth: 0 }, children: [
398
- /* @__PURE__ */ jsx5("div", { className: "hsk-source-name", children: source.name }),
399
- source.price && /* @__PURE__ */ jsxs4("div", { className: "hsk-source-price", children: [
400
- source.currency ?? defaultCurrency,
401
- " ",
402
- source.price
403
- ] })
404
- ] })
405
- ] });
440
+ function SourceCard({
441
+ source,
442
+ defaultCurrency,
443
+ onSelect,
444
+ isReferenced
445
+ }) {
446
+ return /* @__PURE__ */ jsxs4(
447
+ "div",
448
+ {
449
+ className: cn("hsk-source-card", isReferenced && "hsk-source-card--referenced"),
450
+ onClick: () => onSelect?.(source),
451
+ children: [
452
+ source.image && /* @__PURE__ */ jsx5("img", { src: source.image, alt: source.name, className: "hsk-source-img" }),
453
+ /* @__PURE__ */ jsxs4("div", { style: { flex: 1, minWidth: 0, position: "relative" }, children: [
454
+ isReferenced && /* @__PURE__ */ jsx5("div", { className: "hsk-cb-source-ref-badge", title: "Featured in response", style: { top: "0", right: "0" }, children: /* @__PURE__ */ jsx5(SparkleIcon, {}) }),
455
+ /* @__PURE__ */ jsx5("div", { className: "hsk-source-name", style: { paddingRight: isReferenced ? "20px" : void 0 }, children: source.name }),
456
+ source.price && /* @__PURE__ */ jsxs4("div", { className: "hsk-source-price", children: [
457
+ source.currency ?? defaultCurrency,
458
+ " ",
459
+ source.price
460
+ ] })
461
+ ] })
462
+ ]
463
+ }
464
+ );
406
465
  }
407
466
  function ChatWidget({
408
467
  title = "AI Shopping Assistant",
@@ -418,7 +477,7 @@ function ChatWidget({
418
477
  enableVision = false,
419
478
  visionCategoryHint
420
479
  }) {
421
- const { messages, sources, loading: chatLoading, error, send, reset } = useKiku();
480
+ const { messages, sources, referencedIds, loading: chatLoading, streaming, error, send, reset } = useKiku();
422
481
  const [input, setInput] = useState4("");
423
482
  const [visualLoading, setVisualLoading] = useState4(false);
424
483
  const bottomRef = useRef4(null);
@@ -576,7 +635,54 @@ I couldn't find any matching products in the store.`;
576
635
  ] })
577
636
  ] }),
578
637
  msg.role === "assistant" && msg.visualSources && msg.visualSources.length > 0 && /* @__PURE__ */ jsx5("div", { className: "hsk-sources-container", children: /* @__PURE__ */ jsx5("div", { className: "hsk-sources", children: msg.visualSources.map((src, si) => /* @__PURE__ */ jsx5(SourceCard, { source: src, defaultCurrency, onSelect: onSelectSource }, si)) }) }),
579
- msg.role === "assistant" && idx === chatHistory.length - 1 && !msg.visualSources && sources.length > 0 && /* @__PURE__ */ jsx5("div", { className: "hsk-sources-container", children: /* @__PURE__ */ jsx5("div", { className: "hsk-sources", children: sources.map((src, si) => /* @__PURE__ */ jsx5(SourceCard, { source: src, defaultCurrency, onSelect: onSelectSource }, si)) }) })
638
+ msg.role === "assistant" && idx === chatHistory.length - 1 && !msg.visualSources && sources.length > 0 && (() => {
639
+ const isStreamingActive = chatLoading || streaming;
640
+ if (isStreamingActive) {
641
+ return /* @__PURE__ */ jsx5("div", { className: "hsk-sources-container", children: /* @__PURE__ */ jsx5("div", { className: "hsk-sources", children: sources.map((src, si) => {
642
+ const isReferenced = !!(src.id && referencedIds.includes(src.id));
643
+ return /* @__PURE__ */ jsx5(
644
+ SourceCard,
645
+ {
646
+ source: src,
647
+ defaultCurrency,
648
+ onSelect: onSelectSource,
649
+ isReferenced
650
+ },
651
+ si
652
+ );
653
+ }) }) });
654
+ }
655
+ const featured = sources.filter((src) => src.id && referencedIds.includes(src.id));
656
+ const general = sources.filter((src) => !src.id || !referencedIds.includes(src.id));
657
+ return /* @__PURE__ */ jsxs4("div", { className: "hsk-sources-container", children: [
658
+ featured.length > 0 && /* @__PURE__ */ jsxs4("div", { className: "hsk-sources-group", style: { marginBottom: "10px" }, children: [
659
+ /* @__PURE__ */ jsx5("div", { className: "hsk-sources-group-title", children: "\u2B50 Featured in response" }),
660
+ /* @__PURE__ */ jsx5("div", { className: "hsk-sources", children: featured.map((src, si) => /* @__PURE__ */ jsx5(
661
+ SourceCard,
662
+ {
663
+ source: src,
664
+ defaultCurrency,
665
+ onSelect: onSelectSource,
666
+ isReferenced: true
667
+ },
668
+ `feat-${si}`
669
+ )) })
670
+ ] }),
671
+ general.length > 0 && /* @__PURE__ */ jsxs4("div", { className: "hsk-sources-group", children: [
672
+ featured.length > 0 && /* @__PURE__ */ jsx5("div", { className: "hsk-sources-group-title", children: "All matches" }),
673
+ /* @__PURE__ */ jsx5("div", { className: "hsk-sources", children: general.map((src, si) => /* @__PURE__ */ jsx5(
674
+ SourceCard,
675
+ {
676
+ source: src,
677
+ defaultCurrency,
678
+ onSelect: onSelectSource,
679
+ isReferenced: false
680
+ },
681
+ `gen-${si}`
682
+ )) })
683
+ ] })
684
+ ] });
685
+ })()
580
686
  ] }, idx)),
581
687
  loading && /* @__PURE__ */ jsxs4("div", { className: "hsk-msg-row", children: [
582
688
  /* @__PURE__ */ jsx5("div", { className: "hsk-msg-avatar ai", children: /* @__PURE__ */ jsx5(SparkleIcon, {}) }),
@@ -1106,8 +1212,10 @@ function ActionPills({ cart, actionType, onSend, loading }) {
1106
1212
  const pills = [];
1107
1213
  if (hasItems && lastItem) {
1108
1214
  if (actionType === "add_to_cart") {
1109
- pills.push({ emoji: "\u2795", label: "Add 1 more", query: `Add one more ${lastItem.name} to my cart` });
1110
- pills.push({ emoji: "\u{1F5D1}\uFE0F", label: `Remove ${lastItem.name.split(" ")[0]}`, query: `Remove the ${lastItem.name} from my cart` });
1215
+ const name = lastItem.name || "item";
1216
+ const shortName = name.split(" ")[0] || "item";
1217
+ pills.push({ emoji: "\u2795", label: "Add 1 more", query: `Add one more ${name} to my cart` });
1218
+ pills.push({ emoji: "\u{1F5D1}\uFE0F", label: `Remove ${shortName}`, query: `Remove the ${name} from my cart` });
1111
1219
  }
1112
1220
  if (cart.items.length > 1) {
1113
1221
  pills.push({ emoji: "\u{1F6D2}", label: "View cart", query: "Show me my cart" });
@@ -1138,7 +1246,7 @@ function ActionPills({ cart, actionType, onSend, loading }) {
1138
1246
  pill.query
1139
1247
  )) });
1140
1248
  }
1141
- function SourcesCarousel({ sources, defaultCurrency, onSelectSource }) {
1249
+ function SourcesCarousel({ sources, defaultCurrency, onSelectSource, referencedIds = [] }) {
1142
1250
  const client = useAkropolysContext3();
1143
1251
  const isProperty = client?.vertical === "property";
1144
1252
  const railRef = useRef5(null);
@@ -1165,44 +1273,51 @@ function SourcesCarousel({ sources, defaultCurrency, onSelectSource }) {
1165
1273
  railRef.current?.scrollBy({ left: 170, behavior: "smooth" });
1166
1274
  };
1167
1275
  return /* @__PURE__ */ jsxs6("div", { className: "hsk-cb-sources-wrap", children: [
1168
- /* @__PURE__ */ jsx7("div", { className: "hsk-cb-sources", ref: railRef, children: sources.map((src, si) => /* @__PURE__ */ jsxs6(
1169
- "div",
1170
- {
1171
- className: "hsk-cb-source",
1172
- style: { animationDelay: `${si * 50}ms` },
1173
- onClick: () => onSelectSource?.(src),
1174
- children: [
1175
- src.image ? /* @__PURE__ */ jsxs6("div", { className: "hsk-cb-src-imgwrap", style: { position: "relative" }, children: [
1176
- /* @__PURE__ */ jsx7("img", { src: src.image, alt: src.name, loading: "lazy" }),
1177
- isProperty && /* @__PURE__ */ jsx7("div", { style: {
1178
- position: "absolute",
1179
- top: "6px",
1180
- right: "6px",
1181
- background: "rgba(14, 14, 15, 0.75)",
1182
- backdropFilter: "blur(4px)",
1183
- borderRadius: "50%",
1184
- width: "24px",
1185
- height: "24px",
1186
- display: "flex",
1187
- alignItems: "center",
1188
- justifyContent: "center",
1189
- color: "#fbbf24",
1190
- // Gold sparkle badge
1191
- boxShadow: "0 2px 4px rgba(0,0,0,0.2)"
1192
- }, children: /* @__PURE__ */ jsx7(SparkleIcon2, { size: 12 }) })
1193
- ] }) : /* @__PURE__ */ jsx7("div", { className: "hsk-cb-src-imgwrap-empty", children: /* @__PURE__ */ jsx7(SparkleIcon2, {}) }),
1194
- /* @__PURE__ */ jsxs6("div", { className: "hsk-cb-src-info", children: [
1195
- /* @__PURE__ */ jsx7("div", { className: "hsk-cb-src-name", children: src.name }),
1196
- src.price && /* @__PURE__ */ jsxs6("div", { className: "hsk-cb-src-price", children: [
1197
- src.currency ?? defaultCurrency,
1198
- " ",
1199
- parseFloat(String(src.price).replace(/[^0-9.]/g, "") || "0").toLocaleString()
1276
+ /* @__PURE__ */ jsx7("div", { className: "hsk-cb-sources", ref: railRef, children: sources.map((src, si) => {
1277
+ const isReferenced = !!(src.id && referencedIds.includes(src.id));
1278
+ return /* @__PURE__ */ jsxs6(
1279
+ "div",
1280
+ {
1281
+ className: cn("hsk-cb-source", isReferenced && "hsk-cb-source--referenced"),
1282
+ style: { animationDelay: `${si * 50}ms` },
1283
+ onClick: () => onSelectSource?.(src),
1284
+ children: [
1285
+ src.image ? /* @__PURE__ */ jsxs6("div", { className: "hsk-cb-src-imgwrap", style: { position: "relative" }, children: [
1286
+ /* @__PURE__ */ jsx7("img", { src: src.image, alt: src.name, loading: "lazy" }),
1287
+ isReferenced && /* @__PURE__ */ jsx7("div", { className: "hsk-cb-source-ref-badge", title: "Featured in response", children: /* @__PURE__ */ jsx7(SparkleIcon2, { size: 10 }) }),
1288
+ isProperty && /* @__PURE__ */ jsx7("div", { style: {
1289
+ position: "absolute",
1290
+ top: "6px",
1291
+ right: "6px",
1292
+ background: "rgba(14, 14, 15, 0.75)",
1293
+ backdropFilter: "blur(4px)",
1294
+ borderRadius: "50%",
1295
+ width: "24px",
1296
+ height: "24px",
1297
+ display: "flex",
1298
+ alignItems: "center",
1299
+ justifyContent: "center",
1300
+ color: "#fbbf24",
1301
+ // Gold sparkle badge
1302
+ boxShadow: "0 2px 4px rgba(0,0,0,0.2)"
1303
+ }, children: /* @__PURE__ */ jsx7(SparkleIcon2, { size: 12 }) })
1304
+ ] }) : /* @__PURE__ */ jsxs6("div", { className: "hsk-cb-src-imgwrap-empty", style: { position: "relative" }, children: [
1305
+ /* @__PURE__ */ jsx7(SparkleIcon2, {}),
1306
+ isReferenced && /* @__PURE__ */ jsx7("div", { className: "hsk-cb-source-ref-badge", title: "Featured in response", children: /* @__PURE__ */ jsx7(SparkleIcon2, { size: 10 }) })
1307
+ ] }),
1308
+ /* @__PURE__ */ jsxs6("div", { className: "hsk-cb-src-info", children: [
1309
+ /* @__PURE__ */ jsx7("div", { className: "hsk-cb-src-name", children: src.name }),
1310
+ src.price && /* @__PURE__ */ jsxs6("div", { className: "hsk-cb-src-price", children: [
1311
+ src.currency ?? defaultCurrency,
1312
+ " ",
1313
+ parseFloat(String(src.price).replace(/[^0-9.]/g, "") || "0").toLocaleString()
1314
+ ] })
1200
1315
  ] })
1201
- ] })
1202
- ]
1203
- },
1204
- si
1205
- )) }),
1316
+ ]
1317
+ },
1318
+ si
1319
+ );
1320
+ }) }),
1206
1321
  showNext && /* @__PURE__ */ jsxs6(Fragment3, { children: [
1207
1322
  /* @__PURE__ */ jsx7(
1208
1323
  "div",
@@ -1250,11 +1365,12 @@ function SmartContextPills({
1250
1365
  });
1251
1366
  }
1252
1367
  if (cheapest && !isProperty) {
1253
- const short = cheapest.name.split(" ").slice(0, 3).join(" ");
1368
+ const name = cheapest.name || "";
1369
+ const short = name.split(" ").slice(0, 3).join(" ");
1254
1370
  pills.push({
1255
1371
  emoji: "\u{1F6D2}",
1256
1372
  label: `Add ${short}`,
1257
- query: `Add the ${cheapest.name} to my cart`
1373
+ query: `Add the ${name} to my cart`
1258
1374
  });
1259
1375
  }
1260
1376
  if (isProperty) {
@@ -1264,11 +1380,12 @@ function SmartContextPills({
1264
1380
  }
1265
1381
  } else if (intent === "compare" && sources.length > 0) {
1266
1382
  if (cheapest && !isProperty) {
1267
- const short = cheapest.name.split(" ").slice(0, 3).join(" ");
1383
+ const name = cheapest.name || "";
1384
+ const short = name.split(" ").slice(0, 3).join(" ");
1268
1385
  pills.push({
1269
1386
  emoji: "\u{1F6D2}",
1270
1387
  label: `Add ${short}`,
1271
- query: `Add the ${cheapest.name} to my cart`
1388
+ query: `Add the ${name} to my cart`
1272
1389
  });
1273
1390
  }
1274
1391
  if (firstName) {
@@ -1371,7 +1488,7 @@ function ChatModal({
1371
1488
  visionCategoryHint
1372
1489
  }) {
1373
1490
  const client = useAkropolysContext3();
1374
- const { messages, sources, loading, streaming, error, lastAction, lastIntent, send, reset } = useKiku2();
1491
+ const { messages, sources, loading, streaming, error, lastAction, lastIntent, send, reset, referencedIds } = useKiku2();
1375
1492
  const [input, setInput] = useState5("");
1376
1493
  const [loadingMessageIndex, setLoadingMessageIndex] = useState5(0);
1377
1494
  const [attachments, setAttachments] = useState5([]);
@@ -1716,7 +1833,8 @@ function ChatModal({
1716
1833
  {
1717
1834
  sources,
1718
1835
  defaultCurrency,
1719
- onSelectSource: handleSourceClick
1836
+ onSelectSource: handleSourceClick,
1837
+ referencedIds
1720
1838
  }
1721
1839
  ),
1722
1840
  msg.cartSnapshot && msg.cartSnapshot.items?.length > 0 && /* @__PURE__ */ jsx7(