@copilotz/chat-adapter 0.9.22 → 0.9.23

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/index.d.ts CHANGED
@@ -34,6 +34,7 @@ type RestThread = {
34
34
  createdAt?: string;
35
35
  updatedAt?: string;
36
36
  };
37
+ type ThreadActivityStatus = "idle" | "running" | "failed";
37
38
  type RestMessage = {
38
39
  id: string;
39
40
  threadId: string;
@@ -208,6 +209,15 @@ declare function useCopilotz({ userId, userName, userAvatar, assistantName, agen
208
209
  threads: ChatThread[];
209
210
  currentThreadId: string | null;
210
211
  isStreaming: boolean;
212
+ threadActivityStatus: ThreadActivityStatus;
213
+ isRecoveringStream: boolean;
214
+ activityNotice: {
215
+ tone: "info";
216
+ message: string;
217
+ } | {
218
+ tone: "error";
219
+ message: string;
220
+ } | null;
211
221
  specialState: SpecialChatState | null;
212
222
  clearSpecialState: () => void;
213
223
  userContextSeed: Partial<ChatUserContext>;
package/dist/index.js CHANGED
@@ -1659,6 +1659,8 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
1659
1659
  const [isLoadingOlderMessages, setIsLoadingOlderMessages] = useState2(false);
1660
1660
  const [messagePageInfo, setMessagePageInfo] = useState2(createEmptyMessagePageInfo);
1661
1661
  const [isStreaming, setIsStreaming] = useState2(false);
1662
+ const [threadActivityStatus, setThreadActivityStatus] = useState2("idle");
1663
+ const [isRecoveringStream, setIsRecoveringStream] = useState2(false);
1662
1664
  const [specialState, setSpecialState] = useState2(null);
1663
1665
  const [userContextSeed, setUserContextSeed] = useState2(initialContext || {});
1664
1666
  const preferredAgentRef = useRef2(preferredAgentName ?? null);
@@ -1918,8 +1920,83 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
1918
1920
  }
1919
1921
  }
1920
1922
  }, [getRequestHeaders, prepareThreadMessages]);
1923
+ const finalizeStreamingPlaceholders = useCallback2((nextActivityStatus = "idle") => {
1924
+ setThreadActivityStatus(nextActivityStatus);
1925
+ setIsRecoveringStream(false);
1926
+ setIsStreaming(false);
1927
+ setMessages((prev) => {
1928
+ const hasStreaming = prev.some((msg) => msg.isStreaming);
1929
+ if (!hasStreaming) return prev;
1930
+ return prev.map((msg) => msg.isStreaming ? closeAssistantMessage(msg) : msg);
1931
+ });
1932
+ }, []);
1933
+ const startThreadActivityRecovery = useCallback2(
1934
+ (threadId) => {
1935
+ const generation = ++recoveryPollGenerationRef.current;
1936
+ setThreadActivityStatus("running");
1937
+ setIsRecoveringStream(true);
1938
+ setIsStreaming(true);
1939
+ void (async () => {
1940
+ let delayMs = 2e3;
1941
+ let attempts = 0;
1942
+ while (recoveryPollGenerationRef.current === generation) {
1943
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
1944
+ if (recoveryPollGenerationRef.current !== generation) return;
1945
+ if (currentThreadIdRef.current !== threadId) return;
1946
+ try {
1947
+ const activity = await fetchThreadActivity(threadId, getRequestHeaders);
1948
+ setThreadActivityStatus(activity.status);
1949
+ if (activity.status === "running") {
1950
+ attempts += 1;
1951
+ if (attempts % 3 === 0) {
1952
+ await loadThreadMessages(threadId);
1953
+ }
1954
+ if (attempts >= 15) {
1955
+ delayMs = 5e3;
1956
+ }
1957
+ continue;
1958
+ }
1959
+ await loadThreadMessages(threadId);
1960
+ finalizeStreamingPlaceholders(activity.status);
1961
+ return;
1962
+ } catch (error) {
1963
+ if (isAbortError(error)) return;
1964
+ attempts += 1;
1965
+ if (attempts >= 15) {
1966
+ delayMs = 5e3;
1967
+ }
1968
+ }
1969
+ }
1970
+ })();
1971
+ },
1972
+ [finalizeStreamingPlaceholders, getRequestHeaders, loadThreadMessages]
1973
+ );
1974
+ const refreshThreadActivity = useCallback2(
1975
+ async (threadId) => {
1976
+ try {
1977
+ const activity = await fetchThreadActivity(threadId, getRequestHeaders);
1978
+ setThreadActivityStatus(activity.status);
1979
+ if (activity.status === "running") {
1980
+ startThreadActivityRecovery(threadId);
1981
+ return;
1982
+ }
1983
+ setIsRecoveringStream(false);
1984
+ if (activity.status === "failed") {
1985
+ await loadThreadMessages(threadId);
1986
+ }
1987
+ } catch (error) {
1988
+ if (isAbortError(error)) return;
1989
+ console.warn("Unable to load Copilotz thread activity", error);
1990
+ }
1991
+ },
1992
+ [getRequestHeaders, loadThreadMessages, startThreadActivityRecovery]
1993
+ );
1921
1994
  const handleSelectThread = useCallback2(
1922
1995
  async (threadId) => {
1996
+ recoveryPollGenerationRef.current += 1;
1997
+ setThreadActivityStatus("idle");
1998
+ setIsRecoveringStream(false);
1999
+ setIsStreaming(false);
1923
2000
  setCurrentThreadId(threadId);
1924
2001
  setMessages([]);
1925
2002
  setMessagePageInfo(createEmptyMessagePageInfo());
@@ -1927,13 +2004,18 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
1927
2004
  const extMap = threadExternalIdMapRef.current;
1928
2005
  setCurrentThreadExternalId(extMap[threadId] ?? null);
1929
2006
  await loadThreadMessages(threadId);
2007
+ await refreshThreadActivity(threadId);
1930
2008
  },
1931
- [loadThreadMessages]
2009
+ [loadThreadMessages, refreshThreadActivity]
1932
2010
  );
