mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
refactor(site): migrate chats query keys to collections/entities taxonomy (#27841)
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import type { QueryClient } from "react-query";
|
||||
import { type QueryClient, queryOptions } from "react-query";
|
||||
import { API } from "#/api/api";
|
||||
import { invalidateChatProviderDependentQueries } from "#/api/queries/chats";
|
||||
import type {
|
||||
AIProvider,
|
||||
ChatProviderConfig,
|
||||
CreateAIProviderRequest,
|
||||
UpdateAIProviderRequest,
|
||||
} from "#/api/typesGenerated";
|
||||
@@ -17,6 +18,32 @@ export const aiProvidersList = () => ({
|
||||
queryFn: (): Promise<AIProvider[]> => API.getAIProviders(),
|
||||
});
|
||||
|
||||
const selectChatProviderConfigs = (
|
||||
providers: readonly AIProvider[],
|
||||
): ChatProviderConfig[] =>
|
||||
providers.map((provider) => ({
|
||||
id: provider.id,
|
||||
provider: provider.type,
|
||||
display_name: provider.display_name || provider.type,
|
||||
icon: provider.icon,
|
||||
enabled: provider.enabled,
|
||||
has_api_key: provider.api_keys.length > 0,
|
||||
central_api_key_enabled: true,
|
||||
allow_user_api_key: true,
|
||||
allow_central_api_key_fallback: true,
|
||||
base_url: provider.base_url,
|
||||
source: "database",
|
||||
created_at: provider.created_at,
|
||||
updated_at: provider.updated_at,
|
||||
}));
|
||||
|
||||
export const chatProviderConfigs = () =>
|
||||
queryOptions({
|
||||
queryKey: aiProvidersListKey,
|
||||
queryFn: (): Promise<AIProvider[]> => API.getAIProviders(),
|
||||
select: selectChatProviderConfigs,
|
||||
});
|
||||
|
||||
export const aiProvider = (idOrName: string) => ({
|
||||
queryKey: aiProviderKeyFor(idOrName),
|
||||
queryFn: (): Promise<AIProvider> => API.getAIProvider(idOrName),
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import type { QueryClient } from "react-query";
|
||||
import { API } from "#/api/api";
|
||||
|
||||
const chatDebugLoggingKey = ["chat-debug-logging"] as const;
|
||||
const userChatDebugLoggingKey = ["user-chat-debug-logging"] as const;
|
||||
|
||||
export const chatDebugLogging = () => ({
|
||||
queryKey: chatDebugLoggingKey,
|
||||
queryFn: () => API.experimental.getChatDebugLogging(),
|
||||
});
|
||||
|
||||
export const userChatDebugLogging = () => ({
|
||||
queryKey: userChatDebugLoggingKey,
|
||||
queryFn: () => API.experimental.getUserChatDebugLogging(),
|
||||
});
|
||||
|
||||
export const updateChatDebugLogging = (queryClient: QueryClient) => ({
|
||||
mutationFn: API.experimental.updateChatDebugLogging,
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: chatDebugLoggingKey,
|
||||
});
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: userChatDebugLoggingKey,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const updateUserChatDebugLogging = (queryClient: QueryClient) => ({
|
||||
mutationFn: API.experimental.updateUserChatDebugLogging,
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: userChatDebugLoggingKey,
|
||||
});
|
||||
},
|
||||
});
|
||||
+216
-146
@@ -10,25 +10,27 @@ import { buildOptimisticEditedMessage } from "./chatMessageEdits";
|
||||
import {
|
||||
addChildToParentInCache,
|
||||
archiveChat,
|
||||
type ChatListInput,
|
||||
cancelChatListRefetches,
|
||||
chatACL,
|
||||
chatACLKey,
|
||||
chatAdvisorConfig,
|
||||
chatAdvisorConfigKey,
|
||||
chatCost,
|
||||
chatCostKey,
|
||||
chatCostTreeKey,
|
||||
chatDebugRunsKey,
|
||||
chatDiffContentsKey,
|
||||
chatKey,
|
||||
chatEntityKey,
|
||||
chatListFamilyKey,
|
||||
chatListKey,
|
||||
chatMessagesKey,
|
||||
chatSearch,
|
||||
chatsKey,
|
||||
chatsByWorkspace,
|
||||
createChat,
|
||||
createChatMessage,
|
||||
deleteChatQueuedMessage,
|
||||
editChatMessage,
|
||||
infiniteChats,
|
||||
infiniteChatsKey,
|
||||
interruptChat,
|
||||
invalidateChatListQueries,
|
||||
mergeWatchedChatIntoCaches,
|
||||
@@ -42,6 +44,7 @@ import {
|
||||
setChatGroupRole,
|
||||
setChatUserRole,
|
||||
TERMINAL_RUN_STATUSES,
|
||||
toChatListParams,
|
||||
unarchiveChat,
|
||||
unpinChat,
|
||||
updateChatAdvisorConfig,
|
||||
@@ -58,6 +61,7 @@ vi.mock("#/api/api", () => ({
|
||||
createChat: vi.fn(),
|
||||
deleteChatQueuedMessage: vi.fn(),
|
||||
getChats: vi.fn(),
|
||||
getChatsByWorkspace: vi.fn(),
|
||||
getChatCost: vi.fn(),
|
||||
createChatMessage: vi.fn(),
|
||||
editChatMessage: vi.fn(),
|
||||
@@ -72,9 +76,9 @@ vi.mock("#/api/api", () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
type InfiniteChatsTestOptions = Parameters<typeof infiniteChatsKey>[0];
|
||||
type InfiniteChatsTestOptions = ChatListInput;
|
||||
|
||||
const infiniteChatsTestKey = infiniteChatsKey();
|
||||
const infiniteChatsTestKey = chatListKey(toChatListParams());
|
||||
|
||||
type InfiniteData = {
|
||||
pages: TypesGen.Chat[][];
|
||||
@@ -87,7 +91,7 @@ const seedInfiniteChats = (
|
||||
chats: TypesGen.Chat[],
|
||||
opts?: InfiniteChatsTestOptions,
|
||||
) => {
|
||||
queryClient.setQueryData<InfiniteData>(infiniteChatsKey(opts), {
|
||||
queryClient.setQueryData<InfiniteData>(chatListKey(toChatListParams(opts)), {
|
||||
pages: [chats],
|
||||
pageParams: [0],
|
||||
});
|
||||
@@ -98,7 +102,9 @@ const readInfiniteChats = (
|
||||
queryClient: QueryClient,
|
||||
opts?: InfiniteChatsTestOptions,
|
||||
): TypesGen.Chat[] | undefined => {
|
||||
const data = queryClient.getQueryData<InfiniteData>(infiniteChatsKey(opts));
|
||||
const data = queryClient.getQueryData<InfiniteData>(
|
||||
chatListKey(toChatListParams(opts)),
|
||||
);
|
||||
return data?.pages.flat();
|
||||
};
|
||||
|
||||
@@ -192,34 +198,39 @@ describe("invalidateChatListQueries", () => {
|
||||
const queryClient = createTestQueryClient();
|
||||
const chatId = "chat-1";
|
||||
|
||||
// Sidebar queries.
|
||||
queryClient.setQueryData(chatsKey, [makeChat(chatId)]);
|
||||
queryClient.setQueryData(infiniteChatsKey({ archived: false }), {
|
||||
queryClient.setQueryData(chatListKey(toChatListParams()), {
|
||||
pages: [[makeChat(chatId)]],
|
||||
pageParams: [0],
|
||||
});
|
||||
queryClient.setQueryData(
|
||||
chatListKey(toChatListParams({ archived: true })),
|
||||
{
|
||||
pages: [[makeChat(chatId)]],
|
||||
pageParams: [0],
|
||||
},
|
||||
);
|
||||
// Per-chat queries that should NOT be touched.
|
||||
queryClient.setQueryData(chatKey(chatId), makeChat(chatId));
|
||||
queryClient.setQueryData(chatEntityKey(chatId), makeChat(chatId));
|
||||
queryClient.setQueryData(chatMessagesKey(chatId), []);
|
||||
queryClient.setQueryData(chatDiffContentsKey(chatId), {});
|
||||
|
||||
await invalidateChatListQueries(queryClient);
|
||||
|
||||
// Sidebar queries should be invalidated.
|
||||
expect(
|
||||
queryClient.getQueryState(chatsKey)?.isInvalidated,
|
||||
"flat chats should be invalidated",
|
||||
queryClient.getQueryState(chatListKey(toChatListParams()))?.isInvalidated,
|
||||
"default chat list should be invalidated",
|
||||
).toBe(true);
|
||||
expect(
|
||||
queryClient.getQueryState(infiniteChatsKey({ archived: false }))
|
||||
?.isInvalidated,
|
||||
"infinite chats should be invalidated",
|
||||
queryClient.getQueryState(
|
||||
chatListKey(toChatListParams({ archived: true })),
|
||||
)?.isInvalidated,
|
||||
"archived chat list should be invalidated",
|
||||
).toBe(true);
|
||||
|
||||
// Per-chat queries should NOT be invalidated.
|
||||
expect(
|
||||
queryClient.getQueryState(chatKey(chatId))?.isInvalidated,
|
||||
"chatKey should NOT be invalidated",
|
||||
queryClient.getQueryState(chatEntityKey(chatId))?.isInvalidated,
|
||||
"chatEntityKey should NOT be invalidated",
|
||||
).not.toBe(true);
|
||||
expect(
|
||||
queryClient.getQueryState(chatMessagesKey(chatId))?.isInvalidated,
|
||||
@@ -231,10 +242,10 @@ describe("invalidateChatListQueries", () => {
|
||||
).not.toBe(true);
|
||||
});
|
||||
|
||||
it("invalidates the infinite query with undefined opts", async () => {
|
||||
it("invalidates the list query built from default params", async () => {
|
||||
const queryClient = createTestQueryClient();
|
||||
|
||||
queryClient.setQueryData(infiniteChatsKey(), {
|
||||
queryClient.setQueryData(chatListKey(toChatListParams()), {
|
||||
pages: [[makeChat("chat-1")]],
|
||||
pageParams: [0],
|
||||
});
|
||||
@@ -242,8 +253,8 @@ describe("invalidateChatListQueries", () => {
|
||||
await invalidateChatListQueries(queryClient);
|
||||
|
||||
expect(
|
||||
queryClient.getQueryState(infiniteChatsKey())?.isInvalidated,
|
||||
"infinite chats with undefined opts should be invalidated",
|
||||
queryClient.getQueryState(chatListKey(toChatListParams()))?.isInvalidated,
|
||||
"default params chat list should be invalidated",
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
@@ -252,15 +263,17 @@ describe("invalidateChatListQueries", () => {
|
||||
const chatId = "chat-1";
|
||||
const otherChatId = "chat-2";
|
||||
|
||||
queryClient.setQueryData(chatsKey, [makeChat(chatId)]);
|
||||
queryClient.setQueryData(chatKey(otherChatId), makeChat(otherChatId));
|
||||
queryClient.setQueryData(chatListKey(toChatListParams()), [
|
||||
makeChat(chatId),
|
||||
]);
|
||||
queryClient.setQueryData(chatEntityKey(otherChatId), makeChat(otherChatId));
|
||||
queryClient.setQueryData(chatMessagesKey(otherChatId), []);
|
||||
|
||||
await invalidateChatListQueries(queryClient);
|
||||
|
||||
expect(
|
||||
queryClient.getQueryState(chatKey(otherChatId))?.isInvalidated,
|
||||
"other chat's chatKey should NOT be invalidated",
|
||||
queryClient.getQueryState(chatEntityKey(otherChatId))?.isInvalidated,
|
||||
"other chat's chatEntityKey should NOT be invalidated",
|
||||
).not.toBe(true);
|
||||
expect(
|
||||
queryClient.getQueryState(chatMessagesKey(otherChatId))?.isInvalidated,
|
||||
@@ -317,7 +330,7 @@ describe("updateChatTitle cache update", () => {
|
||||
const queryClient = createTestQueryClient();
|
||||
const chatId = "chat-1";
|
||||
queryClient.setQueryData(
|
||||
chatKey(chatId),
|
||||
chatEntityKey(chatId),
|
||||
makeChat(chatId, { title: "Old" }),
|
||||
);
|
||||
seedInfiniteChats(queryClient, [
|
||||
@@ -334,7 +347,7 @@ describe("updateChatTitle cache update", () => {
|
||||
mutation.onSuccess(undefined, { chatId, title: "New" });
|
||||
|
||||
expect(
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatKey(chatId))?.title,
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatEntityKey(chatId))?.title,
|
||||
).toBe("New");
|
||||
expect(
|
||||
readInfiniteChats(queryClient)?.find((chat) => chat.id === chatId),
|
||||
@@ -361,10 +374,10 @@ describe("updateChatTitle cache update", () => {
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
expect(invalidateSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ queryKey: chatsKey }),
|
||||
expect.objectContaining({ queryKey: chatListFamilyKey }),
|
||||
);
|
||||
expect(invalidateSpy).toHaveBeenCalledWith({
|
||||
queryKey: chatKey(chatId),
|
||||
queryKey: chatEntityKey(chatId),
|
||||
exact: true,
|
||||
});
|
||||
invalidateSpy.mockRestore();
|
||||
@@ -394,14 +407,16 @@ describe("archiveChat optimistic update", () => {
|
||||
const queryClient = createTestQueryClient();
|
||||
const chatId = "chat-1";
|
||||
seedInfiniteChats(queryClient, [makeChat(chatId)]);
|
||||
queryClient.setQueryData(chatKey(chatId), makeChat(chatId));
|
||||
queryClient.setQueryData(chatEntityKey(chatId), makeChat(chatId));
|
||||
|
||||
vi.mocked(API.experimental.updateChat).mockResolvedValue();
|
||||
|
||||
const mutation = archiveChat(queryClient);
|
||||
await mutation.onMutate(chatId);
|
||||
|
||||
const cachedChat = queryClient.getQueryData<TypesGen.Chat>(chatKey(chatId));
|
||||
const cachedChat = queryClient.getQueryData<TypesGen.Chat>(
|
||||
chatEntityKey(chatId),
|
||||
);
|
||||
expect(cachedChat?.archived).toBe(true);
|
||||
});
|
||||
|
||||
@@ -440,7 +455,7 @@ describe("archiveChat optimistic update", () => {
|
||||
{ archived: false },
|
||||
);
|
||||
queryClient.setQueryData(
|
||||
chatKey(chatId),
|
||||
chatEntityKey(chatId),
|
||||
makeChat(chatId, { pin_order: 2 }),
|
||||
);
|
||||
|
||||
@@ -453,33 +468,50 @@ describe("archiveChat optimistic update", () => {
|
||||
),
|
||||
).toEqual(["chat-2"]);
|
||||
expect(
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatKey(chatId)),
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatEntityKey(chatId)),
|
||||
).toMatchObject({
|
||||
archived: true,
|
||||
pin_order: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it("clears pin order for archived chats that remain in unfiltered lists", () => {
|
||||
it("clears pin order for archived chats that remain in archived lists", () => {
|
||||
const queryClient = createTestQueryClient();
|
||||
const chatId = "chat-1";
|
||||
seedInfiniteChats(queryClient, [makeChat(chatId, { pin_order: 3 })]);
|
||||
seedInfiniteChats(queryClient, [makeChat(chatId, { pin_order: 3 })], {
|
||||
archived: true,
|
||||
});
|
||||
|
||||
const mutation = archiveChat(queryClient);
|
||||
mutation.onSuccess(undefined, chatId);
|
||||
|
||||
expect(readInfiniteChats(queryClient)?.[0]).toMatchObject({
|
||||
expect(
|
||||
readInfiniteChats(queryClient, { archived: true })?.[0],
|
||||
).toMatchObject({
|
||||
archived: true,
|
||||
pin_order: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it("removes newly archived chats from lists filtered to active chats", () => {
|
||||
const queryClient = createTestQueryClient();
|
||||
const chatId = "chat-1";
|
||||
seedInfiniteChats(queryClient, [makeChat(chatId, { pin_order: 3 })], {
|
||||
archived: false,
|
||||
});
|
||||
|
||||
const mutation = archiveChat(queryClient);
|
||||
mutation.onSuccess(undefined, chatId);
|
||||
|
||||
expect(readInfiniteChats(queryClient, { archived: false })).toEqual([]);
|
||||
});
|
||||
|
||||
it("rolls back the chats list on error by invalidating", async () => {
|
||||
const queryClient = createTestQueryClient();
|
||||
const chatId = "chat-1";
|
||||
const initialChats = [makeChat(chatId)];
|
||||
seedInfiniteChats(queryClient, initialChats);
|
||||
queryClient.setQueryData(chatKey(chatId), makeChat(chatId));
|
||||
queryClient.setQueryData(chatEntityKey(chatId), makeChat(chatId));
|
||||
const invalidateSpy = vi.spyOn(queryClient, "invalidateQueries");
|
||||
|
||||
const mutation = archiveChat(queryClient);
|
||||
@@ -493,7 +525,7 @@ describe("archiveChat optimistic update", () => {
|
||||
mutation.onError(new Error("server error"), chatId, context);
|
||||
|
||||
expect(invalidateSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ queryKey: chatsKey }),
|
||||
expect.objectContaining({ queryKey: chatListFamilyKey }),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -501,18 +533,20 @@ describe("archiveChat optimistic update", () => {
|
||||
const queryClient = createTestQueryClient();
|
||||
const chatId = "chat-1";
|
||||
seedInfiniteChats(queryClient, [makeChat(chatId)]);
|
||||
queryClient.setQueryData(chatKey(chatId), makeChat(chatId));
|
||||
queryClient.setQueryData(chatEntityKey(chatId), makeChat(chatId));
|
||||
|
||||
const mutation = archiveChat(queryClient);
|
||||
const context = await mutation.onMutate(chatId);
|
||||
|
||||
expect(
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatKey(chatId))?.archived,
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatEntityKey(chatId))?.archived,
|
||||
).toBe(true);
|
||||
|
||||
mutation.onError(new Error("server error"), chatId, context);
|
||||
|
||||
const rolledBack = queryClient.getQueryData<TypesGen.Chat>(chatKey(chatId));
|
||||
const rolledBack = queryClient.getQueryData<TypesGen.Chat>(
|
||||
chatEntityKey(chatId),
|
||||
);
|
||||
expect(rolledBack?.archived).toBe(false);
|
||||
});
|
||||
|
||||
@@ -531,7 +565,7 @@ describe("archiveChat optimistic update", () => {
|
||||
|
||||
// The handler should still invalidate to trigger a refetch.
|
||||
expect(invalidateSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ queryKey: chatsKey }),
|
||||
expect.objectContaining({ queryKey: chatListFamilyKey }),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -539,7 +573,6 @@ describe("archiveChat optimistic update", () => {
|
||||
const queryClient = createTestQueryClient();
|
||||
const chatId = "chat-1";
|
||||
seedInfiniteChats(queryClient, [makeChat(chatId)]);
|
||||
// Deliberately do NOT set chatKey(chatId) data.
|
||||
|
||||
const mutation = archiveChat(queryClient);
|
||||
const context = await mutation.onMutate(chatId);
|
||||
@@ -565,10 +598,10 @@ describe("archiveChat optimistic update", () => {
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
expect(invalidateSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ queryKey: chatsKey }),
|
||||
expect.objectContaining({ queryKey: chatListFamilyKey }),
|
||||
);
|
||||
expect(invalidateSpy).toHaveBeenCalledWith({
|
||||
queryKey: chatKey(chatId),
|
||||
queryKey: chatEntityKey(chatId),
|
||||
exact: true,
|
||||
});
|
||||
invalidateSpy.mockRestore();
|
||||
@@ -592,7 +625,7 @@ describe("unarchiveChat optimistic update", () => {
|
||||
const chatId = "chat-1";
|
||||
seedInfiniteChats(queryClient, [makeChat(chatId, { archived: true })]);
|
||||
queryClient.setQueryData(
|
||||
chatKey(chatId),
|
||||
chatEntityKey(chatId),
|
||||
makeChat(chatId, { archived: true }),
|
||||
);
|
||||
|
||||
@@ -600,7 +633,7 @@ describe("unarchiveChat optimistic update", () => {
|
||||
await mutation.onMutate(chatId);
|
||||
|
||||
expect(
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatKey(chatId))?.archived,
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatEntityKey(chatId))?.archived,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
@@ -616,7 +649,7 @@ describe("unarchiveChat optimistic update", () => {
|
||||
{ archived: true },
|
||||
);
|
||||
queryClient.setQueryData(
|
||||
chatKey(chatId),
|
||||
chatEntityKey(chatId),
|
||||
makeChat(chatId, { archived: true }),
|
||||
);
|
||||
|
||||
@@ -629,7 +662,7 @@ describe("unarchiveChat optimistic update", () => {
|
||||
),
|
||||
).toEqual(["chat-2"]);
|
||||
expect(
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatKey(chatId)),
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatEntityKey(chatId)),
|
||||
).toMatchObject({
|
||||
archived: false,
|
||||
});
|
||||
@@ -640,7 +673,7 @@ describe("unarchiveChat optimistic update", () => {
|
||||
const chatId = "chat-1";
|
||||
seedInfiniteChats(queryClient, [makeChat(chatId, { archived: true })]);
|
||||
queryClient.setQueryData(
|
||||
chatKey(chatId),
|
||||
chatEntityKey(chatId),
|
||||
makeChat(chatId, { archived: true }),
|
||||
);
|
||||
const invalidateSpy = vi.spyOn(queryClient, "invalidateQueries");
|
||||
@@ -651,7 +684,7 @@ describe("unarchiveChat optimistic update", () => {
|
||||
// Verify optimistic update.
|
||||
expect(readInfiniteChats(queryClient)?.[0].archived).toBe(false);
|
||||
expect(
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatKey(chatId))?.archived,
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatEntityKey(chatId))?.archived,
|
||||
).toBe(false);
|
||||
|
||||
// Roll back.
|
||||
@@ -659,11 +692,11 @@ describe("unarchiveChat optimistic update", () => {
|
||||
|
||||
// The chats list is rolled back via invalidation.
|
||||
expect(invalidateSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ queryKey: chatsKey }),
|
||||
expect.objectContaining({ queryKey: chatListFamilyKey }),
|
||||
);
|
||||
// The individual chat cache is restored directly.
|
||||
expect(
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatKey(chatId))?.archived,
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatEntityKey(chatId))?.archived,
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
@@ -682,10 +715,10 @@ describe("unarchiveChat optimistic update", () => {
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
expect(invalidateSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ queryKey: chatsKey }),
|
||||
expect.objectContaining({ queryKey: chatListFamilyKey }),
|
||||
);
|
||||
expect(invalidateSpy).toHaveBeenCalledWith({
|
||||
queryKey: chatKey(chatId),
|
||||
queryKey: chatEntityKey(chatId),
|
||||
exact: true,
|
||||
});
|
||||
invalidateSpy.mockRestore();
|
||||
@@ -701,11 +734,14 @@ describe("pinChat optimistic update", () => {
|
||||
makeChat(chatId),
|
||||
makeChat("chat-pinned-2", { pin_order: 2 }),
|
||||
]);
|
||||
queryClient.setQueryData(infiniteChatsKey({ archived: true }), {
|
||||
pages: [[makeChat("chat-pinned-archived", { pin_order: 4 })]],
|
||||
pageParams: [0],
|
||||
});
|
||||
queryClient.setQueryData(chatKey(chatId), makeChat(chatId));
|
||||
queryClient.setQueryData(
|
||||
chatListKey(toChatListParams({ archived: true })),
|
||||
{
|
||||
pages: [[makeChat("chat-pinned-archived", { pin_order: 4 })]],
|
||||
pageParams: [0],
|
||||
},
|
||||
);
|
||||
queryClient.setQueryData(chatEntityKey(chatId), makeChat(chatId));
|
||||
|
||||
const mutation = pinChat(queryClient);
|
||||
await mutation.onMutate(chatId);
|
||||
@@ -715,7 +751,7 @@ describe("pinChat optimistic update", () => {
|
||||
?.pin_order,
|
||||
).toBe(5);
|
||||
expect(
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatKey(chatId))?.pin_order,
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatEntityKey(chatId))?.pin_order,
|
||||
).toBe(5);
|
||||
});
|
||||
});
|
||||
@@ -737,7 +773,7 @@ describe("unpinChat optimistic update", () => {
|
||||
const chatId = "chat-1";
|
||||
seedInfiniteChats(queryClient, [makeChat(chatId, { pin_order: 2 })]);
|
||||
queryClient.setQueryData(
|
||||
chatKey(chatId),
|
||||
chatEntityKey(chatId),
|
||||
makeChat(chatId, { pin_order: 2 }),
|
||||
);
|
||||
|
||||
@@ -745,7 +781,7 @@ describe("unpinChat optimistic update", () => {
|
||||
await mutation.onMutate(chatId);
|
||||
|
||||
expect(
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatKey(chatId))?.pin_order,
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatEntityKey(chatId))?.pin_order,
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
@@ -754,7 +790,7 @@ describe("unpinChat optimistic update", () => {
|
||||
const chatId = "chat-1";
|
||||
seedInfiniteChats(queryClient, [makeChat(chatId, { pin_order: 3 })]);
|
||||
queryClient.setQueryData(
|
||||
chatKey(chatId),
|
||||
chatEntityKey(chatId),
|
||||
makeChat(chatId, { pin_order: 3 }),
|
||||
);
|
||||
const invalidateSpy = vi.spyOn(queryClient, "invalidateQueries");
|
||||
@@ -765,7 +801,7 @@ describe("unpinChat optimistic update", () => {
|
||||
// Verify optimistic update.
|
||||
expect(readInfiniteChats(queryClient)?.[0].pin_order).toBe(0);
|
||||
expect(
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatKey(chatId))?.pin_order,
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatEntityKey(chatId))?.pin_order,
|
||||
).toBe(0);
|
||||
|
||||
// Roll back.
|
||||
@@ -773,11 +809,11 @@ describe("unpinChat optimistic update", () => {
|
||||
|
||||
// The chats list is rolled back via invalidation.
|
||||
expect(invalidateSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ queryKey: chatsKey }),
|
||||
expect.objectContaining({ queryKey: chatListFamilyKey }),
|
||||
);
|
||||
// The individual chat cache is restored directly.
|
||||
expect(
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatKey(chatId))?.pin_order,
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatEntityKey(chatId))?.pin_order,
|
||||
).toBe(3);
|
||||
});
|
||||
|
||||
@@ -790,10 +826,10 @@ describe("unpinChat optimistic update", () => {
|
||||
await mutation.onSettled(undefined, undefined, chatId);
|
||||
|
||||
expect(invalidateSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ queryKey: chatsKey }),
|
||||
expect.objectContaining({ queryKey: chatListFamilyKey }),
|
||||
);
|
||||
expect(invalidateSpy).toHaveBeenCalledWith({
|
||||
queryKey: chatKey(chatId),
|
||||
queryKey: chatEntityKey(chatId),
|
||||
exact: true,
|
||||
});
|
||||
});
|
||||
@@ -813,20 +849,20 @@ describe("reorderPinnedChat", () => {
|
||||
await mutation.onSettled?.(undefined, undefined, { chatId, pinOrder: 2 });
|
||||
|
||||
expect(cancelSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ queryKey: chatsKey }),
|
||||
expect.objectContaining({ queryKey: chatListFamilyKey }),
|
||||
);
|
||||
expect(cancelSpy).toHaveBeenCalledWith({
|
||||
queryKey: chatKey(chatId),
|
||||
queryKey: chatEntityKey(chatId),
|
||||
exact: true,
|
||||
});
|
||||
expect(API.experimental.updateChat).toHaveBeenCalledWith(chatId, {
|
||||
pin_order: 2,
|
||||
});
|
||||
expect(invalidateSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ queryKey: chatsKey }),
|
||||
expect.objectContaining({ queryKey: chatListFamilyKey }),
|
||||
);
|
||||
expect(invalidateSpy).toHaveBeenCalledWith({
|
||||
queryKey: chatKey(chatId),
|
||||
queryKey: chatEntityKey(chatId),
|
||||
exact: true,
|
||||
});
|
||||
});
|
||||
@@ -841,8 +877,20 @@ describe("chat cost query factories", () => {
|
||||
|
||||
const query = chatCost(chatId);
|
||||
|
||||
expect(chatCostKey(chatId)).toEqual(["chats", chatId, "cost"]);
|
||||
expect(query.queryKey).toEqual(["chats", chatId, "cost"]);
|
||||
expect(chatCostTreeKey(chatId)).toEqual([
|
||||
"chats",
|
||||
"analytics",
|
||||
"cost",
|
||||
"tree",
|
||||
chatId,
|
||||
]);
|
||||
expect(query.queryKey).toEqual([
|
||||
"chats",
|
||||
"analytics",
|
||||
"cost",
|
||||
"tree",
|
||||
chatId,
|
||||
]);
|
||||
await query.queryFn();
|
||||
expect(API.experimental.getChatCost).toHaveBeenCalledWith(chatId);
|
||||
});
|
||||
@@ -860,20 +908,13 @@ describe("mutation invalidation scope", () => {
|
||||
/** Populate the QueryClient with every query key that is actively
|
||||
* observed on the /agents/:id detail page. */
|
||||
const seedAllActiveQueries = (queryClient: QueryClient, chatId: string) => {
|
||||
// Infinite sidebar list: ["chats", { archived: false }]
|
||||
queryClient.setQueryData(infiniteChatsKey({ archived: false }), {
|
||||
queryClient.setQueryData(chatListKey(toChatListParams()), {
|
||||
pages: [[makeChat(chatId)]],
|
||||
pageParams: [0],
|
||||
});
|
||||
// Flat chats list: ["chats"]
|
||||
queryClient.setQueryData(chatsKey, [makeChat(chatId)]);
|
||||
// Individual chat: ["chats", chatId]
|
||||
queryClient.setQueryData(chatKey(chatId), makeChat(chatId));
|
||||
// Messages: ["chats", chatId, "messages"]
|
||||
queryClient.setQueryData(chatEntityKey(chatId), makeChat(chatId));
|
||||
queryClient.setQueryData(chatMessagesKey(chatId), []);
|
||||
// Debug runs: ["chats", chatId, "debug-runs"]
|
||||
queryClient.setQueryData(chatDebugRunsKey(chatId), []);
|
||||
// Diff contents: ["chats", chatId, "diff-contents"]
|
||||
queryClient.setQueryData(chatDiffContentsKey(chatId), { files: [] });
|
||||
};
|
||||
|
||||
@@ -913,10 +954,11 @@ describe("mutation invalidation scope", () => {
|
||||
"chatDebugRunsKey should be invalidated",
|
||||
).toBe(true);
|
||||
|
||||
const chatState = queryClient.getQueryState(chatKey(chatId));
|
||||
expect(chatState?.isInvalidated, "chatKey should be invalidated").toBe(
|
||||
true,
|
||||
);
|
||||
const chatState = queryClient.getQueryState(chatEntityKey(chatId));
|
||||
expect(
|
||||
chatState?.isInvalidated,
|
||||
"chatEntityKey should be invalidated",
|
||||
).toBe(true);
|
||||
|
||||
const messagesState = queryClient.getQueryState(chatMessagesKey(chatId));
|
||||
expect(
|
||||
@@ -957,10 +999,11 @@ describe("mutation invalidation scope", () => {
|
||||
// Chat metadata and debug runs should be invalidated because
|
||||
// editing changes the chat's updated_at and can start a new
|
||||
// debug run.
|
||||
const chatState = queryClient.getQueryState(chatKey(chatId));
|
||||
expect(chatState?.isInvalidated, "chatKey should be invalidated").toBe(
|
||||
true,
|
||||
);
|
||||
const chatState = queryClient.getQueryState(chatEntityKey(chatId));
|
||||
expect(
|
||||
chatState?.isInvalidated,
|
||||
"chatEntityKey should be invalidated",
|
||||
).toBe(true);
|
||||
|
||||
// Messages are NOT invalidated. The per-chat WebSocket handles
|
||||
// post-edit message delivery, making REST invalidation
|
||||
@@ -1408,12 +1451,11 @@ describe("mutation invalidation scope", () => {
|
||||
).toBe(true);
|
||||
|
||||
for (const { label, key } of [
|
||||
{ label: "flat chats", key: chatsKey },
|
||||
{
|
||||
label: "infinite chats",
|
||||
key: infiniteChatsKey({ archived: false }),
|
||||
label: "chat list",
|
||||
key: chatListKey(toChatListParams()),
|
||||
},
|
||||
{ label: "chat detail", key: chatKey(chatId) },
|
||||
{ label: "chat detail", key: chatEntityKey(chatId) },
|
||||
{ label: "messages", key: chatMessagesKey(chatId) },
|
||||
...unrelatedKeys(chatId),
|
||||
]) {
|
||||
@@ -1438,13 +1480,8 @@ describe("mutation invalidation scope", () => {
|
||||
|
||||
// Sidebar lists SHOULD be invalidated.
|
||||
expect(
|
||||
queryClient.getQueryState(chatsKey)?.isInvalidated,
|
||||
"flat chats should be invalidated",
|
||||
).toBe(true);
|
||||
expect(
|
||||
queryClient.getQueryState(infiniteChatsKey({ archived: false }))
|
||||
?.isInvalidated,
|
||||
"infinite chats should be invalidated",
|
||||
queryClient.getQueryState(chatListKey(toChatListParams()))?.isInvalidated,
|
||||
"chat list should be invalidated",
|
||||
).toBe(true);
|
||||
|
||||
// Per-chat queries should NOT be touched.
|
||||
@@ -1455,8 +1492,8 @@ describe("mutation invalidation scope", () => {
|
||||
).not.toBe(true);
|
||||
}
|
||||
expect(
|
||||
queryClient.getQueryState(chatKey(chatId))?.isInvalidated,
|
||||
"chatKey should NOT be invalidated",
|
||||
queryClient.getQueryState(chatEntityKey(chatId))?.isInvalidated,
|
||||
"chatEntityKey should NOT be invalidated",
|
||||
).not.toBe(true);
|
||||
expect(
|
||||
queryClient.getQueryState(chatMessagesKey(chatId))?.isInvalidated,
|
||||
@@ -1474,8 +1511,8 @@ describe("mutation invalidation scope", () => {
|
||||
|
||||
// These two should be invalidated (exact match).
|
||||
expect(
|
||||
queryClient.getQueryState(chatKey(chatId))?.isInvalidated,
|
||||
"chatKey should be invalidated",
|
||||
queryClient.getQueryState(chatEntityKey(chatId))?.isInvalidated,
|
||||
"chatEntityKey should be invalidated",
|
||||
).toBe(true);
|
||||
expect(
|
||||
queryClient.getQueryState(chatMessagesKey(chatId))?.isInvalidated,
|
||||
@@ -1492,21 +1529,46 @@ describe("mutation invalidation scope", () => {
|
||||
|
||||
// Sidebar list should NOT be touched.
|
||||
expect(
|
||||
queryClient.getQueryState(chatsKey)?.isInvalidated,
|
||||
"flat chats should NOT be invalidated",
|
||||
queryClient.getQueryState(chatListKey(toChatListParams()))?.isInvalidated,
|
||||
"chat list should NOT be invalidated",
|
||||
).not.toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("infiniteChatsKey shape", () => {
|
||||
it("places the filter object one slot after the chatsKey prefix", () => {
|
||||
// archivedFilterForChatListKey reads the archived filter from the
|
||||
// slot immediately after the chatsKey prefix. If this layout ever
|
||||
// changes, that helper silently stops removing chats from
|
||||
// conflicting filtered lists, so keep the two in sync.
|
||||
const key = infiniteChatsKey({ archived: true });
|
||||
expect(key.length).toBe(chatsKey.length + 1);
|
||||
expect(key[chatsKey.length]).toEqual({ archived: true });
|
||||
describe("chatListKey shape", () => {
|
||||
it("places the params object one slot after the list family prefix", () => {
|
||||
const key = chatListKey(toChatListParams({ archived: true }));
|
||||
expect(key.length).toBe(chatListFamilyKey.length + 1);
|
||||
expect(key[chatListFamilyKey.length]).toEqual({
|
||||
archived: true,
|
||||
prStatuses: [],
|
||||
status: "all",
|
||||
sources: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("chatsByWorkspace", () => {
|
||||
it("disables the query when no workspace IDs are given", () => {
|
||||
expect(chatsByWorkspace([]).enabled).toBe(false);
|
||||
expect(chatsByWorkspace(["ws-1"]).enabled).toBe(true);
|
||||
});
|
||||
|
||||
it("canonicalizes the key with sorted, deduplicated workspace IDs", () => {
|
||||
const options = chatsByWorkspace(["ws-b", "ws-a", "ws-b"]);
|
||||
expect(options.queryKey).toEqual([
|
||||
"chats",
|
||||
"collections",
|
||||
"by-workspace",
|
||||
["ws-a", "ws-b"],
|
||||
]);
|
||||
});
|
||||
|
||||
it("fetches with the sorted, deduplicated workspace IDs", async () => {
|
||||
const getChatsByWorkspace = vi.mocked(API.experimental.getChatsByWorkspace);
|
||||
getChatsByWorkspace.mockResolvedValue({});
|
||||
await chatsByWorkspace(["ws-b", "ws-a", "ws-b"]).queryFn();
|
||||
expect(getChatsByWorkspace).toHaveBeenCalledWith(["ws-a", "ws-b"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1540,6 +1602,7 @@ describe("infiniteChats", () => {
|
||||
expect(API.experimental.getChats).toHaveBeenCalledWith({
|
||||
limit: PAGE_LIMIT,
|
||||
offset: 0,
|
||||
q: "archived:false",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1550,6 +1613,7 @@ describe("infiniteChats", () => {
|
||||
expect(API.experimental.getChats).toHaveBeenCalledWith({
|
||||
limit: PAGE_LIMIT,
|
||||
offset: 0,
|
||||
q: "archived:false",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1561,12 +1625,14 @@ describe("infiniteChats", () => {
|
||||
expect(API.experimental.getChats).toHaveBeenCalledWith({
|
||||
limit: PAGE_LIMIT,
|
||||
offset: PAGE_LIMIT,
|
||||
q: "archived:false",
|
||||
});
|
||||
|
||||
await queryFn({ pageParam: 3 });
|
||||
expect(API.experimental.getChats).toHaveBeenCalledWith({
|
||||
limit: PAGE_LIMIT,
|
||||
offset: PAGE_LIMIT * 2,
|
||||
q: "archived:false",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1616,10 +1682,15 @@ describe("infiniteChats", () => {
|
||||
describe("chatSearch", () => {
|
||||
it("requests chats with q and a fixed limit", async () => {
|
||||
vi.mocked(API.experimental.getChats).mockResolvedValue([]);
|
||||
const query = chatSearch("title:fix");
|
||||
const query = chatSearch({ q: "title:fix" });
|
||||
const queryClient = createTestQueryClient();
|
||||
|
||||
expect(query.queryKey).toEqual(["chats", "search", { q: "title:fix" }]);
|
||||
expect(query.queryKey).toEqual([
|
||||
"chats",
|
||||
"collections",
|
||||
"search",
|
||||
{ q: "title:fix" },
|
||||
]);
|
||||
await queryClient.fetchQuery(query);
|
||||
expect(API.experimental.getChats).toHaveBeenCalledWith({
|
||||
limit: 50,
|
||||
@@ -1634,62 +1705,61 @@ describe("diff_status_change invalidation scope", () => {
|
||||
// invalidate only the individual chat detail and diff-contents
|
||||
// queries, NOT the chat list (sidebar) or messages.
|
||||
|
||||
it("exact chatKey invalidation does not cascade to messages or diff-contents", async () => {
|
||||
it("exact chatEntityKey invalidation does not cascade to messages or diff-contents", async () => {
|
||||
const queryClient = createTestQueryClient();
|
||||
const chatId = "chat-1";
|
||||
|
||||
// Seed all the queries that are active on the /agents/:id page.
|
||||
queryClient.setQueryData(chatKey(chatId), makeChat(chatId));
|
||||
queryClient.setQueryData(chatEntityKey(chatId), makeChat(chatId));
|
||||
queryClient.setQueryData(chatMessagesKey(chatId), []);
|
||||
queryClient.setQueryData(chatDiffContentsKey(chatId), { files: [] });
|
||||
queryClient.setQueryData(chatsKey, [makeChat(chatId)]);
|
||||
queryClient.setQueryData(chatListKey(toChatListParams()), [
|
||||
makeChat(chatId),
|
||||
]);
|
||||
|
||||
// This is what the fixed handler does, exact: true.
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: chatKey(chatId),
|
||||
queryKey: chatEntityKey(chatId),
|
||||
exact: true,
|
||||
});
|
||||
|
||||
// chatKey itself should be invalidated.
|
||||
expect(
|
||||
queryClient.getQueryState(chatKey(chatId))?.isInvalidated,
|
||||
"chatKey should be invalidated",
|
||||
queryClient.getQueryState(chatEntityKey(chatId))?.isInvalidated,
|
||||
"chatEntityKey should be invalidated",
|
||||
).toBe(true);
|
||||
|
||||
// Messages should NOT be invalidated.
|
||||
expect(
|
||||
queryClient.getQueryState(chatMessagesKey(chatId))?.isInvalidated,
|
||||
"chatMessagesKey should NOT be invalidated by exact chatKey",
|
||||
"chatMessagesKey should NOT be invalidated by exact chatEntityKey",
|
||||
).not.toBe(true);
|
||||
|
||||
// Diff-contents should NOT be invalidated.
|
||||
expect(
|
||||
queryClient.getQueryState(chatDiffContentsKey(chatId))?.isInvalidated,
|
||||
"chatDiffContentsKey should NOT be invalidated by exact chatKey",
|
||||
"chatDiffContentsKey should NOT be invalidated by exact chatEntityKey",
|
||||
).not.toBe(true);
|
||||
|
||||
// Chat list should NOT be invalidated.
|
||||
expect(
|
||||
queryClient.getQueryState(chatsKey)?.isInvalidated,
|
||||
"chatsKey should NOT be invalidated by exact chatKey",
|
||||
queryClient.getQueryState(chatListKey(toChatListParams()))?.isInvalidated,
|
||||
"chatListKey should NOT be invalidated by exact chatEntityKey",
|
||||
).not.toBe(true);
|
||||
});
|
||||
|
||||
it("without exact: true, chatKey invalidation cascades to messages and diff-contents (the old bug)", async () => {
|
||||
it("without exact: true, chatEntityKey invalidation cascades to messages and diff-contents (the old bug)", async () => {
|
||||
const queryClient = createTestQueryClient();
|
||||
const chatId = "chat-1";
|
||||
|
||||
queryClient.setQueryData(chatKey(chatId), makeChat(chatId));
|
||||
queryClient.setQueryData(chatEntityKey(chatId), makeChat(chatId));
|
||||
queryClient.setQueryData(chatMessagesKey(chatId), []);
|
||||
queryClient.setQueryData(chatDiffContentsKey(chatId), { files: [] });
|
||||
|
||||
// This is what the OLD (broken) handler did, no exact: true.
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: chatKey(chatId),
|
||||
queryKey: chatEntityKey(chatId),
|
||||
});
|
||||
|
||||
// Without exact: true, ALL queries starting with ["chats", chatId]
|
||||
// get invalidated, including messages and diff-contents.
|
||||
expect(
|
||||
queryClient.getQueryState(chatMessagesKey(chatId))?.isInvalidated,
|
||||
"chatMessagesKey IS invalidated without exact: true (old bug)",
|
||||
@@ -2553,7 +2623,7 @@ describe("mergeWatchedChatIntoCaches", () => {
|
||||
});
|
||||
|
||||
seedInfiniteChats(queryClient, [cachedChat]);
|
||||
queryClient.setQueryData(chatKey(chatId), cachedChat);
|
||||
queryClient.setQueryData(chatEntityKey(chatId), cachedChat);
|
||||
|
||||
mergeWatchedChatIntoCaches(queryClient, watchedChat, {
|
||||
eventKind: "status_change",
|
||||
@@ -2565,7 +2635,7 @@ describe("mergeWatchedChatIntoCaches", () => {
|
||||
updated_at: "2025-01-01T00:05:00.000Z",
|
||||
});
|
||||
expect(
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatKey(chatId)),
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatEntityKey(chatId)),
|
||||
).toMatchObject({
|
||||
status: "running",
|
||||
last_model_config_id: "model-new",
|
||||
@@ -2593,7 +2663,7 @@ describe("mergeWatchedChatIntoCaches", () => {
|
||||
});
|
||||
|
||||
seedInfiniteChats(queryClient, [parent]);
|
||||
queryClient.setQueryData(chatKey(childId), cachedChild);
|
||||
queryClient.setQueryData(chatEntityKey(childId), cachedChild);
|
||||
|
||||
mergeWatchedChatIntoCaches(queryClient, watchedChild, {
|
||||
eventKind: "status_change",
|
||||
@@ -2605,7 +2675,7 @@ describe("mergeWatchedChatIntoCaches", () => {
|
||||
updated_at: "2025-01-01T00:05:00.000Z",
|
||||
});
|
||||
expect(
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatKey(childId)),
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatEntityKey(childId)),
|
||||
).toMatchObject({
|
||||
status: "running",
|
||||
last_model_config_id: "model-new",
|
||||
@@ -2634,7 +2704,7 @@ describe("mergeWatchedChatIntoCaches", () => {
|
||||
});
|
||||
|
||||
seedInfiniteChats(queryClient, [cachedChat]);
|
||||
queryClient.setQueryData(chatKey(chatId), cachedChat);
|
||||
queryClient.setQueryData(chatEntityKey(chatId), cachedChat);
|
||||
|
||||
mergeWatchedChatIntoCaches(queryClient, staleWatchChat, {
|
||||
eventKind: "status_change",
|
||||
@@ -2649,7 +2719,7 @@ describe("mergeWatchedChatIntoCaches", () => {
|
||||
updated_at: "2025-01-01T00:05:00.000Z",
|
||||
});
|
||||
expect(
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatKey(chatId)),
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatEntityKey(chatId)),
|
||||
).toMatchObject({
|
||||
status: "waiting",
|
||||
title: "Fresh title",
|
||||
@@ -2745,7 +2815,7 @@ describe("chat ACL query factories", () => {
|
||||
|
||||
const query = chatACL(chatId);
|
||||
|
||||
expect(chatACLKey(chatId)).toEqual(["chats", chatId, "acl"]);
|
||||
expect(chatACLKey(chatId)).toEqual(["chats", "entities", chatId, "acl"]);
|
||||
expect(query.queryKey).toEqual(chatACLKey(chatId));
|
||||
await expect(query.queryFn()).resolves.toEqual(acl);
|
||||
expect(API.experimental.getChatACL).toHaveBeenCalledWith(chatId);
|
||||
|
||||
+295
-199
File diff suppressed because it is too large
Load Diff
@@ -1,19 +1,15 @@
|
||||
import type { FC } from "react";
|
||||
import {
|
||||
type QueryClient,
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from "react-query";
|
||||
import { API } from "#/api/api";
|
||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
import { chatProviderConfigs } from "#/api/queries/aiProviders";
|
||||
import {
|
||||
chatAdvisorConfig,
|
||||
chatComputerUseProvider,
|
||||
chatModelConfigs,
|
||||
chatModelOverride,
|
||||
chatPersonalModelOverridesAdminSettings,
|
||||
chatProviderConfigs,
|
||||
updateChatAdvisorConfig,
|
||||
updateChatComputerUseProvider,
|
||||
updateChatModelOverride,
|
||||
updateChatPersonalModelOverridesAdminSettings,
|
||||
} from "#/api/queries/chats";
|
||||
import type * as TypesGen from "#/api/typesGenerated";
|
||||
@@ -31,30 +27,6 @@ const titleGenerationOverrideContext: TypesGen.ChatModelOverrideContext =
|
||||
const compactionOverrideContext: TypesGen.ChatModelOverrideContext =
|
||||
"compaction";
|
||||
|
||||
const chatModelOverrideKey = (context: TypesGen.ChatModelOverrideContext) =>
|
||||
["chat-model-override", context] as const;
|
||||
|
||||
const chatModelOverrideQuery = (
|
||||
context: TypesGen.ChatModelOverrideContext,
|
||||
) => ({
|
||||
queryKey: chatModelOverrideKey(context),
|
||||
queryFn: () => API.experimental.getChatModelOverride(context),
|
||||
});
|
||||
|
||||
const updateChatModelOverrideMutation = (
|
||||
queryClient: QueryClient,
|
||||
context: TypesGen.ChatModelOverrideContext,
|
||||
) => ({
|
||||
mutationFn: (req: TypesGen.UpdateChatModelOverrideRequest) =>
|
||||
API.experimental.updateChatModelOverride(context, req),
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: chatModelOverrideKey(context),
|
||||
exact: true,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const CoderAgentsPage: FC = () => {
|
||||
const { permissions } = useAuthenticated();
|
||||
const { experiments } = useDashboard();
|
||||
@@ -70,19 +42,19 @@ const CoderAgentsPage: FC = () => {
|
||||
enabled: canEditDeploymentConfig,
|
||||
});
|
||||
const generalModelOverrideQuery = useQuery({
|
||||
...chatModelOverrideQuery(generalOverrideContext),
|
||||
...chatModelOverride(generalOverrideContext),
|
||||
enabled: canEditDeploymentConfig,
|
||||
});
|
||||
const exploreModelOverrideQuery = useQuery({
|
||||
...chatModelOverrideQuery(exploreOverrideContext),
|
||||
...chatModelOverride(exploreOverrideContext),
|
||||
enabled: canEditDeploymentConfig,
|
||||
});
|
||||
const titleGenerationModelQuery = useQuery({
|
||||
...chatModelOverrideQuery(titleGenerationOverrideContext),
|
||||
...chatModelOverride(titleGenerationOverrideContext),
|
||||
enabled: canEditDeploymentConfig,
|
||||
});
|
||||
const compactionModelQuery = useQuery({
|
||||
...chatModelOverrideQuery(compactionOverrideContext),
|
||||
...chatModelOverride(compactionOverrideContext),
|
||||
enabled: canEditDeploymentConfig,
|
||||
});
|
||||
const modelConfigsQuery = useQuery(chatModelConfigs());
|
||||
@@ -102,19 +74,16 @@ const CoderAgentsPage: FC = () => {
|
||||
updateChatPersonalModelOverridesAdminSettings(queryClient),
|
||||
);
|
||||
const saveGeneralModelOverrideMutation = useMutation(
|
||||
updateChatModelOverrideMutation(queryClient, generalOverrideContext),
|
||||
updateChatModelOverride(queryClient, generalOverrideContext),
|
||||
);
|
||||
const saveTitleGenerationModelMutation = useMutation(
|
||||
updateChatModelOverrideMutation(
|
||||
queryClient,
|
||||
titleGenerationOverrideContext,
|
||||
),
|
||||
updateChatModelOverride(queryClient, titleGenerationOverrideContext),
|
||||
);
|
||||
const saveCompactionModelMutation = useMutation(
|
||||
updateChatModelOverrideMutation(queryClient, compactionOverrideContext),
|
||||
updateChatModelOverride(queryClient, compactionOverrideContext),
|
||||
);
|
||||
const saveExploreModelOverrideMutation = useMutation(
|
||||
updateChatModelOverrideMutation(queryClient, exploreOverrideContext),
|
||||
updateChatModelOverride(queryClient, exploreOverrideContext),
|
||||
);
|
||||
const saveAdvisorConfigMutation = useMutation(
|
||||
updateChatAdvisorConfig(queryClient),
|
||||
|
||||
@@ -3,10 +3,10 @@ import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
import { useNavigate, useSearchParams } from "react-router";
|
||||
import { toast } from "sonner";
|
||||
import { getErrorMessage } from "#/api/errors";
|
||||
import { chatProviderConfigs } from "#/api/queries/aiProviders";
|
||||
import {
|
||||
chatModelConfigs,
|
||||
chatModels,
|
||||
chatProviderConfigs,
|
||||
createChatModelConfig,
|
||||
} from "#/api/queries/chats";
|
||||
import { useAuthenticated } from "#/hooks/useAuthenticated";
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import type { FC } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import {
|
||||
chatModelConfigs,
|
||||
chatModels,
|
||||
chatProviderConfigs,
|
||||
} from "#/api/queries/chats";
|
||||
import { chatProviderConfigs } from "#/api/queries/aiProviders";
|
||||
import { chatModelConfigs, chatModels } from "#/api/queries/chats";
|
||||
import { useAuthenticated } from "#/hooks/useAuthenticated";
|
||||
import { deriveProviderStates } from "#/modules/aiModels/providerStates";
|
||||
import { RequirePermission } from "#/modules/permissions/RequirePermission";
|
||||
|
||||
@@ -3,10 +3,10 @@ import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
import { Navigate, useNavigate, useParams } from "react-router";
|
||||
import { toast } from "sonner";
|
||||
import { getErrorMessage } from "#/api/errors";
|
||||
import { chatProviderConfigs } from "#/api/queries/aiProviders";
|
||||
import {
|
||||
chatModelConfigs,
|
||||
chatModels,
|
||||
chatProviderConfigs,
|
||||
deleteChatModelConfig,
|
||||
updateChatModelConfig,
|
||||
} from "#/api/queries/chats";
|
||||
|
||||
@@ -12,13 +12,14 @@ import { API } from "#/api/api";
|
||||
import { getAuthorizationKey } from "#/api/queries/authCheck";
|
||||
import {
|
||||
chatDiffContentsKey,
|
||||
chatKey,
|
||||
chatEntityKey,
|
||||
chatListKey,
|
||||
chatMessagesKey,
|
||||
chatModelConfigs,
|
||||
chatModelsKey,
|
||||
chatPromptsKey,
|
||||
chatsKey,
|
||||
mcpServerConfigsKey,
|
||||
mcpServersKey,
|
||||
toChatListParams,
|
||||
} from "#/api/queries/chats";
|
||||
import { workspaceByIdKey } from "#/api/queries/workspaces";
|
||||
import type * as TypesGen from "#/api/typesGenerated";
|
||||
@@ -273,7 +274,7 @@ const buildQueries = (
|
||||
diff_status: diffStatus,
|
||||
};
|
||||
return [
|
||||
{ key: chatKey(CHAT_ID), data: chatWithDiffStatus },
|
||||
{ key: chatEntityKey(CHAT_ID), data: chatWithDiffStatus },
|
||||
{
|
||||
key: chatMessagesKey(CHAT_ID),
|
||||
data: { pages: [messagesData], pageParams: [undefined] },
|
||||
@@ -284,7 +285,10 @@ const buildQueries = (
|
||||
prompts: extractPromptsFromMessages(messagesData.messages),
|
||||
} satisfies TypesGen.ChatPromptsResponse,
|
||||
},
|
||||
{ key: chatsKey, data: [chatWithDiffStatus] },
|
||||
{
|
||||
key: chatListKey(toChatListParams()),
|
||||
data: { pages: [[chatWithDiffStatus]], pageParams: [0] },
|
||||
},
|
||||
{
|
||||
key: chatDiffContentsKey(CHAT_ID),
|
||||
data: {
|
||||
@@ -299,7 +303,7 @@ const buildQueries = (
|
||||
},
|
||||
{ key: chatModelsKey, data: mockModelCatalog },
|
||||
{ key: chatModelConfigs().queryKey, data: mockModelConfigs },
|
||||
{ key: mcpServerConfigsKey, data: [] },
|
||||
{ key: mcpServersKey, data: [] },
|
||||
buildChatAuthorizationQuery(chat, {
|
||||
canShareChat: {
|
||||
action: "share",
|
||||
@@ -2986,7 +2990,7 @@ export const SendResponseAfterChatSwitch: Story = {
|
||||
{ messages: [], queued_messages: [], has_more: false },
|
||||
{ diffUrl: undefined },
|
||||
),
|
||||
{ key: chatKey(SWITCHED_CHAT_ID), data: switchedChat },
|
||||
{ key: chatEntityKey(SWITCHED_CHAT_ID), data: switchedChat },
|
||||
{
|
||||
key: chatMessagesKey(SWITCHED_CHAT_ID),
|
||||
data: {
|
||||
@@ -3087,7 +3091,7 @@ export const DetailQueryError: Story = {
|
||||
queued_messages: [],
|
||||
has_more: false,
|
||||
}),
|
||||
chatKey(CHAT_ID),
|
||||
chatEntityKey(CHAT_ID),
|
||||
),
|
||||
},
|
||||
beforeEach: () => {
|
||||
@@ -3134,7 +3138,7 @@ export const ErrorRetryRecovers: Story = {
|
||||
queued_messages: [],
|
||||
has_more: false,
|
||||
}),
|
||||
chatKey(CHAT_ID),
|
||||
chatEntityKey(CHAT_ID),
|
||||
),
|
||||
},
|
||||
beforeEach: ({ parameters }) => {
|
||||
@@ -3164,7 +3168,7 @@ export const ChatNotFound: Story = {
|
||||
queued_messages: [],
|
||||
has_more: false,
|
||||
}),
|
||||
chatKey(CHAT_ID),
|
||||
chatEntityKey(CHAT_ID),
|
||||
),
|
||||
},
|
||||
beforeEach: () => {
|
||||
|
||||
@@ -23,15 +23,15 @@ import {
|
||||
watchWorkspace,
|
||||
} from "#/api/api";
|
||||
import { getErrorMessage, getErrorStatus, isApiError } from "#/api/errors";
|
||||
import { chatProviderConfigs } from "#/api/queries/aiProviders";
|
||||
import { checkAuthorization } from "#/api/queries/authCheck";
|
||||
import { buildOptimisticEditedMessage } from "#/api/queries/chatMessageEdits";
|
||||
import {
|
||||
chat,
|
||||
chatKey,
|
||||
chatEntityKey,
|
||||
chatMessagesForInfiniteScroll,
|
||||
chatModelConfigs,
|
||||
chatModels,
|
||||
chatProviderConfigs,
|
||||
chatQueueConvergence,
|
||||
compactChat,
|
||||
createChatMessage,
|
||||
@@ -1182,8 +1182,10 @@ const AgentChatPage: FC = () => {
|
||||
chat.id === chatId ? { ...chat, plan_mode: planMode } : chat,
|
||||
),
|
||||
);
|
||||
queryClient.setQueryData<TypesGen.Chat>(chatKey(chatId), (previousChat) =>
|
||||
previousChat ? { ...previousChat, plan_mode: planMode } : previousChat,
|
||||
queryClient.setQueryData<TypesGen.Chat>(
|
||||
chatEntityKey(chatId),
|
||||
(previousChat) =>
|
||||
previousChat ? { ...previousChat, plan_mode: planMode } : previousChat,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1693,7 +1695,7 @@ const AgentChatPage: FC = () => {
|
||||
// Hook dispatch failures can park an idle chat in error before returning the request error.
|
||||
acceptServerChatStatus();
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: chatKey(agentId),
|
||||
queryKey: chatEntityKey(agentId),
|
||||
exact: true,
|
||||
});
|
||||
},
|
||||
@@ -1744,7 +1746,7 @@ const AgentChatPage: FC = () => {
|
||||
// Hook dispatch failures can park an idle chat in error before returning the request error.
|
||||
acceptServerChatStatus();
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: chatKey(agentId),
|
||||
queryKey: chatEntityKey(agentId),
|
||||
exact: true,
|
||||
});
|
||||
throw error;
|
||||
|
||||
@@ -3,10 +3,10 @@ import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
import { useLocation, useNavigate } from "react-router";
|
||||
import { toast } from "sonner";
|
||||
import { getErrorMessage } from "#/api/errors";
|
||||
import { chatProviderConfigs } from "#/api/queries/aiProviders";
|
||||
import {
|
||||
chatModelConfigs,
|
||||
chatModels,
|
||||
chatProviderConfigs,
|
||||
createChat,
|
||||
mcpServerConfigs,
|
||||
userChatPersonalModelOverrides,
|
||||
|
||||
@@ -20,12 +20,12 @@ import {
|
||||
applyChatArchiveStateToCaches,
|
||||
archiveChat,
|
||||
cancelChatListRefetches,
|
||||
chatCostKey,
|
||||
chatCostTreeKey,
|
||||
chatDiffContentsKey,
|
||||
chatKey,
|
||||
chatEntityKey,
|
||||
chatModelConfigs,
|
||||
chatModels,
|
||||
chatsByWorkspaceKeyPrefix,
|
||||
chatsByWorkspaceFamilyKey,
|
||||
infiniteChats,
|
||||
invalidateChatListQueries,
|
||||
mergeWatchedChatIntoCaches,
|
||||
@@ -304,11 +304,11 @@ const AgentsPageLayout: FC = () => {
|
||||
clearPersistedRightPanelState(chatId);
|
||||
void invalidateChatListQueries(queryClient);
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: chatKey(chatId),
|
||||
queryKey: chatEntityKey(chatId),
|
||||
exact: true,
|
||||
});
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: chatsByWorkspaceKeyPrefix,
|
||||
queryKey: chatsByWorkspaceFamilyKey,
|
||||
});
|
||||
void invalidateWorkspaceMutationQueries(queryClient, {
|
||||
organizationName,
|
||||
@@ -410,7 +410,7 @@ const AgentsPageLayout: FC = () => {
|
||||
return;
|
||||
}
|
||||
const chat =
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatKey(chatId)) ??
|
||||
queryClient.getQueryData<TypesGen.Chat>(chatEntityKey(chatId)) ??
|
||||
chatList.find((candidate) => candidate.id === chatId);
|
||||
if (chat === undefined || isActiveChat(chat)) {
|
||||
setPendingArchiveChatId(chatId);
|
||||
@@ -445,7 +445,7 @@ const AgentsPageLayout: FC = () => {
|
||||
// callback time so it reflects the user's current
|
||||
// location.
|
||||
activeChatId
|
||||
? queryClient.getQueryData<TypesGen.Chat>(chatKey(activeChatId))
|
||||
? queryClient.getQueryData<TypesGen.Chat>(chatEntityKey(activeChatId))
|
||||
?.root_chat_id
|
||||
: undefined,
|
||||
)
|
||||
@@ -617,7 +617,7 @@ const AgentsPageLayout: FC = () => {
|
||||
);
|
||||
removeChildFromParentInCache(queryClient, updatedChat.id);
|
||||
queryClient.removeQueries({
|
||||
queryKey: chatKey(updatedChat.id),
|
||||
queryKey: chatEntityKey(updatedChat.id),
|
||||
exact: true,
|
||||
});
|
||||
return;
|
||||
@@ -648,9 +648,9 @@ const AgentsPageLayout: FC = () => {
|
||||
// reverts the query to pending/idle with no data
|
||||
// and no retry, which AgentChatPage shows as
|
||||
// "Chat not found".
|
||||
if (queryClient.getQueryData(chatKey(updatedChat.id))) {
|
||||
if (queryClient.getQueryData(chatEntityKey(updatedChat.id))) {
|
||||
void queryClient.cancelQueries({
|
||||
queryKey: chatKey(updatedChat.id),
|
||||
queryKey: chatEntityKey(updatedChat.id),
|
||||
exact: true,
|
||||
});
|
||||
}
|
||||
@@ -683,7 +683,7 @@ const AgentsPageLayout: FC = () => {
|
||||
);
|
||||
if (costChatId) {
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: chatCostKey(costChatId),
|
||||
queryKey: chatCostTreeKey(costChatId),
|
||||
exact: true,
|
||||
});
|
||||
}
|
||||
@@ -695,7 +695,7 @@ const AgentsPageLayout: FC = () => {
|
||||
// active chat has an observer, so other chats are
|
||||
// merely marked stale.
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: chatKey(updatedChat.id),
|
||||
queryKey: chatEntityKey(updatedChat.id),
|
||||
exact: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { act, render, renderHook, waitFor } from "@testing-library/react";
|
||||
import { watchChat } from "#/api/api";
|
||||
import { chatMessagesKey, chatsKey } from "#/api/queries/chats";
|
||||
import {
|
||||
chatListKey,
|
||||
chatMessagesKey,
|
||||
toChatListParams,
|
||||
} from "#/api/queries/chats";
|
||||
|
||||
// The infinite query key used by useInfiniteQuery(infiniteChats())
|
||||
// is [...chatsKey, undefined] = ["chats", undefined].
|
||||
const infiniteChatsTestKey = [...chatsKey, undefined];
|
||||
const infiniteChatsTestKey = chatListKey(toChatListParams());
|
||||
|
||||
type InfiniteData = {
|
||||
pages: TypesGen.Chat[][];
|
||||
|
||||
+5
-4
@@ -3,6 +3,7 @@ import type { FC, PropsWithChildren } from "react";
|
||||
import { act } from "react";
|
||||
import { QueryClient, QueryClientProvider } from "react-query";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { chatEntityKey } from "#/api/queries/chats";
|
||||
import { getWorkspaceQuotaQueryKey } from "#/api/queries/workspaceQuota";
|
||||
import { workspacesQueryKeyPrefix } from "#/api/queries/workspaces";
|
||||
import { createChatStore } from "./chatStore";
|
||||
@@ -106,7 +107,7 @@ describe("useChatToolInvalidations", () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invalidateSpy).toHaveBeenCalledWith({
|
||||
queryKey: ["chats", "chat-1"],
|
||||
queryKey: chatEntityKey("chat-1"),
|
||||
});
|
||||
expect(invalidateSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -144,7 +145,7 @@ describe("useChatToolInvalidations", () => {
|
||||
});
|
||||
|
||||
expect(invalidateSpy).not.toHaveBeenCalledWith({
|
||||
queryKey: ["chats", "chat-1"],
|
||||
queryKey: chatEntityKey("chat-1"),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -183,7 +184,7 @@ describe("useChatToolInvalidations", () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invalidateSpy).toHaveBeenCalledWith({
|
||||
queryKey: ["chats", "chat-1"],
|
||||
queryKey: chatEntityKey("chat-1"),
|
||||
});
|
||||
expect(invalidateSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -250,7 +251,7 @@ describe("useChatToolInvalidations", () => {
|
||||
await waitFor(() => {
|
||||
expect(invalidateSpy).toHaveBeenCalledTimes(6);
|
||||
expect(invalidateSpy).toHaveBeenCalledWith({
|
||||
queryKey: ["chats", "chat-2"],
|
||||
queryKey: chatEntityKey("chat-2"),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useQueryClient } from "react-query";
|
||||
import { chatKey } from "#/api/queries/chats";
|
||||
import { chatEntityKey } from "#/api/queries/chats";
|
||||
import { invalidateWorkspaceMutationQueries } from "#/api/queries/workspaces";
|
||||
import { type ChatStore, useChatSelector } from "./chatStore";
|
||||
import type { StreamState } from "./types";
|
||||
@@ -88,7 +88,7 @@ export function useChatToolInvalidations({
|
||||
|
||||
if (shouldInvalidateChat) {
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: chatKey(chatID),
|
||||
queryKey: chatEntityKey(chatID),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { LoaderIcon, PlayIcon } from "lucide-react";
|
||||
import type React from "react";
|
||||
import { useMutation, useQuery } from "react-query";
|
||||
import { skipToken, useMutation, useQuery } from "react-query";
|
||||
import { API } from "#/api/api";
|
||||
import { chatFilesKey, chatFileTextKey } from "#/api/queries/chats";
|
||||
import { Button } from "#/components/Button/Button";
|
||||
import { CopyButton } from "#/components/CopyButton/CopyButton";
|
||||
import {
|
||||
@@ -34,15 +35,11 @@ export const ProposePlanTool: React.FC<{
|
||||
}) => {
|
||||
const hasInlineContent = (inlineContent?.trim().length ?? 0) > 0;
|
||||
const fileQuery = useQuery({
|
||||
queryKey: ["chatFile", fileID],
|
||||
queryFn: async () => {
|
||||
if (!fileID) {
|
||||
throw new Error("Missing file ID");
|
||||
}
|
||||
|
||||
return API.experimental.getChatFileText(fileID);
|
||||
},
|
||||
enabled: Boolean(fileID) && !hasInlineContent,
|
||||
queryKey: fileID ? chatFileTextKey(fileID) : chatFilesKey,
|
||||
queryFn:
|
||||
fileID && !hasInlineContent
|
||||
? () => API.experimental.getChatFileText(fileID)
|
||||
: skipToken,
|
||||
staleTime: Number.POSITIVE_INFINITY,
|
||||
});
|
||||
|
||||
|
||||
@@ -203,7 +203,7 @@ const ChatSearchDialogContent: FC<ChatSearchDialogContentProps> = ({
|
||||
const hasQuery = hasActiveSearch && normalizedQuery !== undefined;
|
||||
|
||||
const searchQuery = useQuery({
|
||||
...chatSearch(normalizedQuery ?? ""),
|
||||
...chatSearch({ q: normalizedQuery ?? "" }),
|
||||
enabled: open && hasQuery,
|
||||
placeholderData: keepPreviousData,
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import { toast } from "sonner";
|
||||
import { expect, fn, spyOn, userEvent, waitFor, within } from "storybook/test";
|
||||
import type { Mock } from "vitest";
|
||||
import { API } from "#/api/api";
|
||||
import { chatDebugRunKey, chatDebugRunsKey } from "#/api/queries/chats";
|
||||
import type * as TypesGen from "#/api/typesGenerated";
|
||||
import { DebugPanel } from "./DebugPanel";
|
||||
import { CHAT_ID, MockRun, MockStep } from "./debugFixtures";
|
||||
@@ -456,7 +457,7 @@ const getAllRunSummaries = () =>
|
||||
const getDebugRunDetailById = () =>
|
||||
new Map(getAllRunDetails().map((run) => [run.id, run]));
|
||||
|
||||
const debugRunsQueryKey = ["chats", CHAT_ID, "debug-runs"] as const;
|
||||
const debugRunsQueryKey = chatDebugRunsKey(CHAT_ID);
|
||||
|
||||
const getSeededRunSummaries = (
|
||||
queries: readonly { key: readonly unknown[]; data: unknown }[] | undefined,
|
||||
@@ -535,7 +536,7 @@ export const Empty: Story = {
|
||||
parameters: {
|
||||
queries: [
|
||||
{
|
||||
key: ["chats", CHAT_ID, "debug-runs"],
|
||||
key: chatDebugRunsKey(CHAT_ID),
|
||||
data: [],
|
||||
},
|
||||
],
|
||||
@@ -615,7 +616,7 @@ export const RunDetailLoading: Story = {
|
||||
parameters: {
|
||||
queries: [
|
||||
{
|
||||
key: ["chats", CHAT_ID, "debug-runs"],
|
||||
key: chatDebugRunsKey(CHAT_ID),
|
||||
data: [detailProbeSummary],
|
||||
},
|
||||
],
|
||||
@@ -649,7 +650,7 @@ export const RunDetailError: Story = {
|
||||
parameters: {
|
||||
queries: [
|
||||
{
|
||||
key: ["chats", CHAT_ID, "debug-runs"],
|
||||
key: chatDebugRunsKey(CHAT_ID),
|
||||
data: [detailProbeSummary],
|
||||
},
|
||||
],
|
||||
@@ -682,11 +683,11 @@ export const RunWithNoSteps: Story = {
|
||||
parameters: {
|
||||
queries: [
|
||||
{
|
||||
key: ["chats", CHAT_ID, "debug-runs"],
|
||||
key: chatDebugRunsKey(CHAT_ID),
|
||||
data: [detailProbeSummary],
|
||||
},
|
||||
{
|
||||
key: ["chats", CHAT_ID, "debug-runs", detailProbeRunId],
|
||||
key: chatDebugRunKey(CHAT_ID, detailProbeRunId),
|
||||
data: {
|
||||
...MockRun,
|
||||
id: detailProbeRunId,
|
||||
@@ -719,7 +720,7 @@ export const SingleStepSuccessfulRun: Story = {
|
||||
parameters: {
|
||||
queries: [
|
||||
{
|
||||
key: ["chats", CHAT_ID, "debug-runs"],
|
||||
key: chatDebugRunsKey(CHAT_ID),
|
||||
data: [
|
||||
buildRunSummary({
|
||||
id: successfulRunDetail.id,
|
||||
@@ -728,7 +729,7 @@ export const SingleStepSuccessfulRun: Story = {
|
||||
],
|
||||
},
|
||||
{
|
||||
key: ["chats", CHAT_ID, "debug-runs", successfulRunDetail.id],
|
||||
key: chatDebugRunKey(CHAT_ID, successfulRunDetail.id),
|
||||
data: successfulRunDetail,
|
||||
},
|
||||
],
|
||||
@@ -770,7 +771,7 @@ export const ExportAllRuns: Story = {
|
||||
parameters: {
|
||||
queries: [
|
||||
{
|
||||
key: ["chats", CHAT_ID, "debug-runs"],
|
||||
key: chatDebugRunsKey(CHAT_ID),
|
||||
data: [
|
||||
buildRunSummary({
|
||||
id: successfulRunDetail.id,
|
||||
@@ -819,7 +820,7 @@ export const ExportAllRunsUsesCachedTerminalRunDetails: Story = {
|
||||
parameters: {
|
||||
queries: [
|
||||
{
|
||||
key: ["chats", CHAT_ID, "debug-runs"],
|
||||
key: chatDebugRunsKey(CHAT_ID),
|
||||
data: [
|
||||
buildRunSummary({
|
||||
id: successfulRunDetail.id,
|
||||
@@ -828,7 +829,7 @@ export const ExportAllRunsUsesCachedTerminalRunDetails: Story = {
|
||||
],
|
||||
},
|
||||
{
|
||||
key: ["chats", CHAT_ID, "debug-runs", successfulRunDetail.id],
|
||||
key: chatDebugRunKey(CHAT_ID, successfulRunDetail.id),
|
||||
data: successfulRunDetail,
|
||||
},
|
||||
],
|
||||
@@ -979,7 +980,7 @@ export const ExportSingleRun: Story = {
|
||||
parameters: {
|
||||
queries: [
|
||||
{
|
||||
key: ["chats", CHAT_ID, "debug-runs"],
|
||||
key: chatDebugRunsKey(CHAT_ID),
|
||||
data: [
|
||||
buildRunSummary({
|
||||
id: successfulRunDetail.id,
|
||||
@@ -988,7 +989,7 @@ export const ExportSingleRun: Story = {
|
||||
],
|
||||
},
|
||||
{
|
||||
key: ["chats", CHAT_ID, "debug-runs", successfulRunDetail.id],
|
||||
key: chatDebugRunKey(CHAT_ID, successfulRunDetail.id),
|
||||
data: successfulRunDetail,
|
||||
},
|
||||
],
|
||||
@@ -1060,7 +1061,7 @@ export const MultiStepRunWithRetries: Story = {
|
||||
parameters: {
|
||||
queries: [
|
||||
{
|
||||
key: ["chats", CHAT_ID, "debug-runs"],
|
||||
key: chatDebugRunsKey(CHAT_ID),
|
||||
data: [
|
||||
buildRunSummary({
|
||||
id: multiStepRunDetail.id,
|
||||
@@ -1073,7 +1074,7 @@ export const MultiStepRunWithRetries: Story = {
|
||||
],
|
||||
},
|
||||
{
|
||||
key: ["chats", CHAT_ID, "debug-runs", multiStepRunDetail.id],
|
||||
key: chatDebugRunKey(CHAT_ID, multiStepRunDetail.id),
|
||||
data: multiStepRunDetail,
|
||||
},
|
||||
],
|
||||
@@ -1118,7 +1119,7 @@ export const ErrorStateWithRedactedHeaders: Story = {
|
||||
parameters: {
|
||||
queries: [
|
||||
{
|
||||
key: ["chats", CHAT_ID, "debug-runs"],
|
||||
key: chatDebugRunsKey(CHAT_ID),
|
||||
data: [
|
||||
buildRunSummary({
|
||||
id: errorRunDetail.id,
|
||||
@@ -1131,7 +1132,7 @@ export const ErrorStateWithRedactedHeaders: Story = {
|
||||
],
|
||||
},
|
||||
{
|
||||
key: ["chats", CHAT_ID, "debug-runs", errorRunDetail.id],
|
||||
key: chatDebugRunKey(CHAT_ID, errorRunDetail.id),
|
||||
data: errorRunDetail,
|
||||
},
|
||||
],
|
||||
@@ -1174,7 +1175,7 @@ export const CompactionAndTitleGenerationBadges: Story = {
|
||||
parameters: {
|
||||
queries: [
|
||||
{
|
||||
key: ["chats", CHAT_ID, "debug-runs"],
|
||||
key: chatDebugRunsKey(CHAT_ID),
|
||||
data: [
|
||||
buildRunSummary({
|
||||
id: "run-compaction",
|
||||
@@ -1222,7 +1223,7 @@ export const LongRawPayloads: Story = {
|
||||
parameters: {
|
||||
queries: [
|
||||
{
|
||||
key: ["chats", CHAT_ID, "debug-runs"],
|
||||
key: chatDebugRunsKey(CHAT_ID),
|
||||
data: [
|
||||
buildRunSummary({
|
||||
id: longPayloadRunDetail.id,
|
||||
@@ -1234,7 +1235,7 @@ export const LongRawPayloads: Story = {
|
||||
],
|
||||
},
|
||||
{
|
||||
key: ["chats", CHAT_ID, "debug-runs", longPayloadRunDetail.id],
|
||||
key: chatDebugRunKey(CHAT_ID, longPayloadRunDetail.id),
|
||||
data: longPayloadRunDetail,
|
||||
},
|
||||
],
|
||||
@@ -1266,7 +1267,7 @@ export const RichPayloadWithTranscript: Story = {
|
||||
parameters: {
|
||||
queries: [
|
||||
{
|
||||
key: ["chats", CHAT_ID, "debug-runs"],
|
||||
key: chatDebugRunsKey(CHAT_ID),
|
||||
data: [
|
||||
buildRunSummary({
|
||||
id: richRunDetail.id,
|
||||
@@ -1275,7 +1276,7 @@ export const RichPayloadWithTranscript: Story = {
|
||||
],
|
||||
},
|
||||
{
|
||||
key: ["chats", CHAT_ID, "debug-runs", richRunDetail.id],
|
||||
key: chatDebugRunKey(CHAT_ID, richRunDetail.id),
|
||||
data: richRunDetail,
|
||||
},
|
||||
],
|
||||
@@ -1343,7 +1344,7 @@ export const ToolCallStep: Story = {
|
||||
parameters: {
|
||||
queries: [
|
||||
{
|
||||
key: ["chats", CHAT_ID, "debug-runs"],
|
||||
key: chatDebugRunsKey(CHAT_ID),
|
||||
data: [
|
||||
buildRunSummary({
|
||||
id: toolCallRunDetail.id,
|
||||
@@ -1352,7 +1353,7 @@ export const ToolCallStep: Story = {
|
||||
],
|
||||
},
|
||||
{
|
||||
key: ["chats", CHAT_ID, "debug-runs", toolCallRunDetail.id],
|
||||
key: chatDebugRunKey(CHAT_ID, toolCallRunDetail.id),
|
||||
data: toolCallRunDetail,
|
||||
},
|
||||
],
|
||||
@@ -1380,7 +1381,7 @@ export const FallbackLabeledRun: Story = {
|
||||
parameters: {
|
||||
queries: [
|
||||
{
|
||||
key: ["chats", CHAT_ID, "debug-runs"],
|
||||
key: chatDebugRunsKey(CHAT_ID),
|
||||
data: [
|
||||
buildRunSummary({
|
||||
id: "run-fallback",
|
||||
@@ -1409,7 +1410,7 @@ export const InProgressRun: Story = {
|
||||
parameters: {
|
||||
queries: [
|
||||
{
|
||||
key: ["chats", CHAT_ID, "debug-runs"],
|
||||
key: chatDebugRunsKey(CHAT_ID),
|
||||
data: [
|
||||
buildRunSummary({
|
||||
id: "run-progress",
|
||||
@@ -1586,7 +1587,7 @@ export const BackendNormalizedShape: Story = {
|
||||
parameters: {
|
||||
queries: [
|
||||
{
|
||||
key: ["chats", CHAT_ID, "debug-runs"],
|
||||
key: chatDebugRunsKey(CHAT_ID),
|
||||
data: [
|
||||
buildRunSummary({
|
||||
id: backendShapeRunDetail.id,
|
||||
@@ -1597,7 +1598,7 @@ export const BackendNormalizedShape: Story = {
|
||||
],
|
||||
},
|
||||
{
|
||||
key: ["chats", CHAT_ID, "debug-runs", backendShapeRunDetail.id],
|
||||
key: chatDebugRunKey(CHAT_ID, backendShapeRunDetail.id),
|
||||
data: backendShapeRunDetail,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { SetURLSearchParams } from "react-router";
|
||||
import {
|
||||
CHAT_LIST_PR_STATUS_ORDER,
|
||||
CHAT_SOURCE_ORDER,
|
||||
type ChatListPRStatusFilter,
|
||||
type ChatListStatusFilter,
|
||||
canonicalizeChatListPRStatuses,
|
||||
@@ -12,7 +13,7 @@ export const AGENT_CHAT_STATUS_ORDER = [
|
||||
"read",
|
||||
] as const satisfies readonly ChatListStatusFilter[];
|
||||
export const AGENT_PR_STATUS_ORDER = CHAT_LIST_PR_STATUS_ORDER;
|
||||
export const AGENT_SOURCE_ORDER = ["created_by_me", "shared_with_me"] as const;
|
||||
export const AGENT_SOURCE_ORDER = CHAT_SOURCE_ORDER;
|
||||
|
||||
export type AgentArchiveStatusFilter =
|
||||
(typeof AGENT_ARCHIVE_STATUS_ORDER)[number];
|
||||
|
||||
Reference in New Issue
Block a user