@smallwebco/tinypivot-react 1.0.55 → 1.0.58

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/README.md CHANGED
@@ -1,14 +1,15 @@
1
1
  # @smallwebco/tinypivot-react
2
2
 
3
- A lightweight, AI-ready data grid with pivot tables and charts for React. **Under 40KB gzipped** — 10x smaller than AG Grid.
3
+ **Embed an AI Data Analyst in your React app.** Ask questions in plain English, get instant SQL-powered insights. AI-enabled data grid with pivot tables and charts all under 40KB gzipped.
4
4
 
5
5
  **[Live Demo](https://tiny-pivot.com)** · **[Buy License](https://tiny-pivot.com/#pricing)**
6
6
 
7
7
  ## Why TinyPivot?
8
8
 
9
- - **Lightweight**: Under 40KB gzipped vs 500KB+ for AG Grid
10
- - **AI-Ready**: Natural language data exploration with your own API key (BYOK)
11
- - **Batteries Included**: Pivot tables, charts, and Excel-like features out of the box
9
+ - **Embedded AI Data Analyst**: Let users ask questions in plain English — AI generates SQL and returns results instantly
10
+ - **Bring Your Own Key (BYOK)**: Use your own OpenAI, Anthropic, or any LLM API key — full control over costs and privacy
11
+ - **Lightweight**: Under 40KB gzipped vs 500KB+ for AG Grid (10x smaller)
12
+ - **Batteries Included**: Pivot tables, 6 chart types, Excel-like features out of the box
12
13
  - **One-Time License**: No subscriptions — pay once, use forever
13
14
 
14
15
  ## Installation
@@ -88,9 +89,9 @@ export default function App() {
88
89
  | `onExport` | `(payload) => void` | CSV exported |
89
90
  | `onCopy` | `(payload) => void` | Cells copied |
90
91
 
91
- ## AI Data Analyst (Pro)
92
+ ## Embedded AI Data Analyst (Pro)
92
93
 
93
- Enable natural language data exploration with your own AI API key (BYOK).
94
+ Embed an AI-powered data analyst that lets users explore data using natural language. Ask questions like "What's the return rate by category?" and get instant results — no SQL knowledge required for end users.
94
95
 
95
96
  ```tsx
96
97
  import { DataGrid } from '@smallwebco/tinypivot-react'
package/dist/index.cjs CHANGED
@@ -567,6 +567,82 @@ What would you like to know about this data?`
567
567
  setIsLoading(false);
568
568
  }
569
569
  }, [isLoading, schemas, effectiveDataSources, callAIEndpoint, executeQuery, handleDemoResponse, onConversationUpdate, onError]);