1933
2011
  const handleCreateThread = useCallback2((title) => {
1934
2012
  messagesRequestRef.current += 1;
1935
2013
  setIsMessagesLoading(false);
1936
2014
  setIsLoadingOlderMessages(false);
2015
+ recoveryPollGenerationRef.current += 1;
2016
+ setThreadActivityStatus("idle");
2017
+ setIsRecoveringStream(false);
2018
+ setIsStreaming(false);
1937
2019
  const id = generateId();
1938
2020
  const now = nowTs();
1939
2021
  const newThread = {
@@ -2090,6 +2172,8 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2090
2172
  recoveryPollGenerationRef.current += 1;
2091
2173
  abortControllerRef.current?.abort();
2092
2174
  abortControllerRef.current = null;
2175
+ setThreadActivityStatus("idle");
2176
+ setIsRecoveringStream(false);
2093
2177
  setIsStreaming(false);
2094
2178
  setMessages((prev) => {
2095
2179
  const hasStreaming = prev.some((msg) => msg.isStreaming);
@@ -2118,52 +2202,6 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2118
2202
  )
2119
2203
  );
2120
2204
  }, []);
2121
- const finalizeStreamingPlaceholders = useCallback2(() => {
2122
- setIsStreaming(false);
2123
- setMessages((prev) => {
2124
- const hasStreaming = prev.some((msg) => msg.isStreaming);
2125
- if (!hasStreaming) return prev;
2126
- return prev.map((msg) => msg.isStreaming ? closeAssistantMessage(msg) : msg);
2127
- });
2128
- }, []);
2129
- const startThreadActivityRecovery = useCallback2(
2130
- (threadId) => {
2131
- const generation = ++recoveryPollGenerationRef.current;
2132
- setIsStreaming(true);
2133
- void (async () => {
2134
- let delayMs = 2e3;
2135
- let attempts = 0;
2136
- while (recoveryPollGenerationRef.current === generation) {
2137
- await new Promise((resolve) => setTimeout(resolve, delayMs));
2138
- if (recoveryPollGenerationRef.current !== generation) return;
2139
- if (currentThreadIdRef.current !== threadId) return;
2140
- try {
2141
- const activity = await fetchThreadActivity(threadId, getRequestHeaders);
2142
- if (activity.status === "running") {
2143
- attempts += 1;
2144
- if (attempts % 3 === 0) {
2145
- await loadThreadMessages(threadId);
2146
- }
2147
- if (attempts >= 15) {
2148
- delayMs = 5e3;
2149
- }
2150
- continue;
2151
- }
2152
- await loadThreadMessages(threadId);
2153
- finalizeStreamingPlaceholders();
2154
- return;
2155
- } catch (error) {
2156
- if (isAbortError(error)) return;
2157
- attempts += 1;
2158
- if (attempts >= 15) {
2159
- delayMs = 5e3;
2160
- }
2161
- }
2162
- }
2163
- })();
2164
- },
2165
- [finalizeStreamingPlaceholders, getRequestHeaders, loadThreadMessages]
2166
- );
2167
2205
  const sendCopilotzMessage = useCallback2(
2168
2206
  async (params) => {
2169
2207
  let currentAssistantId = params.assistantMessageId ?? generateId();
@@ -2308,6 +2346,8 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2308
2346
  abortControllerRef.current = abortController;
2309
2347
  stopRequestedRef.current = false;
2310
2348
  recoveryPollGenerationRef.current += 1;
2349
+ setThreadActivityStatus("running");
2350
+ setIsRecoveringStream(false);
2311
2351
  setIsStreaming(true);
2312
2352
  liveToolUpdatesRef.current = [];
2313
2353
  let streamError = null;
@@ -2493,10 +2533,12 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2493
2533
  const activityThreadId = await resolveActivityThreadId();
2494
2534
  if (activityThreadId) {
2495
2535
  const activity = await fetchThreadActivity(activityThreadId, getRequestHeaders);
2536
+ setThreadActivityStatus(activity.status);
2496
2537
  if (activity.status === "running") {
2497
2538
  recoveryStarted = true;
2498
2539
  startThreadActivityRecovery(activityThreadId);
2499
2540
  } else if (streamError || activity.status === "failed") {
2541
+ setIsRecoveringStream(false);
2500
2542
  await loadThreadMessages(activityThreadId);
2501
2543
  }
2502
2544
  }
@@ -2677,6 +2719,8 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2677
2719
  ]);
2678
2720
  setMessagePageInfo(createEmptyMessagePageInfo());
2679
2721
  persistedToolUpdatesRef.current = [];
2722
+ setThreadActivityStatus("running");
2723
+ setIsRecoveringStream(false);
2680
2724
  setSpecialState(null);
2681
2725
  try {
2682
2726
  await sendCopilotzMessage({
@@ -2730,6 +2774,8 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2730
2774
  setIsMessagesLoading(false);
2731
2775
  setIsLoadingOlderMessages(false);
2732
2776
  setIsStreaming(false);
2777
+ setThreadActivityStatus("idle");
2778
+ setIsRecoveringStream(false);
2733
2779
  setMessagePageInfo(createEmptyMessagePageInfo());
2734
2780
  persistedToolUpdatesRef.current = [];
2735
2781
  setSpecialState(null);
@@ -2746,6 +2792,7 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2746
2792
  const preferredThreadId = await fetchAndSetThreadsState(userId, urlPreferredThread);
2747
2793
  if (preferredThreadId) {
2748
2794
  await loadThreadMessages(preferredThreadId);
2795
+ await refreshThreadActivity(preferredThreadId);
2749
2796
  } else if (bootstrap) {
2750
2797
  await bootstrapConversation(userId);
2751
2798
  }
@@ -2755,7 +2802,7 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2755
2802
  initializationRef.current = { userId: null, started: false };
2756
2803
  reset();
2757
2804
  }
2758
- }, [userId, fetchAndSetThreadsState, loadThreadMessages, bootstrapConversation, reset, bootstrap, isUrlSyncEnabled]);
2805
+ }, [userId, fetchAndSetThreadsState, loadThreadMessages, refreshThreadActivity, bootstrapConversation, reset, bootstrap, isUrlSyncEnabled]);
2759
2806
  useEffect2(() => {
2760
2807
  if (!isUrlSyncEnabled) return;
2761
2808
  if (!initializationRef.current.started) return;
@@ -2772,6 +2819,13 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2772
2819
  }));
