Merge pull request #128 from sijinhui/dev

Dev
This commit is contained in:
sijinhui 2024-07-21 08:40:15 +08:00 committed by GitHub
commit b8a659e13f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 1044 additions and 538 deletions

View File

@ -1,4 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
#npx lint-staged
npx lint-staged

310
README_JA.md Normal file
View File

@ -0,0 +1,310 @@
<div align="center">
<img src="./docs/images/ent.svg" alt="プレビュー"/>
<h1 align="center">NextChat</h1>
ワンクリックで無料であなた専用の 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)
[<img src="https://vercel.com/button" alt="Zeaburでデプロイ" height="30">](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) [<img src="https://zeabur.com/button.svg" alt="Zeaburでデプロイ" height="30">](https://zeabur.com/templates/ZBUEFA) [<img src="https://gitpod.io/button/open-in-gitpod.svg" alt="Gitpodで開く" height="30">](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
</div>
## 企業版
あなたの会社のプライベートデプロイとカスタマイズのニーズに応える
- **ブランドカスタマイズ**:企業向けに特別に設計された VI/UI、企業ブランドイメージとシームレスにマッチ
- **リソース統合**企業管理者が数十種類のAIリソースを統一管理、チームメンバーはすぐに使用可能
- **権限管理**メンバーの権限、リソースの権限、ナレッジベースの権限を明確にし、企業レベルのAdmin Panelで統一管理
- **知識の統合**企業内部のナレッジベースとAI機能を結びつけ、汎用AIよりも企業自身の業務ニーズに近づける
- **セキュリティ監査**機密質問を自動的にブロックし、すべての履歴対話を追跡可能にし、AIも企業の情報セキュリティ基準に従わせる
- **プライベートデプロイ**:企業レベルのプライベートデプロイ、主要なプライベートクラウドデプロイをサポートし、データのセキュリティとプライバシーを保護
- **継続的な更新**:マルチモーダル、エージェントなどの最先端機能を継続的に更新し、常に最新であり続ける
企業版のお問い合わせ: **business@nextchat.dev**
## 始めに
1. [OpenAI API Key](https://platform.openai.com/account/api-keys)を準備する;
2. 右側のボタンをクリックしてデプロイを開始:
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&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は一部の地域で汚染されているため、カスタムドメインをバインドすると直接接続できます。
<div align="center">
![メインインターフェース](./docs/images/cover.png)
</div>
## 更新を維持する
もし上記の手順に従ってワンクリックでプロジェクトをデプロイした場合、「更新があります」というメッセージが常に表示されることがあります。これは、Vercel がデフォルトで新しいプロジェクトを作成するためで、本プロジェクトを fork していないことが原因です。そのため、正しく更新を検出できません。
以下の手順に従って再デプロイすることをお勧めします:
- 元のリポジトリを削除する
- ページ右上の fork ボタンを使って、本プロジェクトを fork する
- Vercel で再度選択してデプロイする、[詳細な手順はこちらを参照してください](./docs/vercel-ja.md)。
### 自動更新を開く
> Upstream Sync の実行エラーが発生した場合は、手動で Sync Fork してください!
プロジェクトを fork した後、GitHub の制限により、fork 後のプロジェクトの Actions ページで Workflows を手動で有効にし、Upstream Sync Action を有効にする必要があります。有効化後、毎時の定期自動更新が可能になります:
![自動更新](./docs/images/enable-actions.jpg)
![自動更新を有効にする](./docs/images/enable-actions-sync.jpg)
### 手動でコードを更新する
手動で即座に更新したい場合は、[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` (オプション)
『設定』の『ユーザー入力前処理』の初期設定に使用するテンプレートをカスタマイズします。
## 開発
下のボタンをクリックして二次開発を開始してください:
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
コードを書く前に、プロジェクトのルートディレクトリに `.env.local` ファイルを新規作成し、環境変数を記入します:
```
OPENAI_API_KEY=<your api key here>
```
### ローカル開発
1. Node.js 18 と Yarn をインストールします。具体的な方法は ChatGPT にお尋ねください。
2. `yarn install && yarn dev` を実行します。⚠️ 注意:このコマンドはローカル開発用であり、デプロイには使用しないでください。
3. ローカルでデプロイしたい場合は、`yarn install && yarn build && yarn start` コマンドを使用してください。プロセスを守るために pm2 を使用することもできます。詳細は ChatGPT にお尋ねください。
## デプロイ
### コンテナデプロイ(推奨)
> Docker バージョンは 20 以上が必要です。それ以下だとイメージが見つからないというエラーが出ます。
> ⚠️ 注意Docker バージョンは最新バージョンより 12 日遅れることが多いため、デプロイ後に「更新があります」の通知が出続けることがありますが、正常です。
```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/)

View File

@ -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;

View File

@ -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"];

View File

@ -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<void> {
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,
},
],
};

