2024-01-18 15:46:18 +08:00
|
|
|
|
import { Transforms } from "slate";
|
|
|
|
|
import { Editor } from "slate";
|
2024-02-18 12:10:53 +08:00
|
|
|
|
import Quill from "quill";
|
|
|
|
|
|
2024-01-18 15:46:18 +08:00
|
|
|
|
import { extractText } from "@/utils/others/slateutils";
|
|
|
|
|
import {
|
|
|
|
|
updateBracketNumbersInDeltaKeepSelection,
|
|
|
|
|
convertToSuperscript,
|
2024-02-18 15:48:18 +08:00
|
|
|
|
deleteSameBracketNumber,
|
2024-01-18 15:46:18 +08:00
|
|
|
|
} from "@/utils/others/quillutils";
|
2024-01-27 23:25:07 +08:00
|
|
|
|
//redux不能在普通函数使用
|
|
|
|
|
|
2024-01-18 15:46:18 +08:00
|
|
|
|
interface ChatData {
|
|
|
|
|
choices: Array<{
|
|
|
|
|
delta: {
|
|
|
|
|
content?: string;
|
|
|
|
|
};
|
|
|
|
|
}>;
|
|
|
|
|
}
|
2024-01-21 23:08:25 +08:00
|
|
|
|
function isValidApiKey(apiKey: string) {
|
|
|
|
|
return apiKey && apiKey.trim() !== "";
|
|
|
|
|
}
|
2024-01-18 15:46:18 +08:00
|
|
|
|
|
|
|
|
|
const sendMessageToOpenAI = async (
|
2024-01-20 13:43:31 +08:00
|
|
|
|
content: string,
|
2024-02-22 12:35:08 +08:00
|
|
|
|
editor: Quill | null,
|
2024-02-18 12:10:53 +08:00
|
|
|
|
selectedModel: string,
|
2024-01-21 23:08:25 +08:00
|
|
|
|
apiKey: string,
|
2024-01-27 23:25:07 +08:00
|
|
|
|
upsreamUrl: string,
|
2024-02-18 15:48:18 +08:00
|
|
|
|
prompt: string,
|
2024-02-22 12:35:08 +08:00
|
|
|
|
cursorPosition: number | null,
|
|
|
|
|
useEditorFlag = true // 新增的标志,用于决定操作
|
2024-01-18 15:46:18 +08:00
|
|
|
|
) => {
|
2024-01-20 13:43:31 +08:00
|
|
|
|
//识别应该使用的模型
|
2024-02-04 12:55:09 +08:00
|
|
|
|
let model = selectedModel;
|
2024-01-29 20:37:40 +08:00
|
|
|
|
console.log("upstreamUrl", upsreamUrl);
|
2024-01-18 15:46:18 +08:00
|
|
|
|
// 设置API请求参数
|
|
|
|
|
const requestOptions = {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: {
|
|
|
|
|
"Content-Type": "application/json",
|
2024-01-28 14:58:46 +08:00
|
|
|
|
// "Upstream-Url": upsreamUrl,
|
2024-01-21 23:08:25 +08:00
|
|
|
|
Authorization:
|
|
|
|
|
"Bearer " +
|
|
|
|
|
(isValidApiKey(apiKey)
|
|
|
|
|
? apiKey
|
|
|
|
|
: process.env.NEXT_PUBLIC_OPENAI_API_KEY),
|
2024-01-18 15:46:18 +08:00
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify({
|
2024-01-20 13:43:31 +08:00
|
|
|
|
model: model,
|
2024-02-22 12:35:08 +08:00
|
|
|
|
stream: useEditorFlag, // 根据标志确定是否使用streaming
|
2024-01-18 15:46:18 +08:00
|
|
|
|
messages: [
|
|
|
|
|
{
|
|
|
|
|
role: "system",
|
2024-01-21 23:08:25 +08:00
|
|
|
|
content:
|
|
|
|
|
prompt ||
|
|
|
|
|
`作为论文写作助手,您的主要任务是根据用户提供的研究主题和上下文,以及相关的研究论文,来撰写和完善学术论文。在撰写过程中,请注意以下要点:
|
2024-01-18 15:46:18 +08:00
|
|
|
|
1.学术格式:请采用标准的学术论文格式进行写作,包括清晰的段落结构、逻辑严谨的论点展开,以及恰当的专业术语使用。
|
2024-01-28 19:01:11 +08:00
|
|
|
|
2.文献引用:只引用与主题紧密相关的论文。在引用文献时,文末应使用方括号内的数字来标注引用来源,如 [1]。。请确保每个引用在文章中都有其对应的编号,*无需在文章末尾提供参考文献列表*。*每个文献对应的序号只应该出现一次,比如说引用了第一篇文献文中就只能出现一次[1]*。
|
2024-01-18 15:46:18 +08:00
|
|
|
|
3.忽略无关文献:对于与主题无关的论文,请不要包含在您的写作中。只关注对理解和阐述主题有实质性帮助的资料。
|
|
|
|
|
4.来源明确:在文章中,清楚地指出每个引用的具体来源。引用的信息应准确无误,确保读者能够追溯到原始文献。
|
2024-02-18 12:10:53 +08:00
|
|
|
|
5.使用用户所说的语言完成回答,不超过五百字
|
2024-01-19 15:36:41 +08:00
|
|
|
|
6.只能对给出的文献进行引用,坚决不能虚构文献。
|
|
|
|
|
返回格式举例:
|
2024-01-18 23:22:23 +08:00
|
|
|
|
在某个方面,某论文实现了以下突破...[1],在另一篇论文中,研究了...[2]`,
|
2024-01-18 15:46:18 +08:00
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
role: "user",
|
|
|
|
|
content: content,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
}),
|
|
|
|
|
};
|
|
|
|
|
console.log("请求的内容\n", content);
|
|
|
|
|
// 发送API请求
|
|
|
|
|
|
2024-01-23 10:54:12 +08:00
|
|
|
|
let response;
|
|
|
|
|
|
2024-01-18 15:46:18 +08:00
|
|
|
|
try {
|
2024-01-28 14:58:46 +08:00
|
|
|
|
response = await fetch(
|
2024-01-28 15:14:53 +08:00
|
|
|
|
(upsreamUrl || process.env.NEXT_PUBLIC_AI_URL) + "/v1/chat/completions",
|
2024-01-28 14:58:46 +08:00
|
|
|
|
requestOptions
|
|
|
|
|
);
|
2024-02-04 12:55:09 +08:00
|
|
|
|
if (!response.ok || !response.body) {
|
2024-02-09 13:45:26 +08:00
|
|
|
|
throw new Error("");
|
2024-01-23 10:54:12 +08:00
|
|
|
|
}
|
2024-02-22 12:35:08 +08:00
|
|
|
|
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; // 或根据需要处理并返回数据
|
|
|
|
|
}
|
2024-01-18 15:46:18 +08:00
|
|
|
|
} catch (error) {
|
2024-02-13 13:15:38 +08:00
|
|
|
|
console.error("Error:", error);
|
2024-01-23 10:54:12 +08:00
|
|
|
|
// 如果有响应,返回响应的原始内容
|
|
|
|
|
if (response) {
|
|
|
|
|
const rawResponse = await response.text();
|
2024-02-09 13:45:26 +08:00
|
|
|
|
throw new Error(`请求发生错误: ${error}, Response: ${rawResponse}`);
|
2024-01-23 10:54:12 +08:00
|
|
|
|
}
|
|
|
|
|
// 如果没有响应,只抛出错误
|
2024-01-21 23:08:25 +08:00
|
|
|
|
throw error;
|
2024-01-18 15:46:18 +08:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-02-20 10:52:33 +08:00
|
|
|
|
const getAI = async (
|
2024-01-21 23:08:25 +08:00
|
|
|
|
userMessage: string,
|
2024-02-20 10:52:33 +08:00
|
|
|
|
systemPrompt: string,
|
|
|
|
|
apiKey: string,
|
|
|
|
|
upsreamUrl: string,
|
|
|
|
|
selectedModel: string
|
2024-01-21 23:08:25 +08:00
|
|
|
|
) => {
|
2024-01-18 23:22:23 +08:00
|
|
|
|
// 设置API请求参数
|
|
|
|
|
const requestOptions = {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: {
|
|
|
|
|
"Content-Type": "application/json",
|
2024-01-21 23:08:25 +08:00
|
|
|
|
Authorization:
|
|
|
|
|
"Bearer " +
|
|
|
|
|
(isValidApiKey(apiKey)
|
|
|
|
|
? apiKey
|
|
|
|
|
: process.env.NEXT_PUBLIC_OPENAI_API_KEY),
|
2024-01-18 23:22:23 +08:00
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify({
|
2024-02-20 10:52:33 +08:00
|
|
|
|
model: selectedModel || "gpt-3.5-turbo",
|
2024-01-18 23:22:23 +08:00
|
|
|
|
stream: false,
|
|
|
|
|
messages: [
|
|
|
|
|
{
|
|
|
|
|
role: "system",
|
2024-02-20 10:52:33 +08:00
|
|
|
|
content: systemPrompt,
|
2024-01-18 23:22:23 +08:00
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
role: "user",
|
|
|
|
|
content: userMessage,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
}),
|
|
|
|
|
};
|
2024-01-29 13:35:24 +08:00
|
|
|
|
const response = await fetch(
|
2024-02-20 10:52:33 +08:00
|
|
|
|
(upsreamUrl || process.env.NEXT_PUBLIC_AI_URL) + "/v1/chat/completions",
|
2024-01-29 13:35:24 +08:00
|
|
|
|
requestOptions
|
|
|
|
|
);
|
2024-01-19 15:36:41 +08:00
|
|
|
|
const data = await response.json();
|
2024-01-21 23:08:25 +08:00
|
|
|
|
const topic = data.choices[0].message.content;
|
|
|
|
|
return topic; // 获取并返回回复
|
2024-01-18 23:22:23 +08:00
|
|
|
|
};
|
|
|
|
|
|
2024-01-28 11:45:15 +08:00
|
|
|
|
async function processResult(reader, decoder, editor) {
|
|
|
|
|
let buffer = "";
|
2024-01-18 15:46:18 +08:00
|
|
|
|
while (true) {
|
|
|
|
|
const { done, value } = await reader.read();
|
|
|
|
|
if (done) {
|
|
|
|
|
console.log("Stream finished");
|
|
|
|
|
break;
|
|
|
|
|
}
|
2024-01-28 11:45:15 +08:00
|
|
|
|
buffer += decoder.decode(value, { stream: true });
|
2024-01-28 19:01:11 +08:00
|
|
|
|
// console.log("buffer", buffer);
|
2024-01-28 11:45:15 +08:00
|
|
|
|
// 处理缓冲区中的所有完整的 JSON 对象
|
|
|
|
|
let boundary;
|
2024-01-28 14:58:46 +08:00
|
|
|
|
try {
|
|
|
|
|
while ((boundary = buffer.indexOf("}\n")) !== -1) {
|
|
|
|
|
// 找到一个完整的 JSON 对象的边界
|
|
|
|
|
let jsonStr = buffer.substring(0, boundary + 1);
|
|
|
|
|
buffer = buffer.substring(boundary + 2);
|
2024-01-28 19:01:11 +08:00
|
|
|
|
// console.log("jsonStr", jsonStr);
|
2024-01-18 15:46:18 +08:00
|
|
|
|
|
2024-01-28 14:58:46 +08:00
|
|
|
|
// 尝试解析 JSON 对象
|
|
|
|
|
try {
|
|
|
|
|
// 如果 jsonStr 以 "data: " 开头,就移除这个前缀
|
|
|
|
|
// 移除字符串首尾的空白字符
|
|
|
|
|
jsonStr = jsonStr.trim();
|
2024-02-09 11:45:14 +08:00
|
|
|
|
// jsonStr = jsonStr.substring(6);
|
|
|
|
|
jsonStr = jsonStr.replace("data:", "");
|
2024-01-28 14:58:46 +08:00
|
|
|
|
let dataObject = JSON.parse(jsonStr);
|
2024-01-29 13:35:24 +08:00
|
|
|
|
// console.log("dataObject", dataObject);
|
2024-01-28 14:58:46 +08:00
|
|
|
|
// 处理 dataObject 中的 content
|
|
|
|
|
if (dataObject.choices && dataObject.choices.length > 0) {
|
|
|
|
|
let content =
|
|
|
|
|
dataObject.choices[0].message?.content ||
|
|
|
|
|
dataObject.choices[0].delta?.content;
|
|
|
|
|
if (content) {
|
|
|
|
|
// 在当前光标位置插入文本
|
2024-02-13 13:15:38 +08:00
|
|
|
|
// editor.focus();
|
|
|
|
|
editor.insertText(editor.getSelection(true).index, content);
|
2024-01-29 13:35:24 +08:00
|
|
|
|
// console.log("成功插入:", content);
|
2024-01-28 14:58:46 +08:00
|
|
|
|
}
|
2024-01-18 15:46:18 +08:00
|
|
|
|
}
|
2024-01-28 14:58:46 +08:00
|
|
|
|
} catch (error) {
|
2024-02-09 13:45:26 +08:00
|
|
|
|
throw new Error(`
|
|
|
|
|
there is a error in parse JSON object: ${jsonStr},
|
|
|
|
|
error reason: ${error}`);
|
2024-01-18 15:46:18 +08:00
|
|
|
|
}
|
2024-01-28 11:45:15 +08:00
|
|
|
|
}
|
2024-01-28 14:58:46 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
break;
|
2024-01-18 15:46:18 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-20 10:52:33 +08:00
|
|
|
|
export { getAI, sendMessageToOpenAI };
|