@websolutespa/bom-llm 0.1.6 → 0.1.7

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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # @websolutespa/bom-llm
2
2
 
3
+ ## 0.1.7
4
+
5
+ ### Patch Changes
6
+
7
+ - ba32bef: Added: maxFileSize & mimeTypes
8
+ - f826c08: Added: storageMode option
9
+
3
10
  ## 0.1.6
4
11
 
5
12
  ### Patch Changes
package/README.md CHANGED
@@ -148,17 +148,6 @@ You can handle the click event of the generated cta action with the onAction han
148
148
  bomLlm(options);
149
149
  ```
150
150
 
151
- ### Supported types
152
-
153
- Currently available types are:
154
-
155
- - event
156
- - eventItem
157
- - poi
158
- - poiItem
159
- - tripadvisor
160
- - tripadvisorItem
161
-
162
151
  ---
163
152
 
164
153
  ##### *this library is for internal usage and not production ready*
package/dist/esm/index.js CHANGED
@@ -19580,6 +19580,35 @@ const IconLlmPlay = /*#__PURE__*/React.forwardRef((props, ref) => {
19580
19580
  });
19581
19581
  IconLlmPlay.displayName = 'IconLlmPlay';
19582
19582
 
19583
+ const IconLlmRefresh = /*#__PURE__*/React.forwardRef((props, ref) => {
19584
+ return /*#__PURE__*/jsxRuntimeExports.jsxs("svg", {
19585
+ xmlns: "http://www.w3.org/2000/svg",
19586
+ width: "1em",
19587
+ height: "1em",
19588
+ viewBox: "0 0 24 24",
19589
+ fill: "none",
19590
+ stroke: "currentColor",
19591
+ strokeWidth: "2",
19592
+ strokeLinecap: "round",
19593
+ strokeLinejoin: "round",
19594
+ className: "icon-llm-refresh",
19595
+ "data-icon": "icon-llm-refresh",
19596
+ "aria-hidden": "true",
19597
+ ...props,
19598
+ ref: ref,
19599
+ children: [/*#__PURE__*/jsxRuntimeExports.jsx("path", {
19600
+ d: "M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"
19601
+ }), /*#__PURE__*/jsxRuntimeExports.jsx("path", {
19602
+ d: "M3 3v5h5"
19603
+ }), /*#__PURE__*/jsxRuntimeExports.jsx("path", {
19604
+ d: "M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"
19605
+ }), /*#__PURE__*/jsxRuntimeExports.jsx("path", {
19606
+ d: "M16 16h5v5"
19607
+ })]
19608
+ });
19609
+ });
19610
+ IconLlmRefresh.displayName = 'IconLlmRefresh';
19611
+
19583
19612
  const IconLlmSend = /*#__PURE__*/React.forwardRef((props, ref) => {
19584
19613
  return /*#__PURE__*/jsxRuntimeExports.jsx("svg", {
19585
19614
  enableBackground: "new 0 0 24 24",
@@ -31975,6 +32004,10 @@ export type LlmChunkUnknownItem = Omit<Record<string, unknown>, 'type' | 'id'> &
31975
32004
  // type StuctureMappedType<T extends LlmChunkType> = { [K in T]: Array<LlmChunkItem<K>> };
31976
32005
  // export type LlmChunkItems<T extends LlmChunkType = LlmChunkType> = Record<T, (item: Extract<LlmChunkItem, { type: T }>) => string>;
31977
32006
 
32007
+ const LLM_DEFAULT_MIME_TYPES = '.jpg, .jpeg, .png, .svg, .webp, .txt, .md, .pdf, .csv, .doc, .xls, .ppt';
32008
+ const LLM_DEFAULT_MAX_FILE_SIZE = 1048576; // 1Mb
32009
+ // export const LLM_DEFAULT_MAX_FILE_SIZE = 2097152; // 2Mb
32010
+
31978
32011
  const Chunk = _ref => {
31979
32012
  let {
31980
32013
  item,
@@ -41316,6 +41349,7 @@ styleInject(css_248z);
41316
41349
  const PageUpload = () => {
41317
41350
  const label = useLabel();
41318
41351
  const upload = useLlm(state => state.upload);
41352
+ const mimeTypes = useLlm(state => state.mimeTypes);
41319
41353
  const {
41320
41354
  removeFile,
41321
41355
  addFile,
@@ -41333,7 +41367,7 @@ const PageUpload = () => {
41333
41367
  className: getClassNames(style.input),
41334
41368
  type: "file",
41335
41369
  id: "file-input",
41336
- accept: ".jpg, .jpeg, .png, .webp, .gif, .bmp, .tif, .tiff, .txt, .rtf, .csv, .doc, .docx, .pdf",
41370
+ accept: mimeTypes,
41337
41371
  onChange: addFile
41338
41372
  })]
41339
41373
  }), upload && /*#__PURE__*/jsxRuntimeExports.jsxs("button", {
@@ -41417,7 +41451,10 @@ const Prompt = () => {
41417
41451
  const messages = useLlm(state => state.messages);
41418
41452
  const streaming = useLlm(state => state.streaming);
41419
41453
  const speakEnabled = useLlm(state => state.speakEnabled);
41454
+ const mimeTypes = useLlm(state => state.mimeTypes);
41455
+ const storageMode = useLlm(state => state.storageMode);
41420
41456
  const {
41457
+ clear,
41421
41458
  setPrompt,
41422
41459
  recognizeStart,
41423
41460
  recognizeStop,
@@ -41475,56 +41512,71 @@ const Prompt = () => {
41475
41512
  className: "llm__prompt-speak",
41476
41513
  type: "button",
41477
41514
  "aria-label": speakEnabled ? label('llm.disableSpeak') : label('llm.enableSpeak'),
41515
+ title: speakEnabled ? label('llm.disableSpeak') : label('llm.enableSpeak'),
41478
41516
  "aria-pressed": speakEnabled,
41479
41517
  onClick: toggleSpeak,
41480
41518
  children: speakEnabled ? /*#__PURE__*/jsxRuntimeExports.jsx(IconLlmVolume, {}) : /*#__PURE__*/jsxRuntimeExports.jsx(IconLlmVolumeX, {})
41481
- }), /*#__PURE__*/jsxRuntimeExports.jsx("label", {
41482
- htmlFor: "llm-prompt-textarea",
41483
- className: "llm__prompt-label",
41484
- children: label('llm.prompt')
41485
- }), /*#__PURE__*/jsxRuntimeExports.jsx("textarea", {
41486
- id: "llm-prompt-textarea",
41487
- className: "llm__prompt-textarea",
41488
- name: "prompt",
41489
- placeholder: app?.contents.promptPlaceholder,
41490
- onKeyDown: onKeyDown,
41491
- onChange: onChange,
41492
- ref: textAreaRef,
41493
- rows: 1,
41494
- value: prompt,
41495
- "aria-disabled": streaming
41496
- }), /*#__PURE__*/jsxRuntimeExports.jsx(BusyIndicator, {}), hasUpload && /*#__PURE__*/jsxRuntimeExports.jsxs("div", {
41497
- className: "llm__prompt-upload-group",
41498
- children: [/*#__PURE__*/jsxRuntimeExports.jsx("div", {
41499
- className: "llm__prompt-upload",
41500
- "aria-label": label('llm.upload'),
41501
- children: /*#__PURE__*/jsxRuntimeExports.jsx(IconLlmPaperclip, {})
41502
- }), /*#__PURE__*/jsxRuntimeExports.jsx("input", {
41503
- className: "llm__prompt-upload-input",
41504
- type: "file",
41505
- id: "file-input",
41506
- accept: ".jpg, .jpeg, .png, .webp, .gif, .bmp, .tif, .tiff, .txt, .rtf, .csv, .doc, .docx, .pdf",
41507
- onChange: addFile
41519
+ }), storageMode === 'session' && threadId && /*#__PURE__*/jsxRuntimeExports.jsx("button", {
41520
+ className: "llm__prompt-speak",
41521
+ type: "button",
41522
+ "aria-label": label('llm.newThread'),
41523
+ title: label('llm.newThread'),
41524
+ onClick: clear,
41525
+ children: /*#__PURE__*/jsxRuntimeExports.jsx(IconLlmRefresh, {})
41526
+ }), /*#__PURE__*/jsxRuntimeExports.jsxs("div", {
41527
+ className: "llm__prompt-textareaGroup",
41528
+ children: [/*#__PURE__*/jsxRuntimeExports.jsx("label", {
41529
+ htmlFor: "llm-prompt-textarea",
41530
+ className: "llm__prompt-label",
41531
+ children: label('llm.prompt')
41532
+ }), /*#__PURE__*/jsxRuntimeExports.jsx("textarea", {
41533
+ id: "llm-prompt-textarea",
41534
+ className: "llm__prompt-textarea",
41535
+ name: "prompt",
41536
+ placeholder: app?.contents.promptPlaceholder,
41537
+ onKeyDown: onKeyDown,
41538
+ onChange: onChange,
41539
+ ref: textAreaRef,
41540
+ rows: 1,
41541
+ value: prompt,
41542
+ "aria-disabled": streaming
41543
+ }), /*#__PURE__*/jsxRuntimeExports.jsx(BusyIndicator, {})]
41544
+ }), hasUpload && /*#__PURE__*/jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, {
41545
+ children: [/*#__PURE__*/jsxRuntimeExports.jsxs("div", {
41546
+ className: "llm__prompt-upload-group",
41547
+ children: [/*#__PURE__*/jsxRuntimeExports.jsx("div", {
41548
+ className: "llm__prompt-upload",
41549
+ "aria-label": label('llm.upload'),
41550
+ title: label('llm.upload'),
41551
+ children: /*#__PURE__*/jsxRuntimeExports.jsx(IconLlmPaperclip, {})
41552
+ }), /*#__PURE__*/jsxRuntimeExports.jsx("input", {
41553
+ className: "llm__prompt-upload-input",
41554
+ type: "file",
41555
+ id: "file-input",
41556
+ accept: mimeTypes,
41557
+ onChange: addFile
41558
+ })]
41559
+ }), upload && /*#__PURE__*/jsxRuntimeExports.jsxs("button", {
41560
+ className: "llm__prompt-upload-file",
41561
+ onClick: () => removeFile(),
41562
+ title: upload.file.name,
41563
+ children: [/*#__PURE__*/jsxRuntimeExports.jsx("span", {
41564
+ children: upload.file.name
41565
+ }), /*#__PURE__*/jsxRuntimeExports.jsx(IconX, {}), /*#__PURE__*/jsxRuntimeExports.jsx("img", {
41566
+ src: upload.base64,
41567
+ alt: ""
41568
+ })]
41508
41569
  })]
41509
41570
  }), hasSpeechRecognition && /*#__PURE__*/jsxRuntimeExports.jsx("button", {
41510
41571
  className: "llm__prompt-microphone",
41511
41572
  type: "button",
41512
41573
  "aria-label": label('llm.dictate'),
41574
+ title: label('llm.dictate'),
41513
41575
  onMouseDown: () => recognizeStart(),
41514
41576
  onMouseUp: () => recognizeStop(),
41515
41577
  onTouchStart: () => recognizeStart(),
41516
41578
  onTouchEnd: () => recognizeStop(),
41517
41579
  children: /*#__PURE__*/jsxRuntimeExports.jsx(IconLlmMicrophone, {})
41518
- }), hasUpload && upload && /*#__PURE__*/jsxRuntimeExports.jsxs("button", {
41519
- className: "llm__prompt-upload-file",
41520
- onClick: () => removeFile(),
41521
- title: upload.file.name,
41522
- children: [/*#__PURE__*/jsxRuntimeExports.jsx("span", {
41523
- children: upload.file.name
41524
- }), /*#__PURE__*/jsxRuntimeExports.jsx(IconX, {}), /*#__PURE__*/jsxRuntimeExports.jsx("img", {
41525
- src: upload.base64,
41526
- alt: ""
41527
- })]
41528
41580
  })]
41529
41581
  }), streaming ? /*#__PURE__*/jsxRuntimeExports.jsxs("button", {
41530
41582
  className: "llm__prompt-stop",
@@ -44044,6 +44096,7 @@ class ApiService {
44044
44096
  if (mode === void 0) {
44045
44097
  mode = 'default';
44046
44098
  }
44099
+ // console.log('ApiService.decoratedHistory.storedHistory', storedHistory);
44047
44100
  if (storedHistory.length > 0) {
44048
44101
  const threadIds = storedHistory.reduce((p, c) => {
44049
44102
  if (!p.includes(c.threadId)) {
@@ -44051,16 +44104,18 @@ class ApiService {
44051
44104
  }
44052
44105
  return p;
44053
44106
  }, []);
44054
- // console.log('threadIds', threadIds);
44107
+ // console.log('ApiService.decoratedHistory.threadIds', threadIds);
44055
44108
  const decodedThreads = {};
44056
44109
  for (const threadId of threadIds) {
44057
44110
  const app = await this.getInfo({
44058
44111
  threadId
44059
44112
  });
44113
+ // console.log('ApiService.decoratedHistory.thread', app?.thread);
44060
44114
  if (app?.thread) {
44061
44115
  const decodedThread = MessageDecoder.decodeThread(app.thread);
44116
+ // console.log('ApiService.decoratedHistory.decodedThread', decodedThread);
44062
44117
  if (decodedThread) {
44063
- decodedThreads[app.thread.threadId] = decodedThread;
44118
+ decodedThreads[threadId] = decodedThread;
44064
44119
  }
44065
44120
  }
44066
44121
  }
@@ -44072,16 +44127,27 @@ class ApiService {
44072
44127
  } = _ref;
44073
44128
  const decodedThread = decodedThreads[threadId];
44074
44129
  if (decodedThread) {
44130
+ decodedThread.messages.forEach(x => {
44131
+ if (x.role === 'assistant') {
44132
+ x.threadId = threadId;
44133
+ // this should fix old history missing messageId;
44134
+ if (!x.messageId && mode !== 'page') {
44135
+ x.messageId = messageId;
44136
+ }
44137
+ }
44138
+ });
44075
44139
  const index = this.options.test ? i + 1 : decodedThread.messages.findIndex(x => x.messageId === messageId);
44140
+ // console.log('ApiService.decoratedHistory.index', index);
44076
44141
  if (index !== -1) {
44077
44142
  const messages = decodedThread.messages.slice(0, index + 1);
44143
+ // console.log('ApiService.decoratedHistory.messages', messages);
44078
44144
  if (this.options.test) {
44079
44145
  messages[index].threadId = threadId;
44080
44146
  messages[index].messageId = messageId;
44081
44147
  messages[index].createdAt = new Date().toISOString();
44082
44148
  }
44083
44149
  const histories = messagesToHistory(messages);
44084
- // console.log(histories);
44150
+ // console.log('ApiService.decoratedHistory.histories', histories);
44085
44151
  /*
44086
44152
  const history: LlmHistory = {
44087
44153
  threadId,
@@ -44092,10 +44158,11 @@ class ApiService {
44092
44158
  */
44093
44159
  return histories[0];
44094
44160
  }
44161
+ return undefined;
44095
44162
  }
44096
44163
  return undefined;
44097
- }).filter(x => Boolean(x));
44098
- // console.log('history', history);
44164
+ }).filter(x => x !== undefined);
44165
+ // console.log('ApiService.decoratedHistory.history', history);
44099
44166
  history.sort((a, b) => {
44100
44167
  if (a.createdAt && b.createdAt) {
44101
44168
  return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
@@ -44887,6 +44954,17 @@ function fileToResizedBase64(file, maxSize) {
44887
44954
  reader.readAsDataURL(file);
44888
44955
  });
44889
44956
  }
44957
+ function formatBytes(bytes, decimals) {
44958
+ if (decimals === void 0) {
44959
+ decimals = 2;
44960
+ }
44961
+ if (!+bytes) return '0 Bytes';
44962
+ const k = 1024;
44963
+ const dm = decimals < 0 ? 0 : decimals;
44964
+ const sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
44965
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
44966
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
44967
+ }
44890
44968
 
44891
44969
  const createLlmStore = config => {
44892
44970
  const sanitizedConfig = (config.plugins || []).reduce((p, c) => {
@@ -44913,7 +44991,6 @@ const createLlmStore = config => {
44913
44991
  customTheme,
44914
44992
  endpoint,
44915
44993
  preview,
44916
- storage,
44917
44994
  test,
44918
44995
  threadId,
44919
44996
  decorateUrl,
@@ -44924,30 +45001,33 @@ const createLlmStore = config => {
44924
45001
  const theme = getThemes([customTheme], config.defaultTheme);
44925
45002
  const vars = getVars(theme);
44926
45003
  const props = {
44927
- hydrated: false,
44928
- theme,
44929
- vars,
44930
- messages: [],
44931
- history: [],
44932
- storedHistory: [],
44933
- contexts,
44934
- prompt: '',
44935
- locale: 'en',
44936
- streaming: false,
44937
- speakEnabled: true,
44938
45004
  asideEnabled: false,
44939
- ready: false,
44940
- components: {
44941
- ...LLM_COMPONENTS,
44942
- ...options.components
44943
- },
44944
45005
  blocks: {
44945
45006
  ...LLM_BLOCKS,
44946
45007
  ...options.blocks
44947
45008
  },
45009
+ components: {
45010
+ ...LLM_COMPONENTS,
45011
+ ...options.components
45012
+ },
45013
+ contexts,
45014
+ history: [],
45015
+ hydrated: false,
45016
+ locale: 'en',
45017
+ maxFileSize: LLM_DEFAULT_MAX_FILE_SIZE,
45018
+ messages: [],
45019
+ mimeTypes: LLM_DEFAULT_MIME_TYPES,
45020
+ prompt: '',
45021
+ ready: false,
45022
+ speakEnabled: true,
45023
+ storageMode: options.storageMode || 'local',
45024
+ storedHistory: [],
45025
+ streaming: false,
44948
45026
  systemContext: {
44949
45027
  ...options.systemContext
44950
- }
45028
+ },
45029
+ theme,
45030
+ vars
44951
45031
  };
44952
45032
  let messageService = undefined;
44953
45033
  const speech = new Speech();
@@ -45004,10 +45084,11 @@ const createLlmStore = config => {
45004
45084
  return apiService;
45005
45085
  },
45006
45086
  init: async (locale, instance) => {
45087
+ const state = get();
45007
45088
  const apiService = new ApiService({
45008
45089
  apiKey,
45009
45090
  appKey,
45010
- threadId,
45091
+ threadId: threadId || state.threadId,
45011
45092
  endpoint,
45012
45093
  locale,
45013
45094
  test,
@@ -45017,7 +45098,6 @@ const createLlmStore = config => {
45017
45098
  if (!app) {
45018
45099
  return;
45019
45100
  }
45020
- const state = get();
45021
45101
  const textToSpeechApiKey = app.contents.textToSpeechApiKey;
45022
45102
  const textToSpeechVoiceId = app.contents.textToSpeechVoiceId;
45023
45103
  const synthesisMode = app.contents.disableSpeechSynthesis ? 'none' : app.contents.synthesisMode || 'default';
@@ -45045,6 +45125,7 @@ const createLlmStore = config => {
45045
45125
  */
45046
45126
  const theme = getThemes([app.contents.customTheme, testTheme, customTheme], config.defaultTheme);
45047
45127
  const mode = theme.mode;
45128
+ const storageMode = options.storageMode || app.contents.storageMode || 'local';
45048
45129
  const storedHistory = await getHistory(state.storedHistory);
45049
45130
  const history = await apiService.decorateHistory(storedHistory, mode);
45050
45131
  /*
@@ -45062,11 +45143,14 @@ const createLlmStore = config => {
45062
45143
  locale,
45063
45144
  ready: true,
45064
45145
  recognitionMode,
45146
+ storageMode,
45065
45147
  storedHistory,
45066
45148
  synthesisMode,
45067
45149
  theme,
45068
45150
  threadId,
45069
45151
  vars,
45152
+ mimeTypes: app.contents.mimeTypes || LLM_DEFAULT_MIME_TYPES,
45153
+ maxFileSize: app.contents.maxFileSize || LLM_DEFAULT_MAX_FILE_SIZE,
45070
45154
  ...thread
45071
45155
  };
45072
45156
  const newState = {
@@ -45298,6 +45382,9 @@ const createLlmStore = config => {
45298
45382
  return state.app?.contents.enableUpload === true;
45299
45383
  },
45300
45384
  addFile: async event => {
45385
+ const {
45386
+ maxFileSize
45387
+ } = get();
45301
45388
  const files = event.target?.files;
45302
45389
  // console.log('useLlm.addFile', files, Array.isArray(files));
45303
45390
  try {
@@ -45307,6 +45394,11 @@ const createLlmStore = config => {
45307
45394
  console.log('useLlm.addFile.error', 'file not found');
45308
45395
  return;
45309
45396
  }
45397
+ // const app = get().app;
45398
+ if (file.size > maxFileSize) {
45399
+ toast(`Max file size exceeded ${formatBytes(maxFileSize)}`);
45400
+ return;
45401
+ }
45310
45402
  const base64 = file.type.indexOf('image') === 0 ? await fileToResizedBase64(file) : undefined;
45311
45403
  event.target.value = null;
45312
45404
  // console.log('useLlm.addFile.base64', base64);
@@ -45434,7 +45526,7 @@ const createLlmStore = config => {
45434
45526
  }
45435
45527
  }), {
45436
45528
  name: storeKey,
45437
- storage: createJSONStorage(() => storage || localStorage),
45529
+ storage: createJSONStorage(() => props.storageMode === 'session' ? sessionStorage : localStorage),
45438
45530
  onRehydrateStorage: () => () => {
45439
45531
  useStore.setState({
45440
45532
  hydrated: true
@@ -45446,6 +45538,9 @@ const createLlmStore = config => {
45446
45538
  ...persistedState.app
45447
45539
  };
45448
45540
  }
45541
+ if (persistedState.threadId && currentState.storageMode === 'session') {
45542
+ currentState.threadId = persistedState.threadId;
45543
+ }
45449
45544
  currentState.storedHistory = persistedState.storedHistory && [...persistedState.storedHistory];
45450
45545
  currentState.speakEnabled = persistedState.speakEnabled || false;
45451
45546
  speech.enabled = currentState.speakEnabled;
@@ -45459,9 +45554,10 @@ const createLlmStore = config => {
45459
45554
  return currentState;
45460
45555
  },
45461
45556
  partialize: state => {
45557
+ const enabledKeys = state.storageMode === 'session' ? ['locale', 'speakEnabled', 'threadId'] : ['locale', 'speakEnabled'];
45462
45558
  const store = Object.fromEntries(Object.entries(state).filter(_ref => {
45463
45559
  let [key] = _ref;
45464
- return ['locale', 'speakEnabled'].includes(key);
45560
+ return enabledKeys.includes(key);
45465
45561
  }));
45466
45562
  store.storedHistory = historyToStoredHistory(state.history);
45467
45563
  // console.log('history', store.storedHistory);
@@ -45496,6 +45592,7 @@ function messagesToHistory(messages) {
45496
45592
  }
45497
45593
  return p;
45498
45594
  }, []);
45595
+ // console.log('useLlm.messagesToHistory', history);
45499
45596
  return history.filter(x => x.threadId && x.messageId);
45500
45597
  }
45501
45598
  function historyToStoredHistory(history) {
@@ -45967,6 +46064,12 @@ var defaultLabels = [{
45967
46064
  en: "I'm thinking of an answer...",
45968
46065
  it: "Sto pensando a una riposta..."
45969
46066
  }
46067
+ }, {
46068
+ id: "llm.newThread",
46069
+ text: {
46070
+ en: "Start a new chat",
46071
+ it: "Nuova chat"
46072
+ }
45970
46073
  }];
45971
46074
 
45972
46075
  const App = _ref => {