mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-11-14 05:03:43 +08:00
Merge remote-tracking branch 'upstream/main'
This commit is contained in:
@@ -12,17 +12,28 @@ async function handle(
|
||||
|
||||
const requestUrl = new URL(req.url);
|
||||
let endpoint = requestUrl.searchParams.get("endpoint");
|
||||
if (!endpoint?.endsWith("/")) {
|
||||
endpoint += "/";
|
||||
|
||||
// Validate the endpoint to prevent potential SSRF attacks
|
||||
if (!endpoint || !endpoint.startsWith("/")) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: true,
|
||||
msg: "Invalid endpoint",
|
||||
},
|
||||
{
|
||||
status: 400,
|
||||
},
|
||||
);
|
||||
}
|
||||
const endpointPath = params.path.join("/");
|
||||
const targetPath = `${endpoint}/${endpointPath}`;
|
||||
|
||||
// only allow MKCOL, GET, PUT
|
||||
if (req.method !== "MKCOL" && req.method !== "GET" && req.method !== "PUT") {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: true,
|
||||
msg: "you are not allowed to request " + params.path.join("/"),
|
||||
msg: "you are not allowed to request " + targetPath,
|
||||
},
|
||||
{
|
||||
status: 403,
|
||||
@@ -31,14 +42,11 @@ async function handle(
|
||||
}
|
||||
|
||||
// for MKCOL request, only allow request ${folder}
|
||||
if (
|
||||
req.method == "MKCOL" &&
|
||||
!new URL(endpointPath).pathname.endsWith(folder)
|
||||
) {
|
||||
if (req.method === "MKCOL" && !targetPath.endsWith(folder)) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: true,
|
||||
msg: "you are not allowed to request " + params.path.join("/"),
|
||||
msg: "you are not allowed to request " + targetPath,
|
||||
},
|
||||
{
|
||||
status: 403,
|
||||
@@ -47,14 +55,11 @@ async function handle(
|
||||
}
|
||||
|
||||
// for GET request, only allow request ending with fileName
|
||||
if (
|
||||
req.method == "GET" &&
|
||||
!new URL(endpointPath).pathname.endsWith(fileName)
|
||||
) {
|
||||
if (req.method === "GET" && !targetPath.endsWith(fileName)) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: true,
|
||||
msg: "you are not allowed to request " + params.path.join("/"),
|
||||
msg: "you are not allowed to request " + targetPath,
|
||||
},
|
||||
{
|
||||
status: 403,
|
||||
@@ -63,14 +68,11 @@ async function handle(
|
||||
}
|
||||
|
||||
// for PUT request, only allow request ending with fileName
|
||||
if (
|
||||
req.method == "PUT" &&
|
||||
!new URL(endpointPath).pathname.endsWith(fileName)
|
||||
) {
|
||||
if (req.method === "PUT" && !targetPath.endsWith(fileName)) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: true,
|
||||
msg: "you are not allowed to request " + params.path.join("/"),
|
||||
msg: "you are not allowed to request " + targetPath,
|
||||
},
|
||||
{
|
||||
status: 403,
|
||||
@@ -78,7 +80,7 @@ async function handle(
|
||||
);
|
||||
}
|
||||
|
||||
const targetUrl = `${endpoint + endpointPath}`;
|
||||
const targetUrl = `${endpoint}/${endpointPath}`;
|
||||
|
||||
const method = req.method;
|
||||
const shouldNotHaveBody = ["get", "head"].includes(
|
||||
@@ -90,6 +92,7 @@ async function handle(
|
||||
authorization: req.headers.get("authorization") ?? "",
|
||||
},
|
||||
body: shouldNotHaveBody ? null : req.body,
|
||||
redirect: "manual",
|
||||
method,
|
||||
// @ts-ignore
|
||||
duplex: "half",
|
||||
|
||||
@@ -236,6 +236,9 @@ export class ChatGPTApi implements LLMApi {
|
||||
if (finished || controller.signal.aborted) {
|
||||
responseText += remainText;
|
||||
console.log("[Response Animation] finished");
|
||||
if (responseText?.length === 0) {
|
||||
options.onError?.(new Error("empty response from server"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -240,6 +240,8 @@ function useSubmitHandler() {
|
||||
}, []);
|
||||
|
||||
const shouldSubmit = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
// Fix Chinese input method "Enter" on Safari
|
||||
if (e.keyCode == 229) return false;
|
||||
if (e.key !== "Enter") return false;
|
||||
if (e.key === "Enter" && (e.nativeEvent.isComposing || isComposing.current))
|
||||
return false;
|
||||
@@ -399,12 +401,12 @@ function ChatAction(props: {
|
||||
...props.style,
|
||||
} as React.CSSProperties)
|
||||
: props.loding
|
||||
? ({
|
||||
"--icon-width": `30px`,
|
||||
"--full-width": `30px`,
|
||||
...props.style,
|
||||
} as React.CSSProperties)
|
||||
: props.style
|
||||
? ({
|
||||
"--icon-width": `30px`,
|
||||
"--full-width": `30px`,
|
||||
...props.style,
|
||||
} as React.CSSProperties)
|
||||
: props.style
|
||||
}
|
||||
>
|
||||
{props.icon ? (
|
||||
|
||||
@@ -116,9 +116,27 @@ function escapeDollarNumber(text: string) {
|
||||
return escapedText;
|
||||
}
|
||||
|
||||
function escapeBrackets(text: string) {
|
||||
const pattern =
|
||||
/(```[\s\S]*?```|`.*?`)|\\\[([\s\S]*?[^\\])\\\]|\\\((.*?)\\\)/g;
|
||||
return text.replace(
|
||||
pattern,
|
||||
(match, codeBlock, squareBracket, roundBracket) => {
|
||||
if (codeBlock) {
|
||||
return codeBlock;
|
||||
} else if (squareBracket) {
|
||||
return `$$${squareBracket}$$`;
|
||||
} else if (roundBracket) {
|
||||
return `$${roundBracket}$`;
|
||||
}
|
||||
return match;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function _MarkDownContent(props: { content: string }) {
|
||||
const escapedContent = useMemo(
|
||||
() => escapeDollarNumber(props.content),
|
||||
() => escapeBrackets(escapeDollarNumber(props.content)),
|
||||
[props.content],
|
||||
);
|
||||
|
||||
|
||||
14
app/utils.ts
14
app/utils.ts
@@ -59,10 +59,7 @@ export async function downloadAs(text: string, filename: string) {
|
||||
|
||||
if (result !== null) {
|
||||
try {
|
||||
await window.__TAURI__.fs.writeTextFile(
|
||||
result,
|
||||
text
|
||||
);
|
||||
await window.__TAURI__.fs.writeTextFile(result, text);
|
||||
showToast(Locale.Download.Success);
|
||||
} catch (error) {
|
||||
showToast(Locale.Download.Failed);
|
||||
@@ -294,9 +291,8 @@ export function getMessageImages(message: RequestMessage): string[] {
|
||||
}
|
||||
|
||||
export function isVisionModel(model: string) {
|
||||
return (
|
||||
// model.startsWith("gpt-4-vision") ||
|
||||
// model.startsWith("gemini-pro-vision") ||
|
||||
model.includes("vision")
|
||||
);
|
||||
// Note: This is a better way using the TypeScript feature instead of `&&` or `||` (ts v5.5.0-dev.20240314 I've been using)
|
||||
const visionKeywords = ["vision", "claude-3"];
|
||||
|
||||
return visionKeywords.some((keyword) => model.includes(keyword));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user