Upload 2 files
Browse files- src/index.js +156 -1
src/index.js
CHANGED
@@ -215,8 +215,151 @@ router.post('/v1/chat/completions', async (ctx) => {
|
|
215 |
}
|
216 |
})
|
217 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
218 |
|
219 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
220 |
router.get('/v1/models', async (ctx) => {
|
221 |
ctx.body = {
|
222 |
object: "list",
|
@@ -229,6 +372,18 @@ router.get('/v1/models', async (ctx) => {
|
|
229 |
}
|
230 |
})
|
231 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
232 |
|
233 |
// 注册路由
|
234 |
app.use(router.routes()).use(router.allowedMethods())
|
|
|
215 |
}
|
216 |
})
|
217 |
|
218 |
+
// hf/v1 路由(复制 v1 路由的处理逻辑)
|
219 |
+
router.post('/hf/v1/chat/completions', async (ctx) => {
|
220 |
+
const { messages, stream = false, model = 'claude-3-5-sonnet' } = ctx.request.body
|
221 |
+
const session_id = ctx.get('Authorization')?.replace('Bearer ', '')
|
222 |
+
|
223 |
+
if (!session_id) {
|
224 |
+
ctx.status = 401
|
225 |
+
ctx.body = { error: '未提供有效的 session_id' }
|
226 |
+
return
|
227 |
+
}
|
228 |
+
|
229 |
+
try {
|
230 |
+
const response = await makeRequest(session_id, model, messages)
|
231 |
+
if (stream == "true" || stream == true) {
|
232 |
+
ctx.set({
|
233 |
+
'Content-Type': 'text/event-stream',
|
234 |
+
'Cache-Control': 'no-cache',
|
235 |
+
'Connection': 'keep-alive',
|
236 |
+
})
|
237 |
+
} else {
|
238 |
+
ctx.set({
|
239 |
+
'Content-Type': 'application/json',
|
240 |
+
})
|
241 |
+
}
|
242 |
+
|
243 |
+
const messageId = crypto.randomUUID()
|
244 |
+
const reader = response.body.getReader()
|
245 |
+
if (stream == "true" || stream == true) {
|
246 |
+
ctx.res.write(`data: ${JSON.stringify({
|
247 |
+
"id": `chatcmpl-${messageId}`,
|
248 |
+
"choices": [
|
249 |
+
{
|
250 |
+
"index": 0,
|
251 |
+
"delta": {
|
252 |
+
"content": "",
|
253 |
+
"role": "assistant"
|
254 |
+
}
|
255 |
+
}
|
256 |
+
],
|
257 |
+
"created": Math.floor(Date.now() / 1000),
|
258 |
+
"model": models[`${model}`],
|
259 |
+
"object": "chat.completion.chunk"
|
260 |
+
})}\n\n`)
|
261 |
+
}
|
262 |
+
|
263 |
+
try {
|
264 |
+
let resBody = {}
|
265 |
+
|
266 |
+
while (true) {
|
267 |
+
const { done, value } = await reader.read()
|
268 |
+
if (done) {
|
269 |
+
if (stream == "true" || stream == true) {
|
270 |
+
// 发送完成标记
|
271 |
+
ctx.res.write('data: [DONE]\n\n')
|
272 |
+
}
|
273 |
+
break
|
274 |
+
}
|
275 |
+
|
276 |
+
if (stream) {
|
277 |
+
const text = new TextDecoder().decode(value)
|
278 |
+
const textContent = [...text.matchAll(/data:.*"}/g)]
|
279 |
+
|
280 |
+
textContent.forEach(item => {
|
281 |
+
if (!item[0]) {
|
282 |
+
return
|
283 |
+
}
|
284 |
|
285 |
+
const content = JSON.parse(item[0].replace("data: ", ''))
|
286 |
+
if (!content || !content.delta) {
|
287 |
+
return
|
288 |
+
}
|
289 |
+
|
290 |
+
// 发送增量内容
|
291 |
+
ctx.res.write(`data: ${JSON.stringify({
|
292 |
+
"id": `chatcmpl-${messageId}`,
|
293 |
+
"choices": [
|
294 |
+
{
|
295 |
+
"index": 0,
|
296 |
+
"delta": {
|
297 |
+
"content": content.delta
|
298 |
+
}
|
299 |
+
}
|
300 |
+
],
|
301 |
+
"created": Math.floor(Date.now() / 1000),
|
302 |
+
"model": models[`${model}`],
|
303 |
+
"object": "chat.completion.chunk"
|
304 |
+
})}\n\n`)
|
305 |
+
})
|
306 |
+
} else {
|
307 |
+
const text = new TextDecoder().decode(value)
|
308 |
+
const textContent = [...text.matchAll(/data:.*"}/g)]
|
309 |
+
|
310 |
+
textContent.forEach(item => {
|
311 |
+
if (!item[0]) {
|
312 |
+
return
|
313 |
+
}
|
314 |
+
|
315 |
+
const content = JSON.parse(item[0].replace("data: ", ''))
|
316 |
+
if (!content || !content.field_value || content.field_name == 'session_state.answer_is_finished' || content.field_name == 'content' || content.field_name == 'session_state' || content.delta || content.type == 'project_field') {
|
317 |
+
return
|
318 |
+
}
|
319 |
+
|
320 |
+
resBody = {
|
321 |
+
id: `chatcmpl-${messageId}`,
|
322 |
+
object: 'chat.completion',
|
323 |
+
created: Math.floor(Date.now() / 1000),
|
324 |
+
model,
|
325 |
+
choices: [
|
326 |
+
{
|
327 |
+
index: 0,
|
328 |
+
message: {
|
329 |
+
role: 'assistant',
|
330 |
+
content: content.field_value,
|
331 |
+
},
|
332 |
+
finish_reason: 'stop',
|
333 |
+
},
|
334 |
+
],
|
335 |
+
usage: {
|
336 |
+
prompt_tokens: 0,
|
337 |
+
completion_tokens: 0,
|
338 |
+
total_tokens: content.field_value.length,
|
339 |
+
},
|
340 |
+
}
|
341 |
+
})
|
342 |
+
}
|
343 |
+
}
|
344 |
+
|
345 |
+
if (stream == "false" || stream == false) {
|
346 |
+
ctx.body = resBody
|
347 |
+
} else {
|
348 |
+
ctx.res.end()
|
349 |
+
}
|
350 |
+
return
|
351 |
+
} catch (error) {
|
352 |
+
console.error('流式响应出错:', error)
|
353 |
+
ctx.res.end()
|
354 |
+
}
|
355 |
+
} catch (error) {
|
356 |
+
console.error('请求处理出错:', error)
|
357 |
+
ctx.status = 500
|
358 |
+
ctx.body = { error: '请求处理失败' }
|
359 |
+
}
|
360 |
+
})
|
361 |
+
|
362 |
+
// 获取models的v1路由
|
363 |
router.get('/v1/models', async (ctx) => {
|
364 |
ctx.body = {
|
365 |
object: "list",
|
|
|
372 |
}
|
373 |
})
|
374 |
|
375 |
+
// 获取models的hf/v1路由
|
376 |
+
router.get('/hf/v1/models', async (ctx) => {
|
377 |
+
ctx.body = {
|
378 |
+
object: "list",
|
379 |
+
data: Object.keys(models).map(model => ({
|
380 |
+
id: model,
|
381 |
+
object: "model",
|
382 |
+
created: 1706745938,
|
383 |
+
owned_by: "genspark"
|
384 |
+
}))
|
385 |
+
}
|
386 |
+
})
|
387 |
|
388 |
// 注册路由
|
389 |
app.use(router.routes()).use(router.allowedMethods())
|