llmasaservice-ui 0.12.2 → 0.12.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.css CHANGED
@@ -395,6 +395,7 @@
395
395
  display: flex;
396
396
  align-items: center;
397
397
  justify-content: center;
398
+ margin-top: 4px;
398
399
  font-size: 10px;
399
400
  color: #666;
400
401
  opacity: 0.7;
@@ -720,3 +721,60 @@
720
721
  .dark-theme .tool-status {
721
722
  color: #999;
722
723
  }
724
+ .reasoning-section,
725
+ .searching-section {
726
+ margin: 1rem 0;
727
+ padding: 0.75rem;
728
+ border-radius: var(--border-radius);
729
+ border-left: 4px solid;
730
+ background-color: var(--reasoning-background-color, #f8f9fa);
731
+ }
732
+ .reasoning-section {
733
+ border-left-color: var(--reasoning-border-color, #007bff);
734
+ background-color: var(--reasoning-background-color, #f0f8ff);
735
+ }
736
+ .searching-section {
737
+ border-left-color: var(--searching-border-color, #28a745);
738
+ background-color: var(--searching-background-color, #f0fff0);
739
+ }
740
+ .reasoning-header,
741
+ .searching-header {
742
+ font-weight: 600;
743
+ font-size: 0.9rem;
744
+ margin-bottom: 0.5rem;
745
+ color: var(--prompt-text-color);
746
+ opacity: 0.8;
747
+ }
748
+ .reasoning-content,
749
+ .searching-content {
750
+ font-size: 0.85rem;
751
+ line-height: 1.4;
752
+ color: var(--prompt-text-color);
753
+ opacity: 0.9;
754
+ font-style: italic;
755
+ }
756
+ .dark-theme .reasoning-section {
757
+ background-color: var(--reasoning-background-color-dark, #1a1a2e);
758
+ border-left-color: var(--reasoning-border-color-dark, #4a9eff);
759
+ }
760
+ .dark-theme .searching-section {
761
+ background-color: var(--searching-background-color-dark, #1a2e1a);
762
+ border-left-color: var(--searching-border-color-dark, #4ade80);
763
+ }
764
+ .dark-theme .reasoning-header,
765
+ .dark-theme .searching-header,
766
+ .dark-theme .reasoning-content,
767
+ .dark-theme .searching-content {
768
+ color: var(--response-text-color);
769
+ }
770
+ .streaming-response {
771
+ animation: fadeIn 0.3s ease-in-out;
772
+ }
773
+ @keyframes fadeIn {
774
+ from {
775
+ opacity: 0;
776
+ }
777
+ to {
778
+ opacity: 1;
779
+ }
780
+ }
package/dist/index.js CHANGED
@@ -272,30 +272,35 @@ var ChatPanel = ({
272
272
  const [iframeUrl, setIframeUrl] = (0, import_react3.useState)(null);
273
273
  const responseAreaRef = (0, import_react3.useRef)(null);
274
274
  const extractLastThinkingTag = (text) => {
275
- var _a2, _b;
275
+ var _a2, _b, _c, _d;
276
+ console.log("extractLastThinkingTag called with:", (text == null ? void 0 : text.length) ? `${text.substring(0, 100)}...` : "empty");
276
277
  if (!text) return "Thinking";
277
- const reasoningRegex = /<reasoning>(.*?)<\/reasoning>/gi;
278
- const searchingRegex = /<searching>(.*?)<\/searching>/gi;
278
+ const reasoningRegex = /<reasoning>([\s\S]*?)<\/reasoning>/gi;
279
+ const searchingRegex = /<searching>([\s\S]*?)<\/searching>/gi;
279
280
  const allMatches = [];
280
281
  let reasoningMatch;
281
282
  while ((reasoningMatch = reasoningRegex.exec(text)) !== null) {
283
+ console.log("Found reasoning match:", ((_a2 = reasoningMatch[1]) == null ? void 0 : _a2.substring(0, 50)) + "...");
282
284
  allMatches.push({
283
- content: ((_a2 = reasoningMatch[1]) == null ? void 0 : _a2.trim()) || "",
285
+ content: ((_b = reasoningMatch[1]) == null ? void 0 : _b.trim()) || "",
284
286
  index: reasoningMatch.index,
285
287
  type: "reasoning"
286
288
  });
287
289
  }
288
290
  let searchingMatch;
289
291
  while ((searchingMatch = searchingRegex.exec(text)) !== null) {
292
+ console.log("Found searching match:", ((_c = searchingMatch[1]) == null ? void 0 : _c.substring(0, 50)) + "...");
290
293
  allMatches.push({
291
- content: ((_b = searchingMatch[1]) == null ? void 0 : _b.trim()) || "",
294
+ content: ((_d = searchingMatch[1]) == null ? void 0 : _d.trim()) || "",
292
295
  index: searchingMatch.index,
293
296
  type: "searching"
294
297
  });
295
298
  }
299
+ console.log("Total matches found:", allMatches.length);
296
300
  if (allMatches.length > 0) {
297
301
  const lastMatch = allMatches.sort((a, b) => b.index - a.index)[0];
298
302
  let content = (lastMatch == null ? void 0 : lastMatch.content) || "Thinking";
303
+ console.log("Last match content:", content == null ? void 0 : content.substring(0, 100));
299
304
  content = content.replace(/\*\*(.*?)\*\*/g, "$1");
300
305
  content = content.replace(/\*(.*?)\*/g, "$1");
301
306
  content = content.replace(/\n+/g, " ");
@@ -304,16 +309,46 @@ var ChatPanel = ({
304
309
  if (content.length > 80) {
305
310
  content = content.substring(0, 77) + "...";
306
311
  }
312
+ console.log("Final extracted content:", content);
307
313
  return content || "Thinking";
308
314
  }
315
+ console.log("No matches found, returning 'Thinking'");
309
316
  return "Thinking";
310
317
  };
311
- const cleanResponseContent = (text) => {
318
+ const processResponseContent = (text, isStreaming = false) => {
312
319
  if (!text) return "";
313
- let cleanText = text.replace(/<reasoning>[\s\S]*?<\/reasoning>/gi, "");
314
- cleanText = cleanText.replace(/<searching>[\s\S]*?<\/searching>/gi, "");
315
- cleanText = cleanText.replace(/\n\s*\n/g, "\n").trim();
316
- return cleanText;
320
+ let processedText = text;
321
+ processedText = processedText.replace(
322
+ /<reasoning>([\s\S]*?)<\/reasoning>/gi,
323
+ (match, content) => {
324
+ const trimmedContent = content.trim();
325
+ if (!trimmedContent) return "";
326
+ return `
327
+
328
+ <div class="reasoning-section">
329
+ <div class="reasoning-header">\u{1F914} Reasoning</div>
330
+ <div class="reasoning-content">${trimmedContent}</div>
331
+ </div>
332
+
333
+ `;
334
+ }
335
+ );
336
+ processedText = processedText.replace(
337
+ /<searching>([\s\S]*?)<\/searching>/gi,
338
+ (match, content) => {
339
+ const trimmedContent = content.trim();
340
+ if (!trimmedContent) return "";
341
+ return `
342
+
343
+ <div class="searching-section">
344
+ <div class="searching-header">\u{1F50D} Searching</div>
345
+ <div class="searching-content">${trimmedContent}</div>
346
+ </div>
347
+
348
+ `;
349
+ }
350
+ );
351
+ return processedText;
317
352
  };
318
353
  const getBrowserInfo = () => {
319
354
  try {
@@ -465,6 +500,16 @@ var ChatPanel = ({
465
500
  };
466
501
  })
467
502
  });
503
+ (0, import_react3.useEffect)(() => {
504
+ console.log("Streaming state changed:", {
505
+ isLoading,
506
+ idle,
507
+ responseLength: (response == null ? void 0 : response.length) || 0,
508
+ lastCallId,
509
+ hasResponse: !!response,
510
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
511
+ });
512
+ }, [isLoading, idle, response, lastCallId]);
468
513
  (0, import_react3.useEffect)(() => {
469
514
  setShowEmailPanel(customerEmailCaptureMode !== "HIDE");
470
515
  if (customerEmailCaptureMode === "REQUIRED") {
@@ -887,6 +932,14 @@ var ChatPanel = ({
887
932
  });
888
933
  (0, import_react3.useEffect)(() => {
889
934
  if (response && response.length > 0) {
935
+ console.log("Response updated:", {
936
+ length: response.length,
937
+ isLoading,
938
+ idle,
939
+ hasReasoningTags: response.includes("<reasoning>"),
940
+ hasSearchingTags: response.includes("<searching>"),
941
+ preview: response.substring(0, 200) + "..."
942
+ });
890
943
  setIsLoading(false);
891
944
  let newResponse = response;
892
945
  const toolRequests = [];
@@ -958,6 +1011,12 @@ var ChatPanel = ({
958
1011
  } else {
959
1012
  setPendingToolRequests([]);
960
1013
  }
1014
+ console.log("Storing raw response to history:", {
1015
+ originalLength: newResponse.length,
1016
+ hasReasoningTags: newResponse.includes("<reasoning>"),
1017
+ hasSearchingTags: newResponse.includes("<searching>"),
1018
+ preview: newResponse.substring(0, 200) + "..."
1019
+ });
961
1020
  setHistory((prevHistory) => {
962
1021
  const existingEntry = prevHistory[lastKey != null ? lastKey : ""] || {
963
1022
  content: "",
@@ -966,7 +1025,8 @@ var ChatPanel = ({
966
1025
  return __spreadProps(__spreadValues({}, prevHistory), {
967
1026
  [lastKey != null ? lastKey : ""]: __spreadProps(__spreadValues({}, existingEntry), {
968
1027
  // This preserves toolCalls and toolResponses
969
- content: cleanResponseContent(newResponse),
1028
+ content: newResponse,
1029
+ // Store raw response without processing
970
1030
  callId: lastCallId
971
1031
  })
972
1032
  });
@@ -1184,7 +1244,7 @@ var ChatPanel = ({
1184
1244
  setHistory((prevHistory) => {
1185
1245
  return __spreadProps(__spreadValues({}, prevHistory), {
1186
1246
  [lastKey != null ? lastKey : ""]: {
1187
- content: cleanResponseContent(response) + "\n\n(response cancelled)",
1247
+ content: processResponseContent(response) + "\n\n(response cancelled)",
1188
1248
  callId: lastCallId
1189
1249
  }
1190
1250
  });
@@ -1212,7 +1272,7 @@ var ChatPanel = ({
1212
1272
  messagesAndHistory.push({ role: "user", content: promptToSend });
1213
1273
  messagesAndHistory.push({
1214
1274
  role: "assistant",
1215
- content: cleanResponseContent(response2.content)
1275
+ content: processResponseContent(response2.content)
1216
1276
  });
1217
1277
  });
1218
1278
  let nextPromptToSend = suggestion != null ? suggestion : nextPrompt;
@@ -1579,7 +1639,19 @@ var ChatPanel = ({
1579
1639
  var _a2, _b;
1580
1640
  const isLastEntry = index === Object.keys(history).length - 1;
1581
1641
  const hasToolData = !!((((_a2 = historyEntry == null ? void 0 : historyEntry.toolCalls) == null ? void 0 : _a2.length) || 0) > 0 || (((_b = historyEntry == null ? void 0 : historyEntry.toolResponses) == null ? void 0 : _b.length) || 0) > 0);
1582
- return /* @__PURE__ */ import_react3.default.createElement("div", { className: "history-entry", key: index }, hideInitialPrompt && index === 0 ? null : /* @__PURE__ */ import_react3.default.createElement("div", { className: "prompt" }, formatPromptForDisplay(prompt)), /* @__PURE__ */ import_react3.default.createElement("div", { className: "response" }, index === Object.keys(history).length - 1 && isLoading && (!response || response.length < 50) ? /* @__PURE__ */ import_react3.default.createElement("div", { className: "loading-text" }, extractLastThinkingTag(response || ""), "\xA0", /* @__PURE__ */ import_react3.default.createElement("div", { className: "dot" }), /* @__PURE__ */ import_react3.default.createElement("div", { className: "dot" }), /* @__PURE__ */ import_react3.default.createElement("div", { className: "dot" })) : null, /* @__PURE__ */ import_react3.default.createElement(
1642
+ return /* @__PURE__ */ import_react3.default.createElement("div", { className: "history-entry", key: index }, hideInitialPrompt && index === 0 ? null : /* @__PURE__ */ import_react3.default.createElement("div", { className: "prompt" }, formatPromptForDisplay(prompt)), /* @__PURE__ */ import_react3.default.createElement("div", { className: "response" }, index === Object.keys(history).length - 1 && isLoading ? /* @__PURE__ */ import_react3.default.createElement("div", { className: "streaming-response" }, response && response.length > 0 ? /* @__PURE__ */ import_react3.default.createElement(
1643
+ import_react_markdown.default,
1644
+ {
1645
+ className: markdownClass,
1646
+ remarkPlugins: [import_remark_gfm.default],
1647
+ rehypePlugins: [import_rehype_raw.default],
1648
+ components: {
1649
+ /*a: CustomLink,*/
1650
+ code: CodeBlock
1651
+ }
1652
+ },
1653
+ processResponseContent(response, true)
1654
+ ) : /* @__PURE__ */ import_react3.default.createElement("div", { className: "loading-text" }, extractLastThinkingTag(response || ""), "\xA0", /* @__PURE__ */ import_react3.default.createElement("div", { className: "dot" }), /* @__PURE__ */ import_react3.default.createElement("div", { className: "dot" }), /* @__PURE__ */ import_react3.default.createElement("div", { className: "dot" }))) : /* @__PURE__ */ import_react3.default.createElement(
1583
1655
  import_react_markdown.default,
1584
1656
  {
1585
1657
  className: markdownClass,
@@ -1590,7 +1662,7 @@ var ChatPanel = ({
1590
1662
  code: CodeBlock
1591
1663
  }
1592
1664
  },
1593
- historyEntry.content
1665
+ processResponseContent(historyEntry.content)
1594
1666
  ), isLastEntry && pendingToolRequests.length > 0 && /* @__PURE__ */ import_react3.default.createElement("div", { className: "approve-tools-panel" }, getUniqueToolNames(pendingToolRequests).map(
1595
1667
  (toolName) => {
1596
1668
  const tool = toolList.find(
@@ -1627,7 +1699,7 @@ var ChatPanel = ({
1627
1699
  {
1628
1700
  className: "copy-button",
1629
1701
  onClick: () => {
1630
- copyToClipboard(historyEntry.content);
1702
+ copyToClipboard(processResponseContent(historyEntry.content));
1631
1703
  },
1632
1704
  disabled: isDisabledDueToNoEmail()
1633
1705
  },
package/dist/index.mjs CHANGED
@@ -244,30 +244,35 @@ var ChatPanel = ({
244
244
  const [iframeUrl, setIframeUrl] = useState2(null);
245
245
  const responseAreaRef = useRef(null);
246
246
  const extractLastThinkingTag = (text) => {
247
- var _a2, _b;
247
+ var _a2, _b, _c, _d;
248
+ console.log("extractLastThinkingTag called with:", (text == null ? void 0 : text.length) ? `${text.substring(0, 100)}...` : "empty");
248
249
  if (!text) return "Thinking";
249
- const reasoningRegex = /<reasoning>(.*?)<\/reasoning>/gi;
250
- const searchingRegex = /<searching>(.*?)<\/searching>/gi;
250
+ const reasoningRegex = /<reasoning>([\s\S]*?)<\/reasoning>/gi;
251
+ const searchingRegex = /<searching>([\s\S]*?)<\/searching>/gi;
251
252
  const allMatches = [];
252
253
  let reasoningMatch;
253
254
  while ((reasoningMatch = reasoningRegex.exec(text)) !== null) {
255
+ console.log("Found reasoning match:", ((_a2 = reasoningMatch[1]) == null ? void 0 : _a2.substring(0, 50)) + "...");
254
256
  allMatches.push({
255
- content: ((_a2 = reasoningMatch[1]) == null ? void 0 : _a2.trim()) || "",
257
+ content: ((_b = reasoningMatch[1]) == null ? void 0 : _b.trim()) || "",
256
258
  index: reasoningMatch.index,
257
259
  type: "reasoning"
258
260
  });
259
261
  }
260
262
  let searchingMatch;
261
263
  while ((searchingMatch = searchingRegex.exec(text)) !== null) {
264
+ console.log("Found searching match:", ((_c = searchingMatch[1]) == null ? void 0 : _c.substring(0, 50)) + "...");
262
265
  allMatches.push({
263
- content: ((_b = searchingMatch[1]) == null ? void 0 : _b.trim()) || "",
266
+ content: ((_d = searchingMatch[1]) == null ? void 0 : _d.trim()) || "",
264
267
  index: searchingMatch.index,
265
268
  type: "searching"
266
269
  });
267
270
  }
271
+ console.log("Total matches found:", allMatches.length);
268
272
  if (allMatches.length > 0) {
269
273
  const lastMatch = allMatches.sort((a, b) => b.index - a.index)[0];
270
274
  let content = (lastMatch == null ? void 0 : lastMatch.content) || "Thinking";
275
+ console.log("Last match content:", content == null ? void 0 : content.substring(0, 100));
271
276
  content = content.replace(/\*\*(.*?)\*\*/g, "$1");
272
277
  content = content.replace(/\*(.*?)\*/g, "$1");
273
278
  content = content.replace(/\n+/g, " ");
@@ -276,16 +281,46 @@ var ChatPanel = ({
276
281
  if (content.length > 80) {
277
282
  content = content.substring(0, 77) + "...";
278
283
  }
284
+ console.log("Final extracted content:", content);
279
285
  return content || "Thinking";
280
286
  }
287
+ console.log("No matches found, returning 'Thinking'");
281
288
  return "Thinking";
282
289
  };
283
- const cleanResponseContent = (text) => {
290
+ const processResponseContent = (text, isStreaming = false) => {
284
291
  if (!text) return "";
285
- let cleanText = text.replace(/<reasoning>[\s\S]*?<\/reasoning>/gi, "");
286
- cleanText = cleanText.replace(/<searching>[\s\S]*?<\/searching>/gi, "");
287
- cleanText = cleanText.replace(/\n\s*\n/g, "\n").trim();
288
- return cleanText;
292
+ let processedText = text;
293
+ processedText = processedText.replace(
294
+ /<reasoning>([\s\S]*?)<\/reasoning>/gi,
295
+ (match, content) => {
296
+ const trimmedContent = content.trim();
297
+ if (!trimmedContent) return "";
298
+ return `
299
+
300
+ <div class="reasoning-section">
301
+ <div class="reasoning-header">\u{1F914} Reasoning</div>
302
+ <div class="reasoning-content">${trimmedContent}</div>
303
+ </div>
304
+
305
+ `;
306
+ }
307
+ );
308
+ processedText = processedText.replace(
309
+ /<searching>([\s\S]*?)<\/searching>/gi,
310
+ (match, content) => {
311
+ const trimmedContent = content.trim();
312
+ if (!trimmedContent) return "";
313
+ return `
314
+
315
+ <div class="searching-section">
316
+ <div class="searching-header">\u{1F50D} Searching</div>
317
+ <div class="searching-content">${trimmedContent}</div>
318
+ </div>
319
+
320
+ `;
321
+ }
322
+ );
323
+ return processedText;
289
324
  };
290
325
  const getBrowserInfo = () => {
291
326
  try {
@@ -437,6 +472,16 @@ var ChatPanel = ({
437
472
  };
438
473
  })
439
474
  });
475
+ useEffect2(() => {
476
+ console.log("Streaming state changed:", {
477
+ isLoading,
478
+ idle,
479
+ responseLength: (response == null ? void 0 : response.length) || 0,
480
+ lastCallId,
481
+ hasResponse: !!response,
482
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
483
+ });
484
+ }, [isLoading, idle, response, lastCallId]);
440
485
  useEffect2(() => {
441
486
  setShowEmailPanel(customerEmailCaptureMode !== "HIDE");
442
487
  if (customerEmailCaptureMode === "REQUIRED") {
@@ -859,6 +904,14 @@ var ChatPanel = ({
859
904
  });
860
905
  useEffect2(() => {
861
906
  if (response && response.length > 0) {
907
+ console.log("Response updated:", {
908
+ length: response.length,
909
+ isLoading,
910
+ idle,
911
+ hasReasoningTags: response.includes("<reasoning>"),
912
+ hasSearchingTags: response.includes("<searching>"),
913
+ preview: response.substring(0, 200) + "..."
914
+ });
862
915
  setIsLoading(false);
863
916
  let newResponse = response;
864
917
  const toolRequests = [];
@@ -930,6 +983,12 @@ var ChatPanel = ({
930
983
  } else {
931
984
  setPendingToolRequests([]);
932
985
  }
986
+ console.log("Storing raw response to history:", {
987
+ originalLength: newResponse.length,
988
+ hasReasoningTags: newResponse.includes("<reasoning>"),
989
+ hasSearchingTags: newResponse.includes("<searching>"),
990
+ preview: newResponse.substring(0, 200) + "..."
991
+ });
933
992
  setHistory((prevHistory) => {
934
993
  const existingEntry = prevHistory[lastKey != null ? lastKey : ""] || {
935
994
  content: "",
@@ -938,7 +997,8 @@ var ChatPanel = ({
938
997
  return __spreadProps(__spreadValues({}, prevHistory), {
939
998
  [lastKey != null ? lastKey : ""]: __spreadProps(__spreadValues({}, existingEntry), {
940
999
  // This preserves toolCalls and toolResponses
941
- content: cleanResponseContent(newResponse),
1000
+ content: newResponse,
1001
+ // Store raw response without processing
942
1002
  callId: lastCallId
943
1003
  })
944
1004
  });
@@ -1156,7 +1216,7 @@ var ChatPanel = ({
1156
1216
  setHistory((prevHistory) => {
1157
1217
  return __spreadProps(__spreadValues({}, prevHistory), {
1158
1218
  [lastKey != null ? lastKey : ""]: {
1159
- content: cleanResponseContent(response) + "\n\n(response cancelled)",
1219
+ content: processResponseContent(response) + "\n\n(response cancelled)",
1160
1220
  callId: lastCallId
1161
1221
  }
1162
1222
  });
@@ -1184,7 +1244,7 @@ var ChatPanel = ({
1184
1244
  messagesAndHistory.push({ role: "user", content: promptToSend });
1185
1245
  messagesAndHistory.push({
1186
1246
  role: "assistant",
1187
- content: cleanResponseContent(response2.content)
1247
+ content: processResponseContent(response2.content)
1188
1248
  });
1189
1249
  });
1190
1250
  let nextPromptToSend = suggestion != null ? suggestion : nextPrompt;
@@ -1551,7 +1611,19 @@ var ChatPanel = ({
1551
1611
  var _a2, _b;
1552
1612
  const isLastEntry = index === Object.keys(history).length - 1;
1553
1613
  const hasToolData = !!((((_a2 = historyEntry == null ? void 0 : historyEntry.toolCalls) == null ? void 0 : _a2.length) || 0) > 0 || (((_b = historyEntry == null ? void 0 : historyEntry.toolResponses) == null ? void 0 : _b.length) || 0) > 0);
1554
- return /* @__PURE__ */ React3.createElement("div", { className: "history-entry", key: index }, hideInitialPrompt && index === 0 ? null : /* @__PURE__ */ React3.createElement("div", { className: "prompt" }, formatPromptForDisplay(prompt)), /* @__PURE__ */ React3.createElement("div", { className: "response" }, index === Object.keys(history).length - 1 && isLoading && (!response || response.length < 50) ? /* @__PURE__ */ React3.createElement("div", { className: "loading-text" }, extractLastThinkingTag(response || ""), "\xA0", /* @__PURE__ */ React3.createElement("div", { className: "dot" }), /* @__PURE__ */ React3.createElement("div", { className: "dot" }), /* @__PURE__ */ React3.createElement("div", { className: "dot" })) : null, /* @__PURE__ */ React3.createElement(
1614
+ return /* @__PURE__ */ React3.createElement("div", { className: "history-entry", key: index }, hideInitialPrompt && index === 0 ? null : /* @__PURE__ */ React3.createElement("div", { className: "prompt" }, formatPromptForDisplay(prompt)), /* @__PURE__ */ React3.createElement("div", { className: "response" }, index === Object.keys(history).length - 1 && isLoading ? /* @__PURE__ */ React3.createElement("div", { className: "streaming-response" }, response && response.length > 0 ? /* @__PURE__ */ React3.createElement(
1615
+ ReactMarkdown,
1616
+ {
1617
+ className: markdownClass,
1618
+ remarkPlugins: [remarkGfm],
1619
+ rehypePlugins: [rehypeRaw],
1620
+ components: {
1621
+ /*a: CustomLink,*/
1622
+ code: CodeBlock
1623
+ }
1624
+ },
1625
+ processResponseContent(response, true)
1626
+ ) : /* @__PURE__ */ React3.createElement("div", { className: "loading-text" }, extractLastThinkingTag(response || ""), "\xA0", /* @__PURE__ */ React3.createElement("div", { className: "dot" }), /* @__PURE__ */ React3.createElement("div", { className: "dot" }), /* @__PURE__ */ React3.createElement("div", { className: "dot" }))) : /* @__PURE__ */ React3.createElement(
1555
1627
  ReactMarkdown,
1556
1628
  {
1557
1629
  className: markdownClass,
@@ -1562,7 +1634,7 @@ var ChatPanel = ({
1562
1634
  code: CodeBlock
1563
1635
  }
1564
1636
  },
1565
- historyEntry.content
1637
+ processResponseContent(historyEntry.content)
1566
1638
  ), isLastEntry && pendingToolRequests.length > 0 && /* @__PURE__ */ React3.createElement("div", { className: "approve-tools-panel" }, getUniqueToolNames(pendingToolRequests).map(
1567
1639
  (toolName) => {
1568
1640
  const tool = toolList.find(
@@ -1599,7 +1671,7 @@ var ChatPanel = ({
1599
1671
  {
1600
1672
  className: "copy-button",
1601
1673
  onClick: () => {
1602
- copyToClipboard(historyEntry.content);
1674
+ copyToClipboard(processResponseContent(historyEntry.content));
1603
1675
  },
1604
1676
  disabled: isDisabledDueToNoEmail()
1605
1677
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "llmasaservice-ui",
3
- "version": "0.12.2",
3
+ "version": "0.12.4",
4
4
  "description": "Prebuilt UI components for LLMAsAService.io",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
package/src/ChatPanel.css CHANGED
@@ -490,6 +490,7 @@
490
490
  display: flex;
491
491
  align-items: center;
492
492
  justify-content: center;
493
+ margin-top: 4px;
493
494
  font-size: 10px;
494
495
  color: #666;
495
496
  opacity: 0.7;
@@ -885,4 +886,72 @@
885
886
  /* Dark theme support */
886
887
  .dark-theme .tool-status {
887
888
  color: #999;
889
+ }
890
+
891
+ /* Reasoning and Searching Sections */
892
+ .reasoning-section, .searching-section {
893
+ margin: 1rem 0;
894
+ padding: 0.75rem;
895
+ border-radius: var(--border-radius);
896
+ border-left: 4px solid;
897
+ background-color: var(--reasoning-background-color, #f8f9fa);
898
+ }
899
+
900
+ .reasoning-section {
901
+ border-left-color: var(--reasoning-border-color, #007bff);
902
+ background-color: var(--reasoning-background-color, #f0f8ff);
903
+ }
904
+
905
+ .searching-section {
906
+ border-left-color: var(--searching-border-color, #28a745);
907
+ background-color: var(--searching-background-color, #f0fff0);
908
+ }
909
+
910
+ .reasoning-header, .searching-header {
911
+ font-weight: 600;
912
+ font-size: 0.9rem;
913
+ margin-bottom: 0.5rem;
914
+ color: var(--prompt-text-color);
915
+ opacity: 0.8;
916
+ }
917
+
918
+ .reasoning-content, .searching-content {
919
+ font-size: 0.85rem;
920
+ line-height: 1.4;
921
+ color: var(--prompt-text-color);
922
+ opacity: 0.9;
923
+ font-style: italic;
924
+ }
925
+
926
+ /* Dark theme support for reasoning/searching */
927
+ .dark-theme .reasoning-section {
928
+ background-color: var(--reasoning-background-color-dark, #1a1a2e);
929
+ border-left-color: var(--reasoning-border-color-dark, #4a9eff);
930
+ }
931
+
932
+ .dark-theme .searching-section {
933
+ background-color: var(--searching-background-color-dark, #1a2e1a);
934
+ border-left-color: var(--searching-border-color-dark, #4ade80);
935
+ }
936
+
937
+ .dark-theme .reasoning-header,
938
+ .dark-theme .searching-header,
939
+ .dark-theme .reasoning-content,
940
+ .dark-theme .searching-content {
941
+ color: var(--response-text-color);
942
+ }
943
+
944
+ /* Streaming response styling */
945
+ .streaming-response {
946
+ /* Add a subtle animation to indicate streaming */
947
+ animation: fadeIn 0.3s ease-in-out;
948
+ }
949
+
950
+ @keyframes fadeIn {
951
+ from {
952
+ opacity: 0;
953
+ }
954
+ to {
955
+ opacity: 1;
956
+ }
888
957
  }
package/src/ChatPanel.tsx CHANGED
@@ -232,17 +232,20 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
232
232
  const responseAreaRef = useRef(null);
233
233
  // Extract the last reasoning or searching tag from the response
234
234
  const extractLastThinkingTag = (text: string): string => {
235
+ console.log("extractLastThinkingTag called with:", text?.length ? `${text.substring(0, 100)}...` : "empty");
236
+
235
237
  if (!text) return "Thinking";
236
238
 
237
- // Find all reasoning and searching tags using exec method
238
- const reasoningRegex = /<reasoning>(.*?)<\/reasoning>/gi;
239
- const searchingRegex = /<searching>(.*?)<\/searching>/gi;
239
+ // Find all reasoning and searching tags using exec method (with global and multiline flags)
240
+ const reasoningRegex = /<reasoning>([\s\S]*?)<\/reasoning>/gi;
241
+ const searchingRegex = /<searching>([\s\S]*?)<\/searching>/gi;
240
242
 
241
243
  const allMatches: Array<{ content: string; index: number; type: string }> = [];
242
244
 
243
245
  // Find all reasoning matches
244
246
  let reasoningMatch;
245
247
  while ((reasoningMatch = reasoningRegex.exec(text)) !== null) {
248
+ console.log("Found reasoning match:", reasoningMatch[1]?.substring(0, 50) + "...");
246
249
  allMatches.push({
247
250
  content: reasoningMatch[1]?.trim() || "",
248
251
  index: reasoningMatch.index,
@@ -253,6 +256,7 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
253
256
  // Find all searching matches
254
257
  let searchingMatch;
255
258
  while ((searchingMatch = searchingRegex.exec(text)) !== null) {
259
+ console.log("Found searching match:", searchingMatch[1]?.substring(0, 50) + "...");
256
260
  allMatches.push({
257
261
  content: searchingMatch[1]?.trim() || "",
258
262
  index: searchingMatch.index,
@@ -260,11 +264,15 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
260
264
  });
261
265
  }
262
266
 
267
+ console.log("Total matches found:", allMatches.length);
268
+
263
269
  // Sort by position and get the last one
264
270
  if (allMatches.length > 0) {
265
271
  const lastMatch = allMatches.sort((a, b) => b.index - a.index)[0];
266
272
  let content = lastMatch?.content || "Thinking";
267
273
 
274
+ console.log("Last match content:", content?.substring(0, 100));
275
+
268
276
  // Clean up the content - remove markdown formatting and limit length
269
277
  content = content.replace(/\*\*(.*?)\*\*/g, '$1'); // Remove bold
270
278
  content = content.replace(/\*(.*?)\*/g, '$1'); // Remove italics
@@ -277,26 +285,49 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
277
285
  content = content.substring(0, 77) + '...';
278
286
  }
279
287
 
288
+ console.log("Final extracted content:", content);
280
289
  return content || "Thinking";
281
290
  }
282
291
 
292
+ console.log("No matches found, returning 'Thinking'");
283
293
  return "Thinking";
284
294
  };
285
295
 
286
- // Remove reasoning and searching tags from the response content
287
- const cleanResponseContent = (text: string): string => {
296
+ // Process response content to show reasoning/searching in a styled format
297
+ const processResponseContent = (text: string, isStreaming: boolean = false): string => {
288
298
  if (!text) return "";
289
299
 
290
- // Remove reasoning tags and their content (with multiline support)
291
- let cleanText = text.replace(/<reasoning>[\s\S]*?<\/reasoning>/gi, '');
300
+ let processedText = text;
292
301
 
293
- // Remove searching tags and their content (with multiline support)
294
- cleanText = cleanText.replace(/<searching>[\s\S]*?<\/searching>/gi, '');
302
+ // Replace reasoning tags with styled content
303
+ processedText = processedText.replace(
304
+ /<reasoning>([\s\S]*?)<\/reasoning>/gi,
305
+ (match, content) => {
306
+ const trimmedContent = content.trim();
307
+ if (!trimmedContent) return '';
308
+
309
+ return `\n\n<div class="reasoning-section">
310
+ <div class="reasoning-header">🤔 Reasoning</div>
311
+ <div class="reasoning-content">${trimmedContent}</div>
312
+ </div>\n\n`;
313
+ }
314
+ );
295
315
 
296
- // Clean up any extra whitespace that might be left
297
- cleanText = cleanText.replace(/\n\s*\n/g, '\n').trim();
316
+ // Replace searching tags with styled content
317
+ processedText = processedText.replace(
318
+ /<searching>([\s\S]*?)<\/searching>/gi,
319
+ (match, content) => {
320
+ const trimmedContent = content.trim();
321
+ if (!trimmedContent) return '';
322
+
323
+ return `\n\n<div class="searching-section">
324
+ <div class="searching-header">🔍 Searching</div>
325
+ <div class="searching-content">${trimmedContent}</div>
326
+ </div>\n\n`;
327
+ }
328
+ );
298
329
 
299
- return cleanText;
330
+ return processedText;
300
331
  };
301
332
 
302
333
  const getBrowserInfo = () => {
@@ -477,6 +508,18 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
477
508
  }) as [],
478
509
  });
479
510
 
511
+ // Add logging for streaming states
512
+ useEffect(() => {
513
+ console.log("Streaming state changed:", {
514
+ isLoading,
515
+ idle,
516
+ responseLength: response?.length || 0,
517
+ lastCallId,
518
+ hasResponse: !!response,
519
+ timestamp: new Date().toISOString()
520
+ });
521
+ }, [isLoading, idle, response, lastCallId]);
522
+
480
523
  useEffect(() => {
481
524
  setShowEmailPanel(customerEmailCaptureMode !== "HIDE");
482
525
 
@@ -998,6 +1041,15 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
998
1041
 
999
1042
  useEffect(() => {
1000
1043
  if (response && response.length > 0) {
1044
+ console.log("Response updated:", {
1045
+ length: response.length,
1046
+ isLoading,
1047
+ idle,
1048
+ hasReasoningTags: response.includes('<reasoning>'),
1049
+ hasSearchingTags: response.includes('<searching>'),
1050
+ preview: response.substring(0, 200) + "..."
1051
+ });
1052
+
1001
1053
  setIsLoading(false);
1002
1054
 
1003
1055
  let newResponse = response;
@@ -1088,6 +1140,15 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
1088
1140
  setPendingToolRequests([]);
1089
1141
  }
1090
1142
 
1143
+ // Store the raw response without processing reasoning/searching tags
1144
+ // Tags will be processed only during streaming display
1145
+ console.log("Storing raw response to history:", {
1146
+ originalLength: newResponse.length,
1147
+ hasReasoningTags: newResponse.includes('<reasoning>'),
1148
+ hasSearchingTags: newResponse.includes('<searching>'),
1149
+ preview: newResponse.substring(0, 200) + "..."
1150
+ });
1151
+
1091
1152
  setHistory((prevHistory) => {
1092
1153
  // Get any existing tool data from the previous state
1093
1154
  const existingEntry = prevHistory[lastKey ?? ""] || {
@@ -1099,7 +1160,7 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
1099
1160
  ...prevHistory,
1100
1161
  [lastKey ?? ""]: {
1101
1162
  ...existingEntry, // This preserves toolCalls and toolResponses
1102
- content: cleanResponseContent(newResponse),
1163
+ content: newResponse, // Store raw response without processing
1103
1164
  callId: lastCallId,
1104
1165
  },
1105
1166
  };
@@ -1374,7 +1435,7 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
1374
1435
  return {
1375
1436
  ...prevHistory,
1376
1437
  [lastKey ?? ""]: {
1377
- content: cleanResponseContent(response) + "\n\n(response cancelled)",
1438
+ content: processResponseContent(response) + "\n\n(response cancelled)",
1378
1439
  callId: lastCallId,
1379
1440
  },
1380
1441
  };
@@ -1410,7 +1471,7 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
1410
1471
  messagesAndHistory.push({ role: "user", content: promptToSend });
1411
1472
  messagesAndHistory.push({
1412
1473
  role: "assistant",
1413
- content: cleanResponseContent(response.content),
1474
+ content: processResponseContent(response.content),
1414
1475
  });
1415
1476
  });
1416
1477
 
@@ -1885,22 +1946,37 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
1885
1946
  )}
1886
1947
 
1887
1948
  <div className="response">
1888
- {index === Object.keys(history).length - 1 && isLoading && (!response || response.length < 50) ? (
1889
- <div className="loading-text">
1890
- {extractLastThinkingTag(response || "")}&nbsp;
1891
- <div className="dot"></div>
1892
- <div className="dot"></div>
1893
- <div className="dot"></div>
1949
+ {/* Show streaming response with reasoning/searching tags */}
1950
+ {index === Object.keys(history).length - 1 && isLoading ? (
1951
+ <div className="streaming-response">
1952
+ {response && response.length > 0 ? (
1953
+ <ReactMarkdown
1954
+ className={markdownClass}
1955
+ remarkPlugins={[remarkGfm]}
1956
+ rehypePlugins={[rehypeRaw]}
1957
+ components={{ /*a: CustomLink,*/ code: CodeBlock }}
1958
+ >
1959
+ {processResponseContent(response, true)}
1960
+ </ReactMarkdown>
1961
+ ) : (
1962
+ <div className="loading-text">
1963
+ {extractLastThinkingTag(response || "")}&nbsp;
1964
+ <div className="dot"></div>
1965
+ <div className="dot"></div>
1966
+ <div className="dot"></div>
1967
+ </div>
1968
+ )}
1894
1969
  </div>
1895
- ) : null}
1896
- <ReactMarkdown
1897
- className={markdownClass}
1898
- remarkPlugins={[remarkGfm]}
1899
- rehypePlugins={[rehypeRaw]}
1900
- components={{ /*a: CustomLink,*/ code: CodeBlock }}
1901
- >
1902
- {historyEntry.content}
1903
- </ReactMarkdown>
1970
+ ) : (
1971
+ <ReactMarkdown
1972
+ className={markdownClass}
1973
+ remarkPlugins={[remarkGfm]}
1974
+ rehypePlugins={[rehypeRaw]}
1975
+ components={{ /*a: CustomLink,*/ code: CodeBlock }}
1976
+ >
1977
+ {processResponseContent(historyEntry.content)}
1978
+ </ReactMarkdown>
1979
+ )}
1904
1980
 
1905
1981
  {isLastEntry && pendingToolRequests.length > 0 && (
1906
1982
  <div className="approve-tools-panel">
@@ -1963,7 +2039,7 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
1963
2039
  <button
1964
2040
  className="copy-button"
1965
2041
  onClick={() => {
1966
- copyToClipboard(historyEntry.content);
2042
+ copyToClipboard(processResponseContent(historyEntry.content));
1967
2043
  }}
1968
2044
  disabled={isDisabledDueToNoEmail()}
1969
2045
  >
@@ -0,0 +1,52 @@
1
+ // Test to verify that our fix prevents duplication of reasoning blocks
2
+
3
+ const testResponse = `Hello! <reasoning>Let me think about this step by step.
4
+ I need to analyze the problem first.</reasoning>Based on my analysis, here's the answer.`;
5
+
6
+ console.log("Testing duplication fix...\n");
7
+
8
+ // Simulate processing function from ChatPanel
9
+ function processResponseContent(text, isStreaming = false) {
10
+ if (!text) return "";
11
+
12
+ let processedText = text;
13
+
14
+ // Replace reasoning tags with styled content
15
+ processedText = processedText.replace(
16
+ /<reasoning>([\s\S]*?)<\/reasoning>/gi,
17
+ (match, content) => {
18
+ const trimmedContent = content.trim();
19
+ if (!trimmedContent) return '';
20
+
21
+ return `\n\n<div class="reasoning-section">
22
+ <div class="reasoning-header">🤔 Reasoning</div>
23
+ <div class="reasoning-content">${trimmedContent}</div>
24
+ </div>\n\n`;
25
+ }
26
+ );
27
+
28
+ return processedText;
29
+ }
30
+
31
+ console.log("1. Original response (what would be stored in history):");
32
+ console.log(testResponse);
33
+ console.log("\n" + "=".repeat(80) + "\n");
34
+
35
+ console.log("2. Processed response (what would be displayed during streaming):");
36
+ const processedOnce = processResponseContent(testResponse);
37
+ console.log(processedOnce);
38
+ console.log("\n" + "=".repeat(80) + "\n");
39
+
40
+ console.log("3. OLD BUGGY BEHAVIOR - Processing the same content twice:");
41
+ const processedTwice = processResponseContent(processedOnce);
42
+ console.log(processedTwice);
43
+ console.log("\n" + "=".repeat(80) + "\n");
44
+
45
+ console.log("4. NEW FIXED BEHAVIOR - Processing raw content from history:");
46
+ const processedFromHistory = processResponseContent(testResponse);
47
+ console.log(processedFromHistory);
48
+ console.log("\n" + "=".repeat(80) + "\n");
49
+
50
+ console.log("5. Verification:");
51
+ console.log("Processing twice creates duplication:", processedTwice.includes('reasoning-section') && processedTwice.split('reasoning-section').length > 2);
52
+ console.log("Processing from raw history works correctly:", processedFromHistory === processedOnce);
File without changes
@@ -0,0 +1,125 @@
1
+ // Test file to verify tag extraction functions
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ // Mock test data
6
+ const testTexts = [
7
+ "This is a simple response without any tags.",
8
+ "<reasoning>I need to think about this problem step by step.</reasoning>Here's my answer.",
9
+ "Some text <searching>Looking for information about cats</searching> more text",
10
+ "<reasoning>First, I'll analyze the problem.</reasoning>Then I'll search. <searching>Finding relevant data</searching>Final answer here.",
11
+ "Multiple <reasoning>First reasoning</reasoning> and <reasoning>Second reasoning</reasoning> tags.",
12
+ "Incomplete <reasoning>Missing closing tag...",
13
+ "<reasoning>\nMultiline reasoning\nwith several lines\nof content\n</reasoning>Response text here.",
14
+ "Mixed <reasoning>Some thinking</reasoning> and <searching>Some searching</searching> with final answer."
15
+ ];
16
+
17
+ // Copy the extraction function from the React component
18
+ function extractLastThinkingTag(text) {
19
+ console.log("extractLastThinkingTag called with:", text?.length ? `${text.substring(0, 100)}...` : "empty");
20
+
21
+ if (!text) return "Thinking";
22
+
23
+ // Find all reasoning and searching tags using exec method (with global and multiline flags)
24
+ const reasoningRegex = /<reasoning>([\s\S]*?)<\/reasoning>/gi;
25
+ const searchingRegex = /<searching>([\s\S]*?)<\/searching>/gi;
26
+
27
+ const allMatches = [];
28
+
29
+ // Find all reasoning matches
30
+ let reasoningMatch;
31
+ while ((reasoningMatch = reasoningRegex.exec(text)) !== null) {
32
+ console.log("Found reasoning match:", reasoningMatch[1]?.substring(0, 50) + "...");
33
+ allMatches.push({
34
+ content: reasoningMatch[1]?.trim() || "",
35
+ index: reasoningMatch.index,
36
+ type: 'reasoning'
37
+ });
38
+ }
39
+
40
+ // Find all searching matches
41
+ let searchingMatch;
42
+ while ((searchingMatch = searchingRegex.exec(text)) !== null) {
43
+ console.log("Found searching match:", searchingMatch[1]?.substring(0, 50) + "...");
44
+ allMatches.push({
45
+ content: searchingMatch[1]?.trim() || "",
46
+ index: searchingMatch.index,
47
+ type: 'searching'
48
+ });
49
+ }
50
+
51
+ console.log("Total matches found:", allMatches.length);
52
+
53
+ // Sort by position and get the last one
54
+ if (allMatches.length > 0) {
55
+ const lastMatch = allMatches.sort((a, b) => b.index - a.index)[0];
56
+ let content = lastMatch?.content || "Thinking";
57
+
58
+ console.log("Last match content:", content?.substring(0, 100));
59
+
60
+ // Clean up the content - remove markdown formatting and limit length
61
+ content = content.replace(/\*\*(.*?)\*\*/g, '$1'); // Remove bold
62
+ content = content.replace(/\*(.*?)\*/g, '$1'); // Remove italics
63
+ content = content.replace(/\n+/g, ' '); // Replace newlines with spaces
64
+ content = content.replace(/\s+/g, ' '); // Normalize whitespace
65
+ content = content.trim();
66
+
67
+ // Limit length to keep UI clean
68
+ if (content.length > 80) {
69
+ content = content.substring(0, 77) + '...';
70
+ }
71
+
72
+ console.log("Final extracted content:", content);
73
+ return content || "Thinking";
74
+ }
75
+
76
+ console.log("No matches found, returning 'Thinking'");
77
+ return "Thinking";
78
+ }
79
+
80
+ // Copy the processing function from the React component
81
+ function processResponseContent(text, isStreaming = false) {
82
+ if (!text) return "";
83
+
84
+ let processedText = text;
85
+
86
+ // Replace reasoning tags with styled content
87
+ processedText = processedText.replace(
88
+ /<reasoning>([\s\S]*?)<\/reasoning>/gi,
89
+ (match, content) => {
90
+ const trimmedContent = content.trim();
91
+ if (!trimmedContent) return '';
92
+
93
+ return `\n\n<div class="reasoning-section">
94
+ <div class="reasoning-header">🤔 Reasoning</div>
95
+ <div class="reasoning-content">${trimmedContent}</div>
96
+ </div>\n\n`;
97
+ }
98
+ );
99
+
100
+ // Replace searching tags with styled content
101
+ processedText = processedText.replace(
102
+ /<searching>([\s\S]*?)<\/searching>/gi,
103
+ (match, content) => {
104
+ const trimmedContent = content.trim();
105
+ if (!trimmedContent) return '';
106
+
107
+ return `\n\n<div class="searching-section">
108
+ <div class="searching-header">🔍 Searching</div>
109
+ <div class="searching-content">${trimmedContent}</div>
110
+ </div>\n\n`;
111
+ }
112
+ );
113
+
114
+ return processedText;
115
+ }
116
+
117
+ console.log("Testing tag extraction and processing functions...\n");
118
+
119
+ testTexts.forEach((text, index) => {
120
+ console.log(`\n=== Test ${index + 1} ===`);
121
+ console.log("Input:", text);
122
+ console.log("Extracted tag:", extractLastThinkingTag(text));
123
+ console.log("Processed content:", processResponseContent(text));
124
+ console.log("---");
125
+ });
File without changes
File without changes