街角小林 10e9fa3f22 update
2025-02-26 08:50:25 +08:00

121 lines
3.1 KiB
JavaScript

class Ai {
constructor(options = {}) {
this.options = options
this.baseData = {}
this.controller = null
this.currentChunk = ''
this.content = ''
}
init(type = 'huoshan', options = {}) {
// 火山引擎接口
if (type === 'huoshan') {
this.baseData = {
api: options.api,
method: options.method,
headers: {
Authorization: 'Bearer ' + options.key
},
data: {
model: options.model,
stream: true
}
}
}
}
async request(data, progress = () => {}, end = () => {}, err = () => {}) {
try {
const res = await this.postMsg(data)
const decoder = new TextDecoder()
while (1) {
const { done, value } = await res.read()
if (done) {
return
}
// 拿到当前切片的数据
const text = decoder.decode(value)
// 处理切片数据
let chunk = this.handleChunkData(text)
// 判断是否有不完整切片,如果有,合并下一次处理,没有则获取数据
if (this.currentChunk) continue
let isEnd = false
const list = chunk
.split('\n')
.filter(item => {
isEnd = item.includes('[DONE]')
return !!item && !isEnd
})
.map(item => {
return JSON.parse(item.replace(/^data:/, ''))
})
list.forEach(item => {
this.content += item.choices
.map(item2 => {
return item2.delta.content
})
.join('')
})
progress(this.content)
if (isEnd) {
end(this.content)
}
}
} catch (error) {
console.log(error)
// 手动停止请求不需要触发错误回调
if (!(error && error.name === 'AbortError')) {
err(error)
}
}
}
async postMsg(data) {
this.controller = new AbortController()
const res = await fetch(`http://localhost:${this.options.port}/ai/chat`, {
signal: this.controller.signal,
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
...this.baseData,
data: {
...this.baseData.data,
...data
}
})
})
if (res.status && res.status !== 200) {
throw new Error('请求失败')
}
return res.body.getReader()
}
handleChunkData(chunk) {
chunk = chunk.trim()
// 如果存在上一个切片
if (this.currentChunk) {
chunk = this.currentChunk + chunk
this.currentChunk = ''
}
// 如果存在done,认为是完整切片且是最后一个切片
if (chunk.includes('[DONE]')) {
return chunk
}
// 最后一个字符串不为},则默认切片不完整,保存与下次拼接使用(这种方法不严谨,但已经能解决大部分场景的问题)
if (chunk[chunk.length - 1] !== '}') {
this.currentChunk = chunk
}
return chunk
}
stop() {
this.controller.abort()
this.controller = new AbortController()
}
}
export default Ai