paper-ai-release-24-07-21/components/chatAI.tsx
2024-03-06 21:41:44 +08:00

183 lines
6.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Quill from "quill";
import {
updateBracketNumbersInDeltaKeepSelection,
convertToSuperscript,
deleteSameBracketNumber,
} from "@/utils/others/quillutils";
//redux不能在普通函数使用
interface ChatData {
choices: Array<{
delta: {
content?: string;
};
}>;
}
function isValidApiKey(apiKey: string) {
return apiKey && apiKey.trim() !== "";
}
const sendMessageToOpenAI = async (
content: string,
editor: Quill | null,
selectedModel: string,
apiKey: string,
upsreamUrl: string,
prompt: string,
cursorPosition: number | null,
useEditorFlag = true, // 新增的标志,用于决定操作
signal: AbortSignal
) => {
//识别应该使用的模型
let model = selectedModel;
console.log("upstreamUrl", upsreamUrl);
// 设置API请求参数
const requestOptions = {
method: "POST",
signal: signal,
headers: {
"Content-Type": "application/json",
// "Upstream-Url": upsreamUrl,
Authorization:
"Bearer " +
(isValidApiKey(apiKey)
? apiKey
: process.env.NEXT_PUBLIC_OPENAI_API_KEY),
},
body: JSON.stringify({
model: model,
stream: useEditorFlag, // 根据标志确定是否使用streaming
messages: [
{
role: "system",
content:
prompt ||
`作为论文写作助手,您的主要任务是根据用户提供的研究主题和上下文,以及相关的研究论文,来撰写和完善学术论文。在撰写过程中,请注意以下要点:
1.学术格式:请采用标准的学术论文格式进行写作,包括清晰的段落结构、逻辑严谨的论点展开,以及恰当的专业术语使用。
2.文献引用:只引用与主题紧密相关的论文。在引用文献时,文末应使用方括号内的数字来标注引用来源,如 [1]。。请确保每个引用在文章中都有其对应的编号,*无需在文章末尾提供参考文献列表*。*每个文献对应的序号只应该出现一次,比如说引用了第一篇文献文中就只能出现一次[1]*。
3.忽略无关文献:对于与主题无关的论文,请不要包含在您的写作中。只关注对理解和阐述主题有实质性帮助的资料。
4.来源明确:在文章中,清楚地指出每个引用的具体来源。引用的信息应准确无误,确保读者能够追溯到原始文献。
5.使用用户所说的语言完成回答,不超过五百字
6.只能对给出的文献进行引用,坚决不能虚构文献。
返回格式举例:
在某个方面,某论文实现了以下突破...[1],在另一篇论文中,研究了...[2]`,
},
{
role: "user",
content: content,
},
],
}),
};
console.log("请求的内容\n", content);
// 发送API请求
let response;
let responseClone = null; // 用于保存响应内容的变量
try {
response = await fetch(
(upsreamUrl || process.env.NEXT_PUBLIC_AI_URL) + "/v1/chat/completions",
requestOptions
);
// 检查响应状态码是否为429
if (response.status === 429) {
// 可以在这里处理429错误例如通过UI通知用户
throw new Error("请求过于频繁,请稍后再试。");
} else if (!response.ok) {
// 处理其他类型的HTTP错误
throw new Error(`HTTP错误状态码${response.status}`);
}
// 克隆响应以备后用
responseClone = response.clone();
if (useEditorFlag && editor && cursorPosition !== null) {
const reader = response.body!.getReader();
const decoder = new TextDecoder();
//开始前先进行换行
// editor.focus();
editor.insertText(editor.getSelection(true).index, "\n");
await processResult(reader, decoder, editor);
//搜索是否有相同的括号编号,如果有相同的则删除到只剩一个
convertToSuperscript(editor);
deleteSameBracketNumber(editor, cursorPosition);
updateBracketNumbersInDeltaKeepSelection(editor);
} else {
// 直接返回结果的逻辑
const data = await response.json();
const content = data.choices[0].message.content;
return content; // 或根据需要处理并返回数据
}
} catch (error: any) {
if (error.name === "AbortError") {
console.log("Fetch operation was aborted");
//这里不用产生报错因为是手动停止
return;
}
console.error("Error:", error);
// 根据是否成功读取响应体来抛出错误
if (responseClone) {
const textResponse = await responseClone.text(); // 从克隆的响应中读取数据
throw new Error(
`请求发生错误: ${error.message}, Response: ${textResponse}`
);
} else {
throw new Error(`请求发生错误: ${error.message}`);
}
}
};
async function processResult(reader, decoder, editor) {
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) {
console.log("Stream finished");
break;
}
buffer += decoder.decode(value, { stream: true });
// console.log("buffer", buffer);
// 处理缓冲区中的所有完整的 JSON 对象
let boundary;
try {
while ((boundary = buffer.indexOf("}\n")) !== -1) {
// 找到一个完整的 JSON 对象的边界
let jsonStr = buffer.substring(0, boundary + 1);
buffer = buffer.substring(boundary + 2);
// console.log("jsonStr", jsonStr);
// 尝试解析 JSON 对象
try {
// 如果 jsonStr 以 "data: " 开头,就移除这个前缀
// 移除字符串首尾的空白字符
jsonStr = jsonStr.trim();
// jsonStr = jsonStr.substring(6);
jsonStr = jsonStr.replace("data:", "");
let dataObject = JSON.parse(jsonStr);
// console.log("dataObject", dataObject);
// 处理 dataObject 中的 content
if (dataObject.choices && dataObject.choices.length > 0) {
let content =
dataObject.choices[0].delta?.content ??
dataObject.choices[0].message?.content;
if (content) {
// 在当前光标位置插入文本
// editor.focus();
editor.insertText(editor.getSelection(true).index, content);
// console.log("成功插入:", content);
}
}
} catch (error) {
throw new Error(`
there is a error in parse JSON object: ${jsonStr},
error reason: ${error}`);
}
}
} catch (error) {
break;
}
}
}
export { sendMessageToOpenAI };