feat: 允许自定义URL并且解决了一部分pubmed跨域问题

This commit is contained in:
liuweiqing 2024-01-27 23:25:07 +08:00
parent 2906e6f0d1
commit cab4f0bf01
9 changed files with 182 additions and 19 deletions

115
app/api/[...slug]/route.ts Normal file
View File

@ -0,0 +1,115 @@
export async function POST(req: Request) {
// 从请求头中读取上游 URL
const headers = new Headers(req.headers);
const upstreamUrl = headers.get("Upstream-Url");
console.log("headers:", headers);
if (!upstreamUrl) {
throw new Error("Upstream URL not specified in headers");
}
try {
// 创建新 URL
const url = new URL(req.url);
const apiPath = url.pathname.replace("/api", "");
const upstreamEndpoint = upstreamUrl + apiPath;
// 创建新请求的headers对象
const headers = new Headers(req.headers);
// 移除或替换可能引起问题的头部
// headers.delete("Host");
headers.delete("Content-Length");
headers.delete("Upstream-Url"); // 也删除上游 URL 头部,以免发送到上游服务器
// 读取并解析 JSON 请求体
const reader = req.body.getReader();
let requestBody = "";
let done, value;
while (!done) {
({ done, value } = await reader.read());
if (value) {
requestBody += new TextDecoder().decode(value);
}
}
// 尝试解析为 JSON
let jsonBody;
try {
jsonBody = JSON.parse(requestBody);
} catch (error) {
throw new Error("Failed to parse request body as JSON");
}
// 使用fetch方法转发请求到上游服务器
const response = await fetch(upstreamEndpoint, {
method: "POST",
headers: headers,
body: JSON.stringify(jsonBody), // 确保将请求体转换为字符串
});
console.log("headers:", headers);
console.log("req.body:", jsonBody);
// 将响应数据发送回客户端
return new Response(response.body, {
status: response.status,
headers: response.headers,
});
} catch (error) {
// 错误处理
console.error(error);
return new Response(
JSON.stringify({ error: "Internal Server Error in NEXT" }),
{
status: 500,
headers: {
"Content-Type": "application/json",
},
}
);
}
}
export async function GET(req: Request) {
// 从请求头中读取上游 URL
const headers = new Headers(req.headers);
const upstreamUrl = headers.get("Upstream-Url");
if (!upstreamUrl) {
throw new Error("Upstream URL not specified in headers");
}
try {
// 创建新 URL
const url = new URL(req.url);
const apiPath = url.pathname.replace(/\/api\/paper|\/api/g, "");
const upstreamEndpoint = upstreamUrl + apiPath + url.search;
// 创建新请求的headers对象
const headers = new Headers(req.headers);
// 移除或替换可能引起问题的头部
headers.delete("Host");
headers.delete("Upstream-Url"); // 也删除上游 URL 头部,以免发送到上游服务器
// 使用fetch方法转发请求到上游服务器
const response = await fetch(upstreamEndpoint, {
method: "GET",
headers: headers,
});
console.log("response:", response);
// 将响应数据发送回客户端
let text = await response.text();
console.log("text", text);
return new Response(text, {
headers: headers,
status: response.status,
});
} catch (error) {
// 错误处理
console.error(error);
return new Response(
JSON.stringify({ error: "Internal Server Error in NEXT" }),
{
status: 500,
headers: {
"Content-Type": "application/json",
},
}
);
}
}

View File

