opencami 1.9.0 → 2.0.0

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.
Files changed (66) hide show
  1. package/dist/client/assets/{CSPContext-TfUptlEu.js → CSPContext-CrlIQW7-.js} +1 -1
  2. package/dist/client/assets/{DirectionContext-CQMv7g2N.js → DirectionContext-X-0CRn1O.js} +1 -1
  3. package/dist/client/assets/_sessionKey-yNQ57svB.js +23 -0
  4. package/dist/client/assets/agents-WqWjsymD.js +2 -0
  5. package/dist/client/assets/{agents-screen-fSZJpRi_.js → agents-screen-BhOVp_S6.js} +1 -1
  6. package/dist/client/assets/bots-DNqiFT7w.js +2 -0
  7. package/dist/client/assets/{bots-screen-4yT-e3cM.js → bots-screen-DLFd0ydi.js} +1 -1
  8. package/dist/client/assets/button-D0n2Qsd_.js +1 -0
  9. package/dist/client/assets/{composite-BLgu_EOL.js → composite-GtKwZKbV.js} +1 -1
  10. package/dist/client/assets/{connect-CiqRvR6s.js → connect-vQWL0_11.js} +1 -1
  11. package/dist/client/assets/{dashboard-CyWDWpbj.js → dashboard-Knwc61i1.js} +1 -1
  12. package/dist/client/assets/{event-2_Dxdv7h.js → event-CHpdjYFR.js} +1 -1
  13. package/dist/client/assets/file-explorer-screen-FoYNs9zK.js +1 -0
  14. package/dist/client/assets/files-BtR_gArr.js +2 -0
  15. package/dist/client/assets/follow-up-suggestions-DVXNLqga.js +5 -0
  16. package/dist/client/assets/{index-ygitKeM-.js → index-CNPHef4O.js} +1 -1
  17. package/dist/client/assets/{index-C_gsW9fo.js → index-CTT0Y1ya.js} +1 -1
  18. package/dist/client/assets/{keyboard-shortcuts-dialog-z-amTZVi.js → keyboard-shortcuts-dialog-DP8ptQ7N.js} +1 -1
  19. package/dist/client/assets/{main-ZBMVSJTF.js → main-xPlWrMhO.js} +3 -3
  20. package/dist/client/assets/{markdown-CHUjmWcv.js → markdown-3Js_RbUp.js} +1 -1
  21. package/dist/client/assets/memory-BRGPq5t6.js +2 -0
  22. package/dist/client/assets/{memory-screen-C_ZNDGLd.js → memory-screen-nzRra2Qi.js} +1 -1
  23. package/dist/client/assets/{menu-CB88T7R1.js → menu-BnSEqetd.js} +1 -1
  24. package/dist/client/assets/{opencami-logo-C0Kj1DiT.js → opencami-logo-B_hLbomw.js} +1 -1
  25. package/dist/client/assets/{proxy-D-juuhw6.js → proxy-BnlGpgC1.js} +1 -1
  26. package/dist/client/assets/{react-Akh4y69S.js → react-BgjQyJHw.js} +1 -1
  27. package/dist/client/assets/{search-dialog-BasfzCyM.js → search-dialog-Bib2QY9u.js} +1 -1
  28. package/dist/client/assets/{search-sources-badge-DwFHWd7S.js → search-sources-badge-COHcYFRB.js} +1 -1
  29. package/dist/client/assets/{session-export-dialog-CAl3iJnD.js → session-export-dialog-ooPnfHh_.js} +1 -1
  30. package/dist/client/assets/settings-dialog-B8mz99u-.js +1 -0
  31. package/dist/client/assets/skills-DA9J_tsC.js +2 -0
  32. package/dist/client/assets/{skills-panel-B7BRAofP.js → skills-panel-D2uMdCHp.js} +1 -1
  33. package/dist/client/assets/styles-CWabEzNU.css +1 -0
  34. package/dist/client/assets/{switch-DYEbEgy5.js → switch-BUQ0qH6r.js} +1 -1
  35. package/dist/client/assets/{tabs-eiBvL0H7.js → tabs-BEyU6TjN.js} +1 -1
  36. package/dist/client/assets/{thinking-CariuioI.js → thinking-CA48yhOE.js} +1 -1
  37. package/dist/client/assets/{tooltip-CekkGEYG.js → tooltip-CcIdgcV0.js} +1 -1
  38. package/dist/client/assets/{use-file-explorer-state-Dfyh4GwR.js → use-file-explorer-state-CN_IJGcd.js} +2 -2
  39. package/dist/client/assets/{useBaseUiId-DLhdkHJl.js → useBaseUiId-ClbEYEil.js} +1 -1
  40. package/dist/client/assets/useCompositeItem-B_OxfJee.js +1 -0
  41. package/dist/client/assets/{useControlled-CpliTEve.js → useControlled-CyT-lqbs.js} +1 -1
  42. package/dist/client/assets/{useMutation-CpD2Pn0F.js → useMutation-eQUrsn-X.js} +1 -1
  43. package/dist/client/assets/{useOnFirstRender-DsFYFJoB.js → useOnFirstRender-CR_o2MK_.js} +1 -1
  44. package/dist/client/assets/{useQuery-DMTgpIql.js → useQuery-k6EMRoMD.js} +1 -1
  45. package/dist/server/assets/{_sessionKey-Bhksr7VP.js → _sessionKey-CaFqmyhU.js} +388 -390
  46. package/dist/server/assets/{_tanstack-start-manifest_v-D-5ReiD4.js → _tanstack-start-manifest_v-P3skSR3R.js} +1 -1
  47. package/dist/server/assets/{follow-up-suggestions-B3hol2KT.js → follow-up-suggestions-DHv2_XzB.js} +13 -74
  48. package/dist/server/assets/{index-4G_4vZNY.js → index-C7lmufwX.js} +1 -1
  49. package/dist/server/assets/{router-C9JRmWMm.js → router-X2L0PDPI.js} +133 -294
  50. package/dist/server/assets/{search-dialog-CTJULPB8.js → search-dialog-CXhofdoP.js} +2 -2
  51. package/dist/server/assets/{settings-dialog-B5yR2pBy.js → settings-dialog-CPdftvjz.js} +1 -254
  52. package/dist/server/assets/{thinking-CHx4Oouj.js → thinking-YkRSlXtf.js} +2 -2
  53. package/dist/server/server.js +2 -2
  54. package/package.json +1 -1
  55. package/dist/client/assets/_sessionKey-DYknvaDS.js +0 -23
  56. package/dist/client/assets/agents-DNywJUai.js +0 -2
  57. package/dist/client/assets/bots-Bqjqhws8.js +0 -2
  58. package/dist/client/assets/button-DqP4GZwZ.js +0 -1
  59. package/dist/client/assets/file-explorer-screen-CZ2QKk-0.js +0 -1
  60. package/dist/client/assets/files-Cbhud0J8.js +0 -2
  61. package/dist/client/assets/follow-up-suggestions-Bi3Ci2my.js +0 -5
  62. package/dist/client/assets/memory-BRa-0plj.js +0 -2
  63. package/dist/client/assets/settings-dialog-C8OoRXwX.js +0 -1
  64. package/dist/client/assets/skills-Cx12984a.js +0 -2
  65. package/dist/client/assets/styles-CXa-SiWC.css +0 -1
  66. package/dist/client/assets/useCompositeItem-DTSTTR0Z.js +0 -1
