feat: 可以进行多轮文献查询

This commit is contained in:
liuweiqing 2024-02-18 12:10:53 +08:00
parent edfb1c5475
commit 9d799f1736
10 changed files with 219 additions and 175 deletions

View File

@ -6,3 +6,4 @@ NEXT_PUBLIC_PAPER_URL=/api/paper
NEXT_PUBLIC_SEMANTIC_API_KEY=hEQvK6ARe84dzDPcMnpzX4n9jfoqztkMfaftPWnb NEXT_PUBLIC_SEMANTIC_API_KEY=hEQvK6ARe84dzDPcMnpzX4n9jfoqztkMfaftPWnb
NEXT_PUBLIC_PUBMED_API_KEY=057616e7ce6c722f2ae8679e38a8be9b1a09 NEXT_PUBLIC_PUBMED_API_KEY=057616e7ce6c722f2ae8679e38a8be9b1a09
VERCEL_URL=https://www.paperai.life VERCEL_URL=https://www.paperai.life
NODE_ENV=production

View File

@ -19,7 +19,7 @@ const initialState: APIState = {
2.使 [1]***[1]* 2.使 [1]***[1]*
3. 3.
4. 4.
5.使, 5.使
6. 6.
...[1],...[2]`, ...[1],...[2]`,

View File

@ -38,12 +38,13 @@ interface Author {
async function getArxivPapers( async function getArxivPapers(
query: string, query: string,
maxResults = 5, maxResults = 5,
offset = -1,
sortBy = "submittedDate", sortBy = "submittedDate",
sortOrder = "descending" sortOrder = "descending"
) { ) {
const maxOffset = 30 - maxResults; // 假设总记录数为 100 const maxOffset = 20 - maxResults; // 假设总记录数为 20
const start = getRandomOffset(maxOffset); if (offset === -1) offset = getRandomOffset(maxOffset);
const url = `https://export.arxiv.org/api/query?search_query=${query}&start=${start}&max_results=${maxResults}&sortBy=${sortBy}&sortOrder=${sortOrder}`; const url = `https://export.arxiv.org/api/query?search_query=${query}&start=${offset}&max_results=${maxResults}&sortBy=${sortBy}&sortOrder=${sortOrder}`;
try { try {
const response = await axios.get(url); const response = await axios.get(url);

View File

@ -10,14 +10,20 @@ type PubMedID = string;
// 定义idList为PubMedID数组 // 定义idList为PubMedID数组
type IDList = PubMedID[]; type IDList = PubMedID[];
async function getPubMedPapers(query: string, year: number, limit = 2) { async function getPubMedPapers(
query: string,
year: number,
offset = -1,
limit = 2
) {
try { try {
const baseURL = const baseURL =
"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi"; "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi";
const db = "pubmed"; // 设定搜索的数据库为PubMed const db = "pubmed"; // 设定搜索的数据库为PubMed
const retMax = limit; // 检索的最大记录数 const retMax = limit; // 检索的最大记录数
const retStart = getRandomOffset(20 - limit); // 假设每页最多30条根据需要随机偏移 const maxOffset = 20 - limit; // 假设总记录数为 20
const url = `${baseURL}?db=${db}&term=${query}[Title/Abstract]+AND+2018:3000[Date - Publication]&retMax=${retMax}&retStart=${retStart}&api_key=${process.env.NEXT_PUBLIC_PUBMED_API_KEY}`; if (offset === -1) offset = getRandomOffset(maxOffset);
const url = `${baseURL}?db=${db}&term=${query}[Title/Abstract]+AND+2018:3000[Date - Publication]&retMax=${retMax}&retStart=${offset}&api_key=${process.env.NEXT_PUBLIC_PUBMED_API_KEY}`;
const response = await axios.get(url, { responseType: "text" }); const response = await axios.get(url, { responseType: "text" });
console.log(response.data); console.log(response.data);
// 解析XML数据 // 解析XML数据
@ -155,9 +161,14 @@ async function getPubMedPaperDetails(idList: IDList) {
} }
// 示例:使用这些函数 // 示例:使用这些函数
async function fetchPubMedData(query: string, year: number, limit: number) { async function fetchPubMedData(
query: string,
year: number,
limit: number,
offset: number
) {
try { try {
const idList = await getPubMedPapers(query, year, limit); const idList = await getPubMedPapers(query, year, offset, limit);
if (idList && idList.length > 0) { if (idList && idList.length > 0) {
const paperDetails = await getPubMedPaperDetails(idList); const paperDetails = await getPubMedPaperDetails(idList);
console.log("fetchPubMedData", paperDetails); // 处理或显示文章详情 console.log("fetchPubMedData", paperDetails); // 处理或显示文章详情

View File

@ -15,10 +15,15 @@ interface Paper {
url: string; url: string;
} }
async function getSemanticPapers(query: string, year: string, limit = 2) { async function getSemanticPapers(
query: string,
year: string,
offset = -1,
limit = 2
) {
try { try {
const maxOffset = 20 - limit; // 假设总记录数为 20 const maxOffset = 20 - limit; // 假设总记录数为 20
const offset = getRandomOffset(maxOffset); if (offset === -1) offset = getRandomOffset(maxOffset);
const url = `https://api.semanticscholar.org/graph/v1/paper/search`; const url = `https://api.semanticscholar.org/graph/v1/paper/search`;
const response = await axios.get(url, { const response = await axios.get(url, {
headers: { headers: {

View File

@ -101,6 +101,11 @@ const QEditor = ({ lng }) => {
"gpt语言模型", "gpt语言模型",
"gpt-4" "gpt-4"
); // 默认选项 ); // 默认选项
const [generatedPaperNumber, setGeneratedPaperNumber] = useLocalStorage(
"生成次数",
1
); // 初始值设为1
//redux //redux
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const references = useAppSelector((state) => state.auth.referencesRedux); const references = useAppSelector((state) => state.auth.referencesRedux);
@ -231,16 +236,20 @@ const QEditor = ({ lng }) => {
const handleInputChange = (event: any) => { const handleInputChange = (event: any) => {
setUserInput(event.target.value); setUserInput(event.target.value);
}; };
// 处理输入generatedPaperNumber变化的函数
const handleGeneratedPaperNumberChange = (event: any) => {
const newValue = parseInt(event.target.value, 10);
setGeneratedPaperNumber(newValue);
};
// 处理AI写作 // 处理AI写作
const handleAIWrite = async () => { const handleAIWrite = async () => {
quill.setSelection(cursorPosition, 0); // 将光标移动到原来的位置 quill!.setSelection(cursorPosition!, 0); // 将光标移动到原来的位置
const prompt = "请帮助用户完成论文写作,使用用户所说的语言完成"; const prompt = "请帮助用户完成论文写作,使用用户所说的语言完成";
await sendMessageToOpenAI( await sendMessageToOpenAI(
userInput, userInput,
quill, quill!,
selectedModel, selectedModel!,
apiKey, apiKey,
upsreamUrl, upsreamUrl,
prompt prompt
@ -248,20 +257,22 @@ const QEditor = ({ lng }) => {
// 清空input内容 // 清空input内容
setUserInput(""); setUserInput("");
// 重新获取更新后的内容并更新 Redux store // 重新获取更新后的内容并更新 Redux store
const updatedContent = quill.root.innerHTML; const updatedContent = quill!.root.innerHTML;
dispatch(setEditorContent(updatedContent)); dispatch(setEditorContent(updatedContent));
}; };
// 处理paper2AI // 处理paper2AI
async function paper2AI(topic: string) { async function paper2AI(topic: string) {
quill.setSelection(cursorPosition, 0); // 将光标移动到原来的位置 quill!.setSelection(cursorPosition!, 0); // 将光标移动到原来的位置
let offset = -1;
if (generatedPaperNumber) offset = 0;
for (let i = 0; i < generatedPaperNumber!; i++) {
try { try {
if (!topic) { if (!topic) {
//使用ai提取当前要请求的论文主题 //使用ai提取当前要请求的论文主题
const prompt = const prompt =
"As a topic extraction assistant, you can help me extract the current discussion of the paper topic, I will enter the content of the paper, you extract the paper topic , no more than two, Hyphenated query terms yield no matches (replace it with space to find matches) return format is: topic1 topic2"; "As a topic extraction assistant, you can help me extract the current discussion of the paper topic, I will enter the content of the paper, you extract the paper topic , no more than two, Hyphenated query terms yield no matches (replace it with space to find matches) return format is: topic1 topic2";
const userMessage = getTextBeforeCursor(quill, 2000); const userMessage = getTextBeforeCursor(quill!, 2000);
topic = await getTopicFromAI(userMessage, prompt, apiKey); topic = await getTopicFromAI(userMessage, prompt, apiKey);
console.log("topic in AI before removeSpecialCharacters", topic); console.log("topic in AI before removeSpecialCharacters", topic);
topic = removeSpecialCharacters(topic); topic = removeSpecialCharacters(topic);
@ -277,21 +288,21 @@ const QEditor = ({ lng }) => {
rawData = await getArxivPapers(topic); rawData = await getArxivPapers(topic);
console.log("arxiv rawdata:", rawData); console.log("arxiv rawdata:", rawData);
// 将 rawData 转换为引用数组 // 将 rawData 转换为引用数组
newReferences = rawData.map((entry) => ({ newReferences = rawData.map((entry: any) => ({
url: entry.id, url: entry.id,
title: entry.title, title: entry.title,
year: entry.published, year: entry.published,
author: entry.authors?.slice(0, 3).join(", "), author: entry.authors?.slice(0, 3).join(", "),
})); }));
dataString = rawData dataString = rawData
.map((entry) => { .map((entry: any) => {
return `ID: ${entry.id}\nTime: ${entry.published}\nTitle: ${entry.title}\nSummary: ${entry.summary}\n\n`; return `ID: ${entry.id}\nTime: ${entry.published}\nTitle: ${entry.title}\nSummary: ${entry.summary}\n\n`;
}) })
.join(""); .join("");
} else if (selectedSource === "semanticScholar") { } else if (selectedSource === "semanticScholar") {
rawData = await getSemanticPapers(topic, "2015-2023"); rawData = await getSemanticPapers(topic, "2015-2023", offset);
// 将 rawData 转换为引用数组 // 将 rawData 转换为引用数组
newReferences = rawData.map((entry) => ({ newReferences = rawData.map((entry: any) => ({
url: entry.url, url: entry.url,
title: entry.title, title: entry.title,
year: entry.year, year: entry.year,
@ -300,16 +311,16 @@ const QEditor = ({ lng }) => {
journal: formatJournalReference(entry), journal: formatJournalReference(entry),
})); }));
dataString = rawData dataString = rawData
.map((entry) => { .map((entry: any) => {
return `Time: ${entry.year}\nTitle: ${entry.title}\nSummary: ${entry.abstract}\n\n`; return `Time: ${entry.year}\nTitle: ${entry.title}\nSummary: ${entry.abstract}\n\n`;
}) })
.join(""); .join("");
} else if (selectedSource === "pubmed") { } else if (selectedSource === "pubmed") {
rawData = await fetchPubMedData(topic, 2020, 2); rawData = await fetchPubMedData(topic, 2020, offset, 2);
if (!rawData) { if (!rawData) {
throw new Error("未搜索到文献 from PubMed."); throw new Error("未搜索到文献 from PubMed.");
} }
newReferences = rawData.map((entry) => ({ newReferences = rawData.map((entry: any) => ({
id: entry.id, // 文章的 PubMed ID id: entry.id, // 文章的 PubMed ID
title: entry.title, // 文章的标题 title: entry.title, // 文章的标题
abstract: entry.abstract, // 文章的摘要 abstract: entry.abstract, // 文章的摘要
@ -324,13 +335,13 @@ const QEditor = ({ lng }) => {
console.log(newReferences); console.log(newReferences);
dataString = rawData dataString = rawData
.map((entry) => { .map((entry: any) => {
return `Time: ${entry.year}\nTitle: ${entry.title}\nSummary: ${entry.abstract}\n\n`; return `Time: ${entry.year}\nTitle: ${entry.title}\nSummary: ${entry.abstract}\n\n`;
}) })
.join(""); .join("");
} }
//在对应的位置添加文献 //在对应的位置添加文献
const nearestNumber = getNumberBeforeCursor(quill); const nearestNumber = getNumberBeforeCursor(quill!);
dispatch( dispatch(
addReferencesRedux({ addReferencesRedux({
references: newReferences, references: newReferences,
@ -346,20 +357,20 @@ const QEditor = ({ lng }) => {
// editorValue // editorValue
// )}`; // )}`;
const content = `之前用户已经完成的内容上下文:${getTextBeforeCursor( const content = `之前用户已经完成的内容上下文:${getTextBeforeCursor(
quill, quill!,
500 900
)},搜索到的论文内容:${trimmedMessage},${topic},`; )},搜索到的论文内容:${trimmedMessage},${topic},`;
await sendMessageToOpenAI( await sendMessageToOpenAI(
content, content,
quill, quill!,
selectedModel, selectedModel!,
apiKey, apiKey,
upsreamUrl, upsreamUrl,
systemPrompt systemPrompt
); );
setUserInput(""); setUserInput("");
// 重新获取更新后的内容并更新 Redux store // 重新获取更新后的内容并更新 Redux store
const updatedContent = quill.root.innerHTML; const updatedContent = quill!.root.innerHTML;
dispatch(setEditorContent(updatedContent)); dispatch(setEditorContent(updatedContent));
if (isVip) { if (isVip) {
//在云端同步supabase //在云端同步supabase
@ -370,12 +381,15 @@ const QEditor = ({ lng }) => {
paperNumberRedux paperNumberRedux
); );
} }
//修改offset使得按照接下来的顺序进行获取文献
offset += 2;
} catch (error) { } catch (error) {
// console.error("Error fetching data:", error); // console.error("Error fetching data:", error);
// 在处理错误后,再次抛出这个错误 // 在处理错误后,再次抛出这个错误
throw new Error(`Paper2AI出现错误: ${error}`); throw new Error(`Paper2AI出现错误: ${error}`);
} }
} }
}
return ( return (
<div className="flex flex-col "> <div className="flex flex-col ">
@ -420,6 +434,12 @@ const QEditor = ({ lng }) => {
<option value="gpt-4">gpt-4</option> <option value="gpt-4">gpt-4</option>
<option value="deepseek-chat">deepseek-chat</option> <option value="deepseek-chat">deepseek-chat</option>
</select> </select>
<input
type="number"
value={generatedPaperNumber}
onChange={handleGeneratedPaperNumberChange}
className="border border-gray-300 text-gray-700 text-sm p-1 rounded w-16"
/>
<button <button
onClick={() => formatTextInEditor(quill)} // 假设 updateIndex 是处理更新操作的函数 onClick={() => formatTextInEditor(quill)} // 假设 updateIndex 是处理更新操作的函数
className="bg-gray-300 hover:bg-gray-400 text-black font-bold py-2 px-4 rounded" className="bg-gray-300 hover:bg-gray-400 text-black font-bold py-2 px-4 rounded"

View File

@ -1,5 +1,7 @@
import { Transforms } from "slate"; import { Transforms } from "slate";
import { Editor } from "slate"; import { Editor } from "slate";
import Quill from "quill";
import { extractText } from "@/utils/others/slateutils"; import { extractText } from "@/utils/others/slateutils";
import { import {
updateBracketNumbersInDeltaKeepSelection, updateBracketNumbersInDeltaKeepSelection,
@ -20,8 +22,8 @@ function isValidApiKey(apiKey: string) {
const sendMessageToOpenAI = async ( const sendMessageToOpenAI = async (
content: string, content: string,
editor: Editor, editor: Quill,
selectedModel: "gpt3.5", selectedModel: string,
apiKey: string, apiKey: string,
upsreamUrl: string, upsreamUrl: string,
prompt?: string prompt?: string
@ -54,7 +56,7 @@ const sendMessageToOpenAI = async (
2.使 [1]***[1]* 2.使 [1]***[1]*
3. 3.
4. 4.
5.使, 5.使
6. 6.
...[1],...[2]`, ...[1],...[2]`,

View File

@ -4,6 +4,7 @@
import * as Sentry from "@sentry/nextjs"; import * as Sentry from "@sentry/nextjs";
if (process.env.NODE_ENV === "production") {
Sentry.init({ Sentry.init({
dsn: "https://523c4056ba48d012c62a377dfc49f647@o4506728662564864.ingest.sentry.io/4506728672264192", dsn: "https://523c4056ba48d012c62a377dfc49f647@o4506728662564864.ingest.sentry.io/4506728672264192",
@ -32,3 +33,4 @@ Sentry.init({
}), }),
], ],
}); });
}

View File

@ -4,7 +4,7 @@
// https://docs.sentry.io/platforms/javascript/guides/nextjs/ // https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from "@sentry/nextjs"; import * as Sentry from "@sentry/nextjs";
if (process.env.NODE_ENV === "production") {
Sentry.init({ Sentry.init({
dsn: "https://523c4056ba48d012c62a377dfc49f647@o4506728662564864.ingest.sentry.io/4506728672264192", dsn: "https://523c4056ba48d012c62a377dfc49f647@o4506728662564864.ingest.sentry.io/4506728672264192",
@ -14,3 +14,4 @@ Sentry.init({
// Setting this option to true will print useful information to the console while you're setting up Sentry. // Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false, debug: false,
}); });
}

View File

@ -3,7 +3,7 @@
// https://docs.sentry.io/platforms/javascript/guides/nextjs/ // https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from "@sentry/nextjs"; import * as Sentry from "@sentry/nextjs";
if (process.env.NODE_ENV === "production") {
Sentry.init({ Sentry.init({
dsn: "https://523c4056ba48d012c62a377dfc49f647@o4506728662564864.ingest.sentry.io/4506728672264192", dsn: "https://523c4056ba48d012c62a377dfc49f647@o4506728662564864.ingest.sentry.io/4506728672264192",
@ -13,3 +13,4 @@ Sentry.init({
// Setting this option to true will print useful information to the console while you're setting up Sentry. // Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false, debug: false,
}); });
}