opencami 1.9.1 → 2.1.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 (74) hide show
  1. package/dist/client/assets/{CSPContext-B3PAVjBL.js → CSPContext-CxrBvJ99.js} +1 -1
  2. package/dist/client/assets/{DirectionContext-CR5CCisG.js → DirectionContext-BOL0P_1j.js} +1 -1
  3. package/dist/client/assets/_sessionKey-CTtu2ipR.js +25 -0
  4. package/dist/client/assets/agents-D7JS19Lo.js +2 -0
  5. package/dist/client/assets/{agents-screen--ZMzN5cB.js → agents-screen-CqdzZKaR.js} +1 -1
  6. package/dist/client/assets/bots-DAInzfLx.js +2 -0
  7. package/dist/client/assets/{bots-screen-C_uJBNwI.js → bots-screen-RYovD6cu.js} +1 -1
  8. package/dist/client/assets/button-Bv-7bwZZ.js +1 -0
  9. package/dist/client/assets/{composite-DArWbFHm.js → composite-DwQIZFe9.js} +1 -1
  10. package/dist/client/assets/{connect-B48HecjN.js → connect-DZSrhhNT.js} +1 -1
  11. package/dist/client/assets/{dashboard-BVKx9FMy.js → dashboard-y8KVUVF-.js} +1 -1
  12. package/dist/client/assets/{event-BCwqPPkP.js → event-Cnr9OETn.js} +1 -1
  13. package/dist/client/assets/file-explorer-screen-C_mYUJsr.js +1 -0
  14. package/dist/client/assets/files-CG0jFYdO.js +2 -0
  15. package/dist/client/assets/follow-up-suggestions-C6RzpstA.js +2 -0
  16. package/dist/client/assets/{index-0gdwm1_H.js → index-3-ASY4Cl.js} +1 -1
  17. package/dist/client/assets/index-BZxJSF4T.js +3 -0
  18. package/dist/client/assets/{keyboard-shortcuts-dialog-CK_XTzLr.js → keyboard-shortcuts-dialog-jm4-DftO.js} +1 -1
  19. package/dist/client/assets/{main-B2CrcRuC.js → main-CbISK-pm.js} +3 -3
  20. package/dist/client/assets/{markdown-CdkuX06F.js → markdown-UD7RzskQ.js} +1 -1
  21. package/dist/client/assets/memory-BYRJ1l_4.js +2 -0
  22. package/dist/client/assets/{memory-screen-C1RfLy-d.js → memory-screen-BRsm-Q8A.js} +1 -1
  23. package/dist/client/assets/{menu-CIwnliij.js → menu-DRjoHFvG.js} +1 -1
  24. package/dist/client/assets/{opencami-logo-SVuYD55V.js → opencami-logo-D2mv84sX.js} +1 -1
  25. package/dist/client/assets/{proxy-BijR8W1L.js → proxy-DtxqfBHY.js} +1 -1
  26. package/dist/client/assets/{react-DWx7OvUo.js → react-C_7UlEtd.js} +1 -1
  27. package/dist/client/assets/{search-dialog-CB4KE8ec.js → search-dialog-lsT8JUtT.js} +1 -1
  28. package/dist/client/assets/{search-sources-badge-B8Z-8sSf.js → search-sources-badge-BnqKxoia.js} +1 -1
  29. package/dist/client/assets/{session-export-dialog-tDiFuv3a.js → session-export-dialog-DHHpvwI8.js} +1 -1
  30. package/dist/client/assets/settings-dialog-vrd0eJGw.js +1 -0
  31. package/dist/client/assets/skills-Svd-7oxQ.js +2 -0
  32. package/dist/client/assets/{skills-panel-fjJQVMog.js → skills-panel-Dkuo0yyD.js} +1 -1
  33. package/dist/client/assets/styles-CWabEzNU.css +1 -0
  34. package/dist/client/assets/{switch-Bn5uei2k.js → switch-BYFBYPmi.js} +1 -1
  35. package/dist/client/assets/{tabs-DOBNAUVE.js → tabs-1jrPkdUE.js} +1 -1
  36. package/dist/client/assets/{thinking-dfGrFAMV.js → thinking-D9BBn5kK.js} +1 -1
  37. package/dist/client/assets/{tooltip-DOKkNFvu.js → tooltip-DzFgDY86.js} +1 -1
  38. package/dist/client/assets/{use-file-explorer-state-BAa6Cxyr.js → use-file-explorer-state-Dwhw6Mpg.js} +2 -2
  39. package/dist/client/assets/{useBaseUiId-DFpBD0sg.js → useBaseUiId-BdQC9KF3.js} +1 -1
  40. package/dist/client/assets/useCompositeItem-BI09zNFD.js +1 -0
  41. package/dist/client/assets/{useControlled-CQHE0ITz.js → useControlled-Bc2Emhlx.js} +1 -1
  42. package/dist/client/assets/{useMutation-BFl-7GnD.js → useMutation-CMp81lDO.js} +1 -1
  43. package/dist/client/assets/{useOnFirstRender-DlXHIIGk.js → useOnFirstRender-DpB3w0o4.js} +1 -1
  44. package/dist/client/assets/{useQuery-D-sF8Tld.js → useQuery-_BEWdUe1.js} +1 -1
  45. package/dist/server/assets/{_sessionKey-D8TGrDRM.js → _sessionKey-COz7RLKC.js} +170 -279
  46. package/dist/server/assets/_tanstack-start-manifest_v-D0f-0Utn.js +4 -0
  47. package/dist/server/assets/{connect-CTVBm0Vc.js → connect-sd_NDvQN.js} +1 -1
  48. package/dist/server/assets/follow-up-suggestions-DRQqO8_S.js +183 -0
  49. package/dist/server/assets/{index-B_F4DTUu.js → index-197lqzv2.js} +2 -0
  50. package/dist/server/assets/{index-gRco4Ina.js → index-sXe2QC5V.js} +1 -1
  51. package/dist/server/assets/{markdown-CFdYXCRQ.js → markdown-Dk1YdosA.js} +1 -1
  52. package/dist/server/assets/{memory-screen-vqXczcVo.js → memory-screen-DA_o-N1Z.js} +2 -2
  53. package/dist/server/assets/{memory-rBB015W-.js → memory-woPzJFfs.js} +1 -1
  54. package/dist/server/assets/{router-DaKDqc9w.js → router-CZhxPkYW.js} +347 -355
  55. package/dist/server/assets/{search-dialog-DSSK93kq.js → search-dialog-DlvWfq1e.js} +5 -5
  56. package/dist/server/assets/{settings-dialog-DyWNblva.js → settings-dialog-BGtrS2RJ.js} +63 -255
  57. package/dist/server/assets/{thinking-CU0FRlzT.js → thinking-fzwT_aVT.js} +5 -5
  58. package/dist/server/server.js +2 -2
  59. package/package.json +1 -1
  60. package/dist/client/assets/_sessionKey-Bg_9uype.js +0 -23
  61. package/dist/client/assets/agents-BiTHBb6Z.js +0 -2
  62. package/dist/client/assets/bots-DxhRnQp5.js +0 -2
  63. package/dist/client/assets/button-BciDmec0.js +0 -1
  64. package/dist/client/assets/file-explorer-screen-DPs-FWeA.js +0 -1
  65. package/dist/client/assets/files-SEycwYCa.js +0 -2
  66. package/dist/client/assets/follow-up-suggestions-BPjWBpiy.js +0 -5
  67. package/dist/client/assets/index-CGeJcqZ3.js +0 -3
  68. package/dist/client/assets/memory-C7lKdkmc.js +0 -2
  69. package/dist/client/assets/settings-dialog-aL-AH4Rt.js +0 -1
  70. package/dist/client/assets/skills-D1T6uemU.js +0 -2
  71. package/dist/client/assets/styles-D0L88B64.css +0 -1
  72. package/dist/client/assets/useCompositeItem-B-Axq9-D.js +0 -1
  73. package/dist/server/assets/_tanstack-start-manifest_v-DalBo2bY.js +0 -4
  74. package/dist/server/assets/follow-up-suggestions-C65ptDij.js +0 -336
