@apteva/apteva-kit 0.1.69 → 0.1.70

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.d.mts CHANGED
@@ -23,6 +23,8 @@ interface Widget {
23
23
  actions?: Action[];
24
24
  /** Additional data not displayed but captured via onWidgetRender */
25
25
  meta?: Record<string, any>;
26
+ /** Whether the widget is still receiving streaming data */
27
+ isStreaming?: boolean;
26
28
  }
27
29
  interface CardWidget extends Widget {
28
30
  type: 'card';
@@ -392,7 +394,9 @@ interface CardProps {
392
394
  declare function Card({ widget, onAction }: CardProps): react_jsx_runtime.JSX.Element;
393
395
 
394
396
  interface ListProps {
395
- widget: ListWidget;
397
+ widget: ListWidget & {
398
+ isStreaming?: boolean;
399
+ };
396
400
  onAction?: (action: ActionEvent) => void;
397
401
  }
398
402
  declare function List({ widget, onAction }: ListProps): react_jsx_runtime.JSX.Element;
package/dist/index.d.ts CHANGED
@@ -23,6 +23,8 @@ interface Widget {
23
23
  actions?: Action[];
24
24
  /** Additional data not displayed but captured via onWidgetRender */
25
25
  meta?: Record<string, any>;
26
+ /** Whether the widget is still receiving streaming data */
27
+ isStreaming?: boolean;
26
28
  }
27
29
  interface CardWidget extends Widget {
28
30
  type: 'card';
@@ -392,7 +394,9 @@ interface CardProps {
392
394
  declare function Card({ widget, onAction }: CardProps): react_jsx_runtime.JSX.Element;
393
395
 
394
396
  interface ListProps {
395
- widget: ListWidget;
397
+ widget: ListWidget & {
398
+ isStreaming?: boolean;
399
+ };
396
400
  onAction?: (action: ActionEvent) => void;
397
401
  }
398
402
  declare function List({ widget, onAction }: ListProps): react_jsx_runtime.JSX.Element;
package/dist/index.js CHANGED
@@ -502,6 +502,71 @@ function validateFile(file) {
502
502
  }
503
503
 
504
504
  // src/utils/widget-parser.ts
505
+ var STREAMABLE_WIDGET_TYPES = ["list", "table"];
506
+ function parsePartialItemsArray(partialJson) {
507
+ const items = [];
508
+ let isStreaming = false;
509
+ const itemsMatch = partialJson.match(/"items"\s*:\s*\[/);
510
+ if (!itemsMatch) {
511
+ return { items, isStreaming: false };
512
+ }
513
+ const arrayStart = partialJson.indexOf("[", itemsMatch.index);
514
+ if (arrayStart === -1) {
515
+ return { items, isStreaming: true };
516
+ }
517
+ let depth = 0;
518
+ let inString = false;
519
+ let escapeNext = false;
520
+ let objectStart = -1;
521
+ for (let i = arrayStart + 1; i < partialJson.length; i++) {
522
+ const char = partialJson[i];
523
+ if (escapeNext) {
524
+ escapeNext = false;
525
+ continue;
526
+ }
527
+ if (char === "\\" && inString) {
528
+ escapeNext = true;
529
+ continue;
530
+ }
531
+ if (char === '"') {
532
+ inString = !inString;
533
+ continue;
534
+ }
535
+ if (inString) continue;
536
+ if (char === "{") {
537
+ if (depth === 0) {
538
+ objectStart = i;
539
+ }
540
+ depth++;
541
+ } else if (char === "}") {
542
+ depth--;
543
+ if (depth === 0 && objectStart !== -1) {
544
+ const objectJson = partialJson.slice(objectStart, i + 1);
545
+ try {
546
+ const item = JSON.parse(objectJson);
547
+ if (!item.id) {
548
+ item.id = `item-${items.length}-${simpleHash(objectJson)}`;
549
+ }
550
+ items.push(item);
551
+ } catch (e) {
552
+ }
553
+ objectStart = -1;
554
+ }
555
+ } else if (char === "]" && depth === 0) {
556
+ isStreaming = false;
557
+ break;
558
+ }
559
+ }
560
+ if (depth > 0 || objectStart !== -1) {
561
+ isStreaming = true;
562
+ }
563
+ const afterItems = partialJson.slice(arrayStart);
564
+ const closingBracket = findMatchingBracket(afterItems, 0);
565
+ if (closingBracket === -1) {
566
+ isStreaming = true;
567
+ }
568
+ return { items, isStreaming };
569
+ }
505
570
  function simpleHash(str) {
506
571
  let hash = 0;
507
572
  for (let i = 0; i < str.length; i++) {
@@ -563,9 +628,38 @@ function parseWidgetsFromText(text) {
563
628
  const fullBracketStart = lastWidgetStart + bracketOpenIndex;
564
629
  const bracketEnd = findMatchingBracket(text, fullBracketStart);
565
630
  if (bracketEnd === -1) {
566
- processText = text.slice(0, lastWidgetStart);
567
- pendingWidgetType = widgetType;
568
- hasPendingWidget = true;
631
+ if (STREAMABLE_WIDGET_TYPES.includes(widgetType)) {
632
+ const partialContent = text.slice(fullBracketStart + 1);
633
+ const { items, isStreaming } = parsePartialItemsArray(partialContent);
634
+ if (items.length > 0) {
635
+ processText = text.slice(0, lastWidgetStart);
636
+ const widgetId = `widget-${widgetType}-streaming-${simpleHash(partialContent)}`;
637
+ const textBefore = processText.replace(/[\s:;\-–—\.]+$/g, "").trim();
638
+ if (textBefore) {
639
+ segments.push({ type: "text", content: textBefore });
640
+ }
641
+ segments.push({
642
+ type: "widget",
643
+ widget: {
644
+ type: widgetType,
645
+ id: widgetId,
646
+ props: widgetType === "table" ? { rows: items, columns: [] } : { items },
647
+ isStreaming: true
648
+ }
649
+ });
650
+ hasWidgets = true;
651
+ hasPendingWidget = false;
652
+ processText = "";
653
+ } else {
654
+ processText = text.slice(0, lastWidgetStart);
655
+ pendingWidgetType = widgetType;
656
+ hasPendingWidget = true;
657
+ }
658
+ } else {
659
+ processText = text.slice(0, lastWidgetStart);
660
+ pendingWidgetType = widgetType;
661
+ hasPendingWidget = true;
662
+ }
569
663
  }
570
664
  }
571
665
  }
@@ -729,38 +823,73 @@ function Card({ widget, onAction }) {
729
823
 
730
824
  // src/components/Widgets/widget-library/List.tsx
731
825
 
826
+
732
827
  function List({ widget, onAction }) {
733
828
  const { items } = widget.props;
734
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "border border-neutral-200 dark:border-neutral-700 rounded-xl bg-white dark:bg-neutral-900 overflow-hidden", children: items.map((item, index) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
735
- "div",
736
- {
737
- className: `flex items-center p-4 transition-colors ${index !== items.length - 1 ? "border-b border-neutral-200 dark:border-neutral-700" : ""} ${!item.backgroundColor ? "hover:bg-neutral-50 dark:hover:bg-neutral-800" : ""}`,
738
- style: item.backgroundColor ? { backgroundColor: item.backgroundColor } : void 0,
739
- children: [
740
- item.image && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "img", { src: item.image, alt: item.title, className: "w-16 h-16 rounded object-cover" }),
741
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: `flex-1 ${item.image ? "ml-4" : ""}`, children: [
742
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h4", { className: "font-semibold !text-neutral-900 dark:!text-white", children: item.title }),
743
- item.subtitle && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "!text-sm !text-neutral-600 dark:!text-neutral-400", children: item.subtitle }),
744
- item.description && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "!text-xs !text-neutral-500 dark:!text-neutral-500 mt-1", children: item.description })
745
- ] }),
746
- widget.actions && widget.actions.length > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "flex gap-2", children: widget.actions.map((action, idx) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
747
- "button",
748
- {
749
- onClick: () => _optionalChain([onAction, 'optionalCall', _3 => _3({
750
- type: action.type,
751
- payload: item.metadata || item,
752
- widgetId: widget.id,
753
- timestamp: /* @__PURE__ */ new Date()
754
- })]),
755
- className: "px-3 py-1.5 !text-sm rounded-lg font-medium transition-colors bg-blue-500 !text-white hover:bg-blue-600",
756
- children: action.label
757
- },
758
- idx
759
- )) })
760
- ]
761
- },
762
- item.id
763
- )) });
829
+ const isStreaming = _nullishCoalesce(widget.isStreaming, () => ( false));
830
+ const seenItemsRef = _react.useRef.call(void 0, /* @__PURE__ */ new Set());
831
+ const [newItemIds, setNewItemIds] = _react.useState.call(void 0, /* @__PURE__ */ new Set());
832
+ _react.useEffect.call(void 0, () => {
833
+ const currentIds = new Set(items.map((item) => item.id));
834
+ const newIds = /* @__PURE__ */ new Set();
835
+ items.forEach((item) => {
836
+ if (!seenItemsRef.current.has(item.id)) {
837
+ newIds.add(item.id);
838
+ }
839
+ });
840
+ items.forEach((item) => seenItemsRef.current.add(item.id));
841
+ if (newIds.size > 0) {
842
+ setNewItemIds(newIds);
843
+ const timer = setTimeout(() => {
844
+ setNewItemIds(/* @__PURE__ */ new Set());
845
+ }, 500);
846
+ return () => clearTimeout(timer);
847
+ }
848
+ }, [items]);
849
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "border border-neutral-200 dark:border-neutral-700 rounded-xl bg-white dark:bg-neutral-900 overflow-hidden", children: [
850
+ items.map((item, index) => {
851
+ const isNew = newItemIds.has(item.id);
852
+ const isLast = index === items.length - 1;
853
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
854
+ "div",
855
+ {
856
+ className: `apteva-list-item flex items-center p-4 transition-colors ${!isLast || isStreaming ? "border-b border-neutral-200 dark:border-neutral-700" : ""} ${!item.backgroundColor ? "hover:bg-neutral-50 dark:hover:bg-neutral-800" : ""} ${isNew ? "apteva-list-item-new" : ""}`,
857
+ style: item.backgroundColor ? { backgroundColor: item.backgroundColor } : void 0,
858
+ children: [
859
+ item.image && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "img", { src: item.image, alt: item.title, className: "w-16 h-16 rounded object-cover" }),
860
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: `flex-1 ${item.image ? "ml-4" : ""}`, children: [
861
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h4", { className: "font-semibold !text-neutral-900 dark:!text-white", children: item.title }),
862
+ item.subtitle && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "!text-sm !text-neutral-600 dark:!text-neutral-400", children: item.subtitle }),
863
+ item.description && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "!text-xs !text-neutral-500 dark:!text-neutral-500 mt-1", children: item.description })
864
+ ] }),
865
+ widget.actions && widget.actions.length > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "flex gap-2", children: widget.actions.map((action, idx) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
866
+ "button",
867
+ {
868
+ onClick: () => _optionalChain([onAction, 'optionalCall', _3 => _3({
869
+ type: action.type,
870
+ payload: item.metadata || item,
871
+ widgetId: widget.id,
872
+ timestamp: /* @__PURE__ */ new Date()
873
+ })]),
874
+ className: "px-3 py-1.5 !text-sm rounded-lg font-medium transition-colors bg-blue-500 !text-white hover:bg-blue-600",
875
+ children: action.label
876
+ },
877
+ idx
878
+ )) })
879
+ ]
880
+ },
881
+ item.id
882
+ );
883
+ }),
884
+ isStreaming && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "apteva-list-streaming flex items-center gap-3 p-4 text-neutral-500 dark:text-neutral-400", children: [
885
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "apteva-streaming-dots flex gap-1", children: [
886
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "w-2 h-2 bg-current rounded-full animate-pulse", style: { animationDelay: "0ms" } }),
887
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "w-2 h-2 bg-current rounded-full animate-pulse", style: { animationDelay: "150ms" } }),
888
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "w-2 h-2 bg-current rounded-full animate-pulse", style: { animationDelay: "300ms" } })
889
+ ] }),
890
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-sm", children: "Loading more..." })
891
+ ] })
892
+ ] });
764
893
  }
