llmasaservice-ui 0.3.2 → 0.3.4

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 CHANGED
@@ -35,6 +35,13 @@ interface ChatPanelProps {
35
35
  };
36
36
  }) => void;
37
37
  promptTemplate?: string;
38
+ actions?: {
39
+ pattern: string;
40
+ type?: string;
41
+ markdown?: string;
42
+ callback?: (match: string) => void;
43
+ clickCode?: string;
44
+ }[];
38
45
  }
39
46
  interface ExtraProps extends React.HTMLAttributes<HTMLElement> {
40
47
  inline?: boolean;
package/dist/index.d.ts CHANGED
@@ -35,6 +35,13 @@ interface ChatPanelProps {
35
35
  };
36
36
  }) => void;
37
37
  promptTemplate?: string;
38
+ actions?: {
39
+ pattern: string;
40
+ type?: string;
41
+ markdown?: string;
42
+ callback?: (match: string) => void;
43
+ clickCode?: string;
44
+ }[];
38
45
  }
39
46
  interface ExtraProps extends React.HTMLAttributes<HTMLElement> {
40
47
  inline?: boolean;
package/dist/index.js CHANGED
@@ -67,6 +67,7 @@ module.exports = __toCommonJS(llmasaservice_ui_exports);
67
67
  var import_llmasaservice_client = require("llmasaservice-client");
68
68
  var import_react = __toESM(require("react"));
69
69
  var import_react_markdown = __toESM(require("react-markdown"));
70
+ var import_rehype_raw = __toESM(require("rehype-raw"));
70
71
  var import_remark_gfm = __toESM(require("remark-gfm"));
71
72
  var import_react_syntax_highlighter = require("react-syntax-highlighter");
72
73
  var import_material_dark = __toESM(require("react-syntax-highlighter/dist/cjs/styles/prism/material-dark.js"));
@@ -92,7 +93,8 @@ var ChatPanel = ({
92
93
  prismStyle = theme === "light" ? import_material_light.default : import_material_dark.default,
93
94
  service = null,
94
95
  historyChangedCallback = null,
95
- promptTemplate = ""
96
+ promptTemplate = "",
97
+ actions = []
96
98
  }) => {
97
99
  const { send, response, idle, stop, lastCallId } = (0, import_llmasaservice_client.useLLM)({
98
100
  project_id,
@@ -111,9 +113,54 @@ var ChatPanel = ({
111
113
  (0, import_react.useEffect)(() => {
112
114
  if (response && response.length > 0) {
113
115
  setIsLoading(false);
116
+ let newResponse = response;
117
+ if (actions && actions.length > 0) {
118
+ actions.forEach((action, index) => {
119
+ const regex = new RegExp(action.pattern, "g");
120
+ newResponse = newResponse.replace(regex, (match, ...groups) => {
121
+ var _a;
122
+ const matchIndex = groups[groups.length - 2];
123
+ const buttonId = `button-${index}-${matchIndex}`;
124
+ let html = match;
125
+ if (action.type === "button" || action.type === "callback") {
126
+ const button = document.getElementById(buttonId);
127
+ if (!button) {
128
+ html = `<button id="${buttonId}">${match}</button>`;
129
+ }
130
+ } else if (action.type === "markdown") {
131
+ html = (_a = action.markdown) != null ? _a : "";
132
+ }
133
+ html = html.replace("$match", match);
134
+ groups.forEach((group, index2) => {
135
+ html = html.replace(`$${index2 + 1}`, group);
136
+ });
137
+ setTimeout(() => {
138
+ const button = document.getElementById(buttonId);
139
+ if (button) {
140
+ if (!button.onclick) {
141
+ button.onclick = () => {
142
+ if (action.callback) {
143
+ action.callback(match);
144
+ }
145
+ if (action.clickCode) {
146
+ try {
147
+ const func = new Function("match", action.clickCode);
148
+ func(match);
149
+ } catch (error) {
150
+ console.error("Error executing clickCode:", error);
151
+ }
152
+ }
153
+ };
154
+ }
155
+ }
156
+ }, 0);
157
+ return html;
158
+ });
159
+ });
160
+ }
114
161
  setHistory((prevHistory) => {
115
162
  return __spreadProps(__spreadValues({}, prevHistory), {
116
- [lastPrompt != null ? lastPrompt : ""]: { content: response, callId: lastCallId }
163
+ [lastPrompt != null ? lastPrompt : ""]: { content: newResponse, callId: lastCallId }
117
164
  });
118
165
  });
119
166
  }
@@ -198,13 +245,15 @@ var ChatPanel = ({
198
245
  });
199
246
  });
200
247
  let promptToSend = nextPrompt;
201
- if (Object.keys(history).length === 0 && promptTemplate && promptTemplate !== "") {
202
- promptToSend = promptTemplate.replace("{{prompt}}", nextPrompt);
203
- for (let i = 0; i < data.length; i++) {
204
- promptToSend = promptToSend.replace(
205
- "{{" + ((_a = data[i]) == null ? void 0 : _a.key) + "}}",
206
- (_c = (_b = data[i]) == null ? void 0 : _b.data) != null ? _c : ""
207
- );
248
+ if (initialPrompt && initialPrompt !== "" && Object.keys(history).length === 1 || (!initialPrompt || initialPrompt === "") && Object.keys(history).length === 0) {
249
+ if (promptTemplate && promptTemplate !== "") {
250
+ promptToSend = promptTemplate.replace("{{prompt}}", nextPrompt);
251
+ for (let i = 0; i < data.length; i++) {
252
+ promptToSend = promptToSend.replace(
253
+ "{{" + ((_a = data[i]) == null ? void 0 : _a.key) + "}}",
254
+ (_c = (_b = data[i]) == null ? void 0 : _b.data) != null ? _c : ""
255
+ );
256
+ }
208
257
  }
209
258
  }
210
259
  const controller = new AbortController();
@@ -299,7 +348,8 @@ var ChatPanel = ({
299
348
  import_react_markdown.default,
300
349
  {
301
350
  className: markdownClass,
302
- remarkPlugins: [import_remark_gfm.default]
351
+ remarkPlugins: [import_remark_gfm.default],
352
+ rehypePlugins: [import_rehype_raw.default]
303
353
  },
304
354
  initialMessage
305
355
  ))) : null, Object.entries(history).map(([prompt, response2], index) => /* @__PURE__ */ import_react.default.createElement("div", { className: "history-entry", key: index }, hideInitialPrompt && index === 0 ? null : /* @__PURE__ */ import_react.default.createElement("div", { className: "prompt" }, prompt), /* @__PURE__ */ import_react.default.createElement("div", { className: "response" }, index === Object.keys(history).length - 1 && isLoading ? /* @__PURE__ */ import_react.default.createElement("div", { className: "loading-text" }, "loading...") : null, /* @__PURE__ */ import_react.default.createElement(
@@ -307,6 +357,7 @@ var ChatPanel = ({
307
357
  {
308
358
  className: markdownClass,
309
359
  remarkPlugins: [import_remark_gfm.default],
360
+ rehypePlugins: [import_rehype_raw.default],
310
361
  components: { code: CodeBlock }
311
362
  },
312
363
  response2.content
package/dist/index.mjs CHANGED
@@ -34,6 +34,7 @@ var __objRest = (source, exclude) => {
34
34
  import { useLLM } from "llmasaservice-client";
35
35
  import React, { useEffect, useRef, useState } from "react";
36
36
  import ReactMarkdown from "react-markdown";
37
+ import rehypeRaw from "rehype-raw";
37
38
  import remarkGfm from "remark-gfm";
38
39
  import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
39
40
  import materialDark from "react-syntax-highlighter/dist/cjs/styles/prism/material-dark.js";
@@ -59,7 +60,8 @@ var ChatPanel = ({
59
60
  prismStyle = theme === "light" ? materialLight : materialDark,
60
61
  service = null,
61
62
  historyChangedCallback = null,
62
- promptTemplate = ""
63
+ promptTemplate = "",
64
+ actions = []
63
65
  }) => {
64
66
  const { send, response, idle, stop, lastCallId } = useLLM({
65
67
  project_id,
@@ -78,9 +80,54 @@ var ChatPanel = ({
78
80
  useEffect(() => {
79
81
  if (response && response.length > 0) {
80
82
  setIsLoading(false);
83
+ let newResponse = response;
84
+ if (actions && actions.length > 0) {
85
+ actions.forEach((action, index) => {
86
+ const regex = new RegExp(action.pattern, "g");
87
+ newResponse = newResponse.replace(regex, (match, ...groups) => {
88
+ var _a;
89
+ const matchIndex = groups[groups.length - 2];
90
+ const buttonId = `button-${index}-${matchIndex}`;
91
+ let html = match;
92
+ if (action.type === "button" || action.type === "callback") {
93
+ const button = document.getElementById(buttonId);
94
+ if (!button) {
95
+ html = `<button id="${buttonId}">${match}</button>`;
96
+ }
97
+ } else if (action.type === "markdown") {
98
+ html = (_a = action.markdown) != null ? _a : "";
99
+ }
100
+ html = html.replace("$match", match);
101
+ groups.forEach((group, index2) => {
102
+ html = html.replace(`$${index2 + 1}`, group);
103
+ });
104
+ setTimeout(() => {
105
+ const button = document.getElementById(buttonId);
106
+ if (button) {
107
+ if (!button.onclick) {
108
+ button.onclick = () => {
109
+ if (action.callback) {
110
+ action.callback(match);
111
+ }
112
+ if (action.clickCode) {
113
+ try {
114
+ const func = new Function("match", action.clickCode);
115
+ func(match);
116
+ } catch (error) {
117
+ console.error("Error executing clickCode:", error);
118
+ }
119
+ }
120
+ };
121
+ }
122
+ }
123
+ }, 0);
124
+ return html;
125
+ });
126
+ });
127
+ }
81
128
  setHistory((prevHistory) => {
82
129
  return __spreadProps(__spreadValues({}, prevHistory), {
83
- [lastPrompt != null ? lastPrompt : ""]: { content: response, callId: lastCallId }
130
+ [lastPrompt != null ? lastPrompt : ""]: { content: newResponse, callId: lastCallId }
84
131
  });
85
132
  });
86
133
  }
@@ -165,13 +212,15 @@ var ChatPanel = ({
165
212
  });
166
213
  });
167
214
  let promptToSend = nextPrompt;
168
- if (Object.keys(history).length === 0 && promptTemplate && promptTemplate !== "") {
169
- promptToSend = promptTemplate.replace("{{prompt}}", nextPrompt);
170
- for (let i = 0; i < data.length; i++) {
171
- promptToSend = promptToSend.replace(
172
- "{{" + ((_a = data[i]) == null ? void 0 : _a.key) + "}}",
173
- (_c = (_b = data[i]) == null ? void 0 : _b.data) != null ? _c : ""
174
- );
215
+ if (initialPrompt && initialPrompt !== "" && Object.keys(history).length === 1 || (!initialPrompt || initialPrompt === "") && Object.keys(history).length === 0) {
216
+ if (promptTemplate && promptTemplate !== "") {
217
+ promptToSend = promptTemplate.replace("{{prompt}}", nextPrompt);
218
+ for (let i = 0; i < data.length; i++) {
219
+ promptToSend = promptToSend.replace(
220
+ "{{" + ((_a = data[i]) == null ? void 0 : _a.key) + "}}",
221
+ (_c = (_b = data[i]) == null ? void 0 : _b.data) != null ? _c : ""
222
+ );
223
+ }
175
224
  }
176
225
  }
177
226
  const controller = new AbortController();
@@ -266,7 +315,8 @@ var ChatPanel = ({
266
315
  ReactMarkdown,
267
316
  {
268
317
  className: markdownClass,
269
- remarkPlugins: [remarkGfm]
318
+ remarkPlugins: [remarkGfm],
319
+ rehypePlugins: [rehypeRaw]
270
320
  },
271
321
  initialMessage
272
322
  ))) : null, Object.entries(history).map(([prompt, response2], index) => /* @__PURE__ */ React.createElement("div", { className: "history-entry", key: index }, hideInitialPrompt && index === 0 ? null : /* @__PURE__ */ React.createElement("div", { className: "prompt" }, prompt), /* @__PURE__ */ React.createElement("div", { className: "response" }, index === Object.keys(history).length - 1 && isLoading ? /* @__PURE__ */ React.createElement("div", { className: "loading-text" }, "loading...") : null, /* @__PURE__ */ React.createElement(
@@ -274,6 +324,7 @@ var ChatPanel = ({
274
324
  {
275
325
  className: markdownClass,
276
326
  remarkPlugins: [remarkGfm],
327
+ rehypePlugins: [rehypeRaw],
277
328
  components: { code: CodeBlock }
278
329
  },
279
330
  response2.content
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "llmasaservice-ui",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Prebuilt UI components for LLMAsAService.io",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -19,20 +19,20 @@
19
19
  "author": "Predictability at Scale",
20
20
  "license": "MIT",
21
21
  "devDependencies": {
22
- "@chromatic-com/storybook": "^1.6.1",
23
- "@storybook/addon-essentials": "^8.2.7",
24
- "@storybook/addon-interactions": "^8.2.7",
25
- "@storybook/addon-links": "^8.2.7",
26
- "@storybook/addon-onboarding": "^8.2.7",
22
+ "@chromatic-com/storybook": "^2.0.2",
23
+ "@storybook/addon-essentials": "^8.3.6",
24
+ "@storybook/addon-interactions": "^8.3.6",
25
+ "@storybook/addon-links": "^8.3.6",
26
+ "@storybook/addon-onboarding": "^8.3.6",
27
27
  "@storybook/addon-webpack5-compiler-swc": "^1.0.5",
28
- "@storybook/blocks": "^8.2.7",
29
- "@storybook/react": "^8.2.7",
30
- "@storybook/react-webpack5": "^8.2.7",
31
- "@storybook/test": "^8.2.7",
28
+ "@storybook/blocks": "^8.3.6",
29
+ "@storybook/react": "^8.3.6",
30
+ "@storybook/react-webpack5": "^8.3.6",
31
+ "@storybook/test": "^8.3.6",
32
32
  "@types/react": "^18.3.3",
33
33
  "@types/react-syntax-highlighter": "^15.5.13",
34
34
  "react": "^18.3.1",
35
- "storybook": "^8.2.7",
35
+ "storybook": "^8.3.6",
36
36
  "tsup": "^8.2.4",
37
37
  "typescript": "^5.5.4"
38
38
  },
@@ -47,7 +47,9 @@
47
47
  "llmasaservice-client": "^0.5.0",
48
48
  "react-markdown": "^9.0.1",
49
49
  "react-syntax-highlighter": "^15.5.0",
50
- "remark-gfm": "^4.0.0"
50
+ "rehype-raw": "^7.0.0",
51
+ "remark-gfm": "^4.0.0",
52
+ "remark-html": "^16.0.1"
51
53
  },
52
54
  "peerDependencies": {
53
55
  "react": "^18.3.0",
package/src/ChatPanel.tsx CHANGED
@@ -1,6 +1,8 @@
1
1
  import { LLMAsAServiceCustomer, useLLM } from "llmasaservice-client";
2
2
  import React, { useEffect, useRef, useState } from "react";
3
3
  import ReactMarkdown from "react-markdown";
4
+ import remarkHtml from "remark-html";
5
+ import rehypeRaw from "rehype-raw";
4
6
  import "./ChatPanel.css";
5
7
  import remarkGfm from "remark-gfm";
6
8
  import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
@@ -32,6 +34,13 @@ export interface ChatPanelProps {
32
34
  [key: string]: { content: string; callId: string };
33
35
  }) => void;
34
36
  promptTemplate?: string;
37
+ actions?: {
38
+ pattern: string;
39
+ type?: string;
40
+ markdown?: string;
41
+ callback?: (match: string) => void;
42
+ clickCode?: string;
43
+ }[];
35
44
  }
36
45
 
37
46
  interface ExtraProps extends React.HTMLAttributes<HTMLElement> {
@@ -60,6 +69,7 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
60
69
  service = null,
61
70
  historyChangedCallback = null,
62
71
  promptTemplate = "",
72
+ actions = [],
63
73
  }) => {
64
74
  const { send, response, idle, stop, lastCallId } = useLLM({
65
75
  project_id: project_id,
@@ -85,10 +95,85 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
85
95
  if (response && response.length > 0) {
86
96
  setIsLoading(false);
87
97
 
98
+ let newResponse = response;
99
+
100
+ /*
101
+ [
102
+ {
103
+ "pattern": "any",
104
+ "htmlAction": "<a href=\"$1\">link</a>",
105
+ "type": "html"
106
+ }
107
+ ]
108
+
109
+ [
110
+ {
111
+ "pattern": "any",
112
+ "markdown": "[$match]($1)",
113
+ "type": "markdown"
114
+ },
115
+ {
116
+ "pattern": "the",
117
+ "type": "button",
118
+ "clickCode": "alert(match);"
119
+ }
120
+ ]
121
+
122
+ */
123
+
124
+ // replace actions with links
125
+ if (actions && actions.length > 0) {
126
+ actions.forEach((action, index) => {
127
+ const regex = new RegExp(action.pattern, "g");
128
+ newResponse = newResponse.replace(regex, (match, ...groups) => {
129
+ const matchIndex = groups[groups.length - 2]; // The second-to-last argument is the match index
130
+
131
+ const buttonId = `button-${index}-${matchIndex}`;
132
+
133
+ let html = match;
134
+ if (action.type === "button" || action.type === "callback") {
135
+ const button = document.getElementById(buttonId);
136
+ if (!button) {
137
+ html = `<button id="${buttonId}">${match}</button>`;
138
+ }
139
+ } else if (action.type === "markdown") {
140
+ html = action.markdown ?? "";
141
+ }
142
+
143
+ html = html.replace("$match", match);
144
+ groups.forEach((group, index) => {
145
+ html = html.replace(`$${index + 1}`, group);
146
+ });
147
+
148
+ setTimeout(() => {
149
+ const button = document.getElementById(buttonId);
150
+ if (button) {
151
+ if (!button.onclick) {
152
+ button.onclick = () => {
153
+ if (action.callback) {
154
+ action.callback(match);
155
+ }
156
+ if (action.clickCode) {
157
+ try {
158
+ const func = new Function("match", action.clickCode);
159
+ func(match);
160
+ } catch (error) {
161
+ console.error("Error executing clickCode:", error);
162
+ }
163
+ }
164
+ };
165
+ }
166
+ }
167
+ }, 0);
168
+ return html;
169
+ });
170
+ });
171
+ }
172
+
88
173
  setHistory((prevHistory) => {
89
174
  return {
90
175
  ...prevHistory,
91
- [lastPrompt ?? ""]: { content: response, callId: lastCallId },
176
+ [lastPrompt ?? ""]: { content: newResponse, callId: lastCallId },
92
177
  };
93
178
  });
94
179
  }
@@ -193,17 +278,22 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
193
278
  let promptToSend = nextPrompt;
194
279
 
195
280
  // if this is the first user message, use the template. otherwise it is a follow-on question(s)
281
+
196
282
  if (
197
- Object.keys(history).length === 0 &&
198
- promptTemplate &&
199
- promptTemplate !== ""
283
+ (initialPrompt &&
284
+ initialPrompt !== "" &&
285
+ Object.keys(history).length === 1) ||
286
+ ((!initialPrompt || initialPrompt === "") &&
287
+ Object.keys(history).length === 0)
200
288
  ) {
201
- promptToSend = promptTemplate.replace("{{prompt}}", nextPrompt);
202
- for (let i = 0; i < data.length; i++) {
203
- promptToSend = promptToSend.replace(
204
- "{{" + data[i]?.key + "}}",
205
- data[i]?.data ?? ""
206
- );
289
+ if (promptTemplate && promptTemplate !== "") {
290
+ promptToSend = promptTemplate.replace("{{prompt}}", nextPrompt);
291
+ for (let i = 0; i < data.length; i++) {
292
+ promptToSend = promptToSend.replace(
293
+ "{{" + data[i]?.key + "}}",
294
+ data[i]?.data ?? ""
295
+ );
296
+ }
207
297
  }
208
298
  }
209
299
 
@@ -308,6 +398,7 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
308
398
  <ReactMarkdown
309
399
  className={markdownClass}
310
400
  remarkPlugins={[remarkGfm]}
401
+ rehypePlugins={[rehypeRaw]}
311
402
  >
312
403
  {initialMessage}
313
404
  </ReactMarkdown>
@@ -327,6 +418,7 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
327
418
  <ReactMarkdown
328
419
  className={markdownClass}
329
420
  remarkPlugins={[remarkGfm]}
421
+ rehypePlugins={[rehypeRaw]}
330
422
  components={{ code: CodeBlock }}
331
423
  >
332
424
  {response.content}