Pārlūkot izejas kodu

Feat: Render dialog list #3221 (#9249)

### What problem does this PR solve?

Feat: Render dialog list #3221

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
tags/v0.20.1
balibabu pirms 2 mēnešiem
vecāks
revīzija
a264c629b5
Revīzijas autora e-pasta adrese nav piesaistīta nevienam kontam

+ 6
- 3
web/src/hooks/logic-hooks/navigate-hooks.ts Parādīt failu

@@ -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);

+ 96
- 3
web/src/hooks/use-chat-request.ts Parādīt failu

@@ -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<IDialog[]>({
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<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: 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 };
};

+ 1
- 0
web/src/locales/en.ts Parādīt failu

@@ -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',

+ 3
- 1
web/src/pages/agent/form/code-form/index.tsx Parādīt failu

@@ -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<FormSchemaType>({
defaultValues: values,
@@ -94,7 +96,7 @@ function CodeForm({ node }: INextOperatorForm) {
<FormControl>
<Editor
height={300}
theme="vs-dark"
theme={isDarkTheme ? 'vs-dark' : 'vs'}
language={formData.lang}
options={{
minimap: { enabled: false },

+ 1
- 1
web/src/pages/agents/index.tsx Parādīt failu

@@ -10,7 +10,7 @@ import { useCallback } from 'react';
import { AgentCard } from './agent-card';
import { useRenameAgent } from './use-rename-agent';

export default function Agent() {
export default function Agents() {
const { data, pagination, setPagination, searchString, handleInputChange } =
useFetchAgentListByPage();
const { navigateToAgentTemplates } = useNavigatePage();

+ 13
- 11
web/src/pages/datasets/index.tsx Parādīt failu

@@ -53,7 +53,7 @@ export default function Datasets() {
);

return (
<section className="py-4 text-foreground">
<section className="py-4 flex-1 flex flex-col">
<ListFilterBar
title={t('header.knowledgeBase')}
searchString={searchString}
@@ -69,16 +69,18 @@ export default function Datasets() {
{t('knowledgeList.createKnowledgeBase')}
</Button>
</ListFilterBar>
<div className="flex flex-wrap gap-4 max-h-[78vh] overflow-auto px-8">
{kbs.map((dataset) => {
return (
<DatasetCard
dataset={dataset}
key={dataset.id}
showDatasetRenameModal={showDatasetRenameModal}
></DatasetCard>
);
})}
<div className="flex-1">
<div className="flex flex-wrap gap-4 max-h-[78vh] overflow-auto px-8">
{kbs.map((dataset) => {
return (
<DatasetCard
dataset={dataset}
key={dataset.id}
showDatasetRenameModal={showDatasetRenameModal}
></DatasetCard>
);
})}
</div>
</div>
<div className="mt-8 px-8">
<RAGFlowPagination

+ 31
- 38
web/src/pages/next-chats/chat-card.tsx Parādīt failu

@@ -1,52 +1,45 @@
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Button } from '@/components/ui/button';
import { MoreButton } from '@/components/more-button';
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
import { Card, CardContent } from '@/components/ui/card';
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
import { IDialog } from '@/interfaces/database/chat';
import { formatPureDate } from '@/utils/date';
import { ChevronRight, Trash2 } from 'lucide-react';
import { formatDate } from '@/utils/date';
import { ChatDropdown } from './chat-dropdown';
import { useRenameChat } from './hooks/use-rename-chat';

interface IProps {
export type IProps = {
data: IDialog;
}
} & Pick<ReturnType<typeof useRenameChat>, 'showChatRenameModal'>;

export function ChatCard({ data }: IProps) {
export function ChatCard({ data, showChatRenameModal }: IProps) {
const { navigateToChat } = useNavigatePage();

return (
<Card className="bg-colors-background-inverse-weak border-colors-outline-neutral-standard">
<CardContent className="p-4">
<div className="flex justify-between mb-4">
{data.icon ? (
<div
className="w-[70px] h-[70px] rounded-xl bg-cover"
style={{ backgroundImage: `url(${data.icon})` }}
/>
) : (
<Avatar className="w-[70px] h-[70px]">
<AvatarImage src="https://github.com/shadcn.png" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
)}
</div>
<h3 className="text-xl font-bold mb-2">{data.name}</h3>
<p>An app that does things An app that does things</p>
<section className="flex justify-between pt-3">
<div>
Search app
<p className="text-sm opacity-80">
{formatPureDate(data.update_time)}
</p>
</div>
<div className="space-x-2">
<Button variant="icon" size="icon" onClick={navigateToChat}>
<ChevronRight className="h-6 w-6" />
</Button>
<Button variant="icon" size="icon">
<Trash2 />
</Button>
<Card key={data.id} className="w-40" onClick={navigateToChat(data.id)}>
<CardContent className="p-2.5 pt-2 group">
<section className="flex justify-between mb-2">
<div className="flex gap-2 items-center">
<RAGFlowAvatar
className="size-6 rounded-lg"
avatar={data.icon}
name={data.name || 'CN'}
></RAGFlowAvatar>
</div>
<ChatDropdown chat={data} showChatRenameModal={showChatRenameModal}>
<MoreButton></MoreButton>
</ChatDropdown>
</section>
<div className="flex justify-between items-end">
<div className="w-full">
<h3 className="text-lg font-semibold mb-2 line-clamp-1">
{data.name}
</h3>
<p className="text-xs text-text-sub-title">{data.description}</p>
<p className="text-xs text-text-sub-title">
{formatDate(data.update_time)}
</p>
</div>
</div>
</CardContent>
</Card>
);

+ 64
- 0
web/src/pages/next-chats/chat-dropdown.tsx Parādīt failu

@@ -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<ReturnType<typeof useRenameChat>, 'showChatRenameModal'> & {
chat: IDialog;
}) {
const { t } = useTranslation();
const { removeDialog } = useRemoveDialog();

const handleShowChatRenameModal: MouseEventHandler<HTMLDivElement> =
useCallback(
(e) => {
e.stopPropagation();
showChatRenameModal(chat);
},
[chat, showChatRenameModal],
);

const handleDelete: MouseEventHandler<HTMLDivElement> = useCallback(() => {
removeDialog([chat.id]);
}, [chat.id, removeDialog]);

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem onClick={handleShowChatRenameModal}>
{t('common.rename')} <PenLine />
</DropdownMenuItem>
<DropdownMenuSeparator />
<ConfirmDeleteDialog onOk={handleDelete}>
<DropdownMenuItem
className="text-text-delete-red"
onSelect={(e) => {
e.preventDefault();
}}
onClick={(e) => {
e.stopPropagation();
}}
>
{t('common.delete')} <Trash2 />
</DropdownMenuItem>
</ConfirmDeleteDialog>
</DropdownMenuContent>
</DropdownMenu>
);
}

+ 45
- 0
web/src/pages/next-chats/hooks/use-rename-chat.ts Parādīt failu

@@ -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<IDialog>({} 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,
};
};

+ 60
- 13
web/src/pages/next-chats/index.tsx Parādīt failu

@@ -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 (
<section className="p-8">
<ListFilterBar title="Chat apps">
<Button variant={'tertiary'} size={'sm'}>
<Plus className="mr-2 h-4 w-4" />
Create app
</Button>
</ListFilterBar>
<div className="grid gap-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-4 xl:grid-cols-6 2xl:grid-cols-8">
{chatList.map((x) => {
return <ChatCard key={x.id} data={x}></ChatCard>;
})}
<section className="flex flex-col w-full flex-1">
<div className="px-8 pt-8">
<ListFilterBar title="Chat apps">
<Button>
<Plus className="size-2.5" />
{t('chat.createChat')}
</Button>
</ListFilterBar>
</div>
<div className="flex-1 overflow-auto">
<div className="flex flex-wrap gap-4 px-8">
{chatList.map((x) => {
return (
<ChatCard
key={x.id}
data={x}
showChatRenameModal={showChatRenameModal}
></ChatCard>
);
})}
</div>
</div>
<div className="mt-8 px-8 pb-8">
<RAGFlowPagination
{...pick(pagination, 'current', 'pageSize')}
total={pagination.total}
onChange={handlePageChange}
></RAGFlowPagination>
</div>
{chatRenameVisible && (
<RenameDialog
hideModal={hideChatRenameModal}
onOk={onChatRenameOk}
initialName={initialChatName}
loading={chatRenameLoading}
></RenameDialog>
)}
</section>
);
}

+ 1
- 1
web/src/routes.ts Parādīt failu

@@ -214,7 +214,7 @@ const routes = [
],
},
{
path: Routes.Chat,
path: Routes.Chat + '/:id',
layout: false,
component: `@/pages${Routes.Chats}/chat`,
},

Notiek ielāde…
Atcelt
Saglabāt