View File

@ -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,

View File

@ -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<string[]>((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 (

View File

@ -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 && (
<ListItem
title={Locale.Settings.Access.AccessCode.Title}
subTitle={Locale.Settings.Access.AccessCode.SubTitle}
>
<PasswordInput
value={accessStore.accessCode}
type="text"
placeholder={Locale.Settings.Access.AccessCode.Placeholder}
onChange={(e) => {
accessStore.update(
(access) => (access.accessCode = e.currentTarget.value),
);
}}
/>
</ListItem>
);
const useCustomConfigComponent = // Conditionally render the following ListItem based on clientConfig.isApp
!clientConfig?.isApp && ( // only show if isApp is false
<ListItem
title={Locale.Settings.Access.CustomEndpoint.Title}
subTitle={Locale.Settings.Access.CustomEndpoint.SubTitle}
>
<input
type="checkbox"
checked={accessStore.useCustomConfig}
onChange={(e) =>
accessStore.update(
(access) => (access.useCustomConfig = e.currentTarget.checked),
)
}
></input>
</ListItem>
);
const openAIConfigComponent = accessStore.provider ===
ServiceProvider.OpenAI && (
<>
<ListItem
title={Locale.Settings.Access.OpenAI.Endpoint.Title}
subTitle={Locale.Settings.Access.OpenAI.Endpoint.SubTitle}
>
<input
type="text"
value={accessStore.openaiUrl}
placeholder={OPENAI_BASE_URL}
onChange={(e) =>
accessStore.update(
(access) => (access.openaiUrl = e.currentTarget.value),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Access.OpenAI.ApiKey.Title}
subTitle={Locale.Settings.Access.OpenAI.ApiKey.SubTitle}
>
<PasswordInput
value={accessStore.openaiApiKey}
type="text"
placeholder={Locale.Settings.Access.OpenAI.ApiKey.Placeholder}
onChange={(e) => {
accessStore.update(
(access) => (access.openaiApiKey = e.currentTarget.value),
);
}}
/>
</ListItem>
</>
);
const azureConfigComponent = accessStore.provider ===
ServiceProvider.Azure && (
<>
<ListItem
title={Locale.Settings.Access.Azure.Endpoint.Title}
subTitle={
Locale.Settings.Access.Azure.Endpoint.SubTitle + Azure.ExampleEndpoint
}
>
<input
type="text"
value={accessStore.azureUrl}
placeholder={Azure.ExampleEndpoint}
onChange={(e) =>
accessStore.update(
(access) => (access.azureUrl = e.currentTarget.value),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Access.Azure.ApiKey.Title}
subTitle={Locale.Settings.Access.Azure.ApiKey.SubTitle}
>
<PasswordInput
value={accessStore.azureApiKey}
type="text"
placeholder={Locale.Settings.Access.Azure.ApiKey.Placeholder}
onChange={(e) => {
accessStore.update(
(access) => (access.azureApiKey = e.currentTarget.value),
);
}}
/>
</ListItem>
<ListItem
title={Locale.Settings.Access.Azure.ApiVerion.Title}
subTitle={Locale.Settings.Access.Azure.ApiVerion.SubTitle}
>
<input
type="text"
value={accessStore.azureApiVersion}
placeholder="2023-08-01-preview"
onChange={(e) =>
accessStore.update(
(access) => (access.azureApiVersion = e.currentTarget.value),
)
}
></input>
</ListItem>
</>
);
const googleConfigComponent = accessStore.provider ===
ServiceProvider.Google && (
<>
<ListItem
title={Locale.Settings.Access.Google.Endpoint.Title}
subTitle={
Locale.Settings.Access.Google.Endpoint.SubTitle +
Google.ExampleEndpoint
}
>
<input
type="text"
value={accessStore.googleUrl}
placeholder={Google.ExampleEndpoint}
onChange={(e) =>
accessStore.update(
(access) => (access.googleUrl = e.currentTarget.value),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Access.Google.ApiKey.Title}
subTitle={Locale.Settings.Access.Google.ApiKey.SubTitle}
>
<PasswordInput
value={accessStore.googleApiKey}
type="text"
placeholder={Locale.Settings.Access.Google.ApiKey.Placeholder}
onChange={(e) => {
accessStore.update(
(access) => (access.googleApiKey = e.currentTarget.value),
);
}}
/>
</ListItem>
<ListItem
title={Locale.Settings.Access.Google.ApiVersion.Title}
subTitle={Locale.Settings.Access.Google.ApiVersion.SubTitle}
>
<input
type="text"
value={accessStore.googleApiVersion}
placeholder="2023-08-01-preview"
onChange={(e) =>
accessStore.update(
(access) => (access.googleApiVersion = e.currentTarget.value),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Access.Google.GoogleSafetySettings.Title}
subTitle={Locale.Settings.Access.Google.GoogleSafetySettings.SubTitle}
>
<Select
value={accessStore.googleSafetySettings}
onChange={(e) => {
accessStore.update(
(access) =>
(access.googleSafetySettings = e.target
.value as GoogleSafetySettingsThreshold),
);
}}
>
{Object.entries(GoogleSafetySettingsThreshold).map(([k, v]) => (
<option value={v} key={k}>
{k}
</option>
))}
</Select>
</ListItem>
</>
);
const anthropicConfigComponent = accessStore.provider ===
ServiceProvider.Anthropic && (
<>
<ListItem
title={Locale.Settings.Access.Anthropic.Endpoint.Title}
subTitle={
Locale.Settings.Access.Anthropic.Endpoint.SubTitle +
Anthropic.ExampleEndpoint
}
>
<input
type="text"
value={accessStore.anthropicUrl}
placeholder={Anthropic.ExampleEndpoint}
onChange={(e) =>
accessStore.update(
(access) => (access.anthropicUrl = e.currentTarget.value),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Access.Anthropic.ApiKey.Title}
subTitle={Locale.Settings.Access.Anthropic.ApiKey.SubTitle}
>
<PasswordInput
value={accessStore.anthropicApiKey}
type="text"
placeholder={Locale.Settings.Access.Anthropic.ApiKey.Placeholder}
onChange={(e) => {
accessStore.update(
(access) => (access.anthropicApiKey = e.currentTarget.value),
);
}}
/>
</ListItem>
<ListItem
title={Locale.Settings.Access.Anthropic.ApiVerion.Title}
subTitle={Locale.Settings.Access.Anthropic.ApiVerion.SubTitle}
>
<input
type="text"
value={accessStore.anthropicApiVersion}
placeholder={Anthropic.Vision}
onChange={(e) =>
accessStore.update(
(access) => (access.anthropicApiVersion = e.currentTarget.value),
)
}
></input>
</ListItem>
</>
);
const baiduConfigComponent = accessStore.provider ===
ServiceProvider.Baidu && (
<>
<ListItem
title={Locale.Settings.Access.Baidu.Endpoint.Title}
subTitle={Locale.Settings.Access.Baidu.Endpoint.SubTitle}
>
<input
type="text"
value={accessStore.baiduUrl}
placeholder={Baidu.ExampleEndpoint}
onChange={(e) =>
accessStore.update(
(access) => (access.baiduUrl = e.currentTarget.value),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Access.Baidu.ApiKey.Title}
subTitle={Locale.Settings.Access.Baidu.ApiKey.SubTitle}
>
<PasswordInput
value={accessStore.baiduApiKey}
type="text"
placeholder={Locale.Settings.Access.Baidu.ApiKey.Placeholder}
onChange={(e) => {
accessStore.update(
(access) => (access.baiduApiKey = e.currentTarget.value),
);
}}
/>
</ListItem>
<ListItem
title={Locale.Settings.Access.Baidu.SecretKey.Title}
subTitle={Locale.Settings.Access.Baidu.SecretKey.SubTitle}
>
<PasswordInput
value={accessStore.baiduSecretKey}
type="text"
placeholder={Locale.Settings.Access.Baidu.SecretKey.Placeholder}
onChange={(e) => {
accessStore.update(
(access) => (access.baiduSecretKey = e.currentTarget.value),
);
}}
/>
</ListItem>
</>
);
const byteDanceConfigComponent = accessStore.provider ===
ServiceProvider.ByteDance && (
<>
<ListItem
title={Locale.Settings.Access.ByteDance.Endpoint.Title}
subTitle={
Locale.Settings.Access.ByteDance.Endpoint.SubTitle +
ByteDance.ExampleEndpoint
}
>
<input
type="text"
value={accessStore.bytedanceUrl}
placeholder={ByteDance.ExampleEndpoint}
onChange={(e) =>
accessStore.update(
(access) => (access.bytedanceUrl = e.currentTarget.value),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Access.ByteDance.ApiKey.Title}
subTitle={Locale.Settings.Access.ByteDance.ApiKey.SubTitle}
>
<PasswordInput
value={accessStore.bytedanceApiKey}
type="text"
placeholder={Locale.Settings.Access.ByteDance.ApiKey.Placeholder}
onChange={(e) => {
accessStore.update(
(access) => (access.bytedanceApiKey = e.currentTarget.value),
);
}}
/>
</ListItem>
</>
);
const alibabaConfigComponent = accessStore.provider ===
ServiceProvider.Alibaba && (
<>
<ListItem
title={Locale.Settings.Access.Alibaba.Endpoint.Title}
subTitle={
Locale.Settings.Access.Alibaba.Endpoint.SubTitle +
Alibaba.ExampleEndpoint
}
>
<input
type="text"
value={accessStore.alibabaUrl}
placeholder={Alibaba.ExampleEndpoint}
onChange={(e) =>
accessStore.update(
(access) => (access.alibabaUrl = e.currentTarget.value),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Access.Alibaba.ApiKey.Title}
subTitle={Locale.Settings.Access.Alibaba.ApiKey.SubTitle}
>
<PasswordInput
value={accessStore.alibabaApiKey}
type="text"
placeholder={Locale.Settings.Access.Alibaba.ApiKey.Placeholder}
onChange={(e) => {
accessStore.update(
(access) => (access.alibabaApiKey = e.currentTarget.value),
);
}}
/>
</ListItem>
</>
);
return (
<ErrorBoundary>
<div className="window-header" data-tauri-drag-region>
@ -902,489 +1286,92 @@ export function Settings() {
</ListItem>
</List>
{/*<List id={SlotID.CustomModel}>*/}
{/* {showAccessCode && (*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Access.AccessCode.Title}*/}
{/* subTitle={Locale.Settings.Access.AccessCode.SubTitle}*/}
{/* >*/}
{/* <PasswordInput*/}
{/* value={accessStore.accessCode}*/}
{/* type="text"*/}
{/* placeholder={Locale.Settings.Access.AccessCode.Placeholder}*/}
{/* onChange={(e) => {*/}
{/* accessStore.update(*/}
{/* (access) => (access.accessCode = e.currentTarget.value),*/}
{/* );*/}
{/* }}*/}
{/* />*/}
{/* </ListItem>*/}
{/* {accessCodeComponent}*/}
{/* {!accessStore.hideUserApiKey && (*/}
{/* <>*/}
{/* {useCustomConfigComponent}*/}
{/* {accessStore.useCustomConfig && (*/}
{/* <>*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Access.Provider.Title}*/}
{/* subTitle={Locale.Settings.Access.Provider.SubTitle}*/}
{/* >*/}
{/* <Select*/}
{/* value={accessStore.provider}*/}
{/* onChange={(e) => {*/}
{/* accessStore.update(*/}
{/* (access) =>*/}
{/* (access.provider = e.target*/}
{/* .value as ServiceProvider),*/}
{/* );*/}
{/* }}*/}
{/* >*/}
{/* {Object.entries(ServiceProvider).map(([k, v]) => (*/}
{/* <option value={v} key={k}>*/}
{/* {k}*/}
{/* </option>*/}
{/* ))}*/}
{/* </Select>*/}
{/* </ListItem>*/}
{/* {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*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Access.CustomEndpoint.Title}*/}
{/* subTitle={Locale.Settings.Access.CustomEndpoint.SubTitle}*/}
{/* >*/}
{/* <input*/}
{/* type="checkbox"*/}
{/* checked={accessStore.useCustomConfig}*/}
{/* onChange={(e) =>*/}
{/* accessStore.update(*/}
{/* (access) =>*/}
{/* (access.useCustomConfig = e.currentTarget.checked),*/}
{/* {!shouldHideBalanceQuery && !clientConfig?.isApp ? (*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Usage.Title}*/}
{/* subTitle={*/}
{/* showUsage*/}
{/* ? loadingUsage*/}
{/* ? Locale.Settings.Usage.IsChecking*/}
{/* : Locale.Settings.Usage.SubTitle(*/}
{/* usage?.used ?? "[?]",*/}
{/* usage?.subscription ?? "[?]",*/}
{/* )*/}
{/* }*/}
{/* ></input>*/}
{/* </ListItem>*/}
{/* )*/}
{/* }*/}
{/* {accessStore.useCustomConfig && (*/}
{/* <>*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Access.Provider.Title}*/}
{/* subTitle={Locale.Settings.Access.Provider.SubTitle}*/}
{/* >*/}
{/* <Select*/}
{/* value={accessStore.provider}*/}
{/* onChange={(e) => {*/}
{/* accessStore.update(*/}
{/* (access) =>*/}
{/* (access.provider = e.target*/}
{/* .value as ServiceProvider),*/}
{/* );*/}
{/* }}*/}
{/* >*/}
{/* {Object.entries(ServiceProvider).map(([k, v]) => (*/}
{/* <option value={v} key={k}>*/}
{/* {k}*/}
{/* </option>*/}
{/* ))}*/}
{/* </Select>*/}
{/* </ListItem>*/}
{/* : Locale.Settings.Usage.NoAccess*/}
{/* }*/}
{/* >*/}
{/* {!showUsage || loadingUsage ? (*/}
{/* <div />*/}
{/* ) : (*/}
{/* <IconButton*/}
{/* icon={<ResetIcon></ResetIcon>}*/}
{/* text={Locale.Settings.Usage.Check}*/}
{/* onClick={() => checkUsage(true)}*/}
{/* />*/}
{/* )}*/}
{/* </ListItem>*/}
{/* ) : null}*/}
{/*{accessStore.provider === ServiceProvider.OpenAI && (*/}
{/* <>*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Access.OpenAI.Endpoint.Title}*/}
{/* subTitle={*/}
{/* Locale.Settings.Access.OpenAI.Endpoint.SubTitle*/}
{/* }*/}
{/* >*/}
{/* <input*/}
{/* type="text"*/}
{/* value={accessStore.openaiUrl}*/}
{/* placeholder={OPENAI_BASE_URL}*/}
{/* onChange={(e) =>*/}
{/* accessStore.update(*/}
{/* (access) =>*/}
{/* (access.openaiUrl = e.currentTarget.value),*/}
{/* )*/}
{/* }*/}
{/* ></input>*/}
{/* </ListItem>*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Access.OpenAI.ApiKey.Title}*/}
{/* subTitle={Locale.Settings.Access.OpenAI.ApiKey.SubTitle}*/}
{/* >*/}
{/* <PasswordInput*/}
{/* value={accessStore.openaiApiKey}*/}
{/* type="text"*/}
{/* placeholder={*/}
{/* Locale.Settings.Access.OpenAI.ApiKey.Placeholder*/}
{/* }*/}
{/* onChange={(e) => {*/}
{/* accessStore.update(*/}
{/* (access) =>*/}
{/* (access.openaiApiKey = e.currentTarget.value),*/}
{/* );*/}
{/* }}*/}
{/* />*/}
{/* </ListItem>*/}
{/* </>*/}
{/*)}*/}
{/*{accessStore.provider === ServiceProvider.Azure && (*/}
{/* <>*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Access.Azure.Endpoint.Title}*/}
{/* subTitle={*/}
{/* Locale.Settings.Access.Azure.Endpoint.SubTitle +*/}
{/* Azure.ExampleEndpoint*/}
{/* }*/}
{/* >*/}
{/* <input*/}
{/* type="text"*/}
{/* value={accessStore.azureUrl}*/}
{/* placeholder={Azure.ExampleEndpoint}*/}
{/* onChange={(e) =>*/}
{/* accessStore.update(*/}
{/* (access) =>*/}
{/* (access.azureUrl = e.currentTarget.value),*/}
{/* )*/}
{/* }*/}
{/* ></input>*/}
{/* </ListItem>*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Access.Azure.ApiKey.Title}*/}
{/* subTitle={Locale.Settings.Access.Azure.ApiKey.SubTitle}*/}
{/* >*/}
{/* <PasswordInput*/}
{/* value={accessStore.azureApiKey}*/}
{/* type="text"*/}
{/* placeholder={*/}
{/* Locale.Settings.Access.Azure.ApiKey.Placeholder*/}
{/* }*/}
{/* onChange={(e) => {*/}
{/* accessStore.update(*/}
{/* (access) =>*/}
{/* (access.azureApiKey = e.currentTarget.value),*/}
{/* );*/}
{/* }}*/}
{/* />*/}
{/* </ListItem>*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Access.Azure.ApiVerion.Title}*/}
{/* subTitle={*/}
{/* Locale.Settings.Access.Azure.ApiVerion.SubTitle*/}
{/* }*/}
{/* >*/}
{/* <input*/}
{/* type="text"*/}
{/* value={accessStore.azureApiVersion}*/}
{/* placeholder="2023-08-01-preview"*/}
{/* onChange={(e) =>*/}
{/* accessStore.update(*/}
{/* (access) =>*/}
{/* (access.azureApiVersion =*/}
{/* e.currentTarget.value),*/}
{/* )*/}
{/* }*/}
{/* ></input>*/}
{/* </ListItem>*/}
{/* </>*/}
{/*)}*/}
{/*{accessStore.provider === ServiceProvider.Google && (*/}
{/* <>*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Access.Google.Endpoint.Title}*/}
{/* subTitle={*/}
{/* Locale.Settings.Access.Google.Endpoint.SubTitle +*/}
{/* Google.ExampleEndpoint*/}
{/* }*/}
{/* >*/}
{/* <input*/}
{/* type="text"*/}
{/* value={accessStore.googleUrl}*/}
{/* placeholder={Google.ExampleEndpoint}*/}
{/* onChange={(e) =>*/}
{/* accessStore.update(*/}
{/* (access) =>*/}
{/* (access.googleUrl = e.currentTarget.value),*/}
{/* )*/}
{/* }*/}
{/* ></input>*/}
{/* </ListItem>*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Access.Google.ApiKey.Title}*/}
{/* subTitle={Locale.Settings.Access.Google.ApiKey.SubTitle}*/}
{/* >*/}
{/* <PasswordInput*/}
{/* value={accessStore.googleApiKey}*/}
{/* type="text"*/}
{/* placeholder={*/}
{/* Locale.Settings.Access.Google.ApiKey.Placeholder*/}
{/* }*/}
{/* onChange={(e) => {*/}
{/* accessStore.update(*/}
{/* (access) =>*/}
{/* (access.googleApiKey = e.currentTarget.value),*/}
{/* );*/}
{/* }}*/}
{/* />*/}
{/* </ListItem>*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Access.Google.ApiVersion.Title}*/}
{/* subTitle={*/}
{/* Locale.Settings.Access.Google.ApiVersion.SubTitle*/}
{/* }*/}
{/* >*/}
{/* <input*/}
{/* type="text"*/}
{/* value={accessStore.googleApiVersion}*/}
{/* placeholder="2023-08-01-preview"*/}
{/* onChange={(e) =>*/}
{/* accessStore.update(*/}
{/* (access) =>*/}
{/* (access.googleApiVersion =*/}
{/* e.currentTarget.value),*/}
{/* )*/}
{/* }*/}
{/* ></input>*/}
{/* </ListItem>*/}
{/* </>*/}
{/*)}*/}
{/*{accessStore.provider === ServiceProvider.Anthropic && (*/}
{/* <>*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Access.Anthropic.Endpoint.Title}*/}
{/* subTitle={*/}
{/* Locale.Settings.Access.Anthropic.Endpoint.SubTitle +*/}
{/* Anthropic.ExampleEndpoint*/}
{/* }*/}
{/* >*/}
{/* <input*/}
{/* type="text"*/}
{/* value={accessStore.anthropicUrl}*/}
{/* placeholder={Anthropic.ExampleEndpoint}*/}
{/* onChange={(e) =>*/}
{/* accessStore.update(*/}
{/* (access) =>*/}
{/* (access.anthropicUrl = e.currentTarget.value),*/}
{/* )*/}
{/* }*/}
{/* ></input>*/}
{/* </ListItem>*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Access.Anthropic.ApiKey.Title}*/}
{/* subTitle={*/}
{/* Locale.Settings.Access.Anthropic.ApiKey.SubTitle*/}
{/* }*/}
{/* >*/}
{/* <PasswordInput*/}
{/* value={accessStore.anthropicApiKey}*/}
{/* type="text"*/}
{/* placeholder={*/}
{/* Locale.Settings.Access.Anthropic.ApiKey.Placeholder*/}
{/* }*/}
{/* onChange={(e) => {*/}
{/* accessStore.update(*/}
{/* (access) =>*/}
{/* (access.anthropicApiKey =*/}
{/* e.currentTarget.value),*/}
{/* );*/}
{/* }}*/}
{/* />*/}
{/* </ListItem>*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Access.Anthropic.ApiVerion.Title}*/}
{/* subTitle={*/}
{/* Locale.Settings.Access.Anthropic.ApiVerion.SubTitle*/}
{/* }*/}
{/* >*/}
{/* <input*/}
{/* type="text"*/}
{/* value={accessStore.anthropicApiVersion}*/}
{/* placeholder={Anthropic.Vision}*/}
{/* onChange={(e) =>*/}
{/* accessStore.update(*/}
{/* (access) =>*/}
{/* (access.anthropicApiVersion =*/}
{/* e.currentTarget.value),*/}
{/* )*/}
{/* }*/}
{/* ></input>*/}
{/* </ListItem>*/}
{/* </>*/}
{/*)}*/}
{/*{accessStore.provider === ServiceProvider.Baidu && (*/}
{/* <>*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Access.Baidu.Endpoint.Title}*/}
{/* subTitle={*/}
{/* Locale.Settings.Access.Baidu.Endpoint.SubTitle*/}
{/* }*/}
{/* >*/}
{/* <input*/}
{/* type="text"*/}
{/* value={accessStore.baiduUrl}*/}
{/* placeholder={Baidu.ExampleEndpoint}*/}
{/* onChange={(e) =>*/}
{/* accessStore.update(*/}
{/* (access) =>*/}
{/* (access.baiduUrl = e.currentTarget.value),*/}
{/* )*/}
{/* }*/}
{/* ></input>*/}
{/* </ListItem>*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Access.Baidu.ApiKey.Title}*/}
{/* subTitle={Locale.Settings.Access.Baidu.ApiKey.SubTitle}*/}
{/* >*/}
{/* <PasswordInput*/}
{/* value={accessStore.baiduApiKey}*/}
{/* type="text"*/}
{/* placeholder={*/}
{/* Locale.Settings.Access.Baidu.ApiKey.Placeholder*/}
{/* }*/}
{/* onChange={(e) => {*/}
{/* accessStore.update(*/}
{/* (access) =>*/}
{/* (access.baiduApiKey = e.currentTarget.value),*/}
{/* );*/}
{/* }}*/}
{/* />*/}
{/* </ListItem>*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Access.Baidu.SecretKey.Title}*/}
{/* subTitle={*/}
{/* Locale.Settings.Access.Baidu.SecretKey.SubTitle*/}
{/* }*/}
{/* >*/}
{/* <PasswordInput*/}
{/* value={accessStore.baiduSecretKey}*/}
{/* type="text"*/}
{/* placeholder={*/}
{/* Locale.Settings.Access.Baidu.SecretKey.Placeholder*/}
{/* }*/}
{/* onChange={(e) => {*/}
{/* accessStore.update(*/}
{/* (access) =>*/}
{/* (access.baiduSecretKey = e.currentTarget.value),*/}
{/* );*/}
{/* }}*/}
{/* />*/}
{/* </ListItem>*/}
{/* </>*/}
{/*)}*/}
{/* {accessStore.provider === ServiceProvider.ByteDance && (*/}
{/* <>*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Access.ByteDance.Endpoint.Title}*/}
{/* subTitle={*/}
{/* Locale.Settings.Access.ByteDance.Endpoint.SubTitle +*/}
{/* ByteDance.ExampleEndpoint*/}
{/* }*/}
{/* >*/}
{/* <input*/}
{/* type="text"*/}
{/* value={accessStore.bytedanceUrl}*/}
{/* placeholder={ByteDance.ExampleEndpoint}*/}
{/* onChange={(e) =>*/}
{/* accessStore.update(*/}
{/* (access) =>*/}
{/* (access.bytedanceUrl = e.currentTarget.value),*/}
{/* )*/}
{/* }*/}
{/* ></input>*/}
{/* </ListItem>*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Access.ByteDance.ApiKey.Title}*/}
{/* subTitle={*/}
{/* Locale.Settings.Access.ByteDance.ApiKey.SubTitle*/}
{/* }*/}
{/* >*/}
{/* <PasswordInput*/}
{/* value={accessStore.bytedanceApiKey}*/}
{/* type="text"*/}
{/* placeholder={*/}
{/* Locale.Settings.Access.ByteDance.ApiKey.Placeholder*/}
{/* }*/}
{/* onChange={(e) => {*/}
{/* accessStore.update(*/}
{/* (access) =>*/}
{/* (access.bytedanceApiKey =*/}
{/* e.currentTarget.value),*/}
{/* );*/}
{/* }}*/}
{/* />*/}
{/* </ListItem>*/}
{/* </>*/}
{/* )}*/}
{/* {accessStore.provider === ServiceProvider.Alibaba && (*/}
{/* <>*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Access.Alibaba.Endpoint.Title}*/}
{/* subTitle={*/}
{/* Locale.Settings.Access.Alibaba.Endpoint.SubTitle +*/}
{/* Alibaba.ExampleEndpoint*/}
{/* }*/}
{/* >*/}
{/* <input*/}
{/* type="text"*/}
{/* value={accessStore.alibabaUrl}*/}
{/* placeholder={Alibaba.ExampleEndpoint}*/}
{/* onChange={(e) =>*/}
{/* accessStore.update(*/}
{/* (access) =>*/}
{/* (access.alibabaUrl = e.currentTarget.value),*/}
{/* )*/}
{/* }*/}
{/* ></input>*/}
{/* </ListItem>*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Access.Alibaba.ApiKey.Title}*/}
{/* subTitle={*/}
{/* Locale.Settings.Access.Alibaba.ApiKey.SubTitle*/}
{/* }*/}
{/* >*/}
{/* <PasswordInput*/}
{/* value={accessStore.alibabaApiKey}*/}
{/* type="text"*/}
{/* placeholder={*/}
{/* Locale.Settings.Access.Alibaba.ApiKey.Placeholder*/}
{/* }*/}
{/* onChange={(e) => {*/}
{/* accessStore.update(*/}
{/* (access) =>*/}
{/* (access.alibabaApiKey = e.currentTarget.value),*/}
{/* );*/}
{/* }}*/}
{/* />*/}
{/* </ListItem>*/}
{/* </>*/}
{/* )}*/}
{/* </>*/}
{/* )}*/}
{/* </>*/}
{/*)}*/}
{/*{!shouldHideBalanceQuery && !clientConfig?.isApp ? (*/}
{/* <ListItem*/}
{/* title={Locale.Settings.Usage.Title}*/}
{/* subTitle={*/}
{/* showUsage*/}
{/* ? loadingUsage*/}
{/* ? Locale.Settings.Usage.IsChecking*/}
{/* : Locale.Settings.Usage.SubTitle(*/}
{/* usage?.used ?? "[?]",*/}
{/* usage?.subscription ?? "[?]",*/}
{/* )*/}
{/* : Locale.Settings.Usage.NoAccess*/}
{/* }*/}
{/* title={Locale.Settings.Access.CustomModel.Title}*/}
{/* subTitle={Locale.Settings.Access.CustomModel.SubTitle}*/}
{/* >*/}
{/* {!showUsage || loadingUsage ? (*/}
{/* <div />*/}
{/* ) : (*/}
{/* <IconButton*/}
{/* icon={<ResetIcon></ResetIcon>}*/}
{/* text={Locale.Settings.Usage.Check}*/}
{/* onClick={() => checkUsage(true)}*/}
{/* />*/}
{/* )}*/}
{/* <input*/}
{/* type="text"*/}
{/* value={config.customModels}*/}
{/* placeholder="model1,model2,model3"*/}
{/* onChange={(e) =>*/}
{/* config.update(*/}
{/* (config) => (config.customModels = e.currentTarget.value),*/}
{/* )*/}
{/* }*/}
{/* ></input>*/}
{/* </ListItem>*/}
{/*) : null}*/}
{/* /!*<ListItem*!/*/}
{/* /!* title={Locale.Settings.Access.CustomModel.Title}*!/*/}
{/* /!* subTitle={Locale.Settings.Access.CustomModel.SubTitle}*!/*/}
{/* /!*>*!/*/}
{/* /!* <input*!/*/}
{/* /!* type="text"*!/*/}
{/* /!* value={config.customModels}*!/*/}
{/* /!* placeholder="model1,model2,model3"*!/*/}
{/* /!* onChange={(e) =>*!/*/}
{/* /!* config.update(*!/*/}
{/* /!* (config) => (config.customModels = e.currentTarget.value),*!/*/}
{/* /!* )*!/*/}
{/* /!* }*!/*/}
{/* /!* ></input>*!/*/}
{/* /!*</ListItem>*!/*/}
{/*</List>*/}
<List>

View File

@ -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,标准版",

View File

@ -386,6 +386,10 @@ const cn = {
Title: "API 版本(仅适用于 gemini-pro",
SubTitle: "选择一个特定的 API 版本",
},
GoogleSafetySettings: {
Title: "Google 安全过滤级别",
SubTitle: "设置内容过滤级别",
},
},
Baidu: {
ApiKey: {

View File

@ -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",
},
},
},

View File

@ -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,

View File

@ -95,9 +95,13 @@ function createEmptySession(): ChatSession {
};
}
// if it is using gpt-* models, force to use 4o-mini to summarize
const ChatFetchTaskPool: Record<string, any> = {};
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;

View File

@ -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 =

View File

@ -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";

View File

@ -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<string> {
export function compressImage(file: Blob, maxSize: number): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
@ -10,13 +11,18 @@ export function compressImage(file: File, maxSize: number): Promise<string> {
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<string> {
// 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<string, string> = {};
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<string> {
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",
});
}

View File

@ -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"
}

View File

@ -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": [
[]
]

View File

@ -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<e;n++)t+=a[63&r[n]];return t};
self.addEventListener("activate", function (event) {
console.log("ServiceWorker activated.");
});
self.addEventListener("install", function (event) {
self.skipWaiting(); // enable new version
event.waitUntil(
caches.open(CHATGPT_NEXT_WEB_CACHE).then(function (cache) {
return cache.addAll([]);
@ -12,4 +15,45 @@ self.addEventListener("install", function (event) {
);
});
self.addEventListener("fetch", (e) => {});
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))
}
}
});

View File

@ -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);
});
});
}

View File

@ -9,7 +9,7 @@
},
"package": {
"productName": "NextChat",
"version": "2.13.0"
"version": "2.13.1"
},
"tauri": {
"allowlist": {

View File

@ -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"