### What problem does this PR solve? feat: After the voice in the new conversation window is played, jump to the tab of the conversation #1877 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.11.0
| import { useSetModalState } from '@/hooks/common-hooks'; | import { useSetModalState } from '@/hooks/common-hooks'; | ||||
| import { IRemoveMessageById, useSpeechWithSse } from '@/hooks/logic-hooks'; | import { IRemoveMessageById, useSpeechWithSse } from '@/hooks/logic-hooks'; | ||||
| import { IFeedbackRequestBody } from '@/interfaces/request/chat'; | import { IFeedbackRequestBody } from '@/interfaces/request/chat'; | ||||
| import { ConversationContext } from '@/pages/chat/context'; | |||||
| import { getMessagePureId } from '@/utils/chat'; | import { getMessagePureId } from '@/utils/chat'; | ||||
| import { hexStringToUint8Array } from '@/utils/common-util'; | import { hexStringToUint8Array } from '@/utils/common-util'; | ||||
| import { SpeechPlayer } from 'openai-speech-stream-player'; | import { SpeechPlayer } from 'openai-speech-stream-player'; | ||||
| import { useCallback, useEffect, useRef, useState } from 'react'; | |||||
| import { useCallback, useContext, useEffect, useRef, useState } from 'react'; | |||||
| export const useSendFeedback = (messageId: string) => { | export const useSendFeedback = (messageId: string) => { | ||||
| const { visible, hideModal, showModal } = useSetModalState(); | const { visible, hideModal, showModal } = useSetModalState(); | ||||
| const { read } = useSpeechWithSse(); | const { read } = useSpeechWithSse(); | ||||
| const player = useRef<SpeechPlayer>(); | const player = useRef<SpeechPlayer>(); | ||||
| const [isPlaying, setIsPlaying] = useState<boolean>(false); | const [isPlaying, setIsPlaying] = useState<boolean>(false); | ||||
| const callback = useContext(ConversationContext); | |||||
| const initialize = useCallback(async () => { | const initialize = useCallback(async () => { | ||||
| player.current = new SpeechPlayer({ | player.current = new SpeechPlayer({ | ||||
| audio: ref.current!, | audio: ref.current!, | ||||
| onPlaying: () => { | onPlaying: () => { | ||||
| setIsPlaying(true); | setIsPlaying(true); | ||||
| callback?.(true); | |||||
| }, | }, | ||||
| onPause: () => { | onPause: () => { | ||||
| setIsPlaying(false); | setIsPlaying(false); | ||||
| callback?.(false); | |||||
| }, | }, | ||||
| onChunkEnd: () => {}, | onChunkEnd: () => {}, | ||||
| mimeType: 'audio/mpeg', | mimeType: 'audio/mpeg', | ||||
| }); | }); | ||||
| await player.current.init(); | await player.current.init(); | ||||
| }, []); | |||||
| }, [callback]); | |||||
| const pause = useCallback(() => { | const pause = useCallback(() => { | ||||
| player.current?.pause(); | player.current?.pause(); |
| parameters: Parameter[]; | parameters: Parameter[]; | ||||
| prologue: string; | prologue: string; | ||||
| system: string; | system: string; | ||||
| tts?: boolean; | |||||
| } | } | ||||
| export interface Parameter { | export interface Parameter { |
| } from '@/hooks/chat-hooks'; | } from '@/hooks/chat-hooks'; | ||||
| import { useFetchUserInfo } from '@/hooks/user-setting-hooks'; | import { useFetchUserInfo } from '@/hooks/user-setting-hooks'; | ||||
| import { memo } from 'react'; | import { memo } from 'react'; | ||||
| import { ConversationContext } from '../context'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| const ChatContainer = () => { | const ChatContainer = () => { | ||||
| const { data: conversation } = useFetchNextConversation(); | const { data: conversation } = useFetchNextConversation(); | ||||
| const { | const { | ||||
| value, | |||||
| ref, | ref, | ||||
| loading, | loading, | ||||
| sendLoading, | sendLoading, | ||||
| derivedMessages, | derivedMessages, | ||||
| handleInputChange, | handleInputChange, | ||||
| handlePressEnter, | handlePressEnter, | ||||
| value, | |||||
| regenerateMessage, | regenerateMessage, | ||||
| removeMessageById, | removeMessageById, | ||||
| redirectToNewConversation, | |||||
| } = useSendNextMessage(); | } = useSendNextMessage(); | ||||
| const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } = | const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } = | ||||
| <Flex flex={1} vertical className={styles.messageContainer}> | <Flex flex={1} vertical className={styles.messageContainer}> | ||||
| <div> | <div> | ||||
| <Spin spinning={loading}> | <Spin spinning={loading}> | ||||
| {derivedMessages?.map((message, i) => { | |||||
| return ( | |||||
| <MessageItem | |||||
| loading={ | |||||
| message.role === MessageType.Assistant && | |||||
| sendLoading && | |||||
| derivedMessages.length - 1 === i | |||||
| } | |||||
| key={message.id} | |||||
| item={message} | |||||
| nickname={userInfo.nickname} | |||||
| avatar={userInfo.avatar} | |||||
| reference={buildMessageItemReference( | |||||
| { | |||||
| message: derivedMessages, | |||||
| reference: conversation.reference, | |||||
| }, | |||||
| message, | |||||
| )} | |||||
| clickDocumentButton={clickDocumentButton} | |||||
| index={i} | |||||
| removeMessageById={removeMessageById} | |||||
| regenerateMessage={regenerateMessage} | |||||
| sendLoading={sendLoading} | |||||
| ></MessageItem> | |||||
| ); | |||||
| })} | |||||
| <ConversationContext.Provider value={redirectToNewConversation}> | |||||
| {derivedMessages?.map((message, i) => { | |||||
| return ( | |||||
| <MessageItem | |||||
| loading={ | |||||
| message.role === MessageType.Assistant && | |||||
| sendLoading && | |||||
| derivedMessages.length - 1 === i | |||||
| } | |||||
| key={message.id} | |||||
| item={message} | |||||
| nickname={userInfo.nickname} | |||||
| avatar={userInfo.avatar} | |||||
| reference={buildMessageItemReference( | |||||
| { | |||||
| message: derivedMessages, | |||||
| reference: conversation.reference, | |||||
| }, | |||||
| message, | |||||
| )} | |||||
| clickDocumentButton={clickDocumentButton} | |||||
| index={i} | |||||
| removeMessageById={removeMessageById} | |||||
| regenerateMessage={regenerateMessage} | |||||
| sendLoading={sendLoading} | |||||
| ></MessageItem> | |||||
| ); | |||||
| })} | |||||
| </ConversationContext.Provider> | |||||
| </Spin> | </Spin> | ||||
| </div> | </div> | ||||
| <div ref={ref} /> | <div ref={ref} /> |
| import { createContext } from 'react'; | |||||
| export const ConversationContext = createContext< | |||||
| null | ((isPlaying: boolean) => void) | |||||
| >(null); |
| useCallback, | useCallback, | ||||
| useEffect, | useEffect, | ||||
| useMemo, | useMemo, | ||||
| useRef, | |||||
| useState, | useState, | ||||
| } from 'react'; | } from 'react'; | ||||
| import { useSearchParams } from 'umi'; | import { useSearchParams } from 'umi'; | ||||
| removeMessageById, | removeMessageById, | ||||
| removeMessagesAfterCurrentMessage, | removeMessagesAfterCurrentMessage, | ||||
| } = useSelectNextMessages(); | } = useSelectNextMessages(); | ||||
| const { data: dialog } = useFetchNextDialog(); | |||||
| const currentConversationIdRef = useRef<string>(''); | |||||
| const redirectToNewConversation = useCallback( | |||||
| (isPlaying: boolean) => { | |||||
| if (!conversationId && dialog?.prompt_config?.tts && !isPlaying) { | |||||
| handleClickConversation(currentConversationIdRef.current); | |||||
| } | |||||
| }, | |||||
| [dialog, handleClickConversation, conversationId], | |||||
| ); | |||||
| const sendMessage = useCallback( | const sendMessage = useCallback( | ||||
| async ({ | async ({ | ||||
| if (currentConversationId) { | if (currentConversationId) { | ||||
| console.info('111'); | console.info('111'); | ||||
| // new conversation | // new conversation | ||||
| handleClickConversation(currentConversationId); | |||||
| if (!dialog?.prompt_config?.tts) { | |||||
| handleClickConversation(currentConversationId); | |||||
| } | |||||
| } else { | } else { | ||||
| console.info('222'); | console.info('222'); | ||||
| // fetchConversation(conversationId); | // fetchConversation(conversationId); | ||||
| } | } | ||||
| }, | }, | ||||
| [ | [ | ||||
| dialog, | |||||
| derivedMessages, | derivedMessages, | ||||
| conversationId, | conversationId, | ||||
| handleClickConversation, | handleClickConversation, | ||||
| const data = await setConversation(message.content); | const data = await setConversation(message.content); | ||||
| if (data.retcode === 0) { | if (data.retcode === 0) { | ||||
| const id = data.data.id; | const id = data.data.id; | ||||
| currentConversationIdRef.current = id; | |||||
| sendMessage({ | sendMessage({ | ||||
| message, | message, | ||||
| currentConversationId: id, | currentConversationId: id, | ||||
| ref, | ref, | ||||
| derivedMessages, | derivedMessages, | ||||
| removeMessageById, | removeMessageById, | ||||
| redirectToNewConversation, | |||||
| }; | }; | ||||
| }; | }; | ||||