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", "language",
"isJumpToReference", "isJumpToReference",
"isEvaluateTopicMatch", "isEvaluateTopicMatch",
"citationStyle",
], ],
}; };

View File

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

View File

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

View File

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

View File

@ -34,7 +34,8 @@ async function getSemanticPapers(
offset: offset, offset: offset,
limit: limit, limit: limit,
year: year, 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(", "), author: entry.authors?.slice(0, 3).join(", "),
venue: entry.venue, venue: entry.venue,
journal: formatJournalReference(entry), journal: formatJournalReference(entry),
doi: entry.externalIds.DOI,
})); }));
dataString = rawData dataString = rawData
.map((entry: any) => { .map((entry: any) => {
@ -391,6 +392,7 @@ const QEditor = ({ lng }) => {
journal: entry.journal, // 文章的发表杂志 journal: entry.journal, // 文章的发表杂志
url: entry.url, // 文章的 URL url: entry.url, // 文章的 URL
source: "PubMed", // 指示这些引用来自 PubMed source: "PubMed", // 指示这些引用来自 PubMed
doi: entry.doi, // 文章的 DOI
})); }));
// 打印 newReferences // 打印 newReferences
@ -411,7 +413,7 @@ const QEditor = ({ lng }) => {
// )}`; // )}`;
const content = `之前用户已经完成的内容上下文:${getTextBeforeCursor( const content = `之前用户已经完成的内容上下文:${getTextBeforeCursor(
quill!, quill!,
900 800
)},搜索到的论文内容:${trimmedMessage},${topic},`; )},搜索到的论文内容:${trimmedMessage},${topic},`;
await sendMessageToOpenAI( await sendMessageToOpenAI(
content, 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 { Reference } from "@/utils/global";
import { import {
copyToClipboard, copyToClipboard,
getFullReference, getFullReference,
renderCitation,
getAllFullReferences, getAllFullReferences,
delteIndexUpdateBracketNumbersInDeltaKeepSelection, delteIndexUpdateBracketNumbersInDeltaKeepSelection,
} from "@/utils/others/quillutils"; } from "@/utils/others/quillutils";
@ -17,7 +19,9 @@ import {
removeReferenceRedux, removeReferenceRedux,
clearReferencesRedux, clearReferencesRedux,
swapReferencesRedux, swapReferencesRedux,
setReferencesRedux,
} from "@/app/store/slices/authSlice"; } from "@/app/store/slices/authSlice";
import { setCitationStyle } from "@/app/store/slices/stateSlice";
//supabase //supabase
import { submitPaper } from "@/utils/supabase/supabaseutils"; import { submitPaper } from "@/utils/supabase/supabaseutils";
import { createClient } from "@/utils/supabase/client"; import { createClient } from "@/utils/supabase/client";
@ -27,10 +31,22 @@ type ReferenceListProps = {
editor: any; editor: any;
lng: string; 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) { function ReferenceList({ editor, lng }: ReferenceListProps) {
//i18n //i18n
const { t } = useTranslation(lng); const { t } = useTranslation(lng);
//自定义文献
const [newTitle, setNewTitle] = useState(""); const [newTitle, setNewTitle] = useState("");
const [newAuthor, setNewAuthor] = useState(""); const [newAuthor, setNewAuthor] = useState("");
const [newYear, setNewYear] = useState(""); const [newYear, setNewYear] = useState("");
@ -43,6 +59,7 @@ function ReferenceList({ editor, lng }: ReferenceListProps) {
(state) => state.state.paperNumberRedux (state) => state.state.paperNumberRedux
); );
const isVip = useAppSelector((state) => state.state.isVip); const isVip = useAppSelector((state) => state.state.isVip);
const citationStyle = useAppSelector((state) => state.state.citationStyle);
//supabase //supabase
const supabase = createClient(); const supabase = createClient();
@ -95,6 +112,44 @@ function ReferenceList({ editor, lng }: ReferenceListProps) {
} }
}, [references]); }, [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 ( return (
<div className=" mx-auto p-4"> <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"> <li key={index} className="mb-3 p-2 border-b">
{/* 显示序号 */} {/* 显示序号 */}
<span className="font-bold mr-2">[{index + 1}].</span> <span className="font-bold mr-2">[{index + 1}].</span>
{getFullReference(reference)} {/* {getFullReference(reference)} */}
{/* 根据当前风格渲染引用 */}
{renderCitation(reference, citationStyle)}
{reference.url && ( {reference.url && (
<a <a
href={reference.url} href={reference.url}
@ -133,7 +190,9 @@ function ReferenceList({ editor, lng }: ReferenceListProps) {
</button> </button>
<button <button
className="bg-gray-500 hover:bg-gray-700 text-white font-bold py-1 px-2 ml-2 rounded" 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("复制")} {t("复制")}
</button> </button>
@ -230,6 +289,29 @@ function ReferenceList({ editor, lng }: ReferenceListProps) {
> >
{t("删除所有引用")} {t("删除所有引用")}
</button> </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>
</div> </div>
</form> </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 // Path: utils/others/aiutils.ts
import { sendMessageToOpenAI } from "@/components/chatAI"; import { sendMessageToOpenAI } from "@/components/chatAI";
//判断返回的文献是否跟用户输入的主题相关 //判断返回的文献是否跟用户输入的主题相关
@ -8,26 +76,41 @@ export async function evaluateTopicMatch(
selectedModel: string, selectedModel: string,
topic: string topic: string
): Promise<{ relevantPapers: string[]; nonRelevantPapers: string[] }> { ): Promise<{ relevantPapers: string[]; nonRelevantPapers: string[] }> {
const prompt = "请判断文献是否跟用户输入的主题相关,只需要返回true或者false"; const prompt =
"请判断文献是否跟用户输入的主题相关,只需要返回true或false的数组";
let relevantPapers: string[] = []; // 存储相关论文的数组 let relevantPapers: string[] = []; // 存储相关论文的数组
let nonRelevantPapers: string[] = []; // 存储不相关论文的数组 let nonRelevantPapers: string[] = []; // 存储不相关论文的数组
for (const paper of userMessage) { // 修改循环逻辑,每次处理两篇文献
for (let i = 0; i < userMessage.length; i += 2) {
// 检查是否已有足够的相关论文
if (relevantPapers.length >= 2) { if (relevantPapers.length >= 2) {
console.log("已找到两篇相关文献,停止处理剩余文献。"); console.log("已找到两篇相关文献,停止处理剩余文献。");
break; // 如果已经有两篇相关文献,则停止处理 break;
} }
let inputs = [];
let papersToEvaluate = userMessage.slice(i, i + 2); // 获取当前迭代的两篇文献
for (const paper of papersToEvaluate) {
// 检查是否存在 abstract如果不存在直接将论文添加到 nonRelevantPapers // 检查是否存在 abstract如果不存在直接将论文添加到 nonRelevantPapers
if (!paper.abstract) { if (!paper.abstract) {
nonRelevantPapers.push(paper); nonRelevantPapers.push(paper);
console.log( console.log(
`Paper titled "${paper.title}" has no abstract and was added to non-relevant papers.` `Paper titled "${paper.title}" has no abstract and was added to non-relevant papers.`
); );
continue; // 跳过当前迭代,继续下一个论文 continue; // 跳过当前论文,继续下一个论文
} }
// 准备输入数据
const input = `user's topic: ${topic}, \n paper's title: ${paper.title}, \n paper's abstract: ${paper.abstract}`; const input = `user's topic: ${topic}, \n paper's title: ${paper.title}, \n paper's abstract: ${paper.abstract}`;
const isRelevantResult = await sendMessageToOpenAI( inputs.push(input);
input, }
// 如果有需要评估的文献,则发送请求
if (inputs.length > 0) {
const combinedInput = inputs.join("\n\n");
const isRelevantResults = await sendMessageToOpenAI(
combinedInput,
null, null,
selectedModel!, selectedModel!,
apiKey, apiKey,
@ -36,32 +119,34 @@ export async function evaluateTopicMatch(
null, null,
false false
); );
console.log("isRelevantResult", isRelevantResult); console.log("isrelevantResults in 相关性检查", isRelevantResults);
// 尝试解析 JSON 结果,如果无法解析则直接使用结果字符串 // 处理每篇文献的相关性结果
papersToEvaluate.forEach((paper, index) => {
let isRelevant; let isRelevant;
try { try {
const parsedResult = JSON.parse(isRelevantResult); const parsedResults = JSON.parse(isRelevantResults); // 将字符串解析成数组
isRelevant = isRelevant =
parsedResult === true || parsedResult.toLowerCase() === "true"; parsedResults[index] === true ||
parsedResults[index].toString().toLowerCase() === "true";
} catch { } catch {
isRelevant = console.log("Error parsing isRelevantResults or accessing index");
isRelevantResult.includes("true") || isRelevantResult.includes("True");
} }
if (isRelevant) { if (isRelevant) {
relevantPapers.push(paper); // 如果论文相关,则添加到数组中 relevantPapers.push(paper);
console.log(`Paper titled "${paper.title}" is relevant.`);
} else { } else {
nonRelevantPapers.push(paper); // 如果论文不相关,则添加到不相关论文数组中 nonRelevantPapers.push(paper);
console.log(`Paper titled "${paper.title}" is not relevant.`);
}
});
} }
} }
console.log( console.log(
`这次有${nonRelevantPapers.length}篇文献没有通过相关性检查`, `这次有${nonRelevantPapers.length}篇文献没有通过相关性检查`,
nonRelevantPapers nonRelevantPapers
); );
//如果相关文献大于两片则缩减到两篇
// if (relevantPapers.length > 2) {
// relevantPapers = relevantPapers.slice(0, 2);
// console.log("文献太多了,只取前两篇");
// }
return { relevantPapers, nonRelevantPapers }; return { relevantPapers, nonRelevantPapers };
} }

View File

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