|
| 1 | +// custom api version |
| 2 | + |
| 3 | +// There is a lot of duplicated code here, but it is very easy to refactor. |
| 4 | +// The current state is mainly convenient for making targeted changes at any time, |
| 5 | +// and it has not yet had a negative impact on maintenance. |
| 6 | +// If necessary, I will refactor. |
| 7 | + |
| 8 | +import { getUserConfig, maxResponseTokenLength, Models } from '../../config/index.mjs' |
| 9 | +import { fetchSSE } from '../../utils/fetch-sse' |
| 10 | +import { getConversationPairs } from '../../utils/get-conversation-pairs' |
| 11 | +import { isEmpty } from 'lodash-es' |
| 12 | + |
| 13 | +const getCustomApiPromptBase = async () => { |
| 14 | + return `I am a helpful, creative, clever, and very friendly assistant. I am familiar with various languages in the world.` |
| 15 | +} |
| 16 | + |
| 17 | +/** |
| 18 | + * @param {Browser.Runtime.Port} port |
| 19 | + * @param {string} question |
| 20 | + * @param {Session} session |
| 21 | + * @param {string} apiKey |
| 22 | + * @param {string} modelName |
| 23 | + */ |
| 24 | +export async function generateAnswersWithCustomApi(port, question, session, apiKey, modelName) { |
| 25 | + const controller = new AbortController() |
| 26 | + const stopListener = (msg) => { |
| 27 | + if (msg.stop) { |
| 28 | + console.debug('stop generating') |
| 29 | + port.postMessage({ done: true }) |
| 30 | + controller.abort() |
| 31 | + port.onMessage.removeListener(stopListener) |
| 32 | + } |
| 33 | + } |
| 34 | + port.onMessage.addListener(stopListener) |
| 35 | + port.onDisconnect.addListener(() => { |
| 36 | + console.debug('port disconnected') |
| 37 | + controller.abort() |
| 38 | + }) |
| 39 | + |
| 40 | + const prompt = getConversationPairs(session.conversationRecords, true) |
| 41 | + prompt.unshift({ role: 'system', content: await getCustomApiPromptBase() }) |
| 42 | + prompt.push({ role: 'user', content: question }) |
| 43 | + const apiUrl = (await getUserConfig()).customModelApiUrl |
| 44 | + |
| 45 | + let answer = '' |
| 46 | + await fetchSSE(apiUrl, { |
| 47 | + method: 'POST', |
| 48 | + signal: controller.signal, |
| 49 | + headers: { |
| 50 | + 'Content-Type': 'application/json', |
| 51 | + Authorization: `Bearer ${apiKey}`, |
| 52 | + }, |
| 53 | + body: JSON.stringify({ |
| 54 | + messages: prompt, |
| 55 | + model: Models[modelName].value, |
| 56 | + stream: true, |
| 57 | + max_tokens: maxResponseTokenLength, |
| 58 | + }), |
| 59 | + onMessage(message) { |
| 60 | + console.debug('sse message', message) |
| 61 | + if (message === '[DONE]') { |
| 62 | + session.conversationRecords.push({ question: question, answer: answer }) |
| 63 | + console.debug('conversation history', { content: session.conversationRecords }) |
| 64 | + port.postMessage({ answer: null, done: true, session: session }) |
| 65 | + return |
| 66 | + } |
| 67 | + let data |
| 68 | + try { |
| 69 | + data = JSON.parse(message) |
| 70 | + } catch (error) { |
| 71 | + console.debug('json error', error) |
| 72 | + return |
| 73 | + } |
| 74 | + if (data.response) answer = data.response |
| 75 | + port.postMessage({ answer: answer, done: false, session: null }) |
| 76 | + }, |
| 77 | + async onStart() {}, |
| 78 | + async onEnd() { |
| 79 | + port.onMessage.removeListener(stopListener) |
| 80 | + }, |
| 81 | + async onError(resp) { |
| 82 | + if (resp instanceof Error) throw resp |
| 83 | + port.onMessage.removeListener(stopListener) |
| 84 | + if (resp.status === 403) { |
| 85 | + throw new Error('CLOUDFLARE') |
| 86 | + } |
| 87 | + const error = await resp.json().catch(() => ({})) |
| 88 | + throw new Error(!isEmpty(error) ? JSON.stringify(error) : `${resp.status} ${resp.statusText}`) |
| 89 | + }, |
| 90 | + }) |
| 91 | +} |
0 commit comments