@hef2024/llmasaservice-ui 0.24.4 → 0.24.6

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.js CHANGED
@@ -624,6 +624,25 @@ var SearchingIcon = () => /* @__PURE__ */ import_react10.default.createElement(
624
624
  /* @__PURE__ */ import_react10.default.createElement("circle", { cx: "11", cy: "11", r: "8" }),
625
625
  /* @__PURE__ */ import_react10.default.createElement("path", { d: "m21 21-4.3-4.3" })
626
626
  );
627
+ var PlanningIcon = () => /* @__PURE__ */ import_react10.default.createElement(
628
+ "svg",
629
+ {
630
+ xmlns: "http://www.w3.org/2000/svg",
631
+ viewBox: "0 0 24 24",
632
+ fill: "none",
633
+ stroke: "currentColor",
634
+ strokeWidth: "2",
635
+ strokeLinecap: "round",
636
+ strokeLinejoin: "round",
637
+ className: "thinking-block__icon"
638
+ },
639
+ /* @__PURE__ */ import_react10.default.createElement("path", { d: "M9 5h11" }),
640
+ /* @__PURE__ */ import_react10.default.createElement("path", { d: "M9 12h11" }),
641
+ /* @__PURE__ */ import_react10.default.createElement("path", { d: "M9 19h11" }),
642
+ /* @__PURE__ */ import_react10.default.createElement("path", { d: "m4 5 1.5 1.5L7 4.5" }),
643
+ /* @__PURE__ */ import_react10.default.createElement("path", { d: "m4 12 1.5 1.5L7 11.5" }),
644
+ /* @__PURE__ */ import_react10.default.createElement("path", { d: "m4 19 1.5 1.5L7 18.5" })
645
+ );
627
646
  var ChevronIcon = ({ isCollapsed }) => /* @__PURE__ */ import_react10.default.createElement(
628
647
  "svg",
629
648
  {
@@ -646,6 +665,8 @@ var getIcon = (type) => {
646
665
  return /* @__PURE__ */ import_react10.default.createElement(ReasoningIcon, null);
647
666
  case "searching":
648
667
  return /* @__PURE__ */ import_react10.default.createElement(SearchingIcon, null);
668
+ case "planning":
669
+ return /* @__PURE__ */ import_react10.default.createElement(PlanningIcon, null);
649
670
  }
650
671
  };
651
672
  var getDefaultTitle = (type) => {
@@ -656,6 +677,8 @@ var getDefaultTitle = (type) => {
656
677
  return "Reasoning";
657
678
  case "searching":
658
679
  return "Searching";
680
+ case "planning":
681
+ return "Planning";
659
682
  }
660
683
  };
661
684
  var ThinkingBlock = ({
@@ -1414,46 +1437,51 @@ var ChatPanel = ({
1414
1437
  const urlToFetch = `${publicAPIUrl}/tools/${encodeURIComponent(
1415
1438
  m.url
1416
1439
  )}`;
1417
- try {
1418
- const requestHeaders = yield buildMcpRequestHeaders({
1419
- phase: "list",
1420
- mcpServer: m
1421
- });
1422
- const response2 = yield fetch(urlToFetch, {
1423
- headers: requestHeaders
1424
- });
1425
- if (!response2.ok) {
1426
- console.error(
1427
- `Error fetching tools from ${m.url}: ${response2.status} ${response2.statusText}`
1428
- );
1429
- const errorBody = yield response2.text();
1430
- console.error(`Error body: ${errorBody}`);
1431
- throw new Error(
1432
- `HTTP ${response2.status}: ${response2.statusText}`
1433
- );
1434
- }
1435
- const toolsFromServer = yield response2.json();
1436
- if (Array.isArray(toolsFromServer)) {
1437
- return toolsFromServer.map((tool) => __spreadProps(__spreadValues({}, tool), {
1438
- url: m.url,
1439
- accessToken: m.accessToken || "",
1440
- headers: requestHeaders
1441
- }));
1442
- } else {
1443
- return [];
1444
- }
1445
- } catch (fetchError) {
1440
+ const requestHeaders = yield buildMcpRequestHeaders({
1441
+ phase: "list",
1442
+ mcpServer: m
1443
+ });
1444
+ const response2 = yield fetch(urlToFetch, {
1445
+ headers: requestHeaders
1446
+ });
1447
+ if (!response2.ok) {
1446
1448
  console.error(
1447
- `Network or parsing error fetching tools from ${m.url}:`,
1448
- fetchError
1449
+ `Error fetching tools from ${m.url}: ${response2.status} ${response2.statusText}`
1449
1450
  );
1450
- throw fetchError;
1451
+ const errorBody = yield response2.text();
1452
+ console.error(`Error body: ${errorBody}`);
1453
+ throw new Error(
1454
+ `HTTP ${response2.status}: ${response2.statusText}`
1455
+ );
1456
+ }
1457
+ const toolsFromServer = yield response2.json();
1458
+ if (Array.isArray(toolsFromServer)) {
1459
+ return toolsFromServer.map((tool) => __spreadProps(__spreadValues({}, tool), {
1460
+ url: m.url,
1461
+ accessToken: m.accessToken || "",
1462
+ headers: requestHeaders
1463
+ }));
1451
1464
  }
1465
+ return [];
1452
1466
  }));
1453
- const results = yield Promise.all(fetchPromises);
1454
- const allTools = results.flat();
1467
+ const settledResults = yield Promise.allSettled(fetchPromises);
1468
+ const allTools = settledResults.flatMap((result, index) => {
1469
+ if (result.status === "fulfilled") {
1470
+ return result.value;
1471
+ }
1472
+ const server = mcpServers == null ? void 0 : mcpServers[index];
1473
+ const failingUrl = typeof (server == null ? void 0 : server.url) === "string" ? server.url : `mcp[${index}]`;
1474
+ console.error(
1475
+ `Network or parsing error fetching tools from ${failingUrl}:`,
1476
+ result.reason
1477
+ );
1478
+ return [];
1479
+ });
1480
+ const failedCount = settledResults.filter(
1481
+ (result) => result.status === "rejected"
1482
+ ).length;
1455
1483
  setToolList(allTools);
1456
- setToolsFetchError(false);
1484
+ setToolsFetchError(failedCount > 0 && allTools.length === 0);
1457
1485
  } catch (error2) {
1458
1486
  console.error(
1459
1487
  "An error occurred while processing tool fetches:",
@@ -4797,6 +4825,87 @@ var buildThinkingBlockMarker = (type, signature) => {
4797
4825
  normalizedSignature
4798
4826
  )}${INLINE_THINKING_MARKER_SUFFIX}`;
4799
4827
  };
4828
+ var PLAN_STEP_REGEX = /^\s*\[plan-step\]\s*([a-zA-Z_-]+)\s*:\s*(.+?)\s*$/i;
4829
+ var normalizePlanningStatus = (value) => {
4830
+ const normalized = String(value || "").trim().toLowerCase();
4831
+ if (normalized === "doing" || normalized === "in_progress" || normalized === "in-progress") {
4832
+ return "doing";
4833
+ }
4834
+ if (normalized === "done" || normalized === "completed") {
4835
+ return "done";
4836
+ }
4837
+ return "todo";
4838
+ };
4839
+ var formatPlanningStatus = (status) => {
4840
+ if (status === "doing") return "Doing";
4841
+ if (status === "done") return "Done";
4842
+ return "Todo";
4843
+ };
4844
+ var parsePlanningStep = (line) => {
4845
+ const match = PLAN_STEP_REGEX.exec(String(line || ""));
4846
+ if (!match) return null;
4847
+ const title = String(match[2] || "").trim();
4848
+ if (!title) return null;
4849
+ return {
4850
+ status: normalizePlanningStatus(match[1] || ""),
4851
+ title
4852
+ };
4853
+ };
4854
+ var buildPlanningBlockContent = (steps) => {
4855
+ return steps.filter((step) => !!step && typeof step.title === "string" && step.title.trim().length > 0).map((step) => `${formatPlanningStatus(step.status)}: ${step.title.trim()}`).join("\n");
4856
+ };
4857
+ var extractPlanningBlocks = (text) => {
4858
+ const source = typeof text === "string" ? text : "";
4859
+ if (!source) {
4860
+ return {
4861
+ textWithMarkers: "",
4862
+ planningBlocks: []
4863
+ };
4864
+ }
4865
+ const lines = source.split("\n");
4866
+ const planningBlocks = [];
4867
+ const rebuiltSegments = [];
4868
+ let currentOffset = 0;
4869
+ let pendingSteps = [];
4870
+ let pendingIndex = 0;
4871
+ const flushPendingPlanning = () => {
4872
+ if (pendingSteps.length === 0) return;
4873
+ const signature = `planning-${pendingIndex}`;
4874
+ planningBlocks.push({
4875
+ type: "planning",
4876
+ content: buildPlanningBlockContent(pendingSteps),
4877
+ index: pendingIndex,
4878
+ signature,
4879
+ steps: pendingSteps
4880
+ });
4881
+ rebuiltSegments.push(`
4882
+
4883
+ ${buildThinkingBlockMarker("planning", signature)}
4884
+
4885
+ `);
4886
+ pendingSteps = [];
4887
+ };
4888
+ lines.forEach((line, index) => {
4889
+ const lineWithNewline = index < lines.length - 1 ? `${line}
4890
+ ` : line;
4891
+ const planningStep = parsePlanningStep(line);
4892
+ if (planningStep) {
4893
+ if (pendingSteps.length === 0) {
4894
+ pendingIndex = currentOffset;
4895
+ }
4896
+ pendingSteps = [...pendingSteps, planningStep];
4897
+ } else {
4898
+ flushPendingPlanning();
4899
+ rebuiltSegments.push(lineWithNewline);
4900
+ }
4901
+ currentOffset += lineWithNewline.length;
4902
+ });
4903
+ flushPendingPlanning();
4904
+ return {
4905
+ textWithMarkers: rebuiltSegments.join(""),
4906
+ planningBlocks
4907
+ };
4908
+ };
4800
4909
  var buildInlineToolMarker = (toolName, callId) => {
4801
4910
  const normalizedToolName = String(toolName || "").trim() || "tool";
4802
4911
  const normalizedCallId = String(callId || "").trim() || `${normalizedToolName}-call`;
@@ -4862,7 +4971,7 @@ var parseInlineThinkingMarkers = (text) => {
4862
4971
  signature = encodedSignature;
4863
4972
  }
4864
4973
  const normalizedType = String(rawType || "").trim().toLowerCase();
4865
- const type = normalizedType === "reasoning" ? "reasoning" : normalizedType === "searching" ? "searching" : "thinking";
4974
+ const type = normalizedType === "reasoning" ? "reasoning" : normalizedType === "searching" ? "searching" : normalizedType === "planning" ? "planning" : "thinking";
4866
4975
  markers.push({
4867
4976
  type,
4868
4977
  signature: String(signature || "").trim()
@@ -5263,6 +5372,7 @@ var AIChatPanel = ({
5263
5372
  const [lastPrompt, setLastPrompt] = (0, import_react14.useState)(null);
5264
5373
  const [lastKey, setLastKey] = (0, import_react14.useState)(null);
5265
5374
  const [currentConversation, setCurrentConversation] = (0, import_react14.useState)(conversation);
5375
+ const ensuredConversationIdsRef = (0, import_react14.useRef)(/* @__PURE__ */ new Set());
5266
5376
  const [followOnQuestionsState, setFollowOnQuestionsState] = (0, import_react14.useState)(followOnQuestions);
5267
5377
  const [thinkingBlocks, setThinkingBlocks] = (0, import_react14.useState)([]);
5268
5378
  const [currentThinkingIndex, setCurrentThinkingIndex] = (0, import_react14.useState)(0);
@@ -5418,7 +5528,7 @@ var AIChatPanel = ({
5418
5528
  return;
5419
5529
  }
5420
5530
  try {
5421
- const enriched = yield Promise.all(
5531
+ const settled = yield Promise.allSettled(
5422
5532
  mcpServers.map((server) => __async(void 0, null, function* () {
5423
5533
  const resolved = yield resolveMcpAuthHeaders({
5424
5534
  phase: "list",
@@ -5433,6 +5543,18 @@ var AIChatPanel = ({
5433
5543
  });
5434
5544
  }))
5435
5545
  );
5546
+ const enriched = settled.map((result, index) => {
5547
+ var _a2;
5548
+ if (result.status === "fulfilled") {
5549
+ return result.value;
5550
+ }
5551
+ const failingUrl = typeof ((_a2 = mcpServers == null ? void 0 : mcpServers[index]) == null ? void 0 : _a2.url) === "string" ? mcpServers[index].url : `mcp[${index}]`;
5552
+ console.error(
5553
+ `[AIChatPanel] Failed to resolve MCP auth headers for ${failingUrl}:`,
5554
+ result.reason
5555
+ );
5556
+ return mcpServers[index];
5557
+ });
5436
5558
  if (!cancelled) setResolvedMcpServers(enriched);
5437
5559
  } catch (error2) {
5438
5560
  console.error("[AIChatPanel] Failed to resolve MCP auth headers:", error2);
@@ -5524,10 +5646,22 @@ var AIChatPanel = ({
5524
5646
  headers: requestHeaders
5525
5647
  }));
5526
5648
  }));
5527
- const results = yield Promise.all(fetchPromises);
5528
- const allTools = results.flat();
5649
+ const settled = yield Promise.allSettled(fetchPromises);
5650
+ const allTools = settled.flatMap((result, index) => {
5651
+ var _a2;
5652
+ if (result.status === "fulfilled") {
5653
+ return result.value;
5654
+ }
5655
+ const failingUrl = typeof ((_a2 = resolvedMcpServers == null ? void 0 : resolvedMcpServers[index]) == null ? void 0 : _a2.url) === "string" ? resolvedMcpServers[index].url : `mcp[${index}]`;
5656
+ console.error(
5657
+ `[AIChatPanel] Failed to load MCP tools from ${failingUrl}:`,
5658
+ result.reason
5659
+ );
5660
+ return [];
5661
+ });
5662
+ const failedCount = settled.filter((result) => result.status === "rejected").length;
5529
5663
  setToolList(allTools);
5530
- setToolsFetchError(false);
5664
+ setToolsFetchError(failedCount > 0 && allTools.length === 0);
5531
5665
  } catch (error2) {
5532
5666
  console.error("[AIChatPanel] Failed to load MCP tools:", error2);
5533
5667
  setToolList([]);
@@ -5598,22 +5732,25 @@ var AIChatPanel = ({
5598
5732
  }, [propOnToggleSection]);
5599
5733
  const ensureConversation = (0, import_react14.useCallback)(() => {
5600
5734
  const normalizedConversationId = typeof currentConversation === "string" ? currentConversation.trim() : "";
5735
+ const hasConversationHistory = Object.keys(latestHistoryRef.current || {}).length > 0;
5736
+ const shouldInitializeProvidedConversation = normalizedConversationId.length > 10 && createConversationOnFirstChat && !hasConversationHistory && !ensuredConversationIdsRef.current.has(normalizedConversationId);
5601
5737
  console.log("ensureConversation - called with:", {
5602
5738
  currentConversation: normalizedConversationId || null,
5603
5739
  createConversationOnFirstChat,
5740
+ shouldInitializeProvidedConversation,
5604
5741
  project_id,
5605
5742
  publicAPIUrl
5606
5743
  });
5607
- if (normalizedConversationId) {
5744
+ if (normalizedConversationId && !shouldInitializeProvidedConversation) {
5608
5745
  console.log("ensureConversation - using existing conversation:", normalizedConversationId);
5609
5746
  return Promise.resolve(normalizedConversationId);
5610
5747
  }
5611
5748
  if (!createConversationOnFirstChat) {
5612
- return Promise.resolve("");
5749
+ return Promise.resolve(normalizedConversationId || "");
5613
5750
  }
5614
5751
  if (!project_id) {
5615
5752
  console.error("ensureConversation - Cannot create conversation without project_id");
5616
- return Promise.resolve("");
5753
+ return Promise.resolve(normalizedConversationId || "");
5617
5754
  }
5618
5755
  const createConversation = () => {
5619
5756
  var _a2, _b;
@@ -5625,6 +5762,9 @@ var AIChatPanel = ({
5625
5762
  timezone: browserInfo == null ? void 0 : browserInfo.userTimezone,
5626
5763
  language: browserInfo == null ? void 0 : browserInfo.userLanguage
5627
5764
  };
5765
+ if (shouldInitializeProvidedConversation) {
5766
+ requestBody.conversationId = normalizedConversationId;
5767
+ }
5628
5768
  console.log("ensureConversation - Creating conversation with:", requestBody);
5629
5769
  console.log("ensureConversation - API URL:", `${publicAPIUrl}/conversations`);
5630
5770
  return fetch(`${publicAPIUrl}/conversations`, {
@@ -5645,16 +5785,31 @@ var AIChatPanel = ({
5645
5785
  var _a3;
5646
5786
  console.log("ensureConversation - API response:", newConvo);
5647
5787
  const createdId = typeof (newConvo == null ? void 0 : newConvo.id) === "string" && newConvo.id.trim() || typeof (newConvo == null ? void 0 : newConvo.conversationId) === "string" && newConvo.conversationId.trim() || typeof (newConvo == null ? void 0 : newConvo.conversation_id) === "string" && newConvo.conversation_id.trim() || typeof ((_a3 = newConvo == null ? void 0 : newConvo.conversation) == null ? void 0 : _a3.id) === "string" && newConvo.conversation.id.trim() || "";
5788
+ const resolvedConversationId = normalizedConversationId || createdId;
5648
5789
  if (createdId) {
5649
5790
  console.log("ensureConversation - New conversation ID:", createdId);
5791
+ }
5792
+ if (resolvedConversationId) {
5793
+ ensuredConversationIdsRef.current.add(resolvedConversationId);
5794
+ }
5795
+ if (normalizedConversationId && createdId && createdId !== normalizedConversationId) {
5796
+ console.warn(
5797
+ "ensureConversation - API returned a different ID than supplied conversationId; keeping caller-provided ID",
5798
+ { suppliedConversationId: normalizedConversationId, returnedConversationId: createdId }
5799
+ );
5800
+ }
5801
+ if (!normalizedConversationId && createdId) {
5650
5802
  setCurrentConversation(createdId);
5651
5803
  return createdId;
5652
5804
  }
5805
+ if (resolvedConversationId) {
5806
+ return resolvedConversationId;
5807
+ }
5653
5808
  console.warn("ensureConversation - No ID in response");
5654
- return "";
5809
+ return normalizedConversationId || "";
5655
5810
  }).catch((error2) => {
5656
5811
  console.error("Error creating new conversation", error2);
5657
- return "";
5812
+ return normalizedConversationId || "";
5658
5813
  });
5659
5814
  };
5660
5815
  return createConversation();
@@ -6438,6 +6593,13 @@ ${buildThinkingBlockMarker(type, signature)}
6438
6593
  `;
6439
6594
  }
6440
6595
  );
6596
+ const {
6597
+ textWithMarkers: textWithArtifactMarkers,
6598
+ planningBlocks
6599
+ } = extractPlanningBlocks(textWithCompleteMarkers);
6600
+ if (planningBlocks.length > 0) {
6601
+ completedBlocks.push(...planningBlocks);
6602
+ }
6441
6603
  let activeBlock = null;
6442
6604
  const tagTypes = ["thinking", "reasoning", "searching"];
6443
6605
  let latestIncompletePos = -1;
@@ -6467,7 +6629,7 @@ ${buildThinkingBlockMarker(type, signature)}
6467
6629
  }
