diff --git a/web/src/hooks/logic-hooks/navigate-hooks.ts b/web/src/hooks/logic-hooks/navigate-hooks.ts index a334bb0ea..ab3e0ad66 100644 --- a/web/src/hooks/logic-hooks/navigate-hooks.ts +++ b/web/src/hooks/logic-hooks/navigate-hooks.ts @@ -35,9 +35,12 @@ export const useNavigatePage = () => { navigate(Routes.Chats); }, [navigate]); - const navigateToChat = useCallback(() => { - navigate(Routes.Chat); - }, [navigate]); + const navigateToChat = useCallback( + (id: string) => () => { + navigate(`${Routes.Chat}/${id}`); + }, + [navigate], + ); const navigateToAgents = useCallback(() => { navigate(Routes.Agents); diff --git a/web/src/hooks/use-chat-request.ts b/web/src/hooks/use-chat-request.ts index e212ef092..5e7ee80fd 100644 --- a/web/src/hooks/use-chat-request.ts +++ b/web/src/hooks/use-chat-request.ts @@ -1,9 +1,22 @@ +import message from '@/components/ui/message'; import { ChatSearchParams } from '@/constants/chat'; import { IDialog } from '@/interfaces/database/chat'; import chatService from '@/services/chat-service'; -import { useQuery } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { useDebounce } from 'ahooks'; import { useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; import { history, useSearchParams } from 'umi'; +import { + useGetPaginationWithRouter, + useHandleSearchChange, +} from './logic-hooks'; + +export const enum ChatApiAction { + FetchDialogList = 'fetchDialogList', + RemoveDialog = 'removeDialog', + SetDialog = 'setDialog', +} export const useGetChatSearchParams = () => { const [currentQueryParameters] = useSearchParams(); @@ -42,13 +55,22 @@ export const useClickDialogCard = () => { export const useFetchDialogList = (pureFetch = false) => { const { handleClickDialog } = useClickDialogCard(); const { dialogId } = useGetChatSearchParams(); + const { searchString, handleInputChange } = useHandleSearchChange(); + const { pagination, setPagination } = useGetPaginationWithRouter(); + const debouncedSearchString = useDebounce(searchString, { wait: 500 }); const { data, isFetching: loading, refetch, } = useQuery({ - queryKey: ['fetchDialogList'], + queryKey: [ + ChatApiAction.FetchDialogList, + { + debouncedSearchString, + ...pagination, + }, + ], initialData: [], gcTime: 0, refetchOnWindowFocus: false, @@ -73,5 +95,76 @@ export const useFetchDialogList = (pureFetch = false) => { }, }); - return { data, loading, refetch }; + const onInputChange: React.ChangeEventHandler = 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: IDialog) => { + const { data } = await chatService.setDialog(params); + if (data.code === 0) { + queryClient.invalidateQueries({ + exact: false, + queryKey: ['fetchDialogList'], + }); + + queryClient.invalidateQueries({ + queryKey: ['fetchDialog'], + }); + message.success( + t(`message.${params.dialog_id ? 'modified' : 'created'}`), + ); + } + return data?.code; + }, + }); + + return { data, loading, setDialog: mutateAsync }; }; diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index 734024b78..fd2ebda5c 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -562,6 +562,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s tavilyApiKeyHelp: 'How to get it?', crossLanguage: 'Cross-language search', crossLanguageTip: `Select one or more languages for cross‑language search. If no language is selected, the system searches with the original query.`, + createChat: 'Create chat', }, setting: { profile: 'Profile', diff --git a/web/src/pages/agent/form/code-form/index.tsx b/web/src/pages/agent/form/code-form/index.tsx index 304971c6c..2883fdf46 100644 --- a/web/src/pages/agent/form/code-form/index.tsx +++ b/web/src/pages/agent/form/code-form/index.tsx @@ -2,6 +2,7 @@ import Editor, { loader } from '@monaco-editor/react'; import { INextOperatorForm } from '../../interface'; import { FormContainer } from '@/components/form-container'; +import { useIsDarkTheme } from '@/components/theme-provider'; import { Form, FormControl, @@ -46,6 +47,7 @@ function CodeForm({ node }: INextOperatorForm) { const formData = node?.data.form as ICodeForm; const { t } = useTranslation(); const values = useValues(node); + const isDarkTheme = useIsDarkTheme(); const form = useForm({ defaultValues: values, @@ -94,7 +96,7 @@ function CodeForm({ node }: INextOperatorForm) { +
-
- {kbs.map((dataset) => { - return ( - - ); - })} +
+
+ {kbs.map((dataset) => { + return ( + + ); + })} +
, 'showChatRenameModal'>; -export function ChatCard({ data }: IProps) { +export function ChatCard({ data, showChatRenameModal }: IProps) { const { navigateToChat } = useNavigatePage(); return ( - - -
- {data.icon ? ( -
- ) : ( - - - CN - - )} -
-

{data.name}

-

An app that does things An app that does things

-
-
- Search app -

- {formatPureDate(data.update_time)} + + +

+
+ +
+ + + +
+
+
+

+ {data.name} +

+

{data.description}

+

+ {formatDate(data.update_time)}

-
- - -
-
+
); diff --git a/web/src/pages/next-chats/chat-dropdown.tsx b/web/src/pages/next-chats/chat-dropdown.tsx new file mode 100644 index 000000000..29fb33da4 --- /dev/null +++ b/web/src/pages/next-chats/chat-dropdown.tsx @@ -0,0 +1,64 @@ +import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { useRemoveDialog } from '@/hooks/use-chat-request'; +import { IDialog } from '@/interfaces/database/chat'; +import { PenLine, Trash2 } from 'lucide-react'; +import { MouseEventHandler, PropsWithChildren, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useRenameChat } from './hooks/use-rename-chat'; + +export function ChatDropdown({ + children, + showChatRenameModal, + chat, +}: PropsWithChildren & + Pick, 'showChatRenameModal'> & { + chat: IDialog; + }) { + const { t } = useTranslation(); + const { removeDialog } = useRemoveDialog(); + + const handleShowChatRenameModal: MouseEventHandler = + useCallback( + (e) => { + e.stopPropagation(); + showChatRenameModal(chat); + }, + [chat, showChatRenameModal], + ); + + const handleDelete: MouseEventHandler = useCallback(() => { + removeDialog([chat.id]); + }, [chat.id, removeDialog]); + + return ( + + {children} + + + {t('common.rename')} + + + + { + e.preventDefault(); + }} + onClick={(e) => { + e.stopPropagation(); + }} + > + {t('common.delete')} + + + + + ); +} diff --git a/web/src/pages/next-chats/hooks/use-rename-chat.ts b/web/src/pages/next-chats/hooks/use-rename-chat.ts new file mode 100644 index 000000000..3972c1ae3 --- /dev/null +++ b/web/src/pages/next-chats/hooks/use-rename-chat.ts @@ -0,0 +1,45 @@ +import { useSetModalState } from '@/hooks/common-hooks'; +import { useSetDialog } from '@/hooks/use-chat-request'; +import { IDialog } from '@/interfaces/database/chat'; +import { useCallback, useState } from 'react'; + +export const useRenameChat = () => { + const [chat, setChat] = useState({} as IDialog); + const { + visible: chatRenameVisible, + hideModal: hideChatRenameModal, + showModal: showChatRenameModal, + } = useSetModalState(); + const { setDialog, loading } = useSetDialog(); + + const onChatRenameOk = useCallback( + async (name: string) => { + const ret = await setDialog({ + ...chat, + name, + }); + + if (ret === 0) { + hideChatRenameModal(); + } + }, + [setDialog, chat, hideChatRenameModal], + ); + + const handleShowChatRenameModal = useCallback( + async (record: IDialog) => { + setChat(record); + showChatRenameModal(); + }, + [showChatRenameModal], + ); + + return { + chatRenameLoading: loading, + initialChatName: chat?.name, + onChatRenameOk, + chatRenameVisible, + hideChatRenameModal, + showChatRenameModal: handleShowChatRenameModal, + }; +}; diff --git a/web/src/pages/next-chats/index.tsx b/web/src/pages/next-chats/index.tsx index 6c649ca04..064945b2b 100644 --- a/web/src/pages/next-chats/index.tsx +++ b/web/src/pages/next-chats/index.tsx @@ -1,25 +1,72 @@ import ListFilterBar from '@/components/list-filter-bar'; +import { RenameDialog } from '@/components/rename-dialog'; import { Button } from '@/components/ui/button'; -import { useFetchChatAppList } from '@/hooks/chat-hooks'; +import { RAGFlowPagination } from '@/components/ui/ragflow-pagination'; +import { useFetchDialogList } from '@/hooks/use-chat-request'; +import { pick } from 'lodash'; import { Plus } from 'lucide-react'; +import { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; import { ChatCard } from './chat-card'; +import { useRenameChat } from './hooks/use-rename-chat'; export default function ChatList() { - const { data: chatList } = useFetchChatAppList(); + const { data: chatList, setPagination, pagination } = useFetchDialogList(); + const { t } = useTranslation(); + const { + initialChatName, + chatRenameVisible, + showChatRenameModal, + hideChatRenameModal, + onChatRenameOk, + chatRenameLoading, + } = useRenameChat(); + + const handlePageChange = useCallback( + (page: number, pageSize?: number) => { + setPagination({ page, pageSize }); + }, + [setPagination], + ); return ( -
- - - -
- {chatList.map((x) => { - return ; - })} +
+
+ + +
+
+
+ {chatList.map((x) => { + return ( + + ); + })} +
+
+
+ +
+ {chatRenameVisible && ( + + )}
); } diff --git a/web/src/routes.ts b/web/src/routes.ts index 1850940cc..3f2d04b26 100644 --- a/web/src/routes.ts +++ b/web/src/routes.ts @@ -214,7 +214,7 @@ const routes = [ ], }, { - path: Routes.Chat, + path: Routes.Chat + '/:id', layout: false, component: `@/pages${Routes.Chats}/chat`, },