diff --git a/.env.template b/.env.template
index 4b68476daf1..e26583efb42 100644
--- a/.env.template
+++ b/.env.template
@@ -18,6 +18,11 @@ BASE_URL=https://w-pikto-ai.community.saturnenterprise.io:8000
# Default: Empty
OPENAI_ORG_ID=
+# (optional)
+# Default: Empty
+# If you do not want users to use GPT-4, set this value to 1.
+DISABLE_GPT4=
+
# (optional)
# Default: Empty
# If you do not want users to input their own API key, set this value to 1.
@@ -25,10 +30,10 @@ HIDE_USER_API_KEY=
# (optional)
# Default: Empty
-# If you do not want users to use GPT-4, set this value to 1.
-DISABLE_GPT4=
+# If you do want users to query balance, set this value to 1.
+ENABLE_BALANCE_QUERY=
# (optional)
# Default: Empty
-# If you do not want users to query balance, set this value to 1.
-HIDE_BALANCE_QUERY=
+# If you want to disable parse settings from url, set this value to 1.
+DISABLE_FAST_LINK=
diff --git a/README.md b/README.md
index 91e03d80049..3973c84bfde 100644
--- a/README.md
+++ b/README.md
@@ -197,6 +197,13 @@ If you do want users to query balance, set this value to 1, or you should set it
If you want to disable parse settings from url, set this to 1.
+### `CUSTOM_MODELS` (optional)
+
+> Default: Empty
+> Example: `+llama,+claude-2,-gpt-3.5-turbo` means add `llama, claude-2` to model list, and remove `gpt-3.5-turbo` from list.
+
+To control custom models, use `+` to add a custom model, use `-` to hide a model, separated by comma.
+
## Requirements
NodeJS >= 18, Docker >= 20
diff --git a/README_CN.md b/README_CN.md
index 13b97417d40..d8e9553e183 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -106,6 +106,12 @@ OpenAI 接口代理 URL,如果你手动配置了 openai 接口代理,请填
如果你想禁用从链接解析预制设置,将此环境变量设置为 1 即可。
+### `CUSTOM_MODELS` (可选)
+
+> 示例:`+qwen-7b-chat,+glm-6b,-gpt-3.5-turbo` 表示增加 `qwen-7b-chat` 和 `glm-6b` 到模型列表,而从列表中删除 `gpt-3.5-turbo`。
+
+用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,用英文逗号隔开。
+
## 开发
点击下方按钮,开始二次开发:
diff --git a/app/api/common.ts b/app/api/common.ts
index cd2936ee39f..a1decd42f5b 100644
--- a/app/api/common.ts
+++ b/app/api/common.ts
@@ -1,10 +1,9 @@
import { NextRequest, NextResponse } from "next/server";
+import { getServerSideConfig } from "../config/server";
+import { DEFAULT_MODELS, OPENAI_BASE_URL } from "../constant";
+import { collectModelTable, collectModels } from "../utils/model";
-export const OPENAI_URL = "api.openai.com";
-const DEFAULT_PROTOCOL = "https";
-const PROTOCOL = process.env.PROTOCOL || DEFAULT_PROTOCOL;
-const BASE_URL = process.env.BASE_URL || OPENAI_URL;
-const DISABLE_GPT4 = !!process.env.DISABLE_GPT4;
+const serverConfig = getServerSideConfig();
export async function requestOpenai(req: NextRequest) {
const controller = new AbortController();
@@ -14,26 +13,26 @@ export async function requestOpenai(req: NextRequest) {
"",
);
- let baseUrl = BASE_URL;
+ let baseUrl = serverConfig.baseUrl ?? OPENAI_BASE_URL;
if (!baseUrl.startsWith("http")) {
- baseUrl = `${PROTOCOL}://${baseUrl}`;
+ baseUrl = `https://${baseUrl}`;
}
- if (baseUrl.endsWith('/')) {
+ if (baseUrl.endsWith("/")) {
baseUrl = baseUrl.slice(0, -1);
}
console.log("[Proxy] ", openaiPath);
console.log("[Base Url]", baseUrl);
+ console.log("[Org ID]", serverConfig.openaiOrgId);
- if (process.env.OPENAI_ORG_ID) {
- console.log("[Org ID]", process.env.OPENAI_ORG_ID);
- }
-
- const timeoutId = setTimeout(() => {
- controller.abort();
- }, 10 * 60 * 1000);
+ const timeoutId = setTimeout(
+ () => {
+ controller.abort();
+ },
+ 10 * 60 * 1000,
+ );
const fetchUrl = `${baseUrl}/${openaiPath}`;
const fetchOptions: RequestInit = {
@@ -55,18 +54,23 @@ export async function requestOpenai(req: NextRequest) {
};
// #1815 try to refuse gpt4 request
- if (DISABLE_GPT4 && req.body) {
+ if (serverConfig.customModels && req.body) {
try {
+ const modelTable = collectModelTable(
+ DEFAULT_MODELS,
+ serverConfig.customModels,
+ );
const clonedBody = await req.text();
fetchOptions.body = clonedBody;
- const jsonBody = JSON.parse(clonedBody);
+ const jsonBody = JSON.parse(clonedBody) as { model?: string };
- if ((jsonBody?.model ?? "").includes("gpt-4")) {
+ // not undefined and is false
+ if (modelTable[jsonBody?.model ?? ""] === false) {
return NextResponse.json(
{
error: true,
- message: "you are not allowed to use gpt-4 model",
+ message: `you are not allowed to use ${jsonBody?.model} model`,
},
{
status: 403,
diff --git a/app/api/config/route.ts b/app/api/config/route.ts
index 44af8d3b9dd..db84fba175a 100644
--- a/app/api/config/route.ts
+++ b/app/api/config/route.ts
@@ -12,6 +12,7 @@ const DANGER_CONFIG = {
disableGPT4: serverConfig.disableGPT4,
hideBalanceQuery: serverConfig.hideBalanceQuery,
disableFastLink: serverConfig.disableFastLink,
+ customModels: serverConfig.customModels,
};
declare global {
diff --git a/app/client/platforms/openai.ts b/app/client/platforms/openai.ts
index fd4eb59ce77..97392a004c9 100644
--- a/app/client/platforms/openai.ts
+++ b/app/client/platforms/openai.ts
@@ -70,6 +70,7 @@ export class ChatGPTApi implements LLMApi {
presence_penalty: modelConfig.presence_penalty,
frequency_penalty: modelConfig.frequency_penalty,
top_p: modelConfig.top_p,
+ max_tokens: Math.max(modelConfig.max_tokens, 1024),
};
console.log("[Request] openai payload: ", requestPayload);
diff --git a/app/components/chat-list.tsx b/app/components/chat-list.tsx
index 7ba55585239..f76b369f12d 100644
--- a/app/components/chat-list.tsx
+++ b/app/components/chat-list.tsx
@@ -18,6 +18,7 @@ import { MaskAvatar } from "./mask";
import { Mask } from "../store/mask";
import { useRef, useEffect } from "react";
import { showConfirm } from "./ui-lib";
+import { useMobileScreen } from "../utils";
export function ChatItem(props: {
onClick?: () => void;
@@ -80,7 +81,11 @@ export function ChatItem(props: {
{
+ props.onDelete?.();
+ e.preventDefault();
+ e.stopPropagation();
+ }}
>
@@ -101,6 +106,7 @@ export function ChatList(props: { narrow?: boolean }) {
);
const chatStore = useChatStore();
const navigate = useNavigate();
+ const isMobileScreen = useMobileScreen();
const onDragEnd: OnDragEndResponder = (result) => {
const { destination, source } = result;
@@ -142,7 +148,7 @@ export function ChatList(props: { narrow?: boolean }) {
}}
onDelete={async () => {
if (
- !props.narrow ||
+ (!props.narrow && !isMobileScreen) ||
(await showConfirm(Locale.Home.DeleteChat))
) {
chatStore.deleteSession(i);
diff --git a/app/components/chat.tsx b/app/components/chat.tsx
index 18353e8fcab..9afb49f7a66 100644
--- a/app/components/chat.tsx
+++ b/app/components/chat.tsx
@@ -88,6 +88,7 @@ import { ChatCommandPrefix, useChatCommand, useCommand } from "../command";
import { prettyObject } from "../utils/format";
import { ExportMessageModal } from "./exporter";
import { getClientConfig } from "../config/client";
+import { useAllModels } from "../utils/hooks";
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
loading: () => ,
@@ -143,6 +144,7 @@ export function SessionConfigModel(props: { onClose: () => void }) {
extraListItems={
session.mask.modelConfig.sendMemory ? (
@@ -429,14 +431,9 @@ export function ChatActions(props: {
// switch model
const currentModel = chatStore.currentSession().mask.modelConfig.model;
- const models = useMemo(
- () =>
- config
- .allModels()
- .filter((m) => m.available)
- .map((m) => m.name),
- [config],
- );
+ const models = useAllModels()
+ .filter((m) => m.available)
+ .map((m) => m.name);
const [showModelSelector, setShowModelSelector] = useState(false);
return (
diff --git a/app/components/model-config.tsx b/app/components/model-config.tsx
index 63950a40d04..1c730e1449f 100644
--- a/app/components/model-config.tsx
+++ b/app/components/model-config.tsx
@@ -1,14 +1,15 @@
-import { ModalConfigValidator, ModelConfig, useAppConfig } from "../store";
+import { ModalConfigValidator, ModelConfig } from "../store";
import Locale from "../locales";
import { InputRange } from "./input-range";
import { ListItem, Select } from "./ui-lib";
+import { useAllModels } from "../utils/hooks";
export function ModelConfigList(props: {
modelConfig: ModelConfig;
updateConfig: (updater: (config: ModelConfig) => void) => void;
}) {
- const config = useAppConfig();
+ const allModels = useAllModels();
return (
<>
@@ -24,7 +25,7 @@ export function ModelConfigList(props: {
);
}}
>
- {config.allModels().map((v, i) => (
+ {allModels.map((v, i) => (
@@ -75,8 +76,8 @@ export function ModelConfigList(props: {
>
props.updateConfig(
diff --git a/app/components/sidebar.tsx b/app/components/sidebar.tsx
index 85af3ed2e98..beeee865afe 100644
--- a/app/components/sidebar.tsx
+++ b/app/components/sidebar.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useRef, useCallback, useMemo } from "react";
+import { useEffect, useRef, useMemo } from "react";
import styles from "./home.module.scss";
@@ -8,6 +8,7 @@ import GithubIcon from "../icons/github.svg";
import ChatGptIcon from "../icons/chatgpt.svg";
import AddIcon from "../icons/add.svg";
import CloseIcon from "../icons/close.svg";
+import DeleteIcon from "../icons/delete.svg";
import MaskIcon from "../icons/mask.svg";
import PluginIcon from "../icons/plugin.svg";
import DragIcon from "../icons/drag.svg";
@@ -202,7 +203,7 @@ export function SideBar(props: { className?: string }) {
}
+ icon={}
onClick={async () => {
if (await showConfirm(Locale.Home.DeleteChat)) {
chatStore.deleteSession(chatStore.currentSessionIndex);
diff --git a/app/config/server.ts b/app/config/server.ts
index 2df806fed41..007c3973863 100644
--- a/app/config/server.ts
+++ b/app/config/server.ts
@@ -1,4 +1,5 @@
import md5 from "spark-md5";
+import { DEFAULT_MODELS } from "../constant";
declare global {
namespace NodeJS {
@@ -7,6 +8,7 @@ declare global {
CODE?: string;
BASE_URL?: string;
PROXY_URL?: string;
+ OPENAI_ORG_ID?: 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
@@ -14,6 +16,7 @@ declare global {
BUILD_APP?: string; // is building desktop app
ENABLE_BALANCE_QUERY?: string; // allow user to query balance or not
DISABLE_FAST_LINK?: string; // disallow parse settings from url or not
+ CUSTOM_MODELS?: string; // to control custom models
}
}
}
@@ -38,6 +41,16 @@ export const getServerSideConfig = () => {
);
}
+ let disableGPT4 = !!process.env.DISABLE_GPT4;
+ let customModels = process.env.CUSTOM_MODELS ?? "";
+
+ if (disableGPT4) {
+ if (customModels) customModels += ",";
+ customModels += DEFAULT_MODELS.filter((m) => m.name.startsWith("gpt-4"))
+ .map((m) => "-" + m.name)
+ .join(",");
+ }
+
return {
apiKey: process.env.OPENAI_API_KEY,
code: process.env.CODE,
@@ -45,10 +58,12 @@ export const getServerSideConfig = () => {
needCode: ACCESS_CODES.size > 0,
baseUrl: process.env.BASE_URL,
proxyUrl: process.env.PROXY_URL,
+ openaiOrgId: process.env.OPENAI_ORG_ID,
isVercel: !!process.env.VERCEL,
hideUserApiKey: !!process.env.HIDE_USER_API_KEY,
- disableGPT4: !!process.env.DISABLE_GPT4,
+ disableGPT4,
hideBalanceQuery: !process.env.ENABLE_BALANCE_QUERY,
disableFastLink: !!process.env.DISABLE_FAST_LINK,
+ customModels,
};
};
diff --git a/app/store/access.ts b/app/store/access.ts
index 3d889f6e72e..f87e44a2ac4 100644
--- a/app/store/access.ts
+++ b/app/store/access.ts
@@ -17,6 +17,7 @@ const DEFAULT_ACCESS_STATE = {
hideBalanceQuery: false,
disableGPT4: false,
disableFastLink: false,
+ customModels: "",
openaiUrl: DEFAULT_OPENAI_URL,
};
@@ -52,12 +53,6 @@ export const useAccessStore = createPersistStore(
.then((res: DangerConfig) => {
console.log("[Config] got config from server", res);
set(() => ({ ...res }));
-
- if (res.disableGPT4) {
- DEFAULT_MODELS.forEach(
- (m: any) => (m.available = !m.name.startsWith("gpt-4")),
- );
- }
})
.catch(() => {
console.error("[Config] failed to fetch config");
diff --git a/app/store/chat.ts b/app/store/chat.ts
index 95822c19186..ff7eb51b5c1 100644
--- a/app/store/chat.ts
+++ b/app/store/chat.ts
@@ -85,33 +85,6 @@ function getSummarizeModel(currentModel: string) {
return currentModel.startsWith("gpt") ? SUMMARIZE_MODEL : currentModel;
}
-interface ChatStore {
- sessions: ChatSession[];
- currentSessionIndex: number;
- clearSessions: () => void;
- moveSession: (from: number, to: number) => void;
- selectSession: (index: number) => void;
- newSession: (mask?: Mask) => void;
- deleteSession: (index: number) => void;
- currentSession: () => ChatSession;
- nextSession: (delta: number) => void;
- onNewMessage: (message: ChatMessage) => void;
- onUserInput: (content: string) => Promise;
- summarizeSession: () => void;
- updateStat: (message: ChatMessage) => void;
- updateCurrentSession: (updater: (session: ChatSession) => void) => void;
- updateMessage: (
- sessionIndex: number,
- messageIndex: number,
- updater: (message?: ChatMessage) => void,
- ) => void;
- resetSession: () => void;
- getMessagesWithMemory: () => ChatMessage[];
- getMemoryPrompt: () => ChatMessage;
-
- clearAllData: () => void;
-}
-
function countMessages(msgs: ChatMessage[]) {
return msgs.reduce((pre, cur) => pre + estimateTokenLength(cur.content), 0);
}
diff --git a/app/store/config.ts b/app/store/config.ts
index 0fbc26dfe0e..17eb88c30ed 100644
--- a/app/store/config.ts
+++ b/app/store/config.ts
@@ -49,7 +49,7 @@ export const DEFAULT_CONFIG = {
model: "gpt-3.5-turbo" as ModelType,
temperature: 0.5,
top_p: 1,
- max_tokens: 2000,
+ max_tokens: 8192,
presence_penalty: 0,
frequency_penalty: 0,
sendMemory: true,
@@ -82,7 +82,7 @@ export const ModalConfigValidator = {
return x as ModelType;
},
max_tokens(x: number) {
- return limitNumber(x, 0, 100000, 2000);
+ return limitNumber(x, 0, 512000, 1024);
},
presence_penalty(x: number) {
return limitNumber(x, -2, 2, 0);
@@ -128,15 +128,7 @@ export const useAppConfig = createPersistStore(
}));
},
- allModels() {
- const customModels = get()
- .customModels.split(",")
- .filter((v) => !!v && v.length > 0)
- .map((m) => ({ name: m, available: true }));
- const allModels = get().models.concat(customModels);
- allModels.sort((a, b) => (a.name < b.name ? -1 : 1));
- return allModels;
- },
+ allModels() {},
}),
{
name: StoreKey.Config,
diff --git a/app/styles/globals.scss b/app/styles/globals.scss
index def28680c1a..aa22b7d4fd6 100644
--- a/app/styles/globals.scss
+++ b/app/styles/globals.scss
@@ -357,3 +357,7 @@ pre {
overflow: hidden;
text-overflow: ellipsis;
}
+
+.copyable {
+ user-select: text;
+}
diff --git a/app/utils/hooks.ts b/app/utils/hooks.ts
new file mode 100644
index 00000000000..f6bfae67323
--- /dev/null
+++ b/app/utils/hooks.ts
@@ -0,0 +1,16 @@
+import { useMemo } from "react";
+import { useAccessStore, useAppConfig } from "../store";
+import { collectModels } from "./model";
+
+export function useAllModels() {
+ const accessStore = useAccessStore();
+ const configStore = useAppConfig();
+ const models = useMemo(() => {
+ return collectModels(
+ configStore.models,
+ [accessStore.customModels, configStore.customModels].join(","),
+ );
+ }, [accessStore.customModels, configStore.customModels, configStore.models]);
+
+ return models;
+}
diff --git a/app/utils/model.ts b/app/utils/model.ts
new file mode 100644
index 00000000000..23090f9d2f3
--- /dev/null
+++ b/app/utils/model.ts
@@ -0,0 +1,40 @@
+import { LLMModel } from "../client/api";
+
+export function collectModelTable(
+ models: readonly LLMModel[],
+ customModels: string,
+) {
+ const modelTable: Record = {};
+
+ // default models
+ models.forEach((m) => (modelTable[m.name] = m.available));
+
+ // server custom models
+ customModels
+ .split(",")
+ .filter((v) => !!v && v.length > 0)
+ .map((m) => {
+ if (m.startsWith("+")) {
+ modelTable[m.slice(1)] = true;
+ } else if (m.startsWith("-")) {
+ modelTable[m.slice(1)] = false;
+ } else modelTable[m] = true;
+ });
+ return modelTable;
+}
+
+/**
+ * Generate full model table.
+ */
+export function collectModels(
+ models: readonly LLMModel[],
+ customModels: string,
+) {
+ const modelTable = collectModelTable(models, customModels);
+ const allModels = Object.keys(modelTable).map((m) => ({
+ name: m,
+ available: modelTable[m],
+ }));
+
+ return allModels;
+}
diff --git a/docker-compose.yml b/docker-compose.yml
index c4312cfe3f3..57ca12e03f6 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,6 +1,6 @@
-version: '3.9'
+version: "3.9"
services:
- chatgpt-next-web:
+ chatgpt-next-web:
profiles: ["no-proxy"]
container_name: chatgpt-next-web
image: yidadaa/chatgpt-next-web
@@ -13,8 +13,11 @@ services:
- OPENAI_ORG_ID=$OPENAI_ORG_ID
- HIDE_USER_API_KEY=$HIDE_USER_API_KEY
- DISABLE_GPT4=$DISABLE_GPT4
+ - ENABLE_BALANCE_QUERY=$ENABLE_BALANCE_QUERY
+ - DISABLE_FAST_LINK=$DISABLE_FAST_LINK
+ - OPENAI_SB=$OPENAI_SB
- chatgpt-next-web-proxy:
+ chatgpt-next-web-proxy:
profiles: ["proxy"]
container_name: chatgpt-next-web-proxy
image: yidadaa/chatgpt-next-web
@@ -28,3 +31,6 @@ services:
- OPENAI_ORG_ID=$OPENAI_ORG_ID
- HIDE_USER_API_KEY=$HIDE_USER_API_KEY
- DISABLE_GPT4=$DISABLE_GPT4
+ - ENABLE_BALANCE_QUERY=$ENABLE_BALANCE_QUERY
+ - DISABLE_FAST_LINK=$DISABLE_FAST_LINK
+ - OPENAI_SB=$OPENAI_SB
diff --git a/docs/cloudflare-pages-cn.md b/docs/cloudflare-pages-cn.md
index 2f9a99f2d41..137bb9dc328 100644
--- a/docs/cloudflare-pages-cn.md
+++ b/docs/cloudflare-pages-cn.md
@@ -1,6 +1,7 @@
# Cloudflare Pages 部署指南
## 如何新建项目
+
在 Github 上 fork 本项目,然后登录到 dash.cloudflare.com 并进入 Pages。
1. 点击 "Create a project"。
@@ -12,7 +13,7 @@
7. 在 "Build Settings" 中,选择 "Framework presets" 选项并选择 "Next.js"。
8. 由于 node:buffer 的 bug,暂时不要使用默认的 "Build command"。请使用以下命令:
```
- npx https://prerelease-registry.devprod.cloudflare.dev/next-on-pages/runs/4930842298/npm-package-next-on-pages-230 --experimental-minify
+ npx @cloudflare/next-on-pages@1.5.0
```
9. 对于 "Build output directory",使用默认值并且不要修改。
10. 不要修改 "Root Directory"。
@@ -30,10 +31,12 @@
- `OPENAI_ORG_ID= 可选填,指定 OpenAI 中的组织 ID`
- `HIDE_USER_API_KEY=1 可选,不让用户自行填入 API Key`
- `DISABLE_GPT4=1 可选,不让用户使用 GPT-4`
-
+ - `ENABLE_BALANCE_QUERY=1 可选,启用余额查询功能`
+ - `DISABLE_FAST_LINK=1 可选,禁用从链接解析预制设置`
+
12. 点击 "Save and Deploy"。
13. 点击 "Cancel deployment",因为需要填写 Compatibility flags。
14. 前往 "Build settings"、"Functions",找到 "Compatibility flags"。
15. 在 "Configure Production compatibility flag" 和 "Configure Preview compatibility flag" 中填写 "nodejs_compat"。
16. 前往 "Deployments",点击 "Retry deployment"。
-17. Enjoy.
\ No newline at end of file
+17. Enjoy.
diff --git a/docs/cloudflare-pages-en.md b/docs/cloudflare-pages-en.md
index 2279ff232a4..c5d55043872 100644
--- a/docs/cloudflare-pages-en.md
+++ b/docs/cloudflare-pages-en.md
@@ -1,6 +1,7 @@
# Cloudflare Pages Deployment Guide
## How to create a new project
+
Fork this project on GitHub, then log in to dash.cloudflare.com and go to Pages.
1. Click "Create a project".
@@ -11,12 +12,13 @@ Fork this project on GitHub, then log in to dash.cloudflare.com and go to Pages.
6. For "Project name" and "Production branch", use the default values or change them as needed.
7. In "Build Settings", choose the "Framework presets" option and select "Next.js".
8. Do not use the default "Build command" due to a node:buffer bug. Instead, use the following command:
- ```
- npx @cloudflare/next-on-pages --experimental-minify
- ```
+ ```
+ npx @cloudflare/next-on-pages --experimental-minify
+ ```
9. For "Build output directory", use the default value and do not modify it.
10. Do not modify "Root Directory".
11. For "Environment variables", click ">" and then "Add variable". Fill in the following information:
+
- `NODE_VERSION=20.1`
- `NEXT_TELEMETRY_DISABLE=1`
- `OPENAI_API_KEY=your_own_API_key`
@@ -29,7 +31,10 @@ Fork this project on GitHub, then log in to dash.cloudflare.com and go to Pages.
- `OPENAI_ORG_ID= Optional, specify the organization ID in OpenAI`
- `HIDE_USER_API_KEY=1 Optional, do not allow users to enter their own API key`
- `DISABLE_GPT4=1 Optional, do not allow users to use GPT-4`
-
+ - `ENABLE_BALANCE_QUERY=1 Optional, allow users to query balance`
+ - `DISABLE_FAST_LINK=1 Optional, disable parse settings from url`
+ - `OPENAI_SB=1 Optional,use the third-party OpenAI-SB API`
+
12. Click "Save and Deploy".
13. Click "Cancel deployment" because you need to fill in Compatibility flags.
14. Go to "Build settings", "Functions", and find "Compatibility flags".