765
894
 
766
895
  // src/components/Widgets/widget-library/Button.tsx
@@ -840,8 +969,32 @@ function ButtonGroup({ widget, onAction }) {
840
969
 
841
970
  // src/components/Widgets/widget-library/Table.tsx
842
971
 
972
+
843
973
  function Table({ widget, onAction }) {
844
974
  const { columns, rows, caption, compact = false, striped = false } = widget.props;
975
+ const isStreaming = _nullishCoalesce(widget.isStreaming, () => ( false));
976
+ const seenRowsRef = _react.useRef.call(void 0, /* @__PURE__ */ new Set());
977
+ const [newRowIds, setNewRowIds] = _react.useState.call(void 0, /* @__PURE__ */ new Set());
978
+ _react.useEffect.call(void 0, () => {
979
+ const newIds = /* @__PURE__ */ new Set();
980
+ rows.forEach((row, index) => {
981
+ const rowId = row.id || `row-${index}`;
982
+ if (!seenRowsRef.current.has(rowId)) {
983
+ newIds.add(rowId);
984
+ }
985
+ });
986
+ rows.forEach((row, index) => {
987
+ const rowId = row.id || `row-${index}`;
988
+ seenRowsRef.current.add(rowId);
989
+ });
990
+ if (newIds.size > 0) {
991
+ setNewRowIds(newIds);
992
+ const timer = setTimeout(() => {
993
+ setNewRowIds(/* @__PURE__ */ new Set());
994
+ }, 500);
995
+ return () => clearTimeout(timer);
996
+ }
997
+ }, [rows]);
845
998
  const getAlignment = (align) => {
846
999
  switch (align) {
847
1000
  case "center":
@@ -868,47 +1021,67 @@ function Table({ widget, onAction }) {
868
1021
  column.key
869
1022
  )) }) }),
