@copilotz/chat-adapter 0.1.0 → 0.1.1
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 +150 -3
- package/dist/index.js +273 -83
- 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 = [];
|
|
@@ -610,6 +611,110 @@ async function resolveAssetsInMessages(messages) {
|
|
|
610
611
|
return resolved;
|
|
611
612
|
}
|
|
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());
|
|
634
|
+
}
|
|
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
|
+
};
|
|
716
|
+
}
|
|
717
|
+
|
|
613
718
|
// src/useCopilotzChat.ts
|
|
614
719
|
var nowTs = () => Date.now();
|
|
615
720
|
var generateId = () => globalThis.crypto?.randomUUID?.() ?? `id-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
@@ -667,48 +772,45 @@ 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
|
-
|
|
704
|
-
const messagesRequestRef = useRef(0);
|
|
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 [isStreaming, setIsStreaming] = useState2(false);
|
|
790
|
+
const [userContextSeed, setUserContextSeed] = useState2(initialContext || {});
|
|
791
|
+
const preferredAgentRef = useRef2(preferredAgentName ?? null);
|
|
792
|
+
const threadsRef = useRef2(threads);
|
|
793
|
+
const threadMetadataMapRef = useRef2(threadMetadataMap);
|
|
794
|
+
const threadExternalIdMapRef = useRef2(threadExternalIdMap);
|
|
795
|
+
const currentThreadIdRef = useRef2(currentThreadId);
|
|
796
|
+
const currentThreadExternalIdRef = useRef2(currentThreadExternalId);
|
|
797
|
+
const userContextSeedRef = useRef2(userContextSeed);
|
|
798
|
+
threadsRef.current = threads;
|
|
799
|
+
threadMetadataMapRef.current = threadMetadataMap;
|
|
800
|
+
threadExternalIdMapRef.current = threadExternalIdMap;
|
|
801
|
+
currentThreadIdRef.current = currentThreadId;
|
|
802
|
+
currentThreadExternalIdRef.current = currentThreadExternalId;
|
|
803
|
+
userContextSeedRef.current = userContextSeed;
|
|
804
|
+
preferredAgentRef.current = preferredAgentName ?? null;
|
|
805
|
+
const abortControllerRef = useRef2(null);
|
|
806
|
+
const messagesRequestRef = useRef2(0);
|
|
807
|
+
const initializationRef = useRef2({ userId: null, started: false });
|
|
808
|
+
useEffect2(() => {
|
|
707
809
|
if (initialContext) {
|
|
708
810
|
setUserContextSeed((prev) => ({ ...prev, ...initialContext }));
|
|
709
811
|
}
|
|
710
812
|
}, [initialContext]);
|
|
711
|
-
const processToolOutput =
|
|
813
|
+
const processToolOutput = useCallback2((output) => {
|
|
712
814
|
if (!output) return;
|
|
713
815
|
const contextPatch = {};
|
|
714
816
|
if (output.userContext && typeof output.userContext === "object") {
|
|
@@ -719,7 +821,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
719
821
|
}
|
|
720
822
|
onToolOutput?.(output);
|
|
721
823
|
}, [onToolOutput]);
|
|
722
|
-
const handleStreamMessageEvent =
|
|
824
|
+
const handleStreamMessageEvent = useCallback2((event) => {
|
|
723
825
|
const payload = event?.payload;
|
|
724
826
|
if (!payload) return;
|
|
725
827
|
if (payload.senderType === "tool") {
|
|
@@ -776,7 +878,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
776
878
|
});
|
|
777
879
|
}
|
|
778
880
|
}, [processToolOutput]);
|
|
779
|
-
const updateThreadsState =
|
|
881
|
+
const updateThreadsState = useCallback2((rawThreads, preferredExternalId) => {
|
|
780
882
|
const metadataMap = {};
|
|
781
883
|
const externalMap = {};
|
|
782
884
|
const normalized = rawThreads.map((thread) => {
|
|
@@ -818,7 +920,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
818
920
|
setCurrentThreadExternalId(nextThreadId ? externalMap[nextThreadId] ?? null : null);
|
|
819
921
|
return nextThreadId;
|
|
820
922
|
}, []);
|
|
821
|
-
const fetchAndSetThreadsState =
|
|
923
|
+
const fetchAndSetThreadsState = useCallback2(async (uid, preferredExternalId) => {
|
|
822
924
|
try {
|
|
823
925
|
const rawThreads = await fetchThreads(uid);
|
|
824
926
|
return updateThreadsState(rawThreads, preferredExternalId);
|
|
@@ -828,7 +930,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
828
930
|
return null;
|
|
829
931
|
}
|
|
830
932
|
}, [updateThreadsState]);
|
|
831
|
-
const loadThreadMessages =
|
|
933
|
+
const loadThreadMessages = useCallback2(async (threadId) => {
|
|
832
934
|
const requestId = Date.now();
|
|
833
935
|
messagesRequestRef.current = requestId;
|
|
834
936
|
try {
|
|
@@ -859,13 +961,13 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
859
961
|
console.error(`Error loading messages for thread ${threadId}`, error);
|
|
860
962
|
}
|
|
861
963
|
}, [processToolOutput]);
|
|
862
|
-
const handleSelectThread =
|
|
964
|
+
const handleSelectThread = useCallback2(async (threadId) => {
|
|
863
965
|
setCurrentThreadId(threadId);
|
|
864
966
|
const extMap = threadExternalIdMapRef.current;
|
|
865
967
|
setCurrentThreadExternalId(extMap[threadId] ?? null);
|
|
866
968
|
await loadThreadMessages(threadId);
|
|
867
969
|
}, [loadThreadMessages]);
|
|
868
|
-
const handleCreateThread =
|
|
970
|
+
const handleCreateThread = useCallback2((title) => {
|
|
869
971
|
const id = generateId();
|
|
870
972
|
const now = nowTs();
|
|
871
973
|
const newThread = {
|
|
@@ -883,7 +985,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
883
985
|
setCurrentThreadExternalId(id);
|
|
884
986
|
setMessages([]);
|
|
885
987
|
}, []);
|
|
886
|
-
const handleRenameThread =
|
|
988
|
+
const handleRenameThread = useCallback2(async (threadId, newTitle) => {
|
|
887
989
|
const trimmedTitle = newTitle.trim();
|
|
888
990
|
if (!trimmedTitle) return;
|
|
889
991
|
setThreads(
|
|
@@ -907,7 +1009,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
907
1009
|
}
|
|
908
1010
|
}
|
|
909
1011
|
}, [userId, fetchAndSetThreadsState]);
|
|
910
|
-
const handleArchiveThread =
|
|
1012
|
+
const handleArchiveThread = useCallback2(async (threadId) => {
|
|
911
1013
|
const thread = threadsRef.current.find((t) => t.id === threadId);
|
|
912
1014
|
if (!thread) return;
|
|
913
1015
|
const newArchivedStatus = !thread.isArchived;
|
|
@@ -927,7 +1029,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
927
1029
|
}
|
|
928
1030
|
}
|
|
929
1031
|
}, [userId, fetchAndSetThreadsState]);
|
|
930
|
-
const handleDeleteThread =
|
|
1032
|
+
const handleDeleteThread = useCallback2(async (threadId) => {
|
|
931
1033
|
const extMap = threadExternalIdMapRef.current;
|
|
932
1034
|
const isPlaceholder = extMap[threadId] === threadId;
|
|
933
1035
|
setThreads((prev) => prev.filter((t) => t.id !== threadId));
|
|
@@ -964,13 +1066,17 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
964
1066
|
}
|
|
965
1067
|
}
|
|
966
1068
|
}, [userId, fetchAndSetThreadsState, loadThreadMessages]);
|
|
967
|
-
const handleStop =
|
|
1069
|
+
const handleStop = useCallback2(() => {
|
|
968
1070
|
abortControllerRef.current?.abort();
|
|
969
1071
|
abortControllerRef.current = null;
|
|
970
1072
|
setIsStreaming(false);
|
|
971
|
-
setMessages((prev) =>
|
|
1073
|
+
setMessages((prev) => {
|
|
1074
|
+
const hasStreaming = prev.some((msg) => msg.isStreaming);
|
|
1075
|
+
if (!hasStreaming) return prev;
|
|
1076
|
+
return prev.map((msg) => msg.isStreaming ? { ...msg, isStreaming: false, isComplete: true } : msg);
|
|
1077
|
+
});
|
|
972
1078
|
}, []);
|
|
973
|
-
const handleStreamAssetEvent =
|
|
1079
|
+
const handleStreamAssetEvent = useCallback2((payload, assistantMessageId) => {
|
|
974
1080
|
if (!payload?.dataUrl) return;
|
|
975
1081
|
const mimeType = payload.mime || "image/png";
|
|
976
1082
|
const dataUrl = payload.dataUrl;
|
|
@@ -992,22 +1098,36 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
992
1098
|
isComplete: true
|
|
993
1099
|
} : msg));
|
|
994
1100
|
}, []);
|
|
995
|
-
const sendCopilotzMessage =
|
|
1101
|
+
const sendCopilotzMessage = useCallback2(async (params) => {
|
|
996
1102
|
let currentAssistantId = generateId();
|
|
997
1103
|
params.onBeforeStart?.(currentAssistantId);
|
|
998
1104
|
let hasStreamProgress = false;
|
|
999
1105
|
let pendingStartNewAssistantBubble = false;
|
|
1000
|
-
const
|
|
1106
|
+
const updateStreamingMessage = (partial, isComplete) => {
|
|
1107
|
+
if (partial && partial.length > 0) {
|
|
1108
|
+
hasStreamProgress = true;
|
|
1109
|
+
}
|
|
1001
1110
|
setMessages((prev) => {
|
|
1002
1111
|
const idx = prev.findIndex((m) => m.id === currentAssistantId);
|
|
1003
|
-
if (idx >= 0 && prev[idx].role === "assistant"
|
|
1004
|
-
|
|
1112
|
+
if (idx >= 0 && prev[idx].role === "assistant") {
|
|
1113
|
+
const msg = prev[idx];
|
|
1114
|
+
if (msg.content === partial && msg.isStreaming === !isComplete && msg.isComplete === isComplete) {
|
|
1115
|
+
return prev;
|
|
1116
|
+
}
|
|
1117
|
+
const updated = [...prev];
|
|
1118
|
+
updated[idx] = { ...msg, content: partial, isStreaming: !isComplete, isComplete };
|
|
1119
|
+
return updated;
|
|
1005
1120
|
}
|
|
1006
1121
|
const last = prev[prev.length - 1];
|
|
1007
1122
|
if (last && last.role === "assistant" && last.isStreaming) {
|
|
1008
1123
|
currentAssistantId = last.id;
|
|
1009
1124
|
pendingStartNewAssistantBubble = false;
|
|
1010
|
-
|
|
1125
|
+
if (last.content === partial && last.isStreaming === !isComplete && last.isComplete === isComplete) {
|
|
1126
|
+
return prev;
|
|
1127
|
+
}
|
|
1128
|
+
const updated = [...prev];
|
|
1129
|
+
updated[prev.length - 1] = { ...last, content: partial, isStreaming: !isComplete, isComplete };
|
|
1130
|
+
return updated;
|
|
1011
1131
|
}
|
|
1012
1132
|
if (pendingStartNewAssistantBubble || !prev.length || (prev[prev.length - 1].role !== "assistant" || !prev[prev.length - 1].isStreaming)) {
|
|
1013
1133
|
const newId = generateId();
|
|
@@ -1018,25 +1138,26 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
1018
1138
|
{
|
|
1019
1139
|
id: newId,
|
|
1020
1140
|
role: "assistant",
|
|
1021
|
-
content:
|
|
1141
|
+
content: partial,
|
|
1022
1142
|
timestamp: nowTs(),
|
|
1023
|
-
isStreaming:
|
|
1024
|
-
isComplete
|
|
1143
|
+
isStreaming: !isComplete,
|
|
1144
|
+
isComplete
|
|
1025
1145
|
}
|
|
1026
1146
|
];
|
|
1027
1147
|
}
|
|
1028
1148
|
return prev;
|
|
1029
1149
|
});
|
|
1030
1150
|
};
|
|
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
1151
|
const finalizeCurrentAssistantBubble = () => {
|
|
1039
|
-
setMessages((prev) =>
|
|
1152
|
+
setMessages((prev) => {
|
|
1153
|
+
const idx = prev.findIndex((m) => m.id === currentAssistantId);
|
|
1154
|
+
if (idx < 0) return prev;
|
|
1155
|
+
const msg = prev[idx];
|
|
1156
|
+
if (!msg.isStreaming && msg.isComplete) return prev;
|
|
1157
|
+
const updated = [...prev];
|
|
1158
|
+
updated[idx] = { ...msg, isStreaming: false, isComplete: true };
|
|
1159
|
+
return updated;
|
|
1160
|
+
});
|
|
1040
1161
|
};
|
|
1041
1162
|
const curThreadId = currentThreadIdRef.current;
|
|
1042
1163
|
const toServerMessageFromEvent = async (event) => {
|
|
@@ -1139,6 +1260,11 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
1139
1260
|
const currentThreadMetadataMap = threadMetadataMapRef.current;
|
|
1140
1261
|
const messageMetadata = metadataKey ? currentThreadMetadataMap[metadataKey]?.userContext : void 0;
|
|
1141
1262
|
const threadMetadata = metadataKey ? currentThreadMetadataMap[metadataKey] : void 0;
|
|
1263
|
+
const mergedMetadata = {
|
|
1264
|
+
...messageMetadata ?? {},
|
|
1265
|
+
...params.metadata ?? {}
|
|
1266
|
+
};
|
|
1267
|
+
const finalMetadata = Object.keys(mergedMetadata).length > 0 ? mergedMetadata : void 0;
|
|
1142
1268
|
await runCopilotzStream({
|
|
1143
1269
|
threadId: params.threadId ?? void 0,
|
|
1144
1270
|
threadExternalId: params.threadExternalId ?? void 0,
|
|
@@ -1152,9 +1278,10 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
1152
1278
|
}
|
|
1153
1279
|
},
|
|
1154
1280
|
attachments: params.attachments,
|
|
1155
|
-
metadata:
|
|
1281
|
+
metadata: finalMetadata,
|
|
1156
1282
|
threadMetadata: params.threadMetadata ?? threadMetadata,
|
|
1157
1283
|
toolCalls: params.toolCalls,
|
|
1284
|
+
selectedAgent: params.agentName ?? preferredAgentRef.current ?? null,
|
|
1158
1285
|
onToken: (token, isComplete) => updateStreamingMessage(token, isComplete),
|
|
1159
1286
|
onMessageEvent: async (event) => {
|
|
1160
1287
|
const type = event?.type || "";
|
|
@@ -1289,7 +1416,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
1289
1416
|
}
|
|
1290
1417
|
return currentAssistantId;
|
|
1291
1418
|
}, [handleStreamMessageEvent, handleStreamAssetEvent]);
|
|
1292
|
-
const handleSendMessage =
|
|
1419
|
+
const handleSendMessage = useCallback2(async (content, attachments = []) => {
|
|
1293
1420
|
if (!content.trim() && attachments.length === 0) return;
|
|
1294
1421
|
if (!userId) return;
|
|
1295
1422
|
const timestamp = nowTs();
|
|
@@ -1349,6 +1476,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
1349
1476
|
userId,
|
|
1350
1477
|
// userName can be anything, but let's try to find it in context or just fallback
|
|
1351
1478
|
userName: userContextSeedRef.current?.profile?.full_name ?? userId,
|
|
1479
|
+
agentName: preferredAgentRef.current,
|
|
1352
1480
|
// Include pending title for new threads
|
|
1353
1481
|
threadMetadata: pendingTitle ? { name: pendingTitle } : void 0
|
|
1354
1482
|
});
|
|
@@ -1365,7 +1493,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
1365
1493
|
} : msg));
|
|
1366
1494
|
}
|
|
1367
1495
|
}, [userId, fetchAndSetThreadsState, loadThreadMessages, sendCopilotzMessage]);
|
|
1368
|
-
const bootstrapConversation =
|
|
1496
|
+
const bootstrapConversation = useCallback2(async (uid) => {
|
|
1369
1497
|
if (!bootstrap?.initialToolCalls && !bootstrap?.initialMessage) return;
|
|
1370
1498
|
const bootstrapThreadExternalId = generateId();
|
|
1371
1499
|
setCurrentThreadId(bootstrapThreadExternalId);
|
|
@@ -1379,6 +1507,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
1379
1507
|
content: bootstrap.initialMessage || "",
|
|
1380
1508
|
toolCalls: bootstrap.initialToolCalls,
|
|
1381
1509
|
userId: uid,
|
|
1510
|
+
agentName: preferredAgentRef.current,
|
|
1382
1511
|
threadMetadata: {
|
|
1383
1512
|
name: defaultThreadName || "Main Thread"
|
|
1384
1513
|
}
|
|
@@ -1400,7 +1529,7 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
1400
1529
|
]);
|
|
1401
1530
|
}
|
|
1402
1531
|
}, [fetchAndSetThreadsState, loadThreadMessages, sendCopilotzMessage, bootstrap, defaultThreadName]);
|
|
1403
|
-
const reset =
|
|
1532
|
+
const reset = useCallback2(() => {
|
|
1404
1533
|
setThreads([]);
|
|
1405
1534
|
setThreadMetadataMap({});
|
|
1406
1535
|
setThreadExternalIdMap({});
|
|
@@ -1411,14 +1540,15 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
1411
1540
|
setIsStreaming(false);
|
|
1412
1541
|
abortControllerRef.current?.abort();
|
|
1413
1542
|
}, []);
|
|
1414
|
-
|
|
1543
|
+
useEffect2(() => {
|
|
1415
1544
|
if (userId) {
|
|
1416
1545
|
if (initializationRef.current.userId === userId && initializationRef.current.started) {
|
|
1417
1546
|
return;
|
|
1418
1547
|
}
|
|
1419
1548
|
initializationRef.current = { userId, started: true };
|
|
1420
1549
|
const init = async () => {
|
|
1421
|
-
const
|
|
1550
|
+
const urlPreferredThread = isUrlSyncEnabled ? urlState.threadId : void 0;
|
|
1551
|
+
const preferredThreadId = await fetchAndSetThreadsState(userId, urlPreferredThread);
|
|
1422
1552
|
if (preferredThreadId) {
|
|
1423
1553
|
await loadThreadMessages(preferredThreadId);
|
|
1424
1554
|
} else if (bootstrap) {
|
|
@@ -1430,8 +1560,13 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
1430
1560
|
initializationRef.current = { userId: null, started: false };
|
|
1431
1561
|
reset();
|
|
1432
1562
|
}
|
|
1433
|
-
}, [userId, fetchAndSetThreadsState, loadThreadMessages, bootstrapConversation, reset, bootstrap]);
|
|
1434
|
-
|
|
1563
|
+
}, [userId, fetchAndSetThreadsState, loadThreadMessages, bootstrapConversation, reset, bootstrap, isUrlSyncEnabled, urlState.threadId]);
|
|
1564
|
+
useEffect2(() => {
|
|
1565
|
+
if (!isUrlSyncEnabled) return;
|
|
1566
|
+
if (!initializationRef.current.started) return;
|
|
1567
|
+
setUrlThreadId(currentThreadExternalId);
|
|
1568
|
+
}, [currentThreadExternalId, isUrlSyncEnabled, setUrlThreadId]);
|
|
1569
|
+
useEffect2(() => {
|
|
1435
1570
|
if (!currentThreadId) return;
|
|
1436
1571
|
const metadata = threadMetadataMap[currentThreadId];
|
|
1437
1572
|
if (!metadata) return;
|
|
@@ -1454,7 +1589,16 @@ function useCopilotz({ userId, initialContext, bootstrap, defaultThreadName, onT
|
|
|
1454
1589
|
stopGeneration: handleStop,
|
|
1455
1590
|
fetchAndSetThreadsState,
|
|
1456
1591
|
loadThreadMessages,
|
|
1457
|
-
reset
|
|
1592
|
+
reset,
|
|
1593
|
+
// URL state
|
|
1594
|
+
/** Initial prompt from URL (if urlSync enabled) - use for pre-filling input */
|
|
1595
|
+
initialPrompt: isUrlSyncEnabled ? urlState.prompt : null,
|
|
1596
|
+
/** Clear the initial prompt from URL (call after consuming it) */
|
|
1597
|
+
clearInitialPrompt: clearUrlPrompt,
|
|
1598
|
+
/** URL agent ID (if urlSync enabled) - use for agent pre-selection */
|
|
1599
|
+
urlAgentId: isUrlSyncEnabled ? urlState.agentId : null,
|
|
1600
|
+
/** Update agent ID in URL */
|
|
1601
|
+
setUrlAgentId
|
|
1458
1602
|
};
|
|
1459
1603
|
}
|
|
1460
1604
|
|
|
@@ -1476,8 +1620,14 @@ var CopilotzChat = ({
|
|
|
1476
1620
|
onAddMemory,
|
|
1477
1621
|
onUpdateMemory,
|
|
1478
1622
|
onDeleteMemory,
|
|
1479
|
-
|
|
1623
|
+
suggestions,
|
|
1624
|
+
agentOptions = [],
|
|
1625
|
+
selectedAgentId = null,
|
|
1626
|
+
onSelectAgent,
|
|
1627
|
+
className,
|
|
1628
|
+
urlSync
|
|
1480
1629
|
}) => {
|
|
1630
|
+
const selectedAgent = agentOptions.find((agent) => agent.id === selectedAgentId) || null;
|
|
1481
1631
|
const {
|
|
1482
1632
|
messages,
|
|
1483
1633
|
threads,
|
|
@@ -1490,14 +1640,44 @@ var CopilotzChat = ({
|
|
|
1490
1640
|
renameThread,
|
|
1491
1641
|
archiveThread,
|
|
1492
1642
|
deleteThread: deleteThread2,
|
|
1493
|
-
stopGeneration
|
|
1643
|
+
stopGeneration,
|
|
1644
|
+
initialPrompt,
|
|
1645
|
+
clearInitialPrompt,
|
|
1646
|
+
urlAgentId,
|
|
1647
|
+
setUrlAgentId
|
|
1494
1648
|
} = useCopilotz({
|
|
1495
1649
|
userId,
|
|
1496
1650
|
initialContext,
|
|
1497
1651
|
bootstrap,
|
|
1498
1652
|
defaultThreadName: userConfig?.labels?.defaultThreadName,
|
|
1499
|
-
onToolOutput
|
|
1653
|
+
onToolOutput,
|
|
1654
|
+
preferredAgentName: selectedAgent?.name ?? null,
|
|
1655
|
+
urlSync
|
|
1500
1656
|
});
|
|
1657
|
+
const [promptHandled, setPromptHandled] = useState3(false);
|
|
1658
|
+
useEffect3(() => {
|
|
1659
|
+
if (urlAgentId && onSelectAgent && urlAgentId !== selectedAgentId) {
|
|
1660
|
+
const agentExists = agentOptions.some((a) => a.id === urlAgentId);
|
|
1661
|
+
if (agentExists) {
|
|
1662
|
+
onSelectAgent(urlAgentId);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
}, [urlAgentId, selectedAgentId, onSelectAgent, agentOptions]);
|
|
1666
|
+
useEffect3(() => {
|
|
1667
|
+
if (selectedAgentId && urlSync?.enabled) {
|
|
1668
|
+
setUrlAgentId(selectedAgentId);
|
|
1669
|
+
}
|
|
1670
|
+
}, [selectedAgentId, urlSync?.enabled, setUrlAgentId]);
|
|
1671
|
+
useEffect3(() => {
|
|
1672
|
+
if (initialPrompt && !promptHandled && urlSync?.promptBehavior === "auto-send") {
|
|
1673
|
+
const timer = setTimeout(() => {
|
|
1674
|
+
void sendMessage(initialPrompt);
|
|
1675
|
+
clearInitialPrompt();
|
|
1676
|
+
setPromptHandled(true);
|
|
1677
|
+
}, 500);
|
|
1678
|
+
return () => clearTimeout(timer);
|
|
1679
|
+
}
|
|
1680
|
+
}, [initialPrompt, promptHandled, urlSync?.promptBehavior, sendMessage, clearInitialPrompt]);
|
|
1501
1681
|
const chatCallbacks = useMemo(() => ({
|
|
1502
1682
|
onSendMessage: (content, attachments) => {
|
|
1503
1683
|
void sendMessage(content, attachments);
|
|
@@ -1565,6 +1745,10 @@ var CopilotzChat = ({
|
|
|
1565
1745
|
config: mergedConfig,
|
|
1566
1746
|
callbacks: chatCallbacks,
|
|
1567
1747
|
isGenerating: isStreaming,
|
|
1748
|
+
suggestions,
|
|
1749
|
+
agentOptions,
|
|
1750
|
+
selectedAgentId,
|
|
1751
|
+
onSelectAgent,
|
|
1568
1752
|
user: {
|
|
1569
1753
|
id: userId,
|
|
1570
1754
|
name: effectiveUserName,
|
|
@@ -1579,7 +1763,12 @@ var CopilotzChat = ({
|
|
|
1579
1763
|
onAddMemory,
|
|
1580
1764
|
onUpdateMemory,
|
|
1581
1765
|
onDeleteMemory,
|
|
1582
|
-
className
|
|
1766
|
+
className,
|
|
1767
|
+
initialInput: initialPrompt && !promptHandled && urlSync?.promptBehavior !== "auto-send" ? initialPrompt : void 0,
|
|
1768
|
+
onInitialInputConsumed: () => {
|
|
1769
|
+
clearInitialPrompt();
|
|
1770
|
+
setPromptHandled(true);
|
|
1771
|
+
}
|
|
1583
1772
|
}
|
|
1584
1773
|
) });
|
|
1585
1774
|
};
|
|
@@ -1594,7 +1783,8 @@ export {
|
|
|
1594
1783
|
resolveAssetsInMessages,
|
|
1595
1784
|
runCopilotzStream,
|
|
1596
1785
|
updateThread,
|
|
1597
|
-
useCopilotz
|
|
1786
|
+
useCopilotz,
|
|
1787
|
+
useUrlState
|
|
1598
1788
|
};
|
|
1599
1789
|
/*! Bundled license information:
|
|
1600
1790
|
|