6468
6630
  }
6469
6631
  }
6470
- let cleanedText = textWithCompleteMarkers.replace(/<think(?:i(?:n(?:g)?)?)?$/i, "").replace(/<reas(?:o(?:n(?:i(?:n(?:g)?)?)?)?)?$/i, "").replace(/<sear(?:c(?:h(?:i(?:n(?:g)?)?)?)?)?$/i, "").replace(/<thinking>[\s\S]*$/i, "").replace(/<reasoning>[\s\S]*$/i, "").replace(/<searching>[\s\S]*$/i, "").trim();
6632
+ let cleanedText = textWithArtifactMarkers.replace(/<think(?:i(?:n(?:g)?)?)?$/i, "").replace(/<reas(?:o(?:n(?:i(?:n(?:g)?)?)?)?)?$/i, "").replace(/<sear(?:c(?:h(?:i(?:n(?:g)?)?)?)?)?$/i, "").replace(/<thinking>[\s\S]*$/i, "").replace(/<reasoning>[\s\S]*$/i, "").replace(/<searching>[\s\S]*$/i, "").trim();
6471
6633
  let lastThinkingContent = "Thinking";
6472
6634
  if (completedBlocks.length > 0) {
6473
6635
  const lastBlock = completedBlocks[completedBlocks.length - 1];
@@ -6490,6 +6652,13 @@ ${buildThinkingBlockMarker(type, signature)}
6490
6652
  if (incomingSupersetPrefix) {
6491
6653
  return incoming;
6492
6654
  }
6655
+ const incomingSignaturePrefix = incoming.length >= existing.length && existing.every((block, index) => {
6656
+ const next = incoming[index];
6657
+ return !!next && next.type === block.type && next.signature === block.signature;
6658
+ });
6659
+ if (incomingSignaturePrefix) {
6660
+ return incoming;
6661
+ }
6493
6662
  const merged = [...existing];
6494
6663
  const seen = new Set(existing.map((block) => block.signature || `${block.type}::${block.content}`));
6495
6664
  for (const block of incoming) {
@@ -6708,7 +6877,6 @@ ${traceSummary}` : traceSummary;
6708
6877
  hasAutoCollapsedRef.current = false;
6709
6878
  prevBlockCountRef.current = 0;
6710
6879
  setError(null);
6711
- lastProcessedErrorRef.current = null;
6712
6880
  setUserHasScrolled(false);
6713
6881
  prevResponseLengthRef.current = 0;
6714
6882
  setResponse("");
@@ -6994,6 +7162,7 @@ ${traceSummary}` : traceSummary;
6994
7162
  });
