mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-10-09 03:26:38 +08:00
Merge remote-tracking branch 'upstream/main' into dev
This commit is contained in:
commit
9c33e947b1
5
.github/ISSUE_TEMPLATE/反馈问题.md
vendored
5
.github/ISSUE_TEMPLATE/反馈问题.md
vendored
@ -7,6 +7,11 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**反馈须知**
|
||||
> 请在下方中括号内输入 x 来表示你已经知晓相关内容。
|
||||
- [ ] 我确认已经在 [常见问题](https://github.com/Yidadaa/ChatGPT-Next-Web/blob/main/docs/faq-cn.md) 中搜索了此次反馈的问题,没有找到解答;
|
||||
- [ ] 我确认已经在 [Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) 列表(包括已经 Close 的)中搜索了此次反馈的问题,没有找到解答。
|
||||
|
||||
**描述问题**
|
||||
请在此描述你遇到了什么问题。
|
||||
|
||||
|
@ -17,7 +17,6 @@ RUN apk update && apk add --no-cache git
|
||||
|
||||
ENV OPENAI_API_KEY=""
|
||||
ENV CODE=""
|
||||
ARG DOCKER=true
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
@ -46,7 +45,7 @@ CMD if [ -n "$PROXY_URL" ]; then \
|
||||
host=$(echo $PROXY_URL | cut -d/ -f3 | cut -d: -f1); \
|
||||
port=$(echo $PROXY_URL | cut -d: -f3); \
|
||||
conf=/etc/proxychains.conf; \
|
||||
echo "strict_chain" >> $conf; \
|
||||
echo "strict_chain" > $conf; \
|
||||
echo "proxy_dns" >> $conf; \
|
||||
echo "remote_dns_subnet 224" >> $conf; \
|
||||
echo "tcp_read_time_out 15000" >> $conf; \
|
||||
|
@ -7,7 +7,7 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel.
|
||||
|
||||
一键免费部署你的私人 ChatGPT 网页应用。
|
||||
|
||||
[演示 Demo](https://chat-gpt-next-web.vercel.app/) / [反馈 Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [Join Discord](https://discord.gg/zrhvHCr79N) / [QQ 群](https://user-images.githubusercontent.com/16968934/228190818-7dd00845-e9b9-4363-97e5-44c507ac76da.jpeg) / [打赏开发者](https://user-images.githubusercontent.com/16968934/227772541-5bcd52d8-61b7-488c-a203-0330d8006e2b.jpg) / [Buy Me a Coffee](https://www.buymeacoffee.com/yidadaa)
|
||||
[演示 Demo](https://chat-gpt-next-web.vercel.app/) / [反馈 Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [Join Discord](https://discord.gg/zrhvHCr79N) / [QQ 群](https://user-images.githubusercontent.com/16968934/231095592-330adc52-0337-4c13-8452-938ec169e367.jpeg) / [打赏开发者](https://user-images.githubusercontent.com/16968934/227772541-5bcd52d8-61b7-488c-a203-0330d8006e2b.jpg) / [Buy Me a Coffee](https://www.buymeacoffee.com/yidadaa)
|
||||
|
||||
[](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)
|
||||
|
||||
@ -20,6 +20,7 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel.
|
||||
## Features
|
||||
|
||||
- **Deploy for free with one-click** on Vercel in under 1 minute
|
||||
- Privacy first, all data stored locally in the browser
|
||||
- Responsive design, dark mode and PWA
|
||||
- Fast first screen loading speed (~100kb)
|
||||
- Awesome prompts powered by [awesome-chatgpt-prompts-zh](https://github.com/PlexPt/awesome-chatgpt-prompts-zh) and [awesome-chatgpt-prompts](https://github.com/f/awesome-chatgpt-prompts)
|
||||
@ -45,6 +46,7 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel.
|
||||
- 在 1 分钟内使用 Vercel **免费一键部署**
|
||||
- 精心设计的 UI,响应式设计,支持深色模式,支持 PWA
|
||||
- 极快的首屏加载速度(~100kb)
|
||||
- 隐私安全,所有数据保存在用户浏览器本地
|
||||
- 海量的内置 prompt 列表,来自[中文](https://github.com/PlexPt/awesome-chatgpt-prompts-zh)和[英文](https://github.com/f/awesome-chatgpt-prompts)
|
||||
- 自动压缩上下文聊天记录,在节省 Token 的同时支持超长对话
|
||||
- 一键导出聊天记录,完整的 Markdown 支持
|
||||
|
@ -1,17 +0,0 @@
|
||||
import md5 from "spark-md5";
|
||||
|
||||
export function getAccessCodes(): Set<string> {
|
||||
const code = process.env.CODE;
|
||||
|
||||
try {
|
||||
const codes = (code?.split(",") ?? [])
|
||||
.filter((v) => !!v)
|
||||
.map((v) => md5.hash(v.trim()));
|
||||
return new Set(codes);
|
||||
} catch (e) {
|
||||
return new Set();
|
||||
}
|
||||
}
|
||||
|
||||
export const ACCESS_CODES = getAccessCodes();
|
||||
export const IS_IN_DOCKER = process.env.DOCKER;
|
@ -40,7 +40,7 @@ async function createStream(req: NextRequest) {
|
||||
|
||||
const parser = createParser(onParse);
|
||||
for await (const chunk of res.body as any) {
|
||||
parser.feed(decoder.decode(chunk));
|
||||
parser.feed(decoder.decode(chunk, { stream: true }));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
21
app/api/config/route.ts
Normal file
21
app/api/config/route.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
import { getServerSideConfig } from "../../config/server";
|
||||
|
||||
const serverConfig = getServerSideConfig();
|
||||
|
||||
// Danger! Don not write any secret value here!
|
||||
// 警告!不要在这里写入任何敏感信息!
|
||||
const DANGER_CONFIG = {
|
||||
needCode: serverConfig.needCode,
|
||||
};
|
||||
|
||||
declare global {
|
||||
type DangerConfig = typeof DANGER_CONFIG;
|
||||
}
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
return NextResponse.json({
|
||||
needCode: serverConfig.needCode,
|
||||
});
|
||||
}
|
@ -4,4 +4,4 @@ import type {
|
||||
} from "openai";
|
||||
|
||||
export type ChatRequest = CreateChatCompletionRequest;
|
||||
export type ChatReponse = CreateChatCompletionResponse;
|
||||
export type ChatResponse = CreateChatCompletionResponse;
|
||||
|
@ -2,13 +2,7 @@
|
||||
|
||||
require("../polyfill");
|
||||
|
||||
import {
|
||||
useState,
|
||||
useEffect,
|
||||
useRef,
|
||||
useCallback,
|
||||
MouseEventHandler,
|
||||
} from "react";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
|
||||
import { IconButton } from "./button";
|
||||
import styles from "./home.module.scss";
|
||||
@ -30,7 +24,6 @@ import { Chat } from "./chat";
|
||||
import dynamic from "next/dynamic";
|
||||
// import { REPO_URL } from "../constant";
|
||||
import { ErrorBoundary } from "./error";
|
||||
import { useDebounce } from "use-debounce";
|
||||
|
||||
export function Loading(props: { noLogo?: boolean }) {
|
||||
return (
|
||||
|
@ -26,7 +26,7 @@ import {
|
||||
import { Avatar } from "./chat";
|
||||
|
||||
import Locale, { AllLangs, changeLang, getLang } from "../locales";
|
||||
import { getCurrentVersion, getEmojiUrl } from "../utils";
|
||||
import { getEmojiUrl } from "../utils";
|
||||
import Link from "next/link";
|
||||
import { UPDATE_URL } from "../constant";
|
||||
import { SearchService, usePromptStore } from "../store/prompt";
|
||||
@ -88,13 +88,13 @@ export function Settings(props: { closeSettings: () => void }) {
|
||||
|
||||
const updateStore = useUpdateStore();
|
||||
const [checkingUpdate, setCheckingUpdate] = useState(false);
|
||||
const currentId = getCurrentVersion();
|
||||
const remoteId = updateStore.remoteId;
|
||||
const hasNewVersion = currentId !== remoteId;
|
||||
const currentVersion = updateStore.version;
|
||||
const remoteId = updateStore.remoteVersion;
|
||||
const hasNewVersion = currentVersion !== remoteId;
|
||||
|
||||
function checkUpdate(force = false) {
|
||||
setCheckingUpdate(true);
|
||||
updateStore.getLatestCommitId(force).then(() => {
|
||||
updateStore.getLatestVersion(force).then(() => {
|
||||
setCheckingUpdate(false);
|
||||
});
|
||||
}
|
||||
@ -224,7 +224,7 @@ export function Settings(props: { closeSettings: () => void }) {
|
||||
</SettingItem>
|
||||
|
||||
{/* <SettingItem
|
||||
title={Locale.Settings.Update.Version(currentId)}
|
||||
title={Locale.Settings.Update.Version(currentVersion ?? "unknown")}
|
||||
subTitle={
|
||||
checkingUpdate
|
||||
? Locale.Settings.Update.IsChecking
|
||||
|
27
app/config/build.ts
Normal file
27
app/config/build.ts
Normal file
@ -0,0 +1,27 @@
|
||||
const COMMIT_ID: string = (() => {
|
||||
try {
|
||||
const childProcess = require("child_process");
|
||||
return (
|
||||
childProcess
|
||||
// .execSync("git describe --tags --abbrev=0")
|
||||
.execSync("git rev-parse --short HEAD")
|
||||
.toString()
|
||||
.trim()
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("[Build Config] No git or not from git repo.");
|
||||
return "unknown";
|
||||
}
|
||||
})();
|
||||
|
||||
export const getBuildConfig = () => {
|
||||
if (typeof process === "undefined") {
|
||||
throw Error(
|
||||
"[Server Config] you are importing a nodejs-only module outside of nodejs",
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
commitId: COMMIT_ID,
|
||||
};
|
||||
};
|
42
app/config/server.ts
Normal file
42
app/config/server.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import md5 from "spark-md5";
|
||||
|
||||
declare global {
|
||||
namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
OPENAI_API_KEY?: string;
|
||||
CODE?: string;
|
||||
PROXY_URL?: string;
|
||||
VERCEL?: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ACCESS_CODES = (function getAccessCodes(): Set<string> {
|
||||
const code = process.env.CODE;
|
||||
|
||||
try {
|
||||
const codes = (code?.split(",") ?? [])
|
||||
.filter((v) => !!v)
|
||||
.map((v) => md5.hash(v.trim()));
|
||||
return new Set(codes);
|
||||
} catch (e) {
|
||||
return new Set();
|
||||
}
|
||||
})();
|
||||
|
||||
export const getServerSideConfig = () => {
|
||||
if (typeof process === "undefined") {
|
||||
throw Error(
|
||||
"[Server Config] you are importing a nodejs-only module outside of nodejs",
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
apiKey: process.env.OPENAI_API_KEY,
|
||||
code: process.env.CODE,
|
||||
codes: ACCESS_CODES,
|
||||
needCode: ACCESS_CODES.size > 0,
|
||||
proxyUrl: process.env.PROXY_URL,
|
||||
isVercel: !!process.env.VERCEL,
|
||||
};
|
||||
};
|
@ -5,3 +5,4 @@ export const ISSUE_URL = `https://github.com/${OWNER}/${REPO}/issues`;
|
||||
export const UPDATE_URL = `${REPO_URL}#keep-updated`;
|
||||
export const FETCH_COMMIT_URL = `https://api.github.com/repos/${OWNER}/${REPO}/commits?per_page=1`;
|
||||
export const FETCH_TAG_URL = `https://api.github.com/repos/${OWNER}/${REPO}/tags?per_page=1`;
|
||||
export const RUNTIME_CONFIG_DOM = "danger-runtime-config";
|
||||
|
@ -2,19 +2,9 @@
|
||||
import "./styles/globals.scss";
|
||||
import "./styles/markdown.scss";
|
||||
import "./styles/highlight.scss";
|
||||
import process from "child_process";
|
||||
import { ACCESS_CODES, IS_IN_DOCKER } from "./api/access";
|
||||
import { getBuildConfig } from "./config/build";
|
||||
|
||||
let COMMIT_ID: string | undefined;
|
||||
try {
|
||||
COMMIT_ID = process
|
||||
// .execSync("git describe --tags --abbrev=0")
|
||||
.execSync("git rev-parse --short HEAD")
|
||||
.toString()
|
||||
.trim();
|
||||
} catch (e) {
|
||||
console.error("No git or not from git repo.");
|
||||
}
|
||||
const buildConfig = getBuildConfig();
|
||||
|
||||
export const metadata = {
|
||||
title: "ChatGPT Next",
|
||||
@ -26,21 +16,6 @@ export const metadata = {
|
||||
themeColor: "#fafafa",
|
||||
};
|
||||
|
||||
function Meta() {
|
||||
const metas = {
|
||||
version: COMMIT_ID ?? "unknown",
|
||||
access: ACCESS_CODES.size > 0 || IS_IN_DOCKER ? "enabled" : "disabled",
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{Object.entries(metas).map(([k, v]) => (
|
||||
<meta name={k} content={v} key={k} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
@ -58,7 +33,7 @@ export default function RootLayout({
|
||||
content="#151515"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
/>
|
||||
<Meta />
|
||||
<meta name="version" content={buildConfig.commitId} />
|
||||
<link rel="manifest" href="/site.webmanifest"></link>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com"></link>
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com"></link>
|
||||
|
@ -73,6 +73,7 @@ const cn = {
|
||||
es: "Español",
|
||||
it: "Italiano",
|
||||
tr: "Türkçe",
|
||||
jp: "日本語",
|
||||
},
|
||||
},
|
||||
Avatar: "头像",
|
||||
|
@ -76,6 +76,7 @@ const en: LocaleType = {
|
||||
es: "Español",
|
||||
it: "Italiano",
|
||||
tr: "Türkçe",
|
||||
jp: "日本語",
|
||||
},
|
||||
},
|
||||
Avatar: "Avatar",
|
||||
|
@ -76,6 +76,7 @@ const es: LocaleType = {
|
||||
es: "Español",
|
||||
it: "Italiano",
|
||||
tr: "Türkçe",
|
||||
jp: "日本語",
|
||||
},
|
||||
},
|
||||
Avatar: "Avatar",
|
||||
|
@ -4,10 +4,11 @@ import TW from "./tw";
|
||||
import ES from "./es";
|
||||
import IT from "./it";
|
||||
import TR from "./tr";
|
||||
import JP from "./jp";
|
||||
|
||||
export type { LocaleType } from "./cn";
|
||||
|
||||
export const AllLangs = ["en", "cn", "tw", "es", "it", "tr"] as const;
|
||||
export const AllLangs = ["en", "cn", "tw", "es", "it", "tr", "jp"] as const;
|
||||
type Lang = (typeof AllLangs)[number];
|
||||
|
||||
const LANG_KEY = "lang";
|
||||
@ -53,6 +54,8 @@ export function getLang(): Lang {
|
||||
return "it";
|
||||
} else if (lang.includes("tr")) {
|
||||
return "tr";
|
||||
} else if (lang.includes("jp")) {
|
||||
return "jp";
|
||||
} else {
|
||||
return "en";
|
||||
}
|
||||
@ -63,4 +66,6 @@ export function changeLang(lang: Lang) {
|
||||
location.reload();
|
||||
}
|
||||
|
||||
export default { en: EN, cn: CN, tw: TW, es: ES, it: IT, tr: TR }[getLang()];
|
||||
export default { en: EN, cn: CN, tw: TW, es: ES, it: IT, tr: TR, jp: JP }[
|
||||
getLang()
|
||||
];
|
||||
|
@ -76,6 +76,7 @@ const it: LocaleType = {
|
||||
es: "Español",
|
||||
it: "Italiano",
|
||||
tr: "Türkçe",
|
||||
jp: "日本語",
|
||||
},
|
||||
},
|
||||
Avatar: "Avatar",
|
||||
|
182
app/locales/jp.ts
Normal file
182
app/locales/jp.ts
Normal file
@ -0,0 +1,182 @@
|
||||
import { SubmitKey } from "../store/app";
|
||||
|
||||
const jp = {
|
||||
WIP: "この機能は開発中です……",
|
||||
Error: {
|
||||
Unauthorized:
|
||||
"現在は未承認状態です。左下の設定ボタンをクリックし、アクセスパスワードを入力してください。",
|
||||
},
|
||||
ChatItem: {
|
||||
ChatItemCount: (count: number) => `${count} 通のチャット`,
|
||||
},
|
||||
Chat: {
|
||||
SubTitle: (count: number) => `ChatGPTとの ${count} 通のチャット`,
|
||||
Actions: {
|
||||
ChatList: "メッセージリストを表示",
|
||||
CompressedHistory: "圧縮された履歴プロンプトを表示",
|
||||
Export: "チャット履歴をエクスポート",
|
||||
Copy: "コピー",
|
||||
Stop: "停止",
|
||||
Retry: "リトライ",
|
||||
},
|
||||
Rename: "チャットの名前を変更",
|
||||
Typing: "入力中…",
|
||||
Input: (submitKey: string) => {
|
||||
var inputHints = `${submitKey} で送信`;
|
||||
if (submitKey === String(SubmitKey.Enter)) {
|
||||
inputHints += ",Shift + Enter で改行";
|
||||
}
|
||||
return inputHints + ",/ で自動補完をトリガー";
|
||||
},
|
||||
Send: "送信",
|
||||
},
|
||||
Export: {
|
||||
Title: "チャット履歴をMarkdown形式でエクスポート",
|
||||
Copy: "すべてコピー",
|
||||
Download: "ファイルをダウンロード",
|
||||
MessageFromYou: "あなたからのメッセージ",
|
||||
MessageFromChatGPT: "ChatGPTからのメッセージ",
|
||||
},
|
||||
Memory: {
|
||||
Title: "履歴メモリ",
|
||||
EmptyContent: "まだ記憶されていません",
|
||||
Send: "メモリを送信",
|
||||
Copy: "メモリをコピー",
|
||||
Reset: "チャットをリセット",
|
||||
ResetConfirm:
|
||||
"リセット後、現在のチャット履歴と過去のメモリがクリアされます。リセットしてもよろしいですか?",
|
||||
},
|
||||
Home: {
|
||||
NewChat: "新しいチャット",
|
||||
DeleteChat: "選択したチャットを削除してもよろしいですか?",
|
||||
DeleteToast: "チャットが削除されました",
|
||||
Revert: "元に戻す",
|
||||
},
|
||||
Settings: {
|
||||
Title: "設定",
|
||||
SubTitle: "設定オプション",
|
||||
Actions: {
|
||||
ClearAll: "すべてのデータをクリア",
|
||||
ResetAll: "すべてのオプションをリセット",
|
||||
Close: "閉じる",
|
||||
ConfirmResetAll: {
|
||||
Confirm: "すべての設定をリセットしてもよろしいですか?",
|
||||
},
|
||||
ConfirmClearAll: {
|
||||
Confirm: "すべてのチャットをリセットしてもよろしいですか?",
|
||||
},
|
||||
},
|
||||
Lang: {
|
||||
Name: "Language",
|
||||
Options: {
|
||||
cn: "简体中文",
|
||||
en: "English",
|
||||
tw: "繁體中文",
|
||||
es: "Español",
|
||||
it: "Italiano",
|
||||
tr: "Türkçe",
|
||||
jp: "日本語",
|
||||
},
|
||||
},
|
||||
Avatar: "アバター",
|
||||
FontSize: {
|
||||
Title: "フォントサイズ",
|
||||
SubTitle: "チャット内容のフォントサイズ",
|
||||
},
|
||||
|
||||
Update: {
|
||||
Version: (x: string) => `現在のバージョン:${x}`,
|
||||
IsLatest: "最新バージョンです",
|
||||
CheckUpdate: "アップデートを確認",
|
||||
IsChecking: "アップデートを確認しています...",
|
||||
FoundUpdate: (x: string) => `新しいバージョンが見つかりました:${x}`,
|
||||
GoToUpdate: "更新する",
|
||||
},
|
||||
SendKey: "送信キー",
|
||||
Theme: "テーマ",
|
||||
TightBorder: "ボーダーレスモード",
|
||||
SendPreviewBubble: "プレビューバブルの送信",
|
||||
Prompt: {
|
||||
Disable: {
|
||||
Title: "プロンプトの自動補完を無効にする",
|
||||
SubTitle:
|
||||
"入力フィールドの先頭に / を入力すると、自動補完がトリガーされます。",
|
||||
},
|
||||
List: "カスタムプロンプトリスト",
|
||||
ListCount: (builtin: number, custom: number) =>
|
||||
`組み込み ${builtin} 件、ユーザー定義 ${custom} 件`,
|
||||
Edit: "編集",
|
||||
},
|
||||
HistoryCount: {
|
||||
Title: "履歴メッセージ数を添付",
|
||||
SubTitle: "リクエストごとに添付する履歴メッセージ数",
|
||||
},
|
||||
CompressThreshold: {
|
||||
Title: "履歴メッセージの長さ圧縮しきい値",
|
||||
SubTitle:
|
||||
"圧縮されていない履歴メッセージがこの値を超えた場合、圧縮が行われます。",
|
||||
},
|
||||
Token: {
|
||||
Title: "APIキー",
|
||||
SubTitle: "自分のキーを使用してパスワードアクセス制限を迂回する",
|
||||
Placeholder: "OpenAI APIキー",
|
||||
},
|
||||
Usage: {
|
||||
Title: "残高照会",
|
||||
SubTitle(used: any, total: any) {
|
||||
return `今月は $${used} を使用しました。総額は $${total} です。`;
|
||||
},
|
||||
IsChecking: "確認中...",
|
||||
Check: "再確認",
|
||||
NoAccess: "APIキーまたはアクセスパスワードを入力して残高を表示",
|
||||
},
|
||||
AccessCode: {
|
||||
Title: "アクセスパスワード",
|
||||
SubTitle: "暗号化アクセスが有効になっています",
|
||||
Placeholder: "アクセスパスワードを入力してください",
|
||||
},
|
||||
Model: "モデル (model)",
|
||||
Temperature: {
|
||||
Title: "ランダム性 (temperature)",
|
||||
SubTitle:
|
||||
"値が大きいほど、回答がランダムになります。1以上の値には文字化けが含まれる可能性があります。",
|
||||
},
|
||||
MaxTokens: {
|
||||
Title: "シングルレスポンス制限 (max_tokens)",
|
||||
SubTitle: "1回のインタラクションで使用される最大トークン数",
|
||||
},
|
||||
PresencePenlty: {
|
||||
Title: "トピックの新鮮度 (presence_penalty)",
|
||||
SubTitle: "値が大きいほど、新しいトピックへの展開が可能になります。",
|
||||
},
|
||||
},
|
||||
Store: {
|
||||
DefaultTopic: "新しいチャット",
|
||||
BotHello: "何かお手伝いできることはありますか",
|
||||
Error: "エラーが発生しました。しばらくしてからやり直してください。",
|
||||
Prompt: {
|
||||
History: (content: string) =>
|
||||
"これは、AI とユーザの過去のチャットを要約した前提となるストーリーです:" +
|
||||
content,
|
||||
Topic:
|
||||
"4~5文字でこの文章の簡潔な主題を返してください。説明、句読点、感嘆詞、余分なテキストは無しで。もし主題がない場合は、「おしゃべり」を返してください",
|
||||
Summarize:
|
||||
"あなたとユーザの会話を簡潔にまとめて、後続のコンテキストプロンプトとして使ってください。200字以内に抑えてください。",
|
||||
},
|
||||
ConfirmClearAll:
|
||||
"すべてのチャット、設定データをクリアしてもよろしいですか?",
|
||||
},
|
||||
Copy: {
|
||||
Success: "クリップボードに書き込みました",
|
||||
Failed: "コピーに失敗しました。クリップボード許可を与えてください。",
|
||||
},
|
||||
Context: {
|
||||
Toast: (x: any) => `前置コンテキストが ${x} 件設定されました`,
|
||||
Edit: "前置コンテキストと履歴メモリ",
|
||||
Add: "新規追加",
|
||||
},
|
||||
};
|
||||
|
||||
export type LocaleType = typeof jp;
|
||||
|
||||
export default jp;
|
@ -76,6 +76,7 @@ const tr: LocaleType = {
|
||||
es: "Español",
|
||||
it: "Italiano",
|
||||
tr: "Türkçe",
|
||||
jp: "日本語",
|
||||
},
|
||||
},
|
||||
Avatar: "Avatar",
|
||||
@ -136,11 +137,13 @@ const tr: LocaleType = {
|
||||
Model: "Model",
|
||||
Temperature: {
|
||||
Title: "Gerçeklik",
|
||||
SubTitle: "Daha büyük bir değer girildiğinde gerçeklik oranı düşer ve daha rastgele çıktılar üretir",
|
||||
SubTitle:
|
||||
"Daha büyük bir değer girildiğinde gerçeklik oranı düşer ve daha rastgele çıktılar üretir",
|
||||
},
|
||||
MaxTokens: {
|
||||
Title: "Maksimum Belirteç",
|
||||
SubTitle: "Girdi belirteçlerinin ve oluşturulan belirteçlerin maksimum uzunluğu",
|
||||
SubTitle:
|
||||
"Girdi belirteçlerinin ve oluşturulan belirteçlerin maksimum uzunluğu",
|
||||
},
|
||||
PresencePenlty: {
|
||||
Title: "Varlık Cezası",
|
||||
@ -161,7 +164,8 @@ const tr: LocaleType = {
|
||||
Summarize:
|
||||
"Gelecekteki bağlam için bir bilgi istemi olarak kullanmak üzere tartışmamızı en fazla 200 kelimeyle özetleyin.",
|
||||
},
|
||||
ConfirmClearAll: "Tüm sohbet ve ayar verilerini temizlemeyi onaylıyor musunuz?",
|
||||
ConfirmClearAll:
|
||||
"Tüm sohbet ve ayar verilerini temizlemeyi onaylıyor musunuz?",
|
||||
},
|
||||
Copy: {
|
||||
Success: "Panoya kopyalandı",
|
||||
|
@ -74,6 +74,7 @@ const tw: LocaleType = {
|
||||
es: "Español",
|
||||
it: "Italiano",
|
||||
tr: "Türkçe",
|
||||
jp: "日本語",
|
||||
},
|
||||
},
|
||||
Avatar: "大頭貼",
|
||||
|
@ -2,11 +2,15 @@ import { Analytics } from "@vercel/analytics/react";
|
||||
|
||||
import { Home } from "./components/home";
|
||||
|
||||
export default function App() {
|
||||
import { getServerSideConfig } from "./config/server";
|
||||
|
||||
const serverConfig = getServerSideConfig();
|
||||
|
||||
export default async function App() {
|
||||
return (
|
||||
<>
|
||||
<Home />
|
||||
<Analytics />
|
||||
{serverConfig?.isVercel && <Analytics />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { ChatRequest, ChatReponse } from "./api/openai/typing";
|
||||
import type { ChatRequest, ChatResponse } from "./api/openai/typing";
|
||||
import { Message, ModelConfig, useAccessStore, useChatStore } from "./store";
|
||||
import { showToast } from "./components/ui-lib";
|
||||
|
||||
@ -67,7 +67,7 @@ export async function requestChat(messages: Message[]) {
|
||||
const res = await requestOpenaiClient("v1/chat/completions")(req);
|
||||
|
||||
try {
|
||||
const response = (await res.json()) as ChatReponse;
|
||||
const response = (await res.json()) as ChatResponse;
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("[Request Chat] ", error, res.body);
|
||||
@ -171,7 +171,7 @@ export async function requestChatStream(
|
||||
const resTimeoutId = setTimeout(() => finish(), TIME_OUT_MS);
|
||||
const content = await reader?.read();
|
||||
clearTimeout(resTimeoutId);
|
||||
const text = decoder.decode(content?.value);
|
||||
const text = decoder.decode(content?.value, { stream: true });
|
||||
responseText += text;
|
||||
|
||||
const done = !content || content.done;
|
||||
|
@ -1,26 +1,33 @@
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
import { queryMeta } from "../utils";
|
||||
|
||||
export interface AccessControlStore {
|
||||
accessCode: string;
|
||||
token: string;
|
||||
|
||||
needCode: boolean;
|
||||
|
||||
updateToken: (_: string) => void;
|
||||
updateCode: (_: string) => void;
|
||||
enabledAccessControl: () => boolean;
|
||||
isAuthorized: () => boolean;
|
||||
fetch: () => void;
|
||||
}
|
||||
|
||||
export const ACCESS_KEY = "access-control";
|
||||
|
||||
let fetchState = 0; // 0 not fetch, 1 fetching, 2 done
|
||||
|
||||
export const useAccessStore = create<AccessControlStore>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
token: "",
|
||||
accessCode: "",
|
||||
needCode: true,
|
||||
enabledAccessControl() {
|
||||
return queryMeta("access") === "enabled";
|
||||
get().fetch();
|
||||
|
||||
return get().needCode;
|
||||
},
|
||||
updateCode(code: string) {
|
||||
set((state) => ({ accessCode: code }));
|
||||
@ -30,7 +37,28 @@ export const useAccessStore = create<AccessControlStore>()(
|
||||
},
|
||||
isAuthorized() {
|
||||
// has token or has code or disabled access control
|
||||
return !!get().token || !!get().accessCode || !get().enabledAccessControl();
|
||||
return (
|
||||
!!get().token || !!get().accessCode || !get().enabledAccessControl()
|
||||
);
|
||||
},
|
||||
fetch() {
|
||||
if (fetchState > 0) return;
|
||||
fetchState = 1;
|
||||
fetch("/api/config", {
|
||||
method: "post",
|
||||
body: null,
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res: DangerConfig) => {
|
||||
console.log("[Config] got config from server", res);
|
||||
set(() => ({ ...res }));
|
||||
})
|
||||
.catch(() => {
|
||||
console.error("[Config] failed to fetch config");
|
||||
})
|
||||
.finally(() => {
|
||||
fetchState = 2;
|
||||
});
|
||||
},
|
||||
}),
|
||||
{
|
||||
|
@ -1,28 +1,46 @@
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
import { FETCH_COMMIT_URL, FETCH_TAG_URL } from "../constant";
|
||||
import { getCurrentVersion } from "../utils";
|
||||
|
||||
export interface UpdateStore {
|
||||
lastUpdate: number;
|
||||
remoteId: string;
|
||||
remoteVersion: string;
|
||||
|
||||
getLatestCommitId: (force: boolean) => Promise<string>;
|
||||
version: string;
|
||||
getLatestVersion: (force: boolean) => Promise<string>;
|
||||
}
|
||||
|
||||
export const UPDATE_KEY = "chat-update";
|
||||
|
||||
function queryMeta(key: string, defaultValue?: string): string {
|
||||
let ret: string;
|
||||
if (document) {
|
||||
const meta = document.head.querySelector(
|
||||
`meta[name='${key}']`,
|
||||
) as HTMLMetaElement;
|
||||
ret = meta?.content ?? "";
|
||||
} else {
|
||||
ret = defaultValue ?? "";
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
export const useUpdateStore = create<UpdateStore>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
lastUpdate: 0,
|
||||
remoteId: "",
|
||||
remoteVersion: "",
|
||||
|
||||
version: "unknown",
|
||||
|
||||
async getLatestVersion(force = false) {
|
||||
set(() => ({ version: queryMeta("version") }));
|
||||
|
||||
async getLatestCommitId(force = false) {
|
||||
const overTenMins = Date.now() - get().lastUpdate > 10 * 60 * 1000;
|
||||
const shouldFetch = force || overTenMins;
|
||||
if (!shouldFetch) {
|
||||
return getCurrentVersion();
|
||||
return get().version ?? "unknown";
|
||||
}
|
||||
|
||||
try {
|
||||
@ -32,13 +50,13 @@ export const useUpdateStore = create<UpdateStore>()(
|
||||
const remoteId = (data[0].sha as string).substring(0, 7);
|
||||
set(() => ({
|
||||
lastUpdate: Date.now(),
|
||||
remoteId,
|
||||
remoteVersion: remoteId,
|
||||
}));
|
||||
console.log("[Got Upstream] ", remoteId);
|
||||
return remoteId;
|
||||
} catch (error) {
|
||||
console.error("[Fetch Upstream Commit Id]", error);
|
||||
return getCurrentVersion();
|
||||
return get().version ?? "";
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
25
app/utils.ts
25
app/utils.ts
@ -69,31 +69,6 @@ export function selectOrCopy(el: HTMLElement, content: string) {
|
||||
return true;
|
||||
}
|
||||
|
||||
export function queryMeta(key: string, defaultValue?: string): string {
|
||||
let ret: string;
|
||||
if (document) {
|
||||
const meta = document.head.querySelector(
|
||||
`meta[name='${key}']`,
|
||||
) as HTMLMetaElement;
|
||||
ret = meta?.content ?? "";
|
||||
} else {
|
||||
ret = defaultValue ?? "";
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
let currentId: string;
|
||||
export function getCurrentVersion() {
|
||||
if (currentId) {
|
||||
return currentId;
|
||||
}
|
||||
|
||||
currentId = queryMeta("version");
|
||||
|
||||
return currentId;
|
||||
}
|
||||
|
||||
export function getEmojiUrl(unified: string, style: EmojiStyle) {
|
||||
return `https://cdn.staticfile.org/emoji-datasource-apple/14.0.0/img/${style}/64/${unified}.png`;
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
# 常见问题
|
||||
|
||||
> We are sorry that there is currently no English version of the FAQ. English users can use translation tools to access this document. We look forward to receiving your PR for an English version of the documentation.
|
||||
|
||||
## 如何快速获得帮助?
|
||||
1. 询问ChatGPT / Bing / 百度 / Google等。
|
||||
2. 询问网友。请提供问题的背景信息和碰到问题的详细描述。高质量的提问容易获得有用的答案。
|
||||
@ -121,8 +119,9 @@ OpenAI只接受指定地区的信用卡(中国信用卡无法使用)。一
|
||||
3. 网上找人代充
|
||||
|
||||
## 如何使用GPT-4的API访问?
|
||||
(4月6日更新)GPT-4的API访问需要单独申请。到以下地址填写你的信息进入申请队列waitlist(准备好你的OpenAI组织ID):https://openai.com/waitlist/gpt-4-api
|
||||
- GPT-4的API访问需要单独申请。到以下地址填写你的信息进入申请队列waitlist(准备好你的OpenAI组织ID):https://openai.com/waitlist/gpt-4-api
|
||||
之后等待邮件消息。
|
||||
- 开通 ChatGPT Plus 不代表有 GPT-4 权限,两者毫无关系。
|
||||
|
||||
## 如何使用 Azure OpenAI 接口
|
||||
请参考:[#371](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/371)
|
||||
|
@ -1,21 +1,23 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { ACCESS_CODES } from "./app/api/access";
|
||||
import { getServerSideConfig } from "./app/config/server";
|
||||
import md5 from "spark-md5";
|
||||
|
||||
export const config = {
|
||||
matcher: ["/api/openai", "/api/chat-stream"],
|
||||
};
|
||||
|
||||
const serverConfig = getServerSideConfig();
|
||||
|
||||
export function middleware(req: NextRequest) {
|
||||
const accessCode = req.headers.get("access-code");
|
||||
const token = req.headers.get("token");
|
||||
const hashedCode = md5.hash(accessCode ?? "").trim();
|
||||
|
||||
console.log("[Auth] allowed hashed codes: ", [...ACCESS_CODES]);
|
||||
console.log("[Auth] allowed hashed codes: ", [...serverConfig.codes]);
|
||||
console.log("[Auth] got access code:", accessCode);
|
||||
console.log("[Auth] hashed access code:", hashedCode);
|
||||
|
||||
if (ACCESS_CODES.size > 0 && !ACCESS_CODES.has(hashedCode) && !token) {
|
||||
if (serverConfig.needCode && !serverConfig.codes.has(hashedCode) && !token) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: true,
|
||||
@ -30,7 +32,7 @@ export function middleware(req: NextRequest) {
|
||||
|
||||
// inject api key
|
||||
if (!token) {
|
||||
const apiKey = process.env.OPENAI_API_KEY;
|
||||
const apiKey = serverConfig.apiKey;
|
||||
if (apiKey) {
|
||||
console.log("[Auth] set system token");
|
||||
req.headers.set("token", apiKey);
|
||||
|
@ -8,14 +8,11 @@ const nextConfig = {
|
||||
config.module.rules.push({
|
||||
test: /\.svg$/,
|
||||
use: ["@svgr/webpack"],
|
||||
}); // 针对 SVG 的处理规则
|
||||
});
|
||||
|
||||
return config;
|
||||
}
|
||||
},
|
||||
output: "standalone",
|
||||
};
|
||||
|
||||
if (process.env.DOCKER) {
|
||||
nextConfig.output = 'standalone'
|
||||
}
|
||||
|
||||
module.exports = nextConfig;
|
||||
|
Loading…
Reference in New Issue
Block a user