870
1023
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "tbody", { children: [
871
- rows.map((row, rowIndex) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
872
- "tr",
873
- {
874
- className: cn(
875
- "border-b border-neutral-200 dark:border-neutral-700 last:border-b-0",
876
- "transition-colors hover:bg-neutral-50 dark:hover:bg-neutral-800",
877
- striped && rowIndex % 2 === 1 && "bg-neutral-50/50 dark:bg-neutral-800/50"
878
- ),
879
- onClick: () => {
880
- if (widget.actions && widget.actions.length > 0) {
881
- _optionalChain([onAction, 'optionalCall', _15 => _15({
882
- type: widget.actions[0].type,
883
- payload: row,
884
- widgetId: widget.id,
885
- timestamp: /* @__PURE__ */ new Date()
886
- })]);
887
- }
888
- },
889
- style: { cursor: _optionalChain([widget, 'access', _16 => _16.actions, 'optionalAccess', _17 => _17.length]) ? "pointer" : "default" },
890
- children: columns.map((column) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
891
- "td",
892
- {
893
- className: cn(
894
- "text-neutral-700 dark:text-neutral-300",
895
- compact ? "px-3 py-2 text-xs" : "px-4 py-3 text-sm",
896
- getAlignment(column.align)
897
- ),
898
- children: _nullishCoalesce(row[column.key], () => ( "\u2014"))
1024
+ rows.map((row, rowIndex) => {
1025
+ const rowId = row.id || `row-${rowIndex}`;
1026
+ const isNew = newRowIds.has(rowId);
1027
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1028
+ "tr",
1029
+ {
1030
+ className: cn(
1031
+ "apteva-table-row border-b border-neutral-200 dark:border-neutral-700 last:border-b-0",
1032
+ "transition-colors hover:bg-neutral-50 dark:hover:bg-neutral-800",
1033
+ striped && rowIndex % 2 === 1 && "bg-neutral-50/50 dark:bg-neutral-800/50",
1034
+ isNew && "apteva-table-row-new"
1035
+ ),
1036
+ onClick: () => {
1037
+ if (widget.actions && widget.actions.length > 0) {
1038
+ _optionalChain([onAction, 'optionalCall', _15 => _15({
1039
+ type: widget.actions[0].type,
1040
+ payload: row,
1041
+ widgetId: widget.id,
1042
+ timestamp: /* @__PURE__ */ new Date()
1043
+ })]);
1044
+ }
899
1045
  },
900
- column.key
901
- ))
902
- },
903
- row.id || rowIndex
904
- )),
905
- rows.length === 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "tr", { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1046
+ style: { cursor: _optionalChain([widget, 'access', _16 => _16.actions, 'optionalAccess', _17 => _17.length]) ? "pointer" : "default" },
1047
+ children: columns.map((column) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1048
+ "td",
1049
+ {
1050
+ className: cn(
1051
+ "text-neutral-700 dark:text-neutral-300",
1052
+ compact ? "px-3 py-2 text-xs" : "px-4 py-3 text-sm",
1053
+ getAlignment(column.align)
1054
+ ),
1055
+ children: _nullishCoalesce(row[column.key], () => ( "\u2014"))
1056
+ },
1057
+ column.key
1058
+ ))
1059
+ },
1060
+ rowId
1061
+ );
1062
+ }),
1063
+ rows.length === 0 && !isStreaming && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "tr", { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
906
1064
  "td",
907
1065
  {
908
- colSpan: columns.length,
1066
+ colSpan: columns.length || 1,
909
1067
  className: "px-4 py-8 text-center text-sm text-neutral-500 dark:text-neutral-400",
910
1068
  children: "No data available"
911
1069
  }
1070
+ ) }),
1071
+ isStreaming && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "tr", { className: "apteva-table-streaming", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1072
+ "td",
1073
+ {
1074
+ colSpan: columns.length || 1,
1075
+ className: "px-4 py-3 text-center",
1076
+ children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center justify-center gap-3 text-neutral-500 dark:text-neutral-400", children: [
1077
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex gap-1", children: [
1078
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "w-2 h-2 bg-current rounded-full animate-pulse", style: { animationDelay: "0ms" } }),
1079
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "w-2 h-2 bg-current rounded-full animate-pulse", style: { animationDelay: "150ms" } }),
1080
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "w-2 h-2 bg-current rounded-full animate-pulse", style: { animationDelay: "300ms" } })
1081
+ ] }),
1082
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-sm", children: "Loading more..." })
1083
+ ] })
1084
+ }
912
1085
  ) })
913
1086
  ] })
914
1087
  ] }) }) });