feat: 可以切换多种引用格式

This commit is contained in:
liuweiqing 2024-02-23 19:17:12 +08:00
parent cbc1e7068b
commit 9b835bbadd
9 changed files with 277 additions and 57 deletions

View File

@ -30,6 +30,7 @@ const statePersistConfig = {
"language",
"isJumpToReference",
"isEvaluateTopicMatch",
"citationStyle",
],
};

View File

@ -7,6 +7,7 @@ export interface APIState {
language: string;
isJumpToReference: boolean;
isEvaluateTopicMatch: boolean;
citationStyle: string;
}
const initialState: APIState = {
@ -17,6 +18,7 @@ const initialState: APIState = {
language: "en",
isJumpToReference: false,
isEvaluateTopicMatch: false,
citationStyle: "custom-chinese",
};
export const stateSlice = createSlice({
@ -50,6 +52,9 @@ export const stateSlice = createSlice({
setIsEvaluateTopicMatch: (state, action: PayloadAction<boolean>) => {
state.isEvaluateTopicMatch = action.payload;
},
setCitationStyle: (state, action: PayloadAction<string>) => {
state.citationStyle = action.payload;
},
},
});
@ -62,6 +67,7 @@ export const {
setLanguage,
setIsJumpToReference,
setIsEvaluateTopicMatch,
setCitationStyle,
} = stateSlice.actions;
export const stateReducer = stateSlice.reducer;

View File

@ -13,6 +13,7 @@ type ParaIn = {
const ExportDocx = ({ editor }: ParaIn) => {
const references = useAppSelector((state) => state.auth.referencesRedux);
const citationStyle = useAppSelector((state) => state.state.citationStyle);
const prepareReferencesForQuill = (references: Reference[]) => {
// 首先添加一个标题
@ -25,8 +26,17 @@ const ExportDocx = ({ editor }: ParaIn) => {
insert: "\n参考文献\n",
},
];
const referencesString = getAllFullReferences(references);
const quillReferences = [{ insert: referencesString }];
const referencesString = getAllFullReferences(references, citationStyle);
const quillReferences = [
{
attributes: {
// 提供默认值,即使这些值不会改变文本样式
bold: false, // 默认为false因为引用通常不需要加粗
align: "left", // 默认为left这是大多数文本的常规对齐方式
},
insert: referencesString,
},
];
// 合并标题和引用列表
return referencesWithTitle.concat(quillReferences);
};

View File

@ -66,18 +66,18 @@ async function getPubMedPaperDetails(idList: IDList) {
// 解析XML数据
const parser = new xml2js.Parser({
explicitArray: false,
ignoreAttrs: true, // 忽略XML属性
ignoreAttrs: false, // 忽略XML属性
charkey: "text", // 字符数据的键
trim: true, // 去除文本前后空格
});
let result = await parser.parseStringPromise(data);
console.log(result);
// console.log(result);
// 提取并处理文章详细信息
const articles = result.PubmedArticleSet.PubmedArticle.map((article) => {
const medlineCitation = article.MedlineCitation;
const articleDetails = medlineCitation.Article;
console.log("atricledetails", articleDetails);
// console.log("atricledetails", articleDetails);
const abstractTexts = articleDetails.Abstract.AbstractText;
let abstract;
@ -85,7 +85,7 @@ async function getPubMedPaperDetails(idList: IDList) {
if (Array.isArray(abstractTexts)) {
// 如果是数组,遍历数组并连接每个元素的文本
abstract = abstractTexts
.map((text) => (typeof text === "object" ? text._ : text))
.map((text) => (typeof text === "object" ? text.text : text))
.join(" ");
} else if (typeof abstractTexts === "string") {
// 如果 abstractTexts 直接就是字符串
@ -133,7 +133,7 @@ async function getPubMedPaperDetails(idList: IDList) {
journalTitle += `: ${articleDetails.Pagination.StartPage}-${articleDetails.Pagination.EndPage}`;
}
// 构建文章的 PubMed URL
const articleUrl = `https://pubmed.ncbi.nlm.nih.gov/${medlineCitation.PMID}/`;
const articleUrl = `https://pubmed.ncbi.nlm.nih.gov/${medlineCitation.PMID.text}/`;
// console.log("medlineCitation", medlineCitation);
console.log("\n,journalTitle", journalTitle);
let title = articleDetails.ArticleTitle;
@ -141,14 +141,36 @@ async function getPubMedPaperDetails(idList: IDList) {
if (title.endsWith(".")) {
title = title.slice(0, -1);
}
// 提取DOI
let doi = null;
if (
article.PubmedData &&
article.PubmedData.ArticleIdList &&
Array.isArray(article.PubmedData.ArticleIdList.ArticleId)
) {
const doiObject = article.PubmedData.ArticleIdList.ArticleId.find(
(idObj) => idObj.$.IdType === "doi"
);
if (doiObject) {
doi = doiObject.text; // 获取DOI值
}
}
console.log("doi", doi);
console.log(
"链接",
medlineCitation.PMID.text,
"属性",
typeof medlineCitation.PMID.text
);
return {
id: medlineCitation.PMID._,
id: Number(medlineCitation.PMID.text),
title: title,
abstract: abstract,
authors: authors,
url: articleUrl,
year: publishedDate,
journal: journalTitle,
doi: doi,
// 其他需要的字段可以继续添加
};
});

View File

@ -34,7 +34,8 @@ async function getSemanticPapers(
offset: offset,
limit: limit,
year: year,
fields: "title,year,authors.name,abstract,venue,url,journal",
fields:
"title,year,authors.name,abstract,venue,url,journal,externalIds",
},
});
// 提取并处理论文数据

View File

@ -359,6 +359,7 @@ const QEditor = ({ lng }) => {
author: entry.authors?.slice(0, 3).join(", "),
venue: entry.venue,
journal: formatJournalReference(entry),
doi: entry.externalIds.DOI,
}));
dataString = rawData
.map((entry: any) => {
@ -391,6 +392,7 @@ const QEditor = ({ lng }) => {
journal: entry.journal, // 文章的发表杂志
url: entry.url, // 文章的 URL
source: "PubMed", // 指示这些引用来自 PubMed
doi: entry.doi, // 文章的 DOI
}));
// 打印 newReferences
@ -411,7 +413,7 @@ const QEditor = ({ lng }) => {
// )}`;
const content = `之前用户已经完成的内容上下文:${getTextBeforeCursor(
quill!,
900
800
)},搜索到的论文内容:${trimmedMessage},${topic},`;
await sendMessageToOpenAI(
content,

View File

@ -1,9 +1,11 @@
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import { useLocalStorage } from "react-use";
import { Reference } from "@/utils/global";
import {
copyToClipboard,
getFullReference,
renderCitation,
getAllFullReferences,
delteIndexUpdateBracketNumbersInDeltaKeepSelection,
} from "@/utils/others/quillutils";
@ -17,7 +19,9 @@ import {
removeReferenceRedux,
clearReferencesRedux,
swapReferencesRedux,
setReferencesRedux,
} from "@/app/store/slices/authSlice";
import { setCitationStyle } from "@/app/store/slices/stateSlice";
//supabase
import { submitPaper } from "@/utils/supabase/supabaseutils";
import { createClient } from "@/utils/supabase/client";
@ -27,10 +31,22 @@ type ReferenceListProps = {
editor: any;
lng: string;
};
//引用转换
import Cite from "citation-js";
const citationStyles = [
{ name: "中文", template: "custom-chinese" }, // 假设你有一个自定义的“中文”格式
{ name: "APA", template: "apa" },
{ name: "MLA", template: "mla" },
{ name: "Chicago", template: "chicago" },
{ name: "Harvard", template: "harvard" },
{ name: "Vancouver", template: "vancouver" },
{ name: "IEEE", template: "ieee" },
];
function ReferenceList({ editor, lng }: ReferenceListProps) {
//i18n
const { t } = useTranslation(lng);
//自定义文献
const [newTitle, setNewTitle] = useState("");
const [newAuthor, setNewAuthor] = useState("");
const [newYear, setNewYear] = useState("");
@ -43,6 +59,7 @@ function ReferenceList({ editor, lng }: ReferenceListProps) {
(state) => state.state.paperNumberRedux
);
const isVip = useAppSelector((state) => state.state.isVip);
const citationStyle = useAppSelector((state) => state.state.citationStyle);
//supabase
const supabase = createClient();
@ -95,6 +112,44 @@ function ReferenceList({ editor, lng }: ReferenceListProps) {
}
}, [references]);
async function generateCitation(doi, style) {
try {
const citation = await Cite.async(doi);
const output = citation.format("bibliography", {
format: "text",
template: style,
lang: "en-US",
});
return output;
} catch (error) {
console.error("Error generating citation:", error);
return ""; // Return an empty string in case of error
}
}
useEffect(() => {
const fetchCitations = async () => {
const updatedReferences = await Promise.all(
references.map(async (ref) => {
// 检查是否已经有当前风格的引用
if (!ref[citationStyle]) {
// 如果没有,则生成新的引用
const citationText = await generateCitation(ref.doi, citationStyle);
return { ...ref, [citationStyle]: citationText }; // 添加新的引用到对象
}
return ref; // 如果已有引用,则不做改变
})
);
dispatch(setReferencesRedux(updatedReferences));
};
fetchCitations();
}, [citationStyle]);
const handleStyleChange = (event) => {
dispatch(setCitationStyle(event.target.value));
};
return (
<div className=" mx-auto p-4">
{/* 引用列表显示区域 */}
@ -106,7 +161,9 @@ function ReferenceList({ editor, lng }: ReferenceListProps) {
<li key={index} className="mb-3 p-2 border-b">
{/* 显示序号 */}
<span className="font-bold mr-2">[{index + 1}].</span>
{getFullReference(reference)}
{/* {getFullReference(reference)} */}
{/* 根据当前风格渲染引用 */}
{renderCitation(reference, citationStyle)}
{reference.url && (
<a
href={reference.url}
@ -133,7 +190,9 @@ function ReferenceList({ editor, lng }: ReferenceListProps) {
</button>
<button
className="bg-gray-500 hover:bg-gray-700 text-white font-bold py-1 px-2 ml-2 rounded"
onClick={() => copyToClipboard(getFullReference(reference))}
onClick={() =>
copyToClipboard(renderCitation(reference, citationStyle))
}
>
{t("复制")}
</button>
@ -230,6 +289,29 @@ function ReferenceList({ editor, lng }: ReferenceListProps) {
>
{t("删除所有引用")}
</button>
{/* 下拉框用于更改引用风格 */}
<div className="mt-4">
<label
htmlFor="citation-style"
className="block text-sm font-medium text-gray-700"
>
:
</label>
<select
id="citation-style"
className="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"
value={citationStyle}
onChange={handleStyleChange}
>
<option value="apa">APA</option>
<option value="mla">MLA</option>
<option value="chicago">Chicago</option>
<option value="harvard">Harvard</option>
<option value="vancouver">Vancouver</option>
<option value="ieee">IEEE</option>
<option value="custom-chinese"></option>
</select>
</div>
</div>
</div>
</form>

View File

@ -1,3 +1,71 @@
// // Path: utils/others/aiutils.ts
// import { sendMessageToOpenAI } from "@/components/chatAI";
// //判断返回的文献是否跟用户输入的主题相关
// export async function evaluateTopicMatch(
// userMessage: any[],
// apiKey: string,
// upsreamUrl: string,
// selectedModel: string,
// topic: string
// ): Promise<{ relevantPapers: string[]; nonRelevantPapers: string[] }> {
// const prompt = "请判断文献是否跟用户输入的主题相关,只需要返回true或者false";
// let relevantPapers: string[] = []; // 存储相关论文的数组
// let nonRelevantPapers: string[] = []; // 存储不相关论文的数组
// for (const paper of userMessage) {
// if (relevantPapers.length >= 2) {
// console.log("已找到两篇相关文献,停止处理剩余文献。");
// break; // 如果已经有两篇相关文献,则停止处理
// }
// // 检查是否存在 abstract如果不存在直接将论文添加到 nonRelevantPapers
// if (!paper.abstract) {
// nonRelevantPapers.push(paper);
// console.log(
// `Paper titled "${paper.title}" has no abstract and was added to non-relevant papers.`
// );
// continue; // 跳过当前迭代,继续下一个论文
// }
// const input = `user's topic:${topic}, \n paper's title: ${paper.title}, \n paper's abstract: ${paper.abstract}`;
// const isRelevantResult = await sendMessageToOpenAI(
// input,
// null,
// selectedModel!,
// apiKey,
// upsreamUrl,
// prompt,
// null,
// false
// );
// console.log("isRelevantResult", isRelevantResult);
// // 尝试解析 JSON 结果,如果无法解析则直接使用结果字符串
// let isRelevant;
// try {
// const parsedResult = JSON.parse(isRelevantResult);
// isRelevant =
// parsedResult === true || parsedResult.toLowerCase() === "true";
// } catch {
// isRelevant =
// isRelevantResult.includes("true") || isRelevantResult.includes("True");
// }
// if (isRelevant) {
// relevantPapers.push(paper); // 如果论文相关,则添加到数组中
// } else {
// nonRelevantPapers.push(paper); // 如果论文不相关,则添加到不相关论文数组中
// }
// }
// console.log(
// `这次有${nonRelevantPapers.length}篇文献没有通过相关性检查`,
// nonRelevantPapers
// );
// //如果相关文献大于两片则缩减到两篇
// // if (relevantPapers.length > 2) {
// // relevantPapers = relevantPapers.slice(0, 2);
// // console.log("文献太多了,只取前两篇");
// // }
// return { relevantPapers, nonRelevantPapers };
// }
// Path: utils/others/aiutils.ts
import { sendMessageToOpenAI } from "@/components/chatAI";
//判断返回的文献是否跟用户输入的主题相关
@ -8,60 +76,77 @@ export async function evaluateTopicMatch(
selectedModel: string,
topic: string
): Promise<{ relevantPapers: string[]; nonRelevantPapers: string[] }> {
const prompt = "请判断文献是否跟用户输入的主题相关,只需要返回true或者false";
const prompt =
"请判断文献是否跟用户输入的主题相关,只需要返回true或false的数组";
let relevantPapers: string[] = []; // 存储相关论文的数组
let nonRelevantPapers: string[] = []; // 存储不相关论文的数组
for (const paper of userMessage) {
// 修改循环逻辑,每次处理两篇文献
for (let i = 0; i < userMessage.length; i += 2) {
// 检查是否已有足够的相关论文
if (relevantPapers.length >= 2) {
console.log("已找到两篇相关文献,停止处理剩余文献。");
break; // 如果已经有两篇相关文献,则停止处理
}
// 检查是否存在 abstract如果不存在直接将论文添加到 nonRelevantPapers
if (!paper.abstract) {
nonRelevantPapers.push(paper);
console.log(
`Paper titled "${paper.title}" has no abstract and was added to non-relevant papers.`
);
continue; // 跳过当前迭代,继续下一个论文
}
const input = `user's topic:${topic}, \n paper's title: ${paper.title}, \n paper's abstract: ${paper.abstract}`;
const isRelevantResult = await sendMessageToOpenAI(
input,
null,
selectedModel!,
apiKey,
upsreamUrl,
prompt,
null,
false
);
console.log("isRelevantResult", isRelevantResult);
// 尝试解析 JSON 结果,如果无法解析则直接使用结果字符串
let isRelevant;
try {
const parsedResult = JSON.parse(isRelevantResult);
isRelevant =
parsedResult === true || parsedResult.toLowerCase() === "true";
} catch {
isRelevant =
isRelevantResult.includes("true") || isRelevantResult.includes("True");
break;
}
if (isRelevant) {
relevantPapers.push(paper); // 如果论文相关,则添加到数组中
} else {
nonRelevantPapers.push(paper); // 如果论文不相关,则添加到不相关论文数组中
let inputs = [];
let papersToEvaluate = userMessage.slice(i, i + 2); // 获取当前迭代的两篇文献
for (const paper of papersToEvaluate) {
// 检查是否存在 abstract如果不存在直接将论文添加到 nonRelevantPapers
if (!paper.abstract) {
nonRelevantPapers.push(paper);
console.log(
`Paper titled "${paper.title}" has no abstract and was added to non-relevant papers.`
);
continue; // 跳过当前论文,继续下一个论文
}
// 准备输入数据
const input = `user's topic: ${topic}, \n paper's title: ${paper.title}, \n paper's abstract: ${paper.abstract}`;
inputs.push(input);
}
// 如果有需要评估的文献,则发送请求
if (inputs.length > 0) {
const combinedInput = inputs.join("\n\n");
const isRelevantResults = await sendMessageToOpenAI(
combinedInput,
null,
selectedModel!,
apiKey,
upsreamUrl,
prompt,
null,
false
);
console.log("isrelevantResults in 相关性检查", isRelevantResults);
// 处理每篇文献的相关性结果
papersToEvaluate.forEach((paper, index) => {
let isRelevant;
try {
const parsedResults = JSON.parse(isRelevantResults); // 将字符串解析成数组
isRelevant =
parsedResults[index] === true ||
parsedResults[index].toString().toLowerCase() === "true";
} catch {
console.log("Error parsing isRelevantResults or accessing index");
}
if (isRelevant) {
relevantPapers.push(paper);
console.log(`Paper titled "${paper.title}" is relevant.`);
} else {
nonRelevantPapers.push(paper);
console.log(`Paper titled "${paper.title}" is not relevant.`);
}
});
}
}
console.log(
`这次有${nonRelevantPapers.length}篇文献没有通过相关性检查`,
nonRelevantPapers
);
//如果相关文献大于两片则缩减到两篇
// if (relevantPapers.length > 2) {
// relevantPapers = relevantPapers.slice(0, 2);
// console.log("文献太多了,只取前两篇");
// }
return { relevantPapers, nonRelevantPapers };
}

View File

@ -323,14 +323,25 @@ export function getFullReference(reference: Reference) {
fullReference += formatReference(reference);
return fullReference;
}
export function getAllFullReferences(references: Reference[]) {
export function getAllFullReferences(references: Reference[], style: string) {
return references
.map((reference, index) => {
return `[${index + 1}] ${getFullReference(reference)}`;
return `[${index + 1}] ${renderCitation(reference, style)}`;
})
.join("\n");
}
export function renderCitation(reference: any, style: string) {
// 检查当前的引用风格
if (style === "custom-chinese") {
// 如果是“custom-chinese”则调用 getFullReference 来渲染引用
return getFullReference(reference);
} else {
// 否则,返回引用对象中对应风格的引用文本
return reference[style];
}
}
export {
getTextBeforeCursor,
updateBracketNumbersInDelta,