6995
7163
  prevBlockCountRef.current = mergedBlocks.length;
6996
7164
  }
7165
+ const planningBlocks = mergedBlocks.filter((block) => block.type === "planning");
6997
7166
  setHistory((prev) => {
6998
7167
  const newHistory = __spreadValues({}, prev);
6999
7168
  const existingEntry = newHistory[lastKey] || { content: "", callId: "" };
@@ -7004,7 +7173,9 @@ ${traceSummary}` : traceSummary;
7004
7173
  newHistory[lastKey] = __spreadProps(__spreadValues({}, existingEntry), {
7005
7174
  content: nextContent,
7006
7175
  // Store raw content without tool JSON or thinking tags
7007
- callId: lastCallId || existingEntry.callId || ""
7176
+ callId: lastCallId || existingEntry.callId || "",
7177
+ artifactBlocks: mergedBlocks,
7178
+ planningBlocks
7008
7179
  });
7009
7180
  latestHistoryRef.current = newHistory;
7010
7181
  return newHistory;
@@ -7148,69 +7319,74 @@ ${traceSummary}` : traceSummary;
7148
7319
  }
7149
7320
  }, [followOnPrompt, continueChat]);
7150
7321
  (0, import_react14.useEffect)(() => {
7151
- if (llmError && llmError.trim()) {
7152
- if (lastProcessedErrorRef.current === llmError) {
7153
- console.log("[AIChatPanel] Skipping duplicate error:", llmError);
7154
- return;
7155
- }
7156
- console.log("[AIChatPanel] Error detected:", llmError);
7157
- lastProcessedErrorRef.current = llmError;
7158
- const errorMessage = llmError;
7159
- const isAbortError = errorMessage.toLowerCase().includes("abort") || errorMessage.toLowerCase().includes("canceled") || errorMessage.toLowerCase().includes("cancelled");
7160
- if (isAbortError) {
7161
- console.log("[AIChatPanel] Request was aborted by user (useEffect)");
7162
- } else if (errorMessage.includes("413") || errorMessage.toLowerCase().includes("content too large")) {
7163
- setError({
7164
- message: "The context is too large to process. Please start a new conversation or reduce the amount of context.",
7165
- code: "413"
7166
- });
7167
- if (lastKey) {
7168
- setHistory((prev) => {
7169
- const existingEntry = prev[lastKey] || { content: "", callId: "" };
7170
- return __spreadProps(__spreadValues({}, prev), {
7171
- [lastKey]: __spreadProps(__spreadValues({}, existingEntry), {
7172
- content: `Error: ${errorMessage}`,
7173
- callId: lastCallId || existingEntry.callId || ""
7174
- })
7175
- });
7322
+ const normalizedError = typeof llmError === "string" ? llmError.trim() : "";
7323
+ if (!normalizedError) {
7324
+ lastProcessedErrorRef.current = null;
7325
+ return;
7326
+ }
7327
+ if (lastProcessedErrorRef.current === normalizedError) {
7328
+ console.log("[AIChatPanel] Skipping duplicate error:", normalizedError);
7329
+ return;
7330
+ }
7331
+ console.log("[AIChatPanel] Error detected:", normalizedError);
7332
+ lastProcessedErrorRef.current = normalizedError;
7333
+ const errorMessage = normalizedError;
7334
+ const currentLastKey = lastKeyRef.current;
7335
+ const currentLastCallId = lastCallIdRef.current;
7336
+ const isAbortError = errorMessage.toLowerCase().includes("abort") || errorMessage.toLowerCase().includes("canceled") || errorMessage.toLowerCase().includes("cancelled");
7337
+ if (isAbortError) {
7338
+ console.log("[AIChatPanel] Request was aborted by user (useEffect)");
7339
+ } else if (errorMessage.includes("413") || errorMessage.toLowerCase().includes("content too large")) {
7340
+ setError({
7341
+ message: "The context is too large to process. Please start a new conversation or reduce the amount of context.",
7342
+ code: "413"
7343
+ });
7344
+ if (currentLastKey) {
7345
+ setHistory((prev) => {
7346
+ const existingEntry = prev[currentLastKey] || { content: "", callId: "" };
7347
+ return __spreadProps(__spreadValues({}, prev), {
7348
+ [currentLastKey]: __spreadProps(__spreadValues({}, existingEntry), {
7349
+ content: `Error: ${errorMessage}`,
7350
+ callId: currentLastCallId || existingEntry.callId || ""
7351
+ })
7176
7352
  });
7177
- }
7178
- } else if (errorMessage.toLowerCase().includes("network error") || errorMessage.toLowerCase().includes("fetch")) {
7179
- setError({
7180
- message: "Network error. Please check your connection and try again.",
7181
- code: "NETWORK_ERROR"
7182
7353
  });
7183
- if (lastKey) {
7184
- setHistory((prev) => {
7185
- const existingEntry = prev[lastKey] || { content: "", callId: "" };
7186
- return __spreadProps(__spreadValues({}, prev), {
7187
- [lastKey]: __spreadProps(__spreadValues({}, existingEntry), {
7188
- content: `Error: ${errorMessage}`,
7189
- callId: lastCallId || existingEntry.callId || ""
7190
- })
7191
- });
7354
+ }
7355
+ } else if (errorMessage.toLowerCase().includes("network error") || errorMessage.toLowerCase().includes("fetch")) {
7356
+ setError({
7357
+ message: "Network error. Please check your connection and try again.",
7358
+ code: "NETWORK_ERROR"
7359
+ });
7360
+ if (currentLastKey) {
7361
+ setHistory((prev) => {
7362
+ const existingEntry = prev[currentLastKey] || { content: "", callId: "" };
7363
+ return __spreadProps(__spreadValues({}, prev), {
7364
+ [currentLastKey]: __spreadProps(__spreadValues({}, existingEntry), {
7365
+ content: `Error: ${errorMessage}`,
7366
+ callId: currentLastCallId || existingEntry.callId || ""
7367
+ })
7192
7368
  });
7193
- }
7194
- } else {
7195
- setError({
7196
- message: errorMessage,
7197
- code: "UNKNOWN_ERROR"
7198
7369
  });
7199
- if (lastKey) {
7200
- setHistory((prev) => {
7201
- const existingEntry = prev[lastKey] || { content: "", callId: "" };
7202
- return __spreadProps(__spreadValues({}, prev), {
7203
- [lastKey]: __spreadProps(__spreadValues({}, existingEntry), {
7204
- content: `Error: ${errorMessage}`,
7205
- callId: lastCallId || existingEntry.callId || ""
7206
- })
7207
- });
7370
+ }
7371
+ } else {
7372
+ setError({
7373
+ message: errorMessage,
7374
+ code: "UNKNOWN_ERROR"
7375
+ });
7376
+ if (currentLastKey) {
7377
+ setHistory((prev) => {
7378
+ const existingEntry = prev[currentLastKey] || { content: "", callId: "" };
7379
+ return __spreadProps(__spreadValues({}, prev), {
7380
+ [currentLastKey]: __spreadProps(__spreadValues({}, existingEntry), {
7381
+ content: `Error: ${errorMessage}`,
7382
+ callId: currentLastCallId || existingEntry.callId || ""
7383
+ })
7208
7384
  });
7209
- }
7385
+ });
7210
7386
  }
7211
- setIsLoading(false);
7212
7387
  }
7213
- }, [llmError, lastKey, lastCallId]);
7388
+ setIsLoading(false);
7389
+ }, [llmError]);
7214
7390
  (0, import_react14.useEffect)(() => {
7215
7391
  const existingLinks = document.querySelectorAll(
7216
7392
  'link[data-source="ai-chat-panel"]'
@@ -8468,6 +8644,61 @@ var truncatePromptForTitle = (prompt) => {
8468
8644
  }
8469
8645
  return prompt;
8470
8646
  };
8647
+ var stripHistoryPromptTimestamp = (prompt) => {
8648
+ const isoMatch = prompt.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z:(.+)/);
8649
+ if (isoMatch && isoMatch[1]) {
8650
+ return isoMatch[1];
8651
+ }
8652
+ return prompt.replace(/^\d+:/, "");
8653
+ };
8654
+ var upsertConversationSummary = (conversations, summary) => {
8655
+ const normalizedSummary = normalizeConversationListPayload([summary])[0];
8656
+ if (!normalizedSummary) {
8657
+ return conversations;
8658
+ }
8659
+ const dedupedById = /* @__PURE__ */ new Map();
8660
+ for (const conv of conversations) {
8661
+ dedupedById.set(conv.conversationId, conv);
8662
+ }
8663
+ const existing = dedupedById.get(normalizedSummary.conversationId);
8664
+ const existingUpdatedAt = existing ? new Date(existing.updatedAt).getTime() : 0;
8665
+ const incomingUpdatedAt = new Date(normalizedSummary.updatedAt).getTime();
8666
+ const preferIncoming = !existing || incomingUpdatedAt >= existingUpdatedAt;
8667
+ const latest = preferIncoming ? normalizedSummary : existing;
8668
+ const fallback = preferIncoming ? existing : normalizedSummary;
8669
+ dedupedById.set(normalizedSummary.conversationId, __spreadProps(__spreadValues(__spreadValues({}, fallback || {}), latest || {}), {
8670
+ createdAt: (existing == null ? void 0 : existing.createdAt) || normalizedSummary.createdAt,
8671
+ updatedAt: (latest == null ? void 0 : latest.updatedAt) || (fallback == null ? void 0 : fallback.updatedAt) || normalizedSummary.updatedAt,
8672
+ title: (latest == null ? void 0 : latest.title) || (fallback == null ? void 0 : fallback.title) || "",
8673
+ summary: (latest == null ? void 0 : latest.summary) || (fallback == null ? void 0 : fallback.summary),
8674
+ agentId: (latest == null ? void 0 : latest.agentId) || (fallback == null ? void 0 : fallback.agentId),
8675
+ messageCount: typeof (latest == null ? void 0 : latest.messageCount) === "number" ? latest.messageCount : fallback == null ? void 0 : fallback.messageCount
8676
+ }));
8677
+ return Array.from(dedupedById.values()).sort(
8678
+ (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
8679
+ );
8680
+ };
8681
+ var buildOptimisticConversationSummary = (conversation) => {
8682
+ const conversationId = typeof conversation.conversationId === "string" ? conversation.conversationId.trim() : "";
8683
+ if (!conversationId || conversationId.startsWith("new-")) {
8684
+ return null;
8685
+ }
8686
+ const historyKeys = Object.keys(conversation.history || {});
8687
+ if (historyKeys.length === 0 && conversation.title === "New conversation") {
8688
+ return null;
8689
+ }
8690
+ const firstPrompt = historyKeys[0] ? stripHistoryPromptTimestamp(historyKeys[0]) : "";
8691
+ const title = truncatePromptForTitle(firstPrompt || conversation.title || "New conversation");
8692
+ const now = (/* @__PURE__ */ new Date()).toISOString();
8693
+ return {
8694
+ conversationId,
8695
+ title,
8696
+ createdAt: now,
8697
+ updatedAt: now,
8698
+ agentId: conversation.agentId,
8699
+ messageCount: historyKeys.length > 0 ? historyKeys.length * 2 : void 0
8700
+ };
8701
+ };
8471
8702
  var EMPTY_ARRAY = [];
8472
8703
  var EMPTY_HISTORY = {};
8473
8704
  var ChatPanelWrapper = ({
@@ -8871,9 +9102,13 @@ var AIAgentPanel = import_react16.default.forwardRef(({
8871
9102
  buildAgentAwarenessInstructions,
8872
9103
  agentList
8873
9104
  } = useAgentRegistry(agentIds, { url, localOverrides });
9105
+ const getAgentRef = (0, import_react16.useRef)(getAgent);
9106
+ (0, import_react16.useEffect)(() => {
9107
+ getAgentRef.current = getAgent;
9108
+ }, [getAgent]);
8874
9109
  const fetchInProgressRef = (0, import_react16.useRef)(false);
8875
9110
  const loadingTranscriptIdsRef = (0, import_react16.useRef)(/* @__PURE__ */ new Set());
8876
- const lastFetchedAgentRef = (0, import_react16.useRef)(null);
9111
+ const [conversationListRefreshNonce, setConversationListRefreshNonce] = (0, import_react16.useState)(0);
8877
9112
  const checkedPromptsRef = (0, import_react16.useRef)(/* @__PURE__ */ new Set());
8878
9113
  const fetchingPromptsRef = (0, import_react16.useRef)(/* @__PURE__ */ new Set());
8879
9114
  const failedPromptsRef = (0, import_react16.useRef)(/* @__PURE__ */ new Map());
@@ -8881,6 +9116,34 @@ var AIAgentPanel = import_react16.default.forwardRef(({
8881
9116
  activeConversationsRef.current = activeConversations;
8882
9117
  const currentConversationIdRef = (0, import_react16.useRef)(currentConversationId);
8883
9118
  currentConversationIdRef.current = currentConversationId;
9119
+ const controlledConversationIdRef = (0, import_react16.useRef)(controlledConversationId);
9120
+ controlledConversationIdRef.current = controlledConversationId;
9121
+ const requestConversationListRefresh = (0, import_react16.useCallback)(() => {
9122
+ setConversationListRefreshNonce((prev) => prev + 1);
9123
+ }, []);
9124
+ const upsertApiConversationFromHistory = (0, import_react16.useCallback)(
9125
+ (conversationId, history, fallbackTitle) => {
9126
+ const firstPromptKey = Object.keys(history)[0];
9127
+ const firstPrompt = firstPromptKey ? stripHistoryPromptTimestamp(firstPromptKey) : "";
9128
+ const title = truncatePromptForTitle(firstPrompt || fallbackTitle || "New conversation");
9129
+ const messageCount = Object.keys(history).length * 2;
9130
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
9131
+ setApiConversations(
9132
+ (prev) => {
9133
+ var _a2, _b2;
9134
+ return upsertConversationSummary(prev, {
9135
+ conversationId,
9136
+ title,
9137
+ createdAt: ((_a2 = prev.find((conversation2) => conversation2.conversationId === conversationId)) == null ? void 0 : _a2.createdAt) || timestamp,
9138
+ updatedAt: timestamp,
9139
+ agentId: ((_b2 = prev.find((conversation2) => conversation2.conversationId === conversationId)) == null ? void 0 : _b2.agentId) || currentAgentId,
9140
+ messageCount
9141
+ });
9142
+ }
9143
+ );
9144
+ },
9145
+ [currentAgentId]
9146
+ );
8884
9147
  const commitConversationSelection = (0, import_react16.useCallback)(
8885
9148
  (conversationId, notifyChange) => {
8886
9149
  const shouldSyncLocalState = !isConversationControlled || !notifyChange || controlledConversationId === conversationId;
@@ -8948,7 +9211,7 @@ var AIAgentPanel = import_react16.default.forwardRef(({
8948
9211
  fetchingPromptsRef.current.add(conversationId);
8949
9212
  setLoadingPrompts((prev) => new Set(prev).add(conversationId));
8950
9213
  const agentIdToUse = agentIdForConversation || currentAgentId;
8951
- const agentProfile = getAgent(agentIdToUse);
9214
+ const agentProfile = getAgentRef.current(agentIdToUse);
8952
9215
  const projectId = (_a2 = agentProfile == null ? void 0 : agentProfile.metadata) == null ? void 0 : _a2.projectId;
8953
9216
  if (!apiKey || !projectId) {
8954
9217
  fetchingPromptsRef.current.delete(conversationId);
@@ -9013,11 +9276,8 @@ var AIAgentPanel = import_react16.default.forwardRef(({
9013
9276
  return next;
9014
9277
  });
9015
9278
  }
9016
- }), [apiKey, currentAgentId, getAgent]);
9017
- const fetchConversations = (0, import_react16.useCallback)((agentId, signal) => __async(void 0, null, function* () {
9018
- var _a2;
9019
- const agentProfile = getAgent(agentId);
9020
- const projectId = (_a2 = agentProfile == null ? void 0 : agentProfile.metadata) == null ? void 0 : _a2.projectId;
9279
+ }), [apiKey, currentAgentId]);
9280
+ const fetchConversations = (0, import_react16.useCallback)((agentId, projectId, signal) => __async(void 0, null, function* () {
9021
9281
  if (!agentId || !apiKey || !projectId) {
9022
9282
  setApiConversations([]);
9023
9283
  return;
@@ -9043,7 +9303,38 @@ var AIAgentPanel = import_react16.default.forwardRef(({
9043
9303
  const payload = yield response.json();
9044
9304
  const normalized = normalizeConversationListPayload(payload);
9045
9305
  if (!(signal == null ? void 0 : signal.aborted)) {
9046
- setApiConversations(normalized);
9306
+ setApiConversations((prev) => {
9307
+ const next = [...normalized];
9308
+ const optimisticIds = /* @__PURE__ */ new Set();
9309
+ const trackedConversationId = controlledConversationIdRef.current;
9310
+ if (trackedConversationId) {
9311
+ optimisticIds.add(trackedConversationId);
9312
+ }
9313
+ activeConversationsRef.current.forEach((conversation2) => {
9314
+ const optimisticSummary = buildOptimisticConversationSummary(conversation2);
9315
+ if (!optimisticSummary) {
9316
+ return;
9317
+ }
9318
+ optimisticIds.add(optimisticSummary.conversationId);
9319
+ const existing = prev.find(
9320
+ (candidate) => candidate.conversationId === optimisticSummary.conversationId
9321
+ );
9322
+ next.push(__spreadProps(__spreadValues({}, optimisticSummary), {
9323
+ createdAt: (existing == null ? void 0 : existing.createdAt) || optimisticSummary.createdAt,
9324
+ updatedAt: optimisticSummary.updatedAt
9325
+ }));
9326
+ });
9327
+ let merged = normalizeConversationListPayload(next);
9328
+ if (optimisticIds.size > 0) {
9329
+ prev.forEach((conversation2) => {
9330
+ if (!optimisticIds.has(conversation2.conversationId)) {
9331
+ return;
9332
+ }
9333
+ merged = upsertConversationSummary(merged, conversation2);
9334
+ });
9335
+ }
9336
+ return merged;
9337
+ });
9047
9338
  const now = /* @__PURE__ */ new Date();
9048
9339
  const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
9049
9340
  const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1e3);
@@ -9064,7 +9355,7 @@ var AIAgentPanel = import_react16.default.forwardRef(({
9064
9355
  setConversationsLoading(false);
9065
9356
  }
9066
9357
  }
9067
- }), [apiKey, customerId, getAgent, fetchFirstPrompt]);
9358
+ }), [apiKey, customerId, fetchFirstPrompt]);
9068
9359
  const loadConversationTranscript = (0, import_react16.useCallback)((conversationId, agentIdForConversation, title, notifyConversationChange = true) => __async(void 0, null, function* () {
9069
9360
  var _a2;
9070
9361
  const existingActive = activeConversationsRef.current.get(conversationId);
@@ -9077,7 +9368,7 @@ var AIAgentPanel = import_react16.default.forwardRef(({
9077
9368
  return;
9078
9369
  }
9079
9370
  const agentIdToUse = agentIdForConversation || currentAgentId;
9080
- const agentProfile = getAgent(agentIdToUse);
9371
+ const agentProfile = getAgentRef.current(agentIdToUse);
9081
9372
  const projectId = (_a2 = agentProfile == null ? void 0 : agentProfile.metadata) == null ? void 0 : _a2.projectId;
9082
9373
  if (!apiKey || !projectId) {
9083
9374
  setConversationsError("Missing API key or project ID");
@@ -9153,7 +9444,7 @@ var AIAgentPanel = import_react16.default.forwardRef(({
9153
9444
  loadingTranscriptIdsRef.current.delete(conversationId);
9154
9445
  setLoadingConversationId((prev) => prev === conversationId ? null : prev);
9155
9446
  }
9156
- }), [apiKey, commitConversationSelection, conversationFirstPrompts, currentAgentId, getAgent]);
9447
+ }), [apiKey, commitConversationSelection, conversationFirstPrompts, currentAgentId]);
9157
9448
  (0, import_react16.useEffect)(() => {
9158
9449
  if (!isConversationControlled) return;
9159
9450
  const targetConversationId = controlledConversationId;
@@ -9218,21 +9509,32 @@ var AIAgentPanel = import_react16.default.forwardRef(({
9218
9509
  loadConversationTranscript,
9219
9510
  loadingConversationId
9220
9511
  ]);
9512
+ const currentAgentProjectId = (0, import_react16.useMemo)(() => {
9513
+ var _a2;
9514
+ const agentProfile = getAgent(currentAgentId);
9515
+ return ((_a2 = agentProfile == null ? void 0 : agentProfile.metadata) == null ? void 0 : _a2.projectId) || "";
9516
+ }, [currentAgentId, getAgent]);
9221
9517
  const handleRefreshConversations = (0, import_react16.useCallback)(() => {
9222
- fetchConversations(currentAgentId);
9223
- }, [currentAgentId, fetchConversations]);
9518
+ requestConversationListRefresh();
9519
+ }, [requestConversationListRefresh]);
9224
9520
  (0, import_react16.useEffect)(() => {
9225
- var _a2;
9226
9521
  if (!showConversationHistory) return;
9227
9522
  if (!agentsLoading && currentAgentId && apiKey) {
9228
- const agentProfile = getAgent(currentAgentId);
9229
- const projectId = (_a2 = agentProfile == null ? void 0 : agentProfile.metadata) == null ? void 0 : _a2.projectId;
9230
- if (projectId && lastFetchedAgentRef.current !== currentAgentId) {
9231
- lastFetchedAgentRef.current = currentAgentId;
9232
- fetchConversations(currentAgentId);
9523
+ if (currentAgentProjectId) {
9524
+ const controller = new AbortController();
9525
+ fetchConversations(currentAgentId, currentAgentProjectId, controller.signal);
9526
+ return () => controller.abort();
9233
9527
  }
9234
9528
  }
9235
- }, [agentsLoading, currentAgentId, apiKey, fetchConversations, getAgent, showConversationHistory]);
9529
+ }, [
9530
+ agentsLoading,
9531
+ apiKey,
9532
+ conversationListRefreshNonce,
9533
+ currentAgentId,
9534
+ currentAgentProjectId,
9535
+ fetchConversations,
9536
+ showConversationHistory
9537
+ ]);
9236
9538
  const handleNewConversation = (0, import_react16.useCallback)(() => {
9237
9539
  const tempId = createDraftConversation(currentAgentId);
9238
9540
  commitConversationSelection(tempId, true);
@@ -9556,11 +9858,7 @@ var AIAgentPanel = import_react16.default.forwardRef(({
9556
9858
  if (title === "New conversation" && Object.keys(history).length > 0) {
9557
9859
  const firstPrompt = Object.keys(history)[0];
9558
9860
  if (firstPrompt) {
9559
- let cleanPrompt = firstPrompt;
9560
- const isoMatch = cleanPrompt.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z:(.+)/);
9561
- if (isoMatch && isoMatch[1]) {
9562
- cleanPrompt = isoMatch[1];
9563
- }
9861
+ const cleanPrompt = stripHistoryPromptTimestamp(firstPrompt);
9564
9862
  title = cleanPrompt.length > 60 ? cleanPrompt.slice(0, 57) + "..." : cleanPrompt;
9565
9863
  }
9566
9864
  }
@@ -9573,6 +9871,12 @@ var AIAgentPanel = import_react16.default.forwardRef(({
9573
9871
  }
9574
9872
  return prev;
9575
9873
  });
9874
+ const existingActiveConversation = activeConversationsRef.current.get(targetConversationId);
9875
+ upsertApiConversationFromHistory(
9876
+ targetConversationId,
9877
+ history,
9878
+ existingActiveConversation == null ? void 0 : existingActiveConversation.title
9879
+ );
9576
9880
  }
9577
9881
  const lastEntry = Object.entries(history).pop();
9578
9882
  if (lastEntry) {
@@ -9592,9 +9896,11 @@ var AIAgentPanel = import_react16.default.forwardRef(({
9592
9896
  }
9593
9897
  },
9594
9898
  [
9899
+ activeConversationsRef,
9595
9900
  currentAgentId,
9596
9901
  historyChangedCallback,
9597
- agents
9902
+ agents,
9903
+ upsertApiConversationFromHistory
9598
9904
  ]
9599
9905
  );
9600
9906
  const handleLoadingChange = (0, import_react16.useCallback)((isLoading, conversationId) => {
@@ -9603,6 +9909,9 @@ var AIAgentPanel = import_react16.default.forwardRef(({
9603
9909
  setActiveConversations((prev) => {
9604
9910
  const existing = prev.get(targetConversationId);
9605
9911
  if (existing && existing.isLoading !== isLoading) {
9912
+ if (existing.isLoading && !isLoading) {
9913
+ requestConversationListRefresh();
9914
+ }
9606
9915
  const next = new Map(prev);
9607
9916
  next.set(targetConversationId, __spreadProps(__spreadValues({}, existing), {
9608
9917
  isLoading
@@ -9612,7 +9921,7 @@ var AIAgentPanel = import_react16.default.forwardRef(({
9612
9921
  return prev;
9613
9922
  });
9614
9923
  }
9615
- }, []);
9924
+ }, [requestConversationListRefresh]);
9616
9925
  const handleHandoffConfirm = (0, import_react16.useCallback)(() => {
9617
9926
  if (suggestedAgent) {
9618
9927
  handleAgentSwitch(suggestedAgent);
@@ -9658,7 +9967,8 @@ var AIAgentPanel = import_react16.default.forwardRef(({
9658
9967
  if (currentConversationIdRef.current === tempId) {
9659
9968
  commitConversationSelection(realId, true);
9660
9969
  }
9661
- }, [commitConversationSelection]);
9970
+ requestConversationListRefresh();
9971
+ }, [commitConversationSelection, requestConversationListRefresh]);
9662
9972
  const toggleCollapse = (0, import_react16.useCallback)(() => {
9663
9973
  if (!collapsible) return;
9664
9974
  const newValue = !isCollapsed;