forked from XiaoMo/ChatGPT-Next-Web
Merge pull request #48 from Yidadaa/custom-token
v1.4 Custom Api Key & Copy Code Button
This commit is contained in:
commit
84d73fa1f2
@ -2,19 +2,25 @@ import type { ChatRequest } from "../chat/typing";
|
|||||||
import { createParser } from "eventsource-parser";
|
import { createParser } from "eventsource-parser";
|
||||||
import { NextRequest } from "next/server";
|
import { NextRequest } from "next/server";
|
||||||
|
|
||||||
const apiKey = process.env.OPENAI_API_KEY;
|
async function createStream(req: NextRequest) {
|
||||||
|
|
||||||
async function createStream(payload: ReadableStream<Uint8Array>) {
|
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
const decoder = new TextDecoder();
|
const decoder = new TextDecoder();
|
||||||
|
|
||||||
|
let apiKey = process.env.OPENAI_API_KEY;
|
||||||
|
|
||||||
|
const userApiKey = req.headers.get("token");
|
||||||
|
if (userApiKey) {
|
||||||
|
apiKey = userApiKey;
|
||||||
|
console.log("[Stream] using user api key");
|
||||||
|
}
|
||||||
|
|
||||||
const res = await fetch("https://api.openai.com/v1/chat/completions", {
|
const res = await fetch("https://api.openai.com/v1/chat/completions", {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: `Bearer ${apiKey}`,
|
Authorization: `Bearer ${apiKey}`,
|
||||||
},
|
},
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: payload,
|
body: req.body,
|
||||||
});
|
});
|
||||||
|
|
||||||
const stream = new ReadableStream({
|
const stream = new ReadableStream({
|
||||||
@ -49,7 +55,7 @@ async function createStream(payload: ReadableStream<Uint8Array>) {
|
|||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const stream = await createStream(req.body!);
|
const stream = await createStream(req);
|
||||||
return new Response(stream);
|
return new Response(stream);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[Chat Stream]", error);
|
console.error("[Chat Stream]", error);
|
||||||
|
@ -1,23 +1,26 @@
|
|||||||
import { OpenAIApi, Configuration } from "openai";
|
import { OpenAIApi, Configuration } from "openai";
|
||||||
import { ChatRequest } from "./typing";
|
import { ChatRequest } from "./typing";
|
||||||
|
|
||||||
const apiKey = process.env.OPENAI_API_KEY;
|
|
||||||
|
|
||||||
const openai = new OpenAIApi(
|
|
||||||
new Configuration({
|
|
||||||
apiKey,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
export async function POST(req: Request) {
|
export async function POST(req: Request) {
|
||||||
try {
|
try {
|
||||||
const requestBody = (await req.json()) as ChatRequest;
|
let apiKey = process.env.OPENAI_API_KEY;
|
||||||
const completion = await openai!.createChatCompletion(
|
|
||||||
{
|
const userApiKey = req.headers.get("token");
|
||||||
...requestBody,
|
if (userApiKey) {
|
||||||
}
|
apiKey = userApiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
const openai = new OpenAIApi(
|
||||||
|
new Configuration({
|
||||||
|
apiKey,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const requestBody = (await req.json()) as ChatRequest;
|
||||||
|
const completion = await openai!.createChatCompletion({
|
||||||
|
...requestBody,
|
||||||
|
});
|
||||||
|
|
||||||
return new Response(JSON.stringify(completion.data));
|
return new Response(JSON.stringify(completion.data));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("[Chat] ", e);
|
console.error("[Chat] ", e);
|
||||||
|
@ -4,15 +4,36 @@ import RemarkMath from "remark-math";
|
|||||||
import RehypeKatex from "rehype-katex";
|
import RehypeKatex from "rehype-katex";
|
||||||
import RemarkGfm from "remark-gfm";
|
import RemarkGfm from "remark-gfm";
|
||||||
import RehypePrsim from "rehype-prism-plus";
|
import RehypePrsim from "rehype-prism-plus";
|
||||||
|
import { useRef } from "react";
|
||||||
|
import { copyToClipboard } from "../utils";
|
||||||
|
|
||||||
|
export function PreCode(props: { children: any }) {
|
||||||
|
const ref = useRef<HTMLPreElement>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<pre ref={ref}>
|
||||||
|
<span
|
||||||
|
className="copy-code-button"
|
||||||
|
onClick={() => {
|
||||||
|
if (ref.current) {
|
||||||
|
const code = ref.current.innerText;
|
||||||
|
copyToClipboard(code);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
></span>
|
||||||
|
{props.children}
|
||||||
|
</pre>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function Markdown(props: { content: string }) {
|
export function Markdown(props: { content: string }) {
|
||||||
return (
|
return (
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
remarkPlugins={[RemarkMath, RemarkGfm]}
|
remarkPlugins={[RemarkMath, RemarkGfm]}
|
||||||
rehypePlugins={[
|
rehypePlugins={[RehypeKatex, [RehypePrsim, { ignoreMissing: true }]]}
|
||||||
RehypeKatex,
|
components={{
|
||||||
[RehypePrsim, { ignoreMissing: true }],
|
pre: PreCode,
|
||||||
]}
|
}}
|
||||||
>
|
>
|
||||||
{props.content}
|
{props.content}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
|
@ -257,6 +257,20 @@ export function Settings(props: { closeSettings: () => void }) {
|
|||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<SettingItem
|
||||||
|
title={Locale.Settings.Token.Title}
|
||||||
|
subTitle={Locale.Settings.Token.SubTitle}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
value={accessStore.token}
|
||||||
|
type="text"
|
||||||
|
placeholder={Locale.Settings.Token.Placeholder}
|
||||||
|
onChange={(e) => {
|
||||||
|
accessStore.updateToken(e.currentTarget.value);
|
||||||
|
}}
|
||||||
|
></input>
|
||||||
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
title={Locale.Settings.HistoryCount.Title}
|
title={Locale.Settings.HistoryCount.Title}
|
||||||
subTitle={Locale.Settings.HistoryCount.SubTitle}
|
subTitle={Locale.Settings.HistoryCount.SubTitle}
|
||||||
|
@ -69,6 +69,11 @@ const cn = {
|
|||||||
Title: "历史消息长度压缩阈值",
|
Title: "历史消息长度压缩阈值",
|
||||||
SubTitle: "当未压缩的历史消息超过该值时,将进行压缩",
|
SubTitle: "当未压缩的历史消息超过该值时,将进行压缩",
|
||||||
},
|
},
|
||||||
|
Token: {
|
||||||
|
Title: "API Key",
|
||||||
|
SubTitle: "使用自己的 Key 可绕过受控访问限制",
|
||||||
|
Placeholder: "OpenAI API Key",
|
||||||
|
},
|
||||||
AccessCode: {
|
AccessCode: {
|
||||||
Title: "访问码",
|
Title: "访问码",
|
||||||
SubTitle: "现在是受控访问状态",
|
SubTitle: "现在是受控访问状态",
|
||||||
|
@ -74,6 +74,11 @@ const en: LocaleType = {
|
|||||||
SubTitle:
|
SubTitle:
|
||||||
"Will compress if uncompressed messages length exceeds the value",
|
"Will compress if uncompressed messages length exceeds the value",
|
||||||
},
|
},
|
||||||
|
Token: {
|
||||||
|
Title: "API Key",
|
||||||
|
SubTitle: "Use your key to ignore access code limit",
|
||||||
|
Placeholder: "OpenAI API Key",
|
||||||
|
},
|
||||||
AccessCode: {
|
AccessCode: {
|
||||||
Title: "Access Code",
|
Title: "Access Code",
|
||||||
SubTitle: "Access control enabled",
|
SubTitle: "Access control enabled",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Analytics } from "@vercel/analytics/react";
|
import { Analytics } from "@vercel/analytics/react";
|
||||||
import { Home } from './components/home'
|
import { Home } from "./components/home";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
|
@ -35,6 +35,10 @@ function getHeaders() {
|
|||||||
headers["access-code"] = accessStore.accessCode;
|
headers["access-code"] = accessStore.accessCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (accessStore.token && accessStore.token.length > 0) {
|
||||||
|
headers["token"] = accessStore.token;
|
||||||
|
}
|
||||||
|
|
||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,9 @@ import { queryMeta } from "../utils";
|
|||||||
|
|
||||||
export interface AccessControlStore {
|
export interface AccessControlStore {
|
||||||
accessCode: string;
|
accessCode: string;
|
||||||
|
token: string;
|
||||||
|
|
||||||
|
updateToken: (_: string) => void;
|
||||||
updateCode: (_: string) => void;
|
updateCode: (_: string) => void;
|
||||||
enabledAccessControl: () => boolean;
|
enabledAccessControl: () => boolean;
|
||||||
}
|
}
|
||||||
@ -14,6 +16,7 @@ export const ACCESS_KEY = "access-control";
|
|||||||
export const useAccessStore = create<AccessControlStore>()(
|
export const useAccessStore = create<AccessControlStore>()(
|
||||||
persist(
|
persist(
|
||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
|
token: "",
|
||||||
accessCode: "",
|
accessCode: "",
|
||||||
enabledAccessControl() {
|
enabledAccessControl() {
|
||||||
return queryMeta("access") === "enabled";
|
return queryMeta("access") === "enabled";
|
||||||
@ -21,6 +24,9 @@ export const useAccessStore = create<AccessControlStore>()(
|
|||||||
updateCode(code: string) {
|
updateCode(code: string) {
|
||||||
set((state) => ({ accessCode: code }));
|
set((state) => ({ accessCode: code }));
|
||||||
},
|
},
|
||||||
|
updateToken(token: string) {
|
||||||
|
set((state) => ({ token }));
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: ACCESS_KEY,
|
name: ACCESS_KEY,
|
||||||
|
@ -49,22 +49,24 @@ export interface ChatConfig {
|
|||||||
|
|
||||||
export type ModelConfig = ChatConfig["modelConfig"];
|
export type ModelConfig = ChatConfig["modelConfig"];
|
||||||
|
|
||||||
|
const ENABLE_GPT4 = true;
|
||||||
|
|
||||||
export const ALL_MODELS = [
|
export const ALL_MODELS = [
|
||||||
{
|
{
|
||||||
name: "gpt-4",
|
name: "gpt-4",
|
||||||
available: false,
|
available: ENABLE_GPT4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "gpt-4-0314",
|
name: "gpt-4-0314",
|
||||||
available: false,
|
available: ENABLE_GPT4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "gpt-4-32k",
|
name: "gpt-4-32k",
|
||||||
available: false,
|
available: ENABLE_GPT4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "gpt-4-32k-0314",
|
name: "gpt-4-32k-0314",
|
||||||
available: false,
|
available: ENABLE_GPT4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "gpt-3.5-turbo",
|
name: "gpt-3.5-turbo",
|
||||||
|
@ -206,3 +206,36 @@ div.math {
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:hover .copy-code-button {
|
||||||
|
pointer-events: all;
|
||||||
|
transform: translateX(0px);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-code-button {
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0px 5px;
|
||||||
|
background-color: var(--black);
|
||||||
|
color: var(--white);
|
||||||
|
border: var(--border-in-light);
|
||||||
|
border-radius: 10px;
|
||||||
|
transform: translateX(10px);
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all ease 0.3s;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: "copy";
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -8,13 +8,14 @@ export const config = {
|
|||||||
|
|
||||||
export function middleware(req: NextRequest, res: NextResponse) {
|
export function middleware(req: NextRequest, res: NextResponse) {
|
||||||
const accessCode = req.headers.get("access-code");
|
const accessCode = req.headers.get("access-code");
|
||||||
|
const token = req.headers.get("token");
|
||||||
const hashedCode = md5.hash(accessCode ?? "").trim();
|
const hashedCode = md5.hash(accessCode ?? "").trim();
|
||||||
|
|
||||||
console.log("[Auth] allowed hashed codes: ", [...ACCESS_CODES]);
|
console.log("[Auth] allowed hashed codes: ", [...ACCESS_CODES]);
|
||||||
console.log("[Auth] got access code:", accessCode);
|
console.log("[Auth] got access code:", accessCode);
|
||||||
console.log("[Auth] hashed access code:", hashedCode);
|
console.log("[Auth] hashed access code:", hashedCode);
|
||||||
|
|
||||||
if (ACCESS_CODES.size > 0 && !ACCESS_CODES.has(hashedCode)) {
|
if (ACCESS_CODES.size > 0 && !ACCESS_CODES.has(hashedCode) && !token) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{
|
{
|
||||||
needAccessCode: true,
|
needAccessCode: true,
|
||||||
|
Loading…
Reference in New Issue
Block a user