@ -3,12 +3,12 @@
import axios from "axios";
import https from "https";
export default async function handler(req, res) {
export async function POST(req: Request) {
const upstreamUrl = "https://api.liuweiqing.top";
try {
// 创建新 URL
const url = upstreamUrl + req.url.replace("/api/proxy", "");
const url = upstreamUrl + new URL(req.url).pathname.replace("/api", "");
// 创建新请求
const newRequest = {
@ -23,19 +23,25 @@ export default async function handler(req, res) {
// 使用axios.post方法转发请求到上游服务器
const response = await axios.post(url, newRequest.data, {
headers: newRequest.headers,
httpsAgent: agent, // 使用新的https.Agent
// httpsAgent: agent, // 使用新的https.Agent
});
// 将响应数据发送回客户端
res.status(response.status).send(response.data);
return new Response(response.data, {
status: response.status,
});
} catch (error) {
// 错误处理
console.error(error);
res.status(500).json({ error: "Internal Server Error" });
return new Response(
{ error: "Internal Server Error in NEXT" },
{
status: 500,
}
);
}
}
// pages/api/proxy.js
// import type { NextApiRequest, NextApiResponse } from "next";
// export default async function handler(

View File

@ -4,12 +4,14 @@ export interface APIState {
apiKey: string;
referencesRedux: Reference[];
editorContent: string;
upsreamUrl: string;
}
const initialState: APIState = {
apiKey: "",
referencesRedux: [],
editorContent: "",
upsreamUrl: "https://api.liuweiqing.top", //https://api.openai.com
};
export const authSlice = createSlice({
@ -19,6 +21,9 @@ export const authSlice = createSlice({
setApiKey: (state, action: PayloadAction<string>) => {
state.apiKey = action.payload;
},
setUpsreamUrl: (state, action: PayloadAction<string>) => {
state.upsreamUrl = action.payload;
},
addReferenceRedux: (state, action: PayloadAction<Reference>) => {
state.referencesRedux.push(action.payload);
},
@ -42,6 +47,7 @@ export const authSlice = createSlice({
// Action creators are generated for each case reducer function
export const {
setApiKey,
setUpsreamUrl,
addReferenceRedux,
addReferencesRedux,
removeReferenceRedux,

View File

@ -41,11 +41,23 @@ async function getPubMedPapers(query: string, year: number, limit = 2) {
async function getPubMedPaperDetails(idList: IDList) {
try {
const ids = idList.join(","); // 将ID列表转换为逗号分隔的字符串
const baseURL = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi";
// const baseURL = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi";
const baseURL = process.env.NEXT_PUBLIC_PAPER_URL; //通过API接口进行转发
const url = `${baseURL}?db=pubmed&id=${ids}&rettype=abstract&retmode=xml`;
console.log(url);
const response = await fetch(url, {
headers: {
"Upstream-Url":
"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi",
"Accept-Encoding": "identity",
},
});
const response = await axios.get(url);
const data = response.data; // 这里获取的数据是XML格式需要解析
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.text(); // 获取响应文本
// 解析XML数据
const parser = new xml2js.Parser({
explicitArray: false,

View File

@ -50,6 +50,7 @@ const toolbarOptions = [
const QEditor = () => {
//读取redux中的API key
const apiKey = useAppSelector((state: any) => state.auth.apiKey);
const upsreamUrl = useAppSelector((state: any) => state.auth.upsreamUrl);
const [quill, setQuill] = useState(null);
//询问ai用户输入
const [userInput, setUserInput] = useState("robot");
@ -151,7 +152,14 @@ const QEditor = () => {
quill.setSelection(cursorPosition, 0); // 将光标移动到原来的位置
const prompt = "请帮助用户完成论文写作,使用用户所说的语言完成";
await sendMessageToOpenAI(userInput, quill, selectedModel, apiKey, prompt);
await sendMessageToOpenAI(
userInput,
quill,
selectedModel,
apiKey,
upsreamUrl,
prompt
);
};
// 处理paper2AI
@ -248,7 +256,7 @@ const QEditor = () => {
quill,
500
)},搜索到的论文内容:${trimmedMessage},${topic},`;
sendMessageToOpenAI(content, quill, selectedModel, apiKey);
sendMessageToOpenAI(content, quill, selectedModel, apiKey, upsreamUrl);
} catch (error) {
console.error("Error fetching data:", error);
// 在处理错误后,再次抛出这个错误

View File

@ -1,10 +1,11 @@
// Settings.tsx
import { useAppDispatch, useAppSelector } from "@/app/store";
import { setApiKey } from "@/app/store/slices/authSlice";
import { setApiKey, setUpsreamUrl } from "@/app/store/slices/authSlice";
const Settings = () => {
const dispatch = useAppDispatch();
const apiKey = useAppSelector((state) => state.auth.apiKey);
const upstreamUrl = useAppSelector((state) => state.auth.upsreamUrl);
return (
<div className="max-w-md mx-auto p-4">
@ -22,6 +23,22 @@ const Settings = () => {
onChange={(event) => dispatch(setApiKey(event.target.value))}
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
/>
{/* upstream-url */}
<div className="mb-4">
<label
className="block text-gray-700 text-sm font-bold mb-2"
htmlFor="upstream-url"
>
Upstream URL:
</label>
<input
id="upstream-url"
type="text"
value={upstreamUrl} // 这里假设你有一个upstreamUrl状态
onChange={(event) => dispatch(setUpsreamUrl(event.target.value))}
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
/>
</div>
</div>
</div>
);

View File

@ -5,6 +5,8 @@ import {
updateBracketNumbersInDeltaKeepSelection,
convertToSuperscript,
} from "@/utils/others/quillutils";
//redux不能在普通函数使用
interface ChatData {
choices: Array<{
delta: {
@ -21,6 +23,7 @@ const sendMessageToOpenAI = async (
editor: Editor,
selectedModel: "gpt3.5",
apiKey: string,
upsreamUrl: string,
prompt?: string
) => {
// console.log("apiKey", apiKey);
@ -32,12 +35,13 @@ const sendMessageToOpenAI = async (
// );
//识别应该使用的模型
let model = selectedModel === "gpt3.5" ? "gpt-3.5-turbo" : "gpt-4";
console.log("upsreamUrl", upsreamUrl);
// 设置API请求参数
const requestOptions = {
method: "POST",
headers: {
"Content-Type": "application/json",
"Upstream-Url": upsreamUrl,
Authorization:
"Bearer " +
(isValidApiKey(apiKey)

View File

@ -1,5 +0,0 @@
// pages/api/hello.js
export default function handler(req, res) {
res.status(200).json({ message: "Hello from the API!" });
}

View File

@ -23,6 +23,6 @@
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "app/api/[...slug]"],
"exclude": ["node_modules"]
}