diff --git a/components/common/chatUi/chatUi.less b/components/common/chatUi/chatUi.less index 0f66b3d..5392e8e 100644 --- a/components/common/chatUi/chatUi.less +++ b/components/common/chatUi/chatUi.less @@ -343,6 +343,15 @@ } } } + + &-reaction { + padding: 4px; + padding-left: 0; + display: flex; + align-items: center; + gap: 10px; + cursor: pointer; + } } &__assistant { diff --git a/components/common/chatUi/chatUi.tsx b/components/common/chatUi/chatUi.tsx index 23af098..d76c7a2 100644 --- a/components/common/chatUi/chatUi.tsx +++ b/components/common/chatUi/chatUi.tsx @@ -18,8 +18,9 @@ import { v4 as uuidv4 } from 'uuid'; import { cloneDeep, isString } from 'lodash-es'; import Address from '../address'; import Markdown from '../markdown/Markdown'; +import { FaRegThumbsUp, FaThumbsUp, FaRegThumbsDown, FaThumbsDown } from 'react-icons/fa'; import './chatUi.less'; -import { chatWithStream, ConversationProperty, Message } from 'components/utilities/chatWithStream'; +import { chatWithStream, ConversationProperty, Message, ResponseChunk } from 'components/utilities/chatWithStream'; const indexerName: { [key in string]: string } = { '0xd0af1919af890cfdd8d12be5cf1b1421224fc29a': 'Mainnet Operator', @@ -45,6 +46,7 @@ export interface ChatBoxProps { model?: string; onSendMessage?: (message: string) => void; onChatboxOpen?: () => void; + onReaction?: (reaction: 'like' | 'dislike' | 'none', message: Message, userQuestion: Message) => void; } export interface ConversationItemProps { @@ -98,10 +100,18 @@ export const ConversationItem: FC = ({ property, active, export const ConversationMessage = forwardRef< { scrollToBottom: () => void }, - { property: ConversationProperty; answerStatus: ChatBotAnswerStatus; version?: 'chat' | 'chatbox' } ->(({ property, answerStatus, version = 'chat' }, ref) => { + { + property: ConversationProperty; + answerStatus: ChatBotAnswerStatus; + version?: 'chat' | 'chatbox'; + onReaction?: (reaction: 'like' | 'dislike' | 'none', message: Message, userQuestion: Message) => void; + } +>(({ property, answerStatus, version = 'chat', onReaction }, ref) => { const bem = useBem(version === 'chat' ? 'subql-chat-conversation-message' : 'subql-chatbox-conversation-message'); const outerRef = useRef(null); + const [reactionState, setReactionState] = useState<{ + [key in string]: { status: 'like' | 'dislike' | 'none'; message: Message }; + }>({}); useImperativeHandle(ref, () => ({ scrollToBottom: (onlyWhenReachBottom = false) => { if (onlyWhenReachBottom && outerRef.current) { @@ -159,6 +169,66 @@ export const ConversationMessage = forwardRef< {/* TODO: support array */}
{isString(message.content) ? {message.content} : ''} + {message.type !== 'welcome' && message.role === 'assistant' && message.content && ( +
+ {(reactionState[message.id || '0']?.status === 'none' || !reactionState[message.id || '0']) && ( + { + setReactionState({ + ...reactionState, + [message.id || '0']: { + status: 'like', + message, + }, + }); + onReaction?.('like', message, property.messages[index - 1]); + }} + > + )} + {reactionState[message.id || '0']?.status === 'like' && ( + { + // setReactionState({ + // ...reactionState, + // [message.id || '0']: { + // status: 'none', + // message, + // }, + // }); + // onReaction?.('none', message); + // }} + > + )} + {reactionState[message.id || '0']?.status === 'dislike' && ( + { + // setReactionState({ + // ...reactionState, + // [message.id || '0']: { + // status: 'none', + // message, + // }, + // }); + // onReaction?.('none', message); + // }} + > + )} + {(reactionState[message.id || '0']?.status === 'none' || !reactionState[message.id || '0']) && ( + { + setReactionState({ + ...reactionState, + [message.id || '0']: { + status: 'dislike', + message, + }, + }); + onReaction?.('dislike', message, property.messages[index - 1]); + }} + > + )} +
+ )}
); @@ -558,7 +628,7 @@ const ChatBoxIcon: FC<{ className?: string }> = ({ className }) => ( // maybe split to other file. export const ChatBox: FC = (props) => { - const { chatUrl, prompt = '', model, onSendMessage, onChatboxOpen } = props; + const { chatUrl, prompt = '', model, onSendMessage, onChatboxOpen, onReaction } = props; const [popoverOpen, setPopoverOpen] = useState(false); const bem = useBem('subql-chatbox'); const [currentInput, setCurrentInput] = useState(''); @@ -617,6 +687,8 @@ export const ChatBox: FC = (props) => { const robotAnswer: Message = { role: 'assistant' as const, content: '', + id: '0', + conversation_id: '0', }; setCurrentInput(''); @@ -651,8 +723,10 @@ export const ChatBox: FC = (props) => { if (invalidJson) { try { invalidJson += part; - const parsed: { choices: { delta: { content: string } }[] } = JSON.parse(invalidJson); + const parsed: ResponseChunk = JSON.parse(invalidJson); robotAnswer.content += parsed?.choices?.[0]?.delta?.content; + robotAnswer.id = parsed.id !== '0' ? parsed.id : '0'; + robotAnswer.conversation_id = parsed.conversation_id !== '0' ? parsed.conversation_id : '0'; await pushNewMsgToChat(newChat, robotAnswer, curChat); console.warn(messageRef); @@ -668,8 +742,10 @@ export const ChatBox: FC = (props) => { if (partWithHandle) { try { - const parsed: { choices: { delta: { content: string } }[] } = JSON.parse(partWithHandle); + const parsed: ResponseChunk = JSON.parse(partWithHandle); robotAnswer.content += parsed?.choices?.[0]?.delta?.content; + robotAnswer.id = parsed.id !== '0' ? parsed.id : '0'; + robotAnswer.conversation_id = parsed.conversation_id !== '0' ? parsed.conversation_id : '0'; await pushNewMsgToChat(newChat, robotAnswer, curChat); messageRef.current?.scrollToBottom(true); @@ -682,8 +758,10 @@ export const ChatBox: FC = (props) => { if (invalidJson) { try { - const parsed: { choices: { delta: { content: string } }[] } = JSON.parse(invalidJson); + const parsed: ResponseChunk = JSON.parse(invalidJson); robotAnswer.content += parsed?.choices?.[0]?.delta?.content; + robotAnswer.id = parsed.id !== '0' ? parsed.id : '0'; + robotAnswer.conversation_id = parsed.conversation_id !== '0' ? parsed.conversation_id : '0'; await pushNewMsgToChat(newChat, robotAnswer, curChat); messageRef.current?.scrollToBottom(true); @@ -696,7 +774,9 @@ export const ChatBox: FC = (props) => { robotAnswer.content = 'Sorry, The Server is not available now.'; await pushNewMsgToChat(newChat, robotAnswer, curChat); setAnswerStatus(ChatBotAnswerStatus.Error); + return; } + // onResponse?.(robotAnswer); inputRef.current?.focus(); setAnswerStatus(ChatBotAnswerStatus.Success); } catch (e) { @@ -738,6 +818,7 @@ export const ChatBox: FC = (props) => { answerStatus={answerStatus} version="chatbox" ref={messageRef} + onReaction={onReaction} >
diff --git a/components/utilities/chatWithStream.ts b/components/utilities/chatWithStream.ts index b8dc54d..062ad71 100644 --- a/components/utilities/chatWithStream.ts +++ b/components/utilities/chatWithStream.ts @@ -23,6 +23,14 @@ export interface Message { role: AiMessageRole; content: string | Content[]; type?: 'welcome'; // welcome should filter before send + id?: string; + conversation_id?: string; +} + +export interface ResponseChunk { + id?: string; + conversation_id?: string; + choices: { delta: { content: string } }[]; } export const chatWithStream = async (url: string, body: { messages: Message[]; model?: string }) => {