forked from XiaoMo/ChatGPT-Next-Web
Merge branch 'main' of https://github.com/Yidadaa/ChatGPT-Next-Web
This commit is contained in:
commit
96c0a5c911
@ -8,7 +8,7 @@ WORKDIR /app
|
|||||||
|
|
||||||
COPY package.json yarn.lock ./
|
COPY package.json yarn.lock ./
|
||||||
|
|
||||||
RUN yarn config set registry 'https://registry.npm.taobao.org'
|
RUN yarn config set registry 'https://registry.npmmirror.com/'
|
||||||
RUN yarn install
|
RUN yarn install
|
||||||
|
|
||||||
FROM base AS builder
|
FROM base AS builder
|
||||||
|
@ -9,9 +9,9 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel.
|
|||||||
|
|
||||||
一键免费部署你的私人 ChatGPT 网页应用。
|
一键免费部署你的私人 ChatGPT 网页应用。
|
||||||
|
|
||||||
[Demo](https://chat-gpt-next-web.vercel.app/) / [Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [Join Discord](https://discord.gg/zrhvHCr79N) / [Buy Me a Coffee](https://www.buymeacoffee.com/yidadaa)
|
[Demo](https://chatgpt.nextweb.fun/) / [Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [Join Discord](https://discord.gg/zrhvHCr79N) / [Buy Me a Coffee](https://www.buymeacoffee.com/yidadaa)
|
||||||
|
|
||||||
[演示](https://chat-gpt-next-web.vercel.app/) / [反馈](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [QQ 群](https://user-images.githubusercontent.com/16968934/234462588-e8eff256-f5ca-46ef-8f5f-d7db6d28735a.jpg) / [打赏开发者](https://user-images.githubusercontent.com/16968934/227772541-5bcd52d8-61b7-488c-a203-0330d8006e2b.jpg)
|
[演示](https://chatgpt.nextweb.fun/) / [反馈](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [QQ 群](https://user-images.githubusercontent.com/16968934/234462588-e8eff256-f5ca-46ef-8f5f-d7db6d28735a.jpg) / [打赏开发者](https://user-images.githubusercontent.com/16968934/227772541-5bcd52d8-61b7-488c-a203-0330d8006e2b.jpg)
|
||||||
|
|
||||||
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web)
|
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web)
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { NextRequest } from "next/server";
|
import { NextRequest } from "next/server";
|
||||||
import { getServerSideConfig } from "../config/server";
|
import { getServerSideConfig } from "../config/server";
|
||||||
import md5 from "spark-md5";
|
import md5 from "spark-md5";
|
||||||
|
import { ACCESS_CODE_PREFIX } from "../constant";
|
||||||
|
|
||||||
const serverConfig = getServerSideConfig();
|
const serverConfig = getServerSideConfig();
|
||||||
|
|
||||||
@ -17,10 +18,10 @@ function getIP(req: NextRequest) {
|
|||||||
|
|
||||||
function parseApiKey(bearToken: string) {
|
function parseApiKey(bearToken: string) {
|
||||||
const token = bearToken.trim().replaceAll("Bearer ", "").trim();
|
const token = bearToken.trim().replaceAll("Bearer ", "").trim();
|
||||||
const isOpenAiKey = token.startsWith("sk-");
|
const isOpenAiKey = !token.startsWith(ACCESS_CODE_PREFIX);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accessCode: isOpenAiKey ? "" : token,
|
accessCode: isOpenAiKey ? "" : token.slice(ACCESS_CODE_PREFIX.length),
|
||||||
apiKey: isOpenAiKey ? token : "",
|
apiKey: isOpenAiKey ? token : "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,11 @@ declare global {
|
|||||||
type DangerConfig = typeof DANGER_CONFIG;
|
type DangerConfig = typeof DANGER_CONFIG;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function POST() {
|
async function handle() {
|
||||||
return NextResponse.json(DANGER_CONFIG);
|
return NextResponse.json(DANGER_CONFIG);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const GET = handle;
|
||||||
|
export const POST = handle;
|
||||||
|
|
||||||
export const runtime = "edge";
|
export const runtime = "edge";
|
||||||
|
@ -480,7 +480,7 @@ export function Chat() {
|
|||||||
|
|
||||||
// submit user input
|
// submit user input
|
||||||
const onUserSubmit = () => {
|
const onUserSubmit = () => {
|
||||||
if (userInput.length <= 0) return;
|
if (userInput.trim() === "") return;
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
chatStore.onUserInput(userInput).then(() => setIsLoading(false));
|
chatStore.onUserInput(userInput).then(() => setIsLoading(false));
|
||||||
setBeforeInput(userInput);
|
setBeforeInput(userInput);
|
||||||
|
@ -21,7 +21,7 @@ import { useNavigate } from "react-router-dom";
|
|||||||
|
|
||||||
import chatStyle from "./chat.module.scss";
|
import chatStyle from "./chat.module.scss";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { downloadAs } from "../utils";
|
import { downloadAs, readFromFile } from "../utils";
|
||||||
import { Updater } from "../api/openai/typing";
|
import { Updater } from "../api/openai/typing";
|
||||||
import { ModelConfigList } from "./model-config";
|
import { ModelConfigList } from "./model-config";
|
||||||
import { FileName, Path } from "../constant";
|
import { FileName, Path } from "../constant";
|
||||||
@ -222,6 +222,21 @@ export function MaskPage() {
|
|||||||
downloadAs(JSON.stringify(masks), FileName.Masks);
|
downloadAs(JSON.stringify(masks), FileName.Masks);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const importFromFile = () => {
|
||||||
|
readFromFile().then((content) => {
|
||||||
|
try {
|
||||||
|
const importMasks = JSON.parse(content);
|
||||||
|
if (Array.isArray(importMasks)) {
|
||||||
|
for (const mask of importMasks) {
|
||||||
|
if (mask.name) {
|
||||||
|
maskStore.create(mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<div className={styles["mask-page"]}>
|
<div className={styles["mask-page"]}>
|
||||||
@ -247,7 +262,7 @@ export function MaskPage() {
|
|||||||
<IconButton
|
<IconButton
|
||||||
icon={<UploadIcon />}
|
icon={<UploadIcon />}
|
||||||
bordered
|
bordered
|
||||||
onClick={() => showToast(Locale.WIP)}
|
onClick={() => importFromFile()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="window-action-button">
|
<div className="window-action-button">
|
||||||
@ -371,7 +386,10 @@ export function MaskPage() {
|
|||||||
key="export"
|
key="export"
|
||||||
bordered
|
bordered
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
downloadAs(JSON.stringify(editingMask), "mask.json")
|
downloadAs(
|
||||||
|
JSON.stringify(editingMask),
|
||||||
|
`${editingMask.name}.json`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/>,
|
/>,
|
||||||
<IconButton
|
<IconButton
|
||||||
|
@ -36,3 +36,5 @@ export enum StoreKey {
|
|||||||
export const MAX_SIDEBAR_WIDTH = 500;
|
export const MAX_SIDEBAR_WIDTH = 500;
|
||||||
export const MIN_SIDEBAR_WIDTH = 230;
|
export const MIN_SIDEBAR_WIDTH = 230;
|
||||||
export const NARROW_SIDEBAR_WIDTH = 100;
|
export const NARROW_SIDEBAR_WIDTH = 100;
|
||||||
|
|
||||||
|
export const ACCESS_CODE_PREFIX = "ak-";
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
useChatStore,
|
useChatStore,
|
||||||
} from "./store";
|
} from "./store";
|
||||||
import { showToast } from "./components/ui-lib";
|
import { showToast } from "./components/ui-lib";
|
||||||
|
import { ACCESS_CODE_PREFIX } from "./constant";
|
||||||
|
|
||||||
const TIME_OUT_MS = 60000;
|
const TIME_OUT_MS = 60000;
|
||||||
|
|
||||||
@ -44,9 +45,7 @@ const makeRequestParam = (
|
|||||||
|
|
||||||
function getHeaders() {
|
function getHeaders() {
|
||||||
const accessStore = useAccessStore.getState();
|
const accessStore = useAccessStore.getState();
|
||||||
const headers = {
|
let headers: Record<string, string> = {};
|
||||||
Authorization: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
const makeBearer = (token: string) => `Bearer ${token.trim()}`;
|
const makeBearer = (token: string) => `Bearer ${token.trim()}`;
|
||||||
const validString = (x: string) => x && x.length > 0;
|
const validString = (x: string) => x && x.length > 0;
|
||||||
@ -58,7 +57,9 @@ function getHeaders() {
|
|||||||
accessStore.enabledAccessControl() &&
|
accessStore.enabledAccessControl() &&
|
||||||
validString(accessStore.accessCode)
|
validString(accessStore.accessCode)
|
||||||
) {
|
) {
|
||||||
headers.Authorization = makeBearer(accessStore.accessCode);
|
headers.Authorization = makeBearer(
|
||||||
|
ACCESS_CODE_PREFIX + accessStore.accessCode,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return headers;
|
return headers;
|
||||||
|
@ -57,6 +57,7 @@ export const useMaskStore = create<MaskStore>()(
|
|||||||
...createEmptyMask(),
|
...createEmptyMask(),
|
||||||
...mask,
|
...mask,
|
||||||
id,
|
id,
|
||||||
|
builtin: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
set(() => ({ masks }));
|
set(() => ({ masks }));
|
||||||
|
20
app/utils.ts
20
app/utils.ts
@ -42,6 +42,26 @@ export function downloadAs(text: string, filename: string) {
|
|||||||
document.body.removeChild(element);
|
document.body.removeChild(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function readFromFile() {
|
||||||
|
return new Promise<string>((res, rej) => {
|
||||||
|
const fileInput = document.createElement("input");
|
||||||
|
fileInput.type = "file";
|
||||||
|
fileInput.accept = "application/json";
|
||||||
|
|
||||||
|
fileInput.onchange = (event: any) => {
|
||||||
|
const file = event.target.files[0];
|
||||||
|
const fileReader = new FileReader();
|
||||||
|
fileReader.onload = (e: any) => {
|
||||||
|
res(e.target.result);
|
||||||
|
};
|
||||||
|
fileReader.onerror = (e) => rej(e);
|
||||||
|
fileReader.readAsText(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
fileInput.click();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function isIOS() {
|
export function isIOS() {
|
||||||
const userAgent = navigator.userAgent.toLowerCase();
|
const userAgent = navigator.userAgent.toLowerCase();
|
||||||
return /iphone|ipad|ipod/.test(userAgent);
|
return /iphone|ipad|ipod/.test(userAgent);
|
||||||
|
@ -16,7 +16,9 @@ const nextConfig = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return {
|
||||||
|
afterFiles: ret,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
webpack(config) {
|
webpack(config) {
|
||||||
config.module.rules.push({
|
config.module.rules.push({
|
||||||
|
@ -40,7 +40,7 @@ async function fetchEN() {
|
|||||||
return raw
|
return raw
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.slice(1)
|
.slice(1)
|
||||||
.map((v) => v.split('","').map((v) => v.replace('"', "")));
|
.map((v) => v.split('","').map((v) => v.replace(/^"|"$/g, '').replaceAll('""','"')));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[Fetch] failed to fetch en prompts", error);
|
console.error("[Fetch] failed to fetch en prompts", error);
|
||||||
return [];
|
return [];
|
||||||
|
Loading…
Reference in New Issue
Block a user