openAiChat.ts 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import { ChatbotBackend, VisionBackend } from "@/types/backend";
  2. import { Message } from "./messages";
  3. async function getResponseStream(
  4. messages: Message[],
  5. url: string,
  6. model: string,
  7. apiKey: string,
  8. ) {
  9. const headers: Record<string, string> = {
  10. "Content-Type": "application/json",
  11. "Authorization": `Bearer ${apiKey}`,
  12. "HTTP-Referer": "https://amica.arbius.ai",
  13. "X-Title": "Amica",
  14. };
  15. const res = await fetch(`${url}/v1/chat/completions`, {
  16. headers: headers,
  17. method: "POST",
  18. body: JSON.stringify({
  19. model,
  20. messages,
  21. stream: true,
  22. max_tokens: 200,
  23. }),
  24. });
  25. const reader = res.body?.getReader();
  26. if (res.status !== 200 || ! reader) {
  27. if (res.status === 401) {
  28. throw new Error('Invalid OpenAI authentication');
  29. }
  30. if (res.status === 402) {
  31. throw new Error('Payment required');
  32. }
  33. throw new Error(`OpenAI chat error (${res.status})`);
  34. }
  35. const stream = new ReadableStream({
  36. async start(controller: ReadableStreamDefaultController) {
  37. const decoder = new TextDecoder("utf-8");
  38. try {
  39. // sometimes the response is chunked, so we need to combine the chunks
  40. let combined = "";
  41. while (true) {
  42. const { done, value } = await reader.read();
  43. if (done) break;
  44. const data = decoder.decode(value);
  45. const chunks = data
  46. .split("data:")
  47. .filter((val) => !!val && val.trim() !== "[DONE]");
  48. for (const chunk of chunks) {
  49. // skip comments
  50. if (chunk.length > 0 && chunk[0] === ":") {
  51. continue;
  52. }
  53. combined += chunk;
  54. try {
  55. const json = JSON.parse(combined);
  56. const messagePiece = json.choices[0].delta.content;
  57. combined = "";
  58. if (!!messagePiece) {
  59. controller.enqueue(messagePiece);
  60. }
  61. } catch (error) {
  62. console.error(error);
  63. }
  64. }
  65. }
  66. } catch (error) {
  67. console.error(error);
  68. controller.error(error);
  69. } finally {
  70. reader.releaseLock();
  71. controller.close();
  72. }
  73. },
  74. async cancel() {
  75. await reader?.cancel();
  76. reader.releaseLock();
  77. }
  78. });
  79. return stream;
  80. }
  81. export async function getOpenAiChatResponseStream(config: ChatbotBackend["openai"],messages: Message[]) {
  82. const apiKey = config?.openai_apikey!;
  83. const url = config?.openai_url!;
  84. const model = config?.openai_model!;
  85. return getResponseStream(messages, url, model, apiKey);
  86. }
  87. export async function getOpenAiVisionChatResponse(config: VisionBackend["vision_openai"],messages: Message[],) {
  88. const apiKey = config?.vision_openai_apikey!;
  89. const url = config?.vision_openai_url!;
  90. const model = config?.vision_openai_model!;
  91. const stream = await getResponseStream(messages, url, model, apiKey);
  92. const sreader = await stream.getReader();
  93. let combined = "";
  94. while (true) {
  95. const { done, value } = await sreader.read();
  96. if (done) break;
  97. combined += value;
  98. }
  99. return combined;
  100. }