2773
2820
  }
2774
2821
  }, [currentThreadId, threadMetadataMap]);
2822
+ const activityNotice = threadActivityStatus === "running" && isRecoveringStream ? {
2823
+ tone: "info",
2824
+ message: "The agent is still working. Loading updates..."
2825
+ } : threadActivityStatus === "failed" ? {
2826
+ tone: "error",
2827
+ message: "The last run failed. Refreshing the latest messages..."
2828
+ } : null;
2775
2829
  return {
2776
2830
  messages: messages.map(toPublicChatMessage),
2777
2831
  isMessagesLoading,
@@ -2780,6 +2834,9 @@ function useCopilotz({ userId, userName, userAvatar, assistantName, agentOptions
2780
2834
  threads,
2781
2835
  currentThreadId,
2782
2836
  isStreaming,
2837
+ threadActivityStatus,
2838
+ isRecoveringStream,
2839
+ activityNotice,
2783
2840
  specialState,
2784
2841
  clearSpecialState,
2785
2842
  userContextSeed,
@@ -2812,7 +2869,7 @@ var CopilotzChat = ({ userId, userName, userAvatar, userEmail, initialContext, b
2812
2869
  if (!targetAgentId) return null;
2813
2870
  return targetAgentId;
2814
2871
  }, [targetAgentId]);
2815
- const { messages, isMessagesLoading, isLoadingOlderMessages, messagePageInfo, threads, currentThreadId, isStreaming, specialState, clearSpecialState, userContextSeed, sendMessage, createThread, selectThread, renameThread, archiveThread, updateThreadTags, editMessage, deleteThread: deleteThread2, stopGeneration, loadOlderMessages } = useCopilotz({
2872
+ const { messages, isMessagesLoading, isLoadingOlderMessages, messagePageInfo, threads, currentThreadId, isStreaming, activityNotice, specialState, clearSpecialState, userContextSeed, sendMessage, createThread, selectThread, renameThread, archiveThread, updateThreadTags, editMessage, deleteThread: deleteThread2, stopGeneration, loadOlderMessages } = useCopilotz({
2816
2873
  userId,
2817
2874
  userName,
2818
2875
  userAvatar,
@@ -2913,7 +2970,7 @@ var CopilotzChat = ({ userId, userName, userAvatar, userEmail, initialContext, b
2913
2970
  [userConfig?.branding?.title, userConfig?.branding?.avatar, userConfig?.branding?.subtitle]
2914
2971
  );
2915
2972
  const specialStateContent = specialState ? renderSpecialState?.(specialState, { clear: clearSpecialState }) : null;
2916
- return /* @__PURE__ */ jsx(ChatUserContextProvider, { initial: userContextSeed, children: specialStateContent ?? /* @__PURE__ */ jsx(ChatUI, { messages, isMessagesLoading, isLoadingOlderMessages, hasMoreMessagesBefore: messagePageInfo.hasMoreBefore, onLoadOlderMessages: loadOlderMessages, threads, currentThreadId, config: mergedConfig, callbacks: chatCallbacks, isGenerating: isStreaming, suggestions, agentOptions, selectedAgentId, onSelectAgent, participantIds, onParticipantsChange, targetAgentId, onTargetAgentChange, user: userProp, assistant: assistantProp, onAddMemory, onUpdateMemory, onDeleteMemory, userMenuSections, userMenuAdditionalItems, className }) });
2973
+ return /* @__PURE__ */ jsx(ChatUserContextProvider, { initial: userContextSeed, children: specialStateContent ?? /* @__PURE__ */ jsx(ChatUI, { messages, isMessagesLoading, isLoadingOlderMessages, hasMoreMessagesBefore: messagePageInfo.hasMoreBefore, activityNotice, onLoadOlderMessages: loadOlderMessages, threads, currentThreadId, config: mergedConfig, callbacks: chatCallbacks, isGenerating: isStreaming, suggestions, agentOptions, selectedAgentId, onSelectAgent, participantIds, onParticipantsChange, targetAgentId, onTargetAgentChange, user: userProp, assistant: assistantProp, onAddMemory, onUpdateMemory, onDeleteMemory, userMenuSections, userMenuAdditionalItems, className }) });
2917
2974
  };
2918
2975
  export {
2919
2976
  CopilotzChat,