|
@@ -0,0 +1,274 @@
|
|
|
+import {ElMessage, ElNotification} from "element-plus";
|
|
|
+
|
|
|
+const ContentType = {
|
|
|
+ json: 'application/json',
|
|
|
+ stream: 'text/event-stream',
|
|
|
+ audio: 'audio/mpeg',
|
|
|
+ form: 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
|
+ download: 'application/octet-stream', // for download
|
|
|
+ upload: 'multipart/form-data', // for upload
|
|
|
+}
|
|
|
+const baseOptions = {
|
|
|
+ method: 'GET',
|
|
|
+ mode: 'cors',
|
|
|
+ credentials: 'include', // always send cookies、HTTP Basic authentication.
|
|
|
+ headers: new Headers({
|
|
|
+ 'Content-Type': ContentType.json,
|
|
|
+ }),
|
|
|
+ redirect: 'follow',
|
|
|
+}
|
|
|
+const unicodeToChar = (text: string) => {
|
|
|
+ if (!text)
|
|
|
+ return ''
|
|
|
+
|
|
|
+ return text.replace(/\\u[0-9a-f]{4}/g, (_match, p1) => {
|
|
|
+ return String.fromCharCode(parseInt(p1, 16))
|
|
|
+ })
|
|
|
+}
|
|
|
+const handleStream = (
|
|
|
+ response,
|
|
|
+ onData,
|
|
|
+ onCompleted?,
|
|
|
+ onThought?,
|
|
|
+ onMessageEnd?,
|
|
|
+ onMessageReplace?,
|
|
|
+ onFile?,
|
|
|
+ onWorkflowStarted?,
|
|
|
+ onWorkflowFinished?,
|
|
|
+ onNodeStarted?,
|
|
|
+ onNodeFinished?,
|
|
|
+ onIterationStart?,
|
|
|
+ onIterationNext?,
|
|
|
+ onIterationFinish?,
|
|
|
+ onNodeRetry?,
|
|
|
+ onParallelBranchStarted?,
|
|
|
+ onParallelBranchFinished?,
|
|
|
+ onTextChunk?,
|
|
|
+ onTTSChunk?,
|
|
|
+ onTTSEnd?,
|
|
|
+ onTextReplace?,
|
|
|
+) => {
|
|
|
+ if (!response.ok)
|
|
|
+ throw new Error('Network response was not ok')
|
|
|
+
|
|
|
+ const reader = response.body?.getReader()
|
|
|
+ const decoder = new TextDecoder('utf-8')
|
|
|
+ let buffer = ''
|
|
|
+ let bufferObj
|
|
|
+ let isFirstMessage = true
|
|
|
+ const read = () => {
|
|
|
+ let hasError = false
|
|
|
+ reader?.read().then((result: any) => {
|
|
|
+ if (result.done) {
|
|
|
+ onCompleted && onCompleted()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ buffer += decoder.decode(result.value, { stream: true })
|
|
|
+ const lines = buffer.split('\n')
|
|
|
+ try {
|
|
|
+ lines.forEach((message) => {
|
|
|
+ if (message.startsWith('data: ')) { // check if it starts with data:
|
|
|
+ try {
|
|
|
+ bufferObj = JSON.parse(message.substring(6))// remove data: and parse as json
|
|
|
+ }
|
|
|
+ catch (e) {
|
|
|
+ // mute handle message cut off
|
|
|
+ onData('', isFirstMessage, {
|
|
|
+ conversationId: bufferObj?.conversation_id,
|
|
|
+ messageId: bufferObj?.message_id,
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (bufferObj.status === 400 || !bufferObj.event) {
|
|
|
+ onData('', false, {
|
|
|
+ conversationId: undefined,
|
|
|
+ messageId: '',
|
|
|
+ errorMessage: bufferObj?.message,
|
|
|
+ errorCode: bufferObj?.code,
|
|
|
+ })
|
|
|
+ hasError = true
|
|
|
+ onCompleted?.(true, bufferObj?.message)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (bufferObj.event === 'message' || bufferObj.event === 'agent_message') {
|
|
|
+ // can not use format here. Because message is splitted.
|
|
|
+ onData(unicodeToChar(bufferObj.answer), isFirstMessage, {
|
|
|
+ conversationId: bufferObj.conversation_id,
|
|
|
+ taskId: bufferObj.task_id,
|
|
|
+ messageId: bufferObj.id,
|
|
|
+ })
|
|
|
+ isFirstMessage = false
|
|
|
+ }
|
|
|
+ else if (bufferObj.event === 'agent_thought') {
|
|
|
+ onThought?.(bufferObj)
|
|
|
+ }
|
|
|
+ else if (bufferObj.event === 'message_file') {
|
|
|
+ onFile?.(bufferObj)
|
|
|
+ }
|
|
|
+ else if (bufferObj.event === 'message_end') {
|
|
|
+ onMessageEnd?.(bufferObj)
|
|
|
+ }
|
|
|
+ else if (bufferObj.event === 'message_replace') {
|
|
|
+ onMessageReplace?.(bufferObj)
|
|
|
+ }
|
|
|
+ else if (bufferObj.event === 'workflow_started') {
|
|
|
+ onWorkflowStarted?.(bufferObj)
|
|
|
+ }
|
|
|
+ else if (bufferObj.event === 'workflow_finished') {
|
|
|
+ onWorkflowFinished?.(bufferObj)
|
|
|
+ }
|
|
|
+ else if (bufferObj.event === 'node_started') {
|
|
|
+ onNodeStarted?.(bufferObj)
|
|
|
+ }
|
|
|
+ else if (bufferObj.event === 'node_finished') {
|
|
|
+ onNodeFinished?.(bufferObj)
|
|
|
+ }
|
|
|
+ else if (bufferObj.event === 'iteration_started') {
|
|
|
+ onIterationStart?.(bufferObj)
|
|
|
+ }
|
|
|
+ else if (bufferObj.event === 'iteration_next') {
|
|
|
+ onIterationNext?.(bufferObj)
|
|
|
+ }
|
|
|
+ else if (bufferObj.event === 'iteration_completed') {
|
|
|
+ onIterationFinish?.(bufferObj)
|
|
|
+ }
|
|
|
+ else if (bufferObj.event === 'node_retry') {
|
|
|
+ onNodeRetry?.(bufferObj)
|
|
|
+ }
|
|
|
+ else if (bufferObj.event === 'parallel_branch_started') {
|
|
|
+ onParallelBranchStarted?.(bufferObj)
|
|
|
+ }
|
|
|
+ else if (bufferObj.event === 'parallel_branch_finished') {
|
|
|
+ onParallelBranchFinished?.(bufferObj)
|
|
|
+ }
|
|
|
+ else if (bufferObj.event === 'text_chunk') {
|
|
|
+ onTextChunk?.(bufferObj)
|
|
|
+ }
|
|
|
+ else if (bufferObj.event === 'text_replace') {
|
|
|
+ onTextReplace?.(bufferObj)
|
|
|
+ }
|
|
|
+ else if (bufferObj.event === 'tts_message') {
|
|
|
+ onTTSChunk?.(bufferObj.message_id, bufferObj.audio, bufferObj.audio_type)
|
|
|
+ }
|
|
|
+ else if (bufferObj.event === 'tts_message_end') {
|
|
|
+ onTTSEnd?.(bufferObj.message_id, bufferObj.audio)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ buffer = lines[lines.length - 1]
|
|
|
+ } catch (e) {
|
|
|
+ onData('', false, {
|
|
|
+ conversationId: undefined,
|
|
|
+ messageId: '',
|
|
|
+ errorMessage: `${e}`,
|
|
|
+ })
|
|
|
+ hasError = true
|
|
|
+ onCompleted?.(true, e as string)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (!hasError) {
|
|
|
+ read()
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ read()
|
|
|
+}
|
|
|
+export const ssePost = (url, fetchOptions, otherOptions,) => {
|
|
|
+ const {
|
|
|
+ onData,
|
|
|
+ onCompleted,
|
|
|
+ onThought,
|
|
|
+ onFile,
|
|
|
+ onMessageEnd,
|
|
|
+ onMessageReplace,
|
|
|
+ onWorkflowStarted,
|
|
|
+ onWorkflowFinished,
|
|
|
+ onNodeStarted,
|
|
|
+ onNodeFinished,
|
|
|
+ onIterationStart,
|
|
|
+ onIterationNext,
|
|
|
+ onIterationFinish,
|
|
|
+ onNodeRetry,
|
|
|
+ onParallelBranchStarted,
|
|
|
+ onParallelBranchFinished,
|
|
|
+ onTextChunk,
|
|
|
+ onTTSChunk,
|
|
|
+ onTTSEnd,
|
|
|
+ onTextReplace,
|
|
|
+ onError,
|
|
|
+ getAbortController,
|
|
|
+ } = otherOptions
|
|
|
+ const abortController = new AbortController()
|
|
|
+
|
|
|
+ const options = Object.assign({}, baseOptions, {
|
|
|
+ method: 'POST',
|
|
|
+ signal: abortController.signal,
|
|
|
+ }, fetchOptions)
|
|
|
+
|
|
|
+ const contentType = options.headers.get('Content-Type')
|
|
|
+ if (!contentType)
|
|
|
+ options.headers.set('Content-Type', ContentType.json)
|
|
|
+
|
|
|
+ getAbortController?.(abortController)
|
|
|
+
|
|
|
+ const { body } = options
|
|
|
+ if (body)
|
|
|
+ options.body = JSON.stringify(body)
|
|
|
+
|
|
|
+ const accessToken = localStorage.getItem('difyToken')
|
|
|
+ options.headers.set('Authorization', `Bearer ${accessToken}`)
|
|
|
+ globalThis.fetch(url, options).then((res) => {
|
|
|
+ if (!/^(2|3)\d{2}$/.test(String(res.status))) {
|
|
|
+ if (res.status === 401) {
|
|
|
+ ElMessage.error('状态码错误,' + res)
|
|
|
+ // refreshAccessTokenOrRelogin(TIME_OUT).then(() => {
|
|
|
+ // ssePost(url, fetchOptions, otherOptions)
|
|
|
+ // }).catch(() => {
|
|
|
+ // res.json().then((data: any) => {
|
|
|
+ // if (isPublicAPI) {
|
|
|
+ // if (data.code === 'web_sso_auth_required')
|
|
|
+ // requiredWebSSOLogin()
|
|
|
+ //
|
|
|
+ // if (data.code === 'unauthorized') {
|
|
|
+ // removeAccessToken()
|
|
|
+ // globalThis.location.reload()
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // })
|
|
|
+ // })
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ res.json().then((data) => {
|
|
|
+ ElNotification({
|
|
|
+ message: data.message || 'Server Error',
|
|
|
+ type: 'error',
|
|
|
+ })
|
|
|
+ })
|
|
|
+ onError?.('Server Error')
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
+ return handleStream(res, (str: string, isFirstMessage: boolean, moreInfo) => {
|
|
|
+ if (moreInfo.errorMessage) {
|
|
|
+ onError?.(moreInfo.errorMessage, moreInfo.errorCode)
|
|
|
+ // TypeError: Cannot assign to read only property ... will happen in page leave, so it should be ignored.
|
|
|
+ if (moreInfo.errorMessage !== 'AbortError: The user aborted a request.' && !moreInfo.errorMessage.includes('TypeError: Cannot assign to read only property')) {
|
|
|
+ ElNotification({
|
|
|
+ message: moreInfo.errorMessage,
|
|
|
+ type: 'error',
|
|
|
+ })
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
+ onData?.(str, isFirstMessage, moreInfo)
|
|
|
+ }, onCompleted, onThought, onMessageEnd, onMessageReplace, onFile, onWorkflowStarted, onWorkflowFinished, onNodeStarted, onNodeFinished, onIterationStart, onIterationNext, onIterationFinish, onNodeRetry, onParallelBranchStarted, onParallelBranchFinished, onTextChunk, onTTSChunk, onTTSEnd, onTextReplace)
|
|
|
+ }).catch((e) => {
|
|
|
+ if (e.toString() !== 'AbortError: The user aborted a request.' && !e.toString().errorMessage.includes('TypeError: Cannot assign to read only property')) {
|
|
|
+ ElNotification({
|
|
|
+ message: e,
|
|
|
+ type: 'error',
|
|
|
+ })
|
|
|
+ }
|
|
|
+ onError?.(e)
|
|
|
+ })
|
|
|
+}
|