diff --git a/.husky/pre-commit b/.husky/pre-commit
index c460d9c3f..d24fdfc60 100755
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -1,4 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
-#npx lint-staged
+npx lint-staged
diff --git a/README_JA.md b/README_JA.md
new file mode 100644
index 000000000..6b8caadae
--- /dev/null
+++ b/README_JA.md
@@ -0,0 +1,310 @@
+
+

+
+
NextChat
+
+ワンクリックで無料であなた専用の ChatGPT ウェブアプリをデプロイ。GPT3、GPT4 & Gemini Pro モデルをサポート。
+
+[企業版](#企業版) / [デモ](https://chat-gpt-next-web.vercel.app/) / [フィードバック](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [Discordに参加](https://discord.gg/zrhvHCr79N)
+
+[

](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FChatGPTNextWeb%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=nextchat&repository-name=NextChat) [

](https://zeabur.com/templates/ZBUEFA) [

](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
+
+
+
+
+## 企業版
+
+あなたの会社のプライベートデプロイとカスタマイズのニーズに応える
+- **ブランドカスタマイズ**:企業向けに特別に設計された VI/UI、企業ブランドイメージとシームレスにマッチ
+- **リソース統合**:企業管理者が数十種類のAIリソースを統一管理、チームメンバーはすぐに使用可能
+- **権限管理**:メンバーの権限、リソースの権限、ナレッジベースの権限を明確にし、企業レベルのAdmin Panelで統一管理
+- **知識の統合**:企業内部のナレッジベースとAI機能を結びつけ、汎用AIよりも企業自身の業務ニーズに近づける
+- **セキュリティ監査**:機密質問を自動的にブロックし、すべての履歴対話を追跡可能にし、AIも企業の情報セキュリティ基準に従わせる
+- **プライベートデプロイ**:企業レベルのプライベートデプロイ、主要なプライベートクラウドデプロイをサポートし、データのセキュリティとプライバシーを保護
+- **継続的な更新**:マルチモーダル、エージェントなどの最先端機能を継続的に更新し、常に最新であり続ける
+
+企業版のお問い合わせ: **business@nextchat.dev**
+
+
+## 始めに
+
+1. [OpenAI API Key](https://platform.openai.com/account/api-keys)を準備する;
+2. 右側のボタンをクリックしてデプロイを開始:
+ [](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&env=GOOGLE_API_KEY&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web) 、GitHubアカウントで直接ログインし、環境変数ページにAPI Keyと[ページアクセスパスワード](#設定ページアクセスパスワード) CODEを入力してください;
+3. デプロイが完了したら、すぐに使用を開始できます;
+4. (オプション)[カスタムドメインをバインド](https://vercel.com/docs/concepts/projects/domains/add-a-domain):Vercelが割り当てたドメインDNSは一部の地域で汚染されているため、カスタムドメインをバインドすると直接接続できます。
+
+
+
+
+
+
+
+
+## 更新を維持する
+
+もし上記の手順に従ってワンクリックでプロジェクトをデプロイした場合、「更新があります」というメッセージが常に表示されることがあります。これは、Vercel がデフォルトで新しいプロジェクトを作成するためで、本プロジェクトを fork していないことが原因です。そのため、正しく更新を検出できません。
+
+以下の手順に従って再デプロイすることをお勧めします:
+
+- 元のリポジトリを削除する
+- ページ右上の fork ボタンを使って、本プロジェクトを fork する
+- Vercel で再度選択してデプロイする、[詳細な手順はこちらを参照してください](./docs/vercel-ja.md)。
+
+
+### 自動更新を開く
+
+> Upstream Sync の実行エラーが発生した場合は、手動で Sync Fork してください!
+
+プロジェクトを fork した後、GitHub の制限により、fork 後のプロジェクトの Actions ページで Workflows を手動で有効にし、Upstream Sync Action を有効にする必要があります。有効化後、毎時の定期自動更新が可能になります:
+
+
+
+
+
+
+### 手動でコードを更新する
+
+手動で即座に更新したい場合は、[GitHub のドキュメント](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork)を参照して、fork したプロジェクトを上流のコードと同期する方法を確認してください。
+
+このプロジェクトをスターまたはウォッチしたり、作者をフォローすることで、新機能の更新通知をすぐに受け取ることができます。
+
+
+
+## ページアクセスパスワードを設定する
+
+> パスワードを設定すると、ユーザーは設定ページでアクセスコードを手動で入力しない限り、通常のチャットができず、未承認の状態であることを示すメッセージが表示されます。
+
+> **警告**:パスワードの桁数は十分に長く設定してください。7桁以上が望ましいです。さもないと、[ブルートフォース攻撃を受ける可能性があります](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/518)。
+
+このプロジェクトは限られた権限管理機能を提供しています。Vercel プロジェクトのコントロールパネルで、環境変数ページに `CODE` という名前の環境変数を追加し、値をカンマで区切ったカスタムパスワードに設定してください:
+
+```
+code1,code2,code3
+```
+
+この環境変数を追加または変更した後、**プロジェクトを再デプロイ**して変更を有効にしてください。
+
+
+## 環境変数
+
+> 本プロジェクトのほとんどの設定は環境変数で行います。チュートリアル:[Vercel の環境変数を変更する方法](./docs/vercel-ja.md)。
+
+### `OPENAI_API_KEY` (必須)
+
+OpenAI の API キー。OpenAI アカウントページで申請したキーをカンマで区切って複数設定できます。これにより、ランダムにキーが選択されます。
+
+### `CODE` (オプション)
+
+アクセスパスワード。カンマで区切って複数設定可能。
+
+**警告**:この項目を設定しないと、誰でもデプロイしたウェブサイトを利用でき、トークンが急速に消耗する可能性があるため、設定をお勧めします。
+
+### `BASE_URL` (オプション)
+
+> デフォルト: `https://api.openai.com`
+
+> 例: `http://your-openai-proxy.com`
+
+OpenAI API のプロキシ URL。手動で OpenAI API のプロキシを設定している場合はこのオプションを設定してください。
+
+> SSL 証明書の問題がある場合は、`BASE_URL` のプロトコルを http に設定してください。
+
+### `OPENAI_ORG_ID` (オプション)
+
+OpenAI の組織 ID を指定します。
+
+### `AZURE_URL` (オプション)
+
+> 形式: https://{azure-resource-url}/openai/deployments/{deploy-name}
+> `CUSTOM_MODELS` で `displayName` 形式で {deploy-name} を設定した場合、`AZURE_URL` から {deploy-name} を省略できます。
+
+Azure のデプロイ URL。
+
+### `AZURE_API_KEY` (オプション)
+
+Azure の API キー。
+
+### `AZURE_API_VERSION` (オプション)
+
+Azure API バージョン。[Azure ドキュメント](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#chat-completions)で確認できます。
+
+### `GOOGLE_API_KEY` (オプション)
+
+Google Gemini Pro API キー。
+
+### `GOOGLE_URL` (オプション)
+
+Google Gemini Pro API の URL。
+
+### `ANTHROPIC_API_KEY` (オプション)
+
+Anthropic Claude API キー。
+
+### `ANTHROPIC_API_VERSION` (オプション)
+
+Anthropic Claude API バージョン。
+
+### `ANTHROPIC_URL` (オプション)
+
+Anthropic Claude API の URL。
+
+### `BAIDU_API_KEY` (オプション)
+
+Baidu API キー。
+
+### `BAIDU_SECRET_KEY` (オプション)
+
+Baidu シークレットキー。
+
+### `BAIDU_URL` (オプション)
+
+Baidu API の URL。
+
+### `BYTEDANCE_API_KEY` (オプション)
+
+ByteDance API キー。
+
+### `BYTEDANCE_URL` (オプション)
+
+ByteDance API の URL。
+
+### `ALIBABA_API_KEY` (オプション)
+
+アリババ(千问)API キー。
+
+### `ALIBABA_URL` (オプション)
+
+アリババ(千问)API の URL。
+
+### `HIDE_USER_API_KEY` (オプション)
+
+ユーザーが API キーを入力できないようにしたい場合は、この環境変数を 1 に設定します。
+
+### `DISABLE_GPT4` (オプション)
+
+ユーザーが GPT-4 を使用できないようにしたい場合は、この環境変数を 1 に設定します。
+
+### `ENABLE_BALANCE_QUERY` (オプション)
+
+バランスクエリ機能を有効にしたい場合は、この環境変数を 1 に設定します。
+
+### `DISABLE_FAST_LINK` (オプション)
+
+リンクからのプリセット設定解析を無効にしたい場合は、この環境変数を 1 に設定します。
+
+### `WHITE_WEBDEV_ENDPOINTS` (オプション)
+
+アクセス許可を与える WebDAV サービスのアドレスを追加したい場合、このオプションを使用します。フォーマット要件:
+- 各アドレスは完全なエンドポイントでなければなりません。
+> `https://xxxx/xxx`
+- 複数のアドレスは `,` で接続します。
+
+### `CUSTOM_MODELS` (オプション)
+
+> 例:`+qwen-7b-chat,+glm-6b,-gpt-3.5-turbo,gpt-4-1106-preview=gpt-4-turbo` は `qwen-7b-chat` と `glm-6b` をモデルリストに追加し、`gpt-3.5-turbo` を削除し、`gpt-4-1106-preview` のモデル名を `gpt-4-turbo` として表示します。
+> すべてのモデルを無効にし、特定のモデルを有効にしたい場合は、`-all,+gpt-3.5-turbo` を使用します。これは `gpt-3.5-turbo` のみを有効にすることを意味します。
+
+モデルリストを管理します。`+` でモデルを追加し、`-` でモデルを非表示にし、`モデル名=表示名` でモデルの表示名をカスタマイズし、カンマで区切ります。
+
+Azure モードでは、`modelName@azure=deploymentName` 形式でモデル名とデプロイ名(deploy-name)を設定できます。
+> 例:`+gpt-3.5-turbo@azure=gpt35` この設定でモデルリストに `gpt35(Azure)` のオプションが表示されます。
+
+ByteDance モードでは、`modelName@bytedance=deploymentName` 形式でモデル名とデプロイ名(deploy-name)を設定できます。
+> 例: `+Doubao-lite-4k@bytedance=ep-xxxxx-xxx` この設定でモデルリストに `Doubao-lite-4k(ByteDance)` のオプションが表示されます。
+
+### `DEFAULT_MODEL` (オプション)
+
+デフォルトのモデルを変更します。
+
+### `DEFAULT_INPUT_TEMPLATE` (オプション)
+
+『設定』の『ユーザー入力前処理』の初期設定に使用するテンプレートをカスタマイズします。
+
+
+## 開発
+
+下のボタンをクリックして二次開発を開始してください:
+
+[](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
+
+コードを書く前に、プロジェクトのルートディレクトリに `.env.local` ファイルを新規作成し、環境変数を記入します:
+
+```
+OPENAI_API_KEY=
+```
+
+
+### ローカル開発
+
+1. Node.js 18 と Yarn をインストールします。具体的な方法は ChatGPT にお尋ねください。
+2. `yarn install && yarn dev` を実行します。⚠️ 注意:このコマンドはローカル開発用であり、デプロイには使用しないでください。
+3. ローカルでデプロイしたい場合は、`yarn install && yarn build && yarn start` コマンドを使用してください。プロセスを守るために pm2 を使用することもできます。詳細は ChatGPT にお尋ねください。
+
+
+## デプロイ
+
+### コンテナデプロイ(推奨)
+
+> Docker バージョンは 20 以上が必要です。それ以下だとイメージが見つからないというエラーが出ます。
+
+> ⚠️ 注意:Docker バージョンは最新バージョンより 1~2 日遅れることが多いため、デプロイ後に「更新があります」の通知が出続けることがありますが、正常です。
+
+```shell
+docker pull yidadaa/chatgpt-next-web
+
+docker run -d -p 3000:3000 \
+ -e OPENAI_API_KEY=sk-xxxx \
+ -e CODE=ページアクセスパスワード \
+ yidadaa/chatgpt-next-web
+```
+
+プロキシを指定することもできます:
+
+```shell
+docker run -d -p 3000:3000 \
+ -e OPENAI_API_KEY=sk-xxxx \
+ -e CODE=ページアクセスパスワード \
+ --net=host \
+ -e PROXY_URL=http://127.0.0.1:7890 \
+ yidadaa/chatgpt-next-web
+```
+
+ローカルプロキシがアカウントとパスワードを必要とする場合は、以下を使用できます:
+
+```shell
+-e PROXY_URL="http://127.0.0.1:7890 user password"
+```
+
+他の環境変数を指定する必要がある場合は、上記のコマンドに `-e 環境変数=環境変数値` を追加して指定してください。
+
+
+### ローカルデプロイ
+
+コンソールで以下のコマンドを実行します:
+
+```shell
+bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/scripts/setup.sh)
+```
+
+⚠️ 注意:インストール中に問題が発生した場合は、Docker を使用してデプロイしてください。
+
+
+## 謝辞
+
+### 寄付者
+
+> 英語版をご覧ください。
+
+### 貢献者
+
+[プロジェクトの貢献者リストはこちら](https://github.com/Yidadaa/ChatGPT-Next-Web/graphs/contributors)
+
+### 関連プロジェクト
+
+- [one-api](https://github.com/songquanpeng/one-api): 一つのプラットフォームで大規模モデルのクォータ管理を提供し、市場に出回っているすべての主要な大規模言語モデルをサポートします。
+
+
+## オープンソースライセンス
+
+[MIT](https://opensource.org/license/mit/)
diff --git a/app/client/platforms/alibaba.ts b/app/client/platforms/alibaba.ts
index 723ba774b..d5fa3042f 100644
--- a/app/client/platforms/alibaba.ts
+++ b/app/client/platforms/alibaba.ts
@@ -21,7 +21,7 @@ import {
} from "@fortaine/fetch-event-source";
import { prettyObject } from "@/app/utils/format";
import { getClientConfig } from "@/app/config/client";
-import { getMessageTextContent, isVisionModel } from "@/app/utils";
+import { getMessageTextContent } from "@/app/utils";
export interface OpenAIListModelResponse {
object: string;
diff --git a/app/client/platforms/anthropic.ts b/app/client/platforms/anthropic.ts
index bf8faf837..b079ba1ad 100644
--- a/app/client/platforms/anthropic.ts
+++ b/app/client/platforms/anthropic.ts
@@ -3,7 +3,6 @@ import { ChatOptions, getHeaders, LLMApi, MultimodalContent } from "../api";
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
import { getClientConfig } from "@/app/config/client";
import { DEFAULT_API_HOST } from "@/app/constant";
-import { RequestMessage } from "@/app/typing";
import {
EventStreamContentType,
fetchEventSource,
@@ -12,6 +11,7 @@ import {
import Locale from "../../locales";
import { prettyObject } from "@/app/utils/format";
import { getMessageTextContent, isVisionModel } from "@/app/utils";
+import { preProcessImageContent } from "@/app/utils/chat";
import { cloudflareAIGatewayUrl } from "@/app/utils/cloudflare";
export type MultiBlockContent = {
@@ -93,7 +93,12 @@ export class ClaudeApi implements LLMApi {
},
};
- const messages = [...options.messages];
+ // try get base64image from local cache image_url
+ const messages: ChatOptions["messages"] = [];
+ for (const v of options.messages) {
+ const content = await preProcessImageContent(v.content);
+ messages.push({ role: v.role, content });
+ }
const keys = ["system", "user"];
diff --git a/app/client/platforms/google.ts b/app/client/platforms/google.ts
index 8acde1a83..1f55beebc 100644
--- a/app/client/platforms/google.ts
+++ b/app/client/platforms/google.ts
@@ -14,6 +14,7 @@ import {
getMessageImages,
isVisionModel,
} from "@/app/utils";
+import { preProcessImageContent } from "@/app/utils/chat";
export class GeminiProApi implements LLMApi {
path(path: string): string {
@@ -56,7 +57,14 @@ export class GeminiProApi implements LLMApi {
async chat(options: ChatOptions): Promise {
const apiClient = this;
let multimodal = false;
- const messages = options.messages.map((v) => {
+
+ // try get base64image from local cache image_url
+ const _messages: ChatOptions["messages"] = [];
+ for (const v of options.messages) {
+ const content = await preProcessImageContent(v.content);
+ _messages.push({ role: v.role, content });
+ }
+ const messages = _messages.map((v) => {
let parts: any[] = [{ text: getMessageTextContent(v) }];
if (isVisionModel(options.config.model)) {
const images = getMessageImages(v);
@@ -98,6 +106,9 @@ export class GeminiProApi implements LLMApi {
// if (visionModel && messages.length > 1) {
// options.onError?.(new Error("Multiturn chat is not enabled for models/gemini-pro-vision"));
// }
+
+ const accessStore = useAccessStore.getState();
+
const modelConfig = {
...useAppConfig.getState().modelConfig,
...useChatStore.getState().currentSession().mask.modelConfig,
@@ -119,19 +130,19 @@ export class GeminiProApi implements LLMApi {
safetySettings: [
{
category: "HARM_CATEGORY_HARASSMENT",
- threshold: "BLOCK_ONLY_HIGH",
+ threshold: accessStore.googleSafetySettings,
},
{
category: "HARM_CATEGORY_HATE_SPEECH",
- threshold: "BLOCK_ONLY_HIGH",
+ threshold: accessStore.googleSafetySettings,
},
{
category: "HARM_CATEGORY_SEXUALLY_EXPLICIT",
- threshold: "BLOCK_ONLY_HIGH",
+ threshold: accessStore.googleSafetySettings,
},
{
category: "HARM_CATEGORY_DANGEROUS_CONTENT",
- threshold: "BLOCK_ONLY_HIGH",
+ threshold: accessStore.googleSafetySettings,
},
],
};
diff --git a/app/client/platforms/openai.ts b/app/client/platforms/openai.ts
index 4eb26277a..973304e64 100644
--- a/app/client/platforms/openai.ts
+++ b/app/client/platforms/openai.ts
@@ -12,6 +12,7 @@ import {
} from "@/app/constant";
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
import { collectModelsWithDefaultModel } from "@/app/utils/model";
+import { preProcessImageContent } from "@/app/utils/chat";
import { cloudflareAIGatewayUrl } from "@/app/utils/cloudflare";
import {
@@ -106,10 +107,13 @@ export class ChatGPTApi implements LLMApi {
async chat(options: ChatOptions) {
const visionModel = isVisionModel(options.config.model);
- const messages = options.messages.map((v) => ({
- role: v.role,
- content: visionModel ? v.content : getMessageTextContent(v),
- }));
+ const messages: ChatOptions["messages"] = [];
+ for (const v of options.messages) {
+ const content = visionModel
+ ? await preProcessImageContent(v.content)
+ : getMessageTextContent(v);
+ messages.push({ role: v.role, content });
+ }
const modelConfig = {
...useAppConfig.getState().modelConfig,
@@ -119,6 +123,7 @@ export class ChatGPTApi implements LLMApi {
providerName: options.config.providerName,
},
};
+ console.log('-------', modelConfig, options)
const requestPayload: RequestPayload = {
messages,
stream: options.config.stream,
diff --git a/app/components/chat.tsx b/app/components/chat.tsx
index 3d5c0130a..5d42bb995 100644
--- a/app/components/chat.tsx
+++ b/app/components/chat.tsx
@@ -64,7 +64,7 @@ import {
isVisionModel,
} from "../utils";
-import { compressImage } from "@/app/utils/chat";
+import { uploadImage as uploadImageRemote } from "@/app/utils/chat";
import dynamic from "next/dynamic";
@@ -1234,7 +1234,7 @@ function _Chat() {
...(await new Promise((res, rej) => {
setUploading(true);
const imagesData: string[] = [];
- compressImage(file, 256 * 1024)
+ uploadImageRemote(file)
.then((dataUrl) => {
imagesData.push(dataUrl);
setUploading(false);
@@ -1276,7 +1276,7 @@ function _Chat() {
const imagesData: string[] = [];
for (let i = 0; i < files.length; i++) {
const file = event.target.files[i];
- compressImage(file, 256 * 1024)
+ uploadImageRemote(file)
.then((dataUrl) => {
imagesData.push(dataUrl);
if (
diff --git a/app/components/settings.tsx b/app/components/settings.tsx
index 2152ab397..dddc37dd3 100644
--- a/app/components/settings.tsx
+++ b/app/components/settings.tsx
@@ -57,6 +57,7 @@ import {
ByteDance,
Alibaba,
Google,
+ GoogleSafetySettingsThreshold,
OPENAI_BASE_URL,
Path,
RELEASE_URL,
@@ -657,6 +658,389 @@ export function Settings() {
const clientConfig = useMemo(() => getClientConfig(), []);
const showAccessCode = enabledAccessControl && !clientConfig?.isApp;
+ const accessCodeComponent = showAccessCode && (
+
+ {
+ accessStore.update(
+ (access) => (access.accessCode = e.currentTarget.value),
+ );
+ }}
+ />
+
+ );
+
+ const useCustomConfigComponent = // Conditionally render the following ListItem based on clientConfig.isApp
+ !clientConfig?.isApp && ( // only show if isApp is false
+
+
+ accessStore.update(
+ (access) => (access.useCustomConfig = e.currentTarget.checked),
+ )
+ }
+ >
+
+ );
+
+ const openAIConfigComponent = accessStore.provider ===
+ ServiceProvider.OpenAI && (
+ <>
+
+
+ accessStore.update(
+ (access) => (access.openaiUrl = e.currentTarget.value),
+ )
+ }
+ >
+
+
+ {
+ accessStore.update(
+ (access) => (access.openaiApiKey = e.currentTarget.value),
+ );
+ }}
+ />
+
+ >
+ );
+
+ const azureConfigComponent = accessStore.provider ===
+ ServiceProvider.Azure && (
+ <>
+
+
+ accessStore.update(
+ (access) => (access.azureUrl = e.currentTarget.value),
+ )
+ }
+ >
+
+
+ {
+ accessStore.update(
+ (access) => (access.azureApiKey = e.currentTarget.value),
+ );
+ }}
+ />
+
+
+
+ accessStore.update(
+ (access) => (access.azureApiVersion = e.currentTarget.value),
+ )
+ }
+ >
+
+ >
+ );
+
+ const googleConfigComponent = accessStore.provider ===
+ ServiceProvider.Google && (
+ <>
+
+
+ accessStore.update(
+ (access) => (access.googleUrl = e.currentTarget.value),
+ )
+ }
+ >
+
+
+ {
+ accessStore.update(
+ (access) => (access.googleApiKey = e.currentTarget.value),
+ );
+ }}
+ />
+
+
+
+ accessStore.update(
+ (access) => (access.googleApiVersion = e.currentTarget.value),
+ )
+ }
+ >
+
+
+
+
+ >
+ );
+
+ const anthropicConfigComponent = accessStore.provider ===
+ ServiceProvider.Anthropic && (
+ <>
+
+
+ accessStore.update(
+ (access) => (access.anthropicUrl = e.currentTarget.value),
+ )
+ }
+ >
+
+
+ {
+ accessStore.update(
+ (access) => (access.anthropicApiKey = e.currentTarget.value),
+ );
+ }}
+ />
+
+
+
+ accessStore.update(
+ (access) => (access.anthropicApiVersion = e.currentTarget.value),
+ )
+ }
+ >
+
+ >
+ );
+
+ const baiduConfigComponent = accessStore.provider ===
+ ServiceProvider.Baidu && (
+ <>
+
+
+ accessStore.update(
+ (access) => (access.baiduUrl = e.currentTarget.value),
+ )
+ }
+ >
+
+
+ {
+ accessStore.update(
+ (access) => (access.baiduApiKey = e.currentTarget.value),
+ );
+ }}
+ />
+
+
+ {
+ accessStore.update(
+ (access) => (access.baiduSecretKey = e.currentTarget.value),
+ );
+ }}
+ />
+
+ >
+ );
+
+ const byteDanceConfigComponent = accessStore.provider ===
+ ServiceProvider.ByteDance && (
+ <>
+
+
+ accessStore.update(
+ (access) => (access.bytedanceUrl = e.currentTarget.value),
+ )
+ }
+ >
+
+
+ {
+ accessStore.update(
+ (access) => (access.bytedanceApiKey = e.currentTarget.value),
+ );
+ }}
+ />
+
+ >
+ );
+
+ const alibabaConfigComponent = accessStore.provider ===
+ ServiceProvider.Alibaba && (
+ <>
+
+
+ accessStore.update(
+ (access) => (access.alibabaUrl = e.currentTarget.value),
+ )
+ }
+ >
+
+
+ {
+ accessStore.update(
+ (access) => (access.alibabaApiKey = e.currentTarget.value),
+ );
+ }}
+ />
+
+ >
+ );
+
return (
@@ -902,489 +1286,92 @@ export function Settings() {
+
{/*
*/}
- {/* {showAccessCode && (*/}
- {/* */}
- {/* {*/}
- {/* accessStore.update(*/}
- {/* (access) => (access.accessCode = e.currentTarget.value),*/}
- {/* );*/}
- {/* }}*/}
- {/* />*/}
- {/* */}
+ {/* {accessCodeComponent}*/}
+
+ {/* {!accessStore.hideUserApiKey && (*/}
+ {/* <>*/}
+ {/* {useCustomConfigComponent}*/}
+
+ {/* {accessStore.useCustomConfig && (*/}
+ {/* <>*/}
+ {/* */}
+ {/* */}
+ {/* */}
+
+ {/* {openAIConfigComponent}*/}
+ {/* {azureConfigComponent}*/}
+ {/* {googleConfigComponent}*/}
+ {/* {anthropicConfigComponent}*/}
+ {/* {baiduConfigComponent}*/}
+ {/* {byteDanceConfigComponent}*/}
+ {/* {alibabaConfigComponent}*/}
+ {/* >*/}
+ {/* )}*/}
+ {/* >*/}
{/* )}*/}
- {/*{!accessStore.hideUserApiKey && (*/}
- {/* <>*/}
- {/* {*/}
- {/* // Conditionally render the following ListItem based on clientConfig.isApp*/}
- {/* !clientConfig?.isApp && ( // only show if isApp is false*/}
- {/* */}
- {/* */}
- {/* accessStore.update(*/}
- {/* (access) =>*/}
- {/* (access.useCustomConfig = e.currentTarget.checked),*/}
+
+ {/* {!shouldHideBalanceQuery && !clientConfig?.isApp ? (*/}
+ {/* */}
- {/* */}
- {/* )*/}
- {/* }*/}
- {/* {accessStore.useCustomConfig && (*/}
- {/* <>*/}
- {/* */}
- {/* */}
- {/* */}
+ {/* : Locale.Settings.Usage.NoAccess*/}
+ {/* }*/}
+ {/* >*/}
+ {/* {!showUsage || loadingUsage ? (*/}
+ {/* */}
+ {/* ) : (*/}
+ {/* }*/}
+ {/* text={Locale.Settings.Usage.Check}*/}
+ {/* onClick={() => checkUsage(true)}*/}
+ {/* />*/}
+ {/* )}*/}
+ {/* */}
+ {/* ) : null}*/}
- {/*{accessStore.provider === ServiceProvider.OpenAI && (*/}
- {/* <>*/}
- {/* */}
- {/* */}
- {/* accessStore.update(*/}
- {/* (access) =>*/}
- {/* (access.openaiUrl = e.currentTarget.value),*/}
- {/* )*/}
- {/* }*/}
- {/* >*/}
- {/* */}
- {/* */}
- {/* {*/}
- {/* accessStore.update(*/}
- {/* (access) =>*/}
- {/* (access.openaiApiKey = e.currentTarget.value),*/}
- {/* );*/}
- {/* }}*/}
- {/* />*/}
- {/* */}
- {/* >*/}
- {/*)}*/}
- {/*{accessStore.provider === ServiceProvider.Azure && (*/}
- {/* <>*/}
- {/* */}
- {/* */}
- {/* accessStore.update(*/}
- {/* (access) =>*/}
- {/* (access.azureUrl = e.currentTarget.value),*/}
- {/* )*/}
- {/* }*/}
- {/* >*/}
- {/* */}
- {/* */}
- {/* {*/}
- {/* accessStore.update(*/}
- {/* (access) =>*/}
- {/* (access.azureApiKey = e.currentTarget.value),*/}
- {/* );*/}
- {/* }}*/}
- {/* />*/}
- {/* */}
- {/* */}
- {/* */}
- {/* accessStore.update(*/}
- {/* (access) =>*/}
- {/* (access.azureApiVersion =*/}
- {/* e.currentTarget.value),*/}
- {/* )*/}
- {/* }*/}
- {/* >*/}
- {/* */}
- {/* >*/}
- {/*)}*/}
- {/*{accessStore.provider === ServiceProvider.Google && (*/}
- {/* <>*/}
- {/* */}
- {/* */}
- {/* accessStore.update(*/}
- {/* (access) =>*/}
- {/* (access.googleUrl = e.currentTarget.value),*/}
- {/* )*/}
- {/* }*/}
- {/* >*/}
- {/* */}
- {/* */}
- {/* {*/}
- {/* accessStore.update(*/}
- {/* (access) =>*/}
- {/* (access.googleApiKey = e.currentTarget.value),*/}
- {/* );*/}
- {/* }}*/}
- {/* />*/}
- {/* */}
- {/* */}
- {/* */}
- {/* accessStore.update(*/}
- {/* (access) =>*/}
- {/* (access.googleApiVersion =*/}
- {/* e.currentTarget.value),*/}
- {/* )*/}
- {/* }*/}
- {/* >*/}
- {/* */}
- {/* >*/}
- {/*)}*/}
- {/*{accessStore.provider === ServiceProvider.Anthropic && (*/}
- {/* <>*/}
- {/* */}
- {/* */}
- {/* accessStore.update(*/}
- {/* (access) =>*/}
- {/* (access.anthropicUrl = e.currentTarget.value),*/}
- {/* )*/}
- {/* }*/}
- {/* >*/}
- {/* */}
- {/* */}
- {/* {*/}
- {/* accessStore.update(*/}
- {/* (access) =>*/}
- {/* (access.anthropicApiKey =*/}
- {/* e.currentTarget.value),*/}
- {/* );*/}
- {/* }}*/}
- {/* />*/}
- {/* */}
- {/* */}
- {/* */}
- {/* accessStore.update(*/}
- {/* (access) =>*/}
- {/* (access.anthropicApiVersion =*/}
- {/* e.currentTarget.value),*/}
- {/* )*/}
- {/* }*/}
- {/* >*/}
- {/* */}
- {/* >*/}
- {/*)}*/}
- {/*{accessStore.provider === ServiceProvider.Baidu && (*/}
- {/* <>*/}
- {/* */}
- {/* */}
- {/* accessStore.update(*/}
- {/* (access) =>*/}
- {/* (access.baiduUrl = e.currentTarget.value),*/}
- {/* )*/}
- {/* }*/}
- {/* >*/}
- {/* */}
- {/* */}
- {/* {*/}
- {/* accessStore.update(*/}
- {/* (access) =>*/}
- {/* (access.baiduApiKey = e.currentTarget.value),*/}
- {/* );*/}
- {/* }}*/}
- {/* />*/}
- {/* */}
- {/* */}
- {/* {*/}
- {/* accessStore.update(*/}
- {/* (access) =>*/}
- {/* (access.baiduSecretKey = e.currentTarget.value),*/}
- {/* );*/}
- {/* }}*/}
- {/* />*/}
- {/* */}
- {/* >*/}
- {/*)}*/}
-
- {/* {accessStore.provider === ServiceProvider.ByteDance && (*/}
- {/* <>*/}
- {/* */}
- {/* */}
- {/* accessStore.update(*/}
- {/* (access) =>*/}
- {/* (access.bytedanceUrl = e.currentTarget.value),*/}
- {/* )*/}
- {/* }*/}
- {/* >*/}
- {/* */}
- {/* */}
- {/* {*/}
- {/* accessStore.update(*/}
- {/* (access) =>*/}
- {/* (access.bytedanceApiKey =*/}
- {/* e.currentTarget.value),*/}
- {/* );*/}
- {/* }}*/}
- {/* />*/}
- {/* */}
- {/* >*/}
- {/* )}*/}
-
- {/* {accessStore.provider === ServiceProvider.Alibaba && (*/}
- {/* <>*/}
- {/* */}
- {/* */}
- {/* accessStore.update(*/}
- {/* (access) =>*/}
- {/* (access.alibabaUrl = e.currentTarget.value),*/}
- {/* )*/}
- {/* }*/}
- {/* >*/}
- {/* */}
- {/* */}
- {/* {*/}
- {/* accessStore.update(*/}
- {/* (access) =>*/}
- {/* (access.alibabaApiKey = e.currentTarget.value),*/}
- {/* );*/}
- {/* }}*/}
- {/* />*/}
- {/* */}
- {/* >*/}
- {/* )}*/}
- {/* >*/}
- {/* )}*/}
- {/* >*/}
- {/*)}*/}
-
- {/*{!shouldHideBalanceQuery && !clientConfig?.isApp ? (*/}
{/* */}
- {/* {!showUsage || loadingUsage ? (*/}
- {/* */}
- {/* ) : (*/}
- {/* }*/}
- {/* text={Locale.Settings.Usage.Check}*/}
- {/* onClick={() => checkUsage(true)}*/}
- {/* />*/}
- {/* )}*/}
+ {/* */}
+ {/* config.update(*/}
+ {/* (config) => (config.customModels = e.currentTarget.value),*/}
+ {/* )*/}
+ {/* }*/}
+ {/* >*/}
{/* */}
- {/*) : null}*/}
-
- {/* /!**!/*/}
- {/* /!* *!/*/}
- {/* /!* config.update(*!/*/}
- {/* /!* (config) => (config.customModels = e.currentTarget.value),*!/*/}
- {/* /!* )*!/*/}
- {/* /!* }*!/*/}
- {/* /!* >*!/*/}
- {/* /!**!/*/}
{/*
*/}
diff --git a/app/constant.ts b/app/constant.ts
index 1a111c404..748da61e2 100644
--- a/app/constant.ts
+++ b/app/constant.ts
@@ -21,6 +21,9 @@ export const BYTEDANCE_BASE_URL = "https://ark.cn-beijing.volces.com";
export const ALIBABA_BASE_URL = "https://dashscope.aliyuncs.com/api/";
+export const CACHE_URL_PREFIX = "/api/cache";
+export const UPLOAD_URL = `${CACHE_URL_PREFIX}/upload`;
+
export enum Path {
Home = "/",
Chat = "/chat",
@@ -88,6 +91,15 @@ export enum ServiceProvider {
Alibaba = "Alibaba",
}
+// Google API safety settings, see https://ai.google.dev/gemini-api/docs/safety-settings
+// BLOCK_NONE will not block any content, and BLOCK_ONLY_HIGH will block only high-risk content.
+export enum GoogleSafetySettingsThreshold {
+ BLOCK_NONE = "BLOCK_NONE",
+ BLOCK_ONLY_HIGH = "BLOCK_ONLY_HIGH",
+ BLOCK_MEDIUM_AND_ABOVE = "BLOCK_MEDIUM_AND_ABOVE",
+ BLOCK_LOW_AND_ABOVE = "BLOCK_LOW_AND_ABOVE",
+}
+
export enum ModelProvider {
GPT = "GPT",
GeminiPro = "GeminiPro",
@@ -177,6 +189,7 @@ Latex inline: \\(x^2\\)
Latex block: $$e=mc^2$$
`;
+// export const SUMMARIZE_MODEL = "gpt-4o-mini";
export const SUMMARIZE_MODEL = "gpt-4o";
export const GEMINI_SUMMARIZE_MODEL = "gemini-pro";
@@ -243,7 +256,7 @@ const baiduModels = [
"ernie-speed-128k",
"ernie-speed-8k",
"ernie-lite-8k",
- "ernie-tiny-8k"
+ "ernie-tiny-8k",
];
const bytedanceModels = [
@@ -269,6 +282,16 @@ export const DEFAULT_MODELS = [
{
name: "gpt-3.5-turbo",
describe: "GPT-3,质量一般,便宜",
+ available: false,
+ provider: {
+ id: "openai",
+ providerName: "OpenAI",
+ providerType: "openai",
+ },
+ },
+ {
+ name: "gpt-4o-mini",
+ describe: "新出的,可以尝鲜",
available: true,
provider: {
id: "openai",
@@ -286,16 +309,6 @@ export const DEFAULT_MODELS = [
providerType: "azure",
},
},
- {
- name: "gpt-4o-mini",
- describe: "新出的,可以尝鲜",
- available: true,
- provider: {
- id: "openai",
- providerName: "OpenAI",
- providerType: "openai",
- },
- },
{
name: "gpt-4-turbo-2024-04-09",
describe: "GPT-4,标准版",
diff --git a/app/locales/cn.ts b/app/locales/cn.ts
index 43422873c..760010da5 100644
--- a/app/locales/cn.ts
+++ b/app/locales/cn.ts
@@ -386,6 +386,10 @@ const cn = {
Title: "API 版本(仅适用于 gemini-pro)",
SubTitle: "选择一个特定的 API 版本",
},
+ GoogleSafetySettings: {
+ Title: "Google 安全过滤级别",
+ SubTitle: "设置内容过滤级别",
+ },
},
Baidu: {
ApiKey: {
diff --git a/app/locales/en.ts b/app/locales/en.ts
index 1c8d7686f..1955aef63 100644
--- a/app/locales/en.ts
+++ b/app/locales/en.ts
@@ -432,6 +432,10 @@ const en: LocaleType = {
Title: "API Version (specific to gemini-pro)",
SubTitle: "Select a specific API version",
},
+ GoogleSafetySettings: {
+ Title: "Google Safety Settings",
+ SubTitle: "Select a safety filtering level",
+ },
},
},
diff --git a/app/store/access.ts b/app/store/access.ts
index 253d414b9..6735283fd 100644
--- a/app/store/access.ts
+++ b/app/store/access.ts
@@ -1,6 +1,7 @@
import {
ApiPath,
DEFAULT_API_HOST,
+ GoogleSafetySettingsThreshold,
ServiceProvider,
StoreKey,
} from "../constant";
@@ -60,6 +61,7 @@ const DEFAULT_ACCESS_STATE = {
googleUrl: DEFAULT_GOOGLE_URL,
googleApiKey: "",
googleApiVersion: "v1",
+ googleSafetySettings: GoogleSafetySettingsThreshold.BLOCK_ONLY_HIGH,
// anthropic
anthropicUrl: DEFAULT_ANTHROPIC_URL,
diff --git a/app/store/chat.ts b/app/store/chat.ts
index 3cc2bbe74..9ead183ca 100644
--- a/app/store/chat.ts
+++ b/app/store/chat.ts
@@ -95,9 +95,13 @@ function createEmptySession(): ChatSession {
};
}
+ // if it is using gpt-* models, force to use 4o-mini to summarize
const ChatFetchTaskPool: Record = {};
-function getSummarizeModel(currentModel: string) {
+function getSummarizeModel(currentModel: string): {
+ name: string,
+ providerName: string | undefined,
+} {
// if it is using gpt-* models, force to use 3.5 to summarize
if (currentModel.startsWith("gpt")) {
const configStore = useAppConfig.getState();
@@ -110,12 +114,21 @@ function getSummarizeModel(currentModel: string) {
const summarizeModel = allModel.find(
(m) => m.name === SUMMARIZE_MODEL && m.available,
);
- return summarizeModel?.name ?? currentModel;
+ return {
+ name: summarizeModel?.name ?? currentModel,
+ providerName: summarizeModel?.provider?.providerName,
+ }
}
if (currentModel.startsWith("gemini")) {
- return GEMINI_SUMMARIZE_MODEL;
+ return {
+ name: GEMINI_SUMMARIZE_MODEL,
+ providerName: ServiceProvider.Google,
+ }
+ }
+ return {
+ name: currentModel,
+ providerName: undefined,
}
- return currentModel;
}
function countMessages(msgs: ChatMessage[]) {
@@ -905,7 +918,8 @@ export const useChatStore = createPersistStore(
api.llm.chat({
messages: topicMessages,
config: {
- model: getSummarizeModel(session.mask.modelConfig.model),
+ model: getSummarizeModel(session.mask.modelConfig.model).name,
+ providerName: getSummarizeModel(session.mask.modelConfig.model).providerName,
stream: false,
},
onFinish(message) {
@@ -967,7 +981,8 @@ export const useChatStore = createPersistStore(
config: {
...modelcfg,
stream: true,
- model: getSummarizeModel(session.mask.modelConfig.model),
+ model: getSummarizeModel(session.mask.modelConfig.model).name,
+ providerName: getSummarizeModel(session.mask.modelConfig.model).providerName,
},
onUpdate(message) {
session.memoryPrompt = message;
diff --git a/app/store/config.ts b/app/store/config.ts
index bc49b08b1..631f1d955 100644
--- a/app/store/config.ts
+++ b/app/store/config.ts
@@ -140,7 +140,7 @@ export const useAppConfig = createPersistStore(
}),
{
name: StoreKey.Config,
- version: 3.99,
+ version: 3.991,
migrate(persistedState, version) {
const state = persistedState as ChatConfig;
@@ -176,7 +176,7 @@ export const useAppConfig = createPersistStore(
// return { ...DEFAULT_CONFIG };
// }
- if (version < 3.99) {
+ if (version < 3.991) {
// state.modelConfig = DEFAULT_CONFIG.modelConfig;
return { ...DEFAULT_CONFIG };
// state.modelConfig.template =
diff --git a/app/store/prompt.ts b/app/store/prompt.ts
index ea4a26b78..5a1e2cfd6 100644
--- a/app/store/prompt.ts
+++ b/app/store/prompt.ts
@@ -159,7 +159,8 @@ export const usePromptStore = createPersistStore(
onRehydrateStorage(state) {
// const PROMPT_URL = "https://cos.xiaosi.cc/next/public/prompts.json";
- const PROMPT_URL = "https://qn.xiaosi.cc/json/chat/prompts.json";
+ // const PROMPT_URL = "https://qn.xiaosi.cc/json/chat/prompts.json";
+ const PROMPT_URL = "./prompts.json"
const GPT_PROMPT_URL =
"https://qn.xiaosi.cc/json/chat/prompt_library.json";
diff --git a/app/utils/chat.ts b/app/utils/chat.ts
index 7ec0d889e..bb6d38f45 100644
--- a/app/utils/chat.ts
+++ b/app/utils/chat.ts
@@ -1,6 +1,7 @@
-import heic2any from "heic2any";
+import { CACHE_URL_PREFIX, UPLOAD_URL } from "@/app/constant";
+import { RequestMessage } from "@/app/client/api";
-export function compressImage(file: File, maxSize: number): Promise {
+export function compressImage(file: Blob, maxSize: number): Promise {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
@@ -10,13 +11,18 @@ export function compressImage(file: File, maxSize: number): Promise {
reader.onerror = reject;
if (file.type.includes("heic")) {
- heic2any({ blob: file, toType: "image/jpeg" })
- .then((blob) => {
- reader.readAsDataURL(blob as Blob);
- })
- .catch((e) => {
- reject(e);
- });
+ try {
+ const heic2any = require("heic2any");
+ heic2any({ blob: file, toType: "image/jpeg" })
+ .then((blob: Blob) => {
+ reader.readAsDataURL(blob);
+ })
+ .catch((e: any) => {
+ reject(e);
+ });
+ } catch (e) {
+ reject(e);
+ }
}
reader.readAsDataURL(file);
@@ -73,3 +79,87 @@ export function compressImage(file: File, maxSize: number): Promise {
// reader.readAsDataURL(file);
// });
}
+
+export async function preProcessImageContent(
+ content: RequestMessage["content"],
+) {
+ if (typeof content === "string") {
+ return content;
+ }
+ const result = [];
+ for (const part of content) {
+ if (part?.type == "image_url" && part?.image_url?.url) {
+ try {
+ const url = await cacheImageToBase64Image(part?.image_url?.url);
+ result.push({ type: part.type, image_url: { url } });
+ } catch (error) {
+ console.error("Error processing image URL:", error);
+ }
+ } else {
+ result.push({ ...part });
+ }
+ }
+ return result;
+}
+
+const imageCaches: Record = {};
+export function cacheImageToBase64Image(imageUrl: string) {
+ if (imageUrl.includes(CACHE_URL_PREFIX)) {
+ if (!imageCaches[imageUrl]) {
+ const reader = new FileReader();
+ return fetch(imageUrl, {
+ method: "GET",
+ mode: "cors",
+ credentials: "include",
+ })
+ .then((res) => res.blob())
+ .then(
+ async (blob) =>
+ (imageCaches[imageUrl] = await compressImage(blob, 256 * 1024)),
+ ); // compressImage
+ }
+ return Promise.resolve(imageCaches[imageUrl]);
+ }
+ return Promise.resolve(imageUrl);
+}
+
+export function base64Image2Blob(base64Data: string, contentType: string) {
+ const byteCharacters = atob(base64Data);
+ const byteNumbers = new Array(byteCharacters.length);
+ for (let i = 0; i < byteCharacters.length; i++) {
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
+ }
+ const byteArray = new Uint8Array(byteNumbers);
+ return new Blob([byteArray], { type: contentType });
+}
+
+export function uploadImage(file: File): Promise {
+ if (!window._SW_ENABLED) {
+ // if serviceWorker register error, using compressImage
+ return compressImage(file, 256 * 1024);
+ }
+ const body = new FormData();
+ body.append("file", file);
+ return fetch(UPLOAD_URL, {
+ method: "post",
+ body,
+ mode: "cors",
+ credentials: "include",
+ })
+ .then((res) => res.json())
+ .then((res) => {
+ console.log("res", res);
+ if (res?.code == 0 && res?.data) {
+ return res?.data;
+ }
+ throw Error(`upload Error: ${res?.msg}`);
+ });
+}
+
+export function removeImage(imageUrl: string) {
+ return fetch(imageUrl, {
+ method: "DELETE",
+ mode: "cors",
+ credentials: "include",
+ });
+}
diff --git a/package.json b/package.json
index 7c3052d32..55d5f0997 100644
--- a/package.json
+++ b/package.json
@@ -93,7 +93,8 @@
"webpack": "5.93.0"
},
"resolutions": {
- "lint-staged/yaml": "^2.2.2"
+ "lint-staged/yaml": "^2.2.2",
+ "strip-ansi": "6.0.1"
},
"packageManager": "yarn@1.22.19"
}
diff --git a/public/prompts.json b/public/prompts.json
index f69e16851..2a751c52a 100644
--- a/public/prompts.json
+++ b/public/prompts.json
@@ -489,7 +489,9 @@
"你现在将成为一个名为Midjourney的生成式人工智能的提示词生成器。Midjourney人工智能将根据给定的提示词生成图像。\n我将提供一组关键词[1],你需要依据这些关键词为Midjourney生成提示词。\n你永远不会以任何方式更改下面列出的结构和格式,并遵守以下准则:\n你不会以任何形式写描述或使用:。永远不要在[ar]和[v]之间加逗 述现场的情绪/感受和氛围。\n[5] = 风格,例如:摄影、绘画、插图、雕塑、艺术品、文书工作、3d等).[1]\n[6] = 如何实现[5]的描述(例如,带有相机模型和适当相机设置的摄影(如Macro、Fisheye Style、Portrait)、带有关于所用材料和工作材料的详细描述的绘画、带有引擎设置的渲染、数字插图、木刻艺术(以及可以定义为输出类型的其他(完全按照书写方式使用)\n格式:\n您所写的内容必须与以下结构中的格式完全相同,包括/和:\n这是Midjourney提示词结构:/mj [1],[2],[3],[4],[5],[6],[ar][v]。\n这是你的任务:你将为我提供的每组关键词[1]生成4个提示词,每个提示词在描述、环境、氛围和实现方面都是不同的方法。\n你提供的提示词将是中文的。\n请注意:\n-如果我提供的关键词不是真实的,请不要描述为真实、逼真、照片或照片。例如,一个由纸或与幻想有关的场景构成的概念。\n-为每个关键词生成的提示之一必须是逼真的摄影风格。你还应该为它选择镜头类型和尺寸。不要为逼真的摄影提示选择择术家。\n所有的结果翻译为英文"
]
],
-
+ "tw": [
+ []
+ ],
"en": [
[]
]
diff --git a/public/serviceWorker.js b/public/serviceWorker.js
index f5a24b701..c58b2cc5a 100644
--- a/public/serviceWorker.js
+++ b/public/serviceWorker.js
@@ -1,10 +1,13 @@
const CHATGPT_NEXT_WEB_CACHE = "chatgpt-next-web-cache";
+const CHATGPT_NEXT_WEB_FILE_CACHE = "chatgpt-next-web-file";
+let a="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";let nanoid=(e=21)=>{let t="",r=crypto.getRandomValues(new Uint8Array(e));for(let n=0;n {});
+async function upload(request, url) {
+ const formData = await request.formData()
+ const file = formData.getAll('file')[0]
+ let ext = file.name.split('.').pop()
+ if (ext === 'blob') {
+ ext = file.type.split('/').pop()
+ }
+ const fileUrl = `${url.origin}/api/cache/${nanoid()}.${ext}`
+ // console.debug('file', file, fileUrl, request)
+ const cache = await caches.open(CHATGPT_NEXT_WEB_FILE_CACHE)
+ await cache.put(new Request(fileUrl), new Response(file, {
+ headers: {
+ 'content-type': file.type,
+ 'content-length': file.size,
+ 'cache-control': 'no-cache', // file already store in disk
+ 'server': 'ServiceWorker',
+ }
+ }))
+ return Response.json({ code: 0, data: fileUrl })
+}
+
+async function remove(request, url) {
+ const cache = await caches.open(CHATGPT_NEXT_WEB_FILE_CACHE)
+ const res = await cache.delete(request.url)
+ return Response.json({ code: 0 })
+}
+
+self.addEventListener("fetch", (e) => {
+ const url = new URL(e.request.url);
+ if (/^\/api\/cache/.test(url.pathname)) {
+ if ('GET' == e.request.method) {
+ e.respondWith(caches.match(e.request))
+ }
+ if ('POST' == e.request.method) {
+ e.respondWith(upload(e.request, url))
+ }
+ if ('DELETE' == e.request.method) {
+ e.respondWith(remove(e.request, url))
+ }
+ }
+});
+
diff --git a/public/serviceWorkerRegister.js b/public/serviceWorkerRegister.js
index 8405f21aa..737205bb8 100644
--- a/public/serviceWorkerRegister.js
+++ b/public/serviceWorkerRegister.js
@@ -1,9 +1,27 @@
if ('serviceWorker' in navigator) {
- window.addEventListener('load', function () {
+ window.addEventListener('DOMContentLoaded', function () {
navigator.serviceWorker.register('/serviceWorker.js').then(function (registration) {
console.log('ServiceWorker registration successful with scope: ', registration.scope);
+ const sw = registration.installing || registration.waiting
+ if (sw) {
+ sw.onstatechange = function() {
+ if (sw.state === 'installed') {
+ // SW installed. Reload for SW intercept serving SW-enabled page.
+ console.log('ServiceWorker installed reload page');
+ window.location.reload();
+ }
+ }
+ }
+ registration.update().then(res => {
+ console.log('ServiceWorker registration update: ', res);
+ });
+ window._SW_ENABLED = true
}, function (err) {
console.error('ServiceWorker registration failed: ', err);
});
+ navigator.serviceWorker.addEventListener('controllerchange', function() {
+ console.log('ServiceWorker controllerchange ');
+ window.location.reload(true);
+ });
});
-}
\ No newline at end of file
+}
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index f20bfbb74..86faf4c3c 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -9,7 +9,7 @@
},
"package": {
"productName": "NextChat",
- "version": "2.13.0"
+ "version": "2.13.1"
},
"tauri": {
"allowlist": {
diff --git a/yarn.lock b/yarn.lock
index 85426c2f8..a90f2c77f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2558,11 +2558,6 @@ ansi-regex@^5.0.1:
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
-ansi-regex@^6.0.1:
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a"
- integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==
-
ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
@@ -7842,20 +7837,19 @@ stringify-entities@^4.0.0:
character-entities-html4 "^2.0.0"
character-entities-legacy "^3.0.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
- name strip-ansi-cjs
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
-strip-ansi@^7.0.1, strip-ansi@^7.1.0:
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
- integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==
+strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1, strip-ansi@^7.0.1, strip-ansi@^7.1.0:
+ version "6.0.1"
+ resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
- ansi-regex "^6.0.1"
+ ansi-regex "^5.0.1"
strip-bom@^3.0.0:
version "3.0.0"