@@ -19,7 +19,7 @@ import { u as useChatSettings$1 } from "./index-B_F4DTUu.js";
19
19
  import { createPortal } from "react-dom";
20
20
  import { create } from "zustand";
21
21
  import { persist } from "zustand/middleware";
22
- import { a as Route } from "./router-C9JRmWMm.js";
22
+ import { a as Route } from "./router-X2L0PDPI.js";
23
23
  function deriveFriendlyIdFromKey(key) {
24
24
  if (!key) return "main";
25
25
  const trimmed = key.trim();
@@ -1686,7 +1686,7 @@ function areSidebarSessionsEqual(prev, next) {
1686
1686
  return true;
1687
1687
  }
1688
1688
  const SettingsDialog = lazy(
1689
- () => import("./settings-dialog-B5yR2pBy.js").then((m) => ({ default: m.SettingsDialog }))
1689
+ () => import("./settings-dialog-CPdftvjz.js").then((m) => ({ default: m.SettingsDialog }))
1690
1690
  );
1691
1691
  const SessionExportDialog = lazy(
1692
1692
  () => import("./session-export-dialog-CgtlOnwf.js").then((m) => ({
@@ -2833,7 +2833,7 @@ function Tool({ toolPart, defaultOpen = false }) {
2833
2833
  ] }) });
2834
2834
  }
