### What problem does this PR solve? Feat: Display a separate chat multi-model comparison page #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.20.2
| @@ -96,3 +96,22 @@ export function LargeModelFormField() { | |||
| </> | |||
| ); | |||
| } | |||
| export function LargeModelFormFieldWithoutFilter() { | |||
| const form = useFormContext(); | |||
| return ( | |||
| <FormField | |||
| control={form.control} | |||
| name="llm_id" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormControl> | |||
| <NextLLMSelect {...field} /> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| ); | |||
| } | |||
| @@ -78,7 +78,6 @@ export function LlmSettingFieldItems({ | |||
| <FormLabel>{t('model')}</FormLabel> | |||
| <FormControl> | |||
| <SelectWithSearch | |||
| allowClear | |||
| options={options || modelOptions} | |||
| {...field} | |||
| ></SelectWithSearch> | |||
| @@ -2,6 +2,11 @@ import { NextMessageInput } from '@/components/message-input/next'; | |||
| import MessageItem from '@/components/message-item'; | |||
| import { Button } from '@/components/ui/button'; | |||
| import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; | |||
| import { | |||
| Tooltip, | |||
| TooltipContent, | |||
| TooltipTrigger, | |||
| } from '@/components/ui/tooltip'; | |||
| import { MessageType } from '@/constants/chat'; | |||
| import { | |||
| useFetchConversation, | |||
| @@ -10,7 +15,7 @@ import { | |||
| } from '@/hooks/use-chat-request'; | |||
| import { useFetchUserInfo } from '@/hooks/user-setting-hooks'; | |||
| import { buildMessageUuidWithRole } from '@/utils/chat'; | |||
| import { Trash2 } from 'lucide-react'; | |||
| import { ListCheck, Plus, Trash2 } from 'lucide-react'; | |||
| import { useCallback } from 'react'; | |||
| import { | |||
| useGetSendButtonDisabled, | |||
| @@ -19,19 +24,30 @@ import { | |||
| import { useCreateConversationBeforeUploadDocument } from '../../hooks/use-create-conversation'; | |||
| import { useSendMessage } from '../../hooks/use-send-chat-message'; | |||
| import { buildMessageItemReference } from '../../utils'; | |||
| import { LLMSelectForm } from '../llm-select-form'; | |||
| import { useAddChatBox } from '../use-add-box'; | |||
| type MultipleChatBoxProps = { | |||
| controller: AbortController; | |||
| chatBoxIds: string[]; | |||
| } & Pick<ReturnType<typeof useAddChatBox>, 'removeChatBox'>; | |||
| } & Pick< | |||
| ReturnType<typeof useAddChatBox>, | |||
| 'removeChatBox' | 'addChatBox' | 'chatBoxIds' | |||
| >; | |||
| type ChatCardProps = { id: string } & Pick< | |||
| type ChatCardProps = { id: string; idx: number } & Pick< | |||
| MultipleChatBoxProps, | |||
| 'controller' | 'removeChatBox' | |||
| 'controller' | 'removeChatBox' | 'addChatBox' | 'chatBoxIds' | |||
| >; | |||
| function ChatCard({ controller, removeChatBox, id }: ChatCardProps) { | |||
| function ChatCard({ | |||
| controller, | |||
| removeChatBox, | |||
| id, | |||
| idx, | |||
| addChatBox, | |||
| chatBoxIds, | |||
| }: ChatCardProps) { | |||
| const { | |||
| value, | |||
| // scrollRef, | |||
| @@ -49,6 +65,8 @@ function ChatCard({ controller, removeChatBox, id }: ChatCardProps) { | |||
| const { data: currentDialog } = useFetchDialog(); | |||
| const { data: conversation } = useFetchConversation(); | |||
| const isLatestChat = idx === chatBoxIds.length - 1; | |||
| const handleRemoveChatBox = useCallback(() => { | |||
| removeChatBox(id); | |||
| }, [id, removeChatBox]); | |||
| @@ -57,15 +75,31 @@ function ChatCard({ controller, removeChatBox, id }: ChatCardProps) { | |||
| <Card className="bg-transparent border flex-1"> | |||
| <CardHeader className="border-b px-5 py-3"> | |||
| <CardTitle className="flex justify-between items-center"> | |||
| <div> | |||
| <span className="text-base">Card Title</span> | |||
| <Button variant={'ghost'} className="ml-2"> | |||
| GPT-4 | |||
| </Button> | |||
| <div className="flex items-center gap-3"> | |||
| <span className="text-base">{idx + 1}</span> | |||
| <LLMSelectForm></LLMSelectForm> | |||
| </div> | |||
| <div className="space-x-2"> | |||
| <Tooltip> | |||
| <TooltipTrigger> | |||
| <Button variant={'ghost'}> | |||
| <ListCheck /> | |||
| </Button> | |||
| </TooltipTrigger> | |||
| <TooltipContent> | |||
| <p>Apply model configs</p> | |||
| </TooltipContent> | |||
| </Tooltip> | |||
| {!isLatestChat || chatBoxIds.length === 3 ? ( | |||
| <Button variant={'ghost'} onClick={handleRemoveChatBox}> | |||
| <Trash2 /> | |||
| </Button> | |||
| ) : ( | |||
| <Button variant={'ghost'} onClick={addChatBox}> | |||
| <Plus></Plus> | |||
| </Button> | |||
| )} | |||
| </div> | |||
| <Button variant={'ghost'} onClick={handleRemoveChatBox}> | |||
| <Trash2 /> | |||
| </Button> | |||
| </CardTitle> | |||
| </CardHeader> | |||
| <CardContent> | |||
| @@ -111,6 +145,7 @@ export function MultipleChatBox({ | |||
| controller, | |||
| chatBoxIds, | |||
| removeChatBox, | |||
| addChatBox, | |||
| }: MultipleChatBoxProps) { | |||
| const { | |||
| value, | |||
| @@ -125,31 +160,37 @@ export function MultipleChatBox({ | |||
| const { conversationId } = useGetChatSearchParams(); | |||
| const disabled = useGetSendButtonDisabled(); | |||
| const sendDisabled = useSendButtonDisabled(value); | |||
| return ( | |||
| <section className="h-full flex flex-col"> | |||
| <div className="flex gap-4 flex-1 px-5 pb-12"> | |||
| {chatBoxIds.map((id) => ( | |||
| <section className="h-full flex flex-col px-5"> | |||
| <div className="flex gap-4 flex-1 px-5 pb-14"> | |||
| {chatBoxIds.map((id, idx) => ( | |||
| <ChatCard | |||
| key={id} | |||
| idx={idx} | |||
| controller={controller} | |||
| id={id} | |||
| chatBoxIds={chatBoxIds} | |||
| removeChatBox={removeChatBox} | |||
| addChatBox={addChatBox} | |||
| ></ChatCard> | |||
| ))} | |||
| </div> | |||
| <NextMessageInput | |||
| disabled={disabled} | |||
| sendDisabled={sendDisabled} | |||
| sendLoading={sendLoading} | |||
| value={value} | |||
| onInputChange={handleInputChange} | |||
| onPressEnter={handlePressEnter} | |||
| conversationId={conversationId} | |||
| createConversationBeforeUploadDocument={ | |||
| createConversationBeforeUploadDocument | |||
| } | |||
| stopOutputMessage={stopOutputMessage} | |||
| /> | |||
| <div className="px-[20%]"> | |||
| <NextMessageInput | |||
| disabled={disabled} | |||
| sendDisabled={sendDisabled} | |||
| sendLoading={sendLoading} | |||
| value={value} | |||
| onInputChange={handleInputChange} | |||
| onPressEnter={handlePressEnter} | |||
| conversationId={conversationId} | |||
| createConversationBeforeUploadDocument={ | |||
| createConversationBeforeUploadDocument | |||
| } | |||
| stopOutputMessage={stopOutputMessage} | |||
| /> | |||
| </div> | |||
| </section> | |||
| ); | |||
| } | |||
| @@ -11,21 +11,25 @@ import { Button } from '@/components/ui/button'; | |||
| import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; | |||
| import { useSetModalState } from '@/hooks/common-hooks'; | |||
| import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; | |||
| import { useFetchDialog } from '@/hooks/use-chat-request'; | |||
| import { useFetchConversation, useFetchDialog } from '@/hooks/use-chat-request'; | |||
| import { cn } from '@/lib/utils'; | |||
| import { Plus } from 'lucide-react'; | |||
| import { ArrowUpRight, LogOut } from 'lucide-react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { useHandleClickConversationCard } from '../hooks/use-click-card'; | |||
| import { ChatSettings } from './app-settings/chat-settings'; | |||
| import { MultipleChatBox } from './chat-box/multiple-chat-box'; | |||
| import { SingleChatBox } from './chat-box/single-chat-box'; | |||
| import { LLMSelectForm } from './llm-select-form'; | |||
| import { Sessions } from './sessions'; | |||
| import { useAddChatBox } from './use-add-box'; | |||
| import { useSwitchDebugMode } from './use-switch-debug-mode'; | |||
| export default function Chat() { | |||
| const { navigateToChatList } = useNavigatePage(); | |||
| const { data } = useFetchDialog(); | |||
| const { t } = useTranslation(); | |||
| const { data: conversation } = useFetchConversation(); | |||
| const { handleConversationCardClick, controller } = | |||
| useHandleClickConversationCard(); | |||
| const { visible: settingVisible, switchVisible: switchSettingVisible } = | |||
| @@ -38,6 +42,29 @@ export default function Chat() { | |||
| hasThreeChatBox, | |||
| } = useAddChatBox(); | |||
| const { isDebugMode, switchDebugMode } = useSwitchDebugMode(); | |||
| if (isDebugMode) { | |||
| return ( | |||
| <section className="pt-14 h-[100vh] pb-24"> | |||
| <div className="flex items-center justify-between px-10 pb-5"> | |||
| <span className="text-2xl"> | |||
| Multiple Models ({chatBoxIds.length}/3) | |||
| </span> | |||
| <Button variant={'ghost'} onClick={switchDebugMode}> | |||
| Exit <LogOut /> | |||
| </Button> | |||
| </div> | |||
| <MultipleChatBox | |||
| chatBoxIds={chatBoxIds} | |||
| controller={controller} | |||
| removeChatBox={removeChatBox} | |||
| addChatBox={addChatBox} | |||
| ></MultipleChatBox> | |||
| </section> | |||
| ); | |||
| } | |||
| return ( | |||
| <section className="h-full flex flex-col pr-5"> | |||
| <PageHeader> | |||
| @@ -57,6 +84,7 @@ export default function Chat() { | |||
| </PageHeader> | |||
| <div className="flex flex-1 min-h-0"> | |||
| <Sessions | |||
| hasSingleChatBox={hasSingleChatBox} | |||
| handleConversationCardClick={handleConversationCardClick} | |||
| switchSettingVisible={switchSettingVisible} | |||
| ></Sessions> | |||
| @@ -67,32 +95,23 @@ export default function Chat() { | |||
| <CardHeader | |||
| className={cn('p-5', { 'border-b': hasSingleChatBox })} | |||
| > | |||
| <CardTitle className="flex justify-between items-center"> | |||
| <div className="text-base"> | |||
| Card Title | |||
| <Button variant={'ghost'} className="ml-2"> | |||
| GPT-4 | |||
| </Button> | |||
| <CardTitle className="flex justify-between items-center text-base"> | |||
| <div className="flex gap-3 items-center"> | |||
| {conversation.name} | |||
| <LLMSelectForm></LLMSelectForm> | |||
| </div> | |||
| <Button | |||
| variant={'ghost'} | |||
| onClick={addChatBox} | |||
| onClick={switchDebugMode} | |||
| disabled={hasThreeChatBox} | |||
| > | |||
| <Plus></Plus> Multiple Models | |||
| <ArrowUpRight /> Multiple Models | |||
| </Button> | |||
| </CardTitle> | |||
| </CardHeader> | |||
| <CardContent className="flex-1 p-0"> | |||
| {hasSingleChatBox ? ( | |||
| <SingleChatBox controller={controller}></SingleChatBox> | |||
| ) : ( | |||
| <MultipleChatBox | |||
| chatBoxIds={chatBoxIds} | |||
| controller={controller} | |||
| removeChatBox={removeChatBox} | |||
| ></MultipleChatBox> | |||
| )} | |||
| <SingleChatBox controller={controller}></SingleChatBox> | |||
| </CardContent> | |||
| </Card> | |||
| {settingVisible && ( | |||
| @@ -0,0 +1,23 @@ | |||
| import { LargeModelFormFieldWithoutFilter } from '@/components/large-model-form-field'; | |||
| import { LlmSettingSchema } from '@/components/llm-setting-items/next'; | |||
| import { Form } from '@/components/ui/form'; | |||
| import { zodResolver } from '@hookform/resolvers/zod'; | |||
| import { useForm } from 'react-hook-form'; | |||
| import { z } from 'zod'; | |||
| export function LLMSelectForm() { | |||
| const FormSchema = z.object(LlmSettingSchema); | |||
| const form = useForm<z.infer<typeof FormSchema>>({ | |||
| resolver: zodResolver(FormSchema), | |||
| defaultValues: { | |||
| llm_id: '', | |||
| }, | |||
| }); | |||
| return ( | |||
| <Form {...form}> | |||
| <LargeModelFormFieldWithoutFilter></LargeModelFormFieldWithoutFilter> | |||
| </Form> | |||
| ); | |||
| } | |||
| @@ -17,8 +17,9 @@ import { useSelectDerivedConversationList } from '../hooks/use-select-conversati | |||
| type SessionProps = Pick< | |||
| ReturnType<typeof useHandleClickConversationCard>, | |||
| 'handleConversationCardClick' | |||
| > & { switchSettingVisible(): void }; | |||
| > & { switchSettingVisible(): void; hasSingleChatBox: boolean }; | |||
| export function Sessions({ | |||
| hasSingleChatBox, | |||
| handleConversationCardClick, | |||
| switchSettingVisible, | |||
| }: SessionProps) { | |||
| @@ -91,7 +92,11 @@ export function Sessions({ | |||
| ))} | |||
| </div> | |||
| <div className="py-2"> | |||
| <Button className="w-full" onClick={switchSettingVisible}> | |||
| <Button | |||
| className="w-full" | |||
| onClick={switchSettingVisible} | |||
| disabled={!hasSingleChatBox} | |||
| > | |||
| Chat Settings | |||
| </Button> | |||
| </div> | |||
| @@ -0,0 +1,14 @@ | |||
| import { useCallback, useState } from 'react'; | |||
| export function useSwitchDebugMode() { | |||
| const [isDebugMode, setIsDebugMode] = useState(false); | |||
| const switchDebugMode = useCallback(() => { | |||
| setIsDebugMode(!isDebugMode); | |||
| }, [isDebugMode]); | |||
| return { | |||
| isDebugMode, | |||
| switchDebugMode, | |||
| }; | |||
| } | |||