mirror of
https://github.com/Yidadaa/ChatGPT-Next-Web.git
synced 2025-09-02 05:13:06 +08:00
Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
0f739f442e | ||
|
6841846613 | ||
|
d6e6dd09f0 | ||
|
9b61cb1335 | ||
|
7aee53ea05 | ||
|
8df8ee8936 | ||
|
ec985f6a1d | ||
|
2ec99bbb70 | ||
|
d7edcadec7 | ||
|
150735b001 |
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
|||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
@@ -17,7 +17,6 @@ RUN apk update && apk add --no-cache git
|
|||||||
|
|
||||||
ENV OPENAI_API_KEY=""
|
ENV OPENAI_API_KEY=""
|
||||||
ENV CODE=""
|
ENV CODE=""
|
||||||
ARG DOCKER=true
|
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=deps /app/node_modules ./node_modules
|
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); \
|
host=$(echo $PROXY_URL | cut -d/ -f3 | cut -d: -f1); \
|
||||||
port=$(echo $PROXY_URL | cut -d: -f3); \
|
port=$(echo $PROXY_URL | cut -d: -f3); \
|
||||||
conf=/etc/proxychains.conf; \
|
conf=/etc/proxychains.conf; \
|
||||||
echo "strict_chain" >> $conf; \
|
echo "strict_chain" > $conf; \
|
||||||
echo "proxy_dns" >> $conf; \
|
echo "proxy_dns" >> $conf; \
|
||||||
echo "remote_dns_subnet 224" >> $conf; \
|
echo "remote_dns_subnet 224" >> $conf; \
|
||||||
echo "tcp_read_time_out 15000" >> $conf; \
|
echo "tcp_read_time_out 15000" >> $conf; \
|
||||||
|
@@ -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);
|
const parser = createParser(onParse);
|
||||||
for await (const chunk of res.body as any) {
|
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,
|
||||||
|
});
|
||||||
|
}
|
@@ -2,13 +2,7 @@
|
|||||||
|
|
||||||
require("../polyfill");
|
require("../polyfill");
|
||||||
|
|
||||||
import {
|
import { useState, useEffect, useRef } from "react";
|
||||||
useState,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useCallback,
|
|
||||||
MouseEventHandler,
|
|
||||||
} from "react";
|
|
||||||
|
|
||||||
import { IconButton } from "./button";
|
import { IconButton } from "./button";
|
||||||
import styles from "./home.module.scss";
|
import styles from "./home.module.scss";
|
||||||
@@ -30,7 +24,6 @@ import { Chat } from "./chat";
|
|||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { REPO_URL } from "../constant";
|
import { REPO_URL } from "../constant";
|
||||||
import { ErrorBoundary } from "./error";
|
import { ErrorBoundary } from "./error";
|
||||||
import { useDebounce } from "use-debounce";
|
|
||||||
|
|
||||||
export function Loading(props: { noLogo?: boolean }) {
|
export function Loading(props: { noLogo?: boolean }) {
|
||||||
return (
|
return (
|
||||||
|
@@ -26,7 +26,7 @@ import {
|
|||||||
import { Avatar } from "./chat";
|
import { Avatar } from "./chat";
|
||||||
|
|
||||||
import Locale, { AllLangs, changeLang, getLang } from "../locales";
|
import Locale, { AllLangs, changeLang, getLang } from "../locales";
|
||||||
import { getCurrentVersion, getEmojiUrl } from "../utils";
|
import { getEmojiUrl } from "../utils";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { UPDATE_URL } from "../constant";
|
import { UPDATE_URL } from "../constant";
|
||||||
import { SearchService, usePromptStore } from "../store/prompt";
|
import { SearchService, usePromptStore } from "../store/prompt";
|
||||||
@@ -88,13 +88,13 @@ export function Settings(props: { closeSettings: () => void }) {
|
|||||||
|
|
||||||
const updateStore = useUpdateStore();
|
const updateStore = useUpdateStore();
|
||||||
const [checkingUpdate, setCheckingUpdate] = useState(false);
|
const [checkingUpdate, setCheckingUpdate] = useState(false);
|
||||||
const currentId = getCurrentVersion();
|
const currentVersion = updateStore.version;
|
||||||
const remoteId = updateStore.remoteId;
|
const remoteId = updateStore.remoteVersion;
|
||||||
const hasNewVersion = currentId !== remoteId;
|
const hasNewVersion = currentVersion !== remoteId;
|
||||||
|
|
||||||
function checkUpdate(force = false) {
|
function checkUpdate(force = false) {
|
||||||
setCheckingUpdate(true);
|
setCheckingUpdate(true);
|
||||||
updateStore.getLatestCommitId(force).then(() => {
|
updateStore.getLatestVersion(force).then(() => {
|
||||||
setCheckingUpdate(false);
|
setCheckingUpdate(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -224,7 +224,7 @@ export function Settings(props: { closeSettings: () => void }) {
|
|||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
title={Locale.Settings.Update.Version(currentId)}
|
title={Locale.Settings.Update.Version(currentVersion ?? "unknown")}
|
||||||
subTitle={
|
subTitle={
|
||||||
checkingUpdate
|
checkingUpdate
|
||||||
? Locale.Settings.Update.IsChecking
|
? 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 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_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 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/globals.scss";
|
||||||
import "./styles/markdown.scss";
|
import "./styles/markdown.scss";
|
||||||
import "./styles/highlight.scss";
|
import "./styles/highlight.scss";
|
||||||
import process from "child_process";
|
import { getBuildConfig } from "./config/build";
|
||||||
import { ACCESS_CODES, IS_IN_DOCKER } from "./api/access";
|
|
||||||
|
|
||||||
let COMMIT_ID: string | undefined;
|
const buildConfig = getBuildConfig();
|
||||||
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.");
|
|
||||||
}
|
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: "ChatGPT Next Web",
|
title: "ChatGPT Next Web",
|
||||||
@@ -26,21 +16,6 @@ export const metadata = {
|
|||||||
themeColor: "#fafafa",
|
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({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
@@ -58,7 +33,7 @@ export default function RootLayout({
|
|||||||
content="#151515"
|
content="#151515"
|
||||||
media="(prefers-color-scheme: dark)"
|
media="(prefers-color-scheme: dark)"
|
||||||
/>
|
/>
|
||||||
<Meta />
|
<meta name="version" content={buildConfig.commitId} />
|
||||||
<link rel="manifest" href="/site.webmanifest"></link>
|
<link rel="manifest" href="/site.webmanifest"></link>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com"></link>
|
<link rel="preconnect" href="https://fonts.googleapis.com"></link>
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com"></link>
|
<link rel="preconnect" href="https://fonts.gstatic.com"></link>
|
||||||
|
@@ -72,6 +72,7 @@ const cn = {
|
|||||||
tw: "繁體中文",
|
tw: "繁體中文",
|
||||||
es: "Español",
|
es: "Español",
|
||||||
it: "Italiano",
|
it: "Italiano",
|
||||||
|
tr: "Türkçe",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "头像",
|
Avatar: "头像",
|
||||||
|
@@ -75,6 +75,7 @@ const en: LocaleType = {
|
|||||||
tw: "繁體中文",
|
tw: "繁體中文",
|
||||||
es: "Español",
|
es: "Español",
|
||||||
it: "Italiano",
|
it: "Italiano",
|
||||||
|
tr: "Türkçe",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "Avatar",
|
Avatar: "Avatar",
|
||||||
|
@@ -75,6 +75,7 @@ const es: LocaleType = {
|
|||||||
tw: "繁體中文",
|
tw: "繁體中文",
|
||||||
es: "Español",
|
es: "Español",
|
||||||
it: "Italiano",
|
it: "Italiano",
|
||||||
|
tr: "Türkçe",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "Avatar",
|
Avatar: "Avatar",
|
||||||
|
@@ -3,10 +3,11 @@ import EN from "./en";
|
|||||||
import TW from "./tw";
|
import TW from "./tw";
|
||||||
import ES from "./es";
|
import ES from "./es";
|
||||||
import IT from "./it";
|
import IT from "./it";
|
||||||
|
import TR from "./tr";
|
||||||
|
|
||||||
export type { LocaleType } from "./cn";
|
export type { LocaleType } from "./cn";
|
||||||
|
|
||||||
export const AllLangs = ["en", "cn", "tw", "es", "it"] as const;
|
export const AllLangs = ["en", "cn", "tw", "es", "it", "tr"] as const;
|
||||||
type Lang = (typeof AllLangs)[number];
|
type Lang = (typeof AllLangs)[number];
|
||||||
|
|
||||||
const LANG_KEY = "lang";
|
const LANG_KEY = "lang";
|
||||||
@@ -50,6 +51,8 @@ export function getLang(): Lang {
|
|||||||
return "es";
|
return "es";
|
||||||
} else if (lang.includes("it")) {
|
} else if (lang.includes("it")) {
|
||||||
return "it";
|
return "it";
|
||||||
|
} else if (lang.includes("tr")) {
|
||||||
|
return "tr";
|
||||||
} else {
|
} else {
|
||||||
return "en";
|
return "en";
|
||||||
}
|
}
|
||||||
@@ -60,4 +63,4 @@ export function changeLang(lang: Lang) {
|
|||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { en: EN, cn: CN, tw: TW, es: ES, it: IT }[getLang()];
|
export default { en: EN, cn: CN, tw: TW, es: ES, it: IT, tr: TR }[getLang()];
|
||||||
|
@@ -75,6 +75,7 @@ const it: LocaleType = {
|
|||||||
tw: "繁體中文",
|
tw: "繁體中文",
|
||||||
es: "Español",
|
es: "Español",
|
||||||
it: "Italiano",
|
it: "Italiano",
|
||||||
|
tr: "Türkçe",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "Avatar",
|
Avatar: "Avatar",
|
||||||
|
177
app/locales/tr.ts
Normal file
177
app/locales/tr.ts
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
import { SubmitKey } from "../store/app";
|
||||||
|
import type { LocaleType } from "./index";
|
||||||
|
|
||||||
|
const tr: LocaleType = {
|
||||||
|
WIP: "Çalışma devam ediyor...",
|
||||||
|
Error: {
|
||||||
|
Unauthorized:
|
||||||
|
"Yetkisiz erişim, lütfen erişim kodunu ayarlar sayfasından giriniz.",
|
||||||
|
},
|
||||||
|
ChatItem: {
|
||||||
|
ChatItemCount: (count: number) => `${count} mesaj`,
|
||||||
|
},
|
||||||
|
Chat: {
|
||||||
|
SubTitle: (count: number) => `ChatGPT tarafından ${count} mesaj`,
|
||||||
|
Actions: {
|
||||||
|
ChatList: "Sohbet Listesine Git",
|
||||||
|
CompressedHistory: "Sıkıştırılmış Geçmiş Bellek Komutu",
|
||||||
|
Export: "Tüm Mesajları Markdown Olarak Dışa Aktar",
|
||||||
|
Copy: "Kopyala",
|
||||||
|
Stop: "Durdur",
|
||||||
|
Retry: "Tekrar Dene",
|
||||||
|
},
|
||||||
|
Rename: "Sohbeti Yeniden Adlandır",
|
||||||
|
Typing: "Yazıyor…",
|
||||||
|
Input: (submitKey: string) => {
|
||||||
|
var inputHints = `Göndermek için ${submitKey}`;
|
||||||
|
if (submitKey === String(SubmitKey.Enter)) {
|
||||||
|
inputHints += ", kaydırmak için Shift + Enter";
|
||||||
|
}
|
||||||
|
return inputHints + ", komutları aramak için / (eğik çizgi)";
|
||||||
|
},
|
||||||
|
Send: "Gönder",
|
||||||
|
},
|
||||||
|
Export: {
|
||||||
|
Title: "Tüm Mesajlar",
|
||||||
|
Copy: "Tümünü Kopyala",
|
||||||
|
Download: "İndir",
|
||||||
|
MessageFromYou: "Sizin Mesajınız",
|
||||||
|
MessageFromChatGPT: "ChatGPT'nin Mesajı",
|
||||||
|
},
|
||||||
|
Memory: {
|
||||||
|
Title: "Bellek Komutları",
|
||||||
|
EmptyContent: "Henüz değil.",
|
||||||
|
Send: "Belleği Gönder",
|
||||||
|
Copy: "Belleği Kopyala",
|
||||||
|
Reset: "Oturumu Sıfırla",
|
||||||
|
ResetConfirm:
|
||||||
|
"Sıfırlama, geçerli görüşme geçmişini ve geçmiş belleği siler. Sıfırlamak istediğinizden emin misiniz?",
|
||||||
|
},
|
||||||
|
Home: {
|
||||||
|
NewChat: "Yeni Sohbet",
|
||||||
|
DeleteChat: "Seçili sohbeti silmeyi onaylıyor musunuz?",
|
||||||
|
DeleteToast: "Sohbet Silindi",
|
||||||
|
Revert: "Geri Al",
|
||||||
|
},
|
||||||
|
Settings: {
|
||||||
|
Title: "Ayarlar",
|
||||||
|
SubTitle: "Tüm Ayarlar",
|
||||||
|
Actions: {
|
||||||
|
ClearAll: "Tüm Verileri Temizle",
|
||||||
|
ResetAll: "Tüm Ayarları Sıfırla",
|
||||||
|
Close: "Kapat",
|
||||||
|
ConfirmResetAll: {
|
||||||
|
Confirm: "Tüm ayarları sıfırlamak istediğinizden emin misiniz?",
|
||||||
|
},
|
||||||
|
ConfirmClearAll: {
|
||||||
|
Confirm: "Tüm sohbeti sıfırlamak istediğinizden emin misiniz?",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Lang: {
|
||||||
|
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
||||||
|
Options: {
|
||||||
|
cn: "简体中文",
|
||||||
|
en: "English",
|
||||||
|
tw: "繁體中文",
|
||||||
|
es: "Español",
|
||||||
|
it: "Italiano",
|
||||||
|
tr: "Türkçe",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Avatar: "Avatar",
|
||||||
|
FontSize: {
|
||||||
|
Title: "Yazı Boyutu",
|
||||||
|
SubTitle: "Sohbet içeriğinin yazı boyutunu ayarlayın",
|
||||||
|
},
|
||||||
|
Update: {
|
||||||
|
Version: (x: string) => `Sürüm: ${x}`,
|
||||||
|
IsLatest: "En son sürüm",
|
||||||
|
CheckUpdate: "Güncellemeyi Kontrol Et",
|
||||||
|
IsChecking: "Güncelleme kontrol ediliyor...",
|
||||||
|
FoundUpdate: (x: string) => `Yeni sürüm bulundu: ${x}`,
|
||||||
|
GoToUpdate: "Güncelle",
|
||||||
|
},
|
||||||
|
SendKey: "Gönder Tuşu",
|
||||||
|
Theme: "Tema",
|
||||||
|
TightBorder: "Tam Ekran",
|
||||||
|
SendPreviewBubble: "Mesaj Önizleme Balonu",
|
||||||
|
Prompt: {
|
||||||
|
Disable: {
|
||||||
|
Title: "Otomatik tamamlamayı devre dışı bırak",
|
||||||
|
SubTitle: "Otomatik tamamlamayı kullanmak için / (eğik çizgi) girin",
|
||||||
|
},
|
||||||
|
List: "Komut Listesi",
|
||||||
|
ListCount: (builtin: number, custom: number) =>
|
||||||
|
`${builtin} yerleşik, ${custom} kullanıcı tanımlı`,
|
||||||
|
Edit: "Düzenle",
|
||||||
|
},
|
||||||
|
HistoryCount: {
|
||||||
|
Title: "Ekli Mesaj Sayısı",
|
||||||
|
SubTitle: "İstek başına ekli gönderilen mesaj sayısı",
|
||||||
|
},
|
||||||
|
CompressThreshold: {
|
||||||
|
Title: "Geçmiş Sıkıştırma Eşiği",
|
||||||
|
SubTitle:
|
||||||
|
"Sıkıştırılmamış mesajların uzunluğu bu değeri aşarsa sıkıştırılır",
|
||||||
|
},
|
||||||
|
Token: {
|
||||||
|
Title: "API Anahtarı",
|
||||||
|
SubTitle: "Erişim kodu sınırını yoksaymak için anahtarınızı kullanın",
|
||||||
|
Placeholder: "OpenAI API Anahtarı",
|
||||||
|
},
|
||||||
|
Usage: {
|
||||||
|
Title: "Hesap Bakiyesi",
|
||||||
|
SubTitle(used: any, total: any) {
|
||||||
|
return `Bu ay kullanılan $${used}, abonelik $${total}`;
|
||||||
|
},
|
||||||
|
IsChecking: "Kontrol ediliyor...",
|
||||||
|
Check: "Tekrar Kontrol Et",
|
||||||
|
NoAccess: "Bakiyeyi kontrol etmek için API anahtarını girin",
|
||||||
|
},
|
||||||
|
AccessCode: {
|
||||||
|
Title: "Erişim Kodu",
|
||||||
|
SubTitle: "Erişim kontrolü etkinleştirme",
|
||||||
|
Placeholder: "Erişim Kodu Gerekiyor",
|
||||||
|
},
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
MaxTokens: {
|
||||||
|
Title: "Maksimum Belirteç",
|
||||||
|
SubTitle: "Girdi belirteçlerinin ve oluşturulan belirteçlerin maksimum uzunluğu",
|
||||||
|
},
|
||||||
|
PresencePenlty: {
|
||||||
|
Title: "Varlık Cezası",
|
||||||
|
SubTitle:
|
||||||
|
"Daha büyük bir değer, yeni konular hakkında konuşma olasılığını artırır",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Store: {
|
||||||
|
DefaultTopic: "Yeni Konuşma",
|
||||||
|
BotHello: "Merhaba! Size bugün nasıl yardımcı olabilirim?",
|
||||||
|
Error: "Bir şeyler yanlış gitti. Lütfen daha sonra tekrar deneyiniz.",
|
||||||
|
Prompt: {
|
||||||
|
History: (content: string) =>
|
||||||
|
"Bu, yapay zeka ile kullanıcı arasındaki sohbet geçmişinin bir özetidir: " +
|
||||||
|
content,
|
||||||
|
Topic:
|
||||||
|
"Lütfen herhangi bir giriş, noktalama işareti, tırnak işareti, nokta, sembol veya ek metin olmadan konuşmamızı özetleyen dört ila beş kelimelik bir başlık oluşturun. Çevreleyen tırnak işaretlerini kaldırın.",
|
||||||
|
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?",
|
||||||
|
},
|
||||||
|
Copy: {
|
||||||
|
Success: "Panoya kopyalandı",
|
||||||
|
Failed: "Kopyalama başarısız oldu, lütfen panoya erişim izni verin",
|
||||||
|
},
|
||||||
|
Context: {
|
||||||
|
Toast: (x: any) => `${x} bağlamsal bellek komutu`,
|
||||||
|
Edit: "Bağlamsal ve Bellek Komutları",
|
||||||
|
Add: "Yeni Ekle",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default tr;
|
@@ -73,6 +73,7 @@ const tw: LocaleType = {
|
|||||||
tw: "繁體中文",
|
tw: "繁體中文",
|
||||||
es: "Español",
|
es: "Español",
|
||||||
it: "Italiano",
|
it: "Italiano",
|
||||||
|
tr: "Türkçe",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Avatar: "大頭貼",
|
Avatar: "大頭貼",
|
||||||
|
@@ -2,11 +2,15 @@ import { Analytics } from "@vercel/analytics/react";
|
|||||||
|
|
||||||
import { Home } from "./components/home";
|
import { Home } from "./components/home";
|
||||||
|
|
||||||
export default function App() {
|
import { getServerSideConfig } from "./config/server";
|
||||||
|
|
||||||
|
const serverConfig = getServerSideConfig();
|
||||||
|
|
||||||
|
export default async function App() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Home />
|
<Home />
|
||||||
<Analytics />
|
{serverConfig?.isVercel && <Analytics />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -171,7 +171,7 @@ export async function requestChatStream(
|
|||||||
const resTimeoutId = setTimeout(() => finish(), TIME_OUT_MS);
|
const resTimeoutId = setTimeout(() => finish(), TIME_OUT_MS);
|
||||||
const content = await reader?.read();
|
const content = await reader?.read();
|
||||||
clearTimeout(resTimeoutId);
|
clearTimeout(resTimeoutId);
|
||||||
const text = decoder.decode(content?.value);
|
const text = decoder.decode(content?.value, { stream: true });
|
||||||
responseText += text;
|
responseText += text;
|
||||||
|
|
||||||
const done = !content || content.done;
|
const done = !content || content.done;
|
||||||
|
@@ -1,26 +1,33 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { persist } from "zustand/middleware";
|
import { persist } from "zustand/middleware";
|
||||||
import { queryMeta } from "../utils";
|
|
||||||
|
|
||||||
export interface AccessControlStore {
|
export interface AccessControlStore {
|
||||||
accessCode: string;
|
accessCode: string;
|
||||||
token: string;
|
token: string;
|
||||||
|
|
||||||
|
needCode: boolean;
|
||||||
|
|
||||||
updateToken: (_: string) => void;
|
updateToken: (_: string) => void;
|
||||||
updateCode: (_: string) => void;
|
updateCode: (_: string) => void;
|
||||||
enabledAccessControl: () => boolean;
|
enabledAccessControl: () => boolean;
|
||||||
isAuthorized: () => boolean;
|
isAuthorized: () => boolean;
|
||||||
|
fetch: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ACCESS_KEY = "access-control";
|
export const ACCESS_KEY = "access-control";
|
||||||
|
|
||||||
|
let fetchState = 0; // 0 not fetch, 1 fetching, 2 done
|
||||||
|
|
||||||
export const useAccessStore = create<AccessControlStore>()(
|
export const useAccessStore = create<AccessControlStore>()(
|
||||||
persist(
|
persist(
|
||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
token: "",
|
token: "",
|
||||||
accessCode: "",
|
accessCode: "",
|
||||||
|
needCode: true,
|
||||||
enabledAccessControl() {
|
enabledAccessControl() {
|
||||||
return queryMeta("access") === "enabled";
|
get().fetch();
|
||||||
|
|
||||||
|
return get().needCode;
|
||||||
},
|
},
|
||||||
updateCode(code: string) {
|
updateCode(code: string) {
|
||||||
set((state) => ({ accessCode: code }));
|
set((state) => ({ accessCode: code }));
|
||||||
@@ -29,7 +36,29 @@ export const useAccessStore = create<AccessControlStore>()(
|
|||||||
set((state) => ({ token }));
|
set((state) => ({ token }));
|
||||||
},
|
},
|
||||||
isAuthorized() {
|
isAuthorized() {
|
||||||
return !!get().token || !!get().accessCode;
|
// has token or has code or disabled access control
|
||||||
|
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 { create } from "zustand";
|
||||||
import { persist } from "zustand/middleware";
|
import { persist } from "zustand/middleware";
|
||||||
import { FETCH_COMMIT_URL, FETCH_TAG_URL } from "../constant";
|
import { FETCH_COMMIT_URL, FETCH_TAG_URL } from "../constant";
|
||||||
import { getCurrentVersion } from "../utils";
|
|
||||||
|
|
||||||
export interface UpdateStore {
|
export interface UpdateStore {
|
||||||
lastUpdate: number;
|
lastUpdate: number;
|
||||||
remoteId: string;
|
remoteVersion: string;
|
||||||
|
|
||||||
getLatestCommitId: (force: boolean) => Promise<string>;
|
version: string;
|
||||||
|
getLatestVersion: (force: boolean) => Promise<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UPDATE_KEY = "chat-update";
|
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>()(
|
export const useUpdateStore = create<UpdateStore>()(
|
||||||
persist(
|
persist(
|
||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
lastUpdate: 0,
|
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 overTenMins = Date.now() - get().lastUpdate > 10 * 60 * 1000;
|
||||||
const shouldFetch = force || overTenMins;
|
const shouldFetch = force || overTenMins;
|
||||||
if (!shouldFetch) {
|
if (!shouldFetch) {
|
||||||
return getCurrentVersion();
|
return get().version ?? "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -32,13 +50,13 @@ export const useUpdateStore = create<UpdateStore>()(
|
|||||||
const remoteId = (data[0].sha as string).substring(0, 7);
|
const remoteId = (data[0].sha as string).substring(0, 7);
|
||||||
set(() => ({
|
set(() => ({
|
||||||
lastUpdate: Date.now(),
|
lastUpdate: Date.now(),
|
||||||
remoteId,
|
remoteVersion: remoteId,
|
||||||
}));
|
}));
|
||||||
console.log("[Got Upstream] ", remoteId);
|
console.log("[Got Upstream] ", remoteId);
|
||||||
return remoteId;
|
return remoteId;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[Fetch Upstream Commit Id]", 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;
|
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) {
|
export function getEmojiUrl(unified: string, style: EmojiStyle) {
|
||||||
return `https://cdn.staticfile.org/emoji-datasource-apple/14.0.0/img/${style}/64/${unified}.png`;
|
return `https://cdn.staticfile.org/emoji-datasource-apple/14.0.0/img/${style}/64/${unified}.png`;
|
||||||
}
|
}
|
||||||
|
@@ -1,21 +1,23 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { ACCESS_CODES } from "./app/api/access";
|
import { getServerSideConfig } from "./app/config/server";
|
||||||
import md5 from "spark-md5";
|
import md5 from "spark-md5";
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
matcher: ["/api/openai", "/api/chat-stream"],
|
matcher: ["/api/openai", "/api/chat-stream"],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const serverConfig = getServerSideConfig();
|
||||||
|
|
||||||
export function middleware(req: NextRequest) {
|
export function middleware(req: NextRequest) {
|
||||||
const accessCode = req.headers.get("access-code");
|
const accessCode = req.headers.get("access-code");
|
||||||
const token = req.headers.get("token");
|
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: ", [...serverConfig.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) && !token) {
|
if (serverConfig.needCode && !serverConfig.codes.has(hashedCode) && !token) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{
|
{
|
||||||
error: true,
|
error: true,
|
||||||
@@ -30,7 +32,7 @@ export function middleware(req: NextRequest) {
|
|||||||
|
|
||||||
// inject api key
|
// inject api key
|
||||||
if (!token) {
|
if (!token) {
|
||||||
const apiKey = process.env.OPENAI_API_KEY;
|
const apiKey = serverConfig.apiKey;
|
||||||
if (apiKey) {
|
if (apiKey) {
|
||||||
console.log("[Auth] set system token");
|
console.log("[Auth] set system token");
|
||||||
req.headers.set("token", apiKey);
|
req.headers.set("token", apiKey);
|
||||||
|
@@ -8,14 +8,11 @@ const nextConfig = {
|
|||||||
config.module.rules.push({
|
config.module.rules.push({
|
||||||
test: /\.svg$/,
|
test: /\.svg$/,
|
||||||
use: ["@svgr/webpack"],
|
use: ["@svgr/webpack"],
|
||||||
}); // 针对 SVG 的处理规则
|
});
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
},
|
||||||
|
output: "standalone",
|
||||||
};
|
};
|
||||||
|
|
||||||
if (process.env.DOCKER) {
|
|
||||||
nextConfig.output = 'standalone'
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = nextConfig;
|
module.exports = nextConfig;
|
||||||
|
Reference in New Issue
Block a user