From bd6b3dcbe3b95945fa140615c1195baa2324ebc1 Mon Sep 17 00:00:00 2001 From: jialin Date: Mon, 24 Jun 2024 19:14:42 +0800 Subject: [PATCH] fix: submit completion by fetch --- src/hooks/use-event-source.ts | 61 +++++++++++++++++++ src/pages/playground/apis/index.ts | 40 ++++++++++++ .../playground/components/ground-left.tsx | 57 +++++++++++------ .../playground/components/message-item.tsx | 44 +++++++------ src/request-config.ts | 1 - 5 files changed, 164 insertions(+), 39 deletions(-) create mode 100644 src/hooks/use-event-source.ts diff --git a/src/hooks/use-event-source.ts b/src/hooks/use-event-source.ts new file mode 100644 index 00000000..0f3dbe37 --- /dev/null +++ b/src/hooks/use-event-source.ts @@ -0,0 +1,61 @@ +import qs from 'query-string'; +import { useRef } from 'react'; + +export const createEventSourceURL = (url: string) => { + const { host, protocol } = window.location; + return `${protocol}://${host}${url}`; +}; + +/* +0: connecting +1: connect successfully +2: closed +*/ + +export default function useEventSource() { + const eventSourceRef = useRef(null); + + const createEventSourceConnection = (query: { + url: string; + params: any; + onmessage?: (data: any) => void; + }) => { + eventSourceRef.current?.close?.(); + + const { url, params, onmessage = () => {} } = query; + + const sseurl = createEventSourceURL(url); + + eventSourceRef.current = new EventSource( + `${url}?${qs.stringify({ + ...params + })}`, + { + withCredentials: true + } + ); + + eventSourceRef.current.onmessage = (res: any) => { + try { + const data = JSON.parse(res.data); + console.log('event source message: ', { data, resData: res }); + onmessage(data); + } catch (error) { + // error + } + }; + + eventSourceRef.current.onopen = () => { + console.log('event source connected...'); + }; + + eventSourceRef.current.onerror = (error: any) => { + console.log('event source error: ', error); + }; + }; + + return { + eventSourceRef: eventSourceRef, + createEventSourceConnection + }; +} diff --git a/src/pages/playground/apis/index.ts b/src/pages/playground/apis/index.ts index 7412b1dc..5a0a313a 100644 --- a/src/pages/playground/apis/index.ts +++ b/src/pages/playground/apis/index.ts @@ -8,3 +8,43 @@ export async function execChatCompletions(params: any) { data: params }); } + +export const fetchChatStream = async (params: any) => { + const response = await fetch(`/v1${CHAT_API}`, { + method: 'POST', + body: JSON.stringify(params), + headers: { + 'Content-Type': 'application/json' + } + }); + if (!response.ok) { + throw new Error('Network response was not ok'); + return null; + } + const reader = response?.body?.getReader(); + const decoder = new TextDecoder('utf-8'); + return { + reader, + decoder + }; +}; + +export const receiveChatStream = async ( + reader: any, + decoder: TextDecoder, + callback: (data: any) => void +) => { + const { done, value } = await reader.read(); + + if (done) { + return; + } + + let chunk = decoder.decode(value, { stream: true }); + if (chunk.startsWith('data: ')) { + chunk = chunk.substring('data: '.length); + } + const item = JSON.parse(chunk?.trim()); + callback(item); + await receiveChatStream(reader, decoder, callback); +}; diff --git a/src/pages/playground/components/ground-left.tsx b/src/pages/playground/components/ground-left.tsx index d8caa186..23190fc3 100644 --- a/src/pages/playground/components/ground-left.tsx +++ b/src/pages/playground/components/ground-left.tsx @@ -5,7 +5,7 @@ import { useIntl } from '@umijs/max'; import { Button, Input, Spin } from 'antd'; import _ from 'lodash'; import { useRef, useState } from 'react'; -import { execChatCompletions } from '../apis'; +import { fetchChatStream, receiveChatStream } from '../apis'; import { Roles } from '../config'; import '../style/ground-left.less'; import '../style/system-message-wrap.less'; @@ -13,7 +13,6 @@ import ChatFooter from './chat-footer'; import MessageItem from './message-item'; import ReferenceParams from './reference-params'; import ViewCodeModal from './view-code-modal'; - interface MessageProps { parameters: any; } @@ -42,7 +41,11 @@ const MessageList: React.FC = (props) => { const [activeIndex, setActiveIndex] = useState(-1); const [tokenResult, setTokenResult] = useState(null); const systemRef = useRef(null); + const contentRef = useRef(''); + const handleReceiveMessage = (data: any) => { + console.log('event source message: ', { data }); + }; const handleSystemMessageChange = (e: any) => { setSystemMessage(e.target.value); }; @@ -61,10 +64,32 @@ const MessageList: React.FC = (props) => { setActiveIndex(messageList.length - 1); }; + const joinMessage = (chunk: any) => { + if (_.get(chunk, 'choices.0.finish_reason')) { + setTokenResult({ + ...chunk?.usage + }); + + return true; + } + contentRef.current = + contentRef.current + _.get(chunk, 'choices.0.delta.content'); + setMessageList([ + ...messageList, + { + role: Roles.Assistant, + content: contentRef.current, + uid: messageId.current + } + ]); + return false; + }; const submitMessage = async () => { try { setLoading(true); - + setMessageId(); + setTokenResult(null); + contentRef.current = ''; const chatParams = { messages: systemMessage ? [ @@ -75,22 +100,19 @@ const MessageList: React.FC = (props) => { ...messageList ] : [...messageList], - ...parameters + ...parameters, + stream: true }; - const data = await execChatCompletions(chatParams); - const assistant = _.get(data, ['choices', '0', 'message']); - setTokenResult({ - ...data.usage + const result = await fetchChatStream(chatParams); + + if (!result) { + return; + } + const { reader, decoder } = result; + + await receiveChatStream(reader, decoder, (data: any) => { + joinMessage(data); }); - setMessageList([ - ...messageList, - { - role: Roles.Assistant, - content: assistant.content, - uid: messageId.current + 1 - } - ]); - setMessageId(); setLoading(false); } catch (error) { setLoading(false); @@ -105,7 +127,6 @@ const MessageList: React.FC = (props) => { }; const handleSubmit = () => { - console.log('submit'); submitMessage(); }; diff --git a/src/pages/playground/components/message-item.tsx b/src/pages/playground/components/message-item.tsx index 59c560a8..3a210a77 100644 --- a/src/pages/playground/components/message-item.tsx +++ b/src/pages/playground/components/message-item.tsx @@ -32,28 +32,32 @@ const MessageItem: React.FC<{ } }, [isFocus]); - useEffect(() => { - if (isTyping) return; - let index = 0; - const text = message.content; - if (!text.length) { - return; - } - setMessageContent(''); - setIsAnimating(true); - const intervalId = setInterval(() => { - setMessageContent((prev) => prev + text[index]); - index += 1; - if (index === text.length) { - setIsAnimating(false); - clearInterval(intervalId); - } - }, 20); - return () => clearInterval(intervalId); - }, [message.content, isTyping]); + // useEffect(() => { + // if (isTyping) return; + // let index = 0; + // const text = message.content; + // if (!text.length) { + // return; + // } + // setMessageContent(''); + // setIsAnimating(true); + // const intervalId = setInterval(() => { + // setMessageContent((prev) => prev + text[index]); + // index += 1; + // if (index === text.length) { + // setIsAnimating(false); + // clearInterval(intervalId); + // } + // }, 20); + // return () => clearInterval(intervalId); + // }, [message.content, isTyping]); useEffect(() => { - if (!isAnimating && !isInitialRender.current) { + setMessageContent(message.content); + }, [message.content]); + + useEffect(() => { + if (!isInitialRender.current) { updateMessage({ role: roleType, content: messageContent, diff --git a/src/request-config.ts b/src/request-config.ts index 75b395d8..e14cee09 100644 --- a/src/request-config.ts +++ b/src/request-config.ts @@ -28,7 +28,6 @@ export const requestConfig: RequestConfig = { ], responseInterceptors: [ (response) => { - console.log('responseInterceptors+++++++++++++++', response); return response; } ]