117 lines
3.0 KiB
TypeScript
117 lines
3.0 KiB
TypeScript
import type { NitroFetchOptions, NitroFetchRequest } from 'nitropack'
|
|
import { FetchError } from 'ofetch'
|
|
|
|
/**
|
|
* 封装 HTTP 请求
|
|
* @param url 请求的路径
|
|
* @param options 请求选项
|
|
* @returns 返回请求结果
|
|
*
|
|
* @throws {FetchError} 请求失败时抛出错误
|
|
*/
|
|
export const http = async <T>(
|
|
url: string,
|
|
options?: NitroFetchOptions<NitroFetchRequest>,
|
|
) => {
|
|
const loginState = useLoginState()
|
|
|
|
const runtimeConfig = useRuntimeConfig()
|
|
const baseURL = runtimeConfig.public.baseURL as string
|
|
|
|
try {
|
|
const data = await $fetch<T>(url, {
|
|
baseURL,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${loginState.token}`,
|
|
},
|
|
...options,
|
|
})
|
|
|
|
return data
|
|
}
|
|
catch (err: unknown) {
|
|
if (err instanceof FetchError) {
|
|
throw err
|
|
}
|
|
else {
|
|
throw new FetchError('请求失败')
|
|
}
|
|
}
|
|
}
|
|
|
|
export const http_stream = async <ReqT>(
|
|
endpoint: string,
|
|
params: ReqT,
|
|
events: {
|
|
onStart?: (id: string, created_at?: number) => void
|
|
onTextChunk?: (message: string) => void
|
|
onComplete?: (id: string | null, finished_at?: number) => void
|
|
}) => {
|
|
const { onStart, onTextChunk, onComplete } = events
|
|
|
|
const loginState = useLoginState()
|
|
|
|
const runtimeConfig = useRuntimeConfig()
|
|
const baseURL = runtimeConfig.public.baseURL as string
|
|
|
|
const response = await fetch(new URL(endpoint, baseURL), {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'text/event-stream',
|
|
'Authorization': `Bearer ${loginState.token}`,
|
|
},
|
|
body: JSON.stringify(params),
|
|
})
|
|
|
|
if (!response) {
|
|
throw new Error('Network response was not ok')
|
|
}
|
|
|
|
const reader = response.body?.getReader()
|
|
const decoder = new TextDecoder('utf-8')
|
|
let buffer = ''
|
|
|
|
while (true) {
|
|
const { done, value } = await reader?.read() || {}
|
|
if (done) break
|
|
|
|
buffer += decoder.decode(value, { stream: true })
|
|
|
|
const parts = buffer.split('\n\n')
|
|
buffer = parts.pop()!
|
|
|
|
for (const part of parts) {
|
|
if (!part.trim().startsWith('data:')) continue
|
|
const payload = part.replace(/^data:\s*/, '').trim()
|
|
|
|
try {
|
|
const obj = JSON.parse(payload)
|
|
if (obj?.event && obj.event === 'workflow_started') {
|
|
onStart?.(obj?.data?.id || null, obj?.data?.created_at || 0)
|
|
}
|
|
if (obj?.event && obj.event === 'workflow_finished') {
|
|
onComplete?.(obj?.data?.id || null, obj?.data?.finished_at || 0)
|
|
return
|
|
}
|
|
if (obj?.event && obj.event === 'text_chunk') {
|
|
let text_chunk = obj.data?.text as string
|
|
if (text_chunk) {
|
|
if (text_chunk.startsWith('<') && text_chunk.endsWith('>')) {
|
|
const endTag = text_chunk.match(/<\/[^>]*>/)
|
|
if (endTag) {
|
|
text_chunk = text_chunk.replace(endTag[0], '\n' + endTag[0])
|
|
}
|
|
}
|
|
onTextChunk?.(text_chunk)
|
|
}
|
|
}
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}
|
|
}
|
|
onComplete?.(null)
|
|
}
|