add web
This commit is contained in:
127
web/src/hooks/__tests__/logic-hooks.useScrollToBottom.test.tsx
Normal file
127
web/src/hooks/__tests__/logic-hooks.useScrollToBottom.test.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
jest.mock('eventsource-parser/stream', () => ({}));
|
||||
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { useScrollToBottom } from '../logic-hooks';
|
||||
|
||||
function createMockContainer({ atBottom = true } = {}) {
|
||||
const scrollTop = atBottom ? 100 : 0;
|
||||
const clientHeight = 100;
|
||||
const scrollHeight = 200;
|
||||
const listeners = {};
|
||||
return {
|
||||
current: {
|
||||
scrollTop,
|
||||
clientHeight,
|
||||
scrollHeight,
|
||||
addEventListener: jest.fn((event, cb) => {
|
||||
listeners[event] = cb;
|
||||
}),
|
||||
removeEventListener: jest.fn(),
|
||||
},
|
||||
listeners,
|
||||
} as any;
|
||||
}
|
||||
|
||||
// Helper to flush all timers and microtasks
|
||||
async function flushAll() {
|
||||
jest.runAllTimers();
|
||||
// Flush microtasks
|
||||
await Promise.resolve();
|
||||
// Sometimes, effects queue more timers, so run again
|
||||
jest.runAllTimers();
|
||||
await Promise.resolve();
|
||||
}
|
||||
|
||||
describe('useScrollToBottom', () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should set isAtBottom true when user is at bottom', () => {
|
||||
const containerRef = createMockContainer({ atBottom: true });
|
||||
const { result } = renderHook(() => useScrollToBottom([], containerRef));
|
||||
expect(result.current.isAtBottom).toBe(true);
|
||||
});
|
||||
|
||||
it('should set isAtBottom false when user is not at bottom', () => {
|
||||
const containerRef = createMockContainer({ atBottom: false });
|
||||
const { result } = renderHook(() => useScrollToBottom([], containerRef));
|
||||
expect(result.current.isAtBottom).toBe(false);
|
||||
});
|
||||
|
||||
it('should scroll to bottom when isAtBottom is true and messages change', async () => {
|
||||
const containerRef = createMockContainer({ atBottom: true });
|
||||
const mockScroll = jest.fn();
|
||||
|
||||
function useTestScrollToBottom(messages: any, containerRef: any) {
|
||||
const hook = useScrollToBottom(messages, containerRef);
|
||||
hook.scrollRef.current = { scrollIntoView: mockScroll } as any;
|
||||
return hook;
|
||||
}
|
||||
|
||||
const { rerender } = renderHook(
|
||||
({ messages }) => useTestScrollToBottom(messages, containerRef),
|
||||
{ initialProps: { messages: [] } },
|
||||
);
|
||||
|
||||
rerender({ messages: ['msg1'] });
|
||||
await flushAll();
|
||||
|
||||
expect(mockScroll).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should NOT scroll to bottom when isAtBottom is false and messages change', async () => {
|
||||
const containerRef = createMockContainer({ atBottom: false });
|
||||
const mockScroll = jest.fn();
|
||||
|
||||
function useTestScrollToBottom(messages: any, containerRef: any) {
|
||||
const hook = useScrollToBottom(messages, containerRef);
|
||||
hook.scrollRef.current = { scrollIntoView: mockScroll } as any;
|
||||
console.log('HOOK: isAtBottom:', hook.isAtBottom);
|
||||
return hook;
|
||||
}
|
||||
|
||||
const { result, rerender } = renderHook(
|
||||
({ messages }) => useTestScrollToBottom(messages, containerRef),
|
||||
{ initialProps: { messages: [] } },
|
||||
);
|
||||
|
||||
// Simulate user scrolls up before messages change
|
||||
await act(async () => {
|
||||
containerRef.current.scrollTop = 0;
|
||||
containerRef.current.addEventListener.mock.calls[0][1]();
|
||||
await flushAll();
|
||||
// Advance fake timers by 10ms instead of real setTimeout
|
||||
jest.advanceTimersByTime(10);
|
||||
console.log('AFTER SCROLL: isAtBottom:', result.current.isAtBottom);
|
||||
});
|
||||
|
||||
rerender({ messages: ['msg1'] });
|
||||
await flushAll();
|
||||
|
||||
console.log('AFTER RERENDER: isAtBottom:', result.current.isAtBottom);
|
||||
|
||||
expect(mockScroll).not.toHaveBeenCalled();
|
||||
|
||||
// Optionally, flush again after the assertion to see if it gets called late
|
||||
await flushAll();
|
||||
});
|
||||
|
||||
it('should indicate button should appear when user is not at bottom', () => {
|
||||
const containerRef = createMockContainer({ atBottom: false });
|
||||
const { result } = renderHook(() => useScrollToBottom([], containerRef));
|
||||
// The button should appear in the UI when isAtBottom is false
|
||||
expect(result.current.isAtBottom).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
const originalRAF = global.requestAnimationFrame;
|
||||
beforeAll(() => {
|
||||
global.requestAnimationFrame = (cb) => setTimeout(cb, 0);
|
||||
});
|
||||
afterAll(() => {
|
||||
global.requestAnimationFrame = originalRAF;
|
||||
});
|
||||
54
web/src/hooks/auth-hooks.ts
Normal file
54
web/src/hooks/auth-hooks.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import message from '@/components/ui/message';
|
||||
import authorizationUtil from '@/utils/authorization-util';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useNavigate, useSearchParams } from 'umi';
|
||||
|
||||
export const useOAuthCallback = () => {
|
||||
const [currentQueryParameters, setSearchParams] = useSearchParams();
|
||||
const error = currentQueryParameters.get('error');
|
||||
const newQueryParameters: URLSearchParams = useMemo(
|
||||
() => new URLSearchParams(currentQueryParameters.toString()),
|
||||
[currentQueryParameters],
|
||||
);
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
message.error(error);
|
||||
setTimeout(() => {
|
||||
navigate('/login');
|
||||
newQueryParameters.delete('error');
|
||||
setSearchParams(newQueryParameters);
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
const auth = currentQueryParameters.get('auth');
|
||||
if (auth) {
|
||||
authorizationUtil.setAuthorization(auth);
|
||||
newQueryParameters.delete('auth');
|
||||
setSearchParams(newQueryParameters);
|
||||
navigate('/');
|
||||
}
|
||||
}, [
|
||||
error,
|
||||
currentQueryParameters,
|
||||
newQueryParameters,
|
||||
navigate,
|
||||
setSearchParams,
|
||||
]);
|
||||
|
||||
console.debug(currentQueryParameters.get('auth'));
|
||||
return currentQueryParameters.get('auth');
|
||||
};
|
||||
|
||||
export const useAuth = () => {
|
||||
const auth = useOAuthCallback();
|
||||
const [isLogin, setIsLogin] = useState<Nullable<boolean>>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setIsLogin(!!authorizationUtil.getAuthorization() || !!auth);
|
||||
}, [auth]);
|
||||
|
||||
return { isLogin };
|
||||
};
|
||||
642
web/src/hooks/chat-hooks.ts
Normal file
642
web/src/hooks/chat-hooks.ts
Normal file
@@ -0,0 +1,642 @@
|
||||
import { ChatSearchParams } from '@/constants/chat';
|
||||
import {
|
||||
IConversation,
|
||||
IDialog,
|
||||
IStats,
|
||||
IToken,
|
||||
} from '@/interfaces/database/chat';
|
||||
import {
|
||||
IAskRequestBody,
|
||||
IFeedbackRequestBody,
|
||||
} from '@/interfaces/request/chat';
|
||||
import i18n from '@/locales/config';
|
||||
import { IClientConversation } from '@/pages/chat/interface';
|
||||
import { useGetSharedChatSearchParams } from '@/pages/chat/shared-hooks';
|
||||
import chatService from '@/services/chat-service';
|
||||
import {
|
||||
buildMessageListWithUuid,
|
||||
getConversationId,
|
||||
isConversationIdExist,
|
||||
} from '@/utils/chat';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { message } from 'antd';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import { has, set } from 'lodash';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { history, useSearchParams } from 'umi';
|
||||
|
||||
//#region logic
|
||||
|
||||
export const useClickDialogCard = () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [_, setSearchParams] = useSearchParams();
|
||||
|
||||
const newQueryParameters: URLSearchParams = useMemo(() => {
|
||||
return new URLSearchParams();
|
||||
}, []);
|
||||
|
||||
const handleClickDialog = useCallback(
|
||||
(dialogId: string) => {
|
||||
newQueryParameters.set(ChatSearchParams.DialogId, dialogId);
|
||||
// newQueryParameters.set(
|
||||
// ChatSearchParams.ConversationId,
|
||||
// EmptyConversationId,
|
||||
// );
|
||||
setSearchParams(newQueryParameters);
|
||||
},
|
||||
[newQueryParameters, setSearchParams],
|
||||
);
|
||||
|
||||
return { handleClickDialog };
|
||||
};
|
||||
|
||||
export const useClickConversationCard = () => {
|
||||
const [currentQueryParameters, setSearchParams] = useSearchParams();
|
||||
const newQueryParameters: URLSearchParams = useMemo(
|
||||
() => new URLSearchParams(currentQueryParameters.toString()),
|
||||
[currentQueryParameters],
|
||||
);
|
||||
|
||||
const handleClickConversation = useCallback(
|
||||
(conversationId: string, isNew: string) => {
|
||||
newQueryParameters.set(ChatSearchParams.ConversationId, conversationId);
|
||||
newQueryParameters.set(ChatSearchParams.isNew, isNew);
|
||||
setSearchParams(newQueryParameters);
|
||||
},
|
||||
[setSearchParams, newQueryParameters],
|
||||
);
|
||||
|
||||
return { handleClickConversation };
|
||||
};
|
||||
|
||||
export const useGetChatSearchParams = () => {
|
||||
const [currentQueryParameters] = useSearchParams();
|
||||
|
||||
return {
|
||||
dialogId: currentQueryParameters.get(ChatSearchParams.DialogId) || '',
|
||||
conversationId:
|
||||
currentQueryParameters.get(ChatSearchParams.ConversationId) || '',
|
||||
isNew: currentQueryParameters.get(ChatSearchParams.isNew) || '',
|
||||
};
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region dialog
|
||||
|
||||
export const useFetchNextDialogList = (pureFetch = false) => {
|
||||
const { handleClickDialog } = useClickDialogCard();
|
||||
const { dialogId } = useGetChatSearchParams();
|
||||
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery<IDialog[]>({
|
||||
queryKey: ['fetchDialogList'],
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
refetchOnWindowFocus: false,
|
||||
queryFn: async (...params) => {
|
||||
console.log('🚀 ~ queryFn: ~ params:', params);
|
||||
const { data } = await chatService.listDialog();
|
||||
|
||||
if (data.code === 0) {
|
||||
const list: IDialog[] = data.data;
|
||||
if (!pureFetch) {
|
||||
if (list.length > 0) {
|
||||
if (list.every((x) => x.id !== dialogId)) {
|
||||
handleClickDialog(data.data[0].id);
|
||||
}
|
||||
} else {
|
||||
history.push('/chat');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
|
||||
export const useFetchChatAppList = () => {
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery<IDialog[]>({
|
||||
queryKey: ['fetchChatAppList'],
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
refetchOnWindowFocus: false,
|
||||
queryFn: async () => {
|
||||
const { data } = await chatService.listDialog();
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
|
||||
export const useSetNextDialog = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['setDialog'],
|
||||
mutationFn: async (params: IDialog) => {
|
||||
const { data } = await chatService.setDialog(params);
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({
|
||||
exact: false,
|
||||
queryKey: ['fetchDialogList'],
|
||||
});
|
||||
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['fetchDialog'],
|
||||
});
|
||||
message.success(
|
||||
i18n.t(`message.${params.dialog_id ? 'modified' : 'created'}`),
|
||||
);
|
||||
}
|
||||
return data?.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, setDialog: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchNextDialog = () => {
|
||||
const { dialogId } = useGetChatSearchParams();
|
||||
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery<IDialog>({
|
||||
queryKey: ['fetchDialog', dialogId],
|
||||
gcTime: 0,
|
||||
initialData: {} as IDialog,
|
||||
enabled: !!dialogId,
|
||||
refetchOnWindowFocus: false,
|
||||
queryFn: async () => {
|
||||
const { data } = await chatService.getDialog({ dialogId });
|
||||
|
||||
return data?.data ?? ({} as IDialog);
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
|
||||
export const useFetchManualDialog = () => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['fetchManualDialog'],
|
||||
gcTime: 0,
|
||||
mutationFn: async (dialogId: string) => {
|
||||
const { data } = await chatService.getDialog({ dialogId });
|
||||
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, fetchDialog: mutateAsync };
|
||||
};
|
||||
|
||||
export const useRemoveNextDialog = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['removeDialog'],
|
||||
mutationFn: async (dialogIds: string[]) => {
|
||||
const { data } = await chatService.removeDialog({ dialogIds });
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchDialogList'] });
|
||||
|
||||
message.success(i18n.t('message.deleted'));
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, removeDialog: mutateAsync };
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region conversation
|
||||
|
||||
export const useFetchNextConversationList = () => {
|
||||
const { dialogId } = useGetChatSearchParams();
|
||||
const { handleClickConversation } = useClickConversationCard();
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery<IConversation[]>({
|
||||
queryKey: ['fetchConversationList', dialogId],
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
refetchOnWindowFocus: false,
|
||||
enabled: !!dialogId,
|
||||
queryFn: async () => {
|
||||
const { data } = await chatService.listConversation({ dialogId });
|
||||
if (data.code === 0) {
|
||||
if (data.data.length > 0) {
|
||||
handleClickConversation(data.data[0].id, '');
|
||||
} else {
|
||||
handleClickConversation('', '');
|
||||
}
|
||||
}
|
||||
return data?.data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
|
||||
export const useFetchNextConversation = () => {
|
||||
const { isNew, conversationId } = useGetChatSearchParams();
|
||||
const { sharedId } = useGetSharedChatSearchParams();
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery<IClientConversation>({
|
||||
queryKey: ['fetchConversation', conversationId],
|
||||
initialData: {} as IClientConversation,
|
||||
// enabled: isConversationIdExist(conversationId),
|
||||
gcTime: 0,
|
||||
refetchOnWindowFocus: false,
|
||||
queryFn: async () => {
|
||||
if (
|
||||
isNew !== 'true' &&
|
||||
isConversationIdExist(sharedId || conversationId)
|
||||
) {
|
||||
const { data } = await chatService.getConversation({
|
||||
conversationId: conversationId || sharedId,
|
||||
});
|
||||
|
||||
const conversation = data?.data ?? {};
|
||||
|
||||
const messageList = buildMessageListWithUuid(conversation?.message);
|
||||
|
||||
return { ...conversation, message: messageList };
|
||||
}
|
||||
return { message: [] };
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
|
||||
export const useFetchNextConversationSSE = () => {
|
||||
const { isNew } = useGetChatSearchParams();
|
||||
const { sharedId } = useGetSharedChatSearchParams();
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery<IClientConversation>({
|
||||
queryKey: ['fetchConversationSSE', sharedId],
|
||||
initialData: {} as IClientConversation,
|
||||
gcTime: 0,
|
||||
refetchOnWindowFocus: false,
|
||||
queryFn: async () => {
|
||||
if (isNew !== 'true' && isConversationIdExist(sharedId || '')) {
|
||||
if (!sharedId) return {};
|
||||
const { data } = await chatService.getConversationSSE({}, sharedId);
|
||||
const conversation = data?.data ?? {};
|
||||
const messageList = buildMessageListWithUuid(conversation?.message);
|
||||
return { ...conversation, message: messageList };
|
||||
}
|
||||
return { message: [] };
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
|
||||
export const useFetchManualConversation = () => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['fetchManualConversation'],
|
||||
gcTime: 0,
|
||||
mutationFn: async (conversationId: string) => {
|
||||
const { data } = await chatService.getConversation({ conversationId });
|
||||
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, fetchConversation: mutateAsync };
|
||||
};
|
||||
|
||||
export const useUpdateNextConversation = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['updateConversation'],
|
||||
mutationFn: async (params: Record<string, any>) => {
|
||||
const { data } = await chatService.setConversation({
|
||||
...params,
|
||||
conversation_id: params.conversation_id
|
||||
? params.conversation_id
|
||||
: getConversationId(),
|
||||
});
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchConversationList'] });
|
||||
message.success(i18n.t(`message.modified`));
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, updateConversation: mutateAsync };
|
||||
};
|
||||
|
||||
export const useRemoveNextConversation = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { dialogId } = useGetChatSearchParams();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['removeConversation'],
|
||||
mutationFn: async (conversationIds: string[]) => {
|
||||
const { data } = await chatService.removeConversation({
|
||||
conversationIds,
|
||||
dialogId,
|
||||
});
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchConversationList'] });
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, removeConversation: mutateAsync };
|
||||
};
|
||||
|
||||
export const useDeleteMessage = () => {
|
||||
const { conversationId } = useGetChatSearchParams();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['deleteMessage'],
|
||||
mutationFn: async (messageId: string) => {
|
||||
const { data } = await chatService.deleteMessage({
|
||||
messageId,
|
||||
conversationId,
|
||||
});
|
||||
|
||||
if (data.code === 0) {
|
||||
message.success(i18n.t(`message.deleted`));
|
||||
}
|
||||
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, deleteMessage: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFeedback = () => {
|
||||
const { conversationId } = useGetChatSearchParams();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['feedback'],
|
||||
mutationFn: async (params: IFeedbackRequestBody) => {
|
||||
const { data } = await chatService.thumbup({
|
||||
...params,
|
||||
conversationId,
|
||||
});
|
||||
if (data.code === 0) {
|
||||
message.success(i18n.t(`message.operated`));
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, feedback: mutateAsync };
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
// #region API provided for external calls
|
||||
|
||||
export const useCreateNextToken = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['createToken'],
|
||||
mutationFn: async (params: Record<string, any>) => {
|
||||
const { data } = await chatService.createToken(params);
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchTokenList'] });
|
||||
}
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, createToken: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchTokenList = (params: Record<string, any>) => {
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery<IToken[]>({
|
||||
queryKey: ['fetchTokenList', params],
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await chatService.listToken(params);
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
|
||||
export const useRemoveNextToken = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['removeToken'],
|
||||
mutationFn: async (params: {
|
||||
tenantId: string;
|
||||
dialogId?: string;
|
||||
tokens: string[];
|
||||
}) => {
|
||||
const { data } = await chatService.removeToken(params);
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchTokenList'] });
|
||||
}
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, removeToken: mutateAsync };
|
||||
};
|
||||
|
||||
type RangeValue = [Dayjs | null, Dayjs | null] | null;
|
||||
|
||||
const getDay = (date?: Dayjs) => date?.format('YYYY-MM-DD');
|
||||
|
||||
export const useFetchNextStats = () => {
|
||||
const [pickerValue, setPickerValue] = useState<RangeValue>([
|
||||
dayjs().subtract(7, 'day'),
|
||||
dayjs(),
|
||||
]);
|
||||
const { data, isFetching: loading } = useQuery<IStats>({
|
||||
queryKey: ['fetchStats', pickerValue],
|
||||
initialData: {} as IStats,
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
if (Array.isArray(pickerValue) && pickerValue[0]) {
|
||||
const { data } = await chatService.getStats({
|
||||
fromDate: getDay(pickerValue[0]),
|
||||
toDate: getDay(pickerValue[1] ?? dayjs()),
|
||||
});
|
||||
return data?.data ?? {};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, pickerValue, setPickerValue };
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region shared chat
|
||||
|
||||
export const useCreateNextSharedConversation = () => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['createSharedConversation'],
|
||||
mutationFn: async (userId?: string) => {
|
||||
const { data } = await chatService.createExternalConversation({ userId });
|
||||
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, createSharedConversation: mutateAsync };
|
||||
};
|
||||
|
||||
// deprecated
|
||||
export const useFetchNextSharedConversation = (
|
||||
conversationId?: string | null,
|
||||
) => {
|
||||
const { data, isPending: loading } = useQuery({
|
||||
queryKey: ['fetchSharedConversation'],
|
||||
enabled: !!conversationId,
|
||||
queryFn: async () => {
|
||||
if (!conversationId) {
|
||||
return {};
|
||||
}
|
||||
const { data } = await chatService.getExternalConversation(
|
||||
null,
|
||||
conversationId,
|
||||
);
|
||||
|
||||
const messageList = buildMessageListWithUuid(data?.data?.message);
|
||||
|
||||
set(data, 'data.message', messageList);
|
||||
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region search page
|
||||
|
||||
export const useFetchMindMap = () => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['fetchMindMap'],
|
||||
gcTime: 0,
|
||||
mutationFn: async (params: IAskRequestBody) => {
|
||||
try {
|
||||
const ret = await chatService.getMindMap(params);
|
||||
return ret?.data?.data ?? {};
|
||||
} catch (error: any) {
|
||||
if (has(error, 'message')) {
|
||||
message.error(error.message);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, fetchMindMap: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchRelatedQuestions = () => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['fetchRelatedQuestions'],
|
||||
gcTime: 0,
|
||||
mutationFn: async (question: string): Promise<string[]> => {
|
||||
const { data } = await chatService.getRelatedQuestions({ question });
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, fetchRelatedQuestions: mutateAsync };
|
||||
};
|
||||
//#endregion
|
||||
210
web/src/hooks/chunk-hooks.ts
Normal file
210
web/src/hooks/chunk-hooks.ts
Normal file
@@ -0,0 +1,210 @@
|
||||
import { ResponseGetType, ResponseType } from '@/interfaces/database/base';
|
||||
import { IChunk, IKnowledgeFile } from '@/interfaces/database/knowledge';
|
||||
import kbService from '@/services/knowledge-service';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useDebounce } from 'ahooks';
|
||||
import { PaginationProps, message } from 'antd';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
useGetPaginationWithRouter,
|
||||
useHandleSearchChange,
|
||||
} from './logic-hooks';
|
||||
import {
|
||||
useGetKnowledgeSearchParams,
|
||||
useSetPaginationParams,
|
||||
} from './route-hook';
|
||||
|
||||
export interface IChunkListResult {
|
||||
searchString?: string;
|
||||
handleInputChange?: React.ChangeEventHandler<HTMLInputElement>;
|
||||
pagination: PaginationProps;
|
||||
setPagination?: (pagination: { page: number; pageSize: number }) => void;
|
||||
available: number | undefined;
|
||||
handleSetAvailable: (available: number | undefined) => void;
|
||||
}
|
||||
|
||||
export const useFetchNextChunkList = (): ResponseGetType<{
|
||||
data: IChunk[];
|
||||
total: number;
|
||||
documentInfo: IKnowledgeFile;
|
||||
}> &
|
||||
IChunkListResult => {
|
||||
const { pagination, setPagination } = useGetPaginationWithRouter();
|
||||
const { documentId } = useGetKnowledgeSearchParams();
|
||||
const { searchString, handleInputChange } = useHandleSearchChange();
|
||||
const [available, setAvailable] = useState<number | undefined>();
|
||||
const debouncedSearchString = useDebounce(searchString, { wait: 500 });
|
||||
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: [
|
||||
'fetchChunkList',
|
||||
documentId,
|
||||
pagination.current,
|
||||
pagination.pageSize,
|
||||
debouncedSearchString,
|
||||
available,
|
||||
],
|
||||
placeholderData: (previousData) =>
|
||||
previousData ?? { data: [], total: 0, documentInfo: {} }, // https://github.com/TanStack/query/issues/8183
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await kbService.chunk_list({
|
||||
doc_id: documentId,
|
||||
page: pagination.current,
|
||||
size: pagination.pageSize,
|
||||
available_int: available,
|
||||
keywords: searchString,
|
||||
});
|
||||
if (data.code === 0) {
|
||||
const res = data.data;
|
||||
return {
|
||||
data: res.chunks,
|
||||
total: res.total,
|
||||
documentInfo: res.doc,
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
data?.data ?? {
|
||||
data: [],
|
||||
total: 0,
|
||||
documentInfo: {},
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const onInputChange: React.ChangeEventHandler<HTMLInputElement> = useCallback(
|
||||
(e) => {
|
||||
setPagination({ page: 1 });
|
||||
handleInputChange(e);
|
||||
},
|
||||
[handleInputChange, setPagination],
|
||||
);
|
||||
|
||||
const handleSetAvailable = useCallback(
|
||||
(a: number | undefined) => {
|
||||
setPagination({ page: 1 });
|
||||
setAvailable(a);
|
||||
},
|
||||
[setAvailable, setPagination],
|
||||
);
|
||||
|
||||
return {
|
||||
data,
|
||||
loading,
|
||||
pagination,
|
||||
setPagination,
|
||||
searchString,
|
||||
handleInputChange: onInputChange,
|
||||
available,
|
||||
handleSetAvailable,
|
||||
};
|
||||
};
|
||||
|
||||
export const useSelectChunkList = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const data = queryClient.getQueriesData<{
|
||||
data: IChunk[];
|
||||
total: number;
|
||||
documentInfo: IKnowledgeFile;
|
||||
}>({ queryKey: ['fetchChunkList'] });
|
||||
|
||||
return data?.at(-1)?.[1];
|
||||
};
|
||||
|
||||
export const useDeleteChunk = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { setPaginationParams } = useSetPaginationParams();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['deleteChunk'],
|
||||
mutationFn: async (params: { chunkIds: string[]; doc_id: string }) => {
|
||||
const { data } = await kbService.rm_chunk(params);
|
||||
if (data.code === 0) {
|
||||
setPaginationParams(1);
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchChunkList'] });
|
||||
}
|
||||
return data?.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, deleteChunk: mutateAsync };
|
||||
};
|
||||
|
||||
export const useSwitchChunk = () => {
|
||||
const { t } = useTranslation();
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['switchChunk'],
|
||||
mutationFn: async (params: {
|
||||
chunk_ids?: string[];
|
||||
available_int?: number;
|
||||
doc_id: string;
|
||||
}) => {
|
||||
const { data } = await kbService.switch_chunk(params);
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.modified'));
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchChunkList'] });
|
||||
}
|
||||
return data?.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, switchChunk: mutateAsync };
|
||||
};
|
||||
|
||||
export const useCreateChunk = () => {
|
||||
const { t } = useTranslation();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['createChunk'],
|
||||
mutationFn: async (payload: any) => {
|
||||
let service = kbService.create_chunk;
|
||||
if (payload.chunk_id) {
|
||||
service = kbService.set_chunk;
|
||||
}
|
||||
const { data } = await service(payload);
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.created'));
|
||||
setTimeout(() => {
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchChunkList'] });
|
||||
}, 1000); // Delay to ensure the list is updated
|
||||
}
|
||||
return data?.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, createChunk: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchChunk = (chunkId?: string): ResponseType<any> => {
|
||||
const { data } = useQuery({
|
||||
queryKey: ['fetchChunk'],
|
||||
enabled: !!chunkId,
|
||||
initialData: {},
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const data = await kbService.get_chunk({
|
||||
chunk_id: chunkId,
|
||||
});
|
||||
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
127
web/src/hooks/common-hooks.tsx
Normal file
127
web/src/hooks/common-hooks.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
import { ExclamationCircleFilled } from '@ant-design/icons';
|
||||
import { App } from 'antd';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import { ReactNode, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const useSetModalState = (initialVisible = false) => {
|
||||
const [visible, setVisible] = useState(initialVisible);
|
||||
|
||||
const showModal = useCallback(() => {
|
||||
setVisible(true);
|
||||
}, []);
|
||||
const hideModal = useCallback(() => {
|
||||
setVisible(false);
|
||||
}, []);
|
||||
|
||||
const switchVisible = useCallback(() => {
|
||||
setVisible(!visible);
|
||||
}, [visible]);
|
||||
|
||||
return { visible, showModal, hideModal, switchVisible };
|
||||
};
|
||||
|
||||
export const useDeepCompareEffect = (
|
||||
effect: React.EffectCallback,
|
||||
deps: React.DependencyList,
|
||||
) => {
|
||||
const ref = useRef<React.DependencyList>();
|
||||
let callback: ReturnType<React.EffectCallback> = () => {};
|
||||
if (!isEqual(deps, ref.current)) {
|
||||
callback = effect();
|
||||
ref.current = deps;
|
||||
}
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
|
||||
export interface UseDynamicSVGImportOptions {
|
||||
onCompleted?: (
|
||||
name: string,
|
||||
SvgIcon: React.FC<React.SVGProps<SVGSVGElement>> | undefined,
|
||||
) => void;
|
||||
onError?: (err: Error) => void;
|
||||
}
|
||||
|
||||
export function useDynamicSVGImport(
|
||||
name: string,
|
||||
options: UseDynamicSVGImportOptions = {},
|
||||
) {
|
||||
const ImportedIconRef = useRef<React.FC<React.SVGProps<SVGSVGElement>>>();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<Error>();
|
||||
|
||||
const { onCompleted, onError } = options;
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
const importIcon = async (): Promise<void> => {
|
||||
try {
|
||||
ImportedIconRef.current = (await import(name)).ReactComponent;
|
||||
onCompleted?.(name, ImportedIconRef.current);
|
||||
} catch (err: any) {
|
||||
onError?.(err);
|
||||
setError(err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
importIcon();
|
||||
}, [name, onCompleted, onError]);
|
||||
|
||||
return { error, loading, SvgIcon: ImportedIconRef.current };
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
title?: string;
|
||||
content?: ReactNode;
|
||||
onOk?: (...args: any[]) => any;
|
||||
onCancel?: (...args: any[]) => any;
|
||||
}
|
||||
|
||||
export const useShowDeleteConfirm = () => {
|
||||
const { modal } = App.useApp();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const showDeleteConfirm = useCallback(
|
||||
({ title, content, onOk, onCancel }: IProps): Promise<number> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
modal.confirm({
|
||||
title: title ?? t('common.deleteModalTitle'),
|
||||
icon: <ExclamationCircleFilled />,
|
||||
content,
|
||||
okText: t('common.yes'),
|
||||
okType: 'danger',
|
||||
cancelText: t('common.no'),
|
||||
async onOk() {
|
||||
try {
|
||||
const ret = await onOk?.();
|
||||
resolve(ret);
|
||||
console.info(ret);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
},
|
||||
onCancel() {
|
||||
onCancel?.();
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
[t, modal],
|
||||
);
|
||||
|
||||
return showDeleteConfirm;
|
||||
};
|
||||
|
||||
export const useTranslate = (keyPrefix: string) => {
|
||||
return useTranslation('translation', { keyPrefix });
|
||||
};
|
||||
|
||||
export const useCommonTranslation = () => {
|
||||
return useTranslation('translation', { keyPrefix: 'common' });
|
||||
};
|
||||
516
web/src/hooks/document-hooks.ts
Normal file
516
web/src/hooks/document-hooks.ts
Normal file
@@ -0,0 +1,516 @@
|
||||
import { IReferenceChunk } from '@/interfaces/database/chat';
|
||||
import { IDocumentInfo } from '@/interfaces/database/document';
|
||||
import { IChunk } from '@/interfaces/database/knowledge';
|
||||
import {
|
||||
IChangeParserConfigRequestBody,
|
||||
IDocumentMetaRequestBody,
|
||||
} from '@/interfaces/request/document';
|
||||
import i18n from '@/locales/config';
|
||||
import chatService from '@/services/chat-service';
|
||||
import kbService, { listDocument } from '@/services/knowledge-service';
|
||||
import api, { api_host } from '@/utils/api';
|
||||
import { buildChunkHighlights } from '@/utils/document-util';
|
||||
import { post } from '@/utils/request';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { UploadFile, message } from 'antd';
|
||||
import { get } from 'lodash';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { IHighlight } from 'react-pdf-highlighter';
|
||||
import { useParams } from 'umi';
|
||||
import {
|
||||
useGetPaginationWithRouter,
|
||||
useHandleSearchChange,
|
||||
} from './logic-hooks';
|
||||
import {
|
||||
useGetKnowledgeSearchParams,
|
||||
useSetPaginationParams,
|
||||
} from './route-hook';
|
||||
|
||||
export const useGetDocumentUrl = (documentId?: string) => {
|
||||
const getDocumentUrl = useCallback(
|
||||
(id?: string) => {
|
||||
return `${api_host}/document/get/${documentId || id}`;
|
||||
},
|
||||
[documentId],
|
||||
);
|
||||
|
||||
return getDocumentUrl;
|
||||
};
|
||||
|
||||
export const useGetChunkHighlights = (
|
||||
selectedChunk: IChunk | IReferenceChunk,
|
||||
) => {
|
||||
const [size, setSize] = useState({ width: 849, height: 1200 });
|
||||
|
||||
const highlights: IHighlight[] = useMemo(() => {
|
||||
return buildChunkHighlights(selectedChunk, size);
|
||||
}, [selectedChunk, size]);
|
||||
|
||||
const setWidthAndHeight = (width: number, height: number) => {
|
||||
setSize((pre) => {
|
||||
if (pre.height !== height || pre.width !== width) {
|
||||
return { height, width };
|
||||
}
|
||||
return pre;
|
||||
});
|
||||
};
|
||||
|
||||
return { highlights, setWidthAndHeight };
|
||||
};
|
||||
|
||||
export const useFetchNextDocumentList = () => {
|
||||
const { knowledgeId } = useGetKnowledgeSearchParams();
|
||||
const { searchString, handleInputChange } = useHandleSearchChange();
|
||||
const { pagination, setPagination } = useGetPaginationWithRouter();
|
||||
const { id } = useParams();
|
||||
|
||||
const { data, isFetching: loading } = useQuery<{
|
||||
docs: IDocumentInfo[];
|
||||
total: number;
|
||||
}>({
|
||||
queryKey: ['fetchDocumentList', searchString, pagination],
|
||||
initialData: { docs: [], total: 0 },
|
||||
refetchInterval: 15000,
|
||||
enabled: !!knowledgeId || !!id,
|
||||
queryFn: async () => {
|
||||
const ret = await listDocument({
|
||||
kb_id: knowledgeId || id,
|
||||
keywords: searchString,
|
||||
page_size: pagination.pageSize,
|
||||
page: pagination.current,
|
||||
});
|
||||
if (ret.data.code === 0) {
|
||||
return ret.data.data;
|
||||
}
|
||||
|
||||
return {
|
||||
docs: [],
|
||||
total: 0,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const onInputChange: React.ChangeEventHandler<HTMLInputElement> = useCallback(
|
||||
(e) => {
|
||||
setPagination({ page: 1 });
|
||||
handleInputChange(e);
|
||||
},
|
||||
[handleInputChange, setPagination],
|
||||
);
|
||||
|
||||
return {
|
||||
loading,
|
||||
searchString,
|
||||
documents: data.docs,
|
||||
pagination: { ...pagination, total: data?.total },
|
||||
handleInputChange: onInputChange,
|
||||
setPagination,
|
||||
};
|
||||
};
|
||||
|
||||
export const useSetNextDocumentStatus = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['updateDocumentStatus'],
|
||||
mutationFn: async ({
|
||||
status,
|
||||
documentId,
|
||||
}: {
|
||||
status: boolean;
|
||||
documentId: string | string[];
|
||||
}) => {
|
||||
const ids = Array.isArray(documentId) ? documentId : [documentId];
|
||||
const { data } = await kbService.document_change_status({
|
||||
doc_ids: ids,
|
||||
status: Number(status),
|
||||
});
|
||||
if (data.code === 0) {
|
||||
message.success(i18n.t('message.modified'));
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchDocumentList'] });
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { setDocumentStatus: mutateAsync, data, loading };
|
||||
};
|
||||
|
||||
export const useSaveNextDocumentName = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['saveDocumentName'],
|
||||
mutationFn: async ({
|
||||
name,
|
||||
documentId,
|
||||
}: {
|
||||
name: string;
|
||||
documentId: string;
|
||||
}) => {
|
||||
const { data } = await kbService.document_rename({
|
||||
doc_id: documentId,
|
||||
name: name,
|
||||
});
|
||||
if (data.code === 0) {
|
||||
message.success(i18n.t('message.renamed'));
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchDocumentList'] });
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { loading, saveName: mutateAsync, data };
|
||||
};
|
||||
|
||||
export const useCreateNextDocument = () => {
|
||||
const { knowledgeId } = useGetKnowledgeSearchParams();
|
||||
const { setPaginationParams, page } = useSetPaginationParams();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['createDocument'],
|
||||
mutationFn: async (name: string) => {
|
||||
const { data } = await kbService.document_create({
|
||||
name,
|
||||
kb_id: knowledgeId,
|
||||
});
|
||||
if (data.code === 0) {
|
||||
if (page === 1) {
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchDocumentList'] });
|
||||
} else {
|
||||
setPaginationParams(); // fetch document list
|
||||
}
|
||||
|
||||
message.success(i18n.t('message.created'));
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { createDocument: mutateAsync, loading, data };
|
||||
};
|
||||
|
||||
export const useSetNextDocumentParser = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['setDocumentParser'],
|
||||
mutationFn: async ({
|
||||
parserId,
|
||||
documentId,
|
||||
parserConfig,
|
||||
}: {
|
||||
parserId: string;
|
||||
documentId: string;
|
||||
parserConfig: IChangeParserConfigRequestBody;
|
||||
}) => {
|
||||
const { data } = await kbService.document_change_parser({
|
||||
parser_id: parserId,
|
||||
doc_id: documentId,
|
||||
parser_config: parserConfig,
|
||||
});
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchDocumentList'] });
|
||||
|
||||
message.success(i18n.t('message.modified'));
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { setDocumentParser: mutateAsync, data, loading };
|
||||
};
|
||||
|
||||
export const useUploadNextDocument = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { knowledgeId } = useGetKnowledgeSearchParams();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['uploadDocument'],
|
||||
mutationFn: async (fileList: UploadFile[]) => {
|
||||
const formData = new FormData();
|
||||
formData.append('kb_id', knowledgeId);
|
||||
fileList.forEach((file: any) => {
|
||||
formData.append('file', file);
|
||||
});
|
||||
|
||||
try {
|
||||
const ret = await kbService.document_upload(formData);
|
||||
const code = get(ret, 'data.code');
|
||||
|
||||
if (code === 0 || code === 500) {
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchDocumentList'] });
|
||||
}
|
||||
return ret?.data;
|
||||
} catch (error) {
|
||||
console.warn(error);
|
||||
return {
|
||||
code: 500,
|
||||
message: error + '',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return { uploadDocument: mutateAsync, loading, data };
|
||||
};
|
||||
|
||||
export const useNextWebCrawl = () => {
|
||||
const { knowledgeId } = useGetKnowledgeSearchParams();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['webCrawl'],
|
||||
mutationFn: async ({ name, url }: { name: string; url: string }) => {
|
||||
const formData = new FormData();
|
||||
formData.append('name', name);
|
||||
formData.append('url', url);
|
||||
formData.append('kb_id', knowledgeId);
|
||||
|
||||
const ret = await kbService.web_crawl(formData);
|
||||
const code = get(ret, 'data.code');
|
||||
if (code === 0) {
|
||||
message.success(i18n.t('message.uploaded'));
|
||||
}
|
||||
|
||||
return code;
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
data,
|
||||
loading,
|
||||
webCrawl: mutateAsync,
|
||||
};
|
||||
};
|
||||
|
||||
export const useRunNextDocument = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['runDocumentByIds'],
|
||||
mutationFn: async ({
|
||||
documentIds,
|
||||
run,
|
||||
shouldDelete,
|
||||
}: {
|
||||
documentIds: string[];
|
||||
run: number;
|
||||
shouldDelete: boolean;
|
||||
}) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['fetchDocumentList'],
|
||||
});
|
||||
|
||||
const ret = await kbService.document_run({
|
||||
doc_ids: documentIds,
|
||||
run,
|
||||
delete: shouldDelete,
|
||||
});
|
||||
const code = get(ret, 'data.code');
|
||||
if (code === 0) {
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchDocumentList'] });
|
||||
message.success(i18n.t('message.operated'));
|
||||
}
|
||||
|
||||
return code;
|
||||
},
|
||||
});
|
||||
|
||||
return { runDocumentByIds: mutateAsync, loading, data };
|
||||
};
|
||||
|
||||
export const useFetchDocumentInfosByIds = () => {
|
||||
const [ids, setDocumentIds] = useState<string[]>([]);
|
||||
|
||||
const idList = useMemo(() => {
|
||||
return ids.filter((x) => typeof x === 'string' && x !== '');
|
||||
}, [ids]);
|
||||
|
||||
const { data } = useQuery<IDocumentInfo[]>({
|
||||
queryKey: ['fetchDocumentInfos', idList],
|
||||
enabled: idList.length > 0,
|
||||
initialData: [],
|
||||
queryFn: async () => {
|
||||
const { data } = await kbService.document_infos({ doc_ids: idList });
|
||||
if (data.code === 0) {
|
||||
return data.data;
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, setDocumentIds };
|
||||
};
|
||||
|
||||
export const useFetchDocumentThumbnailsByIds = () => {
|
||||
const [ids, setDocumentIds] = useState<string[]>([]);
|
||||
const { data } = useQuery<Record<string, string>>({
|
||||
queryKey: ['fetchDocumentThumbnails', ids],
|
||||
enabled: ids.length > 0,
|
||||
initialData: {},
|
||||
queryFn: async () => {
|
||||
const { data } = await kbService.document_thumbnails({ doc_ids: ids });
|
||||
if (data.code === 0) {
|
||||
return data.data;
|
||||
}
|
||||
return {};
|
||||
},
|
||||
});
|
||||
|
||||
return { data, setDocumentIds };
|
||||
};
|
||||
|
||||
export const useRemoveNextDocument = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['removeDocument'],
|
||||
mutationFn: async (documentIds: string | string[]) => {
|
||||
const { data } = await kbService.document_rm({ doc_id: documentIds });
|
||||
if (data.code === 0) {
|
||||
message.success(i18n.t('message.deleted'));
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchDocumentList'] });
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, removeDocument: mutateAsync };
|
||||
};
|
||||
|
||||
export const useDeleteDocument = () => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['deleteDocument'],
|
||||
mutationFn: async (documentIds: string[]) => {
|
||||
const data = await kbService.document_delete({ doc_ids: documentIds });
|
||||
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, deleteDocument: mutateAsync };
|
||||
};
|
||||
|
||||
export const useUploadAndParseDocument = (uploadMethod: string) => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['uploadAndParseDocument'],
|
||||
mutationFn: async ({
|
||||
conversationId,
|
||||
fileList,
|
||||
}: {
|
||||
conversationId: string;
|
||||
fileList: UploadFile[];
|
||||
}) => {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('conversation_id', conversationId);
|
||||
fileList.forEach((file: UploadFile) => {
|
||||
formData.append('file', file as any);
|
||||
});
|
||||
if (uploadMethod === 'upload_and_parse') {
|
||||
const data = await kbService.upload_and_parse(formData);
|
||||
return data?.data;
|
||||
}
|
||||
const data = await chatService.uploadAndParseExternal(formData);
|
||||
return data?.data;
|
||||
} catch (error) {}
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, uploadAndParseDocument: mutateAsync };
|
||||
};
|
||||
|
||||
export const useParseDocument = () => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['parseDocument'],
|
||||
mutationFn: async (url: string) => {
|
||||
try {
|
||||
const data = await post(api.parse, { url });
|
||||
if (data?.code === 0) {
|
||||
message.success(i18n.t('message.uploaded'));
|
||||
}
|
||||
return data;
|
||||
} catch (error) {
|
||||
message.error('error');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return { parseDocument: mutateAsync, data, loading };
|
||||
};
|
||||
|
||||
export const useSetDocumentMeta = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['setDocumentMeta'],
|
||||
mutationFn: async (params: IDocumentMetaRequestBody) => {
|
||||
try {
|
||||
const { data } = await kbService.setMeta({
|
||||
meta: params.meta,
|
||||
doc_id: params.documentId,
|
||||
});
|
||||
|
||||
if (data?.code === 0) {
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchDocumentList'] });
|
||||
|
||||
message.success(i18n.t('message.modified'));
|
||||
}
|
||||
return data?.code;
|
||||
} catch (error) {
|
||||
message.error('error');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return { setDocumentMeta: mutateAsync, data, loading };
|
||||
};
|
||||
293
web/src/hooks/file-manager-hooks.ts
Normal file
293
web/src/hooks/file-manager-hooks.ts
Normal file
@@ -0,0 +1,293 @@
|
||||
import message from '@/components/ui/message';
|
||||
import { ResponseType } from '@/interfaces/database/base';
|
||||
import { IFolder } from '@/interfaces/database/file-manager';
|
||||
import { IConnectRequestBody } from '@/interfaces/request/file-manager';
|
||||
import fileManagerService from '@/services/file-manager-service';
|
||||
import { downloadFileFromBlob } from '@/utils/file-util';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { PaginationProps, UploadFile } from 'antd';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSearchParams } from 'umi';
|
||||
import {
|
||||
useGetPaginationWithRouter,
|
||||
useHandleSearchChange,
|
||||
} from './logic-hooks';
|
||||
import { useSetPaginationParams } from './route-hook';
|
||||
|
||||
export const useGetFolderId = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const id = searchParams.get('folderId') as string;
|
||||
|
||||
return id ?? '';
|
||||
};
|
||||
|
||||
export interface IListResult {
|
||||
searchString: string;
|
||||
handleInputChange: React.ChangeEventHandler<HTMLInputElement>;
|
||||
pagination: PaginationProps;
|
||||
setPagination: (pagination: { page: number; pageSize: number }) => void;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export const useFetchPureFileList = () => {
|
||||
const { mutateAsync, isPending: loading } = useMutation({
|
||||
mutationKey: ['fetchPureFileList'],
|
||||
gcTime: 0,
|
||||
|
||||
mutationFn: async (parentId: string) => {
|
||||
const { data } = await fileManagerService.listFile({
|
||||
parent_id: parentId,
|
||||
});
|
||||
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { loading, fetchList: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchFileList = (): ResponseType<any> & IListResult => {
|
||||
const { searchString, handleInputChange } = useHandleSearchChange();
|
||||
const { pagination, setPagination } = useGetPaginationWithRouter();
|
||||
const id = useGetFolderId();
|
||||
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: [
|
||||
'fetchFileList',
|
||||
{
|
||||
id,
|
||||
searchString,
|
||||
...pagination,
|
||||
},
|
||||
],
|
||||
initialData: {},
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await fileManagerService.listFile({
|
||||
parent_id: id,
|
||||
keywords: searchString,
|
||||
page_size: pagination.pageSize,
|
||||
page: pagination.current,
|
||||
});
|
||||
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
const onInputChange: React.ChangeEventHandler<HTMLInputElement> = useCallback(
|
||||
(e) => {
|
||||
setPagination({ page: 1 });
|
||||
handleInputChange(e);
|
||||
},
|
||||
[handleInputChange, setPagination],
|
||||
);
|
||||
|
||||
return {
|
||||
...data,
|
||||
searchString,
|
||||
handleInputChange: onInputChange,
|
||||
pagination: { ...pagination, total: data?.data?.total },
|
||||
setPagination,
|
||||
loading,
|
||||
};
|
||||
};
|
||||
|
||||
export const useDeleteFile = () => {
|
||||
const { setPaginationParams } = useSetPaginationParams();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['deleteFile'],
|
||||
mutationFn: async (params: { fileIds: string[]; parentId: string }) => {
|
||||
const { data } = await fileManagerService.removeFile(params);
|
||||
if (data.code === 0) {
|
||||
setPaginationParams(1); // TODO: There should be a better way to paginate the request list
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchFileList'] });
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, deleteFile: mutateAsync };
|
||||
};
|
||||
|
||||
export const useDownloadFile = () => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['downloadFile'],
|
||||
mutationFn: async (params: { id: string; filename?: string }) => {
|
||||
const response = await fileManagerService.getFile({}, params.id);
|
||||
const blob = new Blob([response.data], { type: response.data.type });
|
||||
downloadFileFromBlob(blob, params.filename);
|
||||
},
|
||||
});
|
||||
return { data, loading, downloadFile: mutateAsync };
|
||||
};
|
||||
|
||||
export const useRenameFile = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['renameFile'],
|
||||
mutationFn: async (params: { fileId: string; name: string }) => {
|
||||
const { data } = await fileManagerService.renameFile(params);
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.renamed'));
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchFileList'] });
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, renameFile: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchParentFolderList = (): IFolder[] => {
|
||||
const id = useGetFolderId();
|
||||
const { data } = useQuery({
|
||||
queryKey: ['fetchParentFolderList', id],
|
||||
initialData: [],
|
||||
enabled: !!id,
|
||||
queryFn: async () => {
|
||||
const { data } = await fileManagerService.getAllParentFolder({
|
||||
fileId: id,
|
||||
});
|
||||
|
||||
return data?.data?.parent_folders?.toReversed() ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const useCreateFolder = () => {
|
||||
const { setPaginationParams } = useSetPaginationParams();
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['createFolder'],
|
||||
mutationFn: async (params: { parentId: string; name: string }) => {
|
||||
const { data } = await fileManagerService.createFolder({
|
||||
...params,
|
||||
type: 'folder',
|
||||
});
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.created'));
|
||||
setPaginationParams(1);
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchFileList'] });
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, createFolder: mutateAsync };
|
||||
};
|
||||
|
||||
export const useUploadFile = () => {
|
||||
const { setPaginationParams } = useSetPaginationParams();
|
||||
const { t } = useTranslation();
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['uploadFile'],
|
||||
mutationFn: async (params: {
|
||||
fileList: UploadFile[];
|
||||
parentId: string;
|
||||
}) => {
|
||||
const fileList = params.fileList;
|
||||
const pathList = params.fileList.map(
|
||||
(file) => (file as any).webkitRelativePath,
|
||||
);
|
||||
const formData = new FormData();
|
||||
formData.append('parent_id', params.parentId);
|
||||
fileList.forEach((file: any, index: number) => {
|
||||
formData.append('file', file);
|
||||
formData.append('path', pathList[index]);
|
||||
});
|
||||
try {
|
||||
const ret = await fileManagerService.uploadFile(formData);
|
||||
if (ret?.data.code === 0) {
|
||||
message.success(t('message.uploaded'));
|
||||
setPaginationParams(1);
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchFileList'] });
|
||||
}
|
||||
return ret?.data?.code;
|
||||
} catch (error) {
|
||||
console.log('🚀 ~ useUploadFile ~ error:', error);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, uploadFile: mutateAsync };
|
||||
};
|
||||
|
||||
export const useConnectToKnowledge = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['connectFileToKnowledge'],
|
||||
mutationFn: async (params: IConnectRequestBody) => {
|
||||
const { data } = await fileManagerService.connectFileToKnowledge(params);
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.operated'));
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchFileList'] });
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, connectFileToKnowledge: mutateAsync };
|
||||
};
|
||||
|
||||
export interface IMoveFileBody {
|
||||
src_file_ids: string[];
|
||||
dest_file_id: string; // target folder id
|
||||
}
|
||||
|
||||
export const useMoveFile = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['moveFile'],
|
||||
mutationFn: async (params: IMoveFileBody) => {
|
||||
const { data } = await fileManagerService.moveFile(params);
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.operated'));
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchFileList'] });
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, moveFile: mutateAsync };
|
||||
};
|
||||
316
web/src/hooks/flow-hooks.ts
Normal file
316
web/src/hooks/flow-hooks.ts
Normal file
@@ -0,0 +1,316 @@
|
||||
import { DSL, IFlow } from '@/interfaces/database/flow';
|
||||
import { IDebugSingleRequestBody } from '@/interfaces/request/flow';
|
||||
import i18n from '@/locales/config';
|
||||
import { useGetSharedChatSearchParams } from '@/pages/chat/shared-hooks';
|
||||
import flowService from '@/services/flow-service';
|
||||
import { buildMessageListWithUuid } from '@/utils/chat';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { message } from 'antd';
|
||||
import { set } from 'lodash';
|
||||
import get from 'lodash/get';
|
||||
import { useParams } from 'umi';
|
||||
|
||||
export const useFetchFlowList = (): { data: IFlow[]; loading: boolean } => {
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: ['fetchFlowList'],
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await flowService.listCanvas();
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useFetchListVersion = (
|
||||
canvas_id: string,
|
||||
): {
|
||||
data: {
|
||||
created_at: string;
|
||||
title: string;
|
||||
id: string;
|
||||
}[];
|
||||
loading: boolean;
|
||||
} => {
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: ['fetchListVersion'],
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await flowService.getListVersion({}, canvas_id);
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useFetchVersion = (
|
||||
version_id?: string,
|
||||
): {
|
||||
data?: IFlow;
|
||||
loading: boolean;
|
||||
} => {
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: ['fetchVersion', version_id],
|
||||
initialData: undefined,
|
||||
gcTime: 0,
|
||||
enabled: !!version_id, // Only call API when both values are provided
|
||||
queryFn: async () => {
|
||||
if (!version_id) return undefined;
|
||||
|
||||
const { data } = await flowService.getVersion({}, version_id);
|
||||
|
||||
return data?.data ?? undefined;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useFetchFlow = (): {
|
||||
data: IFlow;
|
||||
loading: boolean;
|
||||
refetch: () => void;
|
||||
} => {
|
||||
const { id } = useParams();
|
||||
const { sharedId } = useGetSharedChatSearchParams();
|
||||
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery({
|
||||
queryKey: ['flowDetail'],
|
||||
initialData: {} as IFlow,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await flowService.getCanvas({}, sharedId || id);
|
||||
|
||||
const messageList = buildMessageListWithUuid(
|
||||
get(data, 'data.dsl.messages', []),
|
||||
);
|
||||
set(data, 'data.dsl.messages', messageList);
|
||||
|
||||
return data?.data ?? {};
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
|
||||
export const useSettingFlow = () => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['SettingFlow'],
|
||||
mutationFn: async (params: any) => {
|
||||
const ret = await flowService.settingCanvas(params);
|
||||
if (ret?.data?.code === 0) {
|
||||
message.success('success');
|
||||
} else {
|
||||
message.error(ret?.data?.data);
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, settingFlow: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchFlowSSE = (): {
|
||||
data: IFlow;
|
||||
loading: boolean;
|
||||
refetch: () => void;
|
||||
} => {
|
||||
const { sharedId } = useGetSharedChatSearchParams();
|
||||
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery({
|
||||
queryKey: ['flowDetailSSE'],
|
||||
initialData: {} as IFlow,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
if (!sharedId) return {};
|
||||
const { data } = await flowService.getCanvasSSE({}, sharedId);
|
||||
|
||||
const messageList = buildMessageListWithUuid(
|
||||
get(data, 'data.dsl.messages', []),
|
||||
);
|
||||
set(data, 'data.dsl.messages', messageList);
|
||||
|
||||
return data?.data ?? {};
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
|
||||
export const useSetFlow = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['setFlow'],
|
||||
mutationFn: async (params: {
|
||||
id?: string;
|
||||
title?: string;
|
||||
dsl?: DSL;
|
||||
avatar?: string;
|
||||
}) => {
|
||||
const { data = {} } = await flowService.setCanvas(params);
|
||||
if (data.code === 0) {
|
||||
message.success(
|
||||
i18n.t(`message.${params?.id ? 'modified' : 'created'}`),
|
||||
);
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchFlowList'] });
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, setFlow: mutateAsync };
|
||||
};
|
||||
|
||||
export const useDeleteFlow = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['deleteFlow'],
|
||||
mutationFn: async (canvasIds: string[]) => {
|
||||
const { data } = await flowService.removeCanvas({ canvasIds });
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['infiniteFetchFlowListTeam'],
|
||||
});
|
||||
}
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, deleteFlow: mutateAsync };
|
||||
};
|
||||
|
||||
export const useRunFlow = () => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['runFlow'],
|
||||
mutationFn: async (params: { id: string; dsl: DSL }) => {
|
||||
const { data } = await flowService.runCanvas(params);
|
||||
if (data.code === 0) {
|
||||
message.success(i18n.t(`message.modified`));
|
||||
}
|
||||
return data?.data ?? {};
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, runFlow: mutateAsync };
|
||||
};
|
||||
|
||||
export const useResetFlow = () => {
|
||||
const { id } = useParams();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['resetFlow'],
|
||||
mutationFn: async () => {
|
||||
const { data } = await flowService.resetCanvas({ id });
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, resetFlow: mutateAsync };
|
||||
};
|
||||
|
||||
export const useTestDbConnect = () => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['testDbConnect'],
|
||||
mutationFn: async (params: any) => {
|
||||
const ret = await flowService.testDbConnect(params);
|
||||
if (ret?.data?.code === 0) {
|
||||
message.success(ret?.data?.data);
|
||||
} else {
|
||||
message.error(ret?.data?.data);
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, testDbConnect: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchInputElements = (componentId?: string) => {
|
||||
const { id } = useParams();
|
||||
|
||||
const { data, isPending: loading } = useQuery({
|
||||
queryKey: ['fetchInputElements', id, componentId],
|
||||
initialData: [],
|
||||
enabled: !!id && !!componentId,
|
||||
retryOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
try {
|
||||
const { data } = await flowService.getInputElements({
|
||||
id,
|
||||
component_id: componentId,
|
||||
});
|
||||
return data?.data ?? [];
|
||||
} catch (error) {
|
||||
console.log('🚀 ~ queryFn: ~ error:', error);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useDebugSingle = () => {
|
||||
const { id } = useParams();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['debugSingle'],
|
||||
mutationFn: async (params: IDebugSingleRequestBody) => {
|
||||
const ret = await flowService.debugSingle({ id, ...params });
|
||||
if (ret?.data?.code !== 0) {
|
||||
message.error(ret?.data?.message);
|
||||
}
|
||||
return ret?.data?.data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, debugSingle: mutateAsync };
|
||||
};
|
||||
498
web/src/hooks/knowledge-hooks.ts
Normal file
498
web/src/hooks/knowledge-hooks.ts
Normal file
@@ -0,0 +1,498 @@
|
||||
import { ResponsePostType } from '@/interfaces/database/base';
|
||||
import {
|
||||
IKnowledge,
|
||||
IKnowledgeGraph,
|
||||
IRenameTag,
|
||||
ITestingResult,
|
||||
} from '@/interfaces/database/knowledge';
|
||||
import i18n from '@/locales/config';
|
||||
import kbService, {
|
||||
deleteKnowledgeGraph,
|
||||
getKnowledgeGraph,
|
||||
listDataset,
|
||||
listTag,
|
||||
removeTag,
|
||||
renameTag,
|
||||
} from '@/services/knowledge-service';
|
||||
import {
|
||||
useInfiniteQuery,
|
||||
useIsMutating,
|
||||
useMutation,
|
||||
useMutationState,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query';
|
||||
import { useDebounce } from 'ahooks';
|
||||
import { message } from 'antd';
|
||||
import { useState } from 'react';
|
||||
import { useParams, useSearchParams } from 'umi';
|
||||
import { useHandleSearchChange } from './logic-hooks';
|
||||
import { useSetPaginationParams } from './route-hook';
|
||||
|
||||
export const useKnowledgeBaseId = (): string => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const { id } = useParams();
|
||||
const knowledgeBaseId = searchParams.get('id') || id;
|
||||
|
||||
return knowledgeBaseId || '';
|
||||
};
|
||||
|
||||
export const useFetchKnowledgeBaseConfiguration = () => {
|
||||
const knowledgeBaseId = useKnowledgeBaseId();
|
||||
|
||||
const { data, isFetching: loading } = useQuery<IKnowledge>({
|
||||
queryKey: ['fetchKnowledgeDetail'],
|
||||
initialData: {} as IKnowledge,
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await kbService.get_kb_detail({
|
||||
kb_id: knowledgeBaseId,
|
||||
});
|
||||
return data?.data ?? {};
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useFetchKnowledgeList = (
|
||||
shouldFilterListWithoutDocument: boolean = false,
|
||||
): {
|
||||
list: IKnowledge[];
|
||||
loading: boolean;
|
||||
} => {
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: ['fetchKnowledgeList'],
|
||||
initialData: [],
|
||||
gcTime: 0, // https://tanstack.com/query/latest/docs/framework/react/guides/caching?from=reactQueryV3
|
||||
queryFn: async () => {
|
||||
const { data } = await listDataset();
|
||||
const list = data?.data?.kbs ?? [];
|
||||
return shouldFilterListWithoutDocument
|
||||
? list.filter((x: IKnowledge) => x.chunk_num > 0)
|
||||
: list;
|
||||
},
|
||||
});
|
||||
|
||||
return { list: data, loading };
|
||||
};
|
||||
|
||||
export const useSelectKnowledgeOptions = () => {
|
||||
const { list } = useFetchKnowledgeList();
|
||||
|
||||
const options = list?.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}));
|
||||
|
||||
return options;
|
||||
};
|
||||
|
||||
export const useInfiniteFetchKnowledgeList = () => {
|
||||
const { searchString, handleInputChange } = useHandleSearchChange();
|
||||
const debouncedSearchString = useDebounce(searchString, { wait: 500 });
|
||||
|
||||
const PageSize = 30;
|
||||
|
||||
const {
|
||||
data,
|
||||
error,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
isFetching,
|
||||
isFetchingNextPage,
|
||||
status,
|
||||
} = useInfiniteQuery({
|
||||
queryKey: ['infiniteFetchKnowledgeList', debouncedSearchString],
|
||||
queryFn: async ({ pageParam }) => {
|
||||
const { data } = await listDataset({
|
||||
page: pageParam,
|
||||
page_size: PageSize,
|
||||
keywords: debouncedSearchString,
|
||||
});
|
||||
const list = data?.data ?? [];
|
||||
return list;
|
||||
},
|
||||
initialPageParam: 1,
|
||||
getNextPageParam: (lastPage, pages, lastPageParam) => {
|
||||
if (lastPageParam * PageSize <= lastPage.total) {
|
||||
return lastPageParam + 1;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
});
|
||||
return {
|
||||
data,
|
||||
loading: isFetching,
|
||||
error,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
isFetching,
|
||||
isFetchingNextPage,
|
||||
status,
|
||||
handleInputChange,
|
||||
searchString,
|
||||
};
|
||||
};
|
||||
|
||||
export const useCreateKnowledge = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['infiniteFetchKnowledgeList'],
|
||||
mutationFn: async (params: { id?: string; name: string }) => {
|
||||
const { data = {} } = await kbService.createKb(params);
|
||||
if (data.code === 0) {
|
||||
message.success(
|
||||
i18n.t(`message.${params?.id ? 'modified' : 'created'}`),
|
||||
);
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchKnowledgeList'] });
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, createKnowledge: mutateAsync };
|
||||
};
|
||||
|
||||
export const useDeleteKnowledge = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['deleteKnowledge'],
|
||||
mutationFn: async (id: string) => {
|
||||
const { data } = await kbService.rmKb({ kb_id: id });
|
||||
if (data.code === 0) {
|
||||
message.success(i18n.t(`message.deleted`));
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['infiniteFetchKnowledgeList'],
|
||||
});
|
||||
}
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, deleteKnowledge: mutateAsync };
|
||||
};
|
||||
|
||||
//#region knowledge configuration
|
||||
|
||||
export const useUpdateKnowledge = (shouldFetchList = false) => {
|
||||
const knowledgeBaseId = useKnowledgeBaseId();
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['saveKnowledge'],
|
||||
mutationFn: async (params: Record<string, any>) => {
|
||||
const { data = {} } = await kbService.updateKb({
|
||||
kb_id: params?.kb_id ? params?.kb_id : knowledgeBaseId,
|
||||
...params,
|
||||
});
|
||||
if (data.code === 0) {
|
||||
message.success(i18n.t(`message.updated`));
|
||||
if (shouldFetchList) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['fetchKnowledgeListByPage'],
|
||||
});
|
||||
} else {
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchKnowledgeDetail'] });
|
||||
}
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, saveKnowledgeConfiguration: mutateAsync };
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Retrieval testing
|
||||
|
||||
export const useTestChunkRetrieval = (): ResponsePostType<ITestingResult> & {
|
||||
testChunk: (...params: any[]) => void;
|
||||
} => {
|
||||
const knowledgeBaseId = useKnowledgeBaseId();
|
||||
const { page, size: pageSize } = useSetPaginationParams();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['testChunk'], // This method is invalid
|
||||
gcTime: 0,
|
||||
mutationFn: async (values: any) => {
|
||||
const { data } = await kbService.retrieval_test({
|
||||
...values,
|
||||
kb_id: values.kb_id ?? knowledgeBaseId,
|
||||
page,
|
||||
size: pageSize,
|
||||
});
|
||||
if (data.code === 0) {
|
||||
const res = data.data;
|
||||
return {
|
||||
...res,
|
||||
documents: res.doc_aggs,
|
||||
};
|
||||
}
|
||||
return (
|
||||
data?.data ?? {
|
||||
chunks: [],
|
||||
documents: [],
|
||||
total: 0,
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
data: data ?? { chunks: [], documents: [], total: 0 },
|
||||
loading,
|
||||
testChunk: mutateAsync,
|
||||
};
|
||||
};
|
||||
|
||||
export const useTestChunkAllRetrieval = (): ResponsePostType<ITestingResult> & {
|
||||
testChunkAll: (...params: any[]) => void;
|
||||
} => {
|
||||
const knowledgeBaseId = useKnowledgeBaseId();
|
||||
const { page, size: pageSize } = useSetPaginationParams();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['testChunkAll'], // This method is invalid
|
||||
gcTime: 0,
|
||||
mutationFn: async (values: any) => {
|
||||
const { data } = await kbService.retrieval_test({
|
||||
...values,
|
||||
kb_id: values.kb_id ?? knowledgeBaseId,
|
||||
doc_ids: [],
|
||||
page,
|
||||
size: pageSize,
|
||||
});
|
||||
if (data.code === 0) {
|
||||
const res = data.data;
|
||||
return {
|
||||
...res,
|
||||
documents: res.doc_aggs,
|
||||
};
|
||||
}
|
||||
return (
|
||||
data?.data ?? {
|
||||
chunks: [],
|
||||
documents: [],
|
||||
total: 0,
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
data: data ?? { chunks: [], documents: [], total: 0 },
|
||||
loading,
|
||||
testChunkAll: mutateAsync,
|
||||
};
|
||||
};
|
||||
|
||||
export const useChunkIsTesting = () => {
|
||||
return useIsMutating({ mutationKey: ['testChunk'] }) > 0;
|
||||
};
|
||||
|
||||
export const useSelectTestingResult = (): ITestingResult => {
|
||||
const data = useMutationState({
|
||||
filters: { mutationKey: ['testChunk'] },
|
||||
select: (mutation) => {
|
||||
return mutation.state.data;
|
||||
},
|
||||
});
|
||||
return (data.at(-1) ?? {
|
||||
chunks: [],
|
||||
documents: [],
|
||||
total: 0,
|
||||
}) as ITestingResult;
|
||||
};
|
||||
|
||||
export const useSelectIsTestingSuccess = () => {
|
||||
const status = useMutationState({
|
||||
filters: { mutationKey: ['testChunk'] },
|
||||
select: (mutation) => {
|
||||
return mutation.state.status;
|
||||
},
|
||||
});
|
||||
return status.at(-1) === 'success';
|
||||
};
|
||||
|
||||
export const useAllTestingSuccess = () => {
|
||||
const status = useMutationState({
|
||||
filters: { mutationKey: ['testChunkAll'] },
|
||||
select: (mutation) => {
|
||||
return mutation.state.status;
|
||||
},
|
||||
});
|
||||
return status.at(-1) === 'success';
|
||||
};
|
||||
|
||||
export const useAllTestingResult = (): ITestingResult => {
|
||||
const data = useMutationState({
|
||||
filters: { mutationKey: ['testChunkAll'] },
|
||||
select: (mutation) => {
|
||||
return mutation.state.data;
|
||||
},
|
||||
});
|
||||
return (data.at(-1) ?? {
|
||||
chunks: [],
|
||||
documents: [],
|
||||
total: 0,
|
||||
}) as ITestingResult;
|
||||
};
|
||||
//#endregion
|
||||
|
||||
//#region tags
|
||||
|
||||
export const useFetchTagList = () => {
|
||||
const knowledgeBaseId = useKnowledgeBaseId();
|
||||
|
||||
const { data, isFetching: loading } = useQuery<Array<[string, number]>>({
|
||||
queryKey: ['fetchTagList'],
|
||||
initialData: [],
|
||||
gcTime: 0, // https://tanstack.com/query/latest/docs/framework/react/guides/caching?from=reactQueryV3
|
||||
queryFn: async () => {
|
||||
const { data } = await listTag(knowledgeBaseId);
|
||||
const list = data?.data || [];
|
||||
return list;
|
||||
},
|
||||
});
|
||||
|
||||
return { list: data, loading };
|
||||
};
|
||||
|
||||
export const useDeleteTag = () => {
|
||||
const knowledgeBaseId = useKnowledgeBaseId();
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['deleteTag'],
|
||||
mutationFn: async (tags: string[]) => {
|
||||
const { data } = await removeTag(knowledgeBaseId, tags);
|
||||
if (data.code === 0) {
|
||||
message.success(i18n.t(`message.deleted`));
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['fetchTagList'],
|
||||
});
|
||||
}
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, deleteTag: mutateAsync };
|
||||
};
|
||||
|
||||
export const useRenameTag = () => {
|
||||
const knowledgeBaseId = useKnowledgeBaseId();
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['renameTag'],
|
||||
mutationFn: async (params: IRenameTag) => {
|
||||
const { data } = await renameTag(knowledgeBaseId, params);
|
||||
if (data.code === 0) {
|
||||
message.success(i18n.t(`message.modified`));
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['fetchTagList'],
|
||||
});
|
||||
}
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, renameTag: mutateAsync };
|
||||
};
|
||||
|
||||
export const useTagIsRenaming = () => {
|
||||
return useIsMutating({ mutationKey: ['renameTag'] }) > 0;
|
||||
};
|
||||
|
||||
export const useFetchTagListByKnowledgeIds = () => {
|
||||
const [knowledgeIds, setKnowledgeIds] = useState<string[]>([]);
|
||||
|
||||
const { data, isFetching: loading } = useQuery<Array<[string, number]>>({
|
||||
queryKey: ['fetchTagListByKnowledgeIds'],
|
||||
enabled: knowledgeIds.length > 0,
|
||||
initialData: [],
|
||||
gcTime: 0, // https://tanstack.com/query/latest/docs/framework/react/guides/caching?from=reactQueryV3
|
||||
queryFn: async () => {
|
||||
const { data } = await kbService.listTagByKnowledgeIds({
|
||||
kb_ids: knowledgeIds.join(','),
|
||||
});
|
||||
const list = data?.data || [];
|
||||
return list;
|
||||
},
|
||||
});
|
||||
|
||||
return { list: data, loading, setKnowledgeIds };
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
export function useFetchKnowledgeGraph() {
|
||||
const knowledgeBaseId = useKnowledgeBaseId();
|
||||
|
||||
const { data, isFetching: loading } = useQuery<IKnowledgeGraph>({
|
||||
queryKey: ['fetchKnowledgeGraph', knowledgeBaseId],
|
||||
initialData: { graph: {}, mind_map: {} } as IKnowledgeGraph,
|
||||
enabled: !!knowledgeBaseId,
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await getKnowledgeGraph(knowledgeBaseId);
|
||||
return data?.data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
}
|
||||
|
||||
export const useRemoveKnowledgeGraph = () => {
|
||||
const knowledgeBaseId = useKnowledgeBaseId();
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['removeKnowledgeGraph'],
|
||||
mutationFn: async () => {
|
||||
const { data } = await deleteKnowledgeGraph(knowledgeBaseId);
|
||||
if (data.code === 0) {
|
||||
message.success(i18n.t(`message.deleted`));
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['fetchKnowledgeGraph'],
|
||||
});
|
||||
}
|
||||
return data?.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, removeKnowledgeGraph: mutateAsync };
|
||||
};
|
||||
384
web/src/hooks/llm-hooks.tsx
Normal file
384
web/src/hooks/llm-hooks.tsx
Normal file
@@ -0,0 +1,384 @@
|
||||
import { LlmIcon } from '@/components/svg-icon';
|
||||
import { LlmModelType } from '@/constants/knowledge';
|
||||
import { ResponseGetType } from '@/interfaces/database/base';
|
||||
import {
|
||||
IFactory,
|
||||
IMyLlmValue,
|
||||
IThirdOAIModelCollection as IThirdAiModelCollection,
|
||||
IThirdOAIModel,
|
||||
IThirdOAIModelCollection,
|
||||
} from '@/interfaces/database/llm';
|
||||
import {
|
||||
IAddLlmRequestBody,
|
||||
IDeleteLlmRequestBody,
|
||||
} from '@/interfaces/request/llm';
|
||||
import userService from '@/services/user-service';
|
||||
import { sortLLmFactoryListBySpecifiedOrder } from '@/utils/common-util';
|
||||
import { getLLMIconName, getRealModelName } from '@/utils/llm-util';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { Flex, message } from 'antd';
|
||||
import { DefaultOptionType } from 'antd/es/select';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const useFetchLlmList = (
|
||||
modelType?: LlmModelType,
|
||||
): IThirdAiModelCollection => {
|
||||
const { data } = useQuery({
|
||||
queryKey: ['llmList'],
|
||||
initialData: {},
|
||||
queryFn: async () => {
|
||||
const { data } = await userService.llm_list({ model_type: modelType });
|
||||
|
||||
return data?.data ?? {};
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const useSelectLlmOptions = () => {
|
||||
const llmInfo: IThirdOAIModelCollection = useFetchLlmList();
|
||||
|
||||
const embeddingModelOptions = useMemo(() => {
|
||||
return Object.entries(llmInfo).map(([key, value]) => {
|
||||
return {
|
||||
label: key,
|
||||
options: value.map((x) => ({
|
||||
label: getRealModelName(x.llm_name),
|
||||
value: `${x.llm_name}@${x.fid}`,
|
||||
disabled: !x.available,
|
||||
})),
|
||||
};
|
||||
});
|
||||
}, [llmInfo]);
|
||||
|
||||
return embeddingModelOptions;
|
||||
};
|
||||
|
||||
function buildLlmOptionsWithIcon(x: IThirdOAIModel) {
|
||||
return {
|
||||
label: (
|
||||
<Flex align="center" gap={6}>
|
||||
<LlmIcon
|
||||
name={getLLMIconName(x.fid, x.llm_name)}
|
||||
width={26}
|
||||
height={26}
|
||||
size={'small'}
|
||||
/>
|
||||
<span>{getRealModelName(x.llm_name)}</span>
|
||||
</Flex>
|
||||
),
|
||||
value: `${x.llm_name}@${x.fid}`,
|
||||
disabled: !x.available,
|
||||
is_tools: x.is_tools,
|
||||
};
|
||||
}
|
||||
|
||||
export const useSelectLlmOptionsByModelType = () => {
|
||||
const llmInfo: IThirdOAIModelCollection = useFetchLlmList();
|
||||
|
||||
const groupImage2TextOptions = useCallback(() => {
|
||||
const modelType = LlmModelType.Image2text;
|
||||
const modelTag = modelType.toUpperCase();
|
||||
|
||||
return Object.entries(llmInfo)
|
||||
.map(([key, value]) => {
|
||||
return {
|
||||
label: key,
|
||||
options: value
|
||||
.filter(
|
||||
(x) =>
|
||||
(x.model_type.includes(modelType) ||
|
||||
(x.tags && x.tags.includes(modelTag))) &&
|
||||
x.available,
|
||||
)
|
||||
.map(buildLlmOptionsWithIcon),
|
||||
};
|
||||
})
|
||||
.filter((x) => x.options.length > 0);
|
||||
}, [llmInfo]);
|
||||
|
||||
const groupOptionsByModelType = useCallback(
|
||||
(modelType: LlmModelType) => {
|
||||
return Object.entries(llmInfo)
|
||||
.filter(([, value]) =>
|
||||
modelType
|
||||
? value.some((x) => x.model_type.includes(modelType))
|
||||
: true,
|
||||
)
|
||||
.map(([key, value]) => {
|
||||
return {
|
||||
label: key,
|
||||
options: value
|
||||
.filter(
|
||||
(x) =>
|
||||
(modelType ? x.model_type.includes(modelType) : true) &&
|
||||
x.available,
|
||||
)
|
||||
.map(buildLlmOptionsWithIcon),
|
||||
};
|
||||
})
|
||||
.filter((x) => x.options.length > 0);
|
||||
},
|
||||
[llmInfo],
|
||||
);
|
||||
|
||||
return {
|
||||
[LlmModelType.Chat]: groupOptionsByModelType(LlmModelType.Chat),
|
||||
[LlmModelType.Embedding]: groupOptionsByModelType(LlmModelType.Embedding),
|
||||
[LlmModelType.Image2text]: groupImage2TextOptions(),
|
||||
[LlmModelType.Speech2text]: groupOptionsByModelType(
|
||||
LlmModelType.Speech2text,
|
||||
),
|
||||
[LlmModelType.Rerank]: groupOptionsByModelType(LlmModelType.Rerank),
|
||||
[LlmModelType.TTS]: groupOptionsByModelType(LlmModelType.TTS),
|
||||
};
|
||||
};
|
||||
|
||||
// Merge different types of models from the same manufacturer under one manufacturer
|
||||
export const useComposeLlmOptionsByModelTypes = (
|
||||
modelTypes: LlmModelType[],
|
||||
) => {
|
||||
const allOptions = useSelectLlmOptionsByModelType();
|
||||
|
||||
return modelTypes.reduce<
|
||||
(DefaultOptionType & {
|
||||
options: {
|
||||
label: JSX.Element;
|
||||
value: string;
|
||||
disabled: boolean;
|
||||
is_tools: boolean;
|
||||
}[];
|
||||
})[]
|
||||
>((pre, cur) => {
|
||||
const options = allOptions[cur];
|
||||
options.forEach((x) => {
|
||||
const item = pre.find((y) => y.label === x.label);
|
||||
if (item) {
|
||||
x.options.forEach((y) => {
|
||||
// A model that is both an image2text and speech2text model
|
||||
if (!item.options.some((z) => z.value === y.value)) {
|
||||
item.options.push(y);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
pre.push(x);
|
||||
}
|
||||
});
|
||||
|
||||
return pre;
|
||||
}, []);
|
||||
};
|
||||
|
||||
export const useFetchLlmFactoryList = (): ResponseGetType<IFactory[]> => {
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: ['factoryList'],
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await userService.factories_list();
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export type LlmItem = { name: string; logo: string } & IMyLlmValue;
|
||||
|
||||
export const useFetchMyLlmList = (): ResponseGetType<
|
||||
Record<string, IMyLlmValue>
|
||||
> => {
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: ['myLlmList'],
|
||||
initialData: {},
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await userService.my_llm();
|
||||
|
||||
return data?.data ?? {};
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useFetchMyLlmListDetailed = (): ResponseGetType<
|
||||
Record<string, any>
|
||||
> => {
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: ['myLlmListDetailed'],
|
||||
initialData: {},
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await userService.my_llm({ include_details: true });
|
||||
|
||||
return data?.data ?? {};
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useSelectLlmList = () => {
|
||||
const { data: myLlmList, loading: myLlmListLoading } = useFetchMyLlmList();
|
||||
const { data: factoryList, loading: factoryListLoading } =
|
||||
useFetchLlmFactoryList();
|
||||
|
||||
const nextMyLlmList: Array<LlmItem> = useMemo(() => {
|
||||
return Object.entries(myLlmList).map(([key, value]) => ({
|
||||
name: key,
|
||||
logo: factoryList.find((x) => x.name === key)?.logo ?? '',
|
||||
...value,
|
||||
llm: value.llm.map((x) => ({ ...x, name: x.name })),
|
||||
}));
|
||||
}, [myLlmList, factoryList]);
|
||||
|
||||
const nextFactoryList = useMemo(() => {
|
||||
const currentList = factoryList.filter((x) =>
|
||||
Object.keys(myLlmList).every((y) => y !== x.name),
|
||||
);
|
||||
return sortLLmFactoryListBySpecifiedOrder(currentList);
|
||||
}, [factoryList, myLlmList]);
|
||||
|
||||
return {
|
||||
myLlmList: nextMyLlmList,
|
||||
factoryList: nextFactoryList,
|
||||
loading: myLlmListLoading || factoryListLoading,
|
||||
};
|
||||
};
|
||||
|
||||
export interface IApiKeySavingParams {
|
||||
llm_factory: string;
|
||||
api_key: string;
|
||||
llm_name?: string;
|
||||
model_type?: string;
|
||||
base_url?: string;
|
||||
}
|
||||
|
||||
export const useSaveApiKey = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['saveApiKey'],
|
||||
mutationFn: async (params: IApiKeySavingParams) => {
|
||||
const { data } = await userService.set_api_key(params);
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.modified'));
|
||||
queryClient.invalidateQueries({ queryKey: ['myLlmList'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['factoryList'] });
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, saveApiKey: mutateAsync };
|
||||
};
|
||||
|
||||
export interface ISystemModelSettingSavingParams {
|
||||
tenant_id: string;
|
||||
name?: string;
|
||||
asr_id: string;
|
||||
embd_id: string;
|
||||
img2txt_id: string;
|
||||
llm_id: string;
|
||||
}
|
||||
|
||||
export const useSaveTenantInfo = () => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['saveTenantInfo'],
|
||||
mutationFn: async (params: ISystemModelSettingSavingParams) => {
|
||||
const { data } = await userService.set_tenant_info(params);
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.modified'));
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, saveTenantInfo: mutateAsync };
|
||||
};
|
||||
|
||||
export const useAddLlm = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['addLlm'],
|
||||
mutationFn: async (params: IAddLlmRequestBody) => {
|
||||
const { data } = await userService.add_llm(params);
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({ queryKey: ['myLlmList'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['factoryList'] });
|
||||
message.success(t('message.modified'));
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, addLlm: mutateAsync };
|
||||
};
|
||||
|
||||
export const useDeleteLlm = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['deleteLlm'],
|
||||
mutationFn: async (params: IDeleteLlmRequestBody) => {
|
||||
const { data } = await userService.delete_llm(params);
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({ queryKey: ['myLlmList'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['factoryList'] });
|
||||
message.success(t('message.deleted'));
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, deleteLlm: mutateAsync };
|
||||
};
|
||||
|
||||
export const useDeleteFactory = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['deleteFactory'],
|
||||
mutationFn: async (params: IDeleteLlmRequestBody) => {
|
||||
const { data } = await userService.deleteFactory(params);
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({ queryKey: ['myLlmList'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['factoryList'] });
|
||||
message.success(t('message.deleted'));
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, deleteFactory: mutateAsync };
|
||||
};
|
||||
748
web/src/hooks/logic-hooks.ts
Normal file
748
web/src/hooks/logic-hooks.ts
Normal file
@@ -0,0 +1,748 @@
|
||||
import { Authorization } from '@/constants/authorization';
|
||||
import { MessageType } from '@/constants/chat';
|
||||
import { LanguageTranslationMap } from '@/constants/common';
|
||||
import { ResponseType } from '@/interfaces/database/base';
|
||||
import { IAnswer, Message } from '@/interfaces/database/chat';
|
||||
import { IKnowledgeFile } from '@/interfaces/database/knowledge';
|
||||
import { IClientConversation, IMessage } from '@/pages/chat/interface';
|
||||
import api from '@/utils/api';
|
||||
import { getAuthorization } from '@/utils/authorization-util';
|
||||
import { buildMessageUuid } from '@/utils/chat';
|
||||
import { PaginationProps, message } from 'antd';
|
||||
import { FormInstance } from 'antd/lib';
|
||||
import axios from 'axios';
|
||||
import { EventSourceParserStream } from 'eventsource-parser/stream';
|
||||
import { has, isEmpty, omit } from 'lodash';
|
||||
import {
|
||||
ChangeEventHandler,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { useTranslate } from './common-hooks';
|
||||
import { useSetPaginationParams } from './route-hook';
|
||||
import { useFetchTenantInfo, useSaveSetting } from './user-setting-hooks';
|
||||
|
||||
export function usePrevious<T>(value: T) {
|
||||
const ref = useRef<T>();
|
||||
useEffect(() => {
|
||||
ref.current = value;
|
||||
}, [value]);
|
||||
return ref.current;
|
||||
}
|
||||
|
||||
export const useSetSelectedRecord = <T = IKnowledgeFile>() => {
|
||||
const [currentRecord, setCurrentRecord] = useState<T>({} as T);
|
||||
|
||||
const setRecord = (record: T) => {
|
||||
setCurrentRecord(record);
|
||||
};
|
||||
|
||||
return { currentRecord, setRecord };
|
||||
};
|
||||
|
||||
export const useChangeLanguage = () => {
|
||||
const { i18n } = useTranslation();
|
||||
const { saveSetting } = useSaveSetting();
|
||||
|
||||
const changeLanguage = (lng: string) => {
|
||||
i18n.changeLanguage(
|
||||
LanguageTranslationMap[lng as keyof typeof LanguageTranslationMap],
|
||||
);
|
||||
saveSetting({ language: lng });
|
||||
};
|
||||
|
||||
return changeLanguage;
|
||||
};
|
||||
|
||||
export const useGetPaginationWithRouter = () => {
|
||||
const { t } = useTranslate('common');
|
||||
const {
|
||||
setPaginationParams,
|
||||
page,
|
||||
size: pageSize,
|
||||
} = useSetPaginationParams();
|
||||
|
||||
const onPageChange: PaginationProps['onChange'] = useCallback(
|
||||
(pageNumber: number, pageSize: number) => {
|
||||
setPaginationParams(pageNumber, pageSize);
|
||||
},
|
||||
[setPaginationParams],
|
||||
);
|
||||
|
||||
const setCurrentPagination = useCallback(
|
||||
(pagination: { page: number; pageSize?: number }) => {
|
||||
if (pagination.pageSize !== pageSize) {
|
||||
pagination.page = 1; // Reset to first page if pageSize changes
|
||||
}
|
||||
setPaginationParams(pagination.page, pagination.pageSize);
|
||||
},
|
||||
[setPaginationParams, pageSize],
|
||||
);
|
||||
|
||||
const pagination: PaginationProps = useMemo(() => {
|
||||
return {
|
||||
showQuickJumper: true,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
current: page,
|
||||
pageSize: pageSize,
|
||||
pageSizeOptions: [1, 2, 10, 20, 50, 100],
|
||||
onChange: onPageChange,
|
||||
showTotal: (total) => `${t('total')} ${total}`,
|
||||
};
|
||||
}, [t, onPageChange, page, pageSize]);
|
||||
|
||||
return {
|
||||
pagination,
|
||||
setPagination: setCurrentPagination,
|
||||
};
|
||||
};
|
||||
|
||||
export const useHandleSearchChange = () => {
|
||||
const [searchString, setSearchString] = useState('');
|
||||
const { setPagination } = useGetPaginationWithRouter();
|
||||
const handleInputChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
const value = e.target.value;
|
||||
setSearchString(value);
|
||||
setPagination({ page: 1 });
|
||||
},
|
||||
[setPagination],
|
||||
);
|
||||
|
||||
return { handleInputChange, searchString };
|
||||
};
|
||||
|
||||
export const useGetPagination = () => {
|
||||
const [pagination, setPagination] = useState({ page: 1, pageSize: 10 });
|
||||
const { t } = useTranslate('common');
|
||||
|
||||
const onPageChange: PaginationProps['onChange'] = useCallback(
|
||||
(pageNumber: number, pageSize: number) => {
|
||||
setPagination({ page: pageNumber, pageSize });
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const currentPagination: PaginationProps = useMemo(() => {
|
||||
return {
|
||||
showQuickJumper: true,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
current: pagination.page,
|
||||
pageSize: pagination.pageSize,
|
||||
pageSizeOptions: [1, 2, 10, 20, 50, 100],
|
||||
onChange: onPageChange,
|
||||
showTotal: (total) => `${t('total')} ${total}`,
|
||||
};
|
||||
}, [t, onPageChange, pagination]);
|
||||
|
||||
return {
|
||||
pagination: currentPagination,
|
||||
};
|
||||
};
|
||||
|
||||
export interface AppConf {
|
||||
appName: string;
|
||||
}
|
||||
|
||||
export const useFetchAppConf = () => {
|
||||
const [appConf, setAppConf] = useState<AppConf>({} as AppConf);
|
||||
const fetchAppConf = useCallback(async () => {
|
||||
const ret = await axios.get('/conf.json');
|
||||
|
||||
setAppConf(ret.data);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchAppConf();
|
||||
}, [fetchAppConf]);
|
||||
|
||||
return appConf;
|
||||
};
|
||||
|
||||
function useSetDoneRecord() {
|
||||
const [doneRecord, setDoneRecord] = useState<Record<string, boolean>>({});
|
||||
|
||||
const clearDoneRecord = useCallback(() => {
|
||||
setDoneRecord({});
|
||||
}, []);
|
||||
|
||||
const setDoneRecordById = useCallback((id: string, val: boolean) => {
|
||||
setDoneRecord((prev) => ({ ...prev, [id]: val }));
|
||||
}, []);
|
||||
|
||||
const allDone = useMemo(() => {
|
||||
return Object.values(doneRecord).every((val) => val);
|
||||
}, [doneRecord]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEmpty(doneRecord) && allDone) {
|
||||
clearDoneRecord();
|
||||
}
|
||||
}, [allDone, clearDoneRecord, doneRecord]);
|
||||
|
||||
return {
|
||||
doneRecord,
|
||||
setDoneRecord,
|
||||
setDoneRecordById,
|
||||
clearDoneRecord,
|
||||
allDone,
|
||||
};
|
||||
}
|
||||
|
||||
export const useSendMessageWithSse = (
|
||||
url: string = api.completeConversation,
|
||||
) => {
|
||||
const [answer, setAnswer] = useState<IAnswer>({} as IAnswer);
|
||||
const [done, setDone] = useState(true);
|
||||
const { doneRecord, clearDoneRecord, setDoneRecordById, allDone } =
|
||||
useSetDoneRecord();
|
||||
const timer = useRef<any>();
|
||||
const sseRef = useRef<AbortController>();
|
||||
|
||||
const initializeSseRef = useCallback(() => {
|
||||
sseRef.current = new AbortController();
|
||||
}, []);
|
||||
|
||||
const resetAnswer = useCallback(() => {
|
||||
if (timer.current) {
|
||||
clearTimeout(timer.current);
|
||||
}
|
||||
timer.current = setTimeout(() => {
|
||||
setAnswer({} as IAnswer);
|
||||
clearTimeout(timer.current);
|
||||
}, 1000);
|
||||
}, []);
|
||||
|
||||
const setDoneValue = useCallback(
|
||||
(body: any, value: boolean) => {
|
||||
if (has(body, 'chatBoxId')) {
|
||||
setDoneRecordById(body.chatBoxId, value);
|
||||
} else {
|
||||
setDone(value);
|
||||
}
|
||||
},
|
||||
[setDoneRecordById],
|
||||
);
|
||||
|
||||
const send = useCallback(
|
||||
async (
|
||||
body: any,
|
||||
controller?: AbortController,
|
||||
): Promise<{ response: Response; data: ResponseType } | undefined> => {
|
||||
initializeSseRef();
|
||||
try {
|
||||
setDoneValue(body, false);
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
[Authorization]: getAuthorization(),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(omit(body, 'chatBoxId')),
|
||||
signal: controller?.signal || sseRef.current?.signal,
|
||||
});
|
||||
|
||||
const res = response.clone().json();
|
||||
|
||||
const reader = response?.body
|
||||
?.pipeThrough(new TextDecoderStream())
|
||||
.pipeThrough(new EventSourceParserStream())
|
||||
.getReader();
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
const x = await reader?.read();
|
||||
if (x) {
|
||||
const { done, value } = x;
|
||||
if (done) {
|
||||
resetAnswer();
|
||||
break;
|
||||
}
|
||||
try {
|
||||
const val = JSON.parse(value?.data || '');
|
||||
const d = val?.data;
|
||||
if (typeof d !== 'boolean') {
|
||||
setAnswer({
|
||||
...d,
|
||||
conversationId: body?.conversation_id,
|
||||
chatBoxId: body.chatBoxId,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// Swallow parse errors silently
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof DOMException && e.name === 'AbortError') {
|
||||
console.log('Request was aborted by user or logic.');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
setDoneValue(body, true);
|
||||
resetAnswer();
|
||||
return { data: await res, response };
|
||||
} catch (e) {
|
||||
setDoneValue(body, true);
|
||||
|
||||
resetAnswer();
|
||||
// Swallow fetch errors silently
|
||||
}
|
||||
},
|
||||
[initializeSseRef, setDoneValue, url, resetAnswer],
|
||||
);
|
||||
|
||||
const stopOutputMessage = useCallback(() => {
|
||||
sseRef.current?.abort();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
send,
|
||||
answer,
|
||||
done,
|
||||
doneRecord,
|
||||
allDone,
|
||||
setDone,
|
||||
resetAnswer,
|
||||
stopOutputMessage,
|
||||
clearDoneRecord,
|
||||
};
|
||||
};
|
||||
|
||||
export const useSpeechWithSse = (url: string = api.tts) => {
|
||||
const read = useCallback(
|
||||
async (body: any) => {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
[Authorization]: getAuthorization(),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
try {
|
||||
const res = await response.clone().json();
|
||||
if (res?.code !== 0) {
|
||||
message.error(res?.message);
|
||||
}
|
||||
} catch (error) {
|
||||
// Swallow errors silently
|
||||
}
|
||||
return response;
|
||||
},
|
||||
[url],
|
||||
);
|
||||
|
||||
return { read };
|
||||
};
|
||||
|
||||
//#region chat hooks
|
||||
|
||||
export const useScrollToBottom = (
|
||||
messages?: unknown,
|
||||
containerRef?: React.RefObject<HTMLDivElement>,
|
||||
) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [isAtBottom, setIsAtBottom] = useState(true);
|
||||
const isAtBottomRef = useRef(true);
|
||||
|
||||
useEffect(() => {
|
||||
isAtBottomRef.current = isAtBottom;
|
||||
}, [isAtBottom]);
|
||||
|
||||
const checkIfUserAtBottom = useCallback(() => {
|
||||
if (!containerRef?.current) return true;
|
||||
const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
|
||||
return Math.abs(scrollTop + clientHeight - scrollHeight) < 25;
|
||||
}, [containerRef]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef?.current) return;
|
||||
const container = containerRef.current;
|
||||
|
||||
const handleScroll = () => {
|
||||
setIsAtBottom(checkIfUserAtBottom());
|
||||
};
|
||||
|
||||
container.addEventListener('scroll', handleScroll);
|
||||
handleScroll();
|
||||
return () => container.removeEventListener('scroll', handleScroll);
|
||||
}, [containerRef, checkIfUserAtBottom]);
|
||||
|
||||
// Imperative scroll function
|
||||
const scrollToBottom = useCallback(() => {
|
||||
if (containerRef?.current) {
|
||||
const container = containerRef.current;
|
||||
container.scrollTo({
|
||||
top: container.scrollHeight - container.clientHeight,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
}, [containerRef]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!messages) return;
|
||||
if (!containerRef?.current) return;
|
||||
requestAnimationFrame(() => {
|
||||
setTimeout(() => {
|
||||
if (isAtBottomRef.current) {
|
||||
scrollToBottom();
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}, [messages, containerRef, scrollToBottom]);
|
||||
|
||||
return { scrollRef: ref, isAtBottom, scrollToBottom };
|
||||
};
|
||||
|
||||
export const useHandleMessageInputChange = () => {
|
||||
const [value, setValue] = useState('');
|
||||
|
||||
const handleInputChange: ChangeEventHandler<HTMLTextAreaElement> = (e) => {
|
||||
const value = e.target.value;
|
||||
const nextValue = value.replaceAll('\\n', '\n').replaceAll('\\t', '\t');
|
||||
setValue(nextValue);
|
||||
};
|
||||
|
||||
return {
|
||||
handleInputChange,
|
||||
value,
|
||||
setValue,
|
||||
};
|
||||
};
|
||||
|
||||
export const useSelectDerivedMessages = () => {
|
||||
const [derivedMessages, setDerivedMessages] = useState<IMessage[]>([]);
|
||||
|
||||
const messageContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { scrollRef, scrollToBottom } = useScrollToBottom(
|
||||
derivedMessages,
|
||||
messageContainerRef,
|
||||
);
|
||||
|
||||
const addNewestQuestion = useCallback(
|
||||
(message: Message, answer: string = '') => {
|
||||
setDerivedMessages((pre) => {
|
||||
return [
|
||||
...pre,
|
||||
{
|
||||
...message,
|
||||
id: buildMessageUuid(message), // The message id is generated on the front end,
|
||||
// and the message id returned by the back end is the same as the question id,
|
||||
// so that the pair of messages can be deleted together when deleting the message
|
||||
},
|
||||
{
|
||||
role: MessageType.Assistant,
|
||||
content: answer,
|
||||
id: buildMessageUuid({ ...message, role: MessageType.Assistant }),
|
||||
},
|
||||
];
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const addNewestOneQuestion = useCallback((message: Message) => {
|
||||
setDerivedMessages((pre) => {
|
||||
return [
|
||||
...pre,
|
||||
{
|
||||
...message,
|
||||
id: buildMessageUuid(message), // The message id is generated on the front end,
|
||||
// and the message id returned by the back end is the same as the question id,
|
||||
// so that the pair of messages can be deleted together when deleting the message
|
||||
},
|
||||
];
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Add the streaming message to the last item in the message list
|
||||
const addNewestAnswer = useCallback((answer: IAnswer) => {
|
||||
setDerivedMessages((pre) => {
|
||||
return [
|
||||
...(pre?.slice(0, -1) ?? []),
|
||||
{
|
||||
role: MessageType.Assistant,
|
||||
content: answer.answer,
|
||||
reference: answer.reference,
|
||||
id: buildMessageUuid({
|
||||
id: answer.id,
|
||||
role: MessageType.Assistant,
|
||||
}),
|
||||
prompt: answer.prompt,
|
||||
audio_binary: answer.audio_binary,
|
||||
...omit(answer, 'reference'),
|
||||
},
|
||||
];
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Add the streaming message to the last item in the message list
|
||||
const addNewestOneAnswer = useCallback((answer: IAnswer) => {
|
||||
setDerivedMessages((pre) => {
|
||||
const idx = pre.findIndex((x) => x.id === answer.id);
|
||||
|
||||
if (idx !== -1) {
|
||||
return pre.map((x) => {
|
||||
if (x.id === answer.id) {
|
||||
return { ...x, ...answer, content: answer.answer };
|
||||
}
|
||||
return x;
|
||||
});
|
||||
}
|
||||
|
||||
return [
|
||||
...(pre ?? []),
|
||||
{
|
||||
role: MessageType.Assistant,
|
||||
content: answer.answer,
|
||||
reference: answer.reference,
|
||||
id: buildMessageUuid({
|
||||
id: answer.id,
|
||||
role: MessageType.Assistant,
|
||||
}),
|
||||
prompt: answer.prompt,
|
||||
audio_binary: answer.audio_binary,
|
||||
...omit(answer, 'reference'),
|
||||
},
|
||||
];
|
||||
});
|
||||
}, []);
|
||||
|
||||
const removeLatestMessage = useCallback(() => {
|
||||
setDerivedMessages((pre) => {
|
||||
const nextMessages = pre?.slice(0, -2) ?? [];
|
||||
return nextMessages;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const removeMessageById = useCallback(
|
||||
(messageId: string) => {
|
||||
setDerivedMessages((pre) => {
|
||||
const nextMessages = pre?.filter((x) => x.id !== messageId) ?? [];
|
||||
return nextMessages;
|
||||
});
|
||||
},
|
||||
[setDerivedMessages],
|
||||
);
|
||||
|
||||
const removeMessagesAfterCurrentMessage = useCallback(
|
||||
(messageId: string) => {
|
||||
setDerivedMessages((pre) => {
|
||||
const index = pre.findIndex((x) => x.id === messageId);
|
||||
if (index !== -1) {
|
||||
let nextMessages = pre.slice(0, index + 2) ?? [];
|
||||
const latestMessage = nextMessages.at(-1);
|
||||
nextMessages = latestMessage
|
||||
? [
|
||||
...nextMessages.slice(0, -1),
|
||||
{
|
||||
...latestMessage,
|
||||
content: '',
|
||||
reference: undefined,
|
||||
prompt: undefined,
|
||||
},
|
||||
]
|
||||
: nextMessages;
|
||||
return nextMessages;
|
||||
}
|
||||
return pre;
|
||||
});
|
||||
},
|
||||
[setDerivedMessages],
|
||||
);
|
||||
|
||||
const removeAllMessages = useCallback(() => {
|
||||
setDerivedMessages([]);
|
||||
}, [setDerivedMessages]);
|
||||
|
||||
const removeAllMessagesExceptFirst = useCallback(() => {
|
||||
setDerivedMessages((list) => {
|
||||
if (list.length <= 1) {
|
||||
return list;
|
||||
}
|
||||
return list.slice(0, 1);
|
||||
});
|
||||
}, [setDerivedMessages]);
|
||||
|
||||
return {
|
||||
scrollRef,
|
||||
messageContainerRef,
|
||||
derivedMessages,
|
||||
setDerivedMessages,
|
||||
addNewestQuestion,
|
||||
addNewestAnswer,
|
||||
removeLatestMessage,
|
||||
removeMessageById,
|
||||
addNewestOneQuestion,
|
||||
addNewestOneAnswer,
|
||||
removeMessagesAfterCurrentMessage,
|
||||
removeAllMessages,
|
||||
scrollToBottom,
|
||||
removeAllMessagesExceptFirst,
|
||||
};
|
||||
};
|
||||
|
||||
export interface IRemoveMessageById {
|
||||
removeMessageById(messageId: string): void;
|
||||
}
|
||||
|
||||
export const useRemoveMessagesAfterCurrentMessage = (
|
||||
setCurrentConversation: (
|
||||
callback: (state: IClientConversation) => IClientConversation,
|
||||
) => void,
|
||||
) => {
|
||||
const removeMessagesAfterCurrentMessage = useCallback(
|
||||
(messageId: string) => {
|
||||
setCurrentConversation((pre) => {
|
||||
const index = pre.message?.findIndex((x) => x.id === messageId);
|
||||
if (index !== -1) {
|
||||
let nextMessages = pre.message?.slice(0, index + 2) ?? [];
|
||||
const latestMessage = nextMessages.at(-1);
|
||||
nextMessages = latestMessage
|
||||
? [
|
||||
...nextMessages.slice(0, -1),
|
||||
{
|
||||
...latestMessage,
|
||||
content: '',
|
||||
reference: undefined,
|
||||
prompt: undefined,
|
||||
},
|
||||
]
|
||||
: nextMessages;
|
||||
return {
|
||||
...pre,
|
||||
message: nextMessages,
|
||||
};
|
||||
}
|
||||
return pre;
|
||||
});
|
||||
},
|
||||
[setCurrentConversation],
|
||||
);
|
||||
|
||||
return { removeMessagesAfterCurrentMessage };
|
||||
};
|
||||
|
||||
export interface IRegenerateMessage {
|
||||
regenerateMessage?: (message: Message) => void;
|
||||
}
|
||||
|
||||
export const useRegenerateMessage = ({
|
||||
removeMessagesAfterCurrentMessage,
|
||||
sendMessage,
|
||||
messages,
|
||||
}: {
|
||||
removeMessagesAfterCurrentMessage(messageId: string): void;
|
||||
sendMessage({
|
||||
message,
|
||||
}: {
|
||||
message: Message;
|
||||
messages?: Message[];
|
||||
}): void | Promise<any>;
|
||||
messages: Message[];
|
||||
}) => {
|
||||
const regenerateMessage = useCallback(
|
||||
async (message: Message) => {
|
||||
if (message.id) {
|
||||
removeMessagesAfterCurrentMessage(message.id);
|
||||
const index = messages.findIndex((x) => x.id === message.id);
|
||||
let nextMessages;
|
||||
if (index !== -1) {
|
||||
nextMessages = messages.slice(0, index);
|
||||
}
|
||||
sendMessage({
|
||||
message: { ...message, id: uuid() },
|
||||
messages: nextMessages,
|
||||
});
|
||||
}
|
||||
},
|
||||
[removeMessagesAfterCurrentMessage, sendMessage, messages],
|
||||
);
|
||||
|
||||
return { regenerateMessage };
|
||||
};
|
||||
|
||||
// #endregion
|
||||
|
||||
/**
|
||||
*
|
||||
* @param defaultId
|
||||
* used to switch between different items, similar to radio
|
||||
* @returns
|
||||
*/
|
||||
export const useSelectItem = (defaultId?: string) => {
|
||||
const [selectedId, setSelectedId] = useState('');
|
||||
|
||||
const handleItemClick = useCallback(
|
||||
(id: string) => () => {
|
||||
setSelectedId(id);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (defaultId) {
|
||||
setSelectedId(defaultId);
|
||||
}
|
||||
}, [defaultId]);
|
||||
|
||||
return { selectedId, handleItemClick };
|
||||
};
|
||||
|
||||
export const useFetchModelId = () => {
|
||||
const { data: tenantInfo } = useFetchTenantInfo(true);
|
||||
|
||||
return tenantInfo?.llm_id ?? '';
|
||||
};
|
||||
|
||||
const ChunkTokenNumMap = {
|
||||
naive: 128,
|
||||
knowledge_graph: 8192,
|
||||
};
|
||||
|
||||
export const useHandleChunkMethodSelectChange = (form: FormInstance) => {
|
||||
// const form = Form.useFormInstance();
|
||||
const handleChange = useCallback(
|
||||
(value: string) => {
|
||||
if (value in ChunkTokenNumMap) {
|
||||
form.setFieldValue(
|
||||
['parser_config', 'chunk_token_num'],
|
||||
ChunkTokenNumMap[value as keyof typeof ChunkTokenNumMap],
|
||||
);
|
||||
}
|
||||
},
|
||||
[form],
|
||||
);
|
||||
|
||||
return handleChange;
|
||||
};
|
||||
|
||||
// reset form fields when modal is form, closed
|
||||
export const useResetFormOnCloseModal = ({
|
||||
form,
|
||||
visible,
|
||||
}: {
|
||||
form: FormInstance;
|
||||
visible?: boolean;
|
||||
}) => {
|
||||
const prevOpenRef = useRef<boolean>();
|
||||
useEffect(() => {
|
||||
prevOpenRef.current = visible;
|
||||
}, [visible]);
|
||||
const prevOpen = prevOpenRef.current;
|
||||
|
||||
useEffect(() => {
|
||||
if (!visible && prevOpen) {
|
||||
form.resetFields();
|
||||
}
|
||||
}, [form, prevOpen, visible]);
|
||||
};
|
||||
191
web/src/hooks/logic-hooks/navigate-hooks.ts
Normal file
191
web/src/hooks/logic-hooks/navigate-hooks.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
import { AgentCategory, AgentQuery } from '@/constants/agent';
|
||||
import { NavigateToDataflowResultProps } from '@/pages/dataflow-result/interface';
|
||||
import { Routes } from '@/routes';
|
||||
import { useCallback } from 'react';
|
||||
import { useNavigate, useParams, useSearchParams } from 'umi';
|
||||
|
||||
export enum QueryStringMap {
|
||||
KnowledgeId = 'knowledgeId',
|
||||
id = 'id',
|
||||
}
|
||||
|
||||
export const useNavigatePage = () => {
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const { id } = useParams();
|
||||
|
||||
const navigateToDatasetList = useCallback(() => {
|
||||
navigate(Routes.Datasets);
|
||||
}, [navigate]);
|
||||
|
||||
const navigateToDataset = useCallback(
|
||||
(id: string) => () => {
|
||||
// navigate(`${Routes.DatasetBase}${Routes.DataSetOverview}/${id}`);
|
||||
navigate(`${Routes.Dataset}/${id}`);
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
const navigateToDatasetOverview = useCallback(
|
||||
(id: string) => () => {
|
||||
navigate(`${Routes.DatasetBase}${Routes.DataSetOverview}/${id}`);
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
|
||||
const navigateToDataFile = useCallback(
|
||||
(id: string) => () => {
|
||||
navigate(`${Routes.DatasetBase}${Routes.DatasetBase}/${id}`);
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
|
||||
const navigateToHome = useCallback(() => {
|
||||
navigate(Routes.Root);
|
||||
}, [navigate]);
|
||||
|
||||
const navigateToProfile = useCallback(() => {
|
||||
navigate(Routes.ProfileSetting);
|
||||
}, [navigate]);
|
||||
|
||||
const navigateToOldProfile = useCallback(() => {
|
||||
navigate(Routes.UserSetting);
|
||||
}, [navigate]);
|
||||
|
||||
const navigateToChatList = useCallback(() => {
|
||||
navigate(Routes.Chats);
|
||||
}, [navigate]);
|
||||
|
||||
const navigateToChat = useCallback(
|
||||
(id: string) => () => {
|
||||
navigate(`${Routes.Chat}/${id}`);
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
|
||||
const navigateToAgents = useCallback(() => {
|
||||
navigate(Routes.Agents);
|
||||
}, [navigate]);
|
||||
|
||||
const navigateToAgentList = useCallback(() => {
|
||||
navigate(Routes.AgentList);
|
||||
}, [navigate]);
|
||||
|
||||
const navigateToAgent = useCallback(
|
||||
(id: string, category?: AgentCategory) => () => {
|
||||
navigate(`${Routes.Agent}/${id}?${AgentQuery.Category}=${category}`);
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
|
||||
const navigateToDataflow = useCallback(
|
||||
(id: string) => () => {
|
||||
navigate(`${Routes.DataFlow}/${id}`);
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
|
||||
const navigateToAgentLogs = useCallback(
|
||||
(id: string) => () => {
|
||||
navigate(`${Routes.AgentLogPage}/${id}`);
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
|
||||
const navigateToAgentTemplates = useCallback(() => {
|
||||
navigate(Routes.AgentTemplates);
|
||||
}, [navigate]);
|
||||
|
||||
const navigateToSearchList = useCallback(() => {
|
||||
navigate(Routes.Searches);
|
||||
}, [navigate]);
|
||||
|
||||
const navigateToSearch = useCallback(
|
||||
(id: string) => () => {
|
||||
navigate(`${Routes.Search}/${id}`);
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
|
||||
const navigateToChunkParsedResult = useCallback(
|
||||
(id: string, knowledgeId?: string) => () => {
|
||||
navigate(
|
||||
`${Routes.ParsedResult}/chunks?id=${knowledgeId}&doc_id=${id}`,
|
||||
// `${Routes.DataflowResult}?id=${knowledgeId}&doc_id=${id}&type=chunk`,
|
||||
);
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
|
||||
const getQueryString = useCallback(
|
||||
(queryStringKey?: QueryStringMap) => {
|
||||
const allQueryString = {
|
||||
[QueryStringMap.KnowledgeId]: searchParams.get(
|
||||
QueryStringMap.KnowledgeId,
|
||||
),
|
||||
[QueryStringMap.id]: searchParams.get(QueryStringMap.id),
|
||||
};
|
||||
if (queryStringKey) {
|
||||
return allQueryString[queryStringKey];
|
||||
}
|
||||
return allQueryString;
|
||||
},
|
||||
[searchParams],
|
||||
);
|
||||
|
||||
const navigateToChunk = useCallback(
|
||||
(route: Routes) => {
|
||||
navigate(
|
||||
`${route}/${id}?${QueryStringMap.KnowledgeId}=${getQueryString(QueryStringMap.KnowledgeId)}`,
|
||||
);
|
||||
},
|
||||
[getQueryString, id, navigate],
|
||||
);
|
||||
|
||||
const navigateToFiles = useCallback(
|
||||
(folderId?: string) => {
|
||||
navigate(`${Routes.Files}?folderId=${folderId}`);
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
|
||||
const navigateToDataflowResult = useCallback(
|
||||
(props: NavigateToDataflowResultProps) => () => {
|
||||
let params: string[] = [];
|
||||
Object.keys(props).forEach((key) => {
|
||||
if (props[key]) {
|
||||
params.push(`${key}=${props[key]}`);
|
||||
}
|
||||
});
|
||||
navigate(
|
||||
// `${Routes.ParsedResult}/${id}?${QueryStringMap.KnowledgeId}=${knowledgeId}`,
|
||||
`${Routes.DataflowResult}?${params.join('&')}`,
|
||||
);
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
|
||||
return {
|
||||
navigateToDatasetList,
|
||||
navigateToDataset,
|
||||
navigateToDatasetOverview,
|
||||
navigateToHome,
|
||||
navigateToProfile,
|
||||
navigateToChatList,
|
||||
navigateToChat,
|
||||
navigateToChunkParsedResult,
|
||||
getQueryString,
|
||||
navigateToChunk,
|
||||
navigateToAgents,
|
||||
navigateToAgent,
|
||||
navigateToAgentLogs,
|
||||
navigateToAgentTemplates,
|
||||
navigateToSearchList,
|
||||
navigateToSearch,
|
||||
navigateToFiles,
|
||||
navigateToAgentList,
|
||||
navigateToOldProfile,
|
||||
navigateToDataflowResult,
|
||||
navigateToDataflow,
|
||||
navigateToDataFile,
|
||||
};
|
||||
};
|
||||
14
web/src/hooks/logic-hooks/use-change-search.ts
Normal file
14
web/src/hooks/logic-hooks/use-change-search.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
export const useHandleSearchStrChange = () => {
|
||||
const [searchString, setSearchString] = useState('');
|
||||
const handleInputChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
const value = e.target.value;
|
||||
setSearchString(value);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return { handleInputChange, searchString };
|
||||
};
|
||||
24
web/src/hooks/logic-hooks/use-pagination.ts
Normal file
24
web/src/hooks/logic-hooks/use-pagination.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
export function useClientPagination(list: Array<any>) {
|
||||
const [page, setPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
||||
const onPaginationChange = useCallback((page: number, pageSize: number) => {
|
||||
setPage(page);
|
||||
setPageSize(pageSize);
|
||||
}, []);
|
||||
|
||||
const pagedList = useMemo(() => {
|
||||
return list?.slice((page - 1) * pageSize, page * pageSize);
|
||||
}, [list, page, pageSize]);
|
||||
|
||||
return {
|
||||
page,
|
||||
pageSize,
|
||||
setPage,
|
||||
setPageSize,
|
||||
onPaginationChange,
|
||||
pagedList,
|
||||
};
|
||||
}
|
||||
39
web/src/hooks/logic-hooks/use-row-selection.ts
Normal file
39
web/src/hooks/logic-hooks/use-row-selection.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { RowSelectionState } from '@tanstack/react-table';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
export function useRowSelection() {
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||
|
||||
const clearRowSelection = useCallback(() => {
|
||||
setRowSelection({});
|
||||
}, []);
|
||||
|
||||
const selectedCount = useMemo(() => {
|
||||
return Object.keys(rowSelection).length;
|
||||
}, [rowSelection]);
|
||||
|
||||
return {
|
||||
rowSelection,
|
||||
setRowSelection,
|
||||
rowSelectionIsEmpty: isEmpty(rowSelection),
|
||||
clearRowSelection,
|
||||
selectedCount,
|
||||
};
|
||||
}
|
||||
|
||||
export type UseRowSelectionType = ReturnType<typeof useRowSelection>;
|
||||
|
||||
export function useSelectedIds<T extends Array<{ id: string }>>(
|
||||
rowSelection: RowSelectionState,
|
||||
list: T,
|
||||
) {
|
||||
const selectedIds = useMemo(() => {
|
||||
const indexes = Object.keys(rowSelection);
|
||||
return list
|
||||
.filter((x, idx) => indexes.some((y) => Number(y) === idx))
|
||||
.map((x) => x.id);
|
||||
}, [list, rowSelection]);
|
||||
|
||||
return { selectedIds };
|
||||
}
|
||||
152
web/src/hooks/login-hooks.ts
Normal file
152
web/src/hooks/login-hooks.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import message from '@/components/ui/message';
|
||||
import { Authorization } from '@/constants/authorization';
|
||||
import userService, {
|
||||
getLoginChannels,
|
||||
loginWithChannel,
|
||||
} from '@/services/user-service';
|
||||
import authorizationUtil, { redirectToLogin } from '@/utils/authorization-util';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { Form } from 'antd';
|
||||
import { FormInstance } from 'antd/lib';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export interface ILoginRequestBody {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface IRegisterRequestBody extends ILoginRequestBody {
|
||||
nickname: string;
|
||||
}
|
||||
|
||||
export interface ILoginChannel {
|
||||
channel: string;
|
||||
display_name: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
export const useLoginChannels = () => {
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ['loginChannels'],
|
||||
queryFn: async () => {
|
||||
const { data: res = {} } = await getLoginChannels();
|
||||
return res.data || [];
|
||||
},
|
||||
});
|
||||
|
||||
return { channels: data as ILoginChannel[], loading: isLoading };
|
||||
};
|
||||
|
||||
export const useLoginWithChannel = () => {
|
||||
const { isPending: loading, mutateAsync } = useMutation({
|
||||
mutationKey: ['loginWithChannel'],
|
||||
mutationFn: async (channel: string) => {
|
||||
loginWithChannel(channel);
|
||||
return Promise.resolve();
|
||||
},
|
||||
});
|
||||
|
||||
return { loading, login: mutateAsync };
|
||||
};
|
||||
|
||||
export const useLogin = () => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['login'],
|
||||
mutationFn: async (params: { email: string; password: string }) => {
|
||||
const { data: res = {}, response } = await userService.login(params);
|
||||
if (res.code === 0) {
|
||||
const { data } = res;
|
||||
const authorization = response.headers.get(Authorization);
|
||||
const token = data.access_token;
|
||||
const userInfo = {
|
||||
avatar: data.avatar,
|
||||
name: data.nickname,
|
||||
email: data.email,
|
||||
};
|
||||
authorizationUtil.setItems({
|
||||
Authorization: authorization,
|
||||
userInfo: JSON.stringify(userInfo),
|
||||
Token: token,
|
||||
});
|
||||
}
|
||||
return res.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, login: mutateAsync };
|
||||
};
|
||||
|
||||
export const useRegister = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['register'],
|
||||
mutationFn: async (params: {
|
||||
email: string;
|
||||
password: string;
|
||||
nickname: string;
|
||||
}) => {
|
||||
const { data = {} } = await userService.register(params);
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.registered'));
|
||||
} else if (
|
||||
data.message &&
|
||||
data.message.includes('registration is disabled')
|
||||
) {
|
||||
message.error(
|
||||
t('message.registerDisabled') || 'User registration is disabled',
|
||||
);
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, register: mutateAsync };
|
||||
};
|
||||
|
||||
export const useLogout = () => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['logout'],
|
||||
mutationFn: async () => {
|
||||
const { data = {} } = await userService.logout();
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.logout'));
|
||||
authorizationUtil.removeAll();
|
||||
redirectToLogin();
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, logout: mutateAsync };
|
||||
};
|
||||
|
||||
export const useHandleSubmittable = (form: FormInstance) => {
|
||||
const [submittable, setSubmittable] = useState<boolean>(false);
|
||||
|
||||
// Watch all values
|
||||
const values = Form.useWatch([], form);
|
||||
|
||||
useEffect(() => {
|
||||
form
|
||||
.validateFields({ validateOnly: true })
|
||||
.then(() => setSubmittable(true))
|
||||
.catch(() => setSubmittable(false));
|
||||
}, [form, values]);
|
||||
|
||||
return { submittable };
|
||||
};
|
||||
17
web/src/hooks/plugin-hooks.tsx
Normal file
17
web/src/hooks/plugin-hooks.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { ILLMTools } from '@/interfaces/database/plugin';
|
||||
import pluginService from '@/services/plugin-service';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
export const useLlmToolsList = (): ILLMTools => {
|
||||
const { data } = useQuery({
|
||||
queryKey: ['llmTools'],
|
||||
initialData: [],
|
||||
queryFn: async () => {
|
||||
const { data } = await pluginService.getLlmTools();
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
91
web/src/hooks/route-hook.ts
Normal file
91
web/src/hooks/route-hook.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import {
|
||||
KnowledgeRouteKey,
|
||||
KnowledgeSearchParams,
|
||||
} from '@/constants/knowledge';
|
||||
import { useCallback } from 'react';
|
||||
import { useLocation, useNavigate, useSearchParams } from 'umi';
|
||||
|
||||
export enum SegmentIndex {
|
||||
Second = '2',
|
||||
Third = '3',
|
||||
}
|
||||
|
||||
export const useSegmentedPathName = (index: SegmentIndex) => {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const pathArray = pathname.split('/');
|
||||
return pathArray[index] || '';
|
||||
};
|
||||
|
||||
export const useSecondPathName = () => {
|
||||
return useSegmentedPathName(SegmentIndex.Second);
|
||||
};
|
||||
|
||||
export const useThirdPathName = () => {
|
||||
return useSegmentedPathName(SegmentIndex.Third);
|
||||
};
|
||||
|
||||
export const useGetKnowledgeSearchParams = () => {
|
||||
const [currentQueryParameters] = useSearchParams();
|
||||
|
||||
return {
|
||||
type: currentQueryParameters.get(KnowledgeSearchParams.Type) || '',
|
||||
documentId:
|
||||
currentQueryParameters.get(KnowledgeSearchParams.DocumentId) || '',
|
||||
knowledgeId:
|
||||
currentQueryParameters.get(KnowledgeSearchParams.KnowledgeId) || '',
|
||||
};
|
||||
};
|
||||
|
||||
export const useNavigateWithFromState = () => {
|
||||
const navigate = useNavigate();
|
||||
return useCallback(
|
||||
(path: string) => {
|
||||
navigate(path, { state: { from: path } });
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
};
|
||||
|
||||
export const useNavigateToDataset = () => {
|
||||
const navigate = useNavigate();
|
||||
const { knowledgeId } = useGetKnowledgeSearchParams();
|
||||
|
||||
return useCallback(() => {
|
||||
navigate(`/knowledge/${KnowledgeRouteKey.Dataset}?id=${knowledgeId}`);
|
||||
}, [knowledgeId, navigate]);
|
||||
};
|
||||
|
||||
export const useGetPaginationParams = () => {
|
||||
const [currentQueryParameters] = useSearchParams();
|
||||
|
||||
return {
|
||||
page: currentQueryParameters.get('page') || 1,
|
||||
size: currentQueryParameters.get('size') || 10,
|
||||
};
|
||||
};
|
||||
|
||||
export const useSetPaginationParams = () => {
|
||||
const [queryParameters, setSearchParams] = useSearchParams();
|
||||
// const newQueryParameters: URLSearchParams = useMemo(
|
||||
// () => new URLSearchParams(queryParameters.toString()),
|
||||
// [queryParameters],
|
||||
// );
|
||||
|
||||
const setPaginationParams = useCallback(
|
||||
(page: number = 1, pageSize?: number) => {
|
||||
queryParameters.set('page', page.toString());
|
||||
if (pageSize) {
|
||||
queryParameters.set('size', pageSize.toString());
|
||||
}
|
||||
setSearchParams(queryParameters);
|
||||
},
|
||||
[setSearchParams, queryParameters],
|
||||
);
|
||||
|
||||
return {
|
||||
setPaginationParams,
|
||||
page: Number(queryParameters.get('page')) || 1,
|
||||
size: Number(queryParameters.get('size')) || 50,
|
||||
};
|
||||
};
|
||||
18
web/src/hooks/system-hooks.ts
Normal file
18
web/src/hooks/system-hooks.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import userService from '@/services/user-service';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
/**
|
||||
* Hook to fetch system configuration including register enable status
|
||||
* @returns System configuration with loading status
|
||||
*/
|
||||
export const useSystemConfig = () => {
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ['systemConfig'],
|
||||
queryFn: async () => {
|
||||
const { data = {} } = await userService.getSystemConfig();
|
||||
return data.data || { registerEnabled: 1 }; // Default to enabling registration
|
||||
},
|
||||
});
|
||||
|
||||
return { config: data, loading: isLoading };
|
||||
};
|
||||
736
web/src/hooks/use-agent-request.ts
Normal file
736
web/src/hooks/use-agent-request.ts
Normal file
@@ -0,0 +1,736 @@
|
||||
import { FileUploadProps } from '@/components/file-upload';
|
||||
import { useHandleFilterSubmit } from '@/components/list-filter-bar/use-handle-filter-submit';
|
||||
import message from '@/components/ui/message';
|
||||
import { AgentGlobals } from '@/constants/agent';
|
||||
import {
|
||||
IAgentLogsRequest,
|
||||
IAgentLogsResponse,
|
||||
IFlow,
|
||||
IFlowTemplate,
|
||||
IPipeLineListRequest,
|
||||
ITraceData,
|
||||
} from '@/interfaces/database/agent';
|
||||
import { IDebugSingleRequestBody } from '@/interfaces/request/agent';
|
||||
import i18n from '@/locales/config';
|
||||
import { BeginId } from '@/pages/agent/constant';
|
||||
import { IInputs } from '@/pages/agent/interface';
|
||||
import { useGetSharedChatSearchParams } from '@/pages/chat/shared-hooks';
|
||||
import agentService, {
|
||||
fetchAgentLogsByCanvasId,
|
||||
fetchPipeLineList,
|
||||
fetchTrace,
|
||||
} from '@/services/agent-service';
|
||||
import api from '@/utils/api';
|
||||
import { buildMessageListWithUuid } from '@/utils/chat';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useDebounce } from 'ahooks';
|
||||
import { get, set } from 'lodash';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useParams, useSearchParams } from 'umi';
|
||||
import {
|
||||
useGetPaginationWithRouter,
|
||||
useHandleSearchChange,
|
||||
} from './logic-hooks';
|
||||
|
||||
export const enum AgentApiAction {
|
||||
FetchAgentListByPage = 'fetchAgentListByPage',
|
||||
FetchAgentList = 'fetchAgentList',
|
||||
UpdateAgentSetting = 'updateAgentSetting',
|
||||
DeleteAgent = 'deleteAgent',
|
||||
FetchAgentDetail = 'fetchAgentDetail',
|
||||
ResetAgent = 'resetAgent',
|
||||
SetAgent = 'setAgent',
|
||||
FetchAgentTemplates = 'fetchAgentTemplates',
|
||||
UploadCanvasFile = 'uploadCanvasFile',
|
||||
UploadCanvasFileWithProgress = 'uploadCanvasFileWithProgress',
|
||||
Trace = 'trace',
|
||||
TestDbConnect = 'testDbConnect',
|
||||
DebugSingle = 'debugSingle',
|
||||
FetchInputForm = 'fetchInputForm',
|
||||
FetchVersionList = 'fetchVersionList',
|
||||
FetchVersion = 'fetchVersion',
|
||||
FetchAgentAvatar = 'fetchAgentAvatar',
|
||||
FetchExternalAgentInputs = 'fetchExternalAgentInputs',
|
||||
SetAgentSetting = 'setAgentSetting',
|
||||
FetchPrompt = 'fetchPrompt',
|
||||
CancelDataflow = 'cancelDataflow',
|
||||
}
|
||||
|
||||
export const EmptyDsl = {
|
||||
graph: {
|
||||
nodes: [
|
||||
{
|
||||
id: BeginId,
|
||||
type: 'beginNode',
|
||||
position: {
|
||||
x: 50,
|
||||
y: 200,
|
||||
},
|
||||
data: {
|
||||
label: 'Begin',
|
||||
name: 'begin',
|
||||
},
|
||||
sourcePosition: 'left',
|
||||
targetPosition: 'right',
|
||||
},
|
||||
],
|
||||
edges: [],
|
||||
},
|
||||
components: {
|
||||
begin: {
|
||||
obj: {
|
||||
component_name: 'Begin',
|
||||
params: {},
|
||||
},
|
||||
downstream: [], // other edge target is downstream, edge source is current node id
|
||||
upstream: [], // edge source is upstream, edge target is current node id
|
||||
},
|
||||
},
|
||||
retrieval: [], // reference
|
||||
history: [],
|
||||
path: [],
|
||||
globals: {
|
||||
[AgentGlobals.SysQuery]: '',
|
||||
[AgentGlobals.SysUserId]: '',
|
||||
[AgentGlobals.SysConversationTurns]: 0,
|
||||
[AgentGlobals.SysFiles]: [],
|
||||
},
|
||||
};
|
||||
|
||||
export const useFetchAgentTemplates = () => {
|
||||
const { data } = useQuery<IFlowTemplate[]>({
|
||||
queryKey: [AgentApiAction.FetchAgentTemplates],
|
||||
initialData: [],
|
||||
queryFn: async () => {
|
||||
const { data } = await agentService.listTemplates();
|
||||
|
||||
return data.data;
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const useFetchAgentListByPage = () => {
|
||||
const { searchString, handleInputChange } = useHandleSearchChange();
|
||||
const { pagination, setPagination } = useGetPaginationWithRouter();
|
||||
const debouncedSearchString = useDebounce(searchString, { wait: 500 });
|
||||
const { filterValue, handleFilterSubmit } = useHandleFilterSubmit();
|
||||
const canvasCategory = Array.isArray(filterValue.canvasCategory)
|
||||
? filterValue.canvasCategory
|
||||
: [];
|
||||
const owner = filterValue.owner;
|
||||
|
||||
const requestParams: Record<string, any> = {
|
||||
keywords: debouncedSearchString,
|
||||
page_size: pagination.pageSize,
|
||||
page: pagination.current,
|
||||
canvas_category:
|
||||
canvasCategory.length === 1 ? canvasCategory[0] : undefined,
|
||||
};
|
||||
|
||||
if (Array.isArray(owner) && owner.length > 0) {
|
||||
requestParams.owner_ids = owner.join(',');
|
||||
}
|
||||
|
||||
const { data, isFetching: loading } = useQuery<{
|
||||
canvas: IFlow[];
|
||||
total: number;
|
||||
}>({
|
||||
queryKey: [
|
||||
AgentApiAction.FetchAgentListByPage,
|
||||
{
|
||||
debouncedSearchString,
|
||||
...pagination,
|
||||
filterValue,
|
||||
},
|
||||
],
|
||||
placeholderData: (previousData) => {
|
||||
if (previousData === undefined) {
|
||||
return { canvas: [], total: 0 };
|
||||
}
|
||||
return previousData;
|
||||
},
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await agentService.listCanvas(
|
||||
{
|
||||
params: requestParams,
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
return data?.data;
|
||||
},
|
||||
});
|
||||
|
||||
const onInputChange: React.ChangeEventHandler<HTMLInputElement> = useCallback(
|
||||
(e) => {
|
||||
// setPagination({ page: 1 });
|
||||
handleInputChange(e);
|
||||
},
|
||||
[handleInputChange],
|
||||
);
|
||||
|
||||
return {
|
||||
data: data?.canvas ?? [],
|
||||
loading,
|
||||
searchString,
|
||||
handleInputChange: onInputChange,
|
||||
pagination: { ...pagination, total: data?.total },
|
||||
setPagination,
|
||||
filterValue,
|
||||
handleFilterSubmit,
|
||||
};
|
||||
};
|
||||
|
||||
export const useUpdateAgentSetting = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [AgentApiAction.UpdateAgentSetting],
|
||||
mutationFn: async (params: any) => {
|
||||
const ret = await agentService.settingCanvas(params);
|
||||
if (ret?.data?.code === 0) {
|
||||
message.success('success');
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [AgentApiAction.FetchAgentListByPage],
|
||||
});
|
||||
} else {
|
||||
message.error(ret?.data?.data);
|
||||
}
|
||||
return ret?.data?.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, updateAgentSetting: mutateAsync };
|
||||
};
|
||||
|
||||
export const useDeleteAgent = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [AgentApiAction.DeleteAgent],
|
||||
mutationFn: async (canvasIds: string[]) => {
|
||||
const { data } = await agentService.removeCanvas({ canvasIds });
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [AgentApiAction.FetchAgentListByPage],
|
||||
});
|
||||
}
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, deleteAgent: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchAgent = (): {
|
||||
data: IFlow;
|
||||
loading: boolean;
|
||||
refetch: () => void;
|
||||
} => {
|
||||
const { id } = useParams();
|
||||
const { sharedId } = useGetSharedChatSearchParams();
|
||||
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery({
|
||||
queryKey: [AgentApiAction.FetchAgentDetail],
|
||||
initialData: {} as IFlow,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await agentService.fetchCanvas(sharedId || id);
|
||||
|
||||
const messageList = buildMessageListWithUuid(
|
||||
get(data, 'data.dsl.messages', []),
|
||||
);
|
||||
set(data, 'data.dsl.messages', messageList);
|
||||
|
||||
return data?.data ?? {};
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
|
||||
export const useResetAgent = () => {
|
||||
const { id } = useParams();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [AgentApiAction.ResetAgent],
|
||||
mutationFn: async () => {
|
||||
const { data } = await agentService.resetCanvas({ id });
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, resetAgent: mutateAsync };
|
||||
};
|
||||
|
||||
export const useSetAgent = (showMessage: boolean = true) => {
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [AgentApiAction.SetAgent],
|
||||
mutationFn: async (params: {
|
||||
id?: string;
|
||||
title?: string;
|
||||
dsl?: Record<string, any>;
|
||||
avatar?: string;
|
||||
canvas_category?: string;
|
||||
}) => {
|
||||
const { data = {} } = await agentService.setCanvas(params);
|
||||
if (data.code === 0) {
|
||||
if (showMessage) {
|
||||
message.success(
|
||||
i18n.t(`message.${params?.id ? 'modified' : 'created'}`),
|
||||
);
|
||||
}
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [AgentApiAction.FetchAgentListByPage],
|
||||
});
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, setAgent: mutateAsync };
|
||||
};
|
||||
|
||||
// Only one file can be uploaded at a time
|
||||
export const useUploadCanvasFile = () => {
|
||||
const { id } = useParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
const shared_id = searchParams.get('shared_id');
|
||||
const canvasId = id || shared_id;
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [AgentApiAction.UploadCanvasFile],
|
||||
mutationFn: async (body: any) => {
|
||||
let nextBody = body;
|
||||
try {
|
||||
if (Array.isArray(body)) {
|
||||
nextBody = new FormData();
|
||||
body.forEach((file: File) => {
|
||||
nextBody.append('file', file as any);
|
||||
});
|
||||
}
|
||||
|
||||
const { data } = await agentService.uploadCanvasFile(
|
||||
{ url: api.uploadAgentFile(canvasId as string), data: nextBody },
|
||||
true,
|
||||
);
|
||||
if (data?.code === 0) {
|
||||
message.success(i18n.t('message.uploaded'));
|
||||
}
|
||||
return data;
|
||||
} catch (error) {
|
||||
message.error('error');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, uploadCanvasFile: mutateAsync };
|
||||
};
|
||||
|
||||
export const useUploadCanvasFileWithProgress = (
|
||||
identifier?: Nullable<string>,
|
||||
) => {
|
||||
const { id } = useParams();
|
||||
|
||||
type UploadParameters = Parameters<NonNullable<FileUploadProps['onUpload']>>;
|
||||
|
||||
type X = { files: UploadParameters[0]; options: UploadParameters[1] };
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [AgentApiAction.UploadCanvasFileWithProgress],
|
||||
mutationFn: async ({
|
||||
files,
|
||||
options: { onError, onSuccess, onProgress },
|
||||
}: X) => {
|
||||
const formData = new FormData();
|
||||
try {
|
||||
if (Array.isArray(files)) {
|
||||
files.forEach((file: File) => {
|
||||
formData.append('file', file);
|
||||
});
|
||||
}
|
||||
|
||||
const { data } = await agentService.uploadCanvasFile(
|
||||
{
|
||||
url: api.uploadAgentFile(identifier || id),
|
||||
data: formData,
|
||||
onUploadProgress: ({ progress }) => {
|
||||
files.forEach((file) => {
|
||||
onProgress(file, (progress || 0) * 100);
|
||||
});
|
||||
},
|
||||
},
|
||||
true,
|
||||
);
|
||||
if (data?.code === 0) {
|
||||
files.forEach((file) => {
|
||||
onSuccess(file);
|
||||
});
|
||||
message.success(i18n.t('message.uploaded'));
|
||||
}
|
||||
return data;
|
||||
} catch (error) {
|
||||
files.forEach((file) => {
|
||||
onError(file, error as Error);
|
||||
});
|
||||
message.error((error as Error)?.message || 'Upload failed');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, uploadCanvasFile: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchMessageTrace = (canvasId?: string) => {
|
||||
const { id } = useParams();
|
||||
const queryId = id || canvasId;
|
||||
const [messageId, setMessageId] = useState('');
|
||||
const [isStopFetchTrace, setISStopFetchTrace] = useState(false);
|
||||
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery<ITraceData[]>({
|
||||
queryKey: [AgentApiAction.Trace, queryId, messageId],
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
gcTime: 0,
|
||||
enabled: !!queryId && !!messageId,
|
||||
refetchInterval: !isStopFetchTrace ? 3000 : false,
|
||||
queryFn: async () => {
|
||||
const { data } = await fetchTrace({
|
||||
canvas_id: queryId as string,
|
||||
message_id: messageId,
|
||||
});
|
||||
|
||||
return Array.isArray(data?.data) ? data?.data : [];
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
data,
|
||||
loading,
|
||||
refetch,
|
||||
setMessageId,
|
||||
messageId,
|
||||
isStopFetchTrace,
|
||||
setISStopFetchTrace,
|
||||
};
|
||||
};
|
||||
|
||||
export const useTestDbConnect = () => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [AgentApiAction.TestDbConnect],
|
||||
mutationFn: async (params: any) => {
|
||||
const ret = await agentService.testDbConnect(params);
|
||||
if (ret?.data?.code === 0) {
|
||||
message.success(ret?.data?.data);
|
||||
} else {
|
||||
message.error(ret?.data?.data);
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, testDbConnect: mutateAsync };
|
||||
};
|
||||
|
||||
export const useDebugSingle = () => {
|
||||
const { id } = useParams();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [AgentApiAction.FetchInputForm],
|
||||
mutationFn: async (params: IDebugSingleRequestBody) => {
|
||||
const ret = await agentService.debugSingle({ id, ...params });
|
||||
if (ret?.data?.code !== 0) {
|
||||
message.error(ret?.data?.message);
|
||||
}
|
||||
return ret?.data?.data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, debugSingle: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchInputForm = (componentId?: string) => {
|
||||
const { id } = useParams();
|
||||
|
||||
const { data } = useQuery<Record<string, any>>({
|
||||
queryKey: [AgentApiAction.FetchInputForm],
|
||||
initialData: {},
|
||||
enabled: !!id && !!componentId,
|
||||
queryFn: async () => {
|
||||
const { data } = await agentService.inputForm(
|
||||
{
|
||||
params: {
|
||||
id,
|
||||
component_id: componentId,
|
||||
},
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
return data.data;
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const useFetchVersionList = () => {
|
||||
const { id } = useParams();
|
||||
const { data, isFetching: loading } = useQuery<
|
||||
Array<{ created_at: string; title: string; id: string }>
|
||||
>({
|
||||
queryKey: [AgentApiAction.FetchVersionList],
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await agentService.fetchVersionList(id);
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useFetchVersion = (
|
||||
version_id?: string,
|
||||
): {
|
||||
data?: IFlow;
|
||||
loading: boolean;
|
||||
} => {
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: [AgentApiAction.FetchVersion, version_id],
|
||||
initialData: undefined,
|
||||
gcTime: 0,
|
||||
enabled: !!version_id, // Only call API when both values are provided
|
||||
queryFn: async () => {
|
||||
if (!version_id) return undefined;
|
||||
|
||||
const { data } = await agentService.fetchVersion(version_id);
|
||||
|
||||
return data?.data ?? undefined;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useFetchAgentAvatar = (): {
|
||||
data: IFlow;
|
||||
loading: boolean;
|
||||
refetch: () => void;
|
||||
} => {
|
||||
const { sharedId } = useGetSharedChatSearchParams();
|
||||
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery({
|
||||
queryKey: [AgentApiAction.FetchAgentAvatar],
|
||||
initialData: {} as IFlow,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
if (!sharedId) return {};
|
||||
const { data } = await agentService.fetchAgentAvatar(sharedId);
|
||||
|
||||
return data?.data ?? {};
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
|
||||
export const useFetchAgentLog = (searchParams: IAgentLogsRequest) => {
|
||||
const { id } = useParams();
|
||||
const { data, isFetching: loading } = useQuery<IAgentLogsResponse>({
|
||||
queryKey: ['fetchAgentLog', id, searchParams],
|
||||
initialData: {} as IAgentLogsResponse,
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await fetchAgentLogsByCanvasId(id as string, {
|
||||
...searchParams,
|
||||
});
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useFetchExternalAgentInputs = () => {
|
||||
const { sharedId } = useGetSharedChatSearchParams();
|
||||
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery<IInputs>({
|
||||
queryKey: [AgentApiAction.FetchExternalAgentInputs],
|
||||
initialData: {} as IInputs,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
gcTime: 0,
|
||||
enabled: !!sharedId,
|
||||
queryFn: async () => {
|
||||
const { data } = await agentService.fetchExternalAgentInputs(sharedId!);
|
||||
|
||||
return data?.data ?? {};
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
|
||||
export const useSetAgentSetting = () => {
|
||||
const { id } = useParams();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [AgentApiAction.SetAgentSetting],
|
||||
mutationFn: async (params: any) => {
|
||||
const ret = await agentService.settingCanvas({ id, ...params });
|
||||
if (ret?.data?.code === 0) {
|
||||
message.success('success');
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [AgentApiAction.FetchAgentDetail],
|
||||
});
|
||||
} else {
|
||||
message.error(ret?.data?.data);
|
||||
}
|
||||
return ret?.data?.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, setAgentSetting: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchPrompt = () => {
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery<Record<string, string>>({
|
||||
queryKey: [AgentApiAction.FetchPrompt],
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await agentService.fetchPrompt();
|
||||
|
||||
return data?.data ?? {};
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
|
||||
export const useFetchAgentList = ({
|
||||
canvas_category,
|
||||
}: IPipeLineListRequest) => {
|
||||
const { data, isFetching: loading } = useQuery<{
|
||||
canvas: IFlow[];
|
||||
total: number;
|
||||
}>({
|
||||
queryKey: [AgentApiAction.FetchAgentList],
|
||||
initialData: { canvas: [], total: 0 },
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await fetchPipeLineList({ canvas_category });
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useCancelDataflow = () => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [AgentApiAction.CancelDataflow],
|
||||
mutationFn: async (taskId: string) => {
|
||||
const ret = await agentService.cancelDataflow(taskId);
|
||||
if (ret?.data?.code === 0) {
|
||||
message.success('success');
|
||||
} else {
|
||||
message.error(ret?.data?.data);
|
||||
}
|
||||
return ret?.data?.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, cancelDataflow: mutateAsync };
|
||||
};
|
||||
|
||||
// export const useFetchKnowledgeList = () => {
|
||||
// const { data, isFetching: loading } = useQuery<IFlow[]>({
|
||||
// queryKey: [AgentApiAction.FetchAgentList],
|
||||
// initialData: [],
|
||||
// gcTime: 0, // https://tanstack.com/query/latest/docs/framework/react/guides/caching?from=reactQueryV3
|
||||
// queryFn: async () => {
|
||||
// const { data } = await agentService.listCanvas();
|
||||
|
||||
// return data?.data ?? [];
|
||||
// },
|
||||
// });
|
||||
|
||||
// return { list: data, loading };
|
||||
// };
|
||||
27
web/src/hooks/use-callback-ref.ts
Normal file
27
web/src/hooks/use-callback-ref.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import * as React from 'react';
|
||||
|
||||
/**
|
||||
* @see https://github.com/radix-ui/primitives/blob/main/packages/react/use-callback-ref/src/useCallbackRef.tsx
|
||||
*/
|
||||
|
||||
/**
|
||||
* A custom hook that converts a callback to a ref to avoid triggering re-renders when passed as a
|
||||
* prop or avoid re-executing effects when passed as a dependency
|
||||
*/
|
||||
function useCallbackRef<T extends (...args: never[]) => unknown>(
|
||||
callback: T | undefined,
|
||||
): T {
|
||||
const callbackRef = React.useRef(callback);
|
||||
|
||||
React.useEffect(() => {
|
||||
callbackRef.current = callback;
|
||||
});
|
||||
|
||||
// https://github.com/facebook/react/issues/19240
|
||||
return React.useMemo(
|
||||
() => ((...args) => callbackRef.current?.(...args)) as T,
|
||||
[],
|
||||
);
|
||||
}
|
||||
|
||||
export { useCallbackRef };
|
||||
532
web/src/hooks/use-chat-request.ts
Normal file
532
web/src/hooks/use-chat-request.ts
Normal file
@@ -0,0 +1,532 @@
|
||||
import { FileUploadProps } from '@/components/file-upload';
|
||||
import message from '@/components/ui/message';
|
||||
import { ChatSearchParams } from '@/constants/chat';
|
||||
import {
|
||||
IConversation,
|
||||
IDialog,
|
||||
IExternalChatInfo,
|
||||
} from '@/interfaces/database/chat';
|
||||
import { IAskRequestBody } from '@/interfaces/request/chat';
|
||||
import { IClientConversation } from '@/pages/next-chats/chat/interface';
|
||||
import { useGetSharedChatSearchParams } from '@/pages/next-chats/hooks/use-send-shared-message';
|
||||
import { isConversationIdExist } from '@/pages/next-chats/utils';
|
||||
import chatService from '@/services/next-chat-service';
|
||||
import { buildMessageListWithUuid, getConversationId } from '@/utils/chat';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useDebounce } from 'ahooks';
|
||||
import { has } from 'lodash';
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams, useSearchParams } from 'umi';
|
||||
import {
|
||||
useGetPaginationWithRouter,
|
||||
useHandleSearchChange,
|
||||
} from './logic-hooks';
|
||||
import { useHandleSearchStrChange } from './logic-hooks/use-change-search';
|
||||
|
||||
export const enum ChatApiAction {
|
||||
FetchDialogList = 'fetchDialogList',
|
||||
RemoveDialog = 'removeDialog',
|
||||
SetDialog = 'setDialog',
|
||||
FetchDialog = 'fetchDialog',
|
||||
FetchConversationList = 'fetchConversationList',
|
||||
FetchConversation = 'fetchConversation',
|
||||
UpdateConversation = 'updateConversation',
|
||||
RemoveConversation = 'removeConversation',
|
||||
DeleteMessage = 'deleteMessage',
|
||||
FetchMindMap = 'fetchMindMap',
|
||||
FetchRelatedQuestions = 'fetchRelatedQuestions',
|
||||
UploadAndParse = 'upload_and_parse',
|
||||
FetchExternalChatInfo = 'fetchExternalChatInfo',
|
||||
}
|
||||
|
||||
export const useGetChatSearchParams = () => {
|
||||
const [currentQueryParameters] = useSearchParams();
|
||||
|
||||
return {
|
||||
dialogId: currentQueryParameters.get(ChatSearchParams.DialogId) || '',
|
||||
conversationId:
|
||||
currentQueryParameters.get(ChatSearchParams.ConversationId) || '',
|
||||
isNew: currentQueryParameters.get(ChatSearchParams.isNew) || '',
|
||||
};
|
||||
};
|
||||
|
||||
export const useClickDialogCard = () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [_, setSearchParams] = useSearchParams();
|
||||
|
||||
const newQueryParameters: URLSearchParams = useMemo(() => {
|
||||
return new URLSearchParams();
|
||||
}, []);
|
||||
|
||||
const handleClickDialog = useCallback(
|
||||
(dialogId: string) => {
|
||||
newQueryParameters.set(ChatSearchParams.DialogId, dialogId);
|
||||
// newQueryParameters.set(
|
||||
// ChatSearchParams.ConversationId,
|
||||
// EmptyConversationId,
|
||||
// );
|
||||
setSearchParams(newQueryParameters);
|
||||
},
|
||||
[newQueryParameters, setSearchParams],
|
||||
);
|
||||
|
||||
return { handleClickDialog };
|
||||
};
|
||||
|
||||
export const useFetchDialogList = () => {
|
||||
const { searchString, handleInputChange } = useHandleSearchChange();
|
||||
const { pagination, setPagination } = useGetPaginationWithRouter();
|
||||
const debouncedSearchString = useDebounce(searchString, { wait: 500 });
|
||||
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery<{ dialogs: IDialog[]; total: number }>({
|
||||
queryKey: [
|
||||
ChatApiAction.FetchDialogList,
|
||||
{
|
||||
debouncedSearchString,
|
||||
...pagination,
|
||||
},
|
||||
],
|
||||
initialData: { dialogs: [], total: 0 },
|
||||
gcTime: 0,
|
||||
refetchOnWindowFocus: false,
|
||||
queryFn: async () => {
|
||||
const { data } = await chatService.listDialog(
|
||||
{
|
||||
params: {
|
||||
keywords: debouncedSearchString,
|
||||
page_size: pagination.pageSize,
|
||||
page: pagination.current,
|
||||
},
|
||||
data: {},
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
return data?.data ?? { dialogs: [], total: 0 };
|
||||
},
|
||||
});
|
||||
|
||||
const onInputChange: React.ChangeEventHandler<HTMLInputElement> = useCallback(
|
||||
(e) => {
|
||||
handleInputChange(e);
|
||||
},
|
||||
[handleInputChange],
|
||||
);
|
||||
|
||||
return {
|
||||
data,
|
||||
loading,
|
||||
refetch,
|
||||
searchString,
|
||||
handleInputChange: onInputChange,
|
||||
pagination: { ...pagination, total: data?.total },
|
||||
setPagination,
|
||||
};
|
||||
};
|
||||
|
||||
export const useRemoveDialog = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [ChatApiAction.RemoveDialog],
|
||||
mutationFn: async (dialogIds: string[]) => {
|
||||
const { data } = await chatService.removeDialog({ dialogIds });
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchDialogList'] });
|
||||
|
||||
message.success(t('message.deleted'));
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, removeDialog: mutateAsync };
|
||||
};
|
||||
|
||||
export const useSetDialog = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [ChatApiAction.SetDialog],
|
||||
mutationFn: async (params: Partial<IDialog>) => {
|
||||
const { data } = await chatService.setDialog(params);
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({
|
||||
exact: false,
|
||||
queryKey: [ChatApiAction.FetchDialogList],
|
||||
});
|
||||
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ChatApiAction.FetchDialog],
|
||||
});
|
||||
|
||||
message.success(
|
||||
t(`message.${params.dialog_id ? 'modified' : 'created'}`),
|
||||
);
|
||||
}
|
||||
return data?.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, setDialog: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchDialog = () => {
|
||||
const { id } = useParams();
|
||||
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery<IDialog>({
|
||||
queryKey: [ChatApiAction.FetchDialog, id],
|
||||
gcTime: 0,
|
||||
initialData: {} as IDialog,
|
||||
enabled: !!id,
|
||||
refetchOnWindowFocus: false,
|
||||
queryFn: async () => {
|
||||
const { data } = await chatService.getDialog(
|
||||
{ params: { dialogId: id } },
|
||||
true,
|
||||
);
|
||||
|
||||
return data?.data ?? ({} as IDialog);
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
|
||||
//#region Conversation
|
||||
|
||||
export const useClickConversationCard = () => {
|
||||
const [currentQueryParameters, setSearchParams] = useSearchParams();
|
||||
const newQueryParameters: URLSearchParams = useMemo(
|
||||
() => new URLSearchParams(currentQueryParameters.toString()),
|
||||
[currentQueryParameters],
|
||||
);
|
||||
|
||||
const handleClickConversation = useCallback(
|
||||
(conversationId: string, isNew: string) => {
|
||||
newQueryParameters.set(ChatSearchParams.ConversationId, conversationId);
|
||||
newQueryParameters.set(ChatSearchParams.isNew, isNew);
|
||||
setSearchParams(newQueryParameters);
|
||||
},
|
||||
[setSearchParams, newQueryParameters],
|
||||
);
|
||||
|
||||
return { handleClickConversation };
|
||||
};
|
||||
|
||||
export const useFetchConversationList = () => {
|
||||
const { id } = useParams();
|
||||
const { handleClickConversation } = useClickConversationCard();
|
||||
|
||||
const { searchString, handleInputChange } = useHandleSearchStrChange();
|
||||
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery<IConversation[]>({
|
||||
queryKey: [ChatApiAction.FetchConversationList, id],
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
refetchOnWindowFocus: false,
|
||||
enabled: !!id,
|
||||
select(data) {
|
||||
return searchString
|
||||
? data.filter((x) => x.name.includes(searchString))
|
||||
: data;
|
||||
},
|
||||
queryFn: async () => {
|
||||
const { data } = await chatService.listConversation(
|
||||
{ params: { dialog_id: id } },
|
||||
true,
|
||||
);
|
||||
if (data.code === 0) {
|
||||
if (data.data.length > 0) {
|
||||
handleClickConversation(data.data[0].id, '');
|
||||
} else {
|
||||
handleClickConversation('', '');
|
||||
}
|
||||
}
|
||||
return data?.data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch, searchString, handleInputChange };
|
||||
};
|
||||
|
||||
export const useFetchConversation = () => {
|
||||
const { isNew, conversationId } = useGetChatSearchParams();
|
||||
const { sharedId } = useGetSharedChatSearchParams();
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery<IClientConversation>({
|
||||
queryKey: [ChatApiAction.FetchConversation, conversationId],
|
||||
initialData: {} as IClientConversation,
|
||||
// enabled: isConversationIdExist(conversationId),
|
||||
gcTime: 0,
|
||||
refetchOnWindowFocus: false,
|
||||
queryFn: async () => {
|
||||
if (
|
||||
isNew !== 'true' &&
|
||||
isConversationIdExist(sharedId || conversationId)
|
||||
) {
|
||||
const { data } = await chatService.getConversation(
|
||||
{
|
||||
params: {
|
||||
conversationId: conversationId || sharedId,
|
||||
},
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
const conversation = data?.data ?? {};
|
||||
|
||||
const messageList = buildMessageListWithUuid(conversation?.message);
|
||||
|
||||
return { ...conversation, message: messageList };
|
||||
}
|
||||
return { message: [] };
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
|
||||
export const useUpdateConversation = () => {
|
||||
const { t } = useTranslation();
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [ChatApiAction.UpdateConversation],
|
||||
mutationFn: async (params: Record<string, any>) => {
|
||||
const { data } = await chatService.setConversation({
|
||||
...params,
|
||||
conversation_id: params.conversation_id
|
||||
? params.conversation_id
|
||||
: getConversationId(),
|
||||
});
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ChatApiAction.FetchConversationList],
|
||||
});
|
||||
message.success(t(`message.modified`));
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, updateConversation: mutateAsync };
|
||||
};
|
||||
|
||||
export const useRemoveConversation = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { dialogId } = useGetChatSearchParams();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [ChatApiAction.RemoveConversation],
|
||||
mutationFn: async (conversationIds: string[]) => {
|
||||
const { data } = await chatService.removeConversation({
|
||||
conversationIds,
|
||||
dialogId,
|
||||
});
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ChatApiAction.FetchConversationList],
|
||||
});
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, removeConversation: mutateAsync };
|
||||
};
|
||||
|
||||
export const useDeleteMessage = () => {
|
||||
const { conversationId } = useGetChatSearchParams();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [ChatApiAction.DeleteMessage],
|
||||
mutationFn: async (messageId: string) => {
|
||||
const { data } = await chatService.deleteMessage({
|
||||
messageId,
|
||||
conversationId,
|
||||
});
|
||||
|
||||
if (data.code === 0) {
|
||||
message.success(t(`message.deleted`));
|
||||
}
|
||||
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, deleteMessage: mutateAsync };
|
||||
};
|
||||
|
||||
type UploadParameters = Parameters<NonNullable<FileUploadProps['onUpload']>>;
|
||||
|
||||
type X = {
|
||||
file: UploadParameters[0][0];
|
||||
options: UploadParameters[1];
|
||||
conversationId?: string;
|
||||
};
|
||||
|
||||
export function useUploadAndParseFile() {
|
||||
const { conversationId: id } = useGetChatSearchParams();
|
||||
const { t } = useTranslation();
|
||||
const controller = useRef(new AbortController());
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [ChatApiAction.UploadAndParse],
|
||||
mutationFn: async ({
|
||||
file,
|
||||
options: { onProgress, onSuccess, onError },
|
||||
conversationId,
|
||||
}: X) => {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('conversation_id', conversationId || id);
|
||||
|
||||
const { data } = await chatService.uploadAndParse(
|
||||
{
|
||||
signal: controller.current.signal,
|
||||
data: formData,
|
||||
onUploadProgress: ({ progress }) => {
|
||||
onProgress(file, (progress || 0) * 100 - 1);
|
||||
},
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
onProgress(file, 100);
|
||||
|
||||
if (data.code === 0) {
|
||||
onSuccess(file);
|
||||
message.success(t(`message.uploaded`));
|
||||
} else {
|
||||
onError(file, new Error(data.message));
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
onError(file, error as Error);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const cancel = useCallback(() => {
|
||||
controller.current.abort();
|
||||
controller.current = new AbortController();
|
||||
}, [controller]);
|
||||
|
||||
return { data, loading, uploadAndParseFile: mutateAsync, cancel };
|
||||
}
|
||||
|
||||
export const useFetchExternalChatInfo = () => {
|
||||
const { sharedId: id } = useGetSharedChatSearchParams();
|
||||
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery<IExternalChatInfo>({
|
||||
queryKey: [ChatApiAction.FetchExternalChatInfo, id],
|
||||
gcTime: 0,
|
||||
initialData: {} as IExternalChatInfo,
|
||||
enabled: !!id,
|
||||
refetchOnWindowFocus: false,
|
||||
queryFn: async () => {
|
||||
const { data } = await chatService.fetchExternalChatInfo(id!);
|
||||
|
||||
return data?.data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region search page
|
||||
|
||||
export const useFetchMindMap = () => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [ChatApiAction.FetchMindMap],
|
||||
gcTime: 0,
|
||||
mutationFn: async (params: IAskRequestBody) => {
|
||||
try {
|
||||
const ret = await chatService.getMindMap(params);
|
||||
return ret?.data?.data ?? {};
|
||||
} catch (error: any) {
|
||||
if (has(error, 'message')) {
|
||||
message.error(error.message);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, fetchMindMap: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchRelatedQuestions = () => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [ChatApiAction.FetchRelatedQuestions],
|
||||
gcTime: 0,
|
||||
mutationFn: async (question: string): Promise<string[]> => {
|
||||
const { data } = await chatService.getRelatedQuestions({ question });
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, fetchRelatedQuestions: mutateAsync };
|
||||
};
|
||||
//#endregion
|
||||
120
web/src/hooks/use-chunk-request.ts
Normal file
120
web/src/hooks/use-chunk-request.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import message from '@/components/ui/message';
|
||||
import { ResponseGetType } from '@/interfaces/database/base';
|
||||
import { IChunk, IKnowledgeFile } from '@/interfaces/database/knowledge';
|
||||
import kbService from '@/services/knowledge-service';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { useDebounce } from 'ahooks';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { IChunkListResult } from './chunk-hooks';
|
||||
import {
|
||||
useGetPaginationWithRouter,
|
||||
useHandleSearchChange,
|
||||
} from './logic-hooks';
|
||||
import { useGetKnowledgeSearchParams } from './route-hook';
|
||||
|
||||
export const useFetchNextChunkList = (
|
||||
enabled = true,
|
||||
): ResponseGetType<{
|
||||
data: IChunk[];
|
||||
total: number;
|
||||
documentInfo: IKnowledgeFile;
|
||||
}> &
|
||||
IChunkListResult => {
|
||||
const { pagination, setPagination } = useGetPaginationWithRouter();
|
||||
const { documentId } = useGetKnowledgeSearchParams();
|
||||
const { searchString, handleInputChange } = useHandleSearchChange();
|
||||
const [available, setAvailable] = useState<number | undefined>();
|
||||
const debouncedSearchString = useDebounce(searchString, { wait: 500 });
|
||||
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: [
|
||||
'fetchChunkList',
|
||||
documentId,
|
||||
pagination.current,
|
||||
pagination.pageSize,
|
||||
debouncedSearchString,
|
||||
available,
|
||||
],
|
||||
placeholderData: (previousData: any) =>
|
||||
previousData ?? { data: [], total: 0, documentInfo: {} }, // https://github.com/TanStack/query/issues/8183
|
||||
gcTime: 0,
|
||||
enabled,
|
||||
queryFn: async () => {
|
||||
const { data } = await kbService.chunk_list({
|
||||
doc_id: documentId,
|
||||
page: pagination.current,
|
||||
size: pagination.pageSize,
|
||||
available_int: available,
|
||||
keywords: searchString,
|
||||
});
|
||||
if (data.code === 0) {
|
||||
const res = data.data;
|
||||
return {
|
||||
data: res.chunks,
|
||||
total: res.total,
|
||||
documentInfo: res.doc,
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
data?.data ?? {
|
||||
data: [],
|
||||
total: 0,
|
||||
documentInfo: {},
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const onInputChange: React.ChangeEventHandler<HTMLInputElement> = useCallback(
|
||||
(e) => {
|
||||
setPagination({ page: 1 });
|
||||
handleInputChange(e);
|
||||
},
|
||||
[handleInputChange, setPagination],
|
||||
);
|
||||
|
||||
const handleSetAvailable = useCallback(
|
||||
(a: number | undefined) => {
|
||||
setPagination({ page: 1 });
|
||||
setAvailable(a);
|
||||
},
|
||||
[setAvailable, setPagination],
|
||||
);
|
||||
|
||||
return {
|
||||
data,
|
||||
loading,
|
||||
pagination,
|
||||
setPagination,
|
||||
searchString,
|
||||
handleInputChange: onInputChange,
|
||||
available,
|
||||
handleSetAvailable,
|
||||
};
|
||||
};
|
||||
|
||||
export const useSwitchChunk = () => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['switchChunk'],
|
||||
mutationFn: async (params: {
|
||||
chunk_ids?: string[];
|
||||
available_int?: number;
|
||||
doc_id: string;
|
||||
}) => {
|
||||
const { data } = await kbService.switch_chunk(params);
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.modified'));
|
||||
}
|
||||
return data?.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, switchChunk: mutateAsync };
|
||||
};
|
||||
67
web/src/hooks/use-controllable-state.ts
Normal file
67
web/src/hooks/use-controllable-state.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { useCallbackRef } from '@/hooks/use-callback-ref';
|
||||
|
||||
/**
|
||||
* @see https://github.com/radix-ui/primitives/blob/main/packages/react/use-controllable-state/src/useControllableState.tsx
|
||||
*/
|
||||
|
||||
type UseControllableStateParams<T> = {
|
||||
prop?: T | undefined;
|
||||
defaultProp?: T | undefined;
|
||||
onChange?: (state: T) => void;
|
||||
};
|
||||
|
||||
type SetStateFn<T> = (prevState?: T) => T;
|
||||
|
||||
function useUncontrolledState<T>({
|
||||
defaultProp,
|
||||
onChange,
|
||||
}: Omit<UseControllableStateParams<T>, 'prop'>) {
|
||||
const uncontrolledState = React.useState<T | undefined>(defaultProp);
|
||||
const [value] = uncontrolledState;
|
||||
const prevValueRef = React.useRef(value);
|
||||
const handleChange = useCallbackRef(onChange);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (prevValueRef.current !== value) {
|
||||
handleChange(value as T);
|
||||
prevValueRef.current = value;
|
||||
}
|
||||
}, [value, prevValueRef, handleChange]);
|
||||
|
||||
return uncontrolledState;
|
||||
}
|
||||
|
||||
function useControllableState<T>({
|
||||
prop,
|
||||
defaultProp,
|
||||
onChange = () => {},
|
||||
}: UseControllableStateParams<T>) {
|
||||
const [uncontrolledProp, setUncontrolledProp] = useUncontrolledState({
|
||||
defaultProp,
|
||||
onChange,
|
||||
});
|
||||
const isControlled = prop !== undefined;
|
||||
const value = isControlled ? prop : uncontrolledProp;
|
||||
const handleChange = useCallbackRef(onChange);
|
||||
|
||||
const setValue: React.Dispatch<React.SetStateAction<T | undefined>> =
|
||||
React.useCallback(
|
||||
(nextValue) => {
|
||||
if (isControlled) {
|
||||
const setter = nextValue as SetStateFn<T>;
|
||||
const value =
|
||||
typeof nextValue === 'function' ? setter(prop) : nextValue;
|
||||
if (value !== prop) handleChange(value as T);
|
||||
} else {
|
||||
setUncontrolledProp(nextValue);
|
||||
}
|
||||
},
|
||||
[isControlled, prop, setUncontrolledProp, handleChange],
|
||||
);
|
||||
|
||||
return [value, setValue] as const;
|
||||
}
|
||||
|
||||
export { useControllableState };
|
||||
91
web/src/hooks/use-dataflow-request.ts
Normal file
91
web/src/hooks/use-dataflow-request.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import message from '@/components/ui/message';
|
||||
import { IFlow } from '@/interfaces/database/agent';
|
||||
import dataflowService from '@/services/dataflow-service';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'umi';
|
||||
|
||||
export const enum DataflowApiAction {
|
||||
ListDataflow = 'listDataflow',
|
||||
RemoveDataflow = 'removeDataflow',
|
||||
FetchDataflow = 'fetchDataflow',
|
||||
RunDataflow = 'runDataflow',
|
||||
SetDataflow = 'setDataflow',
|
||||
}
|
||||
|
||||
export const useRemoveDataflow = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [DataflowApiAction.RemoveDataflow],
|
||||
mutationFn: async (ids: string[]) => {
|
||||
const { data } = await dataflowService.removeDataflow({
|
||||
canvas_ids: ids,
|
||||
});
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [DataflowApiAction.ListDataflow],
|
||||
});
|
||||
|
||||
message.success(t('message.deleted'));
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, removeDataflow: mutateAsync };
|
||||
};
|
||||
|
||||
export const useSetDataflow = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [DataflowApiAction.SetDataflow],
|
||||
mutationFn: async (params: Partial<IFlow>) => {
|
||||
const { data } = await dataflowService.setDataflow(params);
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [DataflowApiAction.FetchDataflow],
|
||||
});
|
||||
|
||||
message.success(t(`message.${params.id ? 'modified' : 'created'}`));
|
||||
}
|
||||
return data?.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, setDataflow: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchDataflow = () => {
|
||||
const { id } = useParams();
|
||||
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery<IFlow>({
|
||||
queryKey: [DataflowApiAction.FetchDataflow, id],
|
||||
gcTime: 0,
|
||||
initialData: {} as IFlow,
|
||||
enabled: !!id,
|
||||
refetchOnWindowFocus: false,
|
||||
queryFn: async () => {
|
||||
const { data } = await dataflowService.fetchDataflow(id);
|
||||
|
||||
return data?.data ?? ({} as IFlow);
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
432
web/src/hooks/use-document-request.ts
Normal file
432
web/src/hooks/use-document-request.ts
Normal file
@@ -0,0 +1,432 @@
|
||||
import { useHandleFilterSubmit } from '@/components/list-filter-bar/use-handle-filter-submit';
|
||||
import message from '@/components/ui/message';
|
||||
import { ResponseType } from '@/interfaces/database/base';
|
||||
import {
|
||||
IDocumentInfo,
|
||||
IDocumentInfoFilter,
|
||||
} from '@/interfaces/database/document';
|
||||
import {
|
||||
IChangeParserConfigRequestBody,
|
||||
IDocumentMetaRequestBody,
|
||||
} from '@/interfaces/request/document';
|
||||
import i18n from '@/locales/config';
|
||||
import kbService, { listDocument } from '@/services/knowledge-service';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useDebounce } from 'ahooks';
|
||||
import { get } from 'lodash';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useParams } from 'umi';
|
||||
import {
|
||||
useGetPaginationWithRouter,
|
||||
useHandleSearchChange,
|
||||
} from './logic-hooks';
|
||||
import {
|
||||
useGetKnowledgeSearchParams,
|
||||
useSetPaginationParams,
|
||||
} from './route-hook';
|
||||
|
||||
export const enum DocumentApiAction {
|
||||
UploadDocument = 'uploadDocument',
|
||||
FetchDocumentList = 'fetchDocumentList',
|
||||
UpdateDocumentStatus = 'updateDocumentStatus',
|
||||
RunDocumentByIds = 'runDocumentByIds',
|
||||
RemoveDocument = 'removeDocument',
|
||||
SaveDocumentName = 'saveDocumentName',
|
||||
SetDocumentParser = 'setDocumentParser',
|
||||
SetDocumentMeta = 'setDocumentMeta',
|
||||
FetchDocumentFilter = 'fetchDocumentFilter',
|
||||
CreateDocument = 'createDocument',
|
||||
}
|
||||
|
||||
export const useUploadNextDocument = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { id } = useParams();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation<ResponseType<IDocumentInfo[]>, Error, File[]>({
|
||||
mutationKey: [DocumentApiAction.UploadDocument],
|
||||
mutationFn: async (fileList) => {
|
||||
const formData = new FormData();
|
||||
formData.append('kb_id', id!);
|
||||
fileList.forEach((file: any) => {
|
||||
formData.append('file', file);
|
||||
});
|
||||
|
||||
try {
|
||||
const ret = await kbService.document_upload(formData);
|
||||
const code = get(ret, 'data.code');
|
||||
|
||||
if (code === 0 || code === 500) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [DocumentApiAction.FetchDocumentList],
|
||||
});
|
||||
}
|
||||
return ret?.data;
|
||||
} catch (error) {
|
||||
console.warn(error);
|
||||
return {
|
||||
code: 500,
|
||||
message: error + '',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return { uploadDocument: mutateAsync, loading, data };
|
||||
};
|
||||
|
||||
export const useFetchDocumentList = () => {
|
||||
const { knowledgeId } = useGetKnowledgeSearchParams();
|
||||
const { searchString, handleInputChange } = useHandleSearchChange();
|
||||
const { pagination, setPagination } = useGetPaginationWithRouter();
|
||||
const { id } = useParams();
|
||||
const debouncedSearchString = useDebounce(searchString, { wait: 500 });
|
||||
const { filterValue, handleFilterSubmit } = useHandleFilterSubmit();
|
||||
const [docs, setDocs] = useState<IDocumentInfo[]>([]);
|
||||
const isLoop = useMemo(() => {
|
||||
return docs.some((doc) => doc.run === '1');
|
||||
}, [docs]);
|
||||
|
||||
const { data, isFetching: loading } = useQuery<{
|
||||
docs: IDocumentInfo[];
|
||||
total: number;
|
||||
}>({
|
||||
queryKey: [
|
||||
DocumentApiAction.FetchDocumentList,
|
||||
debouncedSearchString,
|
||||
pagination,
|
||||
filterValue,
|
||||
],
|
||||
initialData: { docs: [], total: 0 },
|
||||
refetchInterval: isLoop ? 5000 : false,
|
||||
enabled: !!knowledgeId || !!id,
|
||||
queryFn: async () => {
|
||||
const ret = await listDocument(
|
||||
{
|
||||
kb_id: knowledgeId || id,
|
||||
keywords: debouncedSearchString,
|
||||
page_size: pagination.pageSize,
|
||||
page: pagination.current,
|
||||
},
|
||||
{
|
||||
suffix: filterValue.type,
|
||||
run_status: filterValue.run,
|
||||
},
|
||||
);
|
||||
if (ret.data.code === 0) {
|
||||
return ret.data.data;
|
||||
}
|
||||
|
||||
return {
|
||||
docs: [],
|
||||
total: 0,
|
||||
};
|
||||
},
|
||||
});
|
||||
useMemo(() => {
|
||||
setDocs(data.docs);
|
||||
}, [data.docs]);
|
||||
const onInputChange: React.ChangeEventHandler<HTMLInputElement> = useCallback(
|
||||
(e) => {
|
||||
setPagination({ page: 1 });
|
||||
handleInputChange(e);
|
||||
},
|
||||
[handleInputChange, setPagination],
|
||||
);
|
||||
|
||||
return {
|
||||
loading,
|
||||
searchString,
|
||||
documents: data.docs,
|
||||
pagination: { ...pagination, total: data?.total },
|
||||
handleInputChange: onInputChange,
|
||||
setPagination,
|
||||
filterValue,
|
||||
handleFilterSubmit,
|
||||
};
|
||||
};
|
||||
|
||||
// get document filter
|
||||
export const useGetDocumentFilter = (): {
|
||||
filter: IDocumentInfoFilter;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
} => {
|
||||
const { knowledgeId } = useGetKnowledgeSearchParams();
|
||||
const { searchString } = useHandleSearchChange();
|
||||
const { id } = useParams();
|
||||
const debouncedSearchString = useDebounce(searchString, { wait: 500 });
|
||||
const [open, setOpen] = useState<number>(0);
|
||||
const { data } = useQuery({
|
||||
queryKey: [
|
||||
DocumentApiAction.FetchDocumentFilter,
|
||||
debouncedSearchString,
|
||||
knowledgeId,
|
||||
open,
|
||||
],
|
||||
queryFn: async () => {
|
||||
const { data } = await kbService.documentFilter({
|
||||
kb_id: knowledgeId || id,
|
||||
keywords: debouncedSearchString,
|
||||
});
|
||||
if (data.code === 0) {
|
||||
return data.data;
|
||||
}
|
||||
},
|
||||
});
|
||||
const handleOnpenChange = (e: boolean) => {
|
||||
if (e) {
|
||||
const currentOpen = open + 1;
|
||||
setOpen(currentOpen);
|
||||
}
|
||||
};
|
||||
return {
|
||||
filter: data?.filter || {
|
||||
run_status: {},
|
||||
suffix: {},
|
||||
},
|
||||
onOpenChange: handleOnpenChange,
|
||||
};
|
||||
};
|
||||
// update document status
|
||||
export const useSetDocumentStatus = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [DocumentApiAction.UpdateDocumentStatus],
|
||||
mutationFn: async ({
|
||||
status,
|
||||
documentId,
|
||||
}: {
|
||||
status: boolean;
|
||||
documentId: string | string[];
|
||||
}) => {
|
||||
const ids = Array.isArray(documentId) ? documentId : [documentId];
|
||||
const { data } = await kbService.document_change_status({
|
||||
doc_ids: ids,
|
||||
status: Number(status),
|
||||
});
|
||||
if (data.code === 0) {
|
||||
message.success(i18n.t('message.modified'));
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [DocumentApiAction.FetchDocumentList],
|
||||
});
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { setDocumentStatus: mutateAsync, data, loading };
|
||||
};
|
||||
|
||||
// This hook is used to run a document by its IDs
|
||||
export const useRunDocument = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [DocumentApiAction.RunDocumentByIds],
|
||||
mutationFn: async ({
|
||||
documentIds,
|
||||
run,
|
||||
shouldDelete,
|
||||
}: {
|
||||
documentIds: string[];
|
||||
run: number;
|
||||
shouldDelete: boolean;
|
||||
}) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [DocumentApiAction.FetchDocumentList],
|
||||
});
|
||||
|
||||
const ret = await kbService.document_run({
|
||||
doc_ids: documentIds,
|
||||
run,
|
||||
delete: shouldDelete,
|
||||
});
|
||||
const code = get(ret, 'data.code');
|
||||
if (code === 0) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [DocumentApiAction.FetchDocumentList],
|
||||
});
|
||||
message.success(i18n.t('message.operated'));
|
||||
}
|
||||
|
||||
return code;
|
||||
},
|
||||
});
|
||||
|
||||
return { runDocumentByIds: mutateAsync, loading, data };
|
||||
};
|
||||
|
||||
export const useRemoveDocument = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [DocumentApiAction.RemoveDocument],
|
||||
mutationFn: async (documentIds: string | string[]) => {
|
||||
const { data } = await kbService.document_rm({ doc_id: documentIds });
|
||||
if (data.code === 0) {
|
||||
message.success(i18n.t('message.deleted'));
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [DocumentApiAction.FetchDocumentList],
|
||||
});
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, removeDocument: mutateAsync };
|
||||
};
|
||||
|
||||
export const useSaveDocumentName = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [DocumentApiAction.SaveDocumentName],
|
||||
mutationFn: async ({
|
||||
name,
|
||||
documentId,
|
||||
}: {
|
||||
name: string;
|
||||
documentId: string;
|
||||
}) => {
|
||||
const { data } = await kbService.document_rename({
|
||||
doc_id: documentId,
|
||||
name: name,
|
||||
});
|
||||
if (data.code === 0) {
|
||||
message.success(i18n.t('message.renamed'));
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [DocumentApiAction.FetchDocumentList],
|
||||
});
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { loading, saveName: mutateAsync, data };
|
||||
};
|
||||
|
||||
export const useSetDocumentParser = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [DocumentApiAction.SetDocumentParser],
|
||||
mutationFn: async ({
|
||||
parserId,
|
||||
pipelineId,
|
||||
documentId,
|
||||
parserConfig,
|
||||
}: {
|
||||
parserId: string;
|
||||
pipelineId: string;
|
||||
documentId: string;
|
||||
parserConfig: IChangeParserConfigRequestBody;
|
||||
}) => {
|
||||
const { data } = await kbService.document_change_parser({
|
||||
parser_id: parserId,
|
||||
pipeline_id: pipelineId,
|
||||
doc_id: documentId,
|
||||
parser_config: parserConfig,
|
||||
});
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [DocumentApiAction.FetchDocumentList],
|
||||
});
|
||||
|
||||
message.success(i18n.t('message.modified'));
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { setDocumentParser: mutateAsync, data, loading };
|
||||
};
|
||||
|
||||
export const useSetDocumentMeta = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [DocumentApiAction.SetDocumentMeta],
|
||||
mutationFn: async (params: IDocumentMetaRequestBody) => {
|
||||
try {
|
||||
const { data } = await kbService.setMeta({
|
||||
meta: params.meta,
|
||||
doc_id: params.documentId,
|
||||
});
|
||||
|
||||
if (data?.code === 0) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [DocumentApiAction.FetchDocumentList],
|
||||
});
|
||||
|
||||
message.success(i18n.t('message.modified'));
|
||||
}
|
||||
return data?.code;
|
||||
} catch (error) {
|
||||
message.error('error');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return { setDocumentMeta: mutateAsync, data, loading };
|
||||
};
|
||||
|
||||
export const useCreateDocument = () => {
|
||||
const { id } = useParams();
|
||||
const { setPaginationParams, page } = useSetPaginationParams();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [DocumentApiAction.CreateDocument],
|
||||
mutationFn: async (name: string) => {
|
||||
const { data } = await kbService.document_create({
|
||||
name,
|
||||
kb_id: id,
|
||||
});
|
||||
if (data.code === 0) {
|
||||
if (page === 1) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [DocumentApiAction.FetchDocumentList],
|
||||
});
|
||||
} else {
|
||||
setPaginationParams(); // fetch document list
|
||||
}
|
||||
|
||||
message.success(i18n.t('message.created'));
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { createDocument: mutateAsync, loading, data };
|
||||
};
|
||||
231
web/src/hooks/use-file-request.ts
Normal file
231
web/src/hooks/use-file-request.ts
Normal file
@@ -0,0 +1,231 @@
|
||||
import message from '@/components/ui/message';
|
||||
import {
|
||||
IFetchFileListResult,
|
||||
IFolder,
|
||||
} from '@/interfaces/database/file-manager';
|
||||
import fileManagerService from '@/services/file-manager-service';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useDebounce } from 'ahooks';
|
||||
import { PaginationProps } from 'antd';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSearchParams } from 'umi';
|
||||
import {
|
||||
useGetPaginationWithRouter,
|
||||
useHandleSearchChange,
|
||||
} from './logic-hooks';
|
||||
import { useSetPaginationParams } from './route-hook';
|
||||
|
||||
export const enum FileApiAction {
|
||||
UploadFile = 'uploadFile',
|
||||
FetchFileList = 'fetchFileList',
|
||||
MoveFile = 'moveFile',
|
||||
CreateFolder = 'createFolder',
|
||||
FetchParentFolderList = 'fetchParentFolderList',
|
||||
DeleteFile = 'deleteFile',
|
||||
}
|
||||
|
||||
export const useGetFolderId = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const id = searchParams.get('folderId') as string;
|
||||
|
||||
return id ?? '';
|
||||
};
|
||||
|
||||
export const useUploadFile = () => {
|
||||
const { setPaginationParams } = useSetPaginationParams();
|
||||
const { t } = useTranslation();
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [FileApiAction.UploadFile],
|
||||
mutationFn: async (params: { fileList: File[]; parentId: string }) => {
|
||||
const fileList = params.fileList;
|
||||
const pathList = params.fileList.map(
|
||||
(file) => (file as any).webkitRelativePath,
|
||||
);
|
||||
const formData = new FormData();
|
||||
formData.append('parent_id', params.parentId);
|
||||
fileList.forEach((file: any, index: number) => {
|
||||
formData.append('file', file);
|
||||
formData.append('path', pathList[index]);
|
||||
});
|
||||
try {
|
||||
const ret = await fileManagerService.uploadFile(formData);
|
||||
if (ret?.data.code === 0) {
|
||||
message.success(t('message.uploaded'));
|
||||
setPaginationParams(1);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [FileApiAction.FetchFileList],
|
||||
});
|
||||
}
|
||||
return ret?.data?.code;
|
||||
} catch (error) {}
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, uploadFile: mutateAsync };
|
||||
};
|
||||
|
||||
export interface IMoveFileBody {
|
||||
src_file_ids: string[];
|
||||
dest_file_id: string; // target folder id
|
||||
}
|
||||
|
||||
export const useMoveFile = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [FileApiAction.MoveFile],
|
||||
mutationFn: async (params: IMoveFileBody) => {
|
||||
const { data } = await fileManagerService.moveFile(params);
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.operated'));
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [FileApiAction.FetchFileList],
|
||||
});
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, moveFile: mutateAsync };
|
||||
};
|
||||
|
||||
export const useCreateFolder = () => {
|
||||
const { setPaginationParams } = useSetPaginationParams();
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [FileApiAction.CreateFolder],
|
||||
mutationFn: async (params: { parentId: string; name: string }) => {
|
||||
const { data } = await fileManagerService.createFolder({
|
||||
...params,
|
||||
type: 'folder',
|
||||
});
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.created'));
|
||||
setPaginationParams(1);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [FileApiAction.FetchFileList],
|
||||
});
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, createFolder: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchParentFolderList = () => {
|
||||
const id = useGetFolderId();
|
||||
const { data } = useQuery<IFolder[]>({
|
||||
queryKey: [FileApiAction.FetchParentFolderList, id],
|
||||
initialData: [],
|
||||
enabled: !!id,
|
||||
queryFn: async () => {
|
||||
const { data } = await fileManagerService.getAllParentFolder({
|
||||
fileId: id,
|
||||
});
|
||||
|
||||
return data?.data?.parent_folders?.toReversed() ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export interface IListResult {
|
||||
searchString: string;
|
||||
handleInputChange: React.ChangeEventHandler<HTMLInputElement>;
|
||||
pagination: PaginationProps;
|
||||
setPagination: (pagination: { page: number; pageSize: number }) => void;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export const useFetchFileList = () => {
|
||||
const { searchString, handleInputChange } = useHandleSearchChange();
|
||||
const { pagination, setPagination } = useGetPaginationWithRouter();
|
||||
const id = useGetFolderId();
|
||||
const debouncedSearchString = useDebounce(searchString, { wait: 500 });
|
||||
|
||||
const { data, isFetching: loading } = useQuery<IFetchFileListResult>({
|
||||
queryKey: [
|
||||
FileApiAction.FetchFileList,
|
||||
{
|
||||
id,
|
||||
debouncedSearchString,
|
||||
...pagination,
|
||||
},
|
||||
],
|
||||
initialData: { files: [], parent_folder: {} as IFolder, total: 0 },
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await fileManagerService.listFile({
|
||||
parent_id: id,
|
||||
keywords: debouncedSearchString,
|
||||
page_size: pagination.pageSize,
|
||||
page: pagination.current,
|
||||
});
|
||||
|
||||
return data?.data;
|
||||
},
|
||||
});
|
||||
|
||||
const onInputChange: React.ChangeEventHandler<HTMLInputElement> = useCallback(
|
||||
(e) => {
|
||||
setPagination({ page: 1 });
|
||||
handleInputChange(e);
|
||||
},
|
||||
[handleInputChange, setPagination],
|
||||
);
|
||||
|
||||
return {
|
||||
...data,
|
||||
searchString,
|
||||
handleInputChange: onInputChange,
|
||||
pagination: { ...pagination, total: data?.total },
|
||||
setPagination,
|
||||
loading,
|
||||
};
|
||||
};
|
||||
|
||||
export const useDeleteFile = () => {
|
||||
const { setPaginationParams } = useSetPaginationParams();
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [FileApiAction.DeleteFile],
|
||||
mutationFn: async (params: { fileIds: string[]; parentId: string }) => {
|
||||
const { data } = await fileManagerService.removeFile(params);
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.deleted'));
|
||||
setPaginationParams(1); // TODO: There should be a better way to paginate the request list
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [FileApiAction.FetchFileList],
|
||||
});
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, deleteFile: mutateAsync };
|
||||
};
|
||||
356
web/src/hooks/use-knowledge-request.ts
Normal file
356
web/src/hooks/use-knowledge-request.ts
Normal file
@@ -0,0 +1,356 @@
|
||||
import { useHandleFilterSubmit } from '@/components/list-filter-bar/use-handle-filter-submit';
|
||||
import message from '@/components/ui/message';
|
||||
import {
|
||||
IKnowledge,
|
||||
IKnowledgeGraph,
|
||||
IKnowledgeResult,
|
||||
INextTestingResult,
|
||||
} from '@/interfaces/database/knowledge';
|
||||
import { ITestRetrievalRequestBody } from '@/interfaces/request/knowledge';
|
||||
import i18n from '@/locales/config';
|
||||
import kbService, {
|
||||
deleteKnowledgeGraph,
|
||||
getKnowledgeGraph,
|
||||
listDataset,
|
||||
} from '@/services/knowledge-service';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useDebounce } from 'ahooks';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useParams, useSearchParams } from 'umi';
|
||||
import {
|
||||
useGetPaginationWithRouter,
|
||||
useHandleSearchChange,
|
||||
} from './logic-hooks';
|
||||
|
||||
export const enum KnowledgeApiAction {
|
||||
TestRetrieval = 'testRetrieval',
|
||||
FetchKnowledgeListByPage = 'fetchKnowledgeListByPage',
|
||||
CreateKnowledge = 'createKnowledge',
|
||||
DeleteKnowledge = 'deleteKnowledge',
|
||||
SaveKnowledge = 'saveKnowledge',
|
||||
FetchKnowledgeDetail = 'fetchKnowledgeDetail',
|
||||
FetchKnowledgeGraph = 'fetchKnowledgeGraph',
|
||||
FetchMetadata = 'fetchMetadata',
|
||||
FetchKnowledgeList = 'fetchKnowledgeList',
|
||||
RemoveKnowledgeGraph = 'removeKnowledgeGraph',
|
||||
}
|
||||
|
||||
export const useKnowledgeBaseId = (): string => {
|
||||
const { id } = useParams();
|
||||
|
||||
return (id as string) || '';
|
||||
};
|
||||
|
||||
export const useTestRetrieval = () => {
|
||||
const knowledgeBaseId = useKnowledgeBaseId();
|
||||
const [values, setValues] = useState<ITestRetrievalRequestBody>();
|
||||
const mountedRef = useRef(false);
|
||||
const { filterValue, handleFilterSubmit } = useHandleFilterSubmit();
|
||||
|
||||
const [page, setPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
||||
const onPaginationChange = useCallback((page: number, pageSize: number) => {
|
||||
setPage(page);
|
||||
setPageSize(pageSize);
|
||||
}, []);
|
||||
|
||||
const queryParams = useMemo(() => {
|
||||
return {
|
||||
...values,
|
||||
kb_id: values?.kb_id || knowledgeBaseId,
|
||||
page,
|
||||
size: pageSize,
|
||||
doc_ids: filterValue.doc_ids,
|
||||
};
|
||||
}, [filterValue, knowledgeBaseId, page, pageSize, values]);
|
||||
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery<INextTestingResult>({
|
||||
queryKey: [KnowledgeApiAction.TestRetrieval, queryParams, page, pageSize],
|
||||
initialData: {
|
||||
chunks: [],
|
||||
doc_aggs: [],
|
||||
total: 0,
|
||||
isRuned: false,
|
||||
},
|
||||
enabled: false,
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await kbService.retrieval_test(queryParams);
|
||||
const result = data?.data ?? {};
|
||||
return { ...result, isRuned: true };
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (mountedRef.current) {
|
||||
refetch();
|
||||
}
|
||||
mountedRef.current = true;
|
||||
}, [page, pageSize, refetch, filterValue]);
|
||||
|
||||
return {
|
||||
data,
|
||||
loading,
|
||||
setValues,
|
||||
refetch,
|
||||
onPaginationChange,
|
||||
page,
|
||||
pageSize,
|
||||
handleFilterSubmit,
|
||||
filterValue,
|
||||
};
|
||||
};
|
||||
|
||||
export const useFetchNextKnowledgeListByPage = () => {
|
||||
const { searchString, handleInputChange } = useHandleSearchChange();
|
||||
const { pagination, setPagination } = useGetPaginationWithRouter();
|
||||
const debouncedSearchString = useDebounce(searchString, { wait: 500 });
|
||||
const { filterValue, handleFilterSubmit } = useHandleFilterSubmit();
|
||||
|
||||
const { data, isFetching: loading } = useQuery<IKnowledgeResult>({
|
||||
queryKey: [
|
||||
KnowledgeApiAction.FetchKnowledgeListByPage,
|
||||
{
|
||||
debouncedSearchString,
|
||||
...pagination,
|
||||
filterValue,
|
||||
},
|
||||
],
|
||||
initialData: {
|
||||
kbs: [],
|
||||
total: 0,
|
||||
},
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await listDataset(
|
||||
{
|
||||
keywords: debouncedSearchString,
|
||||
page_size: pagination.pageSize,
|
||||
page: pagination.current,
|
||||
},
|
||||
{
|
||||
owner_ids: filterValue.owner,
|
||||
},
|
||||
);
|
||||
|
||||
return data?.data;
|
||||
},
|
||||
});
|
||||
|
||||
const onInputChange: React.ChangeEventHandler<HTMLInputElement> = useCallback(
|
||||
(e) => {
|
||||
// setPagination({ page: 1 }); // TODO: This results in repeated requests
|
||||
handleInputChange(e);
|
||||
},
|
||||
[handleInputChange],
|
||||
);
|
||||
|
||||
return {
|
||||
...data,
|
||||
searchString,
|
||||
handleInputChange: onInputChange,
|
||||
pagination: { ...pagination, total: data?.total },
|
||||
setPagination,
|
||||
loading,
|
||||
filterValue,
|
||||
handleFilterSubmit,
|
||||
};
|
||||
};
|
||||
|
||||
export const useCreateKnowledge = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [KnowledgeApiAction.CreateKnowledge],
|
||||
mutationFn: async (params: { id?: string; name: string }) => {
|
||||
const { data = {} } = await kbService.createKb(params);
|
||||
if (data.code === 0) {
|
||||
message.success(
|
||||
i18n.t(`message.${params?.id ? 'modified' : 'created'}`),
|
||||
);
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchKnowledgeList'] });
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, createKnowledge: mutateAsync };
|
||||
};
|
||||
|
||||
export const useDeleteKnowledge = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [KnowledgeApiAction.DeleteKnowledge],
|
||||
mutationFn: async (id: string) => {
|
||||
const { data } = await kbService.rmKb({ kb_id: id });
|
||||
if (data.code === 0) {
|
||||
message.success(i18n.t(`message.deleted`));
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [KnowledgeApiAction.FetchKnowledgeListByPage],
|
||||
});
|
||||
}
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, deleteKnowledge: mutateAsync };
|
||||
};
|
||||
|
||||
export const useUpdateKnowledge = (shouldFetchList = false) => {
|
||||
const knowledgeBaseId = useKnowledgeBaseId();
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [KnowledgeApiAction.SaveKnowledge],
|
||||
mutationFn: async (params: Record<string, any>) => {
|
||||
const { data = {} } = await kbService.updateKb({
|
||||
kb_id: params?.kb_id ? params?.kb_id : knowledgeBaseId,
|
||||
...params,
|
||||
});
|
||||
if (data.code === 0) {
|
||||
message.success(i18n.t(`message.updated`));
|
||||
if (shouldFetchList) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [KnowledgeApiAction.FetchKnowledgeListByPage],
|
||||
});
|
||||
} else {
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchKnowledgeDetail'] });
|
||||
}
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, saveKnowledgeConfiguration: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchKnowledgeBaseConfiguration = (props?: {
|
||||
isEdit?: boolean;
|
||||
refreshCount?: number;
|
||||
}) => {
|
||||
const { isEdit = true, refreshCount } = props || { isEdit: true };
|
||||
const { id } = useParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
const knowledgeBaseId = searchParams.get('id') || id;
|
||||
|
||||
let queryKey: (KnowledgeApiAction | number)[] = [
|
||||
KnowledgeApiAction.FetchKnowledgeDetail,
|
||||
];
|
||||
if (typeof refreshCount === 'number') {
|
||||
queryKey = [KnowledgeApiAction.FetchKnowledgeDetail, refreshCount];
|
||||
}
|
||||
|
||||
const { data, isFetching: loading } = useQuery<IKnowledge>({
|
||||
queryKey,
|
||||
initialData: {} as IKnowledge,
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
if (isEdit) {
|
||||
const { data } = await kbService.get_kb_detail({
|
||||
kb_id: knowledgeBaseId,
|
||||
});
|
||||
return data?.data ?? {};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export function useFetchKnowledgeGraph() {
|
||||
const knowledgeBaseId = useKnowledgeBaseId();
|
||||
|
||||
const { data, isFetching: loading } = useQuery<IKnowledgeGraph>({
|
||||
queryKey: [KnowledgeApiAction.FetchKnowledgeGraph, knowledgeBaseId],
|
||||
initialData: { graph: {}, mind_map: {} } as IKnowledgeGraph,
|
||||
enabled: !!knowledgeBaseId,
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await getKnowledgeGraph(knowledgeBaseId);
|
||||
return data?.data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
}
|
||||
|
||||
export function useFetchKnowledgeMetadata(kbIds: string[] = []) {
|
||||
const { data, isFetching: loading } = useQuery<
|
||||
Record<string, Record<string, string[]>>
|
||||
>({
|
||||
queryKey: [KnowledgeApiAction.FetchMetadata, kbIds],
|
||||
initialData: {},
|
||||
enabled: kbIds.length > 0,
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await kbService.getMeta({ kb_ids: kbIds.join(',') });
|
||||
return data?.data ?? {};
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
}
|
||||
|
||||
export const useRemoveKnowledgeGraph = () => {
|
||||
const knowledgeBaseId = useKnowledgeBaseId();
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [KnowledgeApiAction.RemoveKnowledgeGraph],
|
||||
mutationFn: async () => {
|
||||
const { data } = await deleteKnowledgeGraph(knowledgeBaseId);
|
||||
if (data.code === 0) {
|
||||
message.success(i18n.t(`message.deleted`));
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [KnowledgeApiAction.FetchKnowledgeGraph],
|
||||
});
|
||||
}
|
||||
return data?.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, removeKnowledgeGraph: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchKnowledgeList = (
|
||||
shouldFilterListWithoutDocument: boolean = false,
|
||||
): {
|
||||
list: IKnowledge[];
|
||||
loading: boolean;
|
||||
} => {
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: [KnowledgeApiAction.FetchKnowledgeList],
|
||||
initialData: [],
|
||||
gcTime: 0, // https://tanstack.com/query/latest/docs/framework/react/guides/caching?from=reactQueryV3
|
||||
queryFn: async () => {
|
||||
const { data } = await listDataset();
|
||||
const list = data?.data?.kbs ?? [];
|
||||
return shouldFilterListWithoutDocument
|
||||
? list.filter((x: IKnowledge) => x.chunk_num > 0)
|
||||
: list;
|
||||
},
|
||||
});
|
||||
|
||||
return { list: data, loading };
|
||||
};
|
||||
47
web/src/hooks/use-llm-request.ts
Normal file
47
web/src/hooks/use-llm-request.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { LlmModelType } from '@/constants/knowledge';
|
||||
import userService from '@/services/user-service';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import {
|
||||
IThirdOAIModelCollection as IThirdAiModelCollection,
|
||||
IThirdOAIModel,
|
||||
} from '@/interfaces/database/llm';
|
||||
import { buildLlmUuid } from '@/utils/llm-util';
|
||||
|
||||
export const enum LLMApiAction {
|
||||
LlmList = 'llmList',
|
||||
}
|
||||
|
||||
export const useFetchLlmList = (modelType?: LlmModelType) => {
|
||||
const { data } = useQuery<IThirdAiModelCollection>({
|
||||
queryKey: [LLMApiAction.LlmList],
|
||||
initialData: {},
|
||||
queryFn: async () => {
|
||||
const { data } = await userService.llm_list({ model_type: modelType });
|
||||
|
||||
return data?.data ?? {};
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
type IThirdOAIModelWithUuid = IThirdOAIModel & { uuid: string };
|
||||
|
||||
export function useSelectFlatLlmList(modelType?: LlmModelType) {
|
||||
const llmList = useFetchLlmList(modelType);
|
||||
|
||||
return Object.values(llmList).reduce<IThirdOAIModelWithUuid[]>((pre, cur) => {
|
||||
pre.push(...cur.map((x) => ({ ...x, uuid: buildLlmUuid(x) })));
|
||||
|
||||
return pre;
|
||||
}, []);
|
||||
}
|
||||
|
||||
export function useFindLlmByUuid(modelType?: LlmModelType) {
|
||||
const flatList = useSelectFlatLlmList(modelType);
|
||||
|
||||
return (uuid: string) => {
|
||||
return flatList.find((x) => x.uuid === uuid);
|
||||
};
|
||||
}
|
||||
270
web/src/hooks/use-mcp-request.ts
Normal file
270
web/src/hooks/use-mcp-request.ts
Normal file
@@ -0,0 +1,270 @@
|
||||
import message from '@/components/ui/message';
|
||||
import { ResponseType } from '@/interfaces/database/base';
|
||||
import {
|
||||
IExportedMcpServers,
|
||||
IMcpServer,
|
||||
IMcpServerListResponse,
|
||||
IMCPTool,
|
||||
IMCPToolRecord,
|
||||
} from '@/interfaces/database/mcp';
|
||||
import {
|
||||
IImportMcpServersRequestBody,
|
||||
ITestMcpRequestBody,
|
||||
} from '@/interfaces/request/mcp';
|
||||
import i18n from '@/locales/config';
|
||||
import mcpServerService, {
|
||||
listMcpServers,
|
||||
} from '@/services/mcp-server-service';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useDebounce } from 'ahooks';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
useGetPaginationWithRouter,
|
||||
useHandleSearchChange,
|
||||
} from './logic-hooks';
|
||||
|
||||
export const enum McpApiAction {
|
||||
ListMcpServer = 'listMcpServer',
|
||||
GetMcpServer = 'getMcpServer',
|
||||
CreateMcpServer = 'createMcpServer',
|
||||
UpdateMcpServer = 'updateMcpServer',
|
||||
DeleteMcpServer = 'deleteMcpServer',
|
||||
ImportMcpServer = 'importMcpServer',
|
||||
ExportMcpServer = 'exportMcpServer',
|
||||
ListMcpServerTools = 'listMcpServerTools',
|
||||
TestMcpServerTool = 'testMcpServerTool',
|
||||
CacheMcpServerTool = 'cacheMcpServerTool',
|
||||
TestMcpServer = 'testMcpServer',
|
||||
}
|
||||
|
||||
export const useListMcpServer = () => {
|
||||
const { searchString, handleInputChange } = useHandleSearchChange();
|
||||
const { pagination, setPagination } = useGetPaginationWithRouter();
|
||||
const debouncedSearchString = useDebounce(searchString, { wait: 500 });
|
||||
|
||||
const { data, isFetching: loading } = useQuery<IMcpServerListResponse>({
|
||||
queryKey: [
|
||||
McpApiAction.ListMcpServer,
|
||||
{
|
||||
debouncedSearchString,
|
||||
...pagination,
|
||||
},
|
||||
],
|
||||
initialData: { total: 0, mcp_servers: [] },
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await listMcpServers({
|
||||
keywords: debouncedSearchString,
|
||||
page_size: pagination.pageSize,
|
||||
page: pagination.current,
|
||||
});
|
||||
return data?.data;
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
data,
|
||||
loading,
|
||||
handleInputChange,
|
||||
setPagination,
|
||||
searchString,
|
||||
pagination: { ...pagination, total: data?.total },
|
||||
};
|
||||
};
|
||||
|
||||
export const useGetMcpServer = (id: string) => {
|
||||
const { data, isFetching: loading } = useQuery<IMcpServer>({
|
||||
queryKey: [McpApiAction.GetMcpServer, id],
|
||||
initialData: {} as IMcpServer,
|
||||
gcTime: 0,
|
||||
enabled: !!id,
|
||||
queryFn: async () => {
|
||||
const { data } = await mcpServerService.get({ mcp_id: id });
|
||||
return data?.data ?? {};
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, id };
|
||||
};
|
||||
|
||||
export const useCreateMcpServer = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [McpApiAction.CreateMcpServer],
|
||||
mutationFn: async (params: Record<string, any>) => {
|
||||
const { data = {} } = await mcpServerService.create(params);
|
||||
if (data.code === 0) {
|
||||
message.success(i18n.t(`message.created`));
|
||||
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [McpApiAction.ListMcpServer],
|
||||
});
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, createMcpServer: mutateAsync };
|
||||
};
|
||||
|
||||
export const useUpdateMcpServer = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [McpApiAction.UpdateMcpServer],
|
||||
mutationFn: async (params: Record<string, any>) => {
|
||||
const { data = {} } = await mcpServerService.update(params);
|
||||
if (data.code === 0) {
|
||||
message.success(i18n.t(`message.updated`));
|
||||
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [McpApiAction.ListMcpServer],
|
||||
});
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, updateMcpServer: mutateAsync };
|
||||
};
|
||||
|
||||
export const useDeleteMcpServer = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [McpApiAction.DeleteMcpServer],
|
||||
mutationFn: async (ids: string[]) => {
|
||||
const { data = {} } = await mcpServerService.delete({ mcp_ids: ids });
|
||||
if (data.code === 0) {
|
||||
message.success(i18n.t(`message.deleted`));
|
||||
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [McpApiAction.ListMcpServer],
|
||||
});
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, deleteMcpServer: mutateAsync };
|
||||
};
|
||||
|
||||
export const useImportMcpServer = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [McpApiAction.ImportMcpServer],
|
||||
mutationFn: async (params: IImportMcpServersRequestBody) => {
|
||||
const { data = {} } = await mcpServerService.import(params);
|
||||
if (data.code === 0) {
|
||||
message.success(i18n.t(`message.operated`));
|
||||
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [McpApiAction.ListMcpServer],
|
||||
});
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, importMcpServer: mutateAsync };
|
||||
};
|
||||
|
||||
export const useExportMcpServer = () => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation<ResponseType<IExportedMcpServers>, Error, string[]>({
|
||||
mutationKey: [McpApiAction.ExportMcpServer],
|
||||
mutationFn: async (ids) => {
|
||||
const { data = {} } = await mcpServerService.export({ mcp_ids: ids });
|
||||
if (data.code === 0) {
|
||||
message.success(i18n.t(`message.operated`));
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, exportMcpServer: mutateAsync };
|
||||
};
|
||||
|
||||
export const useListMcpServerTools = () => {
|
||||
const [ids, setIds] = useState<string[]>([]);
|
||||
const { data, isFetching: loading } = useQuery<IMCPToolRecord>({
|
||||
queryKey: [McpApiAction.ListMcpServerTools],
|
||||
initialData: {} as IMCPToolRecord,
|
||||
gcTime: 0,
|
||||
enabled: ids.length > 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await mcpServerService.listTools({ mcp_ids: ids });
|
||||
return data?.data ?? {};
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, setIds };
|
||||
};
|
||||
|
||||
export const useTestMcpServer = () => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation<ResponseType<IMCPTool[]>, Error, ITestMcpRequestBody>({
|
||||
mutationKey: [McpApiAction.TestMcpServer],
|
||||
mutationFn: async (params) => {
|
||||
const { data } = await mcpServerService.test(params);
|
||||
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, testMcpServer: mutateAsync };
|
||||
};
|
||||
|
||||
export const useCacheMcpServerTool = () => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [McpApiAction.CacheMcpServerTool],
|
||||
mutationFn: async (params: Record<string, any>) => {
|
||||
const { data = {} } = await mcpServerService.cacheTool(params);
|
||||
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, cacheMcpServerTool: mutateAsync };
|
||||
};
|
||||
|
||||
export const useTestMcpServerTool = () => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [McpApiAction.TestMcpServerTool],
|
||||
mutationFn: async (params: Record<string, any>) => {
|
||||
const { data = {} } = await mcpServerService.testTool(params);
|
||||
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, testMcpServerTool: mutateAsync };
|
||||
};
|
||||
188
web/src/hooks/use-send-message.ts
Normal file
188
web/src/hooks/use-send-message.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
import message from '@/components/ui/message';
|
||||
import { Authorization } from '@/constants/authorization';
|
||||
import { IReferenceObject } from '@/interfaces/database/chat';
|
||||
import { BeginQuery } from '@/pages/agent/interface';
|
||||
import api from '@/utils/api';
|
||||
import { getAuthorization } from '@/utils/authorization-util';
|
||||
import { EventSourceParserStream } from 'eventsource-parser/stream';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
|
||||
export enum MessageEventType {
|
||||
WorkflowStarted = 'workflow_started',
|
||||
NodeStarted = 'node_started',
|
||||
NodeFinished = 'node_finished',
|
||||
Message = 'message',
|
||||
MessageEnd = 'message_end',
|
||||
WorkflowFinished = 'workflow_finished',
|
||||
UserInputs = 'user_inputs',
|
||||
NodeLogs = 'node_logs',
|
||||
}
|
||||
|
||||
export interface IAnswerEvent<T> {
|
||||
event: MessageEventType;
|
||||
message_id: string;
|
||||
session_id: string;
|
||||
created_at: number;
|
||||
task_id: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
export interface INodeData {
|
||||
inputs: Record<string, any>;
|
||||
outputs: Record<string, any>;
|
||||
component_id: string;
|
||||
component_name: string;
|
||||
component_type: string;
|
||||
error: null | string;
|
||||
elapsed_time: number;
|
||||
created_at: number;
|
||||
thoughts: string;
|
||||
}
|
||||
|
||||
export interface IInputData {
|
||||
content: string;
|
||||
inputs: Record<string, BeginQuery>;
|
||||
tips: string;
|
||||
}
|
||||
|
||||
export interface IMessageData {
|
||||
content: string;
|
||||
start_to_think?: boolean;
|
||||
end_to_think?: boolean;
|
||||
}
|
||||
|
||||
export interface IMessageEndData {
|
||||
reference: IReferenceObject;
|
||||
}
|
||||
|
||||
export interface ILogData extends INodeData {
|
||||
logs: {
|
||||
name: string;
|
||||
result: string;
|
||||
args: {
|
||||
query: string;
|
||||
topic: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export type INodeEvent = IAnswerEvent<INodeData>;
|
||||
|
||||
export type IMessageEvent = IAnswerEvent<IMessageData>;
|
||||
|
||||
export type IMessageEndEvent = IAnswerEvent<IMessageEndData>;
|
||||
|
||||
export type IInputEvent = IAnswerEvent<IInputData>;
|
||||
|
||||
export type ILogEvent = IAnswerEvent<ILogData>;
|
||||
|
||||
export type IChatEvent = INodeEvent | IMessageEvent | IMessageEndEvent;
|
||||
|
||||
export type IEventList = Array<IChatEvent>;
|
||||
|
||||
export const useSendMessageBySSE = (url: string = api.completeConversation) => {
|
||||
const [answerList, setAnswerList] = useState<IEventList>([]);
|
||||
const [done, setDone] = useState(true);
|
||||
const timer = useRef<any>();
|
||||
const sseRef = useRef<AbortController>();
|
||||
|
||||
const initializeSseRef = useCallback(() => {
|
||||
sseRef.current = new AbortController();
|
||||
}, []);
|
||||
|
||||
const resetAnswerList = useCallback(() => {
|
||||
if (timer.current) {
|
||||
clearTimeout(timer.current);
|
||||
}
|
||||
timer.current = setTimeout(() => {
|
||||
setAnswerList([]);
|
||||
clearTimeout(timer.current);
|
||||
}, 1000);
|
||||
}, []);
|
||||
|
||||
const send = useCallback(
|
||||
async (
|
||||
body: any,
|
||||
controller?: AbortController,
|
||||
): Promise<{ response: Response; data: ResponseType } | undefined> => {
|
||||
initializeSseRef();
|
||||
try {
|
||||
setDone(false);
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
[Authorization]: getAuthorization(),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
signal: controller?.signal || sseRef.current?.signal,
|
||||
});
|
||||
|
||||
const res = response.clone().json();
|
||||
|
||||
const reader = response?.body
|
||||
?.pipeThrough(new TextDecoderStream())
|
||||
.pipeThrough(new EventSourceParserStream())
|
||||
.getReader();
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
const x = await reader?.read();
|
||||
if (x) {
|
||||
const { done, value } = x;
|
||||
if (done) {
|
||||
console.info('done');
|
||||
resetAnswerList();
|
||||
break;
|
||||
}
|
||||
try {
|
||||
const val = JSON.parse(value?.data || '');
|
||||
|
||||
console.info('data:', val);
|
||||
if (val.code === 500) {
|
||||
message.error(val.message);
|
||||
}
|
||||
|
||||
setAnswerList((list) => {
|
||||
const nextList = [...list];
|
||||
nextList.push(val);
|
||||
return nextList;
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof DOMException && e.name === 'AbortError') {
|
||||
console.log('Request was aborted by user or logic.');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
console.info('done?');
|
||||
setDone(true);
|
||||
resetAnswerList();
|
||||
return { data: await res, response };
|
||||
} catch (e) {
|
||||
setDone(true);
|
||||
resetAnswerList();
|
||||
|
||||
console.warn(e);
|
||||
}
|
||||
},
|
||||
[initializeSseRef, url, resetAnswerList],
|
||||
);
|
||||
|
||||
const stopOutputMessage = useCallback(() => {
|
||||
sseRef.current?.abort();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
send,
|
||||
answerList,
|
||||
done,
|
||||
setDone,
|
||||
resetAnswerList,
|
||||
stopOutputMessage,
|
||||
};
|
||||
};
|
||||
464
web/src/hooks/use-user-setting-request.tsx
Normal file
464
web/src/hooks/use-user-setting-request.tsx
Normal file
@@ -0,0 +1,464 @@
|
||||
import message from '@/components/ui/message';
|
||||
import { LanguageTranslationMap } from '@/constants/common';
|
||||
import { ResponseGetType } from '@/interfaces/database/base';
|
||||
import { IToken } from '@/interfaces/database/chat';
|
||||
import { ITenantInfo } from '@/interfaces/database/knowledge';
|
||||
import { ILangfuseConfig } from '@/interfaces/database/system';
|
||||
import {
|
||||
ISystemStatus,
|
||||
ITenant,
|
||||
ITenantUser,
|
||||
IUserInfo,
|
||||
} from '@/interfaces/database/user-setting';
|
||||
import { ISetLangfuseConfigRequestBody } from '@/interfaces/request/system';
|
||||
import userService, {
|
||||
addTenantUser,
|
||||
agreeTenant,
|
||||
deleteTenantUser,
|
||||
listTenant,
|
||||
listTenantUser,
|
||||
} from '@/services/user-service';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { Modal } from 'antd';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { history } from 'umi';
|
||||
|
||||
export const enum UserSettingApiAction {
|
||||
UserInfo = 'userInfo',
|
||||
TenantInfo = 'tenantInfo',
|
||||
SaveSetting = 'saveSetting',
|
||||
FetchManualSystemTokenList = 'fetchManualSystemTokenList',
|
||||
FetchSystemTokenList = 'fetchSystemTokenList',
|
||||
RemoveSystemToken = 'removeSystemToken',
|
||||
CreateSystemToken = 'createSystemToken',
|
||||
ListTenantUser = 'listTenantUser',
|
||||
AddTenantUser = 'addTenantUser',
|
||||
DeleteTenantUser = 'deleteTenantUser',
|
||||
ListTenant = 'listTenant',
|
||||
AgreeTenant = 'agreeTenant',
|
||||
SetLangfuseConfig = 'setLangfuseConfig',
|
||||
DeleteLangfuseConfig = 'deleteLangfuseConfig',
|
||||
FetchLangfuseConfig = 'fetchLangfuseConfig',
|
||||
}
|
||||
|
||||
export const useFetchUserInfo = (): ResponseGetType<IUserInfo> => {
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: [UserSettingApiAction.UserInfo],
|
||||
initialData: {},
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await userService.user_info();
|
||||
if (data.code === 0) {
|
||||
i18n.changeLanguage(
|
||||
LanguageTranslationMap[
|
||||
data.data.language as keyof typeof LanguageTranslationMap
|
||||
],
|
||||
);
|
||||
}
|
||||
return data?.data ?? {};
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useFetchTenantInfo = (
|
||||
showEmptyModelWarn = false,
|
||||
): ResponseGetType<ITenantInfo> => {
|
||||
const { t } = useTranslation();
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: [UserSettingApiAction.TenantInfo],
|
||||
initialData: {},
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data: res } = await userService.get_tenant_info();
|
||||
if (res.code === 0) {
|
||||
// llm_id is chat_id
|
||||
// asr_id is speech2txt
|
||||
const { data } = res;
|
||||
if (
|
||||
showEmptyModelWarn &&
|
||||
(isEmpty(data.embd_id) || isEmpty(data.llm_id))
|
||||
) {
|
||||
Modal.warning({
|
||||
title: t('common.warn'),
|
||||
content: (
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: DOMPurify.sanitize(t('setting.modelProvidersWarn')),
|
||||
}}
|
||||
></div>
|
||||
),
|
||||
onOk() {
|
||||
history.push('/user-setting/model');
|
||||
},
|
||||
});
|
||||
}
|
||||
data.chat_id = data.llm_id;
|
||||
data.speech2text_id = data.asr_id;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
return res;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useSelectParserList = (): Array<{
|
||||
value: string;
|
||||
label: string;
|
||||
}> => {
|
||||
const { data: tenantInfo } = useFetchTenantInfo(true);
|
||||
|
||||
const parserList = useMemo(() => {
|
||||
const parserArray: Array<string> = tenantInfo?.parser_ids?.split(',') ?? [];
|
||||
return parserArray.map((x) => {
|
||||
const arr = x.split(':');
|
||||
return { value: arr[0], label: arr[1] };
|
||||
});
|
||||
}, [tenantInfo]);
|
||||
|
||||
return parserList;
|
||||
};
|
||||
|
||||
export const useSaveSetting = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [UserSettingApiAction.SaveSetting],
|
||||
mutationFn: async (
|
||||
userInfo: { new_password: string } | Partial<IUserInfo>,
|
||||
) => {
|
||||
const { data } = await userService.setting(userInfo);
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.modified'));
|
||||
queryClient.invalidateQueries({ queryKey: ['userInfo'] });
|
||||
}
|
||||
return data?.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, saveSetting: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchSystemVersion = () => {
|
||||
const [version, setVersion] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const fetchSystemVersion = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const { data } = await userService.getSystemVersion();
|
||||
if (data.code === 0) {
|
||||
setVersion(data.data);
|
||||
setLoading(false);
|
||||
}
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return { fetchSystemVersion, version, loading };
|
||||
};
|
||||
|
||||
export const useFetchSystemStatus = () => {
|
||||
const [systemStatus, setSystemStatus] = useState<ISystemStatus>(
|
||||
{} as ISystemStatus,
|
||||
);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const fetchSystemStatus = useCallback(async () => {
|
||||
setLoading(true);
|
||||
const { data } = await userService.getSystemStatus();
|
||||
if (data.code === 0) {
|
||||
setSystemStatus(data.data);
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return {
|
||||
systemStatus,
|
||||
fetchSystemStatus,
|
||||
loading,
|
||||
};
|
||||
};
|
||||
|
||||
export const useFetchManualSystemTokenList = () => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [UserSettingApiAction.FetchManualSystemTokenList],
|
||||
mutationFn: async () => {
|
||||
const { data } = await userService.listToken();
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, fetchSystemTokenList: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchSystemTokenList = () => {
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery<IToken[]>({
|
||||
queryKey: [UserSettingApiAction.FetchSystemTokenList],
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await userService.listToken();
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
|
||||
export const useRemoveSystemToken = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [UserSettingApiAction.RemoveSystemToken],
|
||||
mutationFn: async (token: string) => {
|
||||
const { data } = await userService.removeToken({}, token);
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.deleted'));
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [UserSettingApiAction.FetchSystemTokenList],
|
||||
});
|
||||
}
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, removeToken: mutateAsync };
|
||||
};
|
||||
|
||||
export const useCreateSystemToken = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [UserSettingApiAction.CreateSystemToken],
|
||||
mutationFn: async (params: Record<string, any>) => {
|
||||
const { data } = await userService.createToken(params);
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [UserSettingApiAction.FetchSystemTokenList],
|
||||
});
|
||||
}
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, createToken: mutateAsync };
|
||||
};
|
||||
|
||||
export const useListTenantUser = () => {
|
||||
const { data: tenantInfo } = useFetchTenantInfo();
|
||||
const tenantId = tenantInfo.tenant_id;
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery<ITenantUser[]>({
|
||||
queryKey: [UserSettingApiAction.ListTenantUser, tenantId],
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
enabled: !!tenantId,
|
||||
queryFn: async () => {
|
||||
const { data } = await listTenantUser(tenantId);
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
|
||||
export const useAddTenantUser = () => {
|
||||
const { data: tenantInfo } = useFetchTenantInfo();
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [UserSettingApiAction.AddTenantUser],
|
||||
mutationFn: async (email: string) => {
|
||||
const { data } = await addTenantUser(tenantInfo.tenant_id, email);
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [UserSettingApiAction.ListTenantUser],
|
||||
});
|
||||
}
|
||||
return data?.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, addTenantUser: mutateAsync };
|
||||
};
|
||||
|
||||
export const useDeleteTenantUser = () => {
|
||||
const { data: tenantInfo } = useFetchTenantInfo();
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [UserSettingApiAction.DeleteTenantUser],
|
||||
mutationFn: async ({
|
||||
userId,
|
||||
tenantId,
|
||||
}: {
|
||||
userId: string;
|
||||
tenantId?: string;
|
||||
}) => {
|
||||
const { data } = await deleteTenantUser({
|
||||
tenantId: tenantId ?? tenantInfo.tenant_id,
|
||||
userId,
|
||||
});
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.deleted'));
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [UserSettingApiAction.ListTenantUser],
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [UserSettingApiAction.ListTenant],
|
||||
});
|
||||
}
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, deleteTenantUser: mutateAsync };
|
||||
};
|
||||
|
||||
export const useListTenant = () => {
|
||||
const { data: tenantInfo } = useFetchTenantInfo();
|
||||
const tenantId = tenantInfo.tenant_id;
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery<ITenant[]>({
|
||||
queryKey: [UserSettingApiAction.ListTenant, tenantId],
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
enabled: !!tenantId,
|
||||
queryFn: async () => {
|
||||
const { data } = await listTenant();
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
|
||||
export const useAgreeTenant = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [UserSettingApiAction.AgreeTenant],
|
||||
mutationFn: async (tenantId: string) => {
|
||||
const { data } = await agreeTenant(tenantId);
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.operated'));
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [UserSettingApiAction.ListTenant],
|
||||
});
|
||||
}
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, agreeTenant: mutateAsync };
|
||||
};
|
||||
|
||||
export const useSetLangfuseConfig = () => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [UserSettingApiAction.SetLangfuseConfig],
|
||||
mutationFn: async (params: ISetLangfuseConfigRequestBody) => {
|
||||
const { data } = await userService.setLangfuseConfig(params);
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.operated'));
|
||||
}
|
||||
return data?.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, setLangfuseConfig: mutateAsync };
|
||||
};
|
||||
|
||||
export const useDeleteLangfuseConfig = () => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [UserSettingApiAction.DeleteLangfuseConfig],
|
||||
mutationFn: async () => {
|
||||
const { data } = await userService.deleteLangfuseConfig();
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.deleted'));
|
||||
}
|
||||
return data?.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, deleteLangfuseConfig: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchLangfuseConfig = () => {
|
||||
const { data, isFetching: loading } = useQuery<ILangfuseConfig>({
|
||||
queryKey: [UserSettingApiAction.FetchLangfuseConfig],
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await userService.getLangfuseConfig();
|
||||
|
||||
return data?.data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
434
web/src/hooks/user-setting-hooks.tsx
Normal file
434
web/src/hooks/user-setting-hooks.tsx
Normal file
@@ -0,0 +1,434 @@
|
||||
import message from '@/components/ui/message';
|
||||
import { LanguageTranslationMap } from '@/constants/common';
|
||||
import { ResponseGetType } from '@/interfaces/database/base';
|
||||
import { IToken } from '@/interfaces/database/chat';
|
||||
import { ITenantInfo } from '@/interfaces/database/knowledge';
|
||||
import { ILangfuseConfig } from '@/interfaces/database/system';
|
||||
import {
|
||||
ISystemStatus,
|
||||
ITenant,
|
||||
ITenantUser,
|
||||
IUserInfo,
|
||||
} from '@/interfaces/database/user-setting';
|
||||
import { ISetLangfuseConfigRequestBody } from '@/interfaces/request/system';
|
||||
import userService, {
|
||||
addTenantUser,
|
||||
agreeTenant,
|
||||
deleteTenantUser,
|
||||
listTenant,
|
||||
listTenantUser,
|
||||
} from '@/services/user-service';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { Modal } from 'antd';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { history } from 'umi';
|
||||
|
||||
export const useFetchUserInfo = (): ResponseGetType<IUserInfo> => {
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: ['userInfo'],
|
||||
initialData: {},
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await userService.user_info();
|
||||
if (data.code === 0) {
|
||||
i18n.changeLanguage(
|
||||
LanguageTranslationMap[
|
||||
data.data.language as keyof typeof LanguageTranslationMap
|
||||
],
|
||||
);
|
||||
}
|
||||
return data?.data ?? {};
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useFetchTenantInfo = (
|
||||
showEmptyModelWarn = false,
|
||||
): ResponseGetType<ITenantInfo> => {
|
||||
const { t } = useTranslation();
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: ['tenantInfo'],
|
||||
initialData: {},
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data: res } = await userService.get_tenant_info();
|
||||
if (res.code === 0) {
|
||||
// llm_id is chat_id
|
||||
// asr_id is speech2txt
|
||||
const { data } = res;
|
||||
if (
|
||||
showEmptyModelWarn &&
|
||||
(isEmpty(data.embd_id) || isEmpty(data.llm_id))
|
||||
) {
|
||||
Modal.warning({
|
||||
title: t('common.warn'),
|
||||
content: (
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: DOMPurify.sanitize(t('setting.modelProvidersWarn')),
|
||||
}}
|
||||
></div>
|
||||
),
|
||||
onOk() {
|
||||
history.push('/user-setting/model');
|
||||
},
|
||||
});
|
||||
}
|
||||
data.chat_id = data.llm_id;
|
||||
data.speech2text_id = data.asr_id;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
return res;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useSelectParserList = (): Array<{
|
||||
value: string;
|
||||
label: string;
|
||||
}> => {
|
||||
const { data: tenantInfo } = useFetchTenantInfo(true);
|
||||
|
||||
const parserList = useMemo(() => {
|
||||
const parserArray: Array<string> = tenantInfo?.parser_ids?.split(',') ?? [];
|
||||
return parserArray.map((x) => {
|
||||
const arr = x.split(':');
|
||||
return { value: arr[0], label: arr[1] };
|
||||
});
|
||||
}, [tenantInfo]);
|
||||
|
||||
return parserList;
|
||||
};
|
||||
|
||||
export const useSaveSetting = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['saveSetting'],
|
||||
mutationFn: async (
|
||||
userInfo: { new_password: string } | Partial<IUserInfo>,
|
||||
) => {
|
||||
const { data } = await userService.setting(userInfo);
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.modified'));
|
||||
queryClient.invalidateQueries({ queryKey: ['userInfo'] });
|
||||
}
|
||||
return data?.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, saveSetting: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchSystemVersion = () => {
|
||||
const [version, setVersion] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const fetchSystemVersion = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const { data } = await userService.getSystemVersion();
|
||||
if (data.code === 0) {
|
||||
setVersion(data.data);
|
||||
setLoading(false);
|
||||
}
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return { fetchSystemVersion, version, loading };
|
||||
};
|
||||
|
||||
export const useFetchSystemStatus = () => {
|
||||
const [systemStatus, setSystemStatus] = useState<ISystemStatus>(
|
||||
{} as ISystemStatus,
|
||||
);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const fetchSystemStatus = useCallback(async () => {
|
||||
setLoading(true);
|
||||
const { data } = await userService.getSystemStatus();
|
||||
if (data.code === 0) {
|
||||
setSystemStatus(data.data);
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return {
|
||||
systemStatus,
|
||||
fetchSystemStatus,
|
||||
loading,
|
||||
};
|
||||
};
|
||||
|
||||
export const useFetchManualSystemTokenList = () => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['fetchManualSystemTokenList'],
|
||||
mutationFn: async () => {
|
||||
const { data } = await userService.listToken();
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, fetchSystemTokenList: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchSystemTokenList = () => {
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery<IToken[]>({
|
||||
queryKey: ['fetchSystemTokenList'],
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await userService.listToken();
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
|
||||
export const useRemoveSystemToken = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['removeSystemToken'],
|
||||
mutationFn: async (token: string) => {
|
||||
const { data } = await userService.removeToken({}, token);
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.deleted'));
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchSystemTokenList'] });
|
||||
}
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, removeToken: mutateAsync };
|
||||
};
|
||||
|
||||
export const useCreateSystemToken = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['createSystemToken'],
|
||||
mutationFn: async (params: Record<string, any>) => {
|
||||
const { data } = await userService.createToken(params);
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchSystemTokenList'] });
|
||||
}
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, createToken: mutateAsync };
|
||||
};
|
||||
|
||||
export const useListTenantUser = () => {
|
||||
const { data: tenantInfo } = useFetchTenantInfo();
|
||||
const tenantId = tenantInfo.tenant_id;
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery<ITenantUser[]>({
|
||||
queryKey: ['listTenantUser', tenantId],
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
enabled: !!tenantId,
|
||||
queryFn: async () => {
|
||||
const { data } = await listTenantUser(tenantId);
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
|
||||
export const useAddTenantUser = () => {
|
||||
const { data: tenantInfo } = useFetchTenantInfo();
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['addTenantUser'],
|
||||
mutationFn: async (email: string) => {
|
||||
const { data } = await addTenantUser(tenantInfo.tenant_id, email);
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({ queryKey: ['listTenantUser'] });
|
||||
}
|
||||
return data?.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, addTenantUser: mutateAsync };
|
||||
};
|
||||
|
||||
export const useDeleteTenantUser = () => {
|
||||
const { data: tenantInfo } = useFetchTenantInfo();
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['deleteTenantUser'],
|
||||
mutationFn: async ({
|
||||
userId,
|
||||
tenantId,
|
||||
}: {
|
||||
userId: string;
|
||||
tenantId?: string;
|
||||
}) => {
|
||||
const { data } = await deleteTenantUser({
|
||||
tenantId: tenantId ?? tenantInfo.tenant_id,
|
||||
userId,
|
||||
});
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.deleted'));
|
||||
queryClient.invalidateQueries({ queryKey: ['listTenantUser'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['listTenant'] });
|
||||
}
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, deleteTenantUser: mutateAsync };
|
||||
};
|
||||
|
||||
export const useListTenant = () => {
|
||||
const { data: tenantInfo } = useFetchTenantInfo();
|
||||
const tenantId = tenantInfo.tenant_id;
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery<ITenant[]>({
|
||||
queryKey: ['listTenant', tenantId],
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
enabled: !!tenantId,
|
||||
queryFn: async () => {
|
||||
const { data } = await listTenant();
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
|
||||
export const useAgreeTenant = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['agreeTenant'],
|
||||
mutationFn: async (tenantId: string) => {
|
||||
const { data } = await agreeTenant(tenantId);
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.operated'));
|
||||
queryClient.invalidateQueries({ queryKey: ['listTenant'] });
|
||||
}
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, agreeTenant: mutateAsync };
|
||||
};
|
||||
|
||||
export const useSetLangfuseConfig = () => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['setLangfuseConfig'],
|
||||
mutationFn: async (params: ISetLangfuseConfigRequestBody) => {
|
||||
const { data } = await userService.setLangfuseConfig(params);
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.operated'));
|
||||
}
|
||||
return data?.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, setLangfuseConfig: mutateAsync };
|
||||
};
|
||||
|
||||
export const useDeleteLangfuseConfig = () => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['deleteLangfuseConfig'],
|
||||
mutationFn: async () => {
|
||||
const { data } = await userService.deleteLangfuseConfig();
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.deleted'));
|
||||
}
|
||||
return data?.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, deleteLangfuseConfig: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchLangfuseConfig = () => {
|
||||
const { data, isFetching: loading } = useQuery<ILangfuseConfig>({
|
||||
queryKey: ['fetchLangfuseConfig'],
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await userService.getLangfuseConfig();
|
||||
|
||||
return data?.data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
Reference in New Issue
Block a user