570
+ const loadFullData = (0, import_react.useCallback)(async () => {
571
+ const dataSourceId = conversation.dataSourceId;
572
+ if (!dataSourceId) {
573
+ return null;
574
+ }
575
+ const dataSource = effectiveDataSources.find((ds) => ds.id === dataSourceId);
576
+ if (!dataSource) {
577
+ return null;
578
+ }
579
+ const currentConfig = configRef.current;
580
+ if (currentConfig.dataSourceLoader) {
581
+ try {
582
+ const { data } = await currentConfig.dataSourceLoader(dataSourceId);
583
+ if (data && data.length > 0) {
584
+ return data;
585
+ }
586
+ } catch (err) {
587
+ console.warn("Failed to load full data:", err);
588
+ onError?.({
589
+ message: err instanceof Error ? err.message : "Failed to load full data",
590
+ type: "network"
591
+ });
592
+ }
593
+ return null;
594
+ }
595
+ if (currentConfig.queryExecutor) {
596
+ try {
597
+ const result = await currentConfig.queryExecutor(
598
+ `SELECT * FROM ${dataSource.table}`,
599
+ dataSource.table
600
+ );
601
+ if (result.data && result.data.length > 0) {
602
+ return result.data;
603
+ }
604
+ } catch (err) {
605
+ console.warn("Failed to load full data via query:", err);
606
+ onError?.({
607
+ message: err instanceof Error ? err.message : "Failed to load full data",
608
+ type: "network"
609
+ });
610
+ }
611
+ return null;
612
+ }
613
+ if (currentConfig.endpoint) {
614
+ try {
615
+ const response = await fetch(currentConfig.endpoint, {
616
+ method: "POST",
617
+ headers: { "Content-Type": "application/json" },
618
+ body: JSON.stringify({
619
+ action: "query",
620
+ sql: `SELECT * FROM ${dataSource.table}`,
621
+ table: dataSource.table
622
+ })
623
+ });
624
+ if (!response.ok) {
625
+ throw new Error(`Failed to load data: ${response.statusText}`);
626
+ }
627
+ const data = await response.json();
628
+ if (data.data && data.data.length > 0) {
629
+ return data.data;
630
+ }
631
+ } catch (err) {
632
+ console.warn("Failed to load full data from endpoint:", err);
633
+ onError?.({
634
+ message: err instanceof Error ? err.message : "Failed to load full data",
635
+ type: "network"
636
+ });
637
+ }
638
+ return null;
639
+ }
640
+ if (currentConfig.demoMode) {
641
+ const initialData = (0, import_tinypivot_core.getInitialDemoData)(dataSourceId);
642
+ return initialData || null;
643
+ }
644
+ return null;
645
+ }, [conversation.dataSourceId, effectiveDataSources, onError]);
570
646
  const clearConversation = (0, import_react.useCallback)(() => {
571
647
  const newConv = (0, import_tinypivot_core.createConversation)(configRef.current.sessionId);
572
648
  setConversation(newConv);
@@ -602,13 +678,15 @@ What would you like to know about this data?`
602
678
  exportConversation,
603
679
  importConversation,
604
680
  /** Refresh table list from endpoint */
605
- fetchTables
681
+ fetchTables,
682
+ /** Load full data for the currently selected data source */
683
+ loadFullData
606
684
  };
607
685
  }
608
686
 
609
687
  // src/components/AIAnalyst.tsx
610
688
  var import_jsx_runtime = require("react/jsx-runtime");
611
- function AIAnalyst({
689
+ var AIAnalyst = (0, import_react2.forwardRef)(({
612
690
  config,
613
691
  theme = "light",
614
692
  onDataLoaded,
@@ -616,7 +694,7 @@ function AIAnalyst({
616
694
  onQueryExecuted,
617
695
  onError,
618
696
  onViewResults
619
- }) {
697
+ }, ref) => {
620
698
  const {
621
699
  messages,
622
700
  hasMessages,
@@ -629,7 +707,8 @@ function AIAnalyst({
629
707
  dataSources,
630
708
  selectDataSource,
631
709
  sendMessage,
632
- clearConversation
710
+ clearConversation,
711
+ loadFullData
633
712
  } = useAIAnalyst({
634
713
  config,
635
714
  onDataLoaded,
@@ -637,6 +716,10 @@ function AIAnalyst({
637
716
  onQueryExecuted,
638
717
  onError
639
718
  });
719
+ (0, import_react2.useImperativeHandle)(ref, () => ({
720
+ loadFullData,
721
+ selectedDataSource
722
+ }), [loadFullData, selectedDataSource]);
640
723
  const [inputText, setInputText] = (0, import_react2.useState)("");
641
724
  const [searchQuery, setSearchQuery] = (0, import_react2.useState)("");
642
725
  const [selectedMessageId, setSelectedMessageId] = (0, import_react2.useState)(null);
@@ -1145,7 +1228,7 @@ function AIAnalyst({
1145
1228
  )
1146
1229
  ] })
1147
1230
  ] }) });
1148
- }
1231
+ });
1149
1232
 
1150
1233
  // src/components/CalculatedFieldModal.tsx
1151
1234
  var import_tinypivot_core3 = require("@smallwebco/tinypivot-core");
@@ -4639,7 +4722,9 @@ function DataGrid({
4639
4722
  const [showCopyToast, setShowCopyToast] = (0, import_react13.useState)(false);
4640
4723
  const [copyToastMessage, setCopyToastMessage] = (0, import_react13.useState)("");
4641
4724
  const [viewMode, setViewMode] = (0, import_react13.useState)("grid");
4725
+ const aiAnalystRef = (0, import_react13.useRef)(null);
4642
4726
  const [aiLoadedData, setAiLoadedData] = (0, import_react13.useState)(null);
4727
+ const [isLoadingFullData, setIsLoadingFullData] = (0, import_react13.useState)(false);
4643
4728
  const displayData = (0, import_react13.useMemo)(() => aiLoadedData || data, [aiLoadedData, data]);
4644
4729
  const [_chartConfig, setChartConfig] = (0, import_react13.useState)(null);
4645
4730
  const handleChartConfigChange = (0, import_react13.useCallback)((config) => {
@@ -4997,9 +5082,27 @@ function DataGrid({
4997
5082
  [isSelecting]
4998
5083
  );
4999
5084
  const isShowingAIData = aiLoadedData !== null;
5000
- const resetToFullData = (0, import_react13.useCallback)(() => {
5001
- setAiLoadedData(null);
5002
- }, []);
5085
+ const resetToFullData = (0, import_react13.useCallback)(async () => {
5086
+ if (aiAnalystRef.current?.selectedDataSource) {
5087
+ setIsLoadingFullData(true);
5088
+ try {
5089
+ const fullData = await aiAnalystRef.current.loadFullData();
5090
+ if (fullData && fullData.length > 0) {
5091
+ setAiLoadedData(fullData);
5092
+ } else {
5093
+ setAiLoadedData(null);
5094
+ }
5095
+ } catch (err) {
5096
+ console.warn("Failed to load full data:", err);
5097
+ setAiLoadedData(null);
5098
+ } finally {
5099
+ setIsLoadingFullData(false);
5100
+ }
5101
+ } else {
5102
+ setAiLoadedData(null);
5103
+ }
5104
+ clearAllFilters();
5105
+ }, [clearAllFilters]);
5003
5106
  const handleAIDataLoaded = (0, import_react13.useCallback)(
5004
5107
  (payload) => {
5005
5108
  setAiLoadedData(payload.data);
@@ -5417,11 +5520,12 @@ function DataGrid({
5417
5520
  viewMode === "grid" && isShowingAIData && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
5418
5521
  "button",
5419
5522
  {
5420
- className: "vpg-reset-data-btn",
5523
+ className: `vpg-reset-data-btn${isLoadingFullData ? " vpg-loading-btn" : ""}`,
5524
+ disabled: isLoadingFullData,
5421
5525
  title: "Reset to full dataset",
5422
5526
  onClick: resetToFullData,
5423
5527
  children: [
5424
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("svg", { className: "vpg-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
5528
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("svg", { className: `vpg-icon${isLoadingFullData ? " vpg-spin" : ""}`, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
5425
5529
  "path",
5426
5530
  {
5427
5531
  strokeLinecap: "round",
@@ -5430,7 +5534,7 @@ function DataGrid({
5430
5534
  d: "M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
5431
5535
  }
5432
5536
  ) }),
5433
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Full Data" })
5537
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: isLoadingFullData ? "Loading..." : "Full Data" })
5434
5538
  ]
5435
5539
  }
5436
5540
  ),
@@ -5498,6 +5602,7 @@ function DataGrid({
5498
5602
  showAIAnalyst && aiAnalyst && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "vpg-ai-view", style: { display: viewMode === "ai" ? void 0 : "none" }, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
5499
5603
  AIAnalyst,
5500
5604
  {
5605
+ ref: aiAnalystRef,
5501
5606
  config: aiAnalyst,
5502
5607
  theme: currentTheme,
5503
5608
  onDataLoaded: handleAIDataLoaded,