2835
2835
  const Thinking = lazy(
2836
- () => import("./thinking-CHx4Oouj.js").then((m) => ({
2836
+ () => import("./thinking-YkRSlXtf.js").then((m) => ({
2837
2837
  default: m.Thinking
2838
2838
  }))
2839
2839
  );
@@ -3015,7 +3015,9 @@ function MessageItemComponent({
3015
3015
  wrapperClassName,
3016
3016
  wrapperScrollMarginTop,
3017
3017
  messageDomId,
3018
- highlighted = false
3018
+ highlighted = false,
3019
+ onRetry,
3020
+ onDismiss
3019
3021
  }) {
3020
3022
  const { settings } = useChatSettings$1();
3021
3023
  const role = message.role || "assistant";
@@ -3023,6 +3025,7 @@ function MessageItemComponent({
3023
3025
  const thinking = thinkingFromMessage(message);
3024
3026
  const images = imagesFromMessage(message);
3025
3027
  const isUser = role === "user";
3028
+ const isFailed = isUser && message.status === "error";
3026
3029
  const imageDataUris = useMemo(
3027
3030
  () => images.map(
3028
3031
  (img) => `data:${img.source.media_type};base64,${img.source.data}`
@@ -3165,13 +3168,35 @@ function MessageItemComponent({
3165
3168
  className: cn(
3166
3169
  "text-primary-900 opencami-text-size min-w-0 max-w-full",
3167
3170
  isUser ? "opencami-message-user bg-primary-100 px-4 py-[var(--opencami-user-bubble-py)] max-w-[85%]" : "opencami-message-assistant bg-transparent w-full",
3168
- !isUser && isStreaming && "stream-fade-in"
3171
+ !isUser && isStreaming && "stream-fade-in",
3172
+ isFailed && "opacity-60"
3169
3173
  ),
3170
3174
  children: displayText
3171
3175
  }
3172
3176
  )
3173
3177
  }
3174
3178
  ),
3179
+ isFailed && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-red-500 dark:text-red-400", children: [
3180
+ /* @__PURE__ */ jsx("span", { children: "Failed to send" }),
3181
+ onRetry && /* @__PURE__ */ jsx(
3182
+ "button",
3183
+ {
3184
+ type: "button",
3185
+ className: "underline hover:text-red-700 dark:hover:text-red-300",
3186
+ onClick: () => onRetry(message),
3187
+ children: "Retry"
3188
+ }
3189
+ ),
3190
+ onDismiss && /* @__PURE__ */ jsx(
3191
+ "button",
3192
+ {
3193
+ type: "button",
3194
+ className: "underline hover:text-red-700 dark:hover:text-red-300",
3195
+ onClick: () => onDismiss(message),
3196
+ children: "Dismiss"
3197
+ }
3198
+ )
3199
+ ] }),
3175
3200
  hasToolCalls && settings.showToolMessages && /* @__PURE__ */ jsx("div", { className: "mt-2 flex w-full min-w-0 max-w-[var(--opencami-chat-width)] flex-col gap-3 overflow-x-hidden", children: toolCalls.map((toolCall) => {
3176
3201
  const resultMessage = toolCall.id ? toolResultsByCallId?.get(toolCall.id) : void 0;
3177
3202
  const toolPart = mapToolCallToToolPart(toolCall, resultMessage);
@@ -3213,6 +3238,9 @@ function areMessagesEqual(prevProps, nextProps) {
3213
3238
  }
3214
3239
  if (prevProps.messageDomId !== nextProps.messageDomId) return false;
3215
3240
  if (prevProps.highlighted !== nextProps.highlighted) return false;
3241
+ if (prevProps.onRetry !== nextProps.onRetry) return false;
3242
+ if (prevProps.onDismiss !== nextProps.onDismiss) return false;
3243
+ if (prevProps.message.status !== nextProps.message.status) return false;
3216
3244
  if ((prevProps.message.role || "assistant") !== (nextProps.message.role || "assistant")) {
3217
3245
  return false;
3218
3246
  }
@@ -3474,7 +3502,7 @@ function TypingIndicator({ className }) {
3474
3502
  ] });
3475
3503
  }
3476
3504
  const FollowUpSuggestions = lazy(
3477
- () => import("./follow-up-suggestions-B3hol2KT.js").then((m) => ({
3505
+ () => import("./follow-up-suggestions-DHv2_XzB.js").then((m) => ({
3478
3506
  default: m.FollowUpSuggestions
3479
3507
  }))
3480
3508
  );
@@ -3493,7 +3521,9 @@ function ChatMessageListComponent({
3493
3521
  headerHeight,
3494
3522
  contentStyle,
3495
3523
  onFollowUpClick,
3496
- jumpToMessageId
3524
+ jumpToMessageId,
3525
+ onRetryMessage,
3526
+ onDismissMessage
3497
3527
  }) {
3498
3528
  const anchorRef = useRef(null);
3499
3529
  const lastUserRef = useRef(null);
@@ -3689,7 +3719,9 @@ function ChatMessageListComponent({
3689
3719
  isLastAssistant,
3690
3720
  aggregatedSearchSources: isLastAssistant ? aggregatedSearchSources : void 0,
3691
3721
  messageDomId: messageId ? `message-${messageId}` : void 0,
3692
- highlighted: highlightedMessageId === messageId
3722
+ highlighted: highlightedMessageId === messageId,
3723
+ onRetry: onRetryMessage,
3724
+ onDismiss: onDismissMessage
3693
3725
  },
3694
3726
  messageKey
3695
3727
  );
@@ -3723,7 +3755,9 @@ function ChatMessageListComponent({
3723
3755
  wrapperClassName,
3724
3756
  wrapperScrollMarginTop,
3725
3757
  messageDomId: messageId ? `message-${messageId}` : void 0,
3726
- highlighted: highlightedMessageId === messageId
3758
+ highlighted: highlightedMessageId === messageId,
3759
+ onRetry: onRetryMessage,
3760
+ onDismiss: onDismissMessage
3727
3761
  },
3728
3762
  messageKey
3729
3763
  );
@@ -3757,7 +3791,9 @@ function ChatMessageListComponent({
3757
3791
  isLastAssistant,
3758
3792
  aggregatedSearchSources: isLastAssistant ? aggregatedSearchSources : void 0,
3759
3793
  messageDomId: messageId ? `message-${messageId}` : void 0,
3760
- highlighted: highlightedMessageId === messageId
3794
+ highlighted: highlightedMessageId === messageId,
3795
+ onRetry: onRetryMessage,
3796
+ onDismiss: onDismissMessage
3761
3797
  },
3762
3798
  messageKey
3763
3799
  );
@@ -3782,7 +3818,7 @@ function ChatMessageListComponent({
3782
3818
  );
3783
3819
  }
3784
3820
  function areChatMessageListEqual(prev, next) {
3785
- return prev.messages === next.messages && prev.loading === next.loading && prev.empty === next.empty && prev.emptyState === next.emptyState && prev.notice === next.notice && prev.noticePosition === next.noticePosition && prev.waitingForResponse === next.waitingForResponse && prev.isStreaming === next.isStreaming && prev.sessionKey === next.sessionKey && prev.pinToTop === next.pinToTop && prev.pinGroupMinHeight === next.pinGroupMinHeight && prev.headerHeight === next.headerHeight && prev.contentStyle === next.contentStyle && prev.onFollowUpClick === next.onFollowUpClick && prev.jumpToMessageId === next.jumpToMessageId;
3821
+ return prev.messages === next.messages && prev.loading === next.loading && prev.empty === next.empty && prev.emptyState === next.emptyState && prev.notice === next.notice && prev.noticePosition === next.noticePosition && prev.waitingForResponse === next.waitingForResponse && prev.isStreaming === next.isStreaming && prev.sessionKey === next.sessionKey && prev.pinToTop === next.pinToTop && prev.pinGroupMinHeight === next.pinGroupMinHeight && prev.headerHeight === next.headerHeight && prev.contentStyle === next.contentStyle && prev.onFollowUpClick === next.onFollowUpClick && prev.jumpToMessageId === next.jumpToMessageId && prev.onRetryMessage === next.onRetryMessage && prev.onDismissMessage === next.onDismissMessage;
3786
3822
  }
3787
3823
  const MemoizedChatMessageList = memo(
3788
3824
  ChatMessageListComponent,
@@ -5450,13 +5486,20 @@ function useChatHistory({
5450
5486
  if (message.__optimisticId) return true;
5451
5487
  return Boolean(message.clientId);
5452
5488
  }) : [];
5489
+ const cachedMessages = Array.isArray(cached?.messages) ? cached.messages : [];
5453
5490
  const serverData = await fetchHistory({
5454
5491
  sessionKey: sessionKeyForHistory,
5455
5492
  friendlyId: activeFriendlyId
5456
5493
  });
5457
- if (!optimisticMessages.length) return serverData;
5458
- const merged = mergeOptimisticHistoryMessages(
5494
+ const serverMessages = restoreCachedImageParts(
5459
5495
  serverData.messages,
5496
+ cachedMessages
5497
+ );
5498
+ if (!optimisticMessages.length) {
5499
+ return { ...serverData, messages: serverMessages };
5500
+ }
5501
+ const merged = mergeOptimisticHistoryMessages(
5502
+ serverMessages,
5460
5503
  optimisticMessages
5461
5504
  );
5462
5505
  return {
@@ -5502,30 +5545,101 @@ function useChatHistory({
5502
5545
  sessionKeyForHistory
5503
5546
  };
5504
5547
  }
5505
- function mergeOptimisticHistoryMessages(serverMessages, optimisticMessages) {
5506
- if (!optimisticMessages.length) return serverMessages;
5507
- const merged = [...serverMessages];
5508
- for (const optimisticMessage of optimisticMessages) {
5509
- const hasMatch = serverMessages.some((serverMessage) => {
5510
- if (optimisticMessage.clientId && serverMessage.clientId && optimisticMessage.clientId === serverMessage.clientId) {
5548
+ function findMatchIndex(serverMessages, optimisticMessage) {
5549
+ return serverMessages.findIndex((serverMessage) => {
5550
+ if (optimisticMessage.clientId && serverMessage.clientId && optimisticMessage.clientId === serverMessage.clientId) {
5551
+ return true;
5552
+ }
5553
+ if (optimisticMessage.__optimisticId && serverMessage.__optimisticId && optimisticMessage.__optimisticId === serverMessage.__optimisticId) {
5554
+ return true;
5555
+ }
5556
+ if (optimisticMessage.role && serverMessage.role) {
5557
+ if (optimisticMessage.role !== serverMessage.role) return false;
5558
+ }
5559
+ const optimisticText = textFromMessage(optimisticMessage);
5560
+ if (!optimisticText) return false;
5561
+ if (optimisticText !== textFromMessage(serverMessage)) return false;
5562
+ const optimisticTime = getMessageTimestamp(optimisticMessage);
5563
+ const serverTime = getMessageTimestamp(serverMessage);
5564
+ return Math.abs(optimisticTime - serverTime) <= 1e4;
5565
+ });
5566
+ }
5567
+ function getImageParts(message) {
5568
+ if (!Array.isArray(message.content)) return [];
5569
+ return message.content.filter((part) => {
5570
+ if (typeof part !== "object" || part === null) return false;
5571
+ const p = part;
5572
+ if (p.type !== "image") return false;
5573
+ const source = p.source;
5574
+ return typeof source?.data === "string" && source.data.length > 0;
5575
+ });
5576
+ }
5577
+ function restoreCachedImageParts(serverMessages, cachedMessages) {
5578
+ const cachedMessagesWithImages = cachedMessages.filter(
5579
+ (message) => getImageParts(message).length > 0
5580
+ );
5581
+ if (!cachedMessagesWithImages.length) return serverMessages;
5582
+ return serverMessages.map((serverMessage) => {
5583
+ if (getImageParts(serverMessage).length > 0) return serverMessage;
5584
+ const cachedMatch = cachedMessagesWithImages.find((cachedMessage) => {
5585
+ if (serverMessage.role !== cachedMessage.role) return false;
5586
+ if (serverMessage.id && cachedMessage.id && serverMessage.id === cachedMessage.id) {
5511
5587
  return true;
5512
5588
  }
5513
- if (optimisticMessage.__optimisticId && serverMessage.__optimisticId && optimisticMessage.__optimisticId === serverMessage.__optimisticId) {
5589
+ if (serverMessage.clientId && cachedMessage.clientId && serverMessage.clientId === cachedMessage.clientId) {
5514
5590
  return true;
5515
5591
  }
5516
- if (optimisticMessage.role && serverMessage.role) {
5517
- if (optimisticMessage.role !== serverMessage.role) return false;
5518
- }
5519
- const optimisticText = textFromMessage(optimisticMessage);
5520
- if (!optimisticText) return false;
5521
- if (optimisticText !== textFromMessage(serverMessage)) return false;
5522
- const optimisticTime = getMessageTimestamp(optimisticMessage);
5523
- const serverTime = getMessageTimestamp(serverMessage);
5524
- return Math.abs(optimisticTime - serverTime) <= 1e4;
5592
+ const serverText = textFromMessage(serverMessage);
5593
+ const cachedText = textFromMessage(cachedMessage);
5594
+ if (!serverText || serverText !== cachedText) return false;
5595
+ const timeDiff = Math.abs(
5596
+ getMessageTimestamp(serverMessage) - getMessageTimestamp(cachedMessage)
5597
+ );
5598
+ return timeDiff <= 3e4;
5525
5599
  });
5526
- if (!hasMatch) {
5600
+ if (!cachedMatch) return serverMessage;
5601
+ const imageParts = getImageParts(cachedMatch);
5602
+ if (!imageParts.length) return serverMessage;
5603
+ return {
5604
+ ...serverMessage,
5605
+ content: [
5606
+ ...imageParts,
5607
+ ...Array.isArray(serverMessage.content) ? serverMessage.content : []
5608
+ ]
5609
+ };
5610
+ });
5611
+ }
5612
+ function mergeOptimisticHistoryMessages(serverMessages, optimisticMessages) {
5613
+ if (!optimisticMessages.length) return serverMessages;
5614
+ const merged = [...serverMessages];
5615
+ for (const optimisticMessage of optimisticMessages) {
5616
+ const matchIndex = findMatchIndex(merged, optimisticMessage);
5617
+ if (matchIndex === -1) {
5527
5618
  merged.push(optimisticMessage);
5619
+ continue;
5528
5620
  }
5621
+ const imageParts = getImageParts(optimisticMessage);
5622
+ const serverMessage = merged[matchIndex];
5623
+ const serverImageParts = getImageParts(serverMessage);
5624
+ const needsImages = imageParts.length > 0 && serverImageParts.length === 0;
5625
+ const needsOptimisticId = Boolean(
5626
+ optimisticMessage.__optimisticId && !serverMessage.__optimisticId
5627
+ );
5628
+ const needsClientId = Boolean(
5629
+ optimisticMessage.clientId && !serverMessage.clientId
5630
+ );
5631
+ if (!needsImages && !needsOptimisticId && !needsClientId) continue;
5632
+ merged[matchIndex] = {
5633
+ ...serverMessage,
5634
+ ...needsOptimisticId ? { __optimisticId: optimisticMessage.__optimisticId } : {},
5635
+ ...needsClientId ? { clientId: optimisticMessage.clientId } : {},
5636
+ ...needsImages ? {
5637
+ content: [
5638
+ ...imageParts,
5639
+ ...Array.isArray(serverMessage.content) ? serverMessage.content : []
5640
+ ]
5641
+ } : {}
5642
+ };
5529
5643
  }
5530
5644
  return merged;
5531
5645
  }
@@ -5609,268 +5723,66 @@ function useChatSessions({
5609
5723
  sessionsError
5610
5724
  };
5611
5725
  }
5612
- const LLM_PROVIDER_DEFAULTS = {
5613
- openai: {
5614
- baseUrl: "https://api.openai.com/v1",
5615
- model: "gpt-4.1-nano"
5616
- },
5617
- openrouter: {
5618
- baseUrl: "https://openrouter.ai/api/v1",
5619
- model: "openai/gpt-oss-120b"
5620
- },
5621
- kilocode: {
5622
- baseUrl: "https://api.kilo.ai/api/gateway",
5623
- model: "google/gemini-2.5-flash"
5624
- },
5625
- ollama: {
5626
- baseUrl: "http://localhost:11434/v1",
5627
- model: "llama3.2"
5628
- },
5629
- custom: {
5630
- baseUrl: "",
5631
- model: ""
5632
- }
5633
- };
5634
- const DEFAULT_LLM_SETTINGS = {
5635
- useLlmTitles: true,
5636
- useLlmFollowUps: true,
5637
- llmProvider: "openai",
5638
- llmBaseUrl: "",
5639
- llmModel: "",
5640
- llmApiKey: ""
5641
- };
5642
- function getLlmProviderDefaults(provider) {
5643
- return LLM_PROVIDER_DEFAULTS[provider];
5644
- }
5645
- function getEffectiveLlmBaseUrl(settings) {
5646
- if (settings.llmBaseUrl.trim()) return settings.llmBaseUrl.trim();
5647
- return getLlmProviderDefaults(settings.llmProvider).baseUrl;
5648
- }
5649
- function getEffectiveLlmModel(settings) {
5650
- if (settings.llmModel.trim()) return settings.llmModel.trim();
5651
- return getLlmProviderDefaults(settings.llmProvider).model;
5652
- }
5653
- function getAvailability(settings, hasEnvKey) {
5654
- if (settings.llmProvider === "ollama") return true;
5655
- if (settings.llmProvider === "custom") {
5656
- return Boolean(settings.llmApiKey.trim()) || Boolean(settings.llmBaseUrl.trim() && settings.llmModel.trim());
5657
- }
5658
- return hasEnvKey || Boolean(settings.llmApiKey.trim());
5659
- }
5660
- function migratePersistedState(persistedState) {
5661
- if (!persistedState || typeof persistedState !== "object") {
5662
- return { settings: DEFAULT_LLM_SETTINGS };
5663
- }
5664
- const { settings } = persistedState;
5665
- if (!settings) {
5666
- return { settings: DEFAULT_LLM_SETTINGS };
5667
- }
5668
- const { openaiApiKey, ...rest } = settings;
5669
- const llmApiKey = rest.llmApiKey ?? openaiApiKey ?? "";
5670
- return {
5671
- settings: {
5672
- ...DEFAULT_LLM_SETTINGS,
5673
- ...rest,
5674
- llmApiKey
5675
- }
5676
- };
5677
- }
5678
- const useLlmSettingsStore = create()(
5679
- persist(
5680
- (set) => ({
5681
- settings: {
5682
- ...DEFAULT_LLM_SETTINGS
5683
- },
5684
- updateSettings: (updates) => set((state) => ({
5685
- settings: { ...state.settings, ...updates }
5686
- })),
5687
- clearApiKey: () => set((state) => ({
5688
- settings: { ...state.settings, llmApiKey: "" }
5689
- }))
5690
- }),
5691
- {
5692
- name: "llm-settings",
5693
- version: 2,
5694
- migrate: (persistedState) => migratePersistedState(persistedState)
5695
- }
5696
- )
5697
- );
5698
- function useLlmSettings() {
5699
- const settings = useLlmSettingsStore((state) => state.settings);
5700
- const updateSettings = useLlmSettingsStore((state) => state.updateSettings);
5701
- const clearApiKey = useLlmSettingsStore((state) => state.clearApiKey);
5702
- const [status, setStatus] = useState({
5703
- hasEnvKey: false,
5704
- hasOpenRouterKey: false,
5705
- hasKilocodeKey: false,
5706
- hasUserKey: Boolean(settings.llmApiKey),
5707
- isAvailable: getAvailability(settings, false),
5708
- isLoading: true,
5709
- error: null
5710
- });
5711
- useEffect(() => {
5712
- let cancelled = false;
5713
- async function checkStatus() {
5714
- try {
5715
- const res = await fetch("/api/llm-features");
5716
- if (!res.ok) throw new Error("Failed to check LLM status");
5717
- const data = await res.json();
5718
- if (cancelled) return;
5719
- const hasUserKey = Boolean(settings.llmApiKey);
5720
- const hasProviderKey = settings.llmProvider === "openrouter" ? Boolean(data.hasOpenRouterKey) : settings.llmProvider === "kilocode" ? Boolean(data.hasKilocodeKey) : data.hasEnvKey;
5721
- setStatus({
5722
- hasEnvKey: data.hasEnvKey,
5723
- hasOpenRouterKey: Boolean(data.hasOpenRouterKey),
5724
- hasKilocodeKey: Boolean(data.hasKilocodeKey),
5725
- hasUserKey,
5726
- isAvailable: getAvailability(settings, hasProviderKey),
5727
- isLoading: false,
5728
- error: null
5729
- });
5730
- } catch (err) {
5731
- if (cancelled) return;
5732
- setStatus((prev) => ({
5733
- ...prev,
5734
- isLoading: false,
5735
- error: err instanceof Error ? err.message : "Failed to check status"
5736
- }));
5737
- }
5726
+ function useSmartTitle() {
5727
+ const [isGenerating, setIsGenerating] = useState(false);
5728
+ const [lastTitle, setLastTitle] = useState(null);
5729
+ const [lastSource, setLastSource] = useState(null);
5730
+ const abortControllerRef = useRef(null);
5731
+ const generateTitle = useCallback(async (message) => {
5732
+ if (abortControllerRef.current) {
5733
+ abortControllerRef.current.abort();
5738
5734
  }
5739
- void checkStatus();
5740
- return () => {
5741
- cancelled = true;
5742
- };
5743
- }, [
5744
- settings.llmApiKey,
5745
- settings.llmProvider,
5746
- settings.llmBaseUrl,
5747
- settings.llmModel
5748
- ]);
5749
- const testApiKey = useCallback(async (key) => {
5735
+ const controller = new AbortController();
5736
+ abortControllerRef.current = controller;
5737
+ setIsGenerating(true);
5750
5738
  try {
5751
- const headers = buildLlmHeaders({
5752
- ...settings,
5753
- llmApiKey: key
5754
- });
5755
5739
  const res = await fetch("/api/llm-features", {
5756
5740
  method: "POST",
5757
5741
  headers: {
5758
- "Content-Type": "application/json",
5759
- ...headers
5742
+ "Content-Type": "application/json"
5760
5743
  },
5761
- body: JSON.stringify({ action: "test" })
5744
+ body: JSON.stringify({
5745
+ action: "title",
5746
+ message
5747
+ }),
5748
+ signal: controller.signal
5762
5749
  });
5750
+ if (!res.ok) {
5751
+ throw new Error(`API error: ${res.status}`);
5752
+ }
5763
5753
  const data = await res.json();
5764
- if (!data.ok) {
5765
- return { valid: false, error: data.error || "Test failed" };
5754
+ if (controller.signal.aborted) {
5755
+ throw new Error("Aborted");
5766
5756
  }
5767
- return { valid: data.valid ?? false, error: data.error };
5768
- } catch (err) {
5757
+ const rawTitle = data.title || message.slice(0, 50);
5758
+ const title = rawTitle.length > 64 ? rawTitle.slice(0, 61) + "..." : rawTitle;
5759
+ const source = data.source || "heuristic";
5760
+ setLastTitle(title);
5761
+ setLastSource(source);
5769
5762
  return {
5770
- valid: false,
5771
- error: err instanceof Error ? err.message : "Network error"
5763
+ title,
5764
+ source,
5765
+ error: data.error
5772
5766
  };
5773
- }
5774
- }, [settings]);
5775
- return {
5776
- settings,
5777
- updateSettings,
5778
- clearApiKey,
5779
- status,
5780
- testApiKey
5781
- };
5782
- }
5783
- function buildLlmHeaders(settings) {
5784
- const apiKey = settings.llmApiKey;
5785
- const baseUrl = getEffectiveLlmBaseUrl(settings);
5786
- const model = getEffectiveLlmModel(settings);
5787
- if (apiKey) {
5788
- return {
5789
- "X-OpenAI-API-Key": apiKey,
5790
- ...baseUrl ? { "X-LLM-Base-URL": baseUrl } : {},
5791
- ...model ? { "X-LLM-Model": model } : {}
5792
- };
5793
- }
5794
- return {
5795
- ...baseUrl ? { "X-LLM-Base-URL": baseUrl } : {},
5796
- ...model ? { "X-LLM-Model": model } : {}
5797
- };
5798
- }
5799
- function getLlmHeaders() {
5800
- const settings = useLlmSettingsStore.getState().settings;
5801
- return buildLlmHeaders(settings);
5802
- }
5803
- function useSmartTitle() {
5804
- const llmSettings = useLlmSettingsStore((state) => state.settings);
5805
- const [isGenerating, setIsGenerating] = useState(false);
5806
- const [lastTitle, setLastTitle] = useState(null);
5807
- const [lastSource, setLastSource] = useState(null);
5808
- const abortControllerRef = useRef(null);
5809
- const generateTitle = useCallback(
5810
- async (message) => {
5811
- if (abortControllerRef.current) {
5812
- abortControllerRef.current.abort();
5767
+ } catch (err) {
5768
+ if (err instanceof Error && err.name === "AbortError") {
5769
+ throw err;
5813
5770
  }
5814
- const controller = new AbortController();
5815
- abortControllerRef.current = controller;
5816
- setIsGenerating(true);
5817
- try {
5818
- const headers = {
5819
- "Content-Type": "application/json",
5820
- ...getLlmHeaders()
5821
- };
5822
- const res = await fetch("/api/llm-features", {
5823
- method: "POST",
5824
- headers,
5825
- body: JSON.stringify({
5826
- action: "title",
5827
- message
5828
- }),
5829
- signal: controller.signal
5830
- });
5831
- if (!res.ok) {
5832
- throw new Error(`API error: ${res.status}`);
5833
- }
5834
- const data = await res.json();
5835
- if (controller.signal.aborted) {
5836
- throw new Error("Aborted");
5837
- }
5838
- const rawTitle = data.title || message.slice(0, 50);
5839
- const title = rawTitle.length > 64 ? rawTitle.slice(0, 61) + "..." : rawTitle;
5840
- const source = data.source || "heuristic";
5841
- setLastTitle(title);
5842
- setLastSource(source);
5843
- return {
5844
- title,
5845
- source,
5846
- error: data.error
5847
- };
5848
- } catch (err) {
5849
- if (err instanceof Error && err.name === "AbortError") {
5850
- throw err;
5851
- }
5852
- const fallbackTitle = generateHeuristicTitle(message);
5853
- setLastTitle(fallbackTitle);
5854
- setLastSource("heuristic");
5855
- return {
5856
- title: fallbackTitle,
5857
- source: "heuristic",
5858
- error: err instanceof Error ? err.message : "Unknown error"
5859
- };
5860
- } finally {
5861
- setIsGenerating(false);
5862
- if (abortControllerRef.current === controller) {
5863
- abortControllerRef.current = null;
5864
- }
5771
+ const fallbackTitle = generateHeuristicTitle(message);
5772
+ setLastTitle(fallbackTitle);
5773
+ setLastSource("heuristic");
5774
+ return {
5775
+ title: fallbackTitle,
5776
+ source: "heuristic",
5777
+ error: err instanceof Error ? err.message : "Unknown error"
5778
+ };
5779
+ } finally {
5780
+ setIsGenerating(false);
5781
+ if (abortControllerRef.current === controller) {
5782
+ abortControllerRef.current = null;
5865
5783
  }
5866
- },
5867
- [
5868
- llmSettings.llmApiKey,
5869
- llmSettings.llmBaseUrl,
5870
- llmSettings.llmModel,
5871
- llmSettings.llmProvider
5872
- ]
5873
- );
5784
+ }
5785
+ }, []);
5874
5786
  return {
5875
5787
  generateTitle,
5876
5788
  isGenerating,
@@ -5900,7 +5812,102 @@ function generateHeuristicTitle(message) {
5900
5812
  return title || message.slice(0, 50);
5901
5813
  }
5902
5814
  function useLlmTitlesEnabled() {
5903
- return useLlmSettingsStore((state) => state.settings.useLlmTitles);
5815
+ return true;
5816
+ }
5817
+ function handleAgentEvent(payload, fallbackSessionKey, options) {
5818
+ const agentPayload = asRecord(payload);
5819
+ const stream = normalizeString(agentPayload?.stream);
5820
+ const runId = normalizeString(agentPayload?.runId);
5821
+ const sessionKey = normalizeString(agentPayload?.sessionKey) || fallbackSessionKey;
5822
+ const streamData = asRecord(agentPayload?.data);
5823
+ if (runId) {
5824
+ options.anyRunSeen.current = true;
5825
+ if (stream === "lifecycle") {
5826
+ const phase = normalizeString(streamData?.phase);
5827
+ if (phase === "end" || phase === "error" || phase === "abort") {
5828
+ options.activeRuns.delete(runId);
5829
+ } else if (phase) {
5830
+ options.activeRuns.add(runId);
5831
+ }
5832
+ } else {
5833
+ options.activeRuns.add(runId);
5834
+ }
5835
+ }
5836
+ if (stream === "assistant") {
5837
+ const text = normalizeString(streamData?.delta) || normalizeString(streamData?.text);
5838
+ if (!text) return;
5839
+ options.setState((prev) => {
5840
+ const blocks = [...prev.contentBlocks];
5841
+ const lastBlock = blocks[blocks.length - 1];
5842
+ if (lastBlock?.kind === "text") {
5843
+ blocks[blocks.length - 1] = { ...lastBlock, text: lastBlock.text + text };
5844
+ } else {
5845
+ blocks.push({ kind: "text", text });
5846
+ }
5847
+ return {
5848
+ ...prev,
5849
+ sessionKey,
5850
+ text: prev.text + text,
5851
+ contentBlocks: blocks
5852
+ };
5853
+ });
5854
+ options.onAssistantDelta?.({ text, sessionKey });
5855
+ return;
5856
+ }
5857
+ if (!stream.includes("tool")) return;
5858
+ const toolId = normalizeString(streamData?.toolCallId) || normalizeString(streamData?.id) || normalizeString(streamData?.callId) || `${runId || "tool"}:${normalizeString(streamData?.toolName) || normalizeString(streamData?.name) || "unknown"}`;
5859
+ const toolName = normalizeString(streamData?.toolName) || normalizeString(streamData?.name) || "Tool";
5860
+ const toolStatus = deriveToolStatus(stream, streamData);
5861
+ const toolArgs = asRecord(streamData?.arguments) || asRecord(streamData?.input) || asRecord(streamData?.params) || null;
5862
+ const toolOutput = normalizeString(streamData?.result) || normalizeString(streamData?.output) || (stream.includes("result") ? normalizeString(streamData?.text) : "");
5863
+ options.setState((prev) => {
5864
+ const tools = [...prev.tools];
5865
+ const toolIndex = tools.findIndex((tool) => tool.id === toolId);
5866
+ const nextTool = { id: toolId, name: toolName, status: toolStatus };
5867
+ if (toolIndex >= 0) {
5868
+ tools[toolIndex] = nextTool;
5869
+ } else {
5870
+ tools.push(nextTool);
5871
+ }
5872
+ const blocks = [...prev.contentBlocks];
5873
+ const blockIndex = blocks.findIndex(
5874
+ (b) => b.kind === "tool" && b.id === toolId
5875
+ );
5876
+ const existingBlock = blockIndex >= 0 ? blocks[blockIndex] : null;
5877
+ const nextBlock = {
5878
+ kind: "tool",
5879
+ name: toolName,
5880
+ id: toolId,
5881
+ status: toolStatus,
5882
+ // Merge: keep existing arguments/output if new event doesn't carry them
5883
+ arguments: toolArgs ?? existingBlock?.arguments,
5884
+ output: toolOutput || existingBlock?.output || void 0
5885
+ };
5886
+ if (blockIndex >= 0) {
5887
+ blocks[blockIndex] = nextBlock;
5888
+ } else {
5889
+ blocks.push(nextBlock);
5890
+ }
5891
+ return {
5892
+ ...prev,
5893
+ sessionKey,
5894
+ tools,
5895
+ contentBlocks: blocks
5896
+ };
5897
+ });
5898
+ }
5899
+ function deriveToolStatus(stream, data) {
5900
+ const explicitStatus = normalizeString(data?.phase) || normalizeString(data?.status) || normalizeString(data?.state);
5901
+ if (explicitStatus) return explicitStatus;
5902
+ if (stream.includes("result") || stream.includes("output")) return "done";
5903
+ if (stream.includes("call")) return "running";
5904
+ return "running";
5905
+ }
5906
+ function asRecord(value) {
5907
+ return value && typeof value === "object" ? value : null;
5908
+ }
5909
+ function normalizeString(value) {
5910
+ return typeof value === "string" ? value.trim() : "";
5904
5911
  }
5905
5912
  const INITIAL_STATE = {
5906
5913
  active: false,
@@ -6018,101 +6025,6 @@ function useStreaming(options) {
6018
6025
  }, []);
6019
6026
  return { streaming: state, startStream: start, stopStream: stop };
6020
6027
  }
6021
- function handleAgentEvent(payload, fallbackSessionKey, options) {
6022
- const agentPayload = asRecord(payload);
6023
- const stream = normalizeString(agentPayload?.stream);
6024
- const runId = normalizeString(agentPayload?.runId);
6025
- const sessionKey = normalizeString(agentPayload?.sessionKey) || fallbackSessionKey;
6026
- const streamData = asRecord(agentPayload?.data);
6027
- if (runId) {
6028
- options.anyRunSeen.current = true;
6029
- if (stream === "lifecycle") {
6030
- const phase = normalizeString(streamData?.phase);
6031
- if (phase === "end" || phase === "error" || phase === "abort") {
6032
- options.activeRuns.delete(runId);
6033
- } else if (phase) {
6034
- options.activeRuns.add(runId);
6035
- }
6036
- } else {
6037
- options.activeRuns.add(runId);
6038
- }
6039
- }
6040
- if (stream === "assistant") {
6041
- const text = normalizeString(streamData?.delta) || normalizeString(streamData?.text);
6042
- if (!text) return;
6043
- options.setState((prev) => {
6044
- const blocks = [...prev.contentBlocks];
6045
- const lastBlock = blocks[blocks.length - 1];
6046
- if (lastBlock?.kind === "text") {
6047
- blocks[blocks.length - 1] = { ...lastBlock, text: lastBlock.text + text };
6048
- } else {
6049
- blocks.push({ kind: "text", text });
6050
- }
6051
- return {
6052
- ...prev,
6053
- sessionKey,
6054
- text: prev.text + text,
6055
- contentBlocks: blocks
6056
- };
6057
- });
6058
- options.onAssistantDelta?.({ text, sessionKey });
6059
- return;
6060
- }
6061
- if (!stream.includes("tool")) return;
6062
- const toolId = normalizeString(streamData?.toolCallId) || normalizeString(streamData?.id) || normalizeString(streamData?.callId) || `${runId || "tool"}:${normalizeString(streamData?.toolName) || normalizeString(streamData?.name) || "unknown"}`;
6063
- const toolName = normalizeString(streamData?.toolName) || normalizeString(streamData?.name) || "Tool";
6064
- const toolStatus = deriveToolStatus(stream, streamData);
6065
- const toolArgs = asRecord(streamData?.arguments) || asRecord(streamData?.input) || asRecord(streamData?.params) || null;
6066
- const toolOutput = normalizeString(streamData?.result) || normalizeString(streamData?.output) || (stream.includes("result") ? normalizeString(streamData?.text) : "");
6067
- options.setState((prev) => {
6068
- const tools = [...prev.tools];
6069
- const toolIndex = tools.findIndex((tool) => tool.id === toolId);
6070
- const nextTool = { id: toolId, name: toolName, status: toolStatus };
6071
- if (toolIndex >= 0) {
6072
- tools[toolIndex] = nextTool;
6073
- } else {
6074
- tools.push(nextTool);
6075
- }
6076
- const blocks = [...prev.contentBlocks];
6077
- const blockIndex = blocks.findIndex(
6078
- (b) => b.kind === "tool" && b.id === toolId
6079
- );
6080
- const existingBlock = blockIndex >= 0 ? blocks[blockIndex] : null;
6081
- const nextBlock = {
6082
- kind: "tool",
6083
- name: toolName,
6084
- id: toolId,
6085
- status: toolStatus,
6086
- // Merge: keep existing arguments/output if new event doesn't carry them
6087
- arguments: toolArgs ?? existingBlock?.arguments,
6088
- output: toolOutput || existingBlock?.output || void 0
6089
- };
6090
- if (blockIndex >= 0) {
6091
- blocks[blockIndex] = nextBlock;
6092
- } else {
6093
- blocks.push(nextBlock);
6094
- }
6095
- return {
6096
- ...prev,
6097
- sessionKey,
6098
- tools,
6099
- contentBlocks: blocks
6100
- };
6101
- });
6102
- }
6103
- function deriveToolStatus(stream, data) {
6104
- const explicitStatus = normalizeString(data?.phase) || normalizeString(data?.status) || normalizeString(data?.state);
6105
- if (explicitStatus) return explicitStatus;
6106
- if (stream.includes("result") || stream.includes("output")) return "done";
6107
- if (stream.includes("call")) return "running";
6108
- return "running";
6109
- }
6110
- function asRecord(value) {
6111
- return value && typeof value === "object" ? value : null;
6112
- }
6113
- function normalizeString(value) {
6114
- return typeof value === "string" ? value.trim() : "";
6115
- }
6116
6028
  function useKeyboardShortcuts(handlers) {
6117
6029
  const handleKeyDown = useCallback(
6118
6030
  (event) => {
@@ -6324,11 +6236,32 @@ const KeyboardShortcutsDialog = lazy(
6324
6236
  }))
6325
6237
  );
6326
6238
  const SearchDialog = lazy(
6327
- () => import("./search-dialog-CTJULPB8.js").then((m) => ({
6239
+ () => import("./search-dialog-CXhofdoP.js").then((m) => ({
6328
6240
  default: m.SearchDialog
6329
6241
  }))
6330
6242
  );
6331
6243
  const SEARCH_JUMP_TARGET_KEY = "opencami-search-jump-target";
6244
+ function extractRetryImageAttachments(message) {
6245
+ const parts = Array.isArray(message.content) ? message.content : [];
6246
+ const attachments = [];
6247
+ const payload = [];
6248
+ for (const [index, part] of parts.entries()) {
6249
+ if (part.type !== "image" || !("source" in part) || typeof part.source?.data !== "string" || part.source.data.length === 0) {
6250
+ continue;
6251
+ }
6252
+ const mimeType = typeof part.source?.media_type === "string" && part.source.media_type.length > 0 ? part.source.media_type : "image/jpeg";
6253
+ const content = part.source.data;
6254
+ attachments.push({
6255
+ id: crypto.randomUUID(),
6256
+ file: new File([], `retry-attachment-${index + 1}`, { type: mimeType }),
6257
+ preview: null,
6258
+ type: "image",
6259
+ base64: content
6260
+ });
6261
+ payload.push({ mimeType, content });
6262
+ }
6263
+ return { attachments, payload };
6264
+ }
6332
6265
  function ChatScreen({
6333
6266
  activeFriendlyId,
6334
6267
  isNewChat = false,
@@ -6672,7 +6605,6 @@ function ChatScreen({
6672
6605
  }
6673
6606
  }, [historyMessages, streamFinish]);
6674
6607
  useEffect(() => {
6675
- if (!llmTitlesEnabled) return;
6676
6608
  if (isNewChat || isRedirecting) return;
6677
6609
  const sessionKey = forcedSessionKey || resolvedSessionKey || activeSessionKey;
6678
6610
  if (!sessionKey) return;
@@ -6808,7 +6740,7 @@ function ChatScreen({
6808
6740
  queryClient,
6809
6741
  resolvedSessionKey
6810
6742
  ]);
6811
- function sendMessage(sessionKey, friendlyId, body, skipOptimistic = false, attachments, model) {
6743
+ function sendMessage(sessionKey, friendlyId, body, skipOptimistic = false, attachments, model, retryAttachmentsPayload) {
6812
6744
  let optimisticClientId = "";
6813
6745
  if (!skipOptimistic) {
6814
6746
  const { clientId, optimisticMessage } = createOptimisticMessage(
@@ -6834,10 +6766,14 @@ function ChatScreen({
6834
6766
  setError(null);
6835
6767
  setWaitingForResponse(true);
6836
6768
  setPinToTop(true);
6837
- const attachmentsPayload = attachments?.map((a) => ({
6838
- mimeType: a.file.type,
6839
- content: a.base64
6840
- }));
6769
+ const attachmentsPayload = retryAttachmentsPayload ?? attachments?.flatMap(
6770
+ (a) => a.base64 ? [
6771
+ {
6772
+ mimeType: a.file.type,
6773
+ content: a.base64
6774
+ }
6775
+ ] : []
6776
+ );
6841
6777
  streamingNotificationTextRef.current = "";
6842
6778
  streamStart();
6843
6779
  const preStreamKey = sessionKey || friendlyId;
@@ -7006,6 +6942,70 @@ function ChatScreen({
7006
6942
  return { ...state, isSidebarCollapsed: false };
7007
6943
  });
7008
6944
  }, [queryClient]);
6945
+ const handleRetryMessage = useCallback(
6946
+ (message) => {
6947
+ const messageText = textFromMessage(message);
6948
+ const {
6949
+ attachments: retryAttachments,
6950
+ payload: retryAttachmentsPayload
6951
+ } = extractRetryImageAttachments(message);
6952
+ if (!messageText && retryAttachments.length === 0) return;
6953
+ const sessionKeyForSend = forcedSessionKey || resolvedSessionKey || activeSessionKey;
6954
+ if (!sessionKeyForSend) return;
6955
+ const clientId = message.clientId;
6956
+ const optimisticId = message.__optimisticId;
6957
+ if (clientId) {
6958
+ removeHistoryMessageByClientId(
6959
+ queryClient,
6960
+ activeFriendlyId,
6961
+ sessionKeyForSend,
6962
+ clientId,
6963
+ optimisticId
6964
+ );
6965
+ }
6966
+ sendMessage(
6967
+ sessionKeyForSend,
6968
+ activeFriendlyId,
6969
+ messageText,
6970
+ false,
6971
+ retryAttachments,
6972
+ void 0,
6973
+ retryAttachmentsPayload
6974
+ );
6975
+ },
6976
+ [
6977
+ activeFriendlyId,
6978
+ activeSessionKey,
6979
+ forcedSessionKey,
6980
+ queryClient,
6981
+ resolvedSessionKey
6982
+ ]
6983
+ );
6984
+ const handleDismissMessage = useCallback(
6985
+ (message) => {
6986
+ const sessionKeyForSend = forcedSessionKey || resolvedSessionKey || activeSessionKey;
6987
+ if (!sessionKeyForSend) return;
6988
+ const clientId = message.clientId;
6989
+ const optimisticId = message.__optimisticId;
6990
+ if (clientId) {
6991
+ removeHistoryMessageByClientId(
6992
+ queryClient,
6993
+ activeFriendlyId,
6994
+ sessionKeyForSend,
6995
+ clientId,
6996
+ optimisticId
6997
+ );
6998
+ }
6999
+ setError(null);
7000
+ },
7001
+ [
7002
+ activeFriendlyId,
7003
+ activeSessionKey,
7004
+ forcedSessionKey,
7005
+ queryClient,
7006
+ resolvedSessionKey
7007
+ ]
7008
+ );
7009
7009
  const handleFollowUpClick = useCallback(
7010
7010
  (suggestion) => {
7011
7011
  if (isNewChat || !suggestion.trim()) return;
@@ -7164,7 +7164,9 @@ function ChatScreen({
7164
7164
  headerHeight,
7165
7165
  contentStyle: stableContentStyle,
7166
7166
  onFollowUpClick: handleFollowUpClick,
7167
- jumpToMessageId: searchJumpMessageId
7167
+ jumpToMessageId: searchJumpMessageId,
7168
+ onRetryMessage: handleRetryMessage,
7169
+ onDismissMessage: handleDismissMessage
7168
7170
  }
7169
7171
  ),
7170
7172
  /* @__PURE__ */ jsx(
@@ -7260,11 +7262,7 @@ const $sessionKey = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineP
7260
7262
  export {
7261
7263
  $sessionKey as $,
7262
7264
  Collapsible as C,
7263
- useLlmSettingsStore as a,
7264
- getLlmHeaders as b,
7265
- chatQueryKeys as c,
7266
- CollapsibleTrigger as d,
7267
- CollapsiblePanel as e,
7268
- getLlmProviderDefaults as g,
7269
- useLlmSettings as u
7265
+ CollapsibleTrigger as a,
7266
+ CollapsiblePanel as b,
7267
+ chatQueryKeys as c
7270
7268
  };