bolt-table 0.1.37 → 0.1.38
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 +24 -2
- package/dist/index.d.ts +24 -2
- package/dist/index.js +318 -48
- package/dist/index.mjs +318 -48
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -251,8 +251,30 @@ interface AIColumnVisibilityOperation {
|
|
|
251
251
|
type: "hideColumns" | "showColumns";
|
|
252
252
|
columns: string[];
|
|
253
253
|
}
|
|
254
|
+
/** Resize a column to a specific width. */
|
|
255
|
+
interface AIResizeColumnOperation {
|
|
256
|
+
type: "resizeColumn";
|
|
257
|
+
column: string;
|
|
258
|
+
width: number;
|
|
259
|
+
}
|
|
260
|
+
/** Reorder columns by moving a column to a new position. */
|
|
261
|
+
interface AIReorderColumnsOperation {
|
|
262
|
+
type: "reorderColumns";
|
|
263
|
+
order: string[];
|
|
264
|
+
}
|
|
265
|
+
/** Pin or unpin a column. */
|
|
266
|
+
interface AIPinColumnOperation {
|
|
267
|
+
type: "pinColumn";
|
|
268
|
+
column: string;
|
|
269
|
+
pinned: "left" | "right" | false;
|
|
270
|
+
}
|
|
271
|
+
/** Set the current page number. */
|
|
272
|
+
interface AISetPageOperation {
|
|
273
|
+
type: "setPage";
|
|
274
|
+
page: number;
|
|
275
|
+
}
|
|
254
276
|
/** Union of all possible AI operations. */
|
|
255
|
-
type AIOperation = AIFilterOperation | AIStyleOperation | AICellStyleOperation | AISortOperation | AIColumnVisibilityOperation;
|
|
277
|
+
type AIOperation = AIFilterOperation | AIStyleOperation | AICellStyleOperation | AISortOperation | AIColumnVisibilityOperation | AIResizeColumnOperation | AIReorderColumnsOperation | AIPinColumnOperation | AISetPageOperation;
|
|
256
278
|
/** The structured response returned by the AI. */
|
|
257
279
|
interface AIResponse {
|
|
258
280
|
operations: AIOperation[];
|
|
@@ -595,4 +617,4 @@ interface TableBodyProps {
|
|
|
595
617
|
}
|
|
596
618
|
declare const TableBody: React$1.FC<TableBodyProps>;
|
|
597
619
|
|
|
598
|
-
export { type AICellStyleOperation, type AIColumnVisibilityOperation, type AICondition, type AIFilterOperation, type AIOperation, type AIOperator, type AIResponse, type AISortOperation, type AIStyleOperation, BoltTable, type BoltTableAIConfig, type BoltTableConfig, type BoltTableIcons, type CellContextMenuItem, type ClassNamesTypes, type ColumnContextMenuItem, type ColumnPersistenceConfig, type ColumnType, type DataRecord, DraggableHeader, type ExpandableConfig, type PaginationType, ResizeOverlay, type RowPinningConfig, type RowSelectionConfig, type SortDirection, type StylesTypes, TableBody, defineConfig };
|
|
620
|
+
export { type AICellStyleOperation, type AIColumnVisibilityOperation, type AICondition, type AIFilterOperation, type AIOperation, type AIOperator, type AIPinColumnOperation, type AIReorderColumnsOperation, type AIResizeColumnOperation, type AIResponse, type AISetPageOperation, type AISortOperation, type AIStyleOperation, BoltTable, type BoltTableAIConfig, type BoltTableConfig, type BoltTableIcons, type CellContextMenuItem, type ClassNamesTypes, type ColumnContextMenuItem, type ColumnPersistenceConfig, type ColumnType, type DataRecord, DraggableHeader, type ExpandableConfig, type PaginationType, ResizeOverlay, type RowPinningConfig, type RowSelectionConfig, type SortDirection, type StylesTypes, TableBody, defineConfig };
|
package/dist/index.d.ts
CHANGED
|
@@ -251,8 +251,30 @@ interface AIColumnVisibilityOperation {
|
|
|
251
251
|
type: "hideColumns" | "showColumns";
|
|
252
252
|
columns: string[];
|
|
253
253
|
}
|
|
254
|
+
/** Resize a column to a specific width. */
|
|
255
|
+
interface AIResizeColumnOperation {
|
|
256
|
+
type: "resizeColumn";
|
|
257
|
+
column: string;
|
|
258
|
+
width: number;
|
|
259
|
+
}
|
|
260
|
+
/** Reorder columns by moving a column to a new position. */
|
|
261
|
+
interface AIReorderColumnsOperation {
|
|
262
|
+
type: "reorderColumns";
|
|
263
|
+
order: string[];
|
|
264
|
+
}
|
|
265
|
+
/** Pin or unpin a column. */
|
|
266
|
+
interface AIPinColumnOperation {
|
|
267
|
+
type: "pinColumn";
|
|
268
|
+
column: string;
|
|
269
|
+
pinned: "left" | "right" | false;
|
|
270
|
+
}
|
|
271
|
+
/** Set the current page number. */
|
|
272
|
+
interface AISetPageOperation {
|
|
273
|
+
type: "setPage";
|
|
274
|
+
page: number;
|
|
275
|
+
}
|
|
254
276
|
/** Union of all possible AI operations. */
|
|
255
|
-
type AIOperation = AIFilterOperation | AIStyleOperation | AICellStyleOperation | AISortOperation | AIColumnVisibilityOperation;
|
|
277
|
+
type AIOperation = AIFilterOperation | AIStyleOperation | AICellStyleOperation | AISortOperation | AIColumnVisibilityOperation | AIResizeColumnOperation | AIReorderColumnsOperation | AIPinColumnOperation | AISetPageOperation;
|
|
256
278
|
/** The structured response returned by the AI. */
|
|
257
279
|
interface AIResponse {
|
|
258
280
|
operations: AIOperation[];
|
|
@@ -595,4 +617,4 @@ interface TableBodyProps {
|
|
|
595
617
|
}
|
|
596
618
|
declare const TableBody: React$1.FC<TableBodyProps>;
|
|
597
619
|
|
|
598
|
-
export { type AICellStyleOperation, type AIColumnVisibilityOperation, type AICondition, type AIFilterOperation, type AIOperation, type AIOperator, type AIResponse, type AISortOperation, type AIStyleOperation, BoltTable, type BoltTableAIConfig, type BoltTableConfig, type BoltTableIcons, type CellContextMenuItem, type ClassNamesTypes, type ColumnContextMenuItem, type ColumnPersistenceConfig, type ColumnType, type DataRecord, DraggableHeader, type ExpandableConfig, type PaginationType, ResizeOverlay, type RowPinningConfig, type RowSelectionConfig, type SortDirection, type StylesTypes, TableBody, defineConfig };
|
|
620
|
+
export { type AICellStyleOperation, type AIColumnVisibilityOperation, type AICondition, type AIFilterOperation, type AIOperation, type AIOperator, type AIPinColumnOperation, type AIReorderColumnsOperation, type AIResizeColumnOperation, type AIResponse, type AISetPageOperation, type AISortOperation, type AIStyleOperation, BoltTable, type BoltTableAIConfig, type BoltTableConfig, type BoltTableIcons, type CellContextMenuItem, type ClassNamesTypes, type ColumnContextMenuItem, type ColumnPersistenceConfig, type ColumnType, type DataRecord, DraggableHeader, type ExpandableConfig, type PaginationType, ResizeOverlay, type RowPinningConfig, type RowSelectionConfig, type SortDirection, type StylesTypes, TableBody, defineConfig };
|
package/dist/index.js
CHANGED
|
@@ -711,66 +711,68 @@ function detectColumnType(key, data) {
|
|
|
711
711
|
}
|
|
712
712
|
return { type: "string", sample: sampleStr };
|
|
713
713
|
}
|
|
714
|
+
var cachedSchema = null;
|
|
715
|
+
function buildSchemaFingerprint(columns, dataLen) {
|
|
716
|
+
return columns.filter((c) => c.key !== "__select__" && c.key !== "__expand__").map((c) => c.key).join(",") + `:${dataLen}`;
|
|
717
|
+
}
|
|
714
718
|
function buildSystemPrompt(columns, data) {
|
|
715
|
-
const
|
|
719
|
+
const fingerprint = buildSchemaFingerprint(columns, data.length);
|
|
720
|
+
if (cachedSchema?.fingerprint === fingerprint) {
|
|
721
|
+
return cachedSchema.prompt;
|
|
722
|
+
}
|
|
723
|
+
const cols = columns.filter((c) => c.key !== "__select__" && c.key !== "__expand__");
|
|
724
|
+
const schema = cols.map((c) => {
|
|
716
725
|
const key = c.dataIndex ?? c.key;
|
|
717
726
|
const title = typeof c.title === "string" ? c.title : c.key;
|
|
718
727
|
const info = detectColumnType(key, data);
|
|
719
|
-
|
|
728
|
+
const w = c.width ?? 150;
|
|
729
|
+
const pin = c.pinned ? `, pinned: "${c.pinned}"` : "";
|
|
730
|
+
const hidden = c.hidden ? ", hidden: true" : "";
|
|
731
|
+
const vals = info.sample ? "|vals: " + info.sample : "";
|
|
732
|
+
return ` ${c.key}|${key}|"${title}"|${info.type}|w:${w}${pin}${hidden}${vals}`;
|
|
720
733
|
}).join("\n");
|
|
721
|
-
const sample = data.slice(0,
|
|
734
|
+
const sample = data.slice(0, 3).map((row) => {
|
|
722
735
|
const obj = {};
|
|
723
|
-
for (const col of
|
|
724
|
-
if (col.key === "__select__" || col.key === "__expand__") continue;
|
|
736
|
+
for (const col of cols) {
|
|
725
737
|
const di = col.dataIndex ?? col.key;
|
|
726
738
|
obj[di] = row[di];
|
|
727
739
|
}
|
|
728
740
|
return obj;
|
|
729
741
|
});
|
|
730
|
-
|
|
731
|
-
You MUST respond with ONLY a valid JSON object \u2014 no markdown fences, no explanation, no extra text.
|
|
732
|
-
|
|
733
|
-
## Table Schema
|
|
734
|
-
${schemaLines}
|
|
735
|
-
|
|
736
|
-
## Sample Data (first ${sample.length} of ${data.length} rows)
|
|
737
|
-
${JSON.stringify(sample, null, 2)}
|
|
738
|
-
|
|
739
|
-
## Available Operations
|
|
740
|
-
Combine any of these in a single response:
|
|
742
|
+
const prompt = `Data table AI. Respond ONLY with valid JSON, no markdown/explanation.
|
|
741
743
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
+
SCHEMA (key|dataIndex|title|type|width|flags|sample):
|
|
745
|
+
${schema}
|
|
744
746
|
|
|
745
|
-
|
|
746
|
-
|
|
747
|
+
SAMPLE (${sample.length}/${data.length} rows):
|
|
748
|
+
${JSON.stringify(sample)}
|
|
747
749
|
|
|
748
|
-
|
|
749
|
-
{ "type": "cellStyle", "column": "<dataIndex>", "conditions": [...], "logic": "and"|"or", "style": { "<cssProp>": "<value>" } }
|
|
750
|
+
COLUMN ORDER: [${cols.map((c) => `"${c.key}"`).join(",")}]
|
|
750
751
|
|
|
751
|
-
|
|
752
|
-
|
|
752
|
+
OPS (combine any):
|
|
753
|
+
filter: {type:"filter",conditions:[{column:"<dataIndex>",op:"<op>",value:<v>}],logic:"and"|"or"}
|
|
754
|
+
sort: {type:"sort",column:"<dataIndex>",direction:"asc"|"desc"}
|
|
755
|
+
rowStyle: {type:"rowStyle",conditions:[...],logic:"and"|"or",style:{cssProp:"val"}}
|
|
756
|
+
cellStyle: {type:"cellStyle",column:"<dataIndex>",conditions:[...],logic:"and"|"or",style:{cssProp:"val"}}
|
|
757
|
+
hideColumns: {type:"hideColumns",columns:["key",...]}
|
|
758
|
+
showColumns: {type:"showColumns",columns:["key",...]}
|
|
759
|
+
resizeColumn: {type:"resizeColumn",column:"<key>",width:<px>}
|
|
760
|
+
reorderColumns: {type:"reorderColumns",order:["key1","key2",...]} (full column order)
|
|
761
|
+
pinColumn: {type:"pinColumn",column:"<key>",pinned:"left"|"right"|false}
|
|
762
|
+
setPage: {type:"setPage",page:<number>}
|
|
753
763
|
|
|
754
|
-
|
|
755
|
-
{ "type": "hideColumns" | "showColumns", "columns": ["<key>", ...] }
|
|
764
|
+
OPS: eq,neq,gt,gte,lt,lte,contains,notContains,startsWith,endsWith,in,notIn
|
|
756
765
|
|
|
757
|
-
|
|
758
|
-
eq, neq, gt, gte, lt, lte, contains, notContains, startsWith, endsWith, in, notIn
|
|
766
|
+
FORMAT: {"operations":[...],"message":"brief description"}
|
|
759
767
|
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
- For colors use semi-transparent values like "rgba(255,0,0,0.15)" so text stays readable.
|
|
769
|
-
- CSS property names must be camelCase (e.g. "backgroundColor", "color", "fontWeight").
|
|
770
|
-
- If the user asks to highlight / color / mark specific rows, use rowStyle or cellStyle.
|
|
771
|
-
- If the user asks to show only certain rows, use filter.
|
|
772
|
-
- You can combine filter + rowStyle + sort etc. in one response.
|
|
773
|
-
- The "message" should be concise: what was done, in plain English.`;
|
|
768
|
+
RULES:
|
|
769
|
+
- Use dataIndex for data ops, key for column ops (hide/show/resize/reorder/pin).
|
|
770
|
+
- Colors: semi-transparent rgba. CSS props: camelCase.
|
|
771
|
+
- reorderColumns: provide FULL ordered array of ALL visible column keys.
|
|
772
|
+
- resizeColumn width: integer pixels (min 40, max 800).
|
|
773
|
+
- Combine multiple ops freely. Message: concise plain English.`;
|
|
774
|
+
cachedSchema = { fingerprint, prompt };
|
|
775
|
+
return prompt;
|
|
774
776
|
}
|
|
775
777
|
async function callAI(config, systemPrompt, userQuery) {
|
|
776
778
|
const { provider, apiKey, model, baseUrl, maxTokens = 1024, temperature = 0.1 } = config;
|
|
@@ -936,6 +938,10 @@ function applyAIOperations(data, operations) {
|
|
|
936
938
|
const cellStyleOps = [];
|
|
937
939
|
const hideColumns = [];
|
|
938
940
|
const showColumns = [];
|
|
941
|
+
const resizeOps = [];
|
|
942
|
+
let reorderOp = null;
|
|
943
|
+
const pinOps = [];
|
|
944
|
+
let setPageOp = null;
|
|
939
945
|
for (const op of operations) {
|
|
940
946
|
switch (op.type) {
|
|
941
947
|
case "filter":
|
|
@@ -956,12 +962,24 @@ function applyAIOperations(data, operations) {
|
|
|
956
962
|
case "showColumns":
|
|
957
963
|
showColumns.push(...op.columns);
|
|
958
964
|
break;
|
|
965
|
+
case "resizeColumn":
|
|
966
|
+
resizeOps.push(op);
|
|
967
|
+
break;
|
|
968
|
+
case "reorderColumns":
|
|
969
|
+
reorderOp = op;
|
|
970
|
+
break;
|
|
971
|
+
case "pinColumn":
|
|
972
|
+
pinOps.push(op);
|
|
973
|
+
break;
|
|
974
|
+
case "setPage":
|
|
975
|
+
setPageOp = op;
|
|
976
|
+
break;
|
|
959
977
|
}
|
|
960
978
|
}
|
|
961
979
|
if (sortOp) {
|
|
962
980
|
filteredData = applyAISort(filteredData, sortOp);
|
|
963
981
|
}
|
|
964
|
-
return { filteredData, sortOp, styleOps, cellStyleOps, hideColumns, showColumns };
|
|
982
|
+
return { filteredData, sortOp, styleOps, cellStyleOps, hideColumns, showColumns, resizeOps, reorderOp, pinOps, setPageOp };
|
|
965
983
|
}
|
|
966
984
|
|
|
967
985
|
// src/ResizeOverlay.tsx
|
|
@@ -2248,6 +2266,101 @@ function BoltTable({
|
|
|
2248
2266
|
const [aiFilteredDataKeys, setAiFilteredDataKeys] = (0, import_react4.useState)(null);
|
|
2249
2267
|
const [aiSortKey, setAiSortKey] = (0, import_react4.useState)(null);
|
|
2250
2268
|
const [aiSortDir, setAiSortDir] = (0, import_react4.useState)(null);
|
|
2269
|
+
const aiFiltersStorageKey = columnPersistence && typeof columnPersistence === "object" ? `bt-ai-filters-${columnPersistence.storageKey}` : "bt-ai-filters";
|
|
2270
|
+
const [savedAIFilters, setSavedAIFilters] = (0, import_react4.useState)(() => {
|
|
2271
|
+
try {
|
|
2272
|
+
const raw = localStorage.getItem(aiFiltersStorageKey);
|
|
2273
|
+
return raw ? JSON.parse(raw) : [];
|
|
2274
|
+
} catch {
|
|
2275
|
+
return [];
|
|
2276
|
+
}
|
|
2277
|
+
});
|
|
2278
|
+
const [showSavedFilters, setShowSavedFilters] = (0, import_react4.useState)(false);
|
|
2279
|
+
const savedFiltersRef = (0, import_react4.useRef)(null);
|
|
2280
|
+
import_react4.default.useEffect(() => {
|
|
2281
|
+
if (!showSavedFilters) return;
|
|
2282
|
+
const close = (e) => {
|
|
2283
|
+
if (savedFiltersRef.current && !savedFiltersRef.current.contains(e.target)) {
|
|
2284
|
+
setShowSavedFilters(false);
|
|
2285
|
+
}
|
|
2286
|
+
};
|
|
2287
|
+
document.addEventListener("mousedown", close);
|
|
2288
|
+
return () => document.removeEventListener("mousedown", close);
|
|
2289
|
+
}, [showSavedFilters]);
|
|
2290
|
+
const saveCurrentAIFilter = (0, import_react4.useCallback)(() => {
|
|
2291
|
+
if (!aiResult) return;
|
|
2292
|
+
const label = aiQuery || aiResult.message;
|
|
2293
|
+
const entry = { label, operations: aiResult.operations, query: aiQuery };
|
|
2294
|
+
const next = [...savedAIFilters, entry];
|
|
2295
|
+
setSavedAIFilters(next);
|
|
2296
|
+
try {
|
|
2297
|
+
localStorage.setItem(aiFiltersStorageKey, JSON.stringify(next));
|
|
2298
|
+
} catch {
|
|
2299
|
+
}
|
|
2300
|
+
}, [aiResult, aiQuery, savedAIFilters, aiFiltersStorageKey]);
|
|
2301
|
+
const removeSavedFilter = (0, import_react4.useCallback)((index) => {
|
|
2302
|
+
const next = savedAIFilters.filter((_, i) => i !== index);
|
|
2303
|
+
setSavedAIFilters(next);
|
|
2304
|
+
try {
|
|
2305
|
+
localStorage.setItem(aiFiltersStorageKey, JSON.stringify(next));
|
|
2306
|
+
} catch {
|
|
2307
|
+
}
|
|
2308
|
+
}, [savedAIFilters, aiFiltersStorageKey]);
|
|
2309
|
+
const applySavedFilter = (0, import_react4.useCallback)((filter) => {
|
|
2310
|
+
const { filteredData, sortOp, styleOps: sOps, cellStyleOps: csOps, hideColumns: hideCols, showColumns: showCols, resizeOps, reorderOp, pinOps, setPageOp } = applyAIOperations(data, filter.operations);
|
|
2311
|
+
setAiStyleOps(sOps);
|
|
2312
|
+
setAiCellStyleOps(csOps);
|
|
2313
|
+
if (filter.operations.some((op) => op.type === "filter")) {
|
|
2314
|
+
const keySet = /* @__PURE__ */ new Set();
|
|
2315
|
+
filteredData.forEach((row, idx) => {
|
|
2316
|
+
const k = typeof rowKey === "function" ? rowKey(row) : String(row[typeof rowKey === "string" ? rowKey : "id"] ?? idx);
|
|
2317
|
+
keySet.add(k);
|
|
2318
|
+
});
|
|
2319
|
+
setAiFilteredDataKeys(keySet);
|
|
2320
|
+
} else {
|
|
2321
|
+
setAiFilteredDataKeys(null);
|
|
2322
|
+
}
|
|
2323
|
+
if (sortOp) {
|
|
2324
|
+
setAiSortKey(sortOp.column);
|
|
2325
|
+
setAiSortDir(sortOp.direction);
|
|
2326
|
+
} else {
|
|
2327
|
+
setAiSortKey(null);
|
|
2328
|
+
setAiSortDir(null);
|
|
2329
|
+
}
|
|
2330
|
+
if (hideCols.length > 0 || showCols.length > 0) {
|
|
2331
|
+
setColumns(
|
|
2332
|
+
(prev) => prev.map((col) => {
|
|
2333
|
+
if (hideCols.includes(col.key)) return { ...col, hidden: true };
|
|
2334
|
+
if (showCols.includes(col.key)) return { ...col, hidden: false };
|
|
2335
|
+
return col;
|
|
2336
|
+
})
|
|
2337
|
+
);
|
|
2338
|
+
}
|
|
2339
|
+
for (const rOp of resizeOps) {
|
|
2340
|
+
const w = Math.max(40, Math.min(800, rOp.width));
|
|
2341
|
+
setColumnWidths((prev) => {
|
|
2342
|
+
const n = new Map(prev);
|
|
2343
|
+
n.set(rOp.column, w);
|
|
2344
|
+
return n;
|
|
2345
|
+
});
|
|
2346
|
+
onColumnResize?.(rOp.column, w);
|
|
2347
|
+
}
|
|
2348
|
+
if (reorderOp) {
|
|
2349
|
+
setColumnOrder(reorderOp.order);
|
|
2350
|
+
onColumnOrderChange?.(reorderOp.order);
|
|
2351
|
+
}
|
|
2352
|
+
for (const pOp of pinOps) {
|
|
2353
|
+
setColumns(
|
|
2354
|
+
(prev) => prev.map((col) => col.key === pOp.column ? { ...col, pinned: pOp.pinned } : col)
|
|
2355
|
+
);
|
|
2356
|
+
onColumnPin?.(pOp.column, pOp.pinned);
|
|
2357
|
+
}
|
|
2358
|
+
if (setPageOp) {
|
|
2359
|
+
setInternalPage(setPageOp.page);
|
|
2360
|
+
}
|
|
2361
|
+
setAiResult({ operations: filter.operations, message: `Applied saved filter: ${filter.label}` });
|
|
2362
|
+
setShowSavedFilters(false);
|
|
2363
|
+
}, [data, rowKey, onColumnResize, onColumnOrderChange, onColumnPin]);
|
|
2251
2364
|
const onAIResponseRef = (0, import_react4.useRef)(onAIResponse);
|
|
2252
2365
|
onAIResponseRef.current = onAIResponse;
|
|
2253
2366
|
const handleAISubmit = (0, import_react4.useCallback)(async () => {
|
|
@@ -2269,7 +2382,7 @@ function BoltTable({
|
|
|
2269
2382
|
} else {
|
|
2270
2383
|
throw new Error("AI mode requires either aiConfig or onAIQuery prop");
|
|
2271
2384
|
}
|
|
2272
|
-
const { filteredData, sortOp, styleOps: sOps, cellStyleOps: csOps, hideColumns, showColumns } = applyAIOperations(data, response.operations);
|
|
2385
|
+
const { filteredData, sortOp, styleOps: sOps, cellStyleOps: csOps, hideColumns: hideCols, showColumns: showCols, resizeOps, reorderOp, pinOps, setPageOp } = applyAIOperations(data, response.operations);
|
|
2273
2386
|
setAiStyleOps(sOps);
|
|
2274
2387
|
setAiCellStyleOps(csOps);
|
|
2275
2388
|
if (response.operations.some((op) => op.type === "filter")) {
|
|
@@ -2289,15 +2402,37 @@ function BoltTable({
|
|
|
2289
2402
|
setAiSortKey(null);
|
|
2290
2403
|
setAiSortDir(null);
|
|
2291
2404
|
}
|
|
2292
|
-
if (
|
|
2405
|
+
if (hideCols.length > 0 || showCols.length > 0) {
|
|
2293
2406
|
setColumns(
|
|
2294
2407
|
(prev) => prev.map((col) => {
|
|
2295
|
-
if (
|
|
2296
|
-
if (
|
|
2408
|
+
if (hideCols.includes(col.key)) return { ...col, hidden: true };
|
|
2409
|
+
if (showCols.includes(col.key)) return { ...col, hidden: false };
|
|
2297
2410
|
return col;
|
|
2298
2411
|
})
|
|
2299
2412
|
);
|
|
2300
2413
|
}
|
|
2414
|
+
for (const rOp of resizeOps) {
|
|
2415
|
+
const w = Math.max(40, Math.min(800, rOp.width));
|
|
2416
|
+
setColumnWidths((prev) => {
|
|
2417
|
+
const n = new Map(prev);
|
|
2418
|
+
n.set(rOp.column, w);
|
|
2419
|
+
return n;
|
|
2420
|
+
});
|
|
2421
|
+
onColumnResize?.(rOp.column, w);
|
|
2422
|
+
}
|
|
2423
|
+
if (reorderOp) {
|
|
2424
|
+
setColumnOrder(reorderOp.order);
|
|
2425
|
+
onColumnOrderChange?.(reorderOp.order);
|
|
2426
|
+
}
|
|
2427
|
+
for (const pOp of pinOps) {
|
|
2428
|
+
setColumns(
|
|
2429
|
+
(prev) => prev.map((col) => col.key === pOp.column ? { ...col, pinned: pOp.pinned } : col)
|
|
2430
|
+
);
|
|
2431
|
+
onColumnPin?.(pOp.column, pOp.pinned);
|
|
2432
|
+
}
|
|
2433
|
+
if (setPageOp) {
|
|
2434
|
+
setInternalPage(setPageOp.page);
|
|
2435
|
+
}
|
|
2301
2436
|
setAiResult(response);
|
|
2302
2437
|
onAIResponseRef.current?.(response);
|
|
2303
2438
|
} catch (err) {
|
|
@@ -3435,7 +3570,7 @@ function BoltTable({
|
|
|
3435
3570
|
borderBottom: "1px solid rgba(128,128,128,0.2)",
|
|
3436
3571
|
fontSize: 12,
|
|
3437
3572
|
flexShrink: 0,
|
|
3438
|
-
|
|
3573
|
+
zIndex: 20
|
|
3439
3574
|
},
|
|
3440
3575
|
children: [
|
|
3441
3576
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
@@ -3627,6 +3762,110 @@ function BoltTable({
|
|
|
3627
3762
|
}
|
|
3628
3763
|
)
|
|
3629
3764
|
] }),
|
|
3765
|
+
aiMode && savedAIFilters.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { ref: savedFiltersRef, style: { position: "relative", flexShrink: 0 }, children: [
|
|
3766
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
3767
|
+
"button",
|
|
3768
|
+
{
|
|
3769
|
+
type: "button",
|
|
3770
|
+
onClick: () => setShowSavedFilters((p) => !p),
|
|
3771
|
+
style: {
|
|
3772
|
+
display: "flex",
|
|
3773
|
+
alignItems: "center",
|
|
3774
|
+
gap: 4,
|
|
3775
|
+
background: "none",
|
|
3776
|
+
border: "1px solid rgba(128,128,128,0.2)",
|
|
3777
|
+
borderRadius: 4,
|
|
3778
|
+
cursor: "pointer",
|
|
3779
|
+
padding: "4px 6px",
|
|
3780
|
+
color: "inherit",
|
|
3781
|
+
fontSize: 12
|
|
3782
|
+
},
|
|
3783
|
+
title: "Saved AI filters",
|
|
3784
|
+
children: [
|
|
3785
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polygon", { points: "22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" }) }),
|
|
3786
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: savedAIFilters.length })
|
|
3787
|
+
]
|
|
3788
|
+
}
|
|
3789
|
+
),
|
|
3790
|
+
showSavedFilters && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
3791
|
+
"div",
|
|
3792
|
+
{
|
|
3793
|
+
style: {
|
|
3794
|
+
position: "absolute",
|
|
3795
|
+
top: "100%",
|
|
3796
|
+
right: 0,
|
|
3797
|
+
zIndex: 99999,
|
|
3798
|
+
minWidth: 240,
|
|
3799
|
+
maxWidth: 360,
|
|
3800
|
+
maxHeight: 320,
|
|
3801
|
+
overflowY: "auto",
|
|
3802
|
+
borderRadius: 8,
|
|
3803
|
+
border: "1px solid rgba(128,128,128,0.2)",
|
|
3804
|
+
boxShadow: "0 4px 24px rgba(0,0,0,0.12)",
|
|
3805
|
+
backdropFilter: "blur(16px)",
|
|
3806
|
+
WebkitBackdropFilter: "blur(16px)",
|
|
3807
|
+
backgroundColor: "rgba(128,128,128,0.08)",
|
|
3808
|
+
padding: "4px 0",
|
|
3809
|
+
marginTop: 4
|
|
3810
|
+
},
|
|
3811
|
+
children: [
|
|
3812
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { padding: "6px 12px", fontSize: 11, opacity: 0.5, fontWeight: 600 }, children: "Saved Filters" }),
|
|
3813
|
+
savedAIFilters.map((f, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
3814
|
+
"div",
|
|
3815
|
+
{
|
|
3816
|
+
style: {
|
|
3817
|
+
display: "flex",
|
|
3818
|
+
alignItems: "center",
|
|
3819
|
+
gap: 6,
|
|
3820
|
+
padding: "6px 12px",
|
|
3821
|
+
cursor: "pointer",
|
|
3822
|
+
fontSize: 12
|
|
3823
|
+
},
|
|
3824
|
+
onMouseEnter: (e) => {
|
|
3825
|
+
e.currentTarget.style.backgroundColor = "rgba(128,128,128,0.15)";
|
|
3826
|
+
},
|
|
3827
|
+
onMouseLeave: (e) => {
|
|
3828
|
+
e.currentTarget.style.backgroundColor = "transparent";
|
|
3829
|
+
},
|
|
3830
|
+
children: [
|
|
3831
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
3832
|
+
"span",
|
|
3833
|
+
{
|
|
3834
|
+
style: { flex: "1 1 0%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
3835
|
+
onClick: () => applySavedFilter(f),
|
|
3836
|
+
children: f.label
|
|
3837
|
+
}
|
|
3838
|
+
),
|
|
3839
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
3840
|
+
"button",
|
|
3841
|
+
{
|
|
3842
|
+
type: "button",
|
|
3843
|
+
onClick: (e) => {
|
|
3844
|
+
e.stopPropagation();
|
|
3845
|
+
removeSavedFilter(i);
|
|
3846
|
+
},
|
|
3847
|
+
style: {
|
|
3848
|
+
display: "flex",
|
|
3849
|
+
alignItems: "center",
|
|
3850
|
+
background: "none",
|
|
3851
|
+
border: "none",
|
|
3852
|
+
cursor: "pointer",
|
|
3853
|
+
padding: 2,
|
|
3854
|
+
color: "GrayText",
|
|
3855
|
+
flexShrink: 0
|
|
3856
|
+
},
|
|
3857
|
+
title: "Remove",
|
|
3858
|
+
children: icons?.close ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(XIcon, { style: { width: 10, height: 10 } })
|
|
3859
|
+
}
|
|
3860
|
+
)
|
|
3861
|
+
]
|
|
3862
|
+
},
|
|
3863
|
+
i
|
|
3864
|
+
))
|
|
3865
|
+
]
|
|
3866
|
+
}
|
|
3867
|
+
)
|
|
3868
|
+
] }),
|
|
3630
3869
|
aiMode && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
3631
3870
|
"button",
|
|
3632
3871
|
{
|
|
@@ -3792,6 +4031,37 @@ function BoltTable({
|
|
|
3792
4031
|
children: [
|
|
3793
4032
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: { color: accentColor, display: "flex", flexShrink: 0 }, children: icons?.sparkles ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SparklesIcon, { style: { width: 14, height: 14 } }) }),
|
|
3794
4033
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: { flex: "1 1 0%", opacity: 0.85 }, children: aiResult.message }),
|
|
4034
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
4035
|
+
"button",
|
|
4036
|
+
{
|
|
4037
|
+
type: "button",
|
|
4038
|
+
onClick: saveCurrentAIFilter,
|
|
4039
|
+
style: {
|
|
4040
|
+
display: "flex",
|
|
4041
|
+
alignItems: "center",
|
|
4042
|
+
gap: 4,
|
|
4043
|
+
background: `${accentColor}12`,
|
|
4044
|
+
border: `1px solid ${accentColor}30`,
|
|
4045
|
+
borderRadius: 4,
|
|
4046
|
+
cursor: "pointer",
|
|
4047
|
+
padding: "2px 8px",
|
|
4048
|
+
color: accentColor,
|
|
4049
|
+
fontSize: 11,
|
|
4050
|
+
flexShrink: 0,
|
|
4051
|
+
fontWeight: 500,
|
|
4052
|
+
transition: "all 0.2s ease"
|
|
4053
|
+
},
|
|
4054
|
+
title: "Save this filter for quick access later",
|
|
4055
|
+
children: [
|
|
4056
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "10", height: "10", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
4057
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" }),
|
|
4058
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "17 21 17 13 7 13 7 21" }),
|
|
4059
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "7 3 7 8 15 8" })
|
|
4060
|
+
] }),
|
|
4061
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "Save Filter" })
|
|
4062
|
+
]
|
|
4063
|
+
}
|
|
4064
|
+
),
|
|
3795
4065
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
3796
4066
|
"button",
|
|
3797
4067
|
{
|
package/dist/index.mjs
CHANGED
|
@@ -676,66 +676,68 @@ function detectColumnType(key, data) {
|
|
|
676
676
|
}
|
|
677
677
|
return { type: "string", sample: sampleStr };
|
|
678
678
|
}
|
|
679
|
+
var cachedSchema = null;
|
|
680
|
+
function buildSchemaFingerprint(columns, dataLen) {
|
|
681
|
+
return columns.filter((c) => c.key !== "__select__" && c.key !== "__expand__").map((c) => c.key).join(",") + `:${dataLen}`;
|
|
682
|
+
}
|
|
679
683
|
function buildSystemPrompt(columns, data) {
|
|
680
|
-
const
|
|
684
|
+
const fingerprint = buildSchemaFingerprint(columns, data.length);
|
|
685
|
+
if (cachedSchema?.fingerprint === fingerprint) {
|
|
686
|
+
return cachedSchema.prompt;
|
|
687
|
+
}
|
|
688
|
+
const cols = columns.filter((c) => c.key !== "__select__" && c.key !== "__expand__");
|
|
689
|
+
const schema = cols.map((c) => {
|
|
681
690
|
const key = c.dataIndex ?? c.key;
|
|
682
691
|
const title = typeof c.title === "string" ? c.title : c.key;
|
|
683
692
|
const info = detectColumnType(key, data);
|
|
684
|
-
|
|
693
|
+
const w = c.width ?? 150;
|
|
694
|
+
const pin = c.pinned ? `, pinned: "${c.pinned}"` : "";
|
|
695
|
+
const hidden = c.hidden ? ", hidden: true" : "";
|
|
696
|
+
const vals = info.sample ? "|vals: " + info.sample : "";
|
|
697
|
+
return ` ${c.key}|${key}|"${title}"|${info.type}|w:${w}${pin}${hidden}${vals}`;
|
|
685
698
|
}).join("\n");
|
|
686
|
-
const sample = data.slice(0,
|
|
699
|
+
const sample = data.slice(0, 3).map((row) => {
|
|
687
700
|
const obj = {};
|
|
688
|
-
for (const col of
|
|
689
|
-
if (col.key === "__select__" || col.key === "__expand__") continue;
|
|
701
|
+
for (const col of cols) {
|
|
690
702
|
const di = col.dataIndex ?? col.key;
|
|
691
703
|
obj[di] = row[di];
|
|
692
704
|
}
|
|
693
705
|
return obj;
|
|
694
706
|
});
|
|
695
|
-
|
|
696
|
-
You MUST respond with ONLY a valid JSON object \u2014 no markdown fences, no explanation, no extra text.
|
|
697
|
-
|
|
698
|
-
## Table Schema
|
|
699
|
-
${schemaLines}
|
|
700
|
-
|
|
701
|
-
## Sample Data (first ${sample.length} of ${data.length} rows)
|
|
702
|
-
${JSON.stringify(sample, null, 2)}
|
|
703
|
-
|
|
704
|
-
## Available Operations
|
|
705
|
-
Combine any of these in a single response:
|
|
707
|
+
const prompt = `Data table AI. Respond ONLY with valid JSON, no markdown/explanation.
|
|
706
708
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
+
SCHEMA (key|dataIndex|title|type|width|flags|sample):
|
|
710
|
+
${schema}
|
|
709
711
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
+
SAMPLE (${sample.length}/${data.length} rows):
|
|
713
|
+
${JSON.stringify(sample)}
|
|
712
714
|
|
|
713
|
-
|
|
714
|
-
{ "type": "cellStyle", "column": "<dataIndex>", "conditions": [...], "logic": "and"|"or", "style": { "<cssProp>": "<value>" } }
|
|
715
|
+
COLUMN ORDER: [${cols.map((c) => `"${c.key}"`).join(",")}]
|
|
715
716
|
|
|
716
|
-
|
|
717
|
-
|
|
717
|
+
OPS (combine any):
|
|
718
|
+
filter: {type:"filter",conditions:[{column:"<dataIndex>",op:"<op>",value:<v>}],logic:"and"|"or"}
|
|
719
|
+
sort: {type:"sort",column:"<dataIndex>",direction:"asc"|"desc"}
|
|
720
|
+
rowStyle: {type:"rowStyle",conditions:[...],logic:"and"|"or",style:{cssProp:"val"}}
|
|
721
|
+
cellStyle: {type:"cellStyle",column:"<dataIndex>",conditions:[...],logic:"and"|"or",style:{cssProp:"val"}}
|
|
722
|
+
hideColumns: {type:"hideColumns",columns:["key",...]}
|
|
723
|
+
showColumns: {type:"showColumns",columns:["key",...]}
|
|
724
|
+
resizeColumn: {type:"resizeColumn",column:"<key>",width:<px>}
|
|
725
|
+
reorderColumns: {type:"reorderColumns",order:["key1","key2",...]} (full column order)
|
|
726
|
+
pinColumn: {type:"pinColumn",column:"<key>",pinned:"left"|"right"|false}
|
|
727
|
+
setPage: {type:"setPage",page:<number>}
|
|
718
728
|
|
|
719
|
-
|
|
720
|
-
{ "type": "hideColumns" | "showColumns", "columns": ["<key>", ...] }
|
|
729
|
+
OPS: eq,neq,gt,gte,lt,lte,contains,notContains,startsWith,endsWith,in,notIn
|
|
721
730
|
|
|
722
|
-
|
|
723
|
-
eq, neq, gt, gte, lt, lte, contains, notContains, startsWith, endsWith, in, notIn
|
|
731
|
+
FORMAT: {"operations":[...],"message":"brief description"}
|
|
724
732
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
- For colors use semi-transparent values like "rgba(255,0,0,0.15)" so text stays readable.
|
|
734
|
-
- CSS property names must be camelCase (e.g. "backgroundColor", "color", "fontWeight").
|
|
735
|
-
- If the user asks to highlight / color / mark specific rows, use rowStyle or cellStyle.
|
|
736
|
-
- If the user asks to show only certain rows, use filter.
|
|
737
|
-
- You can combine filter + rowStyle + sort etc. in one response.
|
|
738
|
-
- The "message" should be concise: what was done, in plain English.`;
|
|
733
|
+
RULES:
|
|
734
|
+
- Use dataIndex for data ops, key for column ops (hide/show/resize/reorder/pin).
|
|
735
|
+
- Colors: semi-transparent rgba. CSS props: camelCase.
|
|
736
|
+
- reorderColumns: provide FULL ordered array of ALL visible column keys.
|
|
737
|
+
- resizeColumn width: integer pixels (min 40, max 800).
|
|
738
|
+
- Combine multiple ops freely. Message: concise plain English.`;
|
|
739
|
+
cachedSchema = { fingerprint, prompt };
|
|
740
|
+
return prompt;
|
|
739
741
|
}
|
|
740
742
|
async function callAI(config, systemPrompt, userQuery) {
|
|
741
743
|
const { provider, apiKey, model, baseUrl, maxTokens = 1024, temperature = 0.1 } = config;
|
|
@@ -901,6 +903,10 @@ function applyAIOperations(data, operations) {
|
|
|
901
903
|
const cellStyleOps = [];
|
|
902
904
|
const hideColumns = [];
|
|
903
905
|
const showColumns = [];
|
|
906
|
+
const resizeOps = [];
|
|
907
|
+
let reorderOp = null;
|
|
908
|
+
const pinOps = [];
|
|
909
|
+
let setPageOp = null;
|
|
904
910
|
for (const op of operations) {
|
|
905
911
|
switch (op.type) {
|
|
906
912
|
case "filter":
|
|
@@ -921,12 +927,24 @@ function applyAIOperations(data, operations) {
|
|
|
921
927
|
case "showColumns":
|
|
922
928
|
showColumns.push(...op.columns);
|
|
923
929
|
break;
|
|
930
|
+
case "resizeColumn":
|
|
931
|
+
resizeOps.push(op);
|
|
932
|
+
break;
|
|
933
|
+
case "reorderColumns":
|
|
934
|
+
reorderOp = op;
|
|
935
|
+
break;
|
|
936
|
+
case "pinColumn":
|
|
937
|
+
pinOps.push(op);
|
|
938
|
+
break;
|
|
939
|
+
case "setPage":
|
|
940
|
+
setPageOp = op;
|
|
941
|
+
break;
|
|
924
942
|
}
|
|
925
943
|
}
|
|
926
944
|
if (sortOp) {
|
|
927
945
|
filteredData = applyAISort(filteredData, sortOp);
|
|
928
946
|
}
|
|
929
|
-
return { filteredData, sortOp, styleOps, cellStyleOps, hideColumns, showColumns };
|
|
947
|
+
return { filteredData, sortOp, styleOps, cellStyleOps, hideColumns, showColumns, resizeOps, reorderOp, pinOps, setPageOp };
|
|
930
948
|
}
|
|
931
949
|
|
|
932
950
|
// src/ResizeOverlay.tsx
|
|
@@ -2219,6 +2237,101 @@ function BoltTable({
|
|
|
2219
2237
|
const [aiFilteredDataKeys, setAiFilteredDataKeys] = useState3(null);
|
|
2220
2238
|
const [aiSortKey, setAiSortKey] = useState3(null);
|
|
2221
2239
|
const [aiSortDir, setAiSortDir] = useState3(null);
|
|
2240
|
+
const aiFiltersStorageKey = columnPersistence && typeof columnPersistence === "object" ? `bt-ai-filters-${columnPersistence.storageKey}` : "bt-ai-filters";
|
|
2241
|
+
const [savedAIFilters, setSavedAIFilters] = useState3(() => {
|
|
2242
|
+
try {
|
|
2243
|
+
const raw = localStorage.getItem(aiFiltersStorageKey);
|
|
2244
|
+
return raw ? JSON.parse(raw) : [];
|
|
2245
|
+
} catch {
|
|
2246
|
+
return [];
|
|
2247
|
+
}
|
|
2248
|
+
});
|
|
2249
|
+
const [showSavedFilters, setShowSavedFilters] = useState3(false);
|
|
2250
|
+
const savedFiltersRef = useRef4(null);
|
|
2251
|
+
React4.useEffect(() => {
|
|
2252
|
+
if (!showSavedFilters) return;
|
|
2253
|
+
const close = (e) => {
|
|
2254
|
+
if (savedFiltersRef.current && !savedFiltersRef.current.contains(e.target)) {
|
|
2255
|
+
setShowSavedFilters(false);
|
|
2256
|
+
}
|
|
2257
|
+
};
|
|
2258
|
+
document.addEventListener("mousedown", close);
|
|
2259
|
+
return () => document.removeEventListener("mousedown", close);
|
|
2260
|
+
}, [showSavedFilters]);
|
|
2261
|
+
const saveCurrentAIFilter = useCallback2(() => {
|
|
2262
|
+
if (!aiResult) return;
|
|
2263
|
+
const label = aiQuery || aiResult.message;
|
|
2264
|
+
const entry = { label, operations: aiResult.operations, query: aiQuery };
|
|
2265
|
+
const next = [...savedAIFilters, entry];
|
|
2266
|
+
setSavedAIFilters(next);
|
|
2267
|
+
try {
|
|
2268
|
+
localStorage.setItem(aiFiltersStorageKey, JSON.stringify(next));
|
|
2269
|
+
} catch {
|
|
2270
|
+
}
|
|
2271
|
+
}, [aiResult, aiQuery, savedAIFilters, aiFiltersStorageKey]);
|
|
2272
|
+
const removeSavedFilter = useCallback2((index) => {
|
|
2273
|
+
const next = savedAIFilters.filter((_, i) => i !== index);
|
|
2274
|
+
setSavedAIFilters(next);
|
|
2275
|
+
try {
|
|
2276
|
+
localStorage.setItem(aiFiltersStorageKey, JSON.stringify(next));
|
|
2277
|
+
} catch {
|
|
2278
|
+
}
|
|
2279
|
+
}, [savedAIFilters, aiFiltersStorageKey]);
|
|
2280
|
+
const applySavedFilter = useCallback2((filter) => {
|
|
2281
|
+
const { filteredData, sortOp, styleOps: sOps, cellStyleOps: csOps, hideColumns: hideCols, showColumns: showCols, resizeOps, reorderOp, pinOps, setPageOp } = applyAIOperations(data, filter.operations);
|
|
2282
|
+
setAiStyleOps(sOps);
|
|
2283
|
+
setAiCellStyleOps(csOps);
|
|
2284
|
+
if (filter.operations.some((op) => op.type === "filter")) {
|
|
2285
|
+
const keySet = /* @__PURE__ */ new Set();
|
|
2286
|
+
filteredData.forEach((row, idx) => {
|
|
2287
|
+
const k = typeof rowKey === "function" ? rowKey(row) : String(row[typeof rowKey === "string" ? rowKey : "id"] ?? idx);
|
|
2288
|
+
keySet.add(k);
|
|
2289
|
+
});
|
|
2290
|
+
setAiFilteredDataKeys(keySet);
|
|
2291
|
+
} else {
|
|
2292
|
+
setAiFilteredDataKeys(null);
|
|
2293
|
+
}
|
|
2294
|
+
if (sortOp) {
|
|
2295
|
+
setAiSortKey(sortOp.column);
|
|
2296
|
+
setAiSortDir(sortOp.direction);
|
|
2297
|
+
} else {
|
|
2298
|
+
setAiSortKey(null);
|
|
2299
|
+
setAiSortDir(null);
|
|
2300
|
+
}
|
|
2301
|
+
if (hideCols.length > 0 || showCols.length > 0) {
|
|
2302
|
+
setColumns(
|
|
2303
|
+
(prev) => prev.map((col) => {
|
|
2304
|
+
if (hideCols.includes(col.key)) return { ...col, hidden: true };
|
|
2305
|
+
if (showCols.includes(col.key)) return { ...col, hidden: false };
|
|
2306
|
+
return col;
|
|
2307
|
+
})
|
|
2308
|
+
);
|
|
2309
|
+
}
|
|
2310
|
+
for (const rOp of resizeOps) {
|
|
2311
|
+
const w = Math.max(40, Math.min(800, rOp.width));
|
|
2312
|
+
setColumnWidths((prev) => {
|
|
2313
|
+
const n = new Map(prev);
|
|
2314
|
+
n.set(rOp.column, w);
|
|
2315
|
+
return n;
|
|
2316
|
+
});
|
|
2317
|
+
onColumnResize?.(rOp.column, w);
|
|
2318
|
+
}
|
|
2319
|
+
if (reorderOp) {
|
|
2320
|
+
setColumnOrder(reorderOp.order);
|
|
2321
|
+
onColumnOrderChange?.(reorderOp.order);
|
|
2322
|
+
}
|
|
2323
|
+
for (const pOp of pinOps) {
|
|
2324
|
+
setColumns(
|
|
2325
|
+
(prev) => prev.map((col) => col.key === pOp.column ? { ...col, pinned: pOp.pinned } : col)
|
|
2326
|
+
);
|
|
2327
|
+
onColumnPin?.(pOp.column, pOp.pinned);
|
|
2328
|
+
}
|
|
2329
|
+
if (setPageOp) {
|
|
2330
|
+
setInternalPage(setPageOp.page);
|
|
2331
|
+
}
|
|
2332
|
+
setAiResult({ operations: filter.operations, message: `Applied saved filter: ${filter.label}` });
|
|
2333
|
+
setShowSavedFilters(false);
|
|
2334
|
+
}, [data, rowKey, onColumnResize, onColumnOrderChange, onColumnPin]);
|
|
2222
2335
|
const onAIResponseRef = useRef4(onAIResponse);
|
|
2223
2336
|
onAIResponseRef.current = onAIResponse;
|
|
2224
2337
|
const handleAISubmit = useCallback2(async () => {
|
|
@@ -2240,7 +2353,7 @@ function BoltTable({
|
|
|
2240
2353
|
} else {
|
|
2241
2354
|
throw new Error("AI mode requires either aiConfig or onAIQuery prop");
|
|
2242
2355
|
}
|
|
2243
|
-
const { filteredData, sortOp, styleOps: sOps, cellStyleOps: csOps, hideColumns, showColumns } = applyAIOperations(data, response.operations);
|
|
2356
|
+
const { filteredData, sortOp, styleOps: sOps, cellStyleOps: csOps, hideColumns: hideCols, showColumns: showCols, resizeOps, reorderOp, pinOps, setPageOp } = applyAIOperations(data, response.operations);
|
|
2244
2357
|
setAiStyleOps(sOps);
|
|
2245
2358
|
setAiCellStyleOps(csOps);
|
|
2246
2359
|
if (response.operations.some((op) => op.type === "filter")) {
|
|
@@ -2260,15 +2373,37 @@ function BoltTable({
|
|
|
2260
2373
|
setAiSortKey(null);
|
|
2261
2374
|
setAiSortDir(null);
|
|
2262
2375
|
}
|
|
2263
|
-
if (
|
|
2376
|
+
if (hideCols.length > 0 || showCols.length > 0) {
|
|
2264
2377
|
setColumns(
|
|
2265
2378
|
(prev) => prev.map((col) => {
|
|
2266
|
-
if (
|
|
2267
|
-
if (
|
|
2379
|
+
if (hideCols.includes(col.key)) return { ...col, hidden: true };
|
|
2380
|
+
if (showCols.includes(col.key)) return { ...col, hidden: false };
|
|
2268
2381
|
return col;
|
|
2269
2382
|
})
|
|
2270
2383
|
);
|
|
2271
2384
|
}
|
|
2385
|
+
for (const rOp of resizeOps) {
|
|
2386
|
+
const w = Math.max(40, Math.min(800, rOp.width));
|
|
2387
|
+
setColumnWidths((prev) => {
|
|
2388
|
+
const n = new Map(prev);
|
|
2389
|
+
n.set(rOp.column, w);
|
|
2390
|
+
return n;
|
|
2391
|
+
});
|
|
2392
|
+
onColumnResize?.(rOp.column, w);
|
|
2393
|
+
}
|
|
2394
|
+
if (reorderOp) {
|
|
2395
|
+
setColumnOrder(reorderOp.order);
|
|
2396
|
+
onColumnOrderChange?.(reorderOp.order);
|
|
2397
|
+
}
|
|
2398
|
+
for (const pOp of pinOps) {
|
|
2399
|
+
setColumns(
|
|
2400
|
+
(prev) => prev.map((col) => col.key === pOp.column ? { ...col, pinned: pOp.pinned } : col)
|
|
2401
|
+
);
|
|
2402
|
+
onColumnPin?.(pOp.column, pOp.pinned);
|
|
2403
|
+
}
|
|
2404
|
+
if (setPageOp) {
|
|
2405
|
+
setInternalPage(setPageOp.page);
|
|
2406
|
+
}
|
|
2272
2407
|
setAiResult(response);
|
|
2273
2408
|
onAIResponseRef.current?.(response);
|
|
2274
2409
|
} catch (err) {
|
|
@@ -3406,7 +3541,7 @@ function BoltTable({
|
|
|
3406
3541
|
borderBottom: "1px solid rgba(128,128,128,0.2)",
|
|
3407
3542
|
fontSize: 12,
|
|
3408
3543
|
flexShrink: 0,
|
|
3409
|
-
|
|
3544
|
+
zIndex: 20
|
|
3410
3545
|
},
|
|
3411
3546
|
children: [
|
|
3412
3547
|
/* @__PURE__ */ jsxs5(
|
|
@@ -3598,6 +3733,110 @@ function BoltTable({
|
|
|
3598
3733
|
}
|
|
3599
3734
|
)
|
|
3600
3735
|
] }),
|
|
3736
|
+
aiMode && savedAIFilters.length > 0 && /* @__PURE__ */ jsxs5("div", { ref: savedFiltersRef, style: { position: "relative", flexShrink: 0 }, children: [
|
|
3737
|
+
/* @__PURE__ */ jsxs5(
|
|
3738
|
+
"button",
|
|
3739
|
+
{
|
|
3740
|
+
type: "button",
|
|
3741
|
+
onClick: () => setShowSavedFilters((p) => !p),
|
|
3742
|
+
style: {
|
|
3743
|
+
display: "flex",
|
|
3744
|
+
alignItems: "center",
|
|
3745
|
+
gap: 4,
|
|
3746
|
+
background: "none",
|
|
3747
|
+
border: "1px solid rgba(128,128,128,0.2)",
|
|
3748
|
+
borderRadius: 4,
|
|
3749
|
+
cursor: "pointer",
|
|
3750
|
+
padding: "4px 6px",
|
|
3751
|
+
color: "inherit",
|
|
3752
|
+
fontSize: 12
|
|
3753
|
+
},
|
|
3754
|
+
title: "Saved AI filters",
|
|
3755
|
+
children: [
|
|
3756
|
+
/* @__PURE__ */ jsx5("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx5("polygon", { points: "22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" }) }),
|
|
3757
|
+
/* @__PURE__ */ jsx5("span", { children: savedAIFilters.length })
|
|
3758
|
+
]
|
|
3759
|
+
}
|
|
3760
|
+
),
|
|
3761
|
+
showSavedFilters && /* @__PURE__ */ jsxs5(
|
|
3762
|
+
"div",
|
|
3763
|
+
{
|
|
3764
|
+
style: {
|
|
3765
|
+
position: "absolute",
|
|
3766
|
+
top: "100%",
|
|
3767
|
+
right: 0,
|
|
3768
|
+
zIndex: 99999,
|
|
3769
|
+
minWidth: 240,
|
|
3770
|
+
maxWidth: 360,
|
|
3771
|
+
maxHeight: 320,
|
|
3772
|
+
overflowY: "auto",
|
|
3773
|
+
borderRadius: 8,
|
|
3774
|
+
border: "1px solid rgba(128,128,128,0.2)",
|
|
3775
|
+
boxShadow: "0 4px 24px rgba(0,0,0,0.12)",
|
|
3776
|
+
backdropFilter: "blur(16px)",
|
|
3777
|
+
WebkitBackdropFilter: "blur(16px)",
|
|
3778
|
+
backgroundColor: "rgba(128,128,128,0.08)",
|
|
3779
|
+
padding: "4px 0",
|
|
3780
|
+
marginTop: 4
|
|
3781
|
+
},
|
|
3782
|
+
children: [
|
|
3783
|
+
/* @__PURE__ */ jsx5("div", { style: { padding: "6px 12px", fontSize: 11, opacity: 0.5, fontWeight: 600 }, children: "Saved Filters" }),
|
|
3784
|
+
savedAIFilters.map((f, i) => /* @__PURE__ */ jsxs5(
|
|
3785
|
+
"div",
|
|
3786
|
+
{
|
|
3787
|
+
style: {
|
|
3788
|
+
display: "flex",
|
|
3789
|
+
alignItems: "center",
|
|
3790
|
+
gap: 6,
|
|
3791
|
+
padding: "6px 12px",
|
|
3792
|
+
cursor: "pointer",
|
|
3793
|
+
fontSize: 12
|
|
3794
|
+
},
|
|
3795
|
+
onMouseEnter: (e) => {
|
|
3796
|
+
e.currentTarget.style.backgroundColor = "rgba(128,128,128,0.15)";
|
|
3797
|
+
},
|
|
3798
|
+
onMouseLeave: (e) => {
|
|
3799
|
+
e.currentTarget.style.backgroundColor = "transparent";
|
|
3800
|
+
},
|
|
3801
|
+
children: [
|
|
3802
|
+
/* @__PURE__ */ jsx5(
|
|
3803
|
+
"span",
|
|
3804
|
+
{
|
|
3805
|
+
style: { flex: "1 1 0%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
3806
|
+
onClick: () => applySavedFilter(f),
|
|
3807
|
+
children: f.label
|
|
3808
|
+
}
|
|
3809
|
+
),
|
|
3810
|
+
/* @__PURE__ */ jsx5(
|
|
3811
|
+
"button",
|
|
3812
|
+
{
|
|
3813
|
+
type: "button",
|
|
3814
|
+
onClick: (e) => {
|
|
3815
|
+
e.stopPropagation();
|
|
3816
|
+
removeSavedFilter(i);
|
|
3817
|
+
},
|
|
3818
|
+
style: {
|
|
3819
|
+
display: "flex",
|
|
3820
|
+
alignItems: "center",
|
|
3821
|
+
background: "none",
|
|
3822
|
+
border: "none",
|
|
3823
|
+
cursor: "pointer",
|
|
3824
|
+
padding: 2,
|
|
3825
|
+
color: "GrayText",
|
|
3826
|
+
flexShrink: 0
|
|
3827
|
+
},
|
|
3828
|
+
title: "Remove",
|
|
3829
|
+
children: icons?.close ?? /* @__PURE__ */ jsx5(XIcon, { style: { width: 10, height: 10 } })
|
|
3830
|
+
}
|
|
3831
|
+
)
|
|
3832
|
+
]
|
|
3833
|
+
},
|
|
3834
|
+
i
|
|
3835
|
+
))
|
|
3836
|
+
]
|
|
3837
|
+
}
|
|
3838
|
+
)
|
|
3839
|
+
] }),
|
|
3601
3840
|
aiMode && /* @__PURE__ */ jsxs5(
|
|
3602
3841
|
"button",
|
|
3603
3842
|
{
|
|
@@ -3763,6 +4002,37 @@ function BoltTable({
|
|
|
3763
4002
|
children: [
|
|
3764
4003
|
/* @__PURE__ */ jsx5("span", { style: { color: accentColor, display: "flex", flexShrink: 0 }, children: icons?.sparkles ?? /* @__PURE__ */ jsx5(SparklesIcon, { style: { width: 14, height: 14 } }) }),
|
|
3765
4004
|
/* @__PURE__ */ jsx5("span", { style: { flex: "1 1 0%", opacity: 0.85 }, children: aiResult.message }),
|
|
4005
|
+
/* @__PURE__ */ jsxs5(
|
|
4006
|
+
"button",
|
|
4007
|
+
{
|
|
4008
|
+
type: "button",
|
|
4009
|
+
onClick: saveCurrentAIFilter,
|
|
4010
|
+
style: {
|
|
4011
|
+
display: "flex",
|
|
4012
|
+
alignItems: "center",
|
|
4013
|
+
gap: 4,
|
|
4014
|
+
background: `${accentColor}12`,
|
|
4015
|
+
border: `1px solid ${accentColor}30`,
|
|
4016
|
+
borderRadius: 4,
|
|
4017
|
+
cursor: "pointer",
|
|
4018
|
+
padding: "2px 8px",
|
|
4019
|
+
color: accentColor,
|
|
4020
|
+
fontSize: 11,
|
|
4021
|
+
flexShrink: 0,
|
|
4022
|
+
fontWeight: 500,
|
|
4023
|
+
transition: "all 0.2s ease"
|
|
4024
|
+
},
|
|
4025
|
+
title: "Save this filter for quick access later",
|
|
4026
|
+
children: [
|
|
4027
|
+
/* @__PURE__ */ jsxs5("svg", { width: "10", height: "10", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
4028
|
+
/* @__PURE__ */ jsx5("path", { d: "M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" }),
|
|
4029
|
+
/* @__PURE__ */ jsx5("polyline", { points: "17 21 17 13 7 13 7 21" }),
|
|
4030
|
+
/* @__PURE__ */ jsx5("polyline", { points: "7 3 7 8 15 8" })
|
|
4031
|
+
] }),
|
|
4032
|
+
/* @__PURE__ */ jsx5("span", { children: "Save Filter" })
|
|
4033
|
+
]
|
|
4034
|
+
}
|
|
4035
|
+
),
|
|
3766
4036
|
/* @__PURE__ */ jsxs5(
|
|
3767
4037
|
"button",
|
|
3768
4038
|
{
|
package/package.json
CHANGED