@redsift/table 9.2.4-muiv5 → 9.3.0-muiv5

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