@copilotz/chat-adapter 0.1.0 → 0.1.2
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/README.md +516 -6
- package/dist/index.d.ts +151 -3
- package/dist/index.js +311 -107
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/CopilotzChat.tsx
|
|
2
|
-
import { useMemo } from "react";
|
|
2
|
+
import { useMemo, useEffect as useEffect3, useState as useState3 } from "react";
|
|
3
3
|
import { ChatUI, ChatUserContextProvider } from "@copilotz/chat-ui";
|
|
4
4
|
|
|
5
5
|
// ../node_modules/lucide-react/dist/esm/createLucideIcon.js
|
|
@@ -99,7 +99,7 @@ var __iconNode = [
|
|
|
99
99
|
var User = createLucideIcon("user", __iconNode);
|
|
100
100
|
|
|
101
101
|
// src/useCopilotzChat.ts
|
|
102
|
-
import { useState, useCallback, useRef, useEffect } from "react";
|
|
102
|
+
import { useState as useState2, useCallback as useCallback2, useRef as useRef2, useEffect as useEffect2 } from "react";
|
|
103
103
|
|
|
104
104
|
// src/copilotzService.ts
|
|
105
105
|
var rawBaseValue = import.meta.env?.VITE_API_URL;
|
|
@@ -259,6 +259,7 @@ async function runCopilotzStream(options) {
|
|
|
259
259
|
metadata,
|
|
260
260
|
threadMetadata,
|
|
261
261
|
toolCalls,
|
|
262
|
+
selectedAgent,
|
|
262
263
|
onToken,
|
|
263
264
|
onMessageEvent,
|
|
264
265
|
onAssetEvent,
|
|
@@ -304,7 +305,7 @@ async function runCopilotzStream(options) {
|
|
|
304
305
|
id: threadId ?? null,
|
|
305
306
|
externalId: threadExternalId ?? null,
|
|
306
307
|
name: threadName,
|
|
307
|
-
participants: ["assistant"],
|
|
308
|
+
participants: [selectedAgent || "assistant"],
|
|
308
309
|
metadata: Object.keys(restThreadMetadata).length > 0 ? restThreadMetadata : null
|
|
309
310
|
} : void 0;
|
|
310
311
|
const preparedAudioParts = [];
|
|
@@ -577,37 +578,141 @@ async function getAssetDataUrl(refOrId) {
|
|
|
577
578
|
return { dataUrl: data.dataUrl, mime: data.mime, assetId: data.assetId };
|
|
578
579
|
}
|
|
579
580
|
async function resolveAssetsInMessages(messages) {
|
|
580
|
-
const
|
|
581
|
-
|
|
581
|
+
const inFlightByRef = /* @__PURE__ */ new Map();
|
|
582
|
+
const resolveAssetRef = (assetRef) => {
|
|
583
|
+
if (!inFlightByRef.has(assetRef)) {
|
|
584
|
+
inFlightByRef.set(assetRef, getAssetDataUrl(assetRef));
|
|
585
|
+
}
|
|
586
|
+
return inFlightByRef.get(assetRef);
|
|
587
|
+
};
|
|
588
|
+
return Promise.all(messages.map(async (msg) => {
|
|
582
589
|
const meta = msg.metadata ?? void 0;
|
|
583
590
|
const attachments = Array.isArray(meta?.attachments) ? meta.attachments : void 0;
|
|
584
591
|
if (!attachments || attachments.length === 0) {
|
|
585
|
-
|
|
586
|
-
continue;
|
|
592
|
+
return msg;
|
|
587
593
|
}
|
|
588
|
-
const newAttachments =
|
|
589
|
-
for (const att of attachments) {
|
|
594
|
+
const newAttachments = await Promise.all(attachments.map(async (att) => {
|
|
590
595
|
const assetRef = typeof att?.assetRef === "string" ? att.assetRef : void 0;
|
|
591
|
-
if (assetRef)
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
}
|
|
603
|
-
} else {
|
|
604
|
-
newAttachments.push(att);
|
|
596
|
+
if (!assetRef) return att;
|
|
597
|
+
try {
|
|
598
|
+
const { dataUrl, mime } = await resolveAssetRef(assetRef);
|
|
599
|
+
const kind = typeof att.kind === "string" ? att.kind : "image";
|
|
600
|
+
return {
|
|
601
|
+
kind,
|
|
602
|
+
dataUrl,
|
|
603
|
+
mimeType: typeof att.mimeType === "string" ? att.mimeType : mime ?? void 0
|
|
604
|
+
};
|
|
605
|
+
} catch {
|
|
606
|
+
return att;
|
|
605
607
|
}
|
|
606
|
-
}
|
|
608
|
+
}));
|
|
607
609
|
const newMeta = { ...meta ?? {}, attachments: newAttachments };
|
|
608
|
-
|
|
610
|
+
return { ...msg, metadata: newMeta };
|
|
611
|
+
}));
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// src/useUrlState.ts
|
|
615
|
+
import { useState, useEffect, useCallback, useRef } from "react";
|
|
616
|
+
var DEFAULT_PARAMS = {
|
|
617
|
+
thread: "thread",
|
|
618
|
+
agent: "agent",
|
|
619
|
+
prompt: "prompt"
|
|
620
|
+
};
|
|
621
|
+
var isBrowser = typeof globalThis !== "undefined" && typeof globalThis.location !== "undefined";
|
|
622
|
+
var getSearchParams = () => {
|
|
623
|
+
if (!isBrowser) return new URLSearchParams();
|
|
624
|
+
return new URLSearchParams(globalThis.location.search);
|
|
625
|
+
};
|
|
626
|
+
var updateUrl = (params, mode) => {
|
|
627
|
+
if (!isBrowser) return;
|
|
628
|
+
const url = new URL(globalThis.location.href);
|
|
629
|
+
url.search = params.toString();
|
|
630
|
+
if (mode === "push") {
|
|
631
|
+
globalThis.history.pushState({}, "", url.toString());
|
|
632
|
+
} else {
|
|
633
|
+
globalThis.history.replaceState({}, "", url.toString());
|
|
609
634
|
}
|
|
610
|
-
|
|
635
|
+
};
|
|
636
|
+
function useUrlState(config = {}) {
|
|
637
|
+
const {
|
|
638
|
+
enabled = true,
|
|
639
|
+
mode = "replace",
|
|
640
|
+
params: userParams = {},
|
|
641
|
+
clearPromptAfterRead = true
|
|
642
|
+
} = config;
|
|
643
|
+
const params = { ...DEFAULT_PARAMS, ...userParams };
|
|
644
|
+
const isReadOnly = mode === "read-only";
|
|
645
|
+
const updateMode = mode === "read-only" ? "replace" : mode;
|
|
646
|
+
const initialReadDone = useRef(false);
|
|
647
|
+
const promptCleared = useRef(false);
|
|
648
|
+
const parseUrlState = useCallback(() => {
|
|
649
|
+
if (!enabled || !isBrowser) {
|
|
650
|
+
return { threadId: null, agentId: null, prompt: null };
|
|
651
|
+
}
|
|
652
|
+
const searchParams = getSearchParams();
|
|
653
|
+
return {
|
|
654
|
+
threadId: searchParams.get(params.thread) || null,
|
|
655
|
+
agentId: searchParams.get(params.agent) || null,
|
|
656
|
+
prompt: promptCleared.current ? null : searchParams.get(params.prompt) || null
|
|
657
|
+
};
|
|
658
|
+
}, [enabled, params.thread, params.agent, params.prompt]);
|
|
659
|
+
const [state, setState] = useState(parseUrlState);
|
|
660
|
+
useEffect(() => {
|
|
661
|
+
if (!enabled || !isBrowser) return;
|
|
662
|
+
if (!initialReadDone.current) {
|
|
663
|
+
const initialState = parseUrlState();
|
|
664
|
+
setState(initialState);
|
|
665
|
+
initialReadDone.current = true;
|
|
666
|
+
if (clearPromptAfterRead && initialState.prompt && !isReadOnly) {
|
|
667
|
+
const searchParams = getSearchParams();
|
|
668
|
+
searchParams.delete(params.prompt);
|
|
669
|
+
updateUrl(searchParams, "replace");
|
|
670
|
+
promptCleared.current = true;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
const handlePopState = () => {
|
|
674
|
+
setState(parseUrlState());
|
|
675
|
+
};
|
|
676
|
+
globalThis.addEventListener("popstate", handlePopState);
|
|
677
|
+
return () => globalThis.removeEventListener("popstate", handlePopState);
|
|
678
|
+
}, [enabled, parseUrlState, clearPromptAfterRead, params.prompt, isReadOnly]);
|
|
679
|
+
const setThreadId = useCallback((threadId) => {
|
|
680
|
+
if (!enabled || isReadOnly || !isBrowser) return;
|
|
681
|
+
const searchParams = getSearchParams();
|
|
682
|
+
if (threadId) {
|
|
683
|
+
searchParams.set(params.thread, threadId);
|
|
684
|
+
} else {
|
|
685
|
+
searchParams.delete(params.thread);
|
|
686
|
+
}
|
|
687
|
+
updateUrl(searchParams, updateMode);
|
|
688
|
+
setState((prev) => ({ ...prev, threadId }));
|
|
689
|
+
}, [enabled, isReadOnly, params.thread, updateMode]);
|
|
690
|
+
const setAgentId = useCallback((agentId) => {
|
|
691
|
+
if (!enabled || isReadOnly || !isBrowser) return;
|
|
692
|
+
const searchParams = getSearchParams();
|
|
693
|
+
if (agentId) {
|
|
694
|
+
searchParams.set(params.agent, agentId);
|
|
695
|
+
} else {
|
|
696
|
+
searchParams.delete(params.agent);
|
|
697
|
+
}
|
|
698
|
+
updateUrl(searchParams, updateMode);
|
|
699
|
+
setState((prev) => ({ ...prev, agentId }));
|
|
700
|
+
}, [enabled, isReadOnly, params.agent, updateMode]);
|
|
701
|
+
const clearPrompt = useCallback(() => {
|
|
702
|
+
if (!enabled || isReadOnly || !isBrowser) return;
|
|
703
|
+
const searchParams = getSearchParams();
|
|
704
|
+
searchParams.delete(params.prompt);
|
|
705
|
+
updateUrl(searchParams, "replace");
|
|
706
|
+
promptCleared.current = true;
|
|
707
|
+
setState((prev) => ({ ...prev, prompt: null }));
|
|
708
|
+
}, [enabled, isReadOnly, params.prompt]);
|
|
709
|
+
return {
|
|
710
|
+
state,
|
|
711
|
+
setThreadId,
|
|
712
|
+
setAgentId,
|
|
713
|
+
clearPrompt,
|
|
714
|
+
isEnabled: enabled
|
|
715
|
+
};
|
|
611
716
|
}
|
|
612
717
|
|
|
613
718
|
// src/useCopilotzChat.ts
|
|
@@ -667,48 +772,46 @@ var convertServerMessage = (msg) => {
|
|
|
667
772
|
toolCalls: hasToolCalls ? mappedToolCalls : void 0
|
|
668
773
|
};
|
|
669
774
|
};
|
|
670
|
-
function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onToolOutput }) {
|
|
671
|
-
const
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
const [
|
|
679
|
-
const
|
|
680
|
-
const
|
|
681
|
-
const
|
|
682
|
-
const
|
|
683
|
-
const
|
|
684
|
-
const
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
const
|
|
704
|
-
|
|
705
|
-
const initializationRef = useRef({ userId: null, started: false });
|
|
706
|
-
useEffect(() => {
|
|
775
|
+
function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onToolOutput, preferredAgentName, urlSync }) {
|
|
776
|
+
const {
|
|
777
|
+
state: urlState,
|
|
778
|
+
setThreadId: setUrlThreadId,
|
|
779
|
+
setAgentId: setUrlAgentId,
|
|
780
|
+
clearPrompt: clearUrlPrompt,
|
|
781
|
+
isEnabled: isUrlSyncEnabled
|
|
782
|
+
} = useUrlState(urlSync);
|
|
783
|
+
const [threads, setThreads] = useState2([]);
|
|
784
|
+
const [threadMetadataMap, setThreadMetadataMap] = useState2({});
|
|
785
|
+
const [threadExternalIdMap, setThreadExternalIdMap] = useState2({});
|
|
786
|
+
const [currentThreadId, setCurrentThreadId] = useState2(null);
|
|
787
|
+
const [currentThreadExternalId, setCurrentThreadExternalId] = useState2(null);
|
|
788
|
+
const [messages, setMessages] = useState2([]);
|
|
789
|
+
const [isMessagesLoading, setIsMessagesLoading] = useState2(false);
|
|
790
|
+
const [isStreaming, setIsStreaming] = useState2(false);
|
|
791
|
+
const [userContextSeed, setUserContextSeed] = useState2(initialContext || {});
|
|
792
|
+
const preferredAgentRef = useRef2(preferredAgentName ?? null);
|
|
793
|
+
const threadsRef = useRef2(threads);
|
|
794
|
+
const threadMetadataMapRef = useRef2(threadMetadataMap);
|
|
795
|
+
const threadExternalIdMapRef = useRef2(threadExternalIdMap);
|
|
796
|
+
const currentThreadIdRef = useRef2(currentThreadId);
|
|
797
|
+
const currentThreadExternalIdRef = useRef2(currentThreadExternalId);
|
|
798
|
+
const userContextSeedRef = useRef2(userContextSeed);
|
|
799
|
+
threadsRef.current = threads;
|
|
800
|
+
threadMetadataMapRef.current = threadMetadataMap;
|
|
801
|
+
threadExternalIdMapRef.current = threadExternalIdMap;
|
|
802
|
+
currentThreadIdRef.current = currentThreadId;
|
|
803
|
+
currentThreadExternalIdRef.current = currentThreadExternalId;
|
|
804
|
+
userContextSeedRef.current = userContextSeed;
|
|
805
|
+
preferredAgentRef.current = preferredAgentName ?? null;
|
|
806
|
+
const abortControllerRef = useRef2(null);
|
|
807
|
+
const messagesRequestRef = useRef2(0);
|
|
808
|
+
const initializationRef = useRef2({ userId: null, started: false });
|
|
809
|
+
useEffect2(() => {
|
|
707
810
|
if (initialContext) {
|
|
708
811
|
setUserContextSeed((prev) => ({ ...prev, ...initialContext }));
|
|
709
812
|
}
|
|
710
813
|
}, [initialContext]);
|
|
711
|
-
const processToolOutput =
|
|
814
|
+
const processToolOutput = useCallback2((output) => {
|
|
712
815
|
if (!output) return;
|
|
713
816
|
const contextPatch = {};
|
|
714
817
|
if (output.userContext && typeof output.userContext === "object") {
|
|
@@ -719,7 +822,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
719
822
|
}
|
|
720
823
|
onToolOutput?.(output);
|
|
721
824
|
}, [onToolOutput]);
|
|
722
|
-
const handleStreamMessageEvent =
|
|
825
|
+
const handleStreamMessageEvent = useCallback2((event) => {
|
|
723
826
|
const payload = event?.payload;
|
|
724
827
|
if (!payload) return;
|
|
725
828
|
if (payload.senderType === "tool") {
|
|
@@ -776,7 +879,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
776
879
|
});
|
|
777
880
|
}
|
|
778
881
|
}, [processToolOutput]);
|
|
779
|
-
const updateThreadsState =
|
|
882
|
+
const updateThreadsState = useCallback2((rawThreads, preferredExternalId) => {
|
|
780
883
|
const metadataMap = {};
|
|
781
884
|
const externalMap = {};
|
|
782
885
|
const normalized = rawThreads.map((thread) => {
|
|
@@ -818,7 +921,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
818
921
|
setCurrentThreadExternalId(nextThreadId ? externalMap[nextThreadId] ?? null : null);
|
|
819
922
|
return nextThreadId;
|
|
820
923
|
}, []);
|
|
821
|
-
const fetchAndSetThreadsState =
|
|
924
|
+
const fetchAndSetThreadsState = useCallback2(async (uid, preferredExternalId) => {
|
|
822
925
|
try {
|
|
823
926
|
const rawThreads = await fetchThreads(uid);
|
|
824
927
|
return updateThreadsState(rawThreads, preferredExternalId);
|
|
@@ -828,9 +931,10 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
828
931
|
return null;
|
|
829
932
|
}
|
|
830
933
|
}, [updateThreadsState]);
|
|
831
|
-
const loadThreadMessages =
|
|
832
|
-
const requestId =
|
|
934
|
+
const loadThreadMessages = useCallback2(async (threadId) => {
|
|
935
|
+
const requestId = messagesRequestRef.current + 1;
|
|
833
936
|
messagesRequestRef.current = requestId;
|
|
937
|
+
setIsMessagesLoading(true);
|
|
834
938
|
try {
|
|
835
939
|
const rawMessages = await fetchThreadMessages(threadId);
|
|
836
940
|
const resolvedMessages = await resolveAssetsInMessages(rawMessages);
|
|
@@ -857,15 +961,22 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
857
961
|
} catch (error) {
|
|
858
962
|
if (isAbortError(error)) return;
|
|
859
963
|
console.error(`Error loading messages for thread ${threadId}`, error);
|
|
964
|
+
} finally {
|
|
965
|
+
if (messagesRequestRef.current === requestId) {
|
|
966
|
+
setIsMessagesLoading(false);
|
|
967
|
+
}
|
|
860
968
|
}
|
|
861
969
|
}, [processToolOutput]);
|
|
862
|
-
const handleSelectThread =
|
|
970
|
+
const handleSelectThread = useCallback2(async (threadId) => {
|
|
863
971
|
setCurrentThreadId(threadId);
|
|
972
|
+
setMessages([]);
|
|
864
973
|
const extMap = threadExternalIdMapRef.current;
|
|
865
974
|
setCurrentThreadExternalId(extMap[threadId] ?? null);
|
|
866
975
|
await loadThreadMessages(threadId);
|
|
867
976
|
}, [loadThreadMessages]);
|
|
868
|
-
const handleCreateThread =
|
|
977
|
+
const handleCreateThread = useCallback2((title) => {
|
|
978
|
+
messagesRequestRef.current += 1;
|
|
979
|
+
setIsMessagesLoading(false);
|
|
869
980
|
const id = generateId();
|
|
870
981
|
const now = nowTs();
|
|
871
982
|
const newThread = {
|
|
@@ -883,7 +994,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
883
994
|
setCurrentThreadExternalId(id);
|
|
884
995
|
setMessages([]);
|
|
885
996
|
}, []);
|
|
886
|
-
const handleRenameThread =
|
|
997
|
+
const handleRenameThread = useCallback2(async (threadId, newTitle) => {
|
|
887
998
|
const trimmedTitle = newTitle.trim();
|
|
888
999
|
if (!trimmedTitle) return;
|
|
889
1000
|
setThreads(
|
|
@@ -907,7 +1018,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
907
1018
|
}
|
|
908
1019
|
}
|
|
909
1020
|
}, [userId, fetchAndSetThreadsState]);
|
|
910
|
-
const handleArchiveThread =
|
|
1021
|
+
const handleArchiveThread = useCallback2(async (threadId) => {
|
|
911
1022
|
const thread = threadsRef.current.find((t) => t.id === threadId);
|
|
912
1023
|
if (!thread) return;
|
|
913
1024
|
const newArchivedStatus = !thread.isArchived;
|
|
@@ -927,7 +1038,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
927
1038
|
}
|
|
928
1039
|
}
|
|
929
1040
|
}, [userId, fetchAndSetThreadsState]);
|
|
930
|
-
const handleDeleteThread =
|
|
1041
|
+
const handleDeleteThread = useCallback2(async (threadId) => {
|
|
931
1042
|
const extMap = threadExternalIdMapRef.current;
|
|
932
1043
|
const isPlaceholder = extMap[threadId] === threadId;
|
|
933
1044
|
setThreads((prev) => prev.filter((t) => t.id !== threadId));
|
|
@@ -964,13 +1075,17 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
964
1075
|
}
|
|
965
1076
|
}
|
|
966
1077
|
}, [userId, fetchAndSetThreadsState, loadThreadMessages]);
|
|
967
|
-
const handleStop =
|
|
1078
|
+
const handleStop = useCallback2(() => {
|
|
968
1079
|
abortControllerRef.current?.abort();
|
|
969
1080
|
abortControllerRef.current = null;
|
|
970
1081
|
setIsStreaming(false);
|
|
971
|
-
setMessages((prev) =>
|
|
1082
|
+
setMessages((prev) => {
|
|
1083
|
+
const hasStreaming = prev.some((msg) => msg.isStreaming);
|
|
1084
|
+
if (!hasStreaming) return prev;
|
|
1085
|
+
return prev.map((msg) => msg.isStreaming ? { ...msg, isStreaming: false, isComplete: true } : msg);
|
|
1086
|
+
});
|
|
972
1087
|
}, []);
|
|
973
|
-
const handleStreamAssetEvent =
|
|
1088
|
+
const handleStreamAssetEvent = useCallback2((payload, assistantMessageId) => {
|
|
974
1089
|
if (!payload?.dataUrl) return;
|
|
975
1090
|
const mimeType = payload.mime || "image/png";
|
|
976
1091
|
const dataUrl = payload.dataUrl;
|
|
@@ -992,22 +1107,36 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
992
1107
|
isComplete: true
|
|
993
1108
|
} : msg));
|
|
994
1109
|
}, []);
|
|
995
|
-
const sendCopilotzMessage =
|
|
1110
|
+
const sendCopilotzMessage = useCallback2(async (params) => {
|
|
996
1111
|
let currentAssistantId = generateId();
|
|
997
1112
|
params.onBeforeStart?.(currentAssistantId);
|
|
998
1113
|
let hasStreamProgress = false;
|
|
999
1114
|
let pendingStartNewAssistantBubble = false;
|
|
1000
|
-
const
|
|
1115
|
+
const updateStreamingMessage = (partial, isComplete) => {
|
|
1116
|
+
if (partial && partial.length > 0) {
|
|
1117
|
+
hasStreamProgress = true;
|
|
1118
|
+
}
|
|
1001
1119
|
setMessages((prev) => {
|
|
1002
1120
|
const idx = prev.findIndex((m) => m.id === currentAssistantId);
|
|
1003
|
-
if (idx >= 0 && prev[idx].role === "assistant"
|
|
1004
|
-
|
|
1121
|
+
if (idx >= 0 && prev[idx].role === "assistant") {
|
|
1122
|
+
const msg = prev[idx];
|
|
1123
|
+
if (msg.content === partial && msg.isStreaming === !isComplete && msg.isComplete === isComplete) {
|
|
1124
|
+
return prev;
|
|
1125
|
+
}
|
|
1126
|
+
const updated = [...prev];
|
|
1127
|
+
updated[idx] = { ...msg, content: partial, isStreaming: !isComplete, isComplete };
|
|
1128
|
+
return updated;
|
|
1005
1129
|
}
|
|
1006
1130
|
const last = prev[prev.length - 1];
|
|
1007
1131
|
if (last && last.role === "assistant" && last.isStreaming) {
|
|
1008
1132
|
currentAssistantId = last.id;
|
|
1009
1133
|
pendingStartNewAssistantBubble = false;
|
|
1010
|
-
|
|
1134
|
+
if (last.content === partial && last.isStreaming === !isComplete && last.isComplete === isComplete) {
|
|
1135
|
+
return prev;
|
|
1136
|
+
}
|
|
1137
|
+
const updated = [...prev];
|
|
1138
|
+
updated[prev.length - 1] = { ...last, content: partial, isStreaming: !isComplete, isComplete };
|
|
1139
|
+
return updated;
|
|
1011
1140
|
}
|
|
1012
1141
|
if (pendingStartNewAssistantBubble || !prev.length || (prev[prev.length - 1].role !== "assistant" || !prev[prev.length - 1].isStreaming)) {
|
|
1013
1142
|
const newId = generateId();
|
|
@@ -1018,25 +1147,26 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
1018
1147
|
{
|
|
1019
1148
|
id: newId,
|
|
1020
1149
|
role: "assistant",
|
|
1021
|
-
content:
|
|
1150
|
+
content: partial,
|
|
1022
1151
|
timestamp: nowTs(),
|
|
1023
|
-
isStreaming:
|
|
1024
|
-
isComplete
|
|
1152
|
+
isStreaming: !isComplete,
|
|
1153
|
+
isComplete
|
|
1025
1154
|
}
|
|
1026
1155
|
];
|
|
1027
1156
|
}
|
|
1028
1157
|
return prev;
|
|
1029
1158
|
});
|
|
1030
1159
|
};
|
|
1031
|
-
const updateStreamingMessage = (partial, isComplete) => {
|
|
1032
|
-
if (partial && partial.length > 0) {
|
|
1033
|
-
hasStreamProgress = true;
|
|
1034
|
-
}
|
|
1035
|
-
ensureStreamingBubble();
|
|
1036
|
-
setMessages((prev) => prev.map((msg) => msg.id === currentAssistantId ? { ...msg, content: partial, isStreaming: !isComplete, isComplete } : msg));
|
|
1037
|
-
};
|
|
1038
1160
|
const finalizeCurrentAssistantBubble = () => {
|
|
1039
|
-
setMessages((prev) =>
|
|
1161
|
+
setMessages((prev) => {
|
|
1162
|
+
const idx = prev.findIndex((m) => m.id === currentAssistantId);
|
|
1163
|
+
if (idx < 0) return prev;
|
|
1164
|
+
const msg = prev[idx];
|
|
1165
|
+
if (!msg.isStreaming && msg.isComplete) return prev;
|
|
1166
|
+
const updated = [...prev];
|
|
1167
|
+
updated[idx] = { ...msg, isStreaming: false, isComplete: true };
|
|
1168
|
+
return updated;
|
|
1169
|
+
});
|
|
1040
1170
|
};
|
|
1041
1171
|
const curThreadId = currentThreadIdRef.current;
|
|
1042
1172
|
const toServerMessageFromEvent = async (event) => {
|
|
@@ -1139,6 +1269,11 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
1139
1269
|
const currentThreadMetadataMap = threadMetadataMapRef.current;
|
|
1140
1270
|
const messageMetadata = metadataKey ? currentThreadMetadataMap[metadataKey]?.userContext : void 0;
|
|
1141
1271
|
const threadMetadata = metadataKey ? currentThreadMetadataMap[metadataKey] : void 0;
|
|
1272
|
+
const mergedMetadata = {
|
|
1273
|
+
...messageMetadata ?? {},
|
|
1274
|
+
...params.metadata ?? {}
|
|
1275
|
+
};
|
|
1276
|
+
const finalMetadata = Object.keys(mergedMetadata).length > 0 ? mergedMetadata : void 0;
|
|
1142
1277
|
await runCopilotzStream({
|
|
1143
1278
|
threadId: params.threadId ?? void 0,
|
|
1144
1279
|
threadExternalId: params.threadExternalId ?? void 0,
|
|
@@ -1152,9 +1287,10 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
1152
1287
|
}
|
|
1153
1288
|
},
|
|
1154
1289
|
attachments: params.attachments,
|
|
1155
|
-
metadata:
|
|
1290
|
+
metadata: finalMetadata,
|
|
1156
1291
|
threadMetadata: params.threadMetadata ?? threadMetadata,
|
|
1157
1292
|
toolCalls: params.toolCalls,
|
|
1293
|
+
selectedAgent: params.agentName ?? preferredAgentRef.current ?? null,
|
|
1158
1294
|
onToken: (token, isComplete) => updateStreamingMessage(token, isComplete),
|
|
1159
1295
|
onMessageEvent: async (event) => {
|
|
1160
1296
|
const type = event?.type || "";
|
|
@@ -1289,7 +1425,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
1289
1425
|
}
|
|
1290
1426
|
return currentAssistantId;
|
|
1291
1427
|
}, [handleStreamMessageEvent, handleStreamAssetEvent]);
|
|
1292
|
-
const handleSendMessage =
|
|
1428
|
+
const handleSendMessage = useCallback2(async (content, attachments = []) => {
|
|
1293
1429
|
if (!content.trim() && attachments.length === 0) return;
|
|
1294
1430
|
if (!userId) return;
|
|
1295
1431
|
const timestamp = nowTs();
|
|
@@ -1349,6 +1485,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
1349
1485
|
userId,
|
|
1350
1486
|
// userName can be anything, but let's try to find it in context or just fallback
|
|
1351
1487
|
userName: userContextSeedRef.current?.profile?.full_name ?? userId,
|
|
1488
|
+
agentName: preferredAgentRef.current,
|
|
1352
1489
|
// Include pending title for new threads
|
|
1353
1490
|
threadMetadata: pendingTitle ? { name: pendingTitle } : void 0
|
|
1354
1491
|
});
|
|
@@ -1365,7 +1502,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
1365
1502
|
} : msg));
|
|
1366
1503
|
}
|
|
1367
1504
|
}, [userId, fetchAndSetThreadsState, loadThreadMessages, sendCopilotzMessage]);
|
|
1368
|
-
const bootstrapConversation =
|
|
1505
|
+
const bootstrapConversation = useCallback2(async (uid) => {
|
|
1369
1506
|
if (!bootstrap?.initialToolCalls && !bootstrap?.initialMessage) return;
|
|
1370
1507
|
const bootstrapThreadExternalId = generateId();
|
|
1371
1508
|
setCurrentThreadId(bootstrapThreadExternalId);
|
|
@@ -1379,6 +1516,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
1379
1516
|
content: bootstrap.initialMessage || "",
|
|
1380
1517
|
toolCalls: bootstrap.initialToolCalls,
|
|
1381
1518
|
userId: uid,
|
|
1519
|
+
agentName: preferredAgentRef.current,
|
|
1382
1520
|
threadMetadata: {
|
|
1383
1521
|
name: defaultThreadName || "Main Thread"
|
|
1384
1522
|
}
|
|
@@ -1400,7 +1538,8 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
1400
1538
|
]);
|
|
1401
1539
|
}
|
|
1402
1540
|
}, [fetchAndSetThreadsState, loadThreadMessages, sendCopilotzMessage, bootstrap, defaultThreadName]);
|
|
1403
|
-
const reset =
|
|
1541
|
+
const reset = useCallback2(() => {
|
|
1542
|
+
messagesRequestRef.current += 1;
|
|
1404
1543
|
setThreads([]);
|
|
1405
1544
|
setThreadMetadataMap({});
|
|
1406
1545
|
setThreadExternalIdMap({});
|
|
@@ -1408,17 +1547,19 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
1408
1547
|
setCurrentThreadExternalId(null);
|
|
1409
1548
|
setMessages([]);
|
|
1410
1549
|
setUserContextSeed({});
|
|
1550
|
+
setIsMessagesLoading(false);
|
|
1411
1551
|
setIsStreaming(false);
|
|
1412
1552
|
abortControllerRef.current?.abort();
|
|
1413
1553
|
}, []);
|
|
1414
|
-
|
|
1554
|
+
useEffect2(() => {
|
|
1415
1555
|
if (userId) {
|
|
1416
1556
|
if (initializationRef.current.userId === userId && initializationRef.current.started) {
|
|
1417
1557
|
return;
|
|
1418
1558
|
}
|
|
1419
1559
|
initializationRef.current = { userId, started: true };
|
|
1420
1560
|
const init = async () => {
|
|
1421
|
-
const
|
|
1561
|
+
const urlPreferredThread = isUrlSyncEnabled ? urlState.threadId : void 0;
|
|
1562
|
+
const preferredThreadId = await fetchAndSetThreadsState(userId, urlPreferredThread);
|
|
1422
1563
|
if (preferredThreadId) {
|
|
1423
1564
|
await loadThreadMessages(preferredThreadId);
|
|
1424
1565
|
} else if (bootstrap) {
|
|
@@ -1430,8 +1571,13 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
1430
1571
|
initializationRef.current = { userId: null, started: false };
|
|
1431
1572
|
reset();
|
|
1432
1573
|
}
|
|
1433
|
-
}, [userId, fetchAndSetThreadsState, loadThreadMessages, bootstrapConversation, reset, bootstrap]);
|
|
1434
|
-
|
|
1574
|
+
}, [userId, fetchAndSetThreadsState, loadThreadMessages, bootstrapConversation, reset, bootstrap, isUrlSyncEnabled, urlState.threadId]);
|
|
1575
|
+
useEffect2(() => {
|
|
1576
|
+
if (!isUrlSyncEnabled) return;
|
|
1577
|
+
if (!initializationRef.current.started) return;
|
|
1578
|
+
setUrlThreadId(currentThreadExternalId);
|
|
1579
|
+
}, [currentThreadExternalId, isUrlSyncEnabled, setUrlThreadId]);
|
|
1580
|
+
useEffect2(() => {
|
|
1435
1581
|
if (!currentThreadId) return;
|
|
1436
1582
|
const metadata = threadMetadataMap[currentThreadId];
|
|
1437
1583
|
if (!metadata) return;
|
|
@@ -1441,6 +1587,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
1441
1587
|
}, [currentThreadId, threadMetadataMap]);
|
|
1442
1588
|
return {
|
|
1443
1589
|
messages,
|
|
1590
|
+
isMessagesLoading,
|
|
1444
1591
|
threads,
|
|
1445
1592
|
currentThreadId,
|
|
1446
1593
|
isStreaming,
|
|
@@ -1454,7 +1601,16 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
1454
1601
|
stopGeneration: handleStop,
|
|
1455
1602
|
fetchAndSetThreadsState,
|
|
1456
1603
|
loadThreadMessages,
|
|
1457
|
-
reset
|
|
1604
|
+
reset,
|
|
1605
|
+
// URL state
|
|
1606
|
+
/** Initial prompt from URL (if urlSync enabled) - use for pre-filling input */
|
|
1607
|
+
initialPrompt: isUrlSyncEnabled ? urlState.prompt : null,
|
|
1608
|
+
/** Clear the initial prompt from URL (call after consuming it) */
|
|
1609
|
+
clearInitialPrompt: clearUrlPrompt,
|
|
1610
|
+
/** URL agent ID (if urlSync enabled) - use for agent pre-selection */
|
|
1611
|
+
urlAgentId: isUrlSyncEnabled ? urlState.agentId : null,
|
|
1612
|
+
/** Update agent ID in URL */
|
|
1613
|
+
setUrlAgentId
|
|
1458
1614
|
};
|
|
1459
1615
|
}
|
|
1460
1616
|
|
|
@@ -1476,10 +1632,17 @@ var CopilotzChat = ({
|
|
|
1476
1632
|
onAddMemory,
|
|
1477
1633
|
onUpdateMemory,
|
|
1478
1634
|
onDeleteMemory,
|
|
1479
|
-
|
|
1635
|
+
suggestions,
|
|
1636
|
+
agentOptions = [],
|
|
1637
|
+
selectedAgentId = null,
|
|
1638
|
+
onSelectAgent,
|
|
1639
|
+
className,
|
|
1640
|
+
urlSync
|
|
1480
1641
|
}) => {
|
|
1642
|
+
const selectedAgent = agentOptions.find((agent) => agent.id === selectedAgentId) || null;
|
|
1481
1643
|
const {
|
|
1482
1644
|
messages,
|
|
1645
|
+
isMessagesLoading,
|
|
1483
1646
|
threads,
|
|
1484
1647
|
currentThreadId,
|
|
1485
1648
|
isStreaming,
|
|
@@ -1490,14 +1653,44 @@ var CopilotzChat = ({
|
|
|
1490
1653
|
renameThread,
|
|
1491
1654
|
archiveThread,
|
|
1492
1655
|
deleteThread: deleteThread2,
|
|
1493
|
-
stopGeneration
|
|
1656
|
+
stopGeneration,
|
|
1657
|
+
initialPrompt,
|
|
1658
|
+
clearInitialPrompt,
|
|
1659
|
+
urlAgentId,
|
|
1660
|
+
setUrlAgentId
|
|
1494
1661
|
} = useCopilotz({
|
|
1495
1662
|
userId,
|
|
1496
1663
|
initialContext,
|
|
1497
1664
|
bootstrap,
|
|
1498
1665
|
defaultThreadName: userConfig?.labels?.defaultThreadName,
|
|
1499
|
-
onToolOutput
|
|
1666
|
+
onToolOutput,
|
|
1667
|
+
preferredAgentName: selectedAgent?.name ?? null,
|
|
1668
|
+
urlSync
|
|
1500
1669
|
});
|
|
1670
|
+
const [promptHandled, setPromptHandled] = useState3(false);
|
|
1671
|
+
useEffect3(() => {
|
|
1672
|
+
if (urlAgentId && onSelectAgent && urlAgentId !== selectedAgentId) {
|
|
1673
|
+
const agentExists = agentOptions.some((a) => a.id === urlAgentId);
|
|
1674
|
+
if (agentExists) {
|
|
1675
|
+
onSelectAgent(urlAgentId);
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
}, [urlAgentId, selectedAgentId, onSelectAgent, agentOptions]);
|
|
1679
|
+
useEffect3(() => {
|
|
1680
|
+
if (selectedAgentId && urlSync?.enabled) {
|
|
1681
|
+
setUrlAgentId(selectedAgentId);
|
|
1682
|
+
}
|
|
1683
|
+
}, [selectedAgentId, urlSync?.enabled, setUrlAgentId]);
|
|
1684
|
+
useEffect3(() => {
|
|
1685
|
+
if (initialPrompt && !promptHandled && urlSync?.promptBehavior === "auto-send") {
|
|
1686
|
+
const timer = setTimeout(() => {
|
|
1687
|
+
void sendMessage(initialPrompt);
|
|
1688
|
+
clearInitialPrompt();
|
|
1689
|
+
setPromptHandled(true);
|
|
1690
|
+
}, 500);
|
|
1691
|
+
return () => clearTimeout(timer);
|
|
1692
|
+
}
|
|
1693
|
+
}, [initialPrompt, promptHandled, urlSync?.promptBehavior, sendMessage, clearInitialPrompt]);
|
|
1501
1694
|
const chatCallbacks = useMemo(() => ({
|
|
1502
1695
|
onSendMessage: (content, attachments) => {
|
|
1503
1696
|
void sendMessage(content, attachments);
|
|
@@ -1560,11 +1753,16 @@ var CopilotzChat = ({
|
|
|
1560
1753
|
ChatUI,
|
|
1561
1754
|
{
|
|
1562
1755
|
messages,
|
|
1756
|
+
isMessagesLoading,
|
|
1563
1757
|
threads,
|
|
1564
1758
|
currentThreadId,
|
|
1565
1759
|
config: mergedConfig,
|
|
1566
1760
|
callbacks: chatCallbacks,
|
|
1567
1761
|
isGenerating: isStreaming,
|
|
1762
|
+
suggestions,
|
|
1763
|
+
agentOptions,
|
|
1764
|
+
selectedAgentId,
|
|
1765
|
+
onSelectAgent,
|
|
1568
1766
|
user: {
|
|
1569
1767
|
id: userId,
|
|
1570
1768
|
name: effectiveUserName,
|
|
@@ -1579,7 +1777,12 @@ var CopilotzChat = ({
|
|
|
1579
1777
|
onAddMemory,
|
|
1580
1778
|
onUpdateMemory,
|
|
1581
1779
|
onDeleteMemory,
|
|
1582
|
-
className
|
|
1780
|
+
className,
|
|
1781
|
+
initialInput: initialPrompt && !promptHandled && urlSync?.promptBehavior !== "auto-send" ? initialPrompt : void 0,
|
|
1782
|
+
onInitialInputConsumed: () => {
|
|
1783
|
+
clearInitialPrompt();
|
|
1784
|
+
setPromptHandled(true);
|
|
1785
|
+
}
|
|
1583
1786
|
}
|
|
1584
1787
|
) });
|
|
1585
1788
|
};
|
|
@@ -1594,7 +1797,8 @@ export {
|
|
|
1594
1797
|
resolveAssetsInMessages,
|
|
1595
1798
|
runCopilotzStream,
|
|
1596
1799
|
updateThread,
|
|
1597
|
-
useCopilotz
|
|
1800
|
+
useCopilotz,
|
|
1801
|
+
useUrlState
|
|
1598
1802
|
};
|
|
1599
1803
|
/*! Bundled license information:
|
|
1600
1804
|
|