Merge pull request #3206 from Yidadaa/azure

This commit is contained in:
Yifei Zhang 2023-11-10 02:55:12 +08:00 committed by GitHub
commit 1141cd2e6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 513 additions and 319 deletions

View File

@ -28,7 +28,7 @@ export function auth(req: NextRequest) {
const authToken = req.headers.get("Authorization") ?? ""; const authToken = req.headers.get("Authorization") ?? "";
// check if it is openai api key or user token // check if it is openai api key or user token
const { accessCode, apiKey: token } = parseApiKey(authToken); const { accessCode, apiKey } = parseApiKey(authToken);
const hashedCode = md5.hash(accessCode ?? "").trim(); const hashedCode = md5.hash(accessCode ?? "").trim();
@ -39,7 +39,7 @@ export function auth(req: NextRequest) {
console.log("[User IP] ", getIP(req)); console.log("[User IP] ", getIP(req));
console.log("[Time] ", new Date().toLocaleString()); console.log("[Time] ", new Date().toLocaleString());
if (serverConfig.needCode && !serverConfig.codes.has(hashedCode) && !token) { if (serverConfig.needCode && !serverConfig.codes.has(hashedCode) && !apiKey) {
return { return {
error: true, error: true,
msg: !accessCode ? "empty access code" : "wrong access code", msg: !accessCode ? "empty access code" : "wrong access code",
@ -47,11 +47,17 @@ export function auth(req: NextRequest) {
} }
// if user does not provide an api key, inject system api key // if user does not provide an api key, inject system api key
if (!token) { if (!apiKey) {
const apiKey = serverConfig.apiKey; const serverApiKey = serverConfig.isAzure
if (apiKey) { ? serverConfig.azureApiKey
: serverConfig.apiKey;
if (serverApiKey) {
console.log("[Auth] use system api key"); console.log("[Auth] use system api key");
req.headers.set("Authorization", `Bearer ${apiKey}`); req.headers.set(
"Authorization",
`${serverConfig.isAzure ? "" : "Bearer "}${serverApiKey}`,
);
} else { } else {
console.log("[Auth] admin did not provide an api key"); console.log("[Auth] admin did not provide an api key");
} }

View File

@ -1,19 +1,24 @@
import { NextRequest, NextResponse } from "next/server"; import { NextRequest, NextResponse } from "next/server";
import { getServerSideConfig } from "../config/server"; import { getServerSideConfig } from "../config/server";
import { DEFAULT_MODELS, OPENAI_BASE_URL } from "../constant"; import { DEFAULT_MODELS, OPENAI_BASE_URL } from "../constant";
import { collectModelTable, collectModels } from "../utils/model"; import { collectModelTable } from "../utils/model";
import { makeAzurePath } from "../azure";
const serverConfig = getServerSideConfig(); const serverConfig = getServerSideConfig();
export async function requestOpenai(req: NextRequest) { export async function requestOpenai(req: NextRequest) {
const controller = new AbortController(); const controller = new AbortController();
const authValue = req.headers.get("Authorization") ?? ""; const authValue = req.headers.get("Authorization") ?? "";
const openaiPath = `${req.nextUrl.pathname}${req.nextUrl.search}`.replaceAll( const authHeaderName = serverConfig.isAzure ? "api-key" : "Authorization";
let path = `${req.nextUrl.pathname}${req.nextUrl.search}`.replaceAll(
"/api/openai/", "/api/openai/",
"", "",
); );
let baseUrl = serverConfig.baseUrl ?? OPENAI_BASE_URL; let baseUrl =
serverConfig.azureUrl ?? serverConfig.baseUrl ?? OPENAI_BASE_URL;
if (!baseUrl.startsWith("http")) { if (!baseUrl.startsWith("http")) {
baseUrl = `https://${baseUrl}`; baseUrl = `https://${baseUrl}`;
@ -23,7 +28,7 @@ export async function requestOpenai(req: NextRequest) {
baseUrl = baseUrl.slice(0, -1); baseUrl = baseUrl.slice(0, -1);
} }
console.log("[Proxy] ", openaiPath); console.log("[Proxy] ", path);
console.log("[Base Url]", baseUrl); console.log("[Base Url]", baseUrl);
console.log("[Org ID]", serverConfig.openaiOrgId); console.log("[Org ID]", serverConfig.openaiOrgId);
@ -34,14 +39,24 @@ export async function requestOpenai(req: NextRequest) {
10 * 60 * 1000, 10 * 60 * 1000,
); );
const fetchUrl = `${baseUrl}/${openaiPath}`; if (serverConfig.isAzure) {
if (!serverConfig.azureApiVersion) {
return NextResponse.json({
error: true,
message: `missing AZURE_API_VERSION in server env vars`,
});
}
path = makeAzurePath(path, serverConfig.azureApiVersion);
}
const fetchUrl = `${baseUrl}/${path}`;
const fetchOptions: RequestInit = { const fetchOptions: RequestInit = {
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"Cache-Control": "no-store", "Cache-Control": "no-store",
Authorization: authValue, [authHeaderName]: authValue,
...(process.env.OPENAI_ORG_ID && { ...(serverConfig.openaiOrgId && {
"OpenAI-Organization": process.env.OPENAI_ORG_ID, "OpenAI-Organization": serverConfig.openaiOrgId,
}), }),
}, },
method: req.method, method: req.method,

9
app/azure.ts Normal file
View File

@ -0,0 +1,9 @@
export function makeAzurePath(path: string, apiVersion: string) {
// should omit /v1 prefix
path = path.replaceAll("v1/", "");
// should add api-key to query string
path += `${path.includes("?") ? "&" : "?"}api-version=${apiVersion}`;
return path;
}

View File

@ -1,5 +1,5 @@
import { getClientConfig } from "../config/client"; import { getClientConfig } from "../config/client";
import { ACCESS_CODE_PREFIX } from "../constant"; import { ACCESS_CODE_PREFIX, Azure, ServiceProvider } from "../constant";
import { ChatMessage, ModelType, useAccessStore } from "../store"; import { ChatMessage, ModelType, useAccessStore } from "../store";
import { ChatGPTApi } from "./platforms/openai"; import { ChatGPTApi } from "./platforms/openai";
@ -127,22 +127,26 @@ export const api = new ClientApi();
export function getHeaders() { export function getHeaders() {
const accessStore = useAccessStore.getState(); const accessStore = useAccessStore.getState();
let headers: Record<string, string> = { const headers: Record<string, string> = {
"Content-Type": "application/json", "Content-Type": "application/json",
"x-requested-with": "XMLHttpRequest", "x-requested-with": "XMLHttpRequest",
}; };
const makeBearer = (token: string) => `Bearer ${token.trim()}`; const isAzure = accessStore.provider === ServiceProvider.Azure;
const authHeader = isAzure ? "api-key" : "Authorization";
const apiKey = isAzure ? accessStore.azureApiKey : accessStore.openaiApiKey;
const makeBearer = (s: string) => `${isAzure ? "" : "Bearer "}${s.trim()}`;
const validString = (x: string) => x && x.length > 0; const validString = (x: string) => x && x.length > 0;
// use user's api key first // use user's api key first
if (validString(accessStore.token)) { if (validString(apiKey)) {
headers.Authorization = makeBearer(accessStore.token); headers[authHeader] = makeBearer(apiKey);
} else if ( } else if (
accessStore.enabledAccessControl() && accessStore.enabledAccessControl() &&
validString(accessStore.accessCode) validString(accessStore.accessCode)
) { ) {
headers.Authorization = makeBearer( headers[authHeader] = makeBearer(
ACCESS_CODE_PREFIX + accessStore.accessCode, ACCESS_CODE_PREFIX + accessStore.accessCode,
); );
} }

View File

@ -1,8 +1,10 @@
import { import {
ApiPath,
DEFAULT_API_HOST, DEFAULT_API_HOST,
DEFAULT_MODELS, DEFAULT_MODELS,
OpenaiPath, OpenaiPath,
REQUEST_TIMEOUT_MS, REQUEST_TIMEOUT_MS,
ServiceProvider,
} from "@/app/constant"; } from "@/app/constant";
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store"; import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
@ -14,6 +16,7 @@ import {
} from "@fortaine/fetch-event-source"; } from "@fortaine/fetch-event-source";
import { prettyObject } from "@/app/utils/format"; import { prettyObject } from "@/app/utils/format";
import { getClientConfig } from "@/app/config/client"; import { getClientConfig } from "@/app/config/client";
import { makeAzurePath } from "@/app/azure";
export interface OpenAIListModelResponse { export interface OpenAIListModelResponse {
object: string; object: string;
@ -28,20 +31,35 @@ export class ChatGPTApi implements LLMApi {
private disableListModels = true; private disableListModels = true;
path(path: string): string { path(path: string): string {
let openaiUrl = useAccessStore.getState().openaiUrl; const accessStore = useAccessStore.getState();
const apiPath = "/api/openai";
if (openaiUrl.length === 0) { const isAzure = accessStore.provider === ServiceProvider.Azure;
if (isAzure && !accessStore.isValidAzure()) {
throw Error(
"incomplete azure config, please check it in your settings page",
);
}
let baseUrl = isAzure ? accessStore.azureUrl : accessStore.openaiUrl;
if (baseUrl.length === 0) {
const isApp = !!getClientConfig()?.isApp; const isApp = !!getClientConfig()?.isApp;
openaiUrl = isApp ? DEFAULT_API_HOST : apiPath; baseUrl = isApp ? DEFAULT_API_HOST : ApiPath.OpenAI;
} }
if (openaiUrl.endsWith("/")) {
openaiUrl = openaiUrl.slice(0, openaiUrl.length - 1); if (baseUrl.endsWith("/")) {
baseUrl = baseUrl.slice(0, baseUrl.length - 1);
} }
if (!openaiUrl.startsWith("http") && !openaiUrl.startsWith(apiPath)) { if (!baseUrl.startsWith("http") && !baseUrl.startsWith(ApiPath.OpenAI)) {
openaiUrl = "https://" + openaiUrl; baseUrl = "https://" + baseUrl;
} }
return [openaiUrl, path].join("/");
if (isAzure) {
path = makeAzurePath(path, accessStore.azureApiVersion);
}
return [baseUrl, path].join("/");
} }
extractMessage(res: any) { extractMessage(res: any) {
@ -156,14 +174,20 @@ export class ChatGPTApi implements LLMApi {
} }
const text = msg.data; const text = msg.data;
try { try {
const json = JSON.parse(text); const json = JSON.parse(text) as {
const delta = json.choices[0].delta.content; choices: Array<{
delta: {
content: string;
};
}>;
};
const delta = json.choices[0]?.delta?.content;
if (delta) { if (delta) {
responseText += delta; responseText += delta;
options.onUpdate?.(responseText, delta); options.onUpdate?.(responseText, delta);
} }
} catch (e) { } catch (e) {
console.error("[Request] parse error", text, msg); console.error("[Request] parse error", text);
} }
}, },
onclose() { onclose() {

View File

@ -18,7 +18,7 @@ export function AuthPage() {
const goChat = () => navigate(Path.Chat); const goChat = () => navigate(Path.Chat);
const resetAccessCode = () => { const resetAccessCode = () => {
accessStore.update((access) => { accessStore.update((access) => {
access.token = ""; access.openaiApiKey = "";
access.accessCode = ""; access.accessCode = "";
}); });
}; // Reset access code to empty string }; // Reset access code to empty string
@ -56,11 +56,11 @@ export function AuthPage() {
<input <input
className={styles["auth-input"]} className={styles["auth-input"]}
type="password" type="password"
placeholder={Locale.Settings.Token.Placeholder} placeholder={Locale.Settings.Access.OpenAI.ApiKey.Placeholder}
value={accessStore.token} value={accessStore.openaiApiKey}
onChange={(e) => { onChange={(e) => {
accessStore.update( accessStore.update(
(access) => (access.token = e.currentTarget.value), (access) => (access.openaiApiKey = e.currentTarget.value),
); );
}} }}
/> />

View File

@ -998,7 +998,9 @@ function _Chat() {
).then((res) => { ).then((res) => {
if (!res) return; if (!res) return;
if (payload.key) { if (payload.key) {
accessStore.update((access) => (access.token = payload.key!)); accessStore.update(
(access) => (access.openaiApiKey = payload.key!),
);
} }
if (payload.url) { if (payload.url) {
accessStore.update((access) => (access.openaiUrl = payload.url!)); accessStore.update((access) => (access.openaiUrl = payload.url!));

View File

@ -51,10 +51,13 @@ import Locale, {
import { copyToClipboard } from "../utils"; import { copyToClipboard } from "../utils";
import Link from "next/link"; import Link from "next/link";
import { import {
Azure,
OPENAI_BASE_URL, OPENAI_BASE_URL,
Path, Path,
RELEASE_URL, RELEASE_URL,
STORAGE_KEY, STORAGE_KEY,
ServiceProvider,
SlotID,
UPDATE_URL, UPDATE_URL,
} from "../constant"; } from "../constant";
import { Prompt, SearchService, usePromptStore } from "../store/prompt"; import { Prompt, SearchService, usePromptStore } from "../store/prompt";
@ -580,8 +583,16 @@ export function Settings() {
const accessStore = useAccessStore(); const accessStore = useAccessStore();
const shouldHideBalanceQuery = useMemo(() => { const shouldHideBalanceQuery = useMemo(() => {
const isOpenAiUrl = accessStore.openaiUrl.includes(OPENAI_BASE_URL); const isOpenAiUrl = accessStore.openaiUrl.includes(OPENAI_BASE_URL);
return accessStore.hideBalanceQuery || isOpenAiUrl; return (
}, [accessStore.hideBalanceQuery, accessStore.openaiUrl]); accessStore.hideBalanceQuery ||
isOpenAiUrl ||
accessStore.provider === ServiceProvider.Azure
);
}, [
accessStore.hideBalanceQuery,
accessStore.openaiUrl,
accessStore.provider,
]);
const usage = { const usage = {
used: updateStore.used, used: updateStore.used,
@ -877,16 +888,16 @@ export function Settings() {
</ListItem> </ListItem>
</List> </List>
<List> <List id={SlotID.CustomModel}>
{showAccessCode ? ( {showAccessCode && (
<ListItem <ListItem
title={Locale.Settings.AccessCode.Title} title={Locale.Settings.Access.AccessCode.Title}
subTitle={Locale.Settings.AccessCode.SubTitle} subTitle={Locale.Settings.Access.AccessCode.SubTitle}
> >
<PasswordInput <PasswordInput
value={accessStore.accessCode} value={accessStore.accessCode}
type="text" type="text"
placeholder={Locale.Settings.AccessCode.Placeholder} placeholder={Locale.Settings.Access.AccessCode.Placeholder}
onChange={(e) => { onChange={(e) => {
accessStore.update( accessStore.update(
(access) => (access.accessCode = e.currentTarget.value), (access) => (access.accessCode = e.currentTarget.value),
@ -894,44 +905,152 @@ export function Settings() {
}} }}
/> />
</ListItem> </ListItem>
) : (
<></>
)} )}
{!accessStore.hideUserApiKey ? ( {!accessStore.hideUserApiKey && (
<> <>
<ListItem <ListItem
title={Locale.Settings.Endpoint.Title} title={Locale.Settings.Access.CustomEndpoint.Title}
subTitle={Locale.Settings.Endpoint.SubTitle} subTitle={Locale.Settings.Access.CustomEndpoint.SubTitle}
> >
<input <input
type="text" type="checkbox"
value={accessStore.openaiUrl} checked={accessStore.useCustomConfig}
placeholder="https://api.openai.com/"
onChange={(e) => onChange={(e) =>
accessStore.update( accessStore.update(
(access) => (access.openaiUrl = e.currentTarget.value), (access) =>
(access.useCustomConfig = e.currentTarget.checked),
) )
} }
></input> ></input>
</ListItem> </ListItem>
<ListItem {accessStore.useCustomConfig && (
title={Locale.Settings.Token.Title} <>
subTitle={Locale.Settings.Token.SubTitle} <ListItem
> title={Locale.Settings.Access.Provider.Title}
<PasswordInput subTitle={Locale.Settings.Access.Provider.SubTitle}
value={accessStore.token} >
type="text" <Select
placeholder={Locale.Settings.Token.Placeholder} value={accessStore.provider}
onChange={(e) => { onChange={(e) => {
accessStore.update( accessStore.update(
(access) => (access.token = e.currentTarget.value), (access) =>
); (access.provider = e.target
}} .value as ServiceProvider),
/> );
</ListItem> }}
>
{Object.entries(ServiceProvider).map(([k, v]) => (
<option value={v} key={k}>
{k}
</option>
))}
</Select>
</ListItem>
{accessStore.provider === "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>
</>
) : (
<>
<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>
</>
)}
</>
)}
</> </>
) : null} )}
{!shouldHideBalanceQuery ? ( {!shouldHideBalanceQuery ? (
<ListItem <ListItem
@ -960,8 +1079,8 @@ export function Settings() {
) : null} ) : null}
<ListItem <ListItem
title={Locale.Settings.CustomModel.Title} title={Locale.Settings.Access.CustomModel.Title}
subTitle={Locale.Settings.CustomModel.SubTitle} subTitle={Locale.Settings.Access.CustomModel.SubTitle}
> >
<input <input
type="text" type="text"

View File

@ -235,7 +235,7 @@
.select-with-icon-select { .select-with-icon-select {
height: 100%; height: 100%;
border: var(--border-in-light); border: var(--border-in-light);
padding: 10px 25px 10px 10px; padding: 10px 35px 10px 10px;
border-radius: 10px; border-radius: 10px;
appearance: none; appearance: none;
cursor: pointer; cursor: pointer;

View File

@ -70,14 +70,12 @@ export function ListItem(props: {
); );
} }
export function List(props: { export function List(props: { children: React.ReactNode; id?: string }) {
children: return (
| Array<JSX.Element | null | undefined> <div className={styles.list} id={props.id}>
| JSX.Element {props.children}
| null </div>
| undefined; );
}) {
return <div className={styles.list}>{props.children}</div>;
} }
export function Loading() { export function Loading() {

View File

@ -4,19 +4,28 @@ import { DEFAULT_MODELS } from "../constant";
declare global { declare global {
namespace NodeJS { namespace NodeJS {
interface ProcessEnv { interface ProcessEnv {
PROXY_URL?: string; // docker only
OPENAI_API_KEY?: string; OPENAI_API_KEY?: string;
CODE?: string; CODE?: string;
BASE_URL?: string; BASE_URL?: string;
PROXY_URL?: string; OPENAI_ORG_ID?: string; // openai only
OPENAI_ORG_ID?: string;
VERCEL?: string; VERCEL?: string;
HIDE_USER_API_KEY?: string; // disable user's api key input
DISABLE_GPT4?: string; // allow user to use gpt-4 or not
BUILD_MODE?: "standalone" | "export"; BUILD_MODE?: "standalone" | "export";
BUILD_APP?: string; // is building desktop app BUILD_APP?: string; // is building desktop app
HIDE_USER_API_KEY?: string; // disable user's api key input
DISABLE_GPT4?: string; // allow user to use gpt-4 or not
ENABLE_BALANCE_QUERY?: string; // allow user to query balance or not ENABLE_BALANCE_QUERY?: string; // allow user to query balance or not
DISABLE_FAST_LINK?: string; // disallow parse settings from url or not DISABLE_FAST_LINK?: string; // disallow parse settings from url or not
CUSTOM_MODELS?: string; // to control custom models CUSTOM_MODELS?: string; // to control custom models
// azure only
AZURE_URL?: string; // https://{azure-url}/openai/deployments/{deploy-name}
AZURE_API_KEY?: string;
AZURE_API_VERSION?: string;
} }
} }
} }
@ -41,7 +50,7 @@ export const getServerSideConfig = () => {
); );
} }
let disableGPT4 = !!process.env.DISABLE_GPT4; const disableGPT4 = !!process.env.DISABLE_GPT4;
let customModels = process.env.CUSTOM_MODELS ?? ""; let customModels = process.env.CUSTOM_MODELS ?? "";
if (disableGPT4) { if (disableGPT4) {
@ -51,15 +60,25 @@ export const getServerSideConfig = () => {
.join(","); .join(",");
} }
const isAzure = !!process.env.AZURE_URL;
return { return {
baseUrl: process.env.BASE_URL,
apiKey: process.env.OPENAI_API_KEY, apiKey: process.env.OPENAI_API_KEY,
openaiOrgId: process.env.OPENAI_ORG_ID,
isAzure,
azureUrl: process.env.AZURE_URL,
azureApiKey: process.env.AZURE_API_KEY,
azureApiVersion: process.env.AZURE_API_VERSION,
needCode: ACCESS_CODES.size > 0,
code: process.env.CODE, code: process.env.CODE,
codes: ACCESS_CODES, codes: ACCESS_CODES,
needCode: ACCESS_CODES.size > 0,
baseUrl: process.env.BASE_URL,
proxyUrl: process.env.PROXY_URL, proxyUrl: process.env.PROXY_URL,
openaiOrgId: process.env.OPENAI_ORG_ID,
isVercel: !!process.env.VERCEL, isVercel: !!process.env.VERCEL,
hideUserApiKey: !!process.env.HIDE_USER_API_KEY, hideUserApiKey: !!process.env.HIDE_USER_API_KEY,
disableGPT4, disableGPT4,
hideBalanceQuery: !process.env.ENABLE_BALANCE_QUERY, hideBalanceQuery: !process.env.ENABLE_BALANCE_QUERY,

View File

@ -23,10 +23,12 @@ export enum Path {
export enum ApiPath { export enum ApiPath {
Cors = "/api/cors", Cors = "/api/cors",
OpenAI = "/api/openai",
} }
export enum SlotID { export enum SlotID {
AppBody = "app-body", AppBody = "app-body",
CustomModel = "custom-model",
} }
export enum FileName { export enum FileName {
@ -60,6 +62,11 @@ export const REQUEST_TIMEOUT_MS = 60000;
export const EXPORT_MESSAGE_CLASS_NAME = "export-markdown"; export const EXPORT_MESSAGE_CLASS_NAME = "export-markdown";
export enum ServiceProvider {
OpenAI = "OpenAI",
Azure = "Azure",
}
export const OpenaiPath = { export const OpenaiPath = {
ChatPath: "v1/chat/completions", ChatPath: "v1/chat/completions",
UsagePath: "dashboard/billing/usage", UsagePath: "dashboard/billing/usage",
@ -67,6 +74,10 @@ export const OpenaiPath = {
ListModelPath: "v1/models", ListModelPath: "v1/models",
}; };
export const Azure = {
ExampleEndpoint: "https://{resource-url}/openai/deployments/{deploy-id}",
};
export const DEFAULT_INPUT_TEMPLATE = `{{input}}`; // input / time / model / lang export const DEFAULT_INPUT_TEMPLATE = `{{input}}`; // input / time / model / lang
export const DEFAULT_SYSTEM_TEMPLATE = ` export const DEFAULT_SYSTEM_TEMPLATE = `
You are ChatGPT, a large language model trained by OpenAI. You are ChatGPT, a large language model trained by OpenAI.

View File

@ -167,11 +167,7 @@ ${builtin} مدمجة، ${custom} تم تعريفها من قبل المستخد
Title: "حد الضغط للتاريخ", Title: "حد الضغط للتاريخ",
SubTitle: "سيتم الضغط إذا تجاوزت طول الرسائل غير المضغوطة الحد المحدد", SubTitle: "سيتم الضغط إذا تجاوزت طول الرسائل غير المضغوطة الحد المحدد",
}, },
Token: {
Title: "مفتاح API",
SubTitle: "استخدم مفتاحك لتجاوز حد رمز الوصول",
Placeholder: "مفتاح OpenAI API",
},
Usage: { Usage: {
Title: "رصيد الحساب", Title: "رصيد الحساب",
SubTitle(used: any, total: any) { SubTitle(used: any, total: any) {
@ -181,15 +177,7 @@ ${builtin} مدمجة، ${custom} تم تعريفها من قبل المستخد
Check: "التحقق", Check: "التحقق",
NoAccess: "أدخل مفتاح API للتحقق من الرصيد", NoAccess: "أدخل مفتاح API للتحقق من الرصيد",
}, },
AccessCode: {
Title: "رمز الوصول",
SubTitle: "تم تمكين التحكم في الوصول",
Placeholder: "رمز الوصول المطلوب",
},
Endpoint: {
Title: "نقطة النهاية",
SubTitle: "يجب أن تبدأ نقطة النهاية المخصصة بـ http(s)://",
},
Model: "النموذج", Model: "النموذج",
Temperature: { Temperature: {
Title: "الحرارة", Title: "الحرارة",

View File

@ -199,11 +199,7 @@ const bn: PartialLocaleType = {
SubTitle: SubTitle:
"নকুল বার্তা দৈর্ঘ্য সীমা অতিক্রান্ত হলে ঐ বার্তাটি সঙ্কুচিত হবে", "নকুল বার্তা দৈর্ঘ্য সীমা অতিক্রান্ত হলে ঐ বার্তাটি সঙ্কুচিত হবে",
}, },
Token: {
Title: "অ্যাপি কী",
SubTitle: "অ্যাক্সেস কোড সীমা উপেক্ষা করতে আপনার কীটি ব্যবহার করুন",
Placeholder: "OpenAI API কী",
},
Usage: { Usage: {
Title: "একাউন্ট ব্যালেন্স", Title: "একাউন্ট ব্যালেন্স",
SubTitle(used: any, total: any) { SubTitle(used: any, total: any) {
@ -213,15 +209,7 @@ const bn: PartialLocaleType = {
Check: "চেক", Check: "চেক",
NoAccess: "ব্যালেন্স চেক করতে অ্যাপি কী ইনপুট করুন", NoAccess: "ব্যালেন্স চেক করতে অ্যাপি কী ইনপুট করুন",
}, },
AccessCode: {
Title: "অ্যাক্সেস কোড",
SubTitle: "অ্যাক্সেস নিয়ন্ত্রণ সক্রিয়",
Placeholder: "অ্যাক্সেস কোড প্রয়োজন",
},
Endpoint: {
Title: "ইনটারপয়েন্ট",
SubTitle: "কাস্টম এন্ডপয়েন্টটি হতে হবে http(s):// দিয়ে শুরু হতে হবে",
},
Model: "মডেল", Model: "মডেল",
Temperature: { Temperature: {
Title: "তাপমাত্রা", Title: "তাপমাত্রা",

View File

@ -258,11 +258,6 @@ const cn = {
Title: "历史消息长度压缩阈值", Title: "历史消息长度压缩阈值",
SubTitle: "当未压缩的历史消息超过该值时,将进行压缩", SubTitle: "当未压缩的历史消息超过该值时,将进行压缩",
}, },
Token: {
Title: "API Key",
SubTitle: "使用自己的 Key 可绕过密码访问限制",
Placeholder: "OpenAI API Key",
},
Usage: { Usage: {
Title: "余额查询", Title: "余额查询",
@ -273,19 +268,56 @@ const cn = {
Check: "重新检查", Check: "重新检查",
NoAccess: "输入 API Key 或访问密码查看余额", NoAccess: "输入 API Key 或访问密码查看余额",
}, },
AccessCode: {
Title: "访问密码", Access: {
SubTitle: "管理员已开启加密访问", AccessCode: {
Placeholder: "请输入访问密码", Title: "访问密码",
}, SubTitle: "管理员已开启加密访问",
Endpoint: { Placeholder: "请输入访问密码",
Title: "接口地址", },
SubTitle: "除默认地址外,必须包含 http(s)://", CustomEndpoint: {
}, Title: "自定义接口",
CustomModel: { SubTitle: "是否使用自定义 Azure 或 OpenAI 服务",
Title: "自定义模型名", },
SubTitle: "增加自定义模型可选项,使用英文逗号隔开", Provider: {
Title: "模型服务商",
SubTitle: "切换不同的服务商",
},
OpenAI: {
ApiKey: {
Title: "API Key",
SubTitle: "使用自定义 OpenAI Key 绕过密码访问限制",
Placeholder: "OpenAI API Key",
},
Endpoint: {
Title: "接口地址",
SubTitle: "除默认地址外,必须包含 http(s)://",
},
},
Azure: {
ApiKey: {
Title: "接口密钥",
SubTitle: "使用自定义 Azure Key 绕过密码访问限制",
Placeholder: "Azure API Key",
},
Endpoint: {
Title: "接口地址",
SubTitle: "样例:",
},
ApiVerion: {
Title: "接口版本 (azure api version)",
SubTitle: "选择指定的部分版本",
},
},
CustomModel: {
Title: "自定义模型名",
SubTitle: "增加自定义模型可选项,使用英文逗号隔开",
},
}, },
Model: "模型 (model)", Model: "模型 (model)",
Temperature: { Temperature: {
Title: "随机性 (temperature)", Title: "随机性 (temperature)",

View File

@ -124,11 +124,7 @@ const cs: PartialLocaleType = {
SubTitle: SubTitle:
"Komprese proběhne, pokud délka nekomprimovaných zpráv přesáhne tuto hodnotu", "Komprese proběhne, pokud délka nekomprimovaných zpráv přesáhne tuto hodnotu",
}, },
Token: {
Title: "API klíč",
SubTitle: "Použitím klíče ignorujete omezení přístupového kódu",
Placeholder: "Klíč API OpenAI",
},
Usage: { Usage: {
Title: "Stav účtu", Title: "Stav účtu",
SubTitle(used: any, total: any) { SubTitle(used: any, total: any) {
@ -138,11 +134,7 @@ const cs: PartialLocaleType = {
Check: "Zkontrolovat", Check: "Zkontrolovat",
NoAccess: "Pro kontrolu zůstatku zadejte klíč API", NoAccess: "Pro kontrolu zůstatku zadejte klíč API",
}, },
AccessCode: {
Title: "Přístupový kód",
SubTitle: "Kontrola přístupu povolena",
Placeholder: "Potřebujete přístupový kód",
},
Model: "Model", Model: "Model",
Temperature: { Temperature: {
Title: "Teplota", Title: "Teplota",

View File

@ -124,12 +124,7 @@ const de: PartialLocaleType = {
SubTitle: SubTitle:
"Komprimierung, wenn die Länge der unkomprimierten Nachrichten den Wert überschreitet", "Komprimierung, wenn die Länge der unkomprimierten Nachrichten den Wert überschreitet",
}, },
Token: {
Title: "API-Schlüssel",
SubTitle:
"Verwenden Sie Ihren Schlüssel, um das Zugangscode-Limit zu ignorieren",
Placeholder: "OpenAI API-Schlüssel",
},
Usage: { Usage: {
Title: "Kontostand", Title: "Kontostand",
SubTitle(used: any, total: any) { SubTitle(used: any, total: any) {
@ -139,11 +134,6 @@ const de: PartialLocaleType = {
Check: "Erneut prüfen", Check: "Erneut prüfen",
NoAccess: "API-Schlüssel eingeben, um den Kontostand zu überprüfen", NoAccess: "API-Schlüssel eingeben, um den Kontostand zu überprüfen",
}, },
AccessCode: {
Title: "Zugangscode",
SubTitle: "Zugangskontrolle aktiviert",
Placeholder: "Zugangscode erforderlich",
},
Model: "Modell", Model: "Modell",
Temperature: { Temperature: {
Title: "Temperature", //Temperatur Title: "Temperature", //Temperatur

View File

@ -262,11 +262,7 @@ const en: LocaleType = {
SubTitle: SubTitle:
"Will compress if uncompressed messages length exceeds the value", "Will compress if uncompressed messages length exceeds the value",
}, },
Token: {
Title: "API Key",
SubTitle: "Use your key to ignore access code limit",
Placeholder: "OpenAI API Key",
},
Usage: { Usage: {
Title: "Account Balance", Title: "Account Balance",
SubTitle(used: any, total: any) { SubTitle(used: any, total: any) {
@ -276,19 +272,55 @@ const en: LocaleType = {
Check: "Check", Check: "Check",
NoAccess: "Enter API Key to check balance", NoAccess: "Enter API Key to check balance",
}, },
AccessCode: { Access: {
Title: "Access Code", AccessCode: {
SubTitle: "Access control enabled", Title: "Access Code",
Placeholder: "Need Access Code", SubTitle: "Access control Enabled",
}, Placeholder: "Enter Code",
Endpoint: { },
Title: "Endpoint", CustomEndpoint: {
SubTitle: "Custom endpoint must start with http(s)://", Title: "Custom Endpoint",
}, SubTitle: "Use custom Azure or OpenAI service",
CustomModel: { },
Title: "Custom Models", Provider: {
SubTitle: "Add extra model options, separate by comma", Title: "Model Provider",
SubTitle: "Select Azure or OpenAI",
},
OpenAI: {
ApiKey: {
Title: "OpenAI API Key",
SubTitle: "User custom OpenAI Api Key",
Placeholder: "sk-xxx",
},
Endpoint: {
Title: "OpenAI Endpoint",
SubTitle: "Must starts with http(s):// or use /api/openai as default",
},
},
Azure: {
ApiKey: {
Title: "Azure Api Key",
SubTitle: "Check your api key from Azure console",
Placeholder: "Azure Api Key",
},
Endpoint: {
Title: "Azure Endpoint",
SubTitle: "Example: ",
},
ApiVerion: {
Title: "Azure Api Version",
SubTitle: "Check your api version from azure console",
},
},
CustomModel: {
Title: "Custom Models",
SubTitle: "Custom model options, seperated by comma",
},
}, },
Model: "Model", Model: "Model",
Temperature: { Temperature: {
Title: "Temperature", Title: "Temperature",

View File

@ -124,11 +124,7 @@ const es: PartialLocaleType = {
SubTitle: SubTitle:
"Se comprimirán los mensajes si la longitud de los mensajes no comprimidos supera el valor", "Se comprimirán los mensajes si la longitud de los mensajes no comprimidos supera el valor",
}, },
Token: {
Title: "Clave de API",
SubTitle: "Utiliza tu clave para ignorar el límite de código de acceso",
Placeholder: "Clave de la API de OpenAI",
},
Usage: { Usage: {
Title: "Saldo de la cuenta", Title: "Saldo de la cuenta",
SubTitle(used: any, total: any) { SubTitle(used: any, total: any) {
@ -138,11 +134,7 @@ const es: PartialLocaleType = {
Check: "Comprobar de nuevo", Check: "Comprobar de nuevo",
NoAccess: "Introduzca la clave API para comprobar el saldo", NoAccess: "Introduzca la clave API para comprobar el saldo",
}, },
AccessCode: {
Title: "Código de acceso",
SubTitle: "Control de acceso habilitado",
Placeholder: "Necesita código de acceso",
},
Model: "Modelo", Model: "Modelo",
Temperature: { Temperature: {
Title: "Temperatura", Title: "Temperatura",

View File

@ -173,11 +173,7 @@ const fr: PartialLocaleType = {
SubTitle: SubTitle:
"Comprimera si la longueur des messages non compressés dépasse cette valeur", "Comprimera si la longueur des messages non compressés dépasse cette valeur",
}, },
Token: {
Title: "Clé API",
SubTitle: "Utilisez votre clé pour ignorer la limite du code d'accès",
Placeholder: "Clé OpenAI API",
},
Usage: { Usage: {
Title: "Solde du compte", Title: "Solde du compte",
SubTitle(used: any, total: any) { SubTitle(used: any, total: any) {
@ -187,11 +183,7 @@ const fr: PartialLocaleType = {
Check: "Vérifier", Check: "Vérifier",
NoAccess: "Entrez la clé API pour vérifier le solde", NoAccess: "Entrez la clé API pour vérifier le solde",
}, },
AccessCode: {
Title: "Code d'accès",
SubTitle: "Contrôle d'accès activé",
Placeholder: "Code d'accès requis",
},
Model: "Modèle", Model: "Modèle",
Temperature: { Temperature: {
Title: "Température", Title: "Température",

View File

@ -4,8 +4,9 @@ import { PartialLocaleType } from "./index";
const id: PartialLocaleType = { const id: PartialLocaleType = {
WIP: "Coming Soon...", WIP: "Coming Soon...",
Error: { Error: {
Unauthorized: "Akses tidak diizinkan, silakan masukkan kode akses atau masukkan kunci API OpenAI Anda. di halaman [autentikasi](/#/auth) atau di halaman [Pengaturan](/#/settings).", Unauthorized:
}, "Akses tidak diizinkan, silakan masukkan kode akses atau masukkan kunci API OpenAI Anda. di halaman [autentikasi](/#/auth) atau di halaman [Pengaturan](/#/settings).",
},
Auth: { Auth: {
Title: "Diperlukan Kode Akses", Title: "Diperlukan Kode Akses",
Tips: "Masukkan kode akses di bawah", Tips: "Masukkan kode akses di bawah",
@ -237,11 +238,7 @@ const id: PartialLocaleType = {
SubTitle: SubTitle:
"Jika panjang pesan melebihi batas yang ditentukan, pesan tersebut akan dikompresi", "Jika panjang pesan melebihi batas yang ditentukan, pesan tersebut akan dikompresi",
}, },
Token: {
Title: "Kunci API",
SubTitle: "Gunakan kunci Anda untuk melewati batas kode akses",
Placeholder: "Kunci API OpenAI",
},
Usage: { Usage: {
Title: "Saldo Akun", Title: "Saldo Akun",
SubTitle(used: any, total: any) { SubTitle(used: any, total: any) {
@ -251,15 +248,7 @@ const id: PartialLocaleType = {
Check: "Periksa", Check: "Periksa",
NoAccess: "Masukkan kunci API untuk memeriksa saldo", NoAccess: "Masukkan kunci API untuk memeriksa saldo",
}, },
AccessCode: {
Title: "Kode Akses",
SubTitle: "Kontrol akses diaktifkan",
Placeholder: "Diperlukan kode akses",
},
Endpoint: {
Title: "Endpoint",
SubTitle: "Harus dimulai dengan http(s):// untuk endpoint kustom",
},
Model: "Model", Model: "Model",
Temperature: { Temperature: {
Title: "Suhu", Title: "Suhu",

View File

@ -124,12 +124,7 @@ const it: PartialLocaleType = {
SubTitle: SubTitle:
"Comprimerà se la lunghezza dei messaggi non compressi supera il valore", "Comprimerà se la lunghezza dei messaggi non compressi supera il valore",
}, },
Token: {
Title: "API Key",
SubTitle:
"Utilizzare la chiave per ignorare il limite del codice di accesso",
Placeholder: "OpenAI API Key",
},
Usage: { Usage: {
Title: "Bilancio Account", Title: "Bilancio Account",
SubTitle(used: any, total: any) { SubTitle(used: any, total: any) {
@ -139,11 +134,7 @@ const it: PartialLocaleType = {
Check: "Controlla ancora", Check: "Controlla ancora",
NoAccess: "Inserire la chiave API per controllare il saldo", NoAccess: "Inserire la chiave API per controllare il saldo",
}, },
AccessCode: {
Title: "Codice d'accesso",
SubTitle: "Controllo d'accesso abilitato",
Placeholder: "Inserisci il codice d'accesso",
},
Model: "Modello GPT", Model: "Modello GPT",
Temperature: { Temperature: {
Title: "Temperature", Title: "Temperature",

View File

@ -20,7 +20,8 @@ const jp: PartialLocaleType = {
Stop: "停止", Stop: "停止",
Retry: "リトライ", Retry: "リトライ",
Pin: "ピン", Pin: "ピン",
PinToastContent: "コンテキストプロンプトに1つのメッセージをピン留めしました", PinToastContent:
"コンテキストプロンプトに1つのメッセージをピン留めしました",
PinToastAction: "表示", PinToastAction: "表示",
Delete: "削除", Delete: "削除",
Edit: "編集", Edit: "編集",
@ -146,11 +147,7 @@ const jp: PartialLocaleType = {
SubTitle: SubTitle:
"圧縮されていない履歴メッセージがこの値を超えた場合、圧縮が行われます。", "圧縮されていない履歴メッセージがこの値を超えた場合、圧縮が行われます。",
}, },
Token: {
Title: "APIキー",
SubTitle: "自分のキーを使用してパスワードアクセス制限を迂回する",
Placeholder: "OpenAI APIキー",
},
Usage: { Usage: {
Title: "残高照会", Title: "残高照会",
SubTitle(used: any, total: any) { SubTitle(used: any, total: any) {
@ -160,11 +157,7 @@ const jp: PartialLocaleType = {
Check: "再確認", Check: "再確認",
NoAccess: "APIキーまたはアクセスパスワードを入力して残高を表示", NoAccess: "APIキーまたはアクセスパスワードを入力して残高を表示",
}, },
AccessCode: {
Title: "アクセスパスワード",
SubTitle: "暗号化アクセスが有効になっています",
Placeholder: "アクセスパスワードを入力してください",
},
Model: "モデル (model)", Model: "モデル (model)",
Temperature: { Temperature: {
Title: "ランダム性 (temperature)", Title: "ランダム性 (temperature)",

View File

@ -124,11 +124,7 @@ const ko: PartialLocaleType = {
Title: "기록 압축 임계값", Title: "기록 압축 임계값",
SubTitle: "미압축 메시지 길이가 임계값을 초과하면 압축됨", SubTitle: "미압축 메시지 길이가 임계값을 초과하면 압축됨",
}, },
Token: {
Title: "API 키",
SubTitle: "액세스 코드 제한을 무시하기 위해 키 사용",
Placeholder: "OpenAI API 키",
},
Usage: { Usage: {
Title: "계정 잔액", Title: "계정 잔액",
SubTitle(used: any, total: any) { SubTitle(used: any, total: any) {
@ -138,11 +134,7 @@ const ko: PartialLocaleType = {
Check: "확인", Check: "확인",
NoAccess: "잔액 확인을 위해 API 키를 입력하세요.", NoAccess: "잔액 확인을 위해 API 키를 입력하세요.",
}, },
AccessCode: {
Title: "액세스 코드",
SubTitle: "액세스 제어가 활성화됨",
Placeholder: "액세스 코드 입력",
},
Model: "모델", Model: "모델",
Temperature: { Temperature: {
Title: "온도 (temperature)", Title: "온도 (temperature)",

View File

@ -106,12 +106,7 @@ const no: PartialLocaleType = {
SubTitle: SubTitle:
"Komprimer dersom ikke-komprimert lengde på meldinger overskrider denne verdien", "Komprimer dersom ikke-komprimert lengde på meldinger overskrider denne verdien",
}, },
Token: {
Title: "API Key",
SubTitle:
"Bruk din egen API-nøkkel for å ignorere tilgangskoden begrensning",
Placeholder: "OpenAI API-nøkkel",
},
Usage: { Usage: {
Title: "Saldo for konto", Title: "Saldo for konto",
SubTitle(used: any, total: any) { SubTitle(used: any, total: any) {
@ -121,11 +116,7 @@ const no: PartialLocaleType = {
Check: "Sjekk", Check: "Sjekk",
NoAccess: "Skriv inn API-nøkkelen for å sjekke saldo", NoAccess: "Skriv inn API-nøkkelen for å sjekke saldo",
}, },
AccessCode: {
Title: "Tilgangskode",
SubTitle: "Tilgangskontroll på",
Placeholder: "Trenger tilgangskode",
},
Model: "Model", Model: "Model",
Temperature: { Temperature: {
Title: "Temperatur", Title: "Temperatur",

View File

@ -125,11 +125,7 @@ const ru: PartialLocaleType = {
SubTitle: SubTitle:
"Будет сжимать, если длина несжатых сообщений превышает указанное значение", "Будет сжимать, если длина несжатых сообщений превышает указанное значение",
}, },
Token: {
Title: "API ключ",
SubTitle: "Используйте свой ключ, чтобы игнорировать лимит доступа",
Placeholder: "API ключ OpenAI",
},
Usage: { Usage: {
Title: "Баланс аккаунта", Title: "Баланс аккаунта",
SubTitle(used: any, total: any) { SubTitle(used: any, total: any) {
@ -139,11 +135,7 @@ const ru: PartialLocaleType = {
Check: "Проверить", Check: "Проверить",
NoAccess: "Введите API ключ, чтобы проверить баланс", NoAccess: "Введите API ключ, чтобы проверить баланс",
}, },
AccessCode: {
Title: "Код доступа",
SubTitle: "Контроль доступа включен",
Placeholder: "Требуется код доступа",
},
Model: "Модель", Model: "Модель",
Temperature: { Temperature: {
Title: "Температура", Title: "Температура",

View File

@ -124,11 +124,7 @@ const tr: PartialLocaleType = {
SubTitle: SubTitle:
"Sıkıştırılmamış mesajların uzunluğu bu değeri aşarsa sıkıştırılır", "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: { Usage: {
Title: "Hesap Bakiyesi", Title: "Hesap Bakiyesi",
SubTitle(used: any, total: any) { SubTitle(used: any, total: any) {
@ -138,11 +134,7 @@ const tr: PartialLocaleType = {
Check: "Tekrar Kontrol Et", Check: "Tekrar Kontrol Et",
NoAccess: "Bakiyeyi kontrol etmek için API anahtarını girin", 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", Model: "Model",
Temperature: { Temperature: {
Title: "Gerçeklik", Title: "Gerçeklik",

View File

@ -120,11 +120,7 @@ const tw: PartialLocaleType = {
Title: "歷史訊息長度壓縮閾值", Title: "歷史訊息長度壓縮閾值",
SubTitle: "當未壓縮的歷史訊息超過該值時,將進行壓縮", SubTitle: "當未壓縮的歷史訊息超過該值時,將進行壓縮",
}, },
Token: {
Title: "API Key",
SubTitle: "使用自己的 Key 可規避授權存取限制",
Placeholder: "OpenAI API Key",
},
Usage: { Usage: {
Title: "帳戶餘額", Title: "帳戶餘額",
SubTitle(used: any, total: any) { SubTitle(used: any, total: any) {
@ -134,11 +130,7 @@ const tw: PartialLocaleType = {
Check: "重新檢查", Check: "重新檢查",
NoAccess: "輸入 API Key 檢視餘額", NoAccess: "輸入 API Key 檢視餘額",
}, },
AccessCode: {
Title: "授權碼",
SubTitle: "目前是未授權存取狀態",
Placeholder: "請輸入授權碼",
},
Model: "模型 (model)", Model: "模型 (model)",
Temperature: { Temperature: {
Title: "隨機性 (temperature)", Title: "隨機性 (temperature)",

View File

@ -123,11 +123,7 @@ const vi: PartialLocaleType = {
Title: "Ngưỡng nén lịch sử tin nhắn", Title: "Ngưỡng nén lịch sử tin nhắn",
SubTitle: "Thực hiện nén nếu số lượng tin nhắn chưa nén vượt quá ngưỡng", SubTitle: "Thực hiện nén nếu số lượng tin nhắn chưa nén vượt quá ngưỡng",
}, },
Token: {
Title: "API Key",
SubTitle: "Sử dụng khóa của bạn để bỏ qua giới hạn mã truy cập",
Placeholder: "OpenAI API Key",
},
Usage: { Usage: {
Title: "Hạn mức tài khoản", Title: "Hạn mức tài khoản",
SubTitle(used: any, total: any) { SubTitle(used: any, total: any) {
@ -137,11 +133,7 @@ const vi: PartialLocaleType = {
Check: "Kiểm tra", Check: "Kiểm tra",
NoAccess: "Nhập API Key để kiểm tra hạn mức", NoAccess: "Nhập API Key để kiểm tra hạn mức",
}, },
AccessCode: {
Title: "Mã truy cập",
SubTitle: "Đã bật kiểm soát truy cập",
Placeholder: "Nhập mã truy cập",
},
Model: "Mô hình", Model: "Mô hình",
Temperature: { Temperature: {
Title: "Tính ngẫu nhiên (temperature)", Title: "Tính ngẫu nhiên (temperature)",

View File

@ -1,25 +1,41 @@
import { DEFAULT_API_HOST, DEFAULT_MODELS, StoreKey } from "../constant"; import {
ApiPath,
DEFAULT_API_HOST,
ServiceProvider,
StoreKey,
} from "../constant";
import { getHeaders } from "../client/api"; import { getHeaders } from "../client/api";
import { getClientConfig } from "../config/client"; import { getClientConfig } from "../config/client";
import { createPersistStore } from "../utils/store"; import { createPersistStore } from "../utils/store";
import { ensure } from "../utils/clone";
let fetchState = 0; // 0 not fetch, 1 fetching, 2 done let fetchState = 0; // 0 not fetch, 1 fetching, 2 done
const DEFAULT_OPENAI_URL = const DEFAULT_OPENAI_URL =
getClientConfig()?.buildMode === "export" ? DEFAULT_API_HOST : "/api/openai/"; getClientConfig()?.buildMode === "export" ? DEFAULT_API_HOST : ApiPath.OpenAI;
console.log("[API] default openai url", DEFAULT_OPENAI_URL);
const DEFAULT_ACCESS_STATE = { const DEFAULT_ACCESS_STATE = {
token: "",
accessCode: "", accessCode: "",
useCustomConfig: false,
provider: ServiceProvider.OpenAI,
// openai
openaiUrl: DEFAULT_OPENAI_URL,
openaiApiKey: "",
// azure
azureUrl: "",
azureApiKey: "",
azureApiVersion: "2023-08-01-preview",
// server config
needCode: true, needCode: true,
hideUserApiKey: false, hideUserApiKey: false,
hideBalanceQuery: false, hideBalanceQuery: false,
disableGPT4: false, disableGPT4: false,
disableFastLink: false, disableFastLink: false,
customModels: "", customModels: "",
openaiUrl: DEFAULT_OPENAI_URL,
}; };
export const useAccessStore = createPersistStore( export const useAccessStore = createPersistStore(
@ -31,12 +47,24 @@ export const useAccessStore = createPersistStore(
return get().needCode; return get().needCode;
}, },
isValidOpenAI() {
return ensure(get(), ["openaiUrl", "openaiApiKey"]);
},
isValidAzure() {
return ensure(get(), ["azureUrl", "azureApiKey", "azureApiVersion"]);
},
isAuthorized() { isAuthorized() {
this.fetch(); this.fetch();
// has token or has code or disabled access control // has token or has code or disabled access control
return ( return (
!!get().token || !!get().accessCode || !this.enabledAccessControl() this.isValidOpenAI() ||
this.isValidAzure() ||
!this.enabledAccessControl() ||
(this.enabledAccessControl() && ensure(get(), ["accessCode"]))
); );
}, },
fetch() { fetch() {
@ -64,6 +92,19 @@ export const useAccessStore = createPersistStore(
}), }),
{ {
name: StoreKey.Access, name: StoreKey.Access,
version: 1, version: 2,
migrate(persistedState, version) {
if (version < 2) {
const state = persistedState as {
token: string;
openaiApiKey: string;
azureApiVersion: string;
};
state.openaiApiKey = state.token;
state.azureApiVersion = "2023-08-01-preview";
}
return persistedState as any;
},
}, },
); );

View File

@ -1,3 +1,10 @@
export function deepClone<T>(obj: T) { export function deepClone<T>(obj: T) {
return JSON.parse(JSON.stringify(obj)); return JSON.parse(JSON.stringify(obj));
} }
export function ensure<T extends object>(
obj: T,
keys: Array<[keyof T][number]>,
) {
return keys.every((k) => obj[k] !== undefined && obj[k] !== null);
}

View File

@ -1,5 +1,5 @@
import { create } from "zustand"; import { create } from "zustand";
import { persist } from "zustand/middleware"; import { combine, persist } from "zustand/middleware";
import { Updater } from "../typing"; import { Updater } from "../typing";
import { deepClone } from "./clone"; import { deepClone } from "./clone";
@ -23,33 +23,42 @@ type SetStoreState<T> = (
replace?: boolean | undefined, replace?: boolean | undefined,
) => void; ) => void;
export function createPersistStore<T, M>( export function createPersistStore<T extends object, M>(
defaultState: T, state: T,
methods: ( methods: (
set: SetStoreState<T & MakeUpdater<T>>, set: SetStoreState<T & MakeUpdater<T>>,
get: () => T & MakeUpdater<T>, get: () => T & MakeUpdater<T>,
) => M, ) => M,
persistOptions: SecondParam<typeof persist<T & M & MakeUpdater<T>>>, persistOptions: SecondParam<typeof persist<T & M & MakeUpdater<T>>>,
) { ) {
return create<T & M & MakeUpdater<T>>()( return create(
persist((set, get) => { persist(
return { combine(
...defaultState, {
...methods(set as any, get), ...state,
lastUpdateTime: 0,
},
(set, get) => {
return {
...methods(set, get as any),
lastUpdateTime: 0, markUpdate() {
markUpdate() { set({ lastUpdateTime: Date.now() } as Partial<
set({ lastUpdateTime: Date.now() } as Partial< T & M & MakeUpdater<T>
T & M & MakeUpdater<T> >);
>); },
update(updater) {
const state = deepClone(get());
updater(state);
set({
...state,
lastUpdateTime: Date.now(),
});
},
} as M & MakeUpdater<T>;
}, },
update(updater) { ),
const state = deepClone(get()); persistOptions as any,
updater(state); ),
get().markUpdate();
set(state);
},
};
}, persistOptions),
); );
} }