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.
- package/dist/client/assets/{CSPContext-TfUptlEu.js → CSPContext-CrlIQW7-.js} +1 -1
- package/dist/client/assets/{DirectionContext-CQMv7g2N.js → DirectionContext-X-0CRn1O.js} +1 -1
- package/dist/client/assets/_sessionKey-yNQ57svB.js +23 -0
- package/dist/client/assets/agents-WqWjsymD.js +2 -0
- package/dist/client/assets/{agents-screen-fSZJpRi_.js → agents-screen-BhOVp_S6.js} +1 -1
- package/dist/client/assets/bots-DNqiFT7w.js +2 -0
- package/dist/client/assets/{bots-screen-4yT-e3cM.js → bots-screen-DLFd0ydi.js} +1 -1
- package/dist/client/assets/button-D0n2Qsd_.js +1 -0
- package/dist/client/assets/{composite-BLgu_EOL.js → composite-GtKwZKbV.js} +1 -1
- package/dist/client/assets/{connect-CiqRvR6s.js → connect-vQWL0_11.js} +1 -1
- package/dist/client/assets/{dashboard-CyWDWpbj.js → dashboard-Knwc61i1.js} +1 -1
- package/dist/client/assets/{event-2_Dxdv7h.js → event-CHpdjYFR.js} +1 -1
- package/dist/client/assets/file-explorer-screen-FoYNs9zK.js +1 -0
- package/dist/client/assets/files-BtR_gArr.js +2 -0
- package/dist/client/assets/follow-up-suggestions-DVXNLqga.js +5 -0
- package/dist/client/assets/{index-ygitKeM-.js → index-CNPHef4O.js} +1 -1
- package/dist/client/assets/{index-C_gsW9fo.js → index-CTT0Y1ya.js} +1 -1
- package/dist/client/assets/{keyboard-shortcuts-dialog-z-amTZVi.js → keyboard-shortcuts-dialog-DP8ptQ7N.js} +1 -1
- package/dist/client/assets/{main-ZBMVSJTF.js → main-xPlWrMhO.js} +3 -3
- package/dist/client/assets/{markdown-CHUjmWcv.js → markdown-3Js_RbUp.js} +1 -1
- package/dist/client/assets/memory-BRGPq5t6.js +2 -0
- package/dist/client/assets/{memory-screen-C_ZNDGLd.js → memory-screen-nzRra2Qi.js} +1 -1
- package/dist/client/assets/{menu-CB88T7R1.js → menu-BnSEqetd.js} +1 -1
- package/dist/client/assets/{opencami-logo-C0Kj1DiT.js → opencami-logo-B_hLbomw.js} +1 -1
- package/dist/client/assets/{proxy-D-juuhw6.js → proxy-BnlGpgC1.js} +1 -1
- package/dist/client/assets/{react-Akh4y69S.js → react-BgjQyJHw.js} +1 -1
- package/dist/client/assets/{search-dialog-BasfzCyM.js → search-dialog-Bib2QY9u.js} +1 -1
- package/dist/client/assets/{search-sources-badge-DwFHWd7S.js → search-sources-badge-COHcYFRB.js} +1 -1
- package/dist/client/assets/{session-export-dialog-CAl3iJnD.js → session-export-dialog-ooPnfHh_.js} +1 -1
- package/dist/client/assets/settings-dialog-B8mz99u-.js +1 -0
- package/dist/client/assets/skills-DA9J_tsC.js +2 -0
- package/dist/client/assets/{skills-panel-B7BRAofP.js → skills-panel-D2uMdCHp.js} +1 -1
- package/dist/client/assets/styles-CWabEzNU.css +1 -0
- package/dist/client/assets/{switch-DYEbEgy5.js → switch-BUQ0qH6r.js} +1 -1
- package/dist/client/assets/{tabs-eiBvL0H7.js → tabs-BEyU6TjN.js} +1 -1
- package/dist/client/assets/{thinking-CariuioI.js → thinking-CA48yhOE.js} +1 -1
- package/dist/client/assets/{tooltip-CekkGEYG.js → tooltip-CcIdgcV0.js} +1 -1
- package/dist/client/assets/{use-file-explorer-state-Dfyh4GwR.js → use-file-explorer-state-CN_IJGcd.js} +2 -2
- package/dist/client/assets/{useBaseUiId-DLhdkHJl.js → useBaseUiId-ClbEYEil.js} +1 -1
- package/dist/client/assets/useCompositeItem-B_OxfJee.js +1 -0
- package/dist/client/assets/{useControlled-CpliTEve.js → useControlled-CyT-lqbs.js} +1 -1
- package/dist/client/assets/{useMutation-CpD2Pn0F.js → useMutation-eQUrsn-X.js} +1 -1
- package/dist/client/assets/{useOnFirstRender-DsFYFJoB.js → useOnFirstRender-CR_o2MK_.js} +1 -1
- package/dist/client/assets/{useQuery-DMTgpIql.js → useQuery-k6EMRoMD.js} +1 -1
- package/dist/server/assets/{_sessionKey-Bhksr7VP.js → _sessionKey-CaFqmyhU.js} +388 -390
- package/dist/server/assets/{_tanstack-start-manifest_v-D-5ReiD4.js → _tanstack-start-manifest_v-P3skSR3R.js} +1 -1
- package/dist/server/assets/{follow-up-suggestions-B3hol2KT.js → follow-up-suggestions-DHv2_XzB.js} +13 -74
- package/dist/server/assets/{index-4G_4vZNY.js → index-C7lmufwX.js} +1 -1
- package/dist/server/assets/{router-C9JRmWMm.js → router-X2L0PDPI.js} +133 -294
- package/dist/server/assets/{search-dialog-CTJULPB8.js → search-dialog-CXhofdoP.js} +2 -2
- package/dist/server/assets/{settings-dialog-B5yR2pBy.js → settings-dialog-CPdftvjz.js} +1 -254
- package/dist/server/assets/{thinking-CHx4Oouj.js → thinking-YkRSlXtf.js} +2 -2
- package/dist/server/server.js +2 -2
- package/package.json +1 -1
- package/dist/client/assets/_sessionKey-DYknvaDS.js +0 -23
- package/dist/client/assets/agents-DNywJUai.js +0 -2
- package/dist/client/assets/bots-Bqjqhws8.js +0 -2
- package/dist/client/assets/button-DqP4GZwZ.js +0 -1
- package/dist/client/assets/file-explorer-screen-CZ2QKk-0.js +0 -1
- package/dist/client/assets/files-Cbhud0J8.js +0 -2
- package/dist/client/assets/follow-up-suggestions-Bi3Ci2my.js +0 -5
- package/dist/client/assets/memory-BRa-0plj.js +0 -2
- package/dist/client/assets/settings-dialog-C8OoRXwX.js +0 -1
- package/dist/client/assets/skills-Cx12984a.js +0 -2
- package/dist/client/assets/styles-CXa-SiWC.css +0 -1
- 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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
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 (
|
|
5589
|
+
if (serverMessage.clientId && cachedMessage.clientId && serverMessage.clientId === cachedMessage.clientId) {
|
|
5514
5590
|
return true;
|
|
5515
5591
|
}
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
const
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
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 (!
|
|
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
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
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
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
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({
|
|
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 (
|
|
5765
|
-
|
|
5754
|
+
if (controller.signal.aborted) {
|
|
5755
|
+
throw new Error("Aborted");
|
|
5766
5756
|
}
|
|
5767
|
-
|
|
5768
|
-
|
|
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
|
-
|
|
5771
|
-
|
|
5763
|
+
title,
|
|
5764
|
+
source,
|
|
5765
|
+
error: data.error
|
|
5772
5766
|
};
|
|
5773
|
-
}
|
|
5774
|
-
|
|
5775
|
-
|
|
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
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
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
|
|
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-
|
|
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?.
|
|
6838
|
-
|
|
6839
|
-
|
|
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
|
-
|
|
7264
|
-
|
|
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
|
};
|