Spaces:
Running
Running
import type { ChatRequest, ChatResponse } from "./api/openai/typing"; | |
import { | |
Message, | |
ModelConfig, | |
ModelType, | |
useAccessStore, | |
useAppConfig, | |
useChatStore, | |
} from "./store"; | |
import { showToast } from "./components/ui-lib"; | |
import { ACCESS_CODE_PREFIX } from "./constant"; | |
import { | |
ChatImageRequest, | |
ChatImagesResponse, | |
} from "./api/openai-image/typing"; | |
import { CreateImageRequestSizeEnum } from "openai"; | |
const TIME_OUT_MS = 60000; | |
const makeRequestParam = ( | |
messages: Message[], | |
options?: { | |
stream?: boolean; | |
overrideModel?: ModelType; | |
}, | |
): ChatRequest => { | |
let sendMessages = messages.map((v) => ({ | |
role: v.role, | |
content: v.content, | |
})); | |
const modelConfig = { | |
...useAppConfig.getState().modelConfig, | |
...useChatStore.getState().currentSession().mask.modelConfig, | |
}; | |
// override model config | |
if (options?.overrideModel) { | |
modelConfig.model = options.overrideModel; | |
} | |
return { | |
messages: sendMessages, | |
stream: options?.stream, | |
model: modelConfig.model, | |
temperature: modelConfig.temperature, | |
presence_penalty: modelConfig.presence_penalty, | |
}; | |
}; | |
const makeRevChatRequestParam = (messages: Message[]) => { | |
let sendMessages = messages.map((v) => ({ | |
role: v.role, | |
content: v.content, | |
isSensitive: false, | |
needCheck: true, | |
})); | |
return { | |
messages: JSON.stringify(sendMessages), | |
}; | |
}; | |
const makeImageRequestParam = (messages: Message[]): ChatImageRequest => { | |
return { | |
prompt: messages[messages.length - 1].content, | |
size: CreateImageRequestSizeEnum._1024x1024, | |
}; | |
}; | |
function getHeaders() { | |
const accessStore = useAccessStore.getState(); | |
let headers: Record<string, string> = {}; | |
const makeBearer = (token: string) => `Bearer ${token.trim()}`; | |
const validString = (x: string) => x && x.length > 0; | |
// use user's api key first | |
if (validString(accessStore.token)) { | |
headers.Authorization = makeBearer(accessStore.token); | |
} else if ( | |
accessStore.enabledAccessControl() && | |
validString(accessStore.accessCode) | |
) { | |
headers.Authorization = makeBearer( | |
ACCESS_CODE_PREFIX + accessStore.accessCode, | |
); | |
} | |
return headers; | |
} | |
export function requestOpenaiClient(path: string) { | |
const openaiUrl = useAccessStore.getState().openaiUrl; | |
return (body: any, method = "POST") => | |
fetch(openaiUrl + path, { | |
method, | |
body: body && JSON.stringify(body), | |
headers: getHeaders(), | |
}); | |
} | |
export async function requestChat( | |
messages: Message[], | |
options?: { | |
model?: ModelType; | |
}, | |
) { | |
const req: ChatRequest = makeRequestParam(messages, { | |
overrideModel: options?.model, | |
}); | |
const res = await requestOpenaiClient("v1/chat/completions")(req); | |
try { | |
const response = (await res.json()) as ChatResponse; | |
return response; | |
} catch (error) { | |
console.error("[Request Chat] ", error, res.body); | |
} | |
} | |
export async function requestUsage() { | |
const formatDate = (d: Date) => | |
`${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, "0")}-${d | |
.getDate() | |
.toString() | |
.padStart(2, "0")}`; | |
const ONE_DAY = 1 * 24 * 60 * 60 * 1000; | |
const now = new Date(); | |
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1); | |
const startDate = formatDate(startOfMonth); | |
const endDate = formatDate(new Date(Date.now() + ONE_DAY)); | |
const [used, subs] = await Promise.all([ | |
requestOpenaiClient( | |
`dashboard/billing/usage?start_date=${startDate}&end_date=${endDate}`, | |
)(null, "GET"), | |
requestOpenaiClient("dashboard/billing/subscription")(null, "GET"), | |
]); | |
const response = (await used.json()) as { | |
total_usage?: number; | |
error?: { | |
type: string; | |
message: string; | |
}; | |
}; | |
const total = (await subs.json()) as { | |
hard_limit_usd?: number; | |
}; | |
if (response.error && response.error.type) { | |
showToast(response.error.message); | |
return; | |
} | |
if (response.total_usage) { | |
response.total_usage = Math.round(response.total_usage) / 100; | |
} | |
if (total.hard_limit_usd) { | |
total.hard_limit_usd = Math.round(total.hard_limit_usd * 100) / 100; | |
} | |
return { | |
used: response.total_usage, | |
subscription: total.hard_limit_usd, | |
}; | |
} | |
export async function requestChatStream( | |
messages: Message[], | |
options?: { | |
modelConfig?: ModelConfig; | |
overrideModel?: ModelType; | |
onMessage: (message: string, done: boolean) => void; | |
onError: (error: Error, statusCode?: number) => void; | |
onController?: (controller: AbortController) => void; | |
}, | |
) { | |
const Bot = useAppConfig.getState().bot; | |
if (Bot == "OpenAI") { | |
const req = makeRequestParam(messages, { | |
stream: true, | |
overrideModel: options?.overrideModel, | |
}); | |
console.log("[Request] ", req); | |
const controller = new AbortController(); | |
const reqTimeoutId = setTimeout(() => controller.abort(), TIME_OUT_MS); | |
try { | |
const openaiUrl = useAccessStore.getState().openaiUrl; | |
const res = await fetch(openaiUrl + "v1/chat/completions", { | |
method: "POST", | |
headers: { | |
"Content-Type": "application/json", | |
...getHeaders(), | |
}, | |
body: JSON.stringify(req), | |
signal: controller.signal, | |
}); | |
clearTimeout(reqTimeoutId); | |
let responseText = ""; | |
const finish = () => { | |
options?.onMessage(responseText, true); | |
controller.abort(); | |
}; | |
if (res.ok) { | |
const reader = res.body?.getReader(); | |
const decoder = new TextDecoder(); | |
options?.onController?.(controller); | |
while (true) { | |
const resTimeoutId = setTimeout(() => finish(), TIME_OUT_MS); | |
const content = await reader?.read(); | |
clearTimeout(resTimeoutId); | |
if (!content || !content.value) { | |
break; | |
} | |
const text = decoder.decode(content.value, { stream: true }); | |
responseText += text; | |
const done = content.done; | |
options?.onMessage(responseText, false); | |
if (done) { | |
break; | |
} | |
} | |
finish(); | |
} else if (res.status === 401) { | |
console.error("Unauthorized"); | |
options?.onError(new Error("Unauthorized"), res.status); | |
} else { | |
console.error("Stream Error", res.body); | |
options?.onError(new Error("Stream Error"), res.status); | |
} | |
} catch (err) { | |
console.error("NetWork Error", err); | |
options?.onError(err as Error); | |
} | |
} else if (Bot == "OpenAI绘画") { | |
console.log("[Request] ", messages[messages.length - 1].content); | |
const req = makeImageRequestParam(messages); | |
const controller = new AbortController(); | |
const reqTimeoutId = setTimeout(() => controller.abort(), TIME_OUT_MS); | |
try { | |
const res = await fetch("/api/openai-image", { | |
method: "POST", | |
headers: { | |
"Content-Type": "application/json", | |
...getHeaders(), | |
}, | |
body: JSON.stringify(req), | |
}); | |
clearTimeout(reqTimeoutId); | |
const reg = /^['|"](.*)['|"]$/; | |
const response = (await res.json()) as ChatImagesResponse; | |
options?.onMessage( | |
"![image](" + | |
JSON.stringify(response.data[0].url).replace(reg, "$1") + | |
")", | |
true, | |
); | |
controller.abort(); | |
} catch (err) { | |
console.error("NetWork Error", err); | |
options?.onMessage("请换一个问题试试吧", true); | |
} | |
} else if (Bot == "必应") { | |
console.log("[Request] ", messages[messages.length - 1].content); | |
const controller = new AbortController(); | |
const reqTimeoutId = setTimeout(() => controller.abort(), TIME_OUT_MS); | |
try { | |
const res = await fetch("/api/newbing", { | |
method: "POST", | |
headers: { | |
"Content-Type": "application/json", | |
...getHeaders(), | |
}, | |
body: JSON.stringify(messages[messages.length - 1].content), | |
}); | |
clearTimeout(reqTimeoutId); | |
let message = await res.text(); | |
// let responseText = ""; | |
// for (let i = 1; i <= message.length; i++) { | |
// // handle time out, will stop if no response in 10 secs | |
// let messages = message.slice(0,i); | |
// console.log(message) | |
// responseText = messages; | |
// options?.onMessage(responseText, false); | |
// } | |
options?.onMessage(message, true); | |
controller.abort(); | |
} catch (err) { | |
console.error("NetWork Error", err); | |
options?.onMessage("请换一个问题试试吧", true); | |
} | |
} else if (Bot == "万卷") { | |
console.log("[Request] ", messages[messages.length - 1].content); | |
const controller = new AbortController(); | |
const reqTimeoutId = setTimeout(() => controller.abort(), TIME_OUT_MS); | |
try { | |
const res = await fetch("/api/wanjuan", { | |
method: "POST", | |
headers: { | |
"Content-Type": "application/json", | |
...getHeaders(), | |
}, | |
body: JSON.stringify(messages[messages.length - 1].content), | |
}); | |
clearTimeout(reqTimeoutId); | |
options?.onMessage(await res.text(), true); | |
controller.abort(); | |
} catch (err) { | |
console.error("NetWork Error", err); | |
options?.onMessage("请换一个问题试试吧", true); | |
} | |
} else if (Bot == "必应绘画") { | |
console.log("[Request] ", messages[messages.length - 1].content); | |
const req = makeImageRequestParam(messages); | |
const controller = new AbortController(); | |
const reqTimeoutId = setTimeout(() => controller.abort(), TIME_OUT_MS); | |
try { | |
const res = await fetch("/api/newbing-image", { | |
method: "POST", | |
headers: { | |
"Content-Type": "application/json", | |
...getHeaders(), | |
}, | |
body: JSON.stringify(req), | |
}); | |
clearTimeout(reqTimeoutId); | |
const reg = /^['|"](.*)['|"]$/; | |
const response = (await res.json()) as ChatImagesResponse; | |
options?.onMessage( | |
JSON.stringify(response.data[0].url).replace(reg, "$1"), | |
true, | |
); | |
controller.abort(); | |
} catch (err) { | |
console.error("NetWork Error", err); | |
options?.onMessage("请换一个问题试试吧", true); | |
} | |
} else { | |
const req = makeRevChatRequestParam(messages); | |
console.log("[Request] ", req); | |
const controller = new AbortController(); | |
const reqTimeoutId = setTimeout(() => controller.abort(), TIME_OUT_MS); | |
try { | |
const res = await fetch("/api/revchat", { | |
method: "POST", | |
headers: { | |
"Content-Type": "application/json", | |
...getHeaders(), | |
}, | |
body: JSON.stringify(req), | |
signal: controller.signal, | |
}); | |
clearTimeout(reqTimeoutId); | |
let responseText = ""; | |
const finish = () => { | |
options?.onMessage(responseText, true); | |
controller.abort(); | |
}; | |
if (res.ok) { | |
const reader = res.body?.getReader(); | |
const decoder = new TextDecoder(); | |
options?.onController?.(controller); | |
while (true) { | |
// handle time out, will stop if no response in 10 secs | |
const resTimeoutId = setTimeout(() => finish(), TIME_OUT_MS); | |
const content = await reader?.read(); | |
clearTimeout(resTimeoutId); | |
const text = decoder.decode(content?.value); | |
responseText += text; | |
const done = !content || content.done; | |
options?.onMessage(responseText, false); | |
if (done) { | |
break; | |
} | |
} | |
finish(); | |
} else if (res.status === 401) { | |
console.error("Unauthorized"); | |
options?.onError(new Error("Unauthorized"), res.status); | |
} else { | |
console.error("Stream Error", res.body); | |
options?.onError(new Error("Stream Error"), res.status); | |
} | |
} catch (err) { | |
console.error("NetWork Error", err); | |
options?.onError(err as Error); | |
} | |
} | |
} | |
export async function requestWithPrompt( | |
messages: Message[], | |
prompt: string, | |
options?: { | |
model?: ModelType; | |
}, | |
) { | |
messages = messages.concat([ | |
{ | |
role: "user", | |
content: prompt, | |
date: new Date().toLocaleString(), | |
}, | |
]); | |
const res = await requestChat(messages, options); | |
return res?.choices?.at(0)?.message?.content ?? ""; | |
} | |
// To store message streaming controller | |
export const ControllerPool = { | |
controllers: {} as Record<string, AbortController>, | |
addController( | |
sessionIndex: number, | |
messageId: number, | |
controller: AbortController, | |
) { | |
const key = this.key(sessionIndex, messageId); | |
this.controllers[key] = controller; | |
return key; | |
}, | |
stop(sessionIndex: number, messageId: number) { | |
const key = this.key(sessionIndex, messageId); | |
const controller = this.controllers[key]; | |
controller?.abort(); | |
}, | |
stopAll() { | |
Object.values(this.controllers).forEach((v) => v.abort()); | |
}, | |
hasPending() { | |
return Object.values(this.controllers).length > 0; | |
}, | |
remove(sessionIndex: number, messageId: number) { | |
const key = this.key(sessionIndex, messageId); | |
delete this.controllers[key]; | |
}, | |
key(sessionIndex: number, messageIndex: number) { | |
return `${sessionIndex},${messageIndex}`; | |
}, | |
}; | |