@@ -14,12 +14,12 @@ import { Collapsible as Collapsible$1 } from "@base-ui/react/collapsible";
14
14
  import { ScrollArea } from "@base-ui/react/scroll-area";
15
15
  import { M as MenuRoot, a as MenuTrigger, b as MenuContent, c as MenuItem } from "./menu-D8cKTpmN.js";
16
16
  import { O as OpenCamiLogo, a as OpenCamiText } from "./opencami-logo-C-43FL3R.js";
17
- import { M as Markdown } from "./markdown-CFdYXCRQ.js";
18
- import { u as useChatSettings$1 } from "./index-B_F4DTUu.js";
17
+ import { u as useChatSettings$1 } from "./index-197lqzv2.js";
18
+ import { M as Markdown } from "./markdown-Dk1YdosA.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-DaKDqc9w.js";
22
+ import { a as Route } from "./router-CZhxPkYW.js";
23
23
  function deriveFriendlyIdFromKey(key) {
24
24
  if (!key) return "main";
25
25
  const trimmed = key.trim();
@@ -51,9 +51,21 @@ function stripInboundMeta(text) {
51
51
  s = s.replace(SUPERMEMORY_CONTEXT_REGEX, "").replace(SUPERMEMORY_CONTAINERS_REGEX, "").replace(INBOUND_SENDER_FENCED_REGEX, "").replace(INBOUND_SENDER_INLINE_REGEX, "").replace(INBOUND_META_TIMESTAMP_REGEX, "").replace(MULTI_BLANK_LINES_REGEX, "\n\n");
52
52
  return s.trim();
53
53
  }
54
+ function joinTextParts(parts) {
55
+ return parts.reduce((joined, part) => {
56
+ if (!part) return joined;
57
+ if (!joined) return part;
58
+ if (/\s$/.test(joined) || /^\s/.test(part)) return `${joined}${part}`;
59
+ if (/[([{"'`„“‚‘]$/.test(joined)) return `${joined}${part}`;
60
+ if (/^[,.;:!?%\])}"'`]/.test(part)) return `${joined}${part}`;
61
+ return `${joined} ${part}`;
62
+ }, "");
63
+ }
54
64
  function textFromMessage(msg) {
55
65
  const parts = Array.isArray(msg.content) ? msg.content : [];
56
- const raw = parts.map((part) => part.type === "text" ? String(part.text ?? "") : "").join("").trim();
66
+ const raw = joinTextParts(
67
+ parts.map((part) => part.type === "text" ? String(part.text ?? "") : "")
68
+ ).trim();
57
69
  return stripInboundMeta(raw);
58
70
  }
59
71
  function getToolCallsFromMessage(msg) {
@@ -233,12 +245,30 @@ function updateHistoryMessages(queryClient, friendlyId, sessionKey, updater) {
233
245
  };
234
246
  });
235
247
  }
248
+ function normalizeId(value) {
249
+ return typeof value === "string" ? value.trim() : "";
250
+ }
251
+ function areSameHistoryMessage(a, b) {
252
+ const aId = normalizeId(a.id);
253
+ const bId = normalizeId(b.id);
254
+ if (aId && bId) return aId === bId;
255
+ const aClientId = normalizeId(a.clientId);
256
+ const bClientId = normalizeId(b.clientId);
257
+ if (aClientId && bClientId) return aClientId === bClientId;
258
+ const aOptimisticId = normalizeId(a.__optimisticId);
259
+ const bOptimisticId = normalizeId(b.__optimisticId);
260
+ if (aOptimisticId && bOptimisticId) return aOptimisticId === bOptimisticId;
261
+ return false;
262
+ }
236
263
  function appendHistoryMessage(queryClient, friendlyId, sessionKey, message) {
237
264
  updateHistoryMessages(
238
265
  queryClient,
239
266
  friendlyId,
240
267
  sessionKey,
241
268
  function append(messages) {
269
+ if (messages.some((existing) => areSameHistoryMessage(existing, message))) {
270
+ return messages;
271
+ }
242
272
  return [...messages, message];
243
273
  }
244
274
  );
@@ -355,7 +385,9 @@ async function updateSessionLabel(queryClient, sessionKey, friendlyId, label) {
355
385
  if (!res.ok) {
356
386
  const errorText = await readError(res);
357
387
  console.error("[updateSessionLabel] Failed to persist:", errorText);
388
+ return;
358
389
  }
390
+ await queryClient.invalidateQueries({ queryKey: chatQueryKeys.sessions });
359
391
  } catch (err) {
360
392
  console.error("[updateSessionLabel] Network error:", err);
361
393
  }
@@ -1686,7 +1718,7 @@ function areSidebarSessionsEqual(prev, next) {
1686
1718
  return true;
1687
1719
  }
1688
1720
  const SettingsDialog = lazy(
1689
- () => import("./settings-dialog-DyWNblva.js").then((m) => ({ default: m.SettingsDialog }))
1721
+ () => import("./settings-dialog-BGtrS2RJ.js").then((m) => ({ default: m.SettingsDialog }))
1690
1722
  );
1691
1723
  const SessionExportDialog = lazy(
1692
1724
  () => import("./session-export-dialog-CgtlOnwf.js").then((m) => ({
@@ -1734,6 +1766,13 @@ function ChatSidebarComponent({
1734
1766
  const [exportSessionKey, setExportSessionKey] = useState(null);
1735
1767
  const [exportFriendlyId, setExportFriendlyId] = useState(null);
1736
1768
  const [exportSessionTitle, setExportSessionTitle] = useState("");
1769
+ const [showCrons] = useState(() => {
1770
+ try {
1771
+ return typeof window !== "undefined" ? localStorage.getItem("opencami-cron-manager") !== "false" : true;
1772
+ } catch {
1773
+ return true;
1774
+ }
1775
+ });
1737
1776
  const queryClient = useQueryClient();
1738
1777
  function handleOpenRename(session) {
1739
1778
  setRenameSessionKey(session.key);
@@ -2064,13 +2103,7 @@ function ChatSidebarComponent({
2064
2103
  ) }),
2065
2104
  isCollapsed && /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: "Agents" })
2066
2105
  ] }) }),
2067
- (() => {
2068
- try {
2069
- return localStorage.getItem("opencami-cron-manager") === "true";
2070
- } catch {
2071
- return false;
2072
- }
2073
- })() && /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(TooltipRoot, { children: [
2106
+ showCrons && /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(TooltipRoot, { children: [
2074
2107
  /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
2075
2108
  Link,
2076
2109
  {
@@ -2108,7 +2141,7 @@ function ChatSidebarComponent({
2108
2141
  ] }) }),
2109
2142
  (() => {
2110
2143
  try {
2111
- return localStorage.getItem("feature_dashboard") === "true";
2144
+ return localStorage.getItem("opencami-feature_dashboard") === "true";
2112
2145
  } catch {
2113
2146
  return false;
2114
2147
  }
@@ -2833,7 +2866,7 @@ function Tool({ toolPart, defaultOpen = false }) {
2833
2866
  ] }) });
2834
2867
  }
2835
2868
  const Thinking = lazy(
2836
- () => import("./thinking-CU0FRlzT.js").then((m) => ({
2869
+ () => import("./thinking-fzwT_aVT.js").then((m) => ({
2837
2870
  default: m.Thinking
2838
2871
  }))
2839
2872
  );
@@ -3502,7 +3535,7 @@ function TypingIndicator({ className }) {
3502
3535
  ] });
3503
3536
  }
3504
3537
  const FollowUpSuggestions = lazy(
3505
- () => import("./follow-up-suggestions-C65ptDij.js").then((m) => ({
3538
+ () => import("./follow-up-suggestions-DRQqO8_S.js").then((m) => ({
3506
3539
  default: m.FollowUpSuggestions
3507
3540
  }))
3508
3541
  );
@@ -3531,6 +3564,7 @@ function ChatMessageListComponent({
3531
3564
  const prevPinRef = useRef(pinToTop);
3532
3565
  const prevUserIndexRef = useRef(void 0);
3533
3566
  const [highlightedMessageId, setHighlightedMessageId] = useState(null);
3567
+ const { settings } = useChatSettings$1();
3534
3568
  const displayMessages = useMemo(() => {
3535
3569
  return messages.filter((msg) => msg.role !== "toolResult");
3536
3570
  }, [messages]);
@@ -3659,7 +3693,19 @@ function ChatMessageListComponent({
3659
3693
  const hasGroup = pinToTop && groupStartIndex >= 0;
3660
3694
  const lastAssistantMessage = typeof lastTextAssistantIndex === "number" ? displayMessages[lastTextAssistantIndex] : void 0;
3661
3695
  const lastAssistantText = lastAssistantMessage ? textFromMessage(lastAssistantMessage) : "";
3662
- const showFollowUps = !waitingForResponse && !isStreaming && lastAssistantText.length > 0 && onFollowUpClick !== void 0 && (typeof lastUserIndex !== "number" || typeof lastTextAssistantIndex !== "number" || lastTextAssistantIndex > lastUserIndex);
3696
+ const followUpConversationContext = useMemo(() => {
3697
+ if (typeof lastTextAssistantIndex !== "number") {
3698
+ return "";
3699
+ }
3700
+ const recentMessages = displayMessages.slice(Math.max(0, lastTextAssistantIndex - 5), lastTextAssistantIndex + 1).map((message) => {
3701
+ const text = textFromMessage(message).trim();
3702
+ if (!text) return null;
3703
+ const role = message.role === "user" ? "User" : "Assistant";
3704
+ return `${role}: ${text}`;
3705
+ }).filter((line) => Boolean(line));
3706
+ return recentMessages.join("\n\n").slice(0, 4e3);
3707
+ }, [displayMessages, lastTextAssistantIndex]);
3708
+ const showFollowUps = settings.showFollowUps && !waitingForResponse && !isStreaming && lastAssistantText.length > 0 && onFollowUpClick !== void 0 && (typeof lastUserIndex !== "number" || typeof lastTextAssistantIndex !== "number" || lastTextAssistantIndex > lastUserIndex);
3663
3709
  useLayoutEffect(() => {
3664
3710
  if (loading) return;
3665
3711
  if (pinToTop) {
@@ -3767,6 +3813,7 @@ function ChatMessageListComponent({
3767
3813
  FollowUpSuggestions,
3768
3814
  {
3769
3815
  responseText: lastAssistantText,
3816
+ conversationContext: followUpConversationContext,
3770
3817
  onSuggestionClick: onFollowUpClick,
3771
3818
  disabled: waitingForResponse
3772
3819
  }
@@ -3802,6 +3849,7 @@ function ChatMessageListComponent({
3802
3849
  FollowUpSuggestions,
3803
3850
  {
3804
3851
  responseText: lastAssistantText,
3852
+ conversationContext: followUpConversationContext,
3805
3853
  onSuggestionClick: onFollowUpClick,
3806
3854
  disabled: waitingForResponse
3807
3855
  }
@@ -5491,8 +5539,9 @@ function useChatHistory({
5491
5539
  sessionKey: sessionKeyForHistory,
5492
5540
  friendlyId: activeFriendlyId
5493
5541
  });
5542
+ const dedupedServerMessages = dedupeHistoryMessages(serverData.messages);
5494
5543
  const serverMessages = restoreCachedImageParts(
5495
- serverData.messages,
5544
+ dedupedServerMessages,
5496
5545
  cachedMessages
5497
5546
  );
5498
5547
  if (!optimisticMessages.length) {
@@ -5564,6 +5613,39 @@ function findMatchIndex(serverMessages, optimisticMessage) {
5564
5613
  return Math.abs(optimisticTime - serverTime) <= 1e4;
5565
5614
  });
5566
5615
  }
5616
+ function normalizeMessageIdValue(value) {
5617
+ return typeof value === "string" ? value.trim() : "";
5618
+ }
5619
+ function getMessageIdentity(message) {
5620
+ const id = normalizeMessageIdValue(message.id);
5621
+ if (id) return `id:${id}`;
5622
+ const clientId = normalizeMessageIdValue(message.clientId);
5623
+ if (clientId) return `client:${clientId}`;
5624
+ const optimisticId = normalizeMessageIdValue(message.__optimisticId);
5625
+ if (optimisticId) return `optimistic:${optimisticId}`;
5626
+ const toolCallId = normalizeMessageIdValue(message.toolCallId);
5627
+ if (message.role === "toolResult" && toolCallId) {
5628
+ return `tool-result:${toolCallId}`;
5629
+ }
5630
+ const timestampBucket = Math.floor(getMessageTimestamp(message) / 1e3);
5631
+ const text = textFromMessage(message);
5632
+ if (text) {
5633
+ return `fallback:${message.role ?? "unknown"}:${timestampBucket}:${text}`;
5634
+ }
5635
+ return `fallback:${message.role ?? "unknown"}:${timestampBucket}:${JSON.stringify(message.content ?? [])}`;
5636
+ }
5637
+ function dedupeHistoryMessages(messages) {
5638
+ if (!Array.isArray(messages) || messages.length <= 1) return messages;
5639
+ const deduped = [];
5640
+ const seen = /* @__PURE__ */ new Set();
5641
+ for (const message of messages) {
5642
+ const identity = getMessageIdentity(message);
5643
+ if (seen.has(identity)) continue;
5644
+ seen.add(identity);
5645
+ deduped.push(message);
5646
+ }
5647
+ return deduped;
5648
+ }
5567
5649
  function getImageParts(message) {
5568
5650
  if (!Array.isArray(message.content)) return [];
5569
5651
  return message.content.filter((part) => {
@@ -5723,268 +5805,68 @@ function useChatSessions({
5723
5805
  sessionsError
5724
5806
  };
5725
5807
  }
5726
- const LLM_PROVIDER_DEFAULTS = {
5727
- openai: {
5728
- baseUrl: "https://api.openai.com/v1",
5729
- model: "gpt-4.1-nano"
5730
- },
5731
- openrouter: {
5732
- baseUrl: "https://openrouter.ai/api/v1",
5733
- model: "openai/gpt-oss-120b"
5734
- },
5735
- kilocode: {
5736
- baseUrl: "https://api.kilo.ai/api/gateway",
5737
- model: "google/gemini-2.5-flash"
5738
- },
5739
- ollama: {
5740
- baseUrl: "http://localhost:11434/v1",
5741
- model: "llama3.2"
5742
- },
5743
- custom: {
5744
- baseUrl: "",
5745
- model: ""
5746
- }
5747
- };
5748
- const DEFAULT_LLM_SETTINGS = {
5749
- useLlmTitles: true,
5750
- useLlmFollowUps: true,
5751
- llmProvider: "openai",
5752
- llmBaseUrl: "",
5753
- llmModel: "",
5754
- llmApiKey: ""
5755
- };
5756
- function getLlmProviderDefaults(provider) {
5757
- return LLM_PROVIDER_DEFAULTS[provider];
5758
- }
5759
- function getEffectiveLlmBaseUrl(settings) {
5760
- if (settings.llmBaseUrl.trim()) return settings.llmBaseUrl.trim();
5761
- return getLlmProviderDefaults(settings.llmProvider).baseUrl;
5762
- }
5763
- function getEffectiveLlmModel(settings) {
5764
- if (settings.llmModel.trim()) return settings.llmModel.trim();
5765
- return getLlmProviderDefaults(settings.llmProvider).model;
5766
- }
5767
- function getAvailability(settings, hasEnvKey) {
5768
- if (settings.llmProvider === "ollama") return true;
5769
- if (settings.llmProvider === "custom") {
5770
- return Boolean(settings.llmApiKey.trim()) || Boolean(settings.llmBaseUrl.trim() && settings.llmModel.trim());
5771
- }
5772
- return hasEnvKey || Boolean(settings.llmApiKey.trim());
5773
- }
5774
- function migratePersistedState(persistedState) {
5775
- if (!persistedState || typeof persistedState !== "object") {
5776
- return { settings: DEFAULT_LLM_SETTINGS };
5777
- }
5778
- const { settings } = persistedState;
5779
- if (!settings) {
5780
- return { settings: DEFAULT_LLM_SETTINGS };
5781
- }
5782
- const { openaiApiKey, ...rest } = settings;
5783
- const llmApiKey = rest.llmApiKey ?? openaiApiKey ?? "";
5784
- return {
5785
- settings: {
5786
- ...DEFAULT_LLM_SETTINGS,
5787
- ...rest,
5788
- llmApiKey
5789
- }
5790
- };
5791
- }
5792
- const useLlmSettingsStore = create()(
5793
- persist(
5794
- (set) => ({
5795
- settings: {
5796
- ...DEFAULT_LLM_SETTINGS
5797
- },
5798
- updateSettings: (updates) => set((state) => ({
5799
- settings: { ...state.settings, ...updates }
5800
- })),
5801
- clearApiKey: () => set((state) => ({
5802
- settings: { ...state.settings, llmApiKey: "" }
5803
- }))
5804
- }),
5805
- {
5806
- name: "llm-settings",
5807
- version: 2,
5808
- migrate: (persistedState) => migratePersistedState(persistedState)
5809
- }
5810
- )
5811
- );
5812
- function useLlmSettings() {
5813
- const settings = useLlmSettingsStore((state) => state.settings);
5814
- const updateSettings = useLlmSettingsStore((state) => state.updateSettings);
5815
- const clearApiKey = useLlmSettingsStore((state) => state.clearApiKey);
5816
- const [status, setStatus] = useState({
5817
- hasEnvKey: false,
5818
- hasOpenRouterKey: false,
5819
- hasKilocodeKey: false,
5820
- hasUserKey: Boolean(settings.llmApiKey),
5821
- isAvailable: getAvailability(settings, false),
5822
- isLoading: true,
5823
- error: null
5824
- });
5825
- useEffect(() => {
5826
- let cancelled = false;
5827
- async function checkStatus() {
5828
- try {
5829
- const res = await fetch("/api/llm-features");
5830
- if (!res.ok) throw new Error("Failed to check LLM status");
5831
- const data = await res.json();
5832
- if (cancelled) return;
5833
- const hasUserKey = Boolean(settings.llmApiKey);
5834
- const hasProviderKey = settings.llmProvider === "openrouter" ? Boolean(data.hasOpenRouterKey) : settings.llmProvider === "kilocode" ? Boolean(data.hasKilocodeKey) : data.hasEnvKey;
5835
- setStatus({
5836
- hasEnvKey: data.hasEnvKey,
5837
- hasOpenRouterKey: Boolean(data.hasOpenRouterKey),
5838
- hasKilocodeKey: Boolean(data.hasKilocodeKey),
5839
- hasUserKey,
5840
- isAvailable: getAvailability(settings, hasProviderKey),
5841
- isLoading: false,
5842
- error: null
5843
- });
5844
- } catch (err) {
5845
- if (cancelled) return;
5846
- setStatus((prev) => ({
5847
- ...prev,
5848
- isLoading: false,
5849
- error: err instanceof Error ? err.message : "Failed to check status"
5850
- }));
5851
- }
5808
+ function useSmartTitle() {
5809
+ const { settings } = useChatSettings$1();
5810
+ const [isGenerating, setIsGenerating] = useState(false);
5811
+ const [lastTitle, setLastTitle] = useState(null);
5812
+ const [lastSource, setLastSource] = useState(null);
5813
+ const abortControllerRef = useRef(null);
5814
+ const generateTitle = useCallback(async (message) => {
5815
+ if (abortControllerRef.current) {
5816
+ abortControllerRef.current.abort();
5852
5817
  }
5853
- void checkStatus();
5854
- return () => {
5855
- cancelled = true;
5856
- };
5857
- }, [
5858
- settings.llmApiKey,
5859
- settings.llmProvider,
5860
- settings.llmBaseUrl,
5861
- settings.llmModel
5862
- ]);
5863
- const testApiKey = useCallback(async (key) => {
5818
+ const controller = new AbortController();
5819
+ abortControllerRef.current = controller;
5820
+ setIsGenerating(true);
5864
5821
  try {
5865
- const headers = buildLlmHeaders({
5866
- ...settings,
5867
- llmApiKey: key
5868
- });
5869
5822
  const res = await fetch("/api/llm-features", {
5870
5823
  method: "POST",
5871
5824
  headers: {
5872
- "Content-Type": "application/json",
5873
- ...headers
5825
+ "Content-Type": "application/json"
5874
5826
  },
5875
- body: JSON.stringify({ action: "test" })
5827
+ body: JSON.stringify({
5828
+ action: "title",
5829
+ message,
5830
+ model: settings.llmFeaturesModel ? `openclaw/${settings.llmFeaturesModel}` : void 0
5831
+ }),
5832
+ signal: controller.signal
5876
5833
  });
5834
+ if (!res.ok) {
5835
+ throw new Error(`API error: ${res.status}`);
5836
+ }
5877
5837
  const data = await res.json();
5878
- if (!data.ok) {
5879
- return { valid: false, error: data.error || "Test failed" };
5838
+ if (controller.signal.aborted) {
5839
+ throw new Error("Aborted");
5880
5840
  }
5881
- return { valid: data.valid ?? false, error: data.error };
5882
- } catch (err) {
5841
+ const rawTitle = data.title || message.slice(0, 50);
5842
+ const title = rawTitle.length > 64 ? rawTitle.slice(0, 61) + "..." : rawTitle;
5843
+ const source = data.source || "heuristic";
5844
+ setLastTitle(title);
5845
+ setLastSource(source);
5883
5846
  return {
5884
- valid: false,
5885
- error: err instanceof Error ? err.message : "Network error"
5847
+ title,
5848
+ source,
5849
+ error: data.error
5886
5850
  };
5887
- }
5888
- }, [settings]);
5889
- return {
5890
- settings,
5891
- updateSettings,
5892
- clearApiKey,
5893
- status,
5894
- testApiKey
5895
- };
5896
- }
5897
- function buildLlmHeaders(settings) {
5898
- const apiKey = settings.llmApiKey;
5899
- const baseUrl = getEffectiveLlmBaseUrl(settings);
5900
- const model = getEffectiveLlmModel(settings);
5901
- if (apiKey) {
5902
- return {
5903
- "X-OpenAI-API-Key": apiKey,
5904
- ...baseUrl ? { "X-LLM-Base-URL": baseUrl } : {},
5905
- ...model ? { "X-LLM-Model": model } : {}
5906
- };
5907
- }
5908
- return {
5909
- ...baseUrl ? { "X-LLM-Base-URL": baseUrl } : {},
5910
- ...model ? { "X-LLM-Model": model } : {}
5911
- };
5912
- }
5913
- function getLlmHeaders() {
5914
- const settings = useLlmSettingsStore.getState().settings;
5915
- return buildLlmHeaders(settings);
5916
- }
5917
- function useSmartTitle() {
5918
- const llmSettings = useLlmSettingsStore((state) => state.settings);
5919
- const [isGenerating, setIsGenerating] = useState(false);
5920
- const [lastTitle, setLastTitle] = useState(null);
5921
- const [lastSource, setLastSource] = useState(null);
5922
- const abortControllerRef = useRef(null);
5923
- const generateTitle = useCallback(
5924
- async (message) => {
5925
- if (abortControllerRef.current) {
5926
- abortControllerRef.current.abort();
5851
+ } catch (err) {
5852
+ if (err instanceof Error && err.name === "AbortError") {
5853
+ throw err;
5927
5854
  }
5928
- const controller = new AbortController();
5929
- abortControllerRef.current = controller;
5930
- setIsGenerating(true);
5931
- try {
5932
- const headers = {
5933
- "Content-Type": "application/json",
5934
- ...getLlmHeaders()
5935
- };
5936
- const res = await fetch("/api/llm-features", {
5937
- method: "POST",
5938
- headers,
5939
- body: JSON.stringify({
5940
- action: "title",
5941
- message
5942
- }),
5943
- signal: controller.signal
5944
- });
5945
- if (!res.ok) {
5946
- throw new Error(`API error: ${res.status}`);
5947
- }
5948
- const data = await res.json();
5949
- if (controller.signal.aborted) {
5950
- throw new Error("Aborted");
5951
- }
5952
- const rawTitle = data.title || message.slice(0, 50);
5953
- const title = rawTitle.length > 64 ? rawTitle.slice(0, 61) + "..." : rawTitle;
5954
- const source = data.source || "heuristic";
5955
- setLastTitle(title);
5956
- setLastSource(source);
5957
- return {
5958
- title,
5959
- source,
5960
- error: data.error
5961
- };
5962
- } catch (err) {
5963
- if (err instanceof Error && err.name === "AbortError") {
5964
- throw err;
5965
- }
5966
- const fallbackTitle = generateHeuristicTitle(message);
5967
- setLastTitle(fallbackTitle);
5968
- setLastSource("heuristic");
5969
- return {
5970
- title: fallbackTitle,
5971
- source: "heuristic",
5972
- error: err instanceof Error ? err.message : "Unknown error"
5973
- };
5974
- } finally {
5975
- setIsGenerating(false);
5976
- if (abortControllerRef.current === controller) {
5977
- abortControllerRef.current = null;
5978
- }
5855
+ const fallbackTitle = generateHeuristicTitle(message);
5856
+ setLastTitle(fallbackTitle);
5857
+ setLastSource("heuristic");
5858
+ return {
5859
+ title: fallbackTitle,
5860
+ source: "heuristic",
5861
+ error: err instanceof Error ? err.message : "Unknown error"
5862
+ };
5863
+ } finally {
5864
+ setIsGenerating(false);
5865
+ if (abortControllerRef.current === controller) {
5866
+ abortControllerRef.current = null;
5979
5867
  }
5980
- },
5981
- [
5982
- llmSettings.llmApiKey,
5983
- llmSettings.llmBaseUrl,
5984
- llmSettings.llmModel,
5985
- llmSettings.llmProvider
5986
- ]
5987
- );
5868
+ }
5869
+ }, [settings.llmFeaturesModel]);
5988
5870
  return {
5989
5871
  generateTitle,
5990
5872
  isGenerating,
@@ -6014,7 +5896,7 @@ function generateHeuristicTitle(message) {
6014
5896
  return title || message.slice(0, 50);
6015
5897
  }
6016
5898
  function useLlmTitlesEnabled() {
6017
- return useLlmSettingsStore((state) => state.settings.useLlmTitles);
5899
+ return true;
6018
5900
  }
6019
5901
  function handleAgentEvent(payload, fallbackSessionKey, options) {
6020
5902
  const agentPayload = asRecord(payload);
@@ -6036,8 +5918,8 @@ function handleAgentEvent(payload, fallbackSessionKey, options) {
6036
5918
  }
6037
5919
  }
6038
5920
  if (stream === "assistant") {
6039
- const text = normalizeString(streamData?.delta) || normalizeString(streamData?.text);
6040
- if (!text) return;
5921
+ const text = rawString(streamData?.delta) || rawString(streamData?.text);
5922
+ if (text.length === 0) return;
6041
5923
  options.setState((prev) => {
6042
5924
  const blocks = [...prev.contentBlocks];
6043
5925
  const lastBlock = blocks[blocks.length - 1];
@@ -6111,6 +5993,9 @@ function asRecord(value) {
6111
5993
  function normalizeString(value) {
6112
5994
  return typeof value === "string" ? value.trim() : "";
6113
5995
  }
5996
+ function rawString(value) {
5997
+ return typeof value === "string" ? value : "";
5998
+ }
6114
5999
  const INITIAL_STATE = {
6115
6000
  active: false,
6116
6001
  text: "",
@@ -6438,7 +6323,7 @@ const KeyboardShortcutsDialog = lazy(
6438
6323
  }))
6439
6324
  );
6440
6325
  const SearchDialog = lazy(
6441
- () => import("./search-dialog-DSSK93kq.js").then((m) => ({
6326
+ () => import("./search-dialog-DlvWfq1e.js").then((m) => ({
6442
6327
  default: m.SearchDialog
6443
6328
  }))
6444
6329
  );
@@ -6682,9 +6567,10 @@ function ChatScreen({
6682
6567
  }, [streaming.contentBlocks]);
6683
6568
  const messagesWithStreaming = useMemo(() => {
6684
6569
  if (!streamingMessage) return displayMessages;
6570
+ const lastVisibleIndex = [...displayMessages].map((message, index) => ({ message, index })).filter(({ message }) => message.role !== "toolResult").map(({ index }) => index).pop();
6685
6571
  const lastAssistantIndex = [...displayMessages].map((message, index) => ({ message, index })).filter(({ message }) => message.role === "assistant").map(({ index }) => index).pop();
6686
6572
  const lastUserIndex = [...displayMessages].map((message, index) => ({ message, index })).filter(({ message }) => message.role === "user").map(({ index }) => index).pop();
6687
- const assistantIsLatestTurn = typeof lastAssistantIndex === "number" && (typeof lastUserIndex !== "number" || lastAssistantIndex > lastUserIndex);
6573
+ const assistantIsLatestTurn = typeof lastAssistantIndex === "number" && lastAssistantIndex === lastVisibleIndex && (typeof lastUserIndex !== "number" || lastAssistantIndex > lastUserIndex);
6688
6574
  if (!streaming.active && assistantIsLatestTurn) {
6689
6575
  return displayMessages;
6690
6576
  }
@@ -6807,7 +6693,16 @@ function ChatScreen({
6807
6693
  }
6808
6694
  }, [historyMessages, streamFinish]);
6809
6695
  useEffect(() => {
6810
- if (!llmTitlesEnabled) return;
6696
+ if (!isStreaming || streaming.active) return;
6697
+ const lastAssistantIndex = [...historyMessages].map((message, index) => ({ message, index })).filter(({ message }) => message.role === "assistant").map(({ index }) => index).pop();
6698
+ const lastUserIndex = [...historyMessages].map((message, index) => ({ message, index })).filter(({ message }) => message.role === "user").map(({ index }) => index).pop();
6699
+ const hasCompletedAssistantTurn = typeof lastAssistantIndex === "number" && (typeof lastUserIndex !== "number" || lastAssistantIndex > lastUserIndex);
6700
+ if (hasCompletedAssistantTurn) {
6701
+ streamFinish();
6702
+ pollingPhaseRef.current = "fast";
6703
+ }
6704
+ }, [historyMessages, isStreaming, streamFinish, streaming.active]);
6705
+ useEffect(() => {
6811
6706
  if (isNewChat || isRedirecting) return;
6812
6707
  const sessionKey = forcedSessionKey || resolvedSessionKey || activeSessionKey;
6813
6708
  if (!sessionKey) return;
@@ -7465,11 +7360,7 @@ const $sessionKey = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineP
7465
7360
  export {
7466
7361
  $sessionKey as $,
7467
7362
  Collapsible as C,
7468
- useLlmSettingsStore as a,
7469
- getLlmHeaders as b,
7470
- chatQueryKeys as c,
7471
- CollapsibleTrigger as d,
7472
- CollapsiblePanel as e,
7473
- getLlmProviderDefaults as g,
7474
- useLlmSettings as u
7363
+ CollapsibleTrigger as a,
7364
+ CollapsiblePanel as b,
7365
+ chatQueryKeys as c
7475
7366
  };
@@ -0,0 +1,4 @@
1
+ const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "/root/opencami/src/routes/__root.tsx", "children": ["/", "/agents", "/bots", "/connect", "/dashboard", "/files", "/memory", "/new", "/skills", "/api/agents", "/api/cron", "/api/follow-ups", "/api/history", "/api/llm-features", "/api/llm-models", "/api/models", "/api/paths", "/api/personas", "/api/ping", "/api/send", "/api/sessions", "/api/skills", "/api/stream", "/api/stt", "/api/tts", "/chat/$sessionKey", "/api/dashboard/crons", "/api/dashboard/gateway", "/api/dashboard/system", "/api/files/delete", "/api/files/download", "/api/files/info", "/api/files/list", "/api/files/mkdir", "/api/files/read", "/api/files/rename", "/api/files/save", "/api/files/upload"], "preloads": ["/assets/main-CbISK-pm.js"], "assets": [] }, "/": { "filePath": "/root/opencami/src/routes/index.tsx", "assets": [], "preloads": ["/assets/index-3-ASY4Cl.js"] }, "/agents": { "filePath": "/root/opencami/src/routes/agents.tsx", "assets": [], "preloads": ["/assets/agents-D7JS19Lo.js"] }, "/bots": { "filePath": "/root/opencami/src/routes/bots.tsx", "assets": [], "preloads": ["/assets/bots-DAInzfLx.js"] }, "/connect": { "filePath": "/root/opencami/src/routes/connect.tsx", "assets": [], "preloads": ["/assets/connect-DZSrhhNT.js", "/assets/index-BZxJSF4T.js", "/assets/button-Bv-7bwZZ.js", "/assets/react-C_7UlEtd.js"] }, "/dashboard": { "filePath": "/root/opencami/src/routes/dashboard.tsx", "assets": [], "preloads": ["/assets/dashboard-y8KVUVF-.js", "/assets/useQuery-_BEWdUe1.js"] }, "/files": { "filePath": "/root/opencami/src/routes/files.tsx", "assets": [], "preloads": ["/assets/files-CG0jFYdO.js"] }, "/memory": { "filePath": "/root/opencami/src/routes/memory.tsx", "assets": [], "preloads": ["/assets/memory-BYRJ1l_4.js"] }, "/new": { "filePath": "/root/opencami/src/routes/new.tsx", "assets": [], "preloads": ["/assets/new-DLlBm66g.js"] }, "/skills": { "filePath": "/root/opencami/src/routes/skills.tsx", "assets": [], "preloads": ["/assets/skills-Svd-7oxQ.js"] }, "/api/agents": { "filePath": "/root/opencami/src/routes/api/agents.ts" }, "/api/cron": { "filePath": "/root/opencami/src/routes/api/cron.ts" }, "/api/follow-ups": { "filePath": "/root/opencami/src/routes/api/follow-ups.ts" }, "/api/history": { "filePath": "/root/opencami/src/routes/api/history.ts" }, "/api/llm-features": { "filePath": "/root/opencami/src/routes/api/llm-features.ts" }, "/api/llm-models": { "filePath": "/root/opencami/src/routes/api/llm-models.ts" }, "/api/models": { "filePath": "/root/opencami/src/routes/api/models.ts" }, "/api/paths": { "filePath": "/root/opencami/src/routes/api/paths.ts" }, "/api/personas": { "filePath": "/root/opencami/src/routes/api/personas.ts" }, "/api/ping": { "filePath": "/root/opencami/src/routes/api/ping.ts" }, "/api/send": { "filePath": "/root/opencami/src/routes/api/send.ts" }, "/api/sessions": { "filePath": "/root/opencami/src/routes/api/sessions.ts" }, "/api/skills": { "filePath": "/root/opencami/src/routes/api/skills.ts" }, "/api/stream": { "filePath": "/root/opencami/src/routes/api/stream.ts" }, "/api/stt": { "filePath": "/root/opencami/src/routes/api/stt.ts" }, "/api/tts": { "filePath": "/root/opencami/src/routes/api/tts.ts" }, "/chat/$sessionKey": { "filePath": "/root/opencami/src/routes/chat/$sessionKey.tsx", "assets": [], "preloads": ["/assets/_sessionKey-CTtu2ipR.js", "/assets/useQuery-_BEWdUe1.js", "/assets/tooltip-DzFgDY86.js", "/assets/button-Bv-7bwZZ.js", "/assets/useMutation-CMp81lDO.js", "/assets/use-file-explorer-state-Dwhw6Mpg.js", "/assets/useBaseUiId-BdQC9KF3.js", "/assets/useControlled-Bc2Emhlx.js", "/assets/useOnFirstRender-DpB3w0o4.js", "/assets/event-Cnr9OETn.js", "/assets/CSPContext-CxrBvJ99.js", "/assets/DirectionContext-BOL0P_1j.js", "/assets/menu-DRjoHFvG.js", "/assets/opencami-logo-D2mv84sX.js", "/assets/proxy-DtxqfBHY.js", "/assets/index-BZxJSF4T.js", "/assets/markdown-UD7RzskQ.js", "/assets/react-C_7UlEtd.js"] }, "/api/dashboard/crons": { "filePath": "/root/opencami/src/routes/api/dashboard/crons.ts" }, "/api/dashboard/gateway": { "filePath": "/root/opencami/src/routes/api/dashboard/gateway.ts" }, "/api/dashboard/system": { "filePath": "/root/opencami/src/routes/api/dashboard/system.ts" }, "/api/files/delete": { "filePath": "/root/opencami/src/routes/api/files/delete.ts" }, "/api/files/download": { "filePath": "/root/opencami/src/routes/api/files/download.ts" }, "/api/files/info": { "filePath": "/root/opencami/src/routes/api/files/info.ts" }, "/api/files/list": { "filePath": "/root/opencami/src/routes/api/files/list.ts" }, "/api/files/mkdir": { "filePath": "/root/opencami/src/routes/api/files/mkdir.ts" }, "/api/files/read": { "filePath": "/root/opencami/src/routes/api/files/read.ts" }, "/api/files/rename": { "filePath": "/root/opencami/src/routes/api/files/rename.ts" }, "/api/files/save": { "filePath": "/root/opencami/src/routes/api/files/save.ts" }, "/api/files/upload": { "filePath": "/root/opencami/src/routes/api/files/upload.ts" } }, "clientEntry": "/assets/main-CbISK-pm.js" });
2
+ export {
3
+ tsrStartManifest
4
+ };
@@ -1,5 +1,5 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { C as CodeBlock } from "./index-B_F4DTUu.js";
2
+ import { C as CodeBlock } from "./index-197lqzv2.js";
3
3
  import "react";
4
4
  import "@hugeicons/react";
5
5
  import "@hugeicons/core-free-icons";