mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-29 02:46:25 +02:00
Merge upstream/dev
This commit is contained in:
commit
440762fb07
92 changed files with 3227 additions and 2502 deletions
|
|
@ -2,6 +2,8 @@ import { atomWithMutation } from "jotai-tanstack-query";
|
|||
import { toast } from "sonner";
|
||||
import type {
|
||||
CreateImageGenConfigRequest,
|
||||
CreateImageGenConfigResponse,
|
||||
DeleteImageGenConfigResponse,
|
||||
GetImageGenConfigsResponse,
|
||||
UpdateImageGenConfigRequest,
|
||||
UpdateImageGenConfigResponse,
|
||||
|
|
@ -23,14 +25,14 @@ export const createImageGenConfigMutationAtom = atomWithMutation((get) => {
|
|||
mutationFn: async (request: CreateImageGenConfigRequest) => {
|
||||
return imageGenConfigApiService.createConfig(request);
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.success("Image model configuration created");
|
||||
onSuccess: (_: CreateImageGenConfigResponse, request: CreateImageGenConfigRequest) => {
|
||||
toast.success(`${request.name} created`);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: cacheKeys.imageGenConfigs.all(Number(searchSpaceId)),
|
||||
});
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.error(error.message || "Failed to create image model configuration");
|
||||
toast.error(error.message || "Failed to create image model");
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
@ -48,7 +50,7 @@ export const updateImageGenConfigMutationAtom = atomWithMutation((get) => {
|
|||
return imageGenConfigApiService.updateConfig(request);
|
||||
},
|
||||
onSuccess: (_: UpdateImageGenConfigResponse, request: UpdateImageGenConfigRequest) => {
|
||||
toast.success("Image model configuration updated");
|
||||
toast.success(`${request.data.name ?? "Configuration"} updated`);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: cacheKeys.imageGenConfigs.all(Number(searchSpaceId)),
|
||||
});
|
||||
|
|
@ -57,7 +59,7 @@ export const updateImageGenConfigMutationAtom = atomWithMutation((get) => {
|
|||
});
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.error(error.message || "Failed to update image model configuration");
|
||||
toast.error(error.message || "Failed to update image model");
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
@ -71,21 +73,21 @@ export const deleteImageGenConfigMutationAtom = atomWithMutation((get) => {
|
|||
return {
|
||||
mutationKey: ["image-gen-configs", "delete"],
|
||||
enabled: !!searchSpaceId,
|
||||
mutationFn: async (id: number) => {
|
||||
return imageGenConfigApiService.deleteConfig(id);
|
||||
mutationFn: async (request: { id: number; name: string }) => {
|
||||
return imageGenConfigApiService.deleteConfig(request.id);
|
||||
},
|
||||
onSuccess: (_, id: number) => {
|
||||
toast.success("Image model configuration deleted");
|
||||
onSuccess: (_: DeleteImageGenConfigResponse, request: { id: number; name: string }) => {
|
||||
toast.success(`${request.name} deleted`);
|
||||
queryClient.setQueryData(
|
||||
cacheKeys.imageGenConfigs.all(Number(searchSpaceId)),
|
||||
(oldData: GetImageGenConfigsResponse | undefined) => {
|
||||
if (!oldData) return oldData;
|
||||
return oldData.filter((config) => config.id !== id);
|
||||
return oldData.filter((config) => config.id !== request.id);
|
||||
}
|
||||
);
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.error(error.message || "Failed to delete image model configuration");
|
||||
toast.error(error.message || "Failed to delete image model");
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ import { atomWithMutation } from "jotai-tanstack-query";
|
|||
import { toast } from "sonner";
|
||||
import type {
|
||||
CreateNewLLMConfigRequest,
|
||||
CreateNewLLMConfigResponse,
|
||||
DeleteNewLLMConfigRequest,
|
||||
DeleteNewLLMConfigResponse,
|
||||
GetNewLLMConfigsResponse,
|
||||
UpdateLLMPreferencesRequest,
|
||||
UpdateNewLLMConfigRequest,
|
||||
|
|
@ -25,14 +27,14 @@ export const createNewLLMConfigMutationAtom = atomWithMutation((get) => {
|
|||
mutationFn: async (request: CreateNewLLMConfigRequest) => {
|
||||
return newLLMConfigApiService.createConfig(request);
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.success("Configuration created successfully");
|
||||
onSuccess: (_: CreateNewLLMConfigResponse, request: CreateNewLLMConfigRequest) => {
|
||||
toast.success(`${request.name} created`);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: cacheKeys.newLLMConfigs.all(Number(searchSpaceId)),
|
||||
});
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.error(error.message || "Failed to create configuration");
|
||||
toast.error(error.message || "Failed to create LLM model");
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
@ -50,7 +52,7 @@ export const updateNewLLMConfigMutationAtom = atomWithMutation((get) => {
|
|||
return newLLMConfigApiService.updateConfig(request);
|
||||
},
|
||||
onSuccess: (_: UpdateNewLLMConfigResponse, request: UpdateNewLLMConfigRequest) => {
|
||||
toast.success("Configuration updated successfully");
|
||||
toast.success(`${request.data.name ?? "Configuration"} updated`);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: cacheKeys.newLLMConfigs.all(Number(searchSpaceId)),
|
||||
});
|
||||
|
|
@ -59,7 +61,7 @@ export const updateNewLLMConfigMutationAtom = atomWithMutation((get) => {
|
|||
});
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.error(error.message || "Failed to update configuration");
|
||||
toast.error(error.message || "Failed to update");
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
@ -73,11 +75,14 @@ export const deleteNewLLMConfigMutationAtom = atomWithMutation((get) => {
|
|||
return {
|
||||
mutationKey: ["new-llm-configs", "delete"],
|
||||
enabled: !!searchSpaceId,
|
||||
mutationFn: async (request: DeleteNewLLMConfigRequest) => {
|
||||
return newLLMConfigApiService.deleteConfig(request);
|
||||
mutationFn: async (request: DeleteNewLLMConfigRequest & { name: string }) => {
|
||||
return newLLMConfigApiService.deleteConfig({ id: request.id });
|
||||
},
|
||||
onSuccess: (_, request: DeleteNewLLMConfigRequest) => {
|
||||
toast.success("Configuration deleted successfully");
|
||||
onSuccess: (
|
||||
_: DeleteNewLLMConfigResponse,
|
||||
request: DeleteNewLLMConfigRequest & { name: string }
|
||||
) => {
|
||||
toast.success(`${request.name} deleted`);
|
||||
queryClient.setQueryData(
|
||||
cacheKeys.newLLMConfigs.all(Number(searchSpaceId)),
|
||||
(oldData: GetNewLLMConfigsResponse | undefined) => {
|
||||
|
|
@ -87,7 +92,7 @@ export const deleteNewLLMConfigMutationAtom = atomWithMutation((get) => {
|
|||
);
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.error(error.message || "Failed to delete configuration");
|
||||
toast.error(error.message || "Failed to delete");
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ const initialState: TabsState = {
|
|||
activeTabId: "chat-new",
|
||||
};
|
||||
|
||||
// Prevent race conditions where route-sync recreates a just-deleted chat tab.
|
||||
const deletedChatIdsAtom = atom<Set<number>>(new Set<number>());
|
||||
|
||||
const sessionStorageAdapter = createJSONStorage<TabsState>(
|
||||
() => (typeof window !== "undefined" ? sessionStorage : undefined) as Storage
|
||||
);
|
||||
|
|
@ -71,6 +74,10 @@ export const syncChatTabAtom = atom(
|
|||
set,
|
||||
{ chatId, title, chatUrl }: { chatId: number | null; title?: string; chatUrl?: string }
|
||||
) => {
|
||||
if (chatId && get(deletedChatIdsAtom).has(chatId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const state = get(tabsStateAtom);
|
||||
const tabId = makeChatTabId(chatId);
|
||||
const existing = state.tabs.find((t) => t.id === tabId);
|
||||
|
|
@ -128,6 +135,19 @@ export const updateChatTabTitleAtom = atom(
|
|||
(get, set, { chatId, title }: { chatId: number; title: string }) => {
|
||||
const state = get(tabsStateAtom);
|
||||
const tabId = makeChatTabId(chatId);
|
||||
const hasExactTab = state.tabs.some((t) => t.id === tabId);
|
||||
|
||||
// During lazy thread creation, title updates can arrive before "chat-new"
|
||||
// is swapped to chat-{id}. In that case, promote the active "chat-new" tab.
|
||||
if (!hasExactTab && state.activeTabId === "chat-new") {
|
||||
set(tabsStateAtom, {
|
||||
...state,
|
||||
activeTabId: tabId,
|
||||
tabs: state.tabs.map((t) => (t.id === "chat-new" ? { ...t, id: tabId, chatId, title } : t)),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
set(tabsStateAtom, {
|
||||
...state,
|
||||
tabs: state.tabs.map((t) => (t.id === tabId ? { ...t, title } : t)),
|
||||
|
|
@ -213,7 +233,39 @@ export const closeTabAtom = atom(null, (get, set, tabId: string) => {
|
|||
return remaining.find((t) => t.id === newActiveId) ?? null;
|
||||
});
|
||||
|
||||
/** Remove a chat tab by chat ID (used when a chat is deleted). */
|
||||
export const removeChatTabAtom = atom(null, (get, set, chatId: number) => {
|
||||
const state = get(tabsStateAtom);
|
||||
const tabId = makeChatTabId(chatId);
|
||||
const idx = state.tabs.findIndex((t) => t.id === tabId);
|
||||
if (idx === -1) return null;
|
||||
|
||||
const deletedChatIds = get(deletedChatIdsAtom);
|
||||
set(deletedChatIdsAtom, new Set([...deletedChatIds, chatId]));
|
||||
|
||||
const remaining = state.tabs.filter((t) => t.id !== tabId);
|
||||
|
||||
// Always keep at least one tab available.
|
||||
if (remaining.length === 0) {
|
||||
set(tabsStateAtom, {
|
||||
tabs: [INITIAL_CHAT_TAB],
|
||||
activeTabId: "chat-new",
|
||||
});
|
||||
return INITIAL_CHAT_TAB;
|
||||
}
|
||||
|
||||
let newActiveId = state.activeTabId;
|
||||
if (state.activeTabId === tabId) {
|
||||
const newIdx = Math.min(idx, remaining.length - 1);
|
||||
newActiveId = remaining[newIdx].id;
|
||||
}
|
||||
|
||||
set(tabsStateAtom, { tabs: remaining, activeTabId: newActiveId });
|
||||
return remaining.find((t) => t.id === newActiveId) ?? null;
|
||||
});
|
||||
|
||||
/** Reset tabs when switching search spaces. */
|
||||
export const resetTabsAtom = atom(null, (_get, set) => {
|
||||
set(tabsStateAtom, { ...initialState });
|
||||
set(deletedChatIdsAtom, new Set<number>());
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue