@redsift/table 9.2.4 → 9.3.0

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/index.d.ts CHANGED
@@ -83,11 +83,22 @@ type StyledDataGridProps = {
83
83
 
84
84
  declare const DataGrid: Comp<DataGridProps, HTMLDivElement>;
85
85
 
86
+ type CompletionResponse = {
87
+ linkOperator: 'and' | 'or';
88
+ items: [
89
+ {
90
+ columnField: string;
91
+ operatorValue: string;
92
+ value?: string;
93
+ }
94
+ ];
95
+ };
86
96
  interface FilterConfig {
87
97
  columns: object[];
88
98
  typeOperators: object;
89
99
  notes: string;
90
- openaiApiKey: string | undefined;
100
+ openaiApiKey?: string;
101
+ completionFunc?: (nlpFilterConfig: FilterConfig, prompt: string, model: string) => Promise<CompletionResponse>;
91
102
  }
92
103
 
93
104
  type GridToolbarColumnsProps = Omit<typeof GridToolbarColumnsButton, 'ref'>;
package/index.js CHANGED
@@ -3,7 +3,7 @@ export * from '@mui/x-data-grid-pro';
3
3
  export { getGridBooleanOperators, getGridDateOperators, getGridSingleSelectOperators } from '@mui/x-data-grid-pro';
4
4
  import * as React from 'react';
5
5
  import React__default, { Children, isValidElement, cloneElement, useLayoutEffect, useEffect, useRef, forwardRef, useState, useCallback, createElement } from 'react';
6
- import { Icon, baseContainer, useId as useId$2, partitionComponents, isComponent, Flexbox, TextField as TextField$2, Button, Switch, CtasColorPalette, NotificationsColorPalette, IconButton as IconButton$2, Checkbox, Text, LinkButton, Shield } from '@redsift/design-system';
6
+ import { Icon, baseContainer, useId as useId$2, partitionComponents, isComponent, Flexbox, TextField as TextField$2, Button, Switch, Text, CtasColorPalette, NotificationsColorPalette, IconButton as IconButton$2, Checkbox, LinkButton, Shield } from '@redsift/design-system';
7
7
  import { mdiSync, mdiFilterVariant, mdiViewColumn, mdiChevronUp, mdiChevronDown, mdiViewHeadline, mdiViewSequential, mdiViewStream, mdiChevronRight, mdiTrayArrowDown } from '@redsift/icons';
8
8
  import emStyled from '@emotion/styled';
9
9
  import { Global, ThemeContext, keyframes } from '@emotion/react';
@@ -21661,12 +21661,7 @@ const StyledGridToolbarFilterSemanticField = styled$3.form`
21661
21661
  const API_URL = 'https://api.openai.com/v1/chat/completions';
21662
21662
  async function getCompletion(text, role, openai_api_key) {
21663
21663
  let model = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'gpt-3.5-turbo-0613';
21664
- let verbose = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true;
21665
- /**
21666
- * Returns the text responded by GPT.
21667
- */
21668
21664
  try {
21669
- const t0 = new Date();
21670
21665
  const messages = [{
21671
21666
  role: 'system',
21672
21667
  content: role
@@ -21674,15 +21669,8 @@ async function getCompletion(text, role, openai_api_key) {
21674
21669
  role: 'user',
21675
21670
  content: text
21676
21671
  }];
21677
- if (verbose) {
21678
- console.log('******************************');
21679
- console.log('prompt:', text);
21680
- console.log('model:', model);
21681
- console.log('characters count (4 chars ~ 1 token)');
21682
- console.log(`- system=${role.length}`);
21683
- console.log(`- user=${text.length}`);
21684
- }
21685
- const response = await fetch(API_URL, {
21672
+ const url = API_URL;
21673
+ const response = await fetch(url, {
21686
21674
  method: 'POST',
21687
21675
  headers: {
21688
21676
  'Content-Type': 'application/json',
@@ -21695,15 +21683,6 @@ async function getCompletion(text, role, openai_api_key) {
21695
21683
  })
21696
21684
  });
21697
21685
  const data = await response.json();
21698
- if (verbose) {
21699
- const t1 = new Date();
21700
- const t = (role.length + text.length + data.choices[0].message.content.length) / 4;
21701
- console.log(`- response=${data.choices[0].message.content.length}`);
21702
- console.log(`total is about ${t.toFixed(0)} tokens and took ${// @ts-ignore
21703
- ((t1 - t0) / 1000).toFixed(0)} seconds to complete`);
21704
- console.log('response:');
21705
- console.log(data.choices[0].message.content);
21706
- }
21707
21686
  return data.choices[0].message.content;
21708
21687
  } catch (error) {
21709
21688
  return '';
@@ -21714,77 +21693,66 @@ const _excluded$e = ["className", "nlpFilterConfig", "onFilterModelChange"];
21714
21693
  const COMPONENT_NAME$2 = 'GridToolbarFilterSemanticField';
21715
21694
  const CLASSNAME$2 = 'redsift-datagrid-toolbar-nlp-filter-field';
21716
21695
  const DATE_FORMAT = 'yyyy-mm-dd';
21696
+ const DEFAULT_GPT_MODEL = 'gpt-4-0613';
21717
21697
  const DEFAULT_FILTER = {
21718
21698
  items: []
21719
21699
  };
21720
- const ROLE = `The AI assistant parses user input to generate a JSON object that will be used as a row filter for a data table MUI Data Grid.
21700
+ const getRole = config => {
21701
+ const today = new Date().toDateString();
21702
+ const columns = `[${config.columns.map(_ref => {
21703
+ let {
21704
+ field
21705
+ } = _ref;
21706
+ return `"${field}"`;
21707
+ }).join(', ')}]`;
21708
+ const operators = Object.entries(config.typeOperators).map(_ref2 => {
21709
+ let [k, values] = _ref2;
21710
+ return values.length === 1 ? ` - For "${k}" data type, operator must only be "${values[0]}"` : ` - For "${k}" data type, operator must be one of [${values.map(v => `"${v}"`).join(', ')}]`;
21711
+ }).join('\n');
21712
+ const column_description = config.columns.map(_ref3 => {
21713
+ let {
21714
+ field,
21715
+ type,
21716
+ description
21717
+ } = _ref3;
21718
+ return `- "${field}": "${type}" data type; ${description ? description.trim() : ''}`;
21719
+ }).join('\n');
21720
+ return `The AI assistant parses user input to generate a JSON object that will be used as a row filter for a data table MUI Data Grid.
21721
21721
  The filter supports mulitple conditions using only two logical operator "and", "or". It only allows "and" between all conditions or "or" between all conditions. It can't mix the two types.
21722
21722
  The AI assistant extracts information from the user input and generates a JSON object with exactly the two keys "linkOperator" and "items":
21723
21723
  - "linkOperator": the logical operator, only "and" or "or" are allowed. If there is only one condition in the "items", use "and".
21724
21724
  - "items": a list of conditions, each is an object with exactly the three keys "columnField", "operatorValue" and "value":
21725
- - "columnField": the column name, must be one of {{columns}}
21726
- - "value":
21725
+ - "columnField": the column name, must be one of ${columns}
21726
+ - "value":
21727
21727
  - this can be skipped if the "operatorValue" is either "isEmpty" or "isNotEmpty"
21728
21728
  - a list of multiple values if the "operatorValue" ends with "AnyOf"
21729
- - otherwise, it's a single value represented as a string: "true" instead of true, "false" instead of false, "0.6" instead of 0.6.
21730
- For "date" data type, use ${DATE_FORMAT}. If relative date is input, convert to the actual date given today is {{today}}.
21729
+ - otherwise, it's a single value represented as a string: "true" instead of true, "false" instead of false, "0.6" instead of 0.6.
21730
+ For "date" data type, use ${DATE_FORMAT}. If relative date is input, convert to the actual date given today is ${today}.
21731
21731
  - "operatorValue": the comparison operator, accepted values depend on the data type of the column
21732
- {{type operators}}
21732
+ ${operators}
21733
21733
 
21734
21734
  Below is the datatype in square bracket, constraints on the data range if any, followed by the description of each column used in the data table:
21735
- {{column description}}
21735
+ ${column_description}
21736
21736
 
21737
21737
  Notes:
21738
21738
  - For "boolean" data type, use "is" operator with value "false" instead of "isEmpty".
21739
- {{notes}}
21739
+ ${config.notes.trim()}
21740
21740
 
21741
21741
  Pay close attention to the the data type, description and supported operators above to make a valid selection of fields.
21742
21742
  Think step by step and check carefully if the chosen operator is supported by the chosen data type.
21743
21743
  Return just the JSON object without any extra text, explanation or note.
21744
- If the user input can't be parsed, return a JSON object to indicate the error and the reason {"code":"error", "reason":"explain why it was failed to parse"}.`;
21745
- function fillSlots(role, config) {
21746
- // Today
21747
- const todayText = new Date().toDateString();
21748
- role = role.replace('{{today}}', todayText);
21749
-
21750
- // Column names
21751
- const columnText = '[' + config.columns.map(_ref => {
21752
- let {
21753
- field
21754
- } = _ref;
21755
- return `"${field}"`;
21756
- }).join(', ') + ']';
21757
- role = role.replace('{{columns}}', columnText);
21758
-
21759
- // Type operators
21760
- const opreatorText = Object.entries(config.typeOperators).map(_ref2 => {
21761
- let [k, values] = _ref2;
21762
- if (values.length == 1) {
21763
- return ` - For "${k}" data type, operator must only be "${values[0]}"`;
21764
- } else {
21765
- const types = '[' + values.map(v => `"${v}"`).join(', ') + ']';
21766
- return ` - For "${k}" data type, operator must be one of ${types}`;
21767
- }
21768
- }).join('\n');
21769
- role = role.replace('{{type operators}}', opreatorText);
21770
-
21771
- // Column description
21772
- // const descriptionText = config.columns.map(({field, type, description}) => `- "${field}": "${type}" data type; operators must be one of [${config.typeOperators[type].map(v => '"' + v + '"').join(', ')}]; ${description.trim()}`).join('\n');
21773
- const descriptionText = config.columns.map(_ref3 => {
21774
- let {
21775
- field,
21776
- type,
21777
- description
21778
- } = _ref3;
21779
- return `- "${field}": "${type}" data type; ${description ? description.trim() : ''}`;
21780
- }).join('\n');
21781
- role = role.replace('{{column description}}', descriptionText);
21782
-
21783
- // Notes
21784
- role = role.replace('{{notes}}', config.notes.trim());
21785
-
21786
- // console.log(role);
21787
- return role;
21744
+ If the user input can't be parsed, return a JSON object to indicate the error and the reason {"code":"error", "reason":"explain why it was failed to parse"}.
21745
+ `;
21746
+ };
21747
+ async function getOpenAICompletion(config, prompt, model) {
21748
+ const text = 'Parse the text delimited by triple backticks: ```' + prompt.trim() + '``` and make sure the output is a valid JSON object';
21749
+ const role = getRole(config);
21750
+ const completion = await getCompletion(text, role, config.openaiApiKey, model);
21751
+ const response = JSON.parse(completion);
21752
+ if ('code' in response) {
21753
+ throw new Error(response.reason);
21754
+ }
21755
+ return response;
21788
21756
  }
21789
21757
 
21790
21758
  /**
@@ -21799,7 +21767,8 @@ const GridToolbarFilterSemanticField = /*#__PURE__*/forwardRef((props, ref) => {
21799
21767
  } = props,
21800
21768
  forwardedProps = _objectWithoutProperties(props, _excluded$e);
21801
21769
  const [prompt, setPrompt] = useState('');
21802
- const [model, setModel] = useState('gpt-4-0613');
21770
+ const modelRef = useRef(DEFAULT_GPT_MODEL);
21771
+ const showErrorRef = useRef(false);
21803
21772
  const [isLoading, setIsLoading] = useState(false);
21804
21773
  const handlePromptSubmit = async event => {
21805
21774
  event.preventDefault();
@@ -21811,36 +21780,33 @@ const GridToolbarFilterSemanticField = /*#__PURE__*/forwardRef((props, ref) => {
21811
21780
  const response = sessionStorage.getItem(prompt);
21812
21781
  if (response && response !== JSON.stringify(DEFAULT_FILTER)) {
21813
21782
  filter = JSON.parse(response);
21814
- console.log('filter from cache:', filter);
21815
21783
  } else {
21816
21784
  setIsLoading(true);
21817
- const role = fillSlots(ROLE, nlpFilterConfig);
21818
- const text = 'Parse the text delimited by triple backticks: ```' + prompt.trim() + '``` and make sure the output is a valid JSON object';
21819
- const output_str = await getCompletion(text, role, nlpFilterConfig.openaiApiKey, model);
21785
+ showErrorRef.current = false;
21820
21786
  try {
21821
- filter = JSON.parse(output_str);
21822
- if (filter.code === 'error') {
21823
- console.log('GPT does not understand input', filter.reason);
21824
- filter = DEFAULT_FILTER;
21787
+ if (nlpFilterConfig.completionFunc !== undefined) {
21788
+ filter = await nlpFilterConfig.completionFunc(nlpFilterConfig, prompt, modelRef.current);
21825
21789
  } else {
21826
- sessionStorage.setItem(prompt, output_str);
21790
+ filter = await getOpenAICompletion(nlpFilterConfig, prompt, modelRef.current);
21827
21791
  }
21792
+ sessionStorage.setItem(prompt, JSON.stringify(filter));
21828
21793
  } catch (error) {
21829
- console.log(error);
21794
+ showErrorRef.current = true;
21830
21795
  filter = DEFAULT_FILTER;
21831
21796
  }
21832
21797
 
21833
21798
  // MUI requires different id
21834
- filter.items.forEach((d, i) => {
21835
- d.id = i;
21836
- });
21837
- console.log('filter:', filter);
21799
+ filter.items.forEach((d, i) => d.id = i);
21838
21800
  }
21839
21801
  onFilterModelChange(filter);
21840
21802
  setIsLoading(false);
21841
21803
  }
21842
21804
  };
21843
- return /*#__PURE__*/React__default.createElement(StyledGridToolbarFilterSemanticField, _extends$2({}, forwardedProps, {
21805
+ return /*#__PURE__*/React__default.createElement(Flexbox, {
21806
+ flexDirection: "column",
21807
+ gap: "0",
21808
+ width: "100%"
21809
+ }, /*#__PURE__*/React__default.createElement(StyledGridToolbarFilterSemanticField, _extends$2({}, forwardedProps, {
21844
21810
  className: classNames(GridToolbarFilterSemanticField.className, className),
21845
21811
  ref: fieldRef,
21846
21812
  onSubmit: handlePromptSubmit
@@ -21858,13 +21824,14 @@ const GridToolbarFilterSemanticField = /*#__PURE__*/forwardRef((props, ref) => {
21858
21824
  "aria-label": "Submit",
21859
21825
  type: "submit",
21860
21826
  isLoading: isLoading
21861
- }, "Run")), /*#__PURE__*/React__default.createElement(Tooltip, null, /*#__PURE__*/React__default.createElement(Tooltip.Trigger, null, /*#__PURE__*/React__default.createElement(Switch
21862
- // style={{"display":"none"}} // Hide it for the demo
21863
- , {
21827
+ }, "Run")), /*#__PURE__*/React__default.createElement(Tooltip, null, /*#__PURE__*/React__default.createElement(Tooltip.Trigger, null, /*#__PURE__*/React__default.createElement(Switch, {
21864
21828
  width: "175px",
21865
- isSelected: model === 'gpt-4-0613',
21866
- onChange: value => setModel(value ? 'gpt-4-0613' : 'gpt-3.5-turbo-0613')
21867
- }, "Power mode")), /*#__PURE__*/React__default.createElement(Tooltip.Content, null, "The Power mode can get better results but is slower.")));
21829
+ isSelected: modelRef.current === 'gpt-4-0613',
21830
+ onChange: value => modelRef.current = value ? 'gpt-4-0613' : 'gpt-3.5-turbo-0613'
21831
+ }, "Power mode")), /*#__PURE__*/React__default.createElement(Tooltip.Content, null, "The Power mode can get better results but is slower."))), showErrorRef.current && /*#__PURE__*/React__default.createElement(Text, {
21832
+ color: "error",
21833
+ marginLeft: "8px"
21834
+ }, "Unable to find a valid filter, please try again with a more specific prompt."));
21868
21835
  });
21869
21836
  GridToolbarFilterSemanticField.className = CLASSNAME$2;
21870
21837
  GridToolbarFilterSemanticField.displayName = COMPONENT_NAME$2;