llmasaservice-ui 0.12.13 → 0.12.14

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
@@ -778,3 +778,69 @@
778
778
  opacity: 1;
779
779
  }
780
780
  }
781
+ .thinking-block-container {
782
+ margin-bottom: var(--spacing-medium);
783
+ }
784
+ .thinking-section {
785
+ border: 1px solid var(--input-border-color);
786
+ border-radius: var(--border-radius);
787
+ padding: var(--spacing-medium);
788
+ background-color: var(--input-background-color);
789
+ margin-bottom: var(--spacing-small);
790
+ }
791
+ .thinking-header {
792
+ display: flex;
793
+ justify-content: space-between;
794
+ align-items: center;
795
+ font-weight: bold;
796
+ color: var(--title-text-color);
797
+ margin-bottom: var(--spacing-small);
798
+ font-size: 0.9rem;
799
+ }
800
+ .thinking-navigation {
801
+ display: flex;
802
+ align-items: center;
803
+ gap: var(--spacing-small);
804
+ }
805
+ .thinking-nav-btn {
806
+ background: var(--button-background-color);
807
+ border: 1px solid var(--button-border-color);
808
+ border-radius: 4px;
809
+ padding: 2px 6px;
810
+ cursor: pointer;
811
+ font-size: 12px;
812
+ line-height: 1;
813
+ min-width: 20px;
814
+ height: 20px;
815
+ display: flex;
816
+ align-items: center;
817
+ justify-content: center;
818
+ }
819
+ .thinking-nav-btn:hover:not(:disabled) {
820
+ background: var(--button-background-color-hover);
821
+ color: var(--button-text-color-hover);
822
+ }
823
+ .thinking-nav-btn:disabled {
824
+ background: var(--button-disabled-background-color);
825
+ color: var(--button-disabled-color);
826
+ cursor: not-allowed;
827
+ }
828
+ .thinking-counter {
829
+ font-size: 11px;
830
+ color: var(--title-text-color);
831
+ font-weight: normal;
832
+ min-width: 30px;
833
+ text-align: center;
834
+ }
835
+ .thinking-content {
836
+ color: var(--input-text-color);
837
+ font-size: 0.85rem;
838
+ line-height: 1.4;
839
+ white-space: pre-wrap;
840
+ }
841
+ .reasoning-section {
842
+ border-left: 3px solid #4a90e2;
843
+ }
844
+ .searching-section {
845
+ border-left: 3px solid #7b68ee;
846
+ }
package/dist/index.js CHANGED
@@ -254,8 +254,8 @@ var ChatPanel = ({
254
254
  []
255
255
  );
256
256
  const [alwaysApprovedTools, setAlwaysApprovedTools] = (0, import_react3.useState)([]);
257
- const [reasoningBlocks, setReasoningBlocks] = (0, import_react3.useState)([]);
258
- const [searchingBlocks, setSearchingBlocks] = (0, import_react3.useState)([]);
257
+ const [thinkingBlocks, setThinkingBlocks] = (0, import_react3.useState)([]);
258
+ const [currentThinkingIndex, setCurrentThinkingIndex] = (0, import_react3.useState)(0);
259
259
  (0, import_react3.useEffect)(() => {
260
260
  const stored = localStorage.getItem("alwaysApprovedTools");
261
261
  if (stored) setAlwaysApprovedTools(JSON.parse(stored));
@@ -281,12 +281,9 @@ var ChatPanel = ({
281
281
  var _a2, _b;
282
282
  if (!text) return {
283
283
  cleanedText: "",
284
- reasoningBlocks: [],
285
- searchingBlocks: [],
284
+ thinkingBlocks: [],
286
285
  lastThinkingContent: "Thinking"
287
286
  };
288
- const newReasoningBlocks = [];
289
- const newSearchingBlocks = [];
290
287
  const allMatches = [];
291
288
  let cleanedText = text;
292
289
  const reasoningRegex = new RegExp(THINKING_PATTERNS.reasoning.source, "gi");
@@ -294,7 +291,6 @@ var ChatPanel = ({
294
291
  while ((reasoningMatch = reasoningRegex.exec(text)) !== null) {
295
292
  const content = (_a2 = reasoningMatch[1]) == null ? void 0 : _a2.trim();
296
293
  if (content) {
297
- newReasoningBlocks.push(content);
298
294
  allMatches.push({
299
295
  content,
300
296
  index: reasoningMatch.index,
@@ -307,7 +303,6 @@ var ChatPanel = ({
307
303
  while ((searchingMatch = searchingRegex.exec(text)) !== null) {
308
304
  const content = (_b = searchingMatch[1]) == null ? void 0 : _b.trim();
309
305
  if (content) {
310
- newSearchingBlocks.push(content);
311
306
  allMatches.push({
312
307
  content,
313
308
  index: searchingMatch.index,
@@ -315,12 +310,13 @@ var ChatPanel = ({
315
310
  });
316
311
  }
317
312
  }
313
+ const thinkingBlocks2 = allMatches.sort((a, b) => a.index - b.index);
318
314
  cleanedText = cleanedText.replace(THINKING_PATTERNS.reasoning, "");
319
315
  cleanedText = cleanedText.replace(THINKING_PATTERNS.searching, "");
320
316
  let lastThinkingContent = "Thinking";
321
- if (allMatches.length > 0) {
322
- const lastMatch = allMatches.sort((a, b) => b.index - a.index)[0];
323
- let content = (lastMatch == null ? void 0 : lastMatch.content) || "Thinking";
317
+ if (thinkingBlocks2.length > 0) {
318
+ const lastBlock = thinkingBlocks2[thinkingBlocks2.length - 1];
319
+ let content = (lastBlock == null ? void 0 : lastBlock.content) || "Thinking";
324
320
  content = content.replace(/\*\*(.*?)\*\*/g, "$1");
325
321
  content = content.replace(/\*(.*?)\*/g, "$1");
326
322
  content = content.replace(/\n+/g, " ");
@@ -333,24 +329,33 @@ var ChatPanel = ({
333
329
  }
334
330
  return {
335
331
  cleanedText: cleanedText.trim(),
336
- reasoningBlocks: newReasoningBlocks,
337
- searchingBlocks: newSearchingBlocks,
332
+ thinkingBlocks: thinkingBlocks2,
338
333
  lastThinkingContent
339
334
  };
340
335
  };
341
336
  const renderThinkingBlocks = () => {
342
- const blocks = [];
343
- reasoningBlocks.forEach((content, index) => {
344
- blocks.push(
345
- /* @__PURE__ */ import_react3.default.createElement("div", { key: `reasoning-${index}`, className: "reasoning-section" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "reasoning-header" }, "\u{1F914} Reasoning"), /* @__PURE__ */ import_react3.default.createElement("div", { className: "reasoning-content" }, content))
346
- );
347
- });
348
- searchingBlocks.forEach((content, index) => {
349
- blocks.push(
350
- /* @__PURE__ */ import_react3.default.createElement("div", { key: `searching-${index}`, className: "searching-section" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "searching-header" }, "\u{1F50D} Searching"), /* @__PURE__ */ import_react3.default.createElement("div", { className: "searching-content" }, content))
351
- );
352
- });
353
- return blocks;
337
+ if (thinkingBlocks.length === 0) return null;
338
+ const currentBlock = thinkingBlocks[currentThinkingIndex];
339
+ if (!currentBlock) return null;
340
+ const icon = currentBlock.type === "reasoning" ? "\u{1F914}" : "\u{1F50D}";
341
+ const title2 = currentBlock.type === "reasoning" ? "Reasoning" : "Searching";
342
+ return /* @__PURE__ */ import_react3.default.createElement("div", { className: "thinking-block-container" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: `thinking-section ${currentBlock.type}-section` }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "thinking-header" }, icon, " ", title2, thinkingBlocks.length > 1 && /* @__PURE__ */ import_react3.default.createElement("div", { className: "thinking-navigation" }, /* @__PURE__ */ import_react3.default.createElement(
343
+ "button",
344
+ {
345
+ onClick: () => setCurrentThinkingIndex(Math.max(0, currentThinkingIndex - 1)),
346
+ disabled: currentThinkingIndex === 0,
347
+ className: "thinking-nav-btn"
348
+ },
349
+ "\u2190"
350
+ ), /* @__PURE__ */ import_react3.default.createElement("span", { className: "thinking-counter" }, currentThinkingIndex + 1, " / ", thinkingBlocks.length), /* @__PURE__ */ import_react3.default.createElement(
351
+ "button",
352
+ {
353
+ onClick: () => setCurrentThinkingIndex(Math.min(thinkingBlocks.length - 1, currentThinkingIndex + 1)),
354
+ disabled: currentThinkingIndex === thinkingBlocks.length - 1,
355
+ className: "thinking-nav-btn"
356
+ },
357
+ "\u2192"
358
+ ))), /* @__PURE__ */ import_react3.default.createElement("div", { className: "thinking-content" }, currentBlock.content)));
354
359
  };
355
360
  const getBrowserInfo = () => {
356
361
  try {
@@ -943,9 +948,9 @@ var ChatPanel = ({
943
948
  preview: response.substring(0, 200) + "..."
944
949
  });
945
950
  setIsLoading(false);
946
- const { cleanedText, reasoningBlocks: newReasoningBlocks, searchingBlocks: newSearchingBlocks } = processThinkingTags(response);
947
- setReasoningBlocks(newReasoningBlocks);
948
- setSearchingBlocks(newSearchingBlocks);
951
+ const { cleanedText, thinkingBlocks: newThinkingBlocks } = processThinkingTags(response);
952
+ setThinkingBlocks(newThinkingBlocks);
953
+ setCurrentThinkingIndex(Math.max(0, newThinkingBlocks.length - 1));
949
954
  let newResponse = cleanedText;
950
955
  const toolRequests = [];
951
956
  if (allActions && allActions.length > 0) {
@@ -1055,8 +1060,8 @@ var ChatPanel = ({
1055
1060
  if (initialPrompt && initialPrompt !== "") {
1056
1061
  if (initialPrompt !== lastPrompt) {
1057
1062
  setIsLoading(true);
1058
- setReasoningBlocks([]);
1059
- setSearchingBlocks([]);
1063
+ setThinkingBlocks([]);
1064
+ setCurrentThinkingIndex(0);
1060
1065
  ensureConversation().then((convId) => {
1061
1066
  if (lastController) stop(lastController);
1062
1067
  const controller = new AbortController();
@@ -1235,8 +1240,8 @@ var ChatPanel = ({
1235
1240
  }, [currentCustomer, project_id, agent, publicAPIUrl, emailInput]);
1236
1241
  const continueChat = (suggestion) => {
1237
1242
  console.log("continueChat", suggestion);
1238
- setReasoningBlocks([]);
1239
- setSearchingBlocks([]);
1243
+ setThinkingBlocks([]);
1244
+ setCurrentThinkingIndex(0);
1240
1245
  if (emailInput && isEmailAddress(emailInput) && !emailInputSet) {
1241
1246
  const newId = (currentCustomer == null ? void 0 : currentCustomer.customer_id) && currentCustomer.customer_id !== "" && currentCustomer.customer_id !== (currentCustomer == null ? void 0 : currentCustomer.customer_user_email) ? currentCustomer.customer_id : emailInput;
1242
1247
  setEmailInputSet(true);
@@ -1649,8 +1654,17 @@ var ChatPanel = ({
1649
1654
  var _a2, _b;
1650
1655
  const isLastEntry = index === Object.keys(history).length - 1;
1651
1656
  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);
1652
- 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" }, renderThinkingBlocks(), (() => {
1653
- const { cleanedText, lastThinkingContent } = processThinkingTags(response || "");
1657
+ 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" }, (() => {
1658
+ const { cleanedText } = processThinkingTags(response || "");
1659
+ if (thinkingBlocks.length > 0) {
1660
+ return renderThinkingBlocks();
1661
+ }
1662
+ if (!cleanedText || cleanedText.length === 0) {
1663
+ return /* @__PURE__ */ import_react3.default.createElement("div", { className: "thinking-block-container" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "thinking-section" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "thinking-header" }, "\u{1F914} Thinking"), /* @__PURE__ */ import_react3.default.createElement("div", { className: "thinking-content" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "loading-text" }, "Thinking...\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" })))));
1664
+ }
1665
+ return null;
1666
+ })(), (() => {
1667
+ const { cleanedText } = processThinkingTags(response || "");
1654
1668
  return cleanedText && cleanedText.length > 0 ? /* @__PURE__ */ import_react3.default.createElement(
1655
1669
  import_react_markdown.default,
1656
1670
  {
@@ -1663,8 +1677,8 @@ var ChatPanel = ({
1663
1677
  }
1664
1678
  },
1665
1679
  cleanedText
1666
- ) : /* @__PURE__ */ import_react3.default.createElement("div", { className: "loading-text" }, lastThinkingContent, "\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" }));
1667
- })()) : /* @__PURE__ */ import_react3.default.createElement("div", null, isLastEntry && (reasoningBlocks.length > 0 || searchingBlocks.length > 0) && renderThinkingBlocks(), /* @__PURE__ */ import_react3.default.createElement(
1680
+ ) : null;
1681
+ })()) : /* @__PURE__ */ import_react3.default.createElement("div", null, isLastEntry && thinkingBlocks.length > 0 && renderThinkingBlocks(), /* @__PURE__ */ import_react3.default.createElement(
1668
1682
  import_react_markdown.default,
1669
1683
  {
1670
1684
  className: markdownClass,
package/dist/index.mjs CHANGED
@@ -226,8 +226,8 @@ var ChatPanel = ({
226
226
  []
227
227
  );
228
228
  const [alwaysApprovedTools, setAlwaysApprovedTools] = useState2([]);
229
- const [reasoningBlocks, setReasoningBlocks] = useState2([]);
230
- const [searchingBlocks, setSearchingBlocks] = useState2([]);
229
+ const [thinkingBlocks, setThinkingBlocks] = useState2([]);
230
+ const [currentThinkingIndex, setCurrentThinkingIndex] = useState2(0);
231
231
  useEffect2(() => {
232
232
  const stored = localStorage.getItem("alwaysApprovedTools");
233
233
  if (stored) setAlwaysApprovedTools(JSON.parse(stored));
@@ -253,12 +253,9 @@ var ChatPanel = ({
253
253
  var _a2, _b;
254
254
  if (!text) return {
255
255
  cleanedText: "",
256
- reasoningBlocks: [],
257
- searchingBlocks: [],
256
+ thinkingBlocks: [],
258
257
  lastThinkingContent: "Thinking"
259
258
  };
260
- const newReasoningBlocks = [];
261
- const newSearchingBlocks = [];
262
259
  const allMatches = [];
263
260
  let cleanedText = text;
264
261
  const reasoningRegex = new RegExp(THINKING_PATTERNS.reasoning.source, "gi");
@@ -266,7 +263,6 @@ var ChatPanel = ({
266
263
  while ((reasoningMatch = reasoningRegex.exec(text)) !== null) {
267
264
  const content = (_a2 = reasoningMatch[1]) == null ? void 0 : _a2.trim();
268
265
  if (content) {
269
- newReasoningBlocks.push(content);
270
266
  allMatches.push({
271
267
  content,
272
268
  index: reasoningMatch.index,
@@ -279,7 +275,6 @@ var ChatPanel = ({
279
275
  while ((searchingMatch = searchingRegex.exec(text)) !== null) {
280
276
  const content = (_b = searchingMatch[1]) == null ? void 0 : _b.trim();
281
277
  if (content) {
282
- newSearchingBlocks.push(content);
283
278
  allMatches.push({
284
279
  content,
285
280
  index: searchingMatch.index,
@@ -287,12 +282,13 @@ var ChatPanel = ({
287
282
  });
288
283
  }
289
284
  }
285
+ const thinkingBlocks2 = allMatches.sort((a, b) => a.index - b.index);
290
286
  cleanedText = cleanedText.replace(THINKING_PATTERNS.reasoning, "");
291
287
  cleanedText = cleanedText.replace(THINKING_PATTERNS.searching, "");
292
288
  let lastThinkingContent = "Thinking";
293
- if (allMatches.length > 0) {
294
- const lastMatch = allMatches.sort((a, b) => b.index - a.index)[0];
295
- let content = (lastMatch == null ? void 0 : lastMatch.content) || "Thinking";
289
+ if (thinkingBlocks2.length > 0) {
290
+ const lastBlock = thinkingBlocks2[thinkingBlocks2.length - 1];
291
+ let content = (lastBlock == null ? void 0 : lastBlock.content) || "Thinking";
296
292
  content = content.replace(/\*\*(.*?)\*\*/g, "$1");
297
293
  content = content.replace(/\*(.*?)\*/g, "$1");
298
294
  content = content.replace(/\n+/g, " ");
@@ -305,24 +301,33 @@ var ChatPanel = ({
305
301
  }
306
302
  return {
307
303
  cleanedText: cleanedText.trim(),
308
- reasoningBlocks: newReasoningBlocks,
309
- searchingBlocks: newSearchingBlocks,
304
+ thinkingBlocks: thinkingBlocks2,
310
305
  lastThinkingContent
311
306
  };
312
307
  };
313
308
  const renderThinkingBlocks = () => {
314
- const blocks = [];
315
- reasoningBlocks.forEach((content, index) => {
316
- blocks.push(
317
- /* @__PURE__ */ React3.createElement("div", { key: `reasoning-${index}`, className: "reasoning-section" }, /* @__PURE__ */ React3.createElement("div", { className: "reasoning-header" }, "\u{1F914} Reasoning"), /* @__PURE__ */ React3.createElement("div", { className: "reasoning-content" }, content))
318
- );
319
- });
320
- searchingBlocks.forEach((content, index) => {
321
- blocks.push(
322
- /* @__PURE__ */ React3.createElement("div", { key: `searching-${index}`, className: "searching-section" }, /* @__PURE__ */ React3.createElement("div", { className: "searching-header" }, "\u{1F50D} Searching"), /* @__PURE__ */ React3.createElement("div", { className: "searching-content" }, content))
323
- );
324
- });
325
- return blocks;
309
+ if (thinkingBlocks.length === 0) return null;
310
+ const currentBlock = thinkingBlocks[currentThinkingIndex];
311
+ if (!currentBlock) return null;
312
+ const icon = currentBlock.type === "reasoning" ? "\u{1F914}" : "\u{1F50D}";
313
+ const title2 = currentBlock.type === "reasoning" ? "Reasoning" : "Searching";
314
+ return /* @__PURE__ */ React3.createElement("div", { className: "thinking-block-container" }, /* @__PURE__ */ React3.createElement("div", { className: `thinking-section ${currentBlock.type}-section` }, /* @__PURE__ */ React3.createElement("div", { className: "thinking-header" }, icon, " ", title2, thinkingBlocks.length > 1 && /* @__PURE__ */ React3.createElement("div", { className: "thinking-navigation" }, /* @__PURE__ */ React3.createElement(
315
+ "button",
316
+ {
317
+ onClick: () => setCurrentThinkingIndex(Math.max(0, currentThinkingIndex - 1)),
318
+ disabled: currentThinkingIndex === 0,
319
+ className: "thinking-nav-btn"
320
+ },
321
+ "\u2190"
322
+ ), /* @__PURE__ */ React3.createElement("span", { className: "thinking-counter" }, currentThinkingIndex + 1, " / ", thinkingBlocks.length), /* @__PURE__ */ React3.createElement(
323
+ "button",
324
+ {
325
+ onClick: () => setCurrentThinkingIndex(Math.min(thinkingBlocks.length - 1, currentThinkingIndex + 1)),
326
+ disabled: currentThinkingIndex === thinkingBlocks.length - 1,
327
+ className: "thinking-nav-btn"
328
+ },
329
+ "\u2192"
330
+ ))), /* @__PURE__ */ React3.createElement("div", { className: "thinking-content" }, currentBlock.content)));
326
331
  };
327
332
  const getBrowserInfo = () => {
328
333
  try {
@@ -915,9 +920,9 @@ var ChatPanel = ({
915
920
  preview: response.substring(0, 200) + "..."
916
921
  });
917
922
  setIsLoading(false);
918
- const { cleanedText, reasoningBlocks: newReasoningBlocks, searchingBlocks: newSearchingBlocks } = processThinkingTags(response);
919
- setReasoningBlocks(newReasoningBlocks);
920
- setSearchingBlocks(newSearchingBlocks);
923
+ const { cleanedText, thinkingBlocks: newThinkingBlocks } = processThinkingTags(response);
924
+ setThinkingBlocks(newThinkingBlocks);
925
+ setCurrentThinkingIndex(Math.max(0, newThinkingBlocks.length - 1));
921
926
  let newResponse = cleanedText;
922
927
  const toolRequests = [];
923
928
  if (allActions && allActions.length > 0) {
@@ -1027,8 +1032,8 @@ var ChatPanel = ({
1027
1032
  if (initialPrompt && initialPrompt !== "") {
1028
1033
  if (initialPrompt !== lastPrompt) {
1029
1034
  setIsLoading(true);
1030
- setReasoningBlocks([]);
1031
- setSearchingBlocks([]);
1035
+ setThinkingBlocks([]);
1036
+ setCurrentThinkingIndex(0);
1032
1037
  ensureConversation().then((convId) => {
1033
1038
  if (lastController) stop(lastController);
1034
1039
  const controller = new AbortController();
@@ -1207,8 +1212,8 @@ var ChatPanel = ({
1207
1212
  }, [currentCustomer, project_id, agent, publicAPIUrl, emailInput]);
1208
1213
  const continueChat = (suggestion) => {
1209
1214
  console.log("continueChat", suggestion);
1210
- setReasoningBlocks([]);
1211
- setSearchingBlocks([]);
1215
+ setThinkingBlocks([]);
1216
+ setCurrentThinkingIndex(0);
1212
1217
  if (emailInput && isEmailAddress(emailInput) && !emailInputSet) {
1213
1218
  const newId = (currentCustomer == null ? void 0 : currentCustomer.customer_id) && currentCustomer.customer_id !== "" && currentCustomer.customer_id !== (currentCustomer == null ? void 0 : currentCustomer.customer_user_email) ? currentCustomer.customer_id : emailInput;
1214
1219
  setEmailInputSet(true);
@@ -1621,8 +1626,17 @@ var ChatPanel = ({
1621
1626
  var _a2, _b;
1622
1627
  const isLastEntry = index === Object.keys(history).length - 1;
1623
1628
  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);
1624
- 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" }, renderThinkingBlocks(), (() => {
1625
- const { cleanedText, lastThinkingContent } = processThinkingTags(response || "");
1629
+ 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" }, (() => {
1630
+ const { cleanedText } = processThinkingTags(response || "");
1631
+ if (thinkingBlocks.length > 0) {
1632
+ return renderThinkingBlocks();
1633
+ }
1634
+ if (!cleanedText || cleanedText.length === 0) {
1635
+ return /* @__PURE__ */ React3.createElement("div", { className: "thinking-block-container" }, /* @__PURE__ */ React3.createElement("div", { className: "thinking-section" }, /* @__PURE__ */ React3.createElement("div", { className: "thinking-header" }, "\u{1F914} Thinking"), /* @__PURE__ */ React3.createElement("div", { className: "thinking-content" }, /* @__PURE__ */ React3.createElement("div", { className: "loading-text" }, "Thinking...\xA0", /* @__PURE__ */ React3.createElement("div", { className: "dot" }), /* @__PURE__ */ React3.createElement("div", { className: "dot" }), /* @__PURE__ */ React3.createElement("div", { className: "dot" })))));
1636
+ }
1637
+ return null;
1638
+ })(), (() => {
1639
+ const { cleanedText } = processThinkingTags(response || "");
1626
1640
  return cleanedText && cleanedText.length > 0 ? /* @__PURE__ */ React3.createElement(
1627
1641
  ReactMarkdown,
1628
1642
  {
@@ -1635,8 +1649,8 @@ var ChatPanel = ({
1635
1649
  }
1636
1650
  },
1637
1651
  cleanedText
1638
- ) : /* @__PURE__ */ React3.createElement("div", { className: "loading-text" }, lastThinkingContent, "\xA0", /* @__PURE__ */ React3.createElement("div", { className: "dot" }), /* @__PURE__ */ React3.createElement("div", { className: "dot" }), /* @__PURE__ */ React3.createElement("div", { className: "dot" }));
1639
- })()) : /* @__PURE__ */ React3.createElement("div", null, isLastEntry && (reasoningBlocks.length > 0 || searchingBlocks.length > 0) && renderThinkingBlocks(), /* @__PURE__ */ React3.createElement(
1652
+ ) : null;
1653
+ })()) : /* @__PURE__ */ React3.createElement("div", null, isLastEntry && thinkingBlocks.length > 0 && renderThinkingBlocks(), /* @__PURE__ */ React3.createElement(
1640
1654
  ReactMarkdown,
1641
1655
  {
1642
1656
  className: markdownClass,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "llmasaservice-ui",
3
- "version": "0.12.13",
3
+ "version": "0.12.14",
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
@@ -954,4 +954,82 @@
954
954
  to {
955
955
  opacity: 1;
956
956
  }
957
+ }
958
+
959
+ /* Thinking Block Styles */
960
+ .thinking-block-container {
961
+ margin-bottom: var(--spacing-medium);
962
+ }
963
+
964
+ .thinking-section {
965
+ border: 1px solid var(--input-border-color);
966
+ border-radius: var(--border-radius);
967
+ padding: var(--spacing-medium);
968
+ background-color: var(--input-background-color);
969
+ margin-bottom: var(--spacing-small);
970
+ }
971
+
972
+ .thinking-header {
973
+ display: flex;
974
+ justify-content: space-between;
975
+ align-items: center;
976
+ font-weight: bold;
977
+ color: var(--title-text-color);
978
+ margin-bottom: var(--spacing-small);
979
+ font-size: 0.9rem;
980
+ }
981
+
982
+ .thinking-navigation {
983
+ display: flex;
984
+ align-items: center;
985
+ gap: var(--spacing-small);
986
+ }
987
+
988
+ .thinking-nav-btn {
989
+ background: var(--button-background-color);
990
+ border: 1px solid var(--button-border-color);
991
+ border-radius: 4px;
992
+ padding: 2px 6px;
993
+ cursor: pointer;
994
+ font-size: 12px;
995
+ line-height: 1;
996
+ min-width: 20px;
997
+ height: 20px;
998
+ display: flex;
999
+ align-items: center;
1000
+ justify-content: center;
1001
+ }
1002
+
1003
+ .thinking-nav-btn:hover:not(:disabled) {
1004
+ background: var(--button-background-color-hover);
1005
+ color: var(--button-text-color-hover);
1006
+ }
1007
+
1008
+ .thinking-nav-btn:disabled {
1009
+ background: var(--button-disabled-background-color);
1010
+ color: var(--button-disabled-color);
1011
+ cursor: not-allowed;
1012
+ }
1013
+
1014
+ .thinking-counter {
1015
+ font-size: 11px;
1016
+ color: var(--title-text-color);
1017
+ font-weight: normal;
1018
+ min-width: 30px;
1019
+ text-align: center;
1020
+ }
1021
+
1022
+ .thinking-content {
1023
+ color: var(--input-text-color);
1024
+ font-size: 0.85rem;
1025
+ line-height: 1.4;
1026
+ white-space: pre-wrap;
1027
+ }
1028
+
1029
+ .reasoning-section {
1030
+ border-left: 3px solid #4a90e2;
1031
+ }
1032
+
1033
+ .searching-section {
1034
+ border-left: 3px solid #7b68ee;
957
1035
  }
package/src/ChatPanel.tsx CHANGED
@@ -208,9 +208,9 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
208
208
  );
209
209
  const [alwaysApprovedTools, setAlwaysApprovedTools] = useState<string[]>([]);
210
210
 
211
- // State for tracking reasoning and searching content separately
212
- const [reasoningBlocks, setReasoningBlocks] = useState<string[]>([]);
213
- const [searchingBlocks, setSearchingBlocks] = useState<string[]>([]);
211
+ // State for tracking thinking content and navigation
212
+ const [thinkingBlocks, setThinkingBlocks] = useState<Array<{ type: 'reasoning' | 'searching'; content: string; index: number }>>([]);
213
+ const [currentThinkingIndex, setCurrentThinkingIndex] = useState(0);
214
214
 
215
215
  // load “always” approvals
216
216
  useEffect(() => {
@@ -241,23 +241,19 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
241
241
  searching: /<searching>([\s\S]*?)<\/searching>/gi
242
242
  } as const;
243
243
 
244
- // Single function to extract thinking blocks with optional storage
244
+ // Single function to extract thinking blocks in order
245
245
  const processThinkingTags = (text: string): {
246
246
  cleanedText: string;
247
- reasoningBlocks: string[];
248
- searchingBlocks: string[];
247
+ thinkingBlocks: Array<{ type: 'reasoning' | 'searching'; content: string; index: number }>;
249
248
  lastThinkingContent: string;
250
249
  } => {
251
250
  if (!text) return {
252
251
  cleanedText: "",
253
- reasoningBlocks: [],
254
- searchingBlocks: [],
252
+ thinkingBlocks: [],
255
253
  lastThinkingContent: "Thinking"
256
254
  };
257
255
 
258
- const newReasoningBlocks: string[] = [];
259
- const newSearchingBlocks: string[] = [];
260
- const allMatches: Array<{ content: string; index: number; type: string }> = [];
256
+ const allMatches: Array<{ content: string; index: number; type: 'reasoning' | 'searching' }> = [];
261
257
  let cleanedText = text;
262
258
 
263
259
  // Process reasoning blocks
@@ -266,7 +262,6 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
266
262
  while ((reasoningMatch = reasoningRegex.exec(text)) !== null) {
267
263
  const content = reasoningMatch[1]?.trim();
268
264
  if (content) {
269
- newReasoningBlocks.push(content);
270
265
  allMatches.push({
271
266
  content,
272
267
  index: reasoningMatch.index,
@@ -281,7 +276,6 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
281
276
  while ((searchingMatch = searchingRegex.exec(text)) !== null) {
282
277
  const content = searchingMatch[1]?.trim();
283
278
  if (content) {
284
- newSearchingBlocks.push(content);
285
279
  allMatches.push({
286
280
  content,
287
281
  index: searchingMatch.index,
@@ -290,15 +284,18 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
290
284
  }
291
285
  }
292
286
 
287
+ // Sort by index to preserve original order
288
+ const thinkingBlocks = allMatches.sort((a, b) => a.index - b.index);
289
+
293
290
  // Clean the text
294
291
  cleanedText = cleanedText.replace(THINKING_PATTERNS.reasoning, '');
295
292
  cleanedText = cleanedText.replace(THINKING_PATTERNS.searching, '');
296
293
 
297
294
  // Get last thinking content
298
295
  let lastThinkingContent = "Thinking";
299
- if (allMatches.length > 0) {
300
- const lastMatch = allMatches.sort((a, b) => b.index - a.index)[0];
301
- let content = lastMatch?.content || "Thinking";
296
+ if (thinkingBlocks.length > 0) {
297
+ const lastBlock = thinkingBlocks[thinkingBlocks.length - 1];
298
+ let content = lastBlock?.content || "Thinking";
302
299
 
303
300
  // Clean up the content for display
304
301
  content = content.replace(/\*\*(.*?)\*\*/g, '$1'); // Remove bold
@@ -317,38 +314,52 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
317
314
 
318
315
  return {
319
316
  cleanedText: cleanedText.trim(),
320
- reasoningBlocks: newReasoningBlocks,
321
- searchingBlocks: newSearchingBlocks,
317
+ thinkingBlocks,
322
318
  lastThinkingContent
323
319
  };
324
320
  };
325
321
 
326
- // Process response content to show reasoning/searching in a styled format
327
- // Render stored thinking blocks
328
- const renderThinkingBlocks = (): JSX.Element[] => {
329
- const blocks: JSX.Element[] = [];
322
+ // Render thinking blocks with navigation
323
+ const renderThinkingBlocks = (): JSX.Element | null => {
324
+ if (thinkingBlocks.length === 0) return null;
330
325
 
331
- // Add reasoning blocks
332
- reasoningBlocks.forEach((content, index) => {
333
- blocks.push(
334
- <div key={`reasoning-${index}`} className="reasoning-section">
335
- <div className="reasoning-header">🤔 Reasoning</div>
336
- <div className="reasoning-content">{content}</div>
337
- </div>
338
- );
339
- });
326
+ const currentBlock = thinkingBlocks[currentThinkingIndex];
327
+ if (!currentBlock) return null;
340
328
 
341
- // Add searching blocks
342
- searchingBlocks.forEach((content, index) => {
343
- blocks.push(
344
- <div key={`searching-${index}`} className="searching-section">
345
- <div className="searching-header">🔍 Searching</div>
346
- <div className="searching-content">{content}</div>
347
- </div>
348
- );
349
- });
329
+ const icon = currentBlock.type === 'reasoning' ? '🤔' : '🔍';
330
+ const title = currentBlock.type === 'reasoning' ? 'Reasoning' : 'Searching';
350
331
 
351
- return blocks;
332
+ return (
333
+ <div className="thinking-block-container">
334
+ <div className={`thinking-section ${currentBlock.type}-section`}>
335
+ <div className="thinking-header">
336
+ {icon} {title}
337
+ {thinkingBlocks.length > 1 && (
338
+ <div className="thinking-navigation">
339
+ <button
340
+ onClick={() => setCurrentThinkingIndex(Math.max(0, currentThinkingIndex - 1))}
341
+ disabled={currentThinkingIndex === 0}
342
+ className="thinking-nav-btn"
343
+ >
344
+
345
+ </button>
346
+ <span className="thinking-counter">
347
+ {currentThinkingIndex + 1} / {thinkingBlocks.length}
348
+ </span>
349
+ <button
350
+ onClick={() => setCurrentThinkingIndex(Math.min(thinkingBlocks.length - 1, currentThinkingIndex + 1))}
351
+ disabled={currentThinkingIndex === thinkingBlocks.length - 1}
352
+ className="thinking-nav-btn"
353
+ >
354
+
355
+ </button>
356
+ </div>
357
+ )}
358
+ </div>
359
+ <div className="thinking-content">{currentBlock.content}</div>
360
+ </div>
361
+ </div>
362
+ );
352
363
  };
353
364
 
354
365
  const getBrowserInfo = () => {
@@ -1074,11 +1085,12 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
1074
1085
  setIsLoading(false);
1075
1086
 
1076
1087
  // Process thinking tags and get cleaned response
1077
- const { cleanedText, reasoningBlocks: newReasoningBlocks, searchingBlocks: newSearchingBlocks } = processThinkingTags(response);
1088
+ const { cleanedText, thinkingBlocks: newThinkingBlocks } = processThinkingTags(response);
1078
1089
 
1079
1090
  // Replace the blocks entirely (don't append) to avoid duplicates during streaming
1080
- setReasoningBlocks(newReasoningBlocks);
1081
- setSearchingBlocks(newSearchingBlocks);
1091
+ setThinkingBlocks(newThinkingBlocks);
1092
+ // Always show the latest (last) thinking block
1093
+ setCurrentThinkingIndex(Math.max(0, newThinkingBlocks.length - 1));
1082
1094
 
1083
1095
  let newResponse = cleanedText;
1084
1096
 
@@ -1217,8 +1229,8 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
1217
1229
  setIsLoading(true);
1218
1230
 
1219
1231
  // Clear thinking blocks for new response
1220
- setReasoningBlocks([]);
1221
- setSearchingBlocks([]);
1232
+ setThinkingBlocks([]);
1233
+ setCurrentThinkingIndex(0);
1222
1234
 
1223
1235
  ensureConversation().then((convId) => {
1224
1236
  if (lastController) stop(lastController);
@@ -1441,8 +1453,8 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
1441
1453
  console.log("continueChat", suggestion);
1442
1454
 
1443
1455
  // Clear thinking blocks for new response
1444
- setReasoningBlocks([]);
1445
- setSearchingBlocks([]);
1456
+ setThinkingBlocks([]);
1457
+ setCurrentThinkingIndex(0);
1446
1458
 
1447
1459
  // Auto-set email if valid before proceeding
1448
1460
  if (emailInput && isEmailAddress(emailInput) && !emailInputSet) {
@@ -1986,12 +1998,40 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
1986
1998
  {/* Show streaming response with thinking blocks displayed separately */}
1987
1999
  {index === Object.keys(history).length - 1 && isLoading ? (
1988
2000
  <div className="streaming-response">
1989
- {/* Display stored thinking blocks */}
1990
- {renderThinkingBlocks()}
2001
+ {/* Display current thinking block or thinking message */}
2002
+ {(() => {
2003
+ const { cleanedText } = processThinkingTags(response || "");
2004
+
2005
+ // If we have thinking blocks, show the current one
2006
+ if (thinkingBlocks.length > 0) {
2007
+ return renderThinkingBlocks();
2008
+ }
2009
+
2010
+ // If no thinking blocks yet but no main content, show generic thinking
2011
+ if (!cleanedText || cleanedText.length === 0) {
2012
+ return (
2013
+ <div className="thinking-block-container">
2014
+ <div className="thinking-section">
2015
+ <div className="thinking-header">🤔 Thinking</div>
2016
+ <div className="thinking-content">
2017
+ <div className="loading-text">
2018
+ Thinking...&nbsp;
2019
+ <div className="dot"></div>
2020
+ <div className="dot"></div>
2021
+ <div className="dot"></div>
2022
+ </div>
2023
+ </div>
2024
+ </div>
2025
+ </div>
2026
+ );
2027
+ }
2028
+
2029
+ return null;
2030
+ })()}
1991
2031
 
1992
2032
  {/* Display the main content (cleaned of thinking tags) */}
1993
2033
  {(() => {
1994
- const { cleanedText, lastThinkingContent } = processThinkingTags(response || "");
2034
+ const { cleanedText } = processThinkingTags(response || "");
1995
2035
  return cleanedText && cleanedText.length > 0 ? (
1996
2036
  <ReactMarkdown
1997
2037
  className={markdownClass}
@@ -2001,20 +2041,13 @@ const ChatPanel: React.FC<ChatPanelProps & ExtraProps> = ({
2001
2041
  >
2002
2042
  {cleanedText}
2003
2043
  </ReactMarkdown>
2004
- ) : (
2005
- <div className="loading-text">
2006
- {lastThinkingContent}&nbsp;
2007
- <div className="dot"></div>
2008
- <div className="dot"></div>
2009
- <div className="dot"></div>
2010
- </div>
2011
- );
2044
+ ) : null;
2012
2045
  })()}
2013
2046
  </div>
2014
2047
  ) : (
2015
2048
  <div>
2016
2049
  {/* For completed responses, show stored thinking blocks if this is the last entry */}
2017
- {isLastEntry && (reasoningBlocks.length > 0 || searchingBlocks.length > 0) && renderThinkingBlocks()}
2050
+ {isLastEntry && thinkingBlocks.length > 0 && renderThinkingBlocks()}
2018
2051
 
2019
2052
  {/* Show the main content */}
2020
2053
  <ReactMarkdown