diff --git a/client/data/FeConfig.json b/client/data/FeConfig.json
new file mode 100644
index 000000000..8a2b55763
--- /dev/null
+++ b/client/data/FeConfig.json
@@ -0,0 +1,9 @@
+{
+ "show_emptyChat": false,
+ "show_register": false,
+ "show_appStore": false,
+ "show_promotion": false,
+ "show_userDetail": false,
+ "show_git": false,
+ "authorText": "Made by FastGpt Team."
+}
diff --git a/client/public/docs/chatProblem.md b/client/public/docs/chatProblem.md
index 8b5420961..7802d77a3 100644
--- a/client/public/docs/chatProblem.md
+++ b/client/public/docs/chatProblem.md
@@ -1,7 +1,7 @@
### 常见问题
**反馈问卷**: 如果你遇到任何使用问题或有期望的功能,可以[填写该问卷](https://www.wjx.cn/vm/rLIw1uD.aspx#)
-**Git 地址**: [项目地址。V4-preview 暂未开源,在正式版发布后会开源。](https://github.com/c121914yu/FastGPT)
+**Git 地址**: [项目地址。V4-preview 暂未开源,在正式版发布后会开源。](https://github.com/labring/FastGPT)
**问题文档**: [先看文档,再提问](https://kjqvjse66l.feishu.cn/docx/HtrgdT0pkonP4kxGx8qcu6XDnGh)
**价格表**
| 计费项 | 价格: 元/ 1K tokens(包含上下文)|
diff --git a/client/src/components/ChatBox/index.tsx b/client/src/components/ChatBox/index.tsx
index e10087ee9..2f3c14159 100644
--- a/client/src/components/ChatBox/index.tsx
+++ b/client/src/components/ChatBox/index.tsx
@@ -14,13 +14,10 @@ import { useToast } from '@/hooks/useToast';
import { useCopyData, voiceBroadcast, hasVoiceApi, getErrText } from '@/utils/tools';
import { Box, Card, Flex, Input, Textarea, Button, useTheme } from '@chakra-ui/react';
import { useUserStore } from '@/store/user';
-
+import { feConfigs } from '@/store/static';
import { Types } from 'mongoose';
import { HUMAN_ICON, quoteLenKey, rawSearchKey } from '@/constants/chat';
-import Markdown from '@/components/Markdown';
import { EventNameEnum } from '../Markdown/constant';
-import MyIcon from '@/components/Icon';
-import Avatar from '@/components/Avatar';
import { adaptChatItem_openAI } from '@/utils/plugin/openai';
import { useMarkdown } from '@/hooks/useMarkdown';
@@ -32,14 +29,17 @@ import { fileDownload } from '@/utils/file';
import { htmlTemplate } from '@/constants/common';
import { useRouter } from 'next/router';
import { useGlobalStore } from '@/store/global';
-import dynamic from 'next/dynamic';
-
-const QuoteModal = dynamic(() => import('./QuoteModal'));
-
import { QuoteItemType } from '@/pages/api/app/modules/kb/search';
import { FlowModuleTypeEnum } from '@/constants/flow';
-import MyTooltip from '../MyTooltip';
+
+import dynamic from 'next/dynamic';
+const QuoteModal = dynamic(() => import('./QuoteModal'));
+
+import MyIcon from '@/components/Icon';
+import Avatar from '@/components/Avatar';
+import Markdown from '@/components/Markdown';
import MySelect from '@/components/Select';
+import MyTooltip from '../MyTooltip';
import styles from './index.module.scss';
const textareaMinH = '22px';
@@ -393,7 +393,12 @@ const ChatBox = (
};
const showEmpty = useMemo(
- () => showEmptyIntro && chatHistory.length === 0 && !variableModules?.length && !welcomeText,
+ () =>
+ feConfigs.show_emptyChat &&
+ showEmptyIntro &&
+ chatHistory.length === 0 &&
+ !variableModules?.length &&
+ !welcomeText,
[chatHistory.length, showEmptyIntro, variableModules, welcomeText]
);
diff --git a/client/src/components/Layout/navbar.tsx b/client/src/components/Layout/navbar.tsx
index 01fcd2b89..c1ddaeb26 100644
--- a/client/src/components/Layout/navbar.tsx
+++ b/client/src/components/Layout/navbar.tsx
@@ -1,13 +1,14 @@
import React, { useMemo } from 'react';
import { Box, Flex, Link } from '@chakra-ui/react';
import { useRouter } from 'next/router';
-import MyIcon from '../Icon';
import { useUserStore } from '@/store/user';
import { useChatStore } from '@/store/chat';
-import Avatar from '../Avatar';
import { HUMAN_ICON } from '@/constants/chat';
+import { feConfigs } from '@/store/static';
import NextLink from 'next/link';
import Badge from '../Badge';
+import Avatar from '../Avatar';
+import MyIcon from '../Icon';
export enum NavbarTypeEnum {
normal = 'normal',
@@ -41,13 +42,17 @@ const Navbar = ({ unread }: { unread: number }) => {
link: `/kb/list`,
activeLink: ['/kb/list', '/kb/detail']
},
- {
- label: '市场',
- icon: 'appStoreLight',
- activeIcon: 'appStoreFill',
- link: '/appStore',
- activeLink: ['/appStore']
- },
+ ...(feConfigs.show_appStore
+ ? [
+ {
+ label: '市场',
+ icon: 'appStoreLight',
+ activeIcon: 'appStoreFill',
+ link: '/appStore',
+ activeLink: ['/appStore']
+ }
+ ]
+ : []),
{
label: '账号',
icon: 'meLight',
@@ -138,17 +143,19 @@ const Navbar = ({ unread }: { unread: number }) => {
)}
-
-
-
-
-
+ {feConfigs.show_git && (
+
+
+
+
+
+ )}
);
};
diff --git a/client/src/components/Layout/navbarPhone.tsx b/client/src/components/Layout/navbarPhone.tsx
index af502f74f..240e90983 100644
--- a/client/src/components/Layout/navbarPhone.tsx
+++ b/client/src/components/Layout/navbarPhone.tsx
@@ -1,9 +1,9 @@
import React, { useMemo } from 'react';
import { useRouter } from 'next/router';
-import MyIcon from '../Icon';
import { Flex, Box } from '@chakra-ui/react';
import { useChatStore } from '@/store/chat';
import Badge from '../Badge';
+import MyIcon from '../Icon';
const NavbarPhone = ({ unread }: { unread: number }) => {
const router = useRouter();
diff --git a/client/src/constants/flow/inputTemplate.ts b/client/src/constants/flow/inputTemplate.ts
index 912c783fc..3dbef267a 100644
--- a/client/src/constants/flow/inputTemplate.ts
+++ b/client/src/constants/flow/inputTemplate.ts
@@ -17,5 +17,6 @@ export const Input_Template_History: FlowInputItemType = {
export const Input_Template_UserChatInput: FlowInputItemType = {
key: SystemInputEnum.userChatInput,
type: FlowInputItemTypeEnum.target,
- label: '用户问题'
+ label: '用户问题',
+ required: true
};
diff --git a/client/src/pages/api/system/getInitData.ts b/client/src/pages/api/system/getInitData.ts
index 1d746a0c1..e8f3a05f9 100644
--- a/client/src/pages/api/system/getInitData.ts
+++ b/client/src/pages/api/system/getInitData.ts
@@ -6,7 +6,7 @@ import {
type ChatModelItemType,
type VectorModelItemType
} from '@/types/model';
-import { readFileSync } from 'fs';
+import type { FeConfigsType } from '@/types';
export type InitDateResponse = {
beianText: string;
@@ -15,48 +15,7 @@ export type InitDateResponse = {
chatModels: ChatModelItemType[];
qaModels: QAModelItemType[];
vectorModels: VectorModelItemType[];
-};
-
-const defaultmodels = {
- 'FastAI-4k': {
- model: 'gpt-3.5-turbo',
- name: 'FastAI-4k',
- contextMaxToken: 4000,
- systemMaxToken: 2400,
- maxTemperature: 1.2,
- price: 1.5
- },
- 'FastAI-16k': {
- model: 'gpt-3.5-turbo',
- name: 'FastAI-16k',
- contextMaxToken: 16000,
- systemMaxToken: 8000,
- maxTemperature: 1.2,
- price: 3
- },
- 'FastAI-Plus': {
- model: 'gpt-4',
- name: 'FastAI-Plus',
- contextMaxToken: 8000,
- systemMaxToken: 4000,
- maxTemperature: 1.2,
- price: 45
- }
-};
-const defaultQaModels = {
- 'FastAI-16k': {
- model: 'gpt-3.5-turbo',
- name: 'FastAI-16k',
- maxToken: 16000,
- price: 3
- }
-};
-const defaultVectorModels = {
- 'text-embedding-ada-002': {
- model: 'text-embedding-ada-002',
- name: 'Embedding-2',
- price: 0.2
- }
+ feConfigs: FeConfigsType;
};
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
@@ -69,46 +28,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
jsonRes(res, {
data: {
...envs,
- ...initSystemModels()
+ chatModels: global.chatModels,
+ qaModels: global.qaModels,
+ vectorModels: global.vectorModels,
+ feConfigs: global.feConfigs
}
});
}
-
-export function initSystemModels() {
- const { chatModels, qaModels, vectorModels } = (() => {
- try {
- const chatModels = Object.values(JSON.parse(readFileSync('data/ChatModels.json', 'utf-8')));
- const qaModels = Object.values(JSON.parse(readFileSync('data/QAModels.json', 'utf-8')));
- const vectorModels = Object.values(
- JSON.parse(readFileSync('data/VectorModels.json', 'utf-8'))
- );
-
- return {
- chatModels,
- qaModels,
- vectorModels
- };
- } catch (error) {
- console.log(error);
-
- return {
- chatModels: Object.values(defaultmodels),
- qaModels: Object.values(defaultQaModels),
- vectorModels: Object.values(defaultVectorModels)
- };
- }
- })() as {
- chatModels: ChatModelItemType[];
- qaModels: QAModelItemType[];
- vectorModels: VectorModelItemType[];
- };
- global.chatModels = chatModels;
- global.qaModels = qaModels;
- global.vectorModels = vectorModels;
-
- return {
- chatModels,
- qaModels,
- vectorModels
- };
-}
diff --git a/client/src/pages/api/system/updateEnv.ts b/client/src/pages/api/system/updateEnv.ts
index 5b0dd7bde..fa11e2c42 100644
--- a/client/src/pages/api/system/updateEnv.ts
+++ b/client/src/pages/api/system/updateEnv.ts
@@ -2,6 +2,11 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authUser } from '@/service/utils/auth';
import { readFileSync } from 'fs';
+import {
+ type QAModelItemType,
+ type ChatModelItemType,
+ type VectorModelItemType
+} from '@/types/model';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
await authUser({ req, authRoot: true });
@@ -22,3 +27,97 @@ export async function updateSystemEnv() {
console.log('update system env error');
}
}
+
+const defaultmodels = {
+ 'FastAI-4k': {
+ model: 'gpt-3.5-turbo',
+ name: 'FastAI-4k',
+ contextMaxToken: 4000,
+ systemMaxToken: 2400,
+ maxTemperature: 1.2,
+ price: 1.5
+ },
+ 'FastAI-16k': {
+ model: 'gpt-3.5-turbo',
+ name: 'FastAI-16k',
+ contextMaxToken: 16000,
+ systemMaxToken: 8000,
+ maxTemperature: 1.2,
+ price: 3
+ },
+ 'FastAI-Plus': {
+ model: 'gpt-4',
+ name: 'FastAI-Plus',
+ contextMaxToken: 8000,
+ systemMaxToken: 4000,
+ maxTemperature: 1.2,
+ price: 45
+ }
+};
+const defaultQaModels = {
+ 'FastAI-16k': {
+ model: 'gpt-3.5-turbo',
+ name: 'FastAI-16k',
+ maxToken: 16000,
+ price: 3
+ }
+};
+const defaultVectorModels = {
+ 'text-embedding-ada-002': {
+ model: 'text-embedding-ada-002',
+ name: 'Embedding-2',
+ price: 0.2
+ }
+};
+export function initSystemModels() {
+ const { chatModels, qaModels, vectorModels } = (() => {
+ try {
+ const chatModels = Object.values(JSON.parse(readFileSync('data/ChatModels.json', 'utf-8')));
+ const qaModels = Object.values(JSON.parse(readFileSync('data/QAModels.json', 'utf-8')));
+ const vectorModels = Object.values(
+ JSON.parse(readFileSync('data/VectorModels.json', 'utf-8'))
+ );
+
+ return {
+ chatModels,
+ qaModels,
+ vectorModels
+ };
+ } catch (error) {
+ console.log(error);
+
+ return {
+ chatModels: Object.values(defaultmodels),
+ qaModels: Object.values(defaultQaModels),
+ vectorModels: Object.values(defaultVectorModels)
+ };
+ }
+ })() as {
+ chatModels: ChatModelItemType[];
+ qaModels: QAModelItemType[];
+ vectorModels: VectorModelItemType[];
+ };
+ global.chatModels = chatModels;
+ global.qaModels = qaModels;
+ global.vectorModels = vectorModels;
+ console.log({
+ chatModels,
+ qaModels,
+ vectorModels
+ });
+
+ return {
+ chatModels,
+ qaModels,
+ vectorModels
+ };
+}
+
+export function initFeConfig() {
+ const feConfig = JSON.parse(readFileSync('data/FeConfig.json', 'utf-8'));
+
+ global.feConfigs = feConfig;
+ console.log(feConfig);
+
+ return feConfig;
+}
diff --git a/client/src/pages/api/user/sendAuthCode.ts b/client/src/pages/api/user/sendAuthCode.ts
index 17f272c54..40747baaa 100644
--- a/client/src/pages/api/user/sendAuthCode.ts
+++ b/client/src/pages/api/user/sendAuthCode.ts
@@ -32,6 +32,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
await connectToDatabase();
+ // register switch
+ if (type === UserAuthTypeEnum.register && !global.feConfigs.show_register) {
+ throw new Error('Register is closed');
+ }
+
const code = nanoid();
// 判断 1 分钟内是否有重复数据
diff --git a/client/src/pages/chat/components/ChatHeader.tsx b/client/src/pages/chat/components/ChatHeader.tsx
index 87104815b..0212041d0 100644
--- a/client/src/pages/chat/components/ChatHeader.tsx
+++ b/client/src/pages/chat/components/ChatHeader.tsx
@@ -25,8 +25,8 @@ const ChatHeader = ({
return (
{
-
-
- {beianText && (
-
- {beianText}
-
- )}
+ {feConfigs?.authorText && (
+
+
+ {beianText && (
+
+ {beianText}
+
+ )}
- Made by FastGpt Team.
-
-
+ {feConfigs?.authorText}
+
+
+ )}
);
};
diff --git a/client/src/pages/number/index.tsx b/client/src/pages/number/index.tsx
index c799f3e38..de2d1c445 100644
--- a/client/src/pages/number/index.tsx
+++ b/client/src/pages/number/index.tsx
@@ -14,6 +14,7 @@ import dynamic from 'next/dynamic';
import { useSelectFile } from '@/hooks/useSelectFile';
import { compressImg } from '@/utils/file';
import { getErrText, useCopyData } from '@/utils/tools';
+import { feConfigs } from '@/store/static';
import Loading from '@/components/Loading';
import Avatar from '@/components/Avatar';
@@ -58,7 +59,7 @@ const NumberSetting = ({ tableType }: { tableType: `${TableEnum}` }) => {
const { copyData } = useCopyData();
const { userInfo, updateUserInfo, initUserInfo, setUserInfo } = useUserStore();
const { setLoading } = useGlobalStore();
- const { register, handleSubmit, reset } = useForm({
+ const { reset } = useForm({
defaultValues: userInfo as UserType
});
const { toast } = useToast();
@@ -146,105 +147,117 @@ const NumberSetting = ({ tableType }: { tableType: `${TableEnum}` }) => {
);
return (
-
-
-
-
-
- 账号信息
-
-
-
-
- 头像:
-
-
-
- 账号:
- {userInfo?.username}
-
-
-
- 余额:
-
- {userInfo?.balance} 元
+ <>
+
+
+
+
+
+ 账号信息
-
-
-
-
-
- 我的邀请
-
- {[
- { label: '佣金比例', value: `${userInfo?.promotion.rate || 15}%` },
- { label: '已注册用户数', value: `${invitedAmount}人` },
- { label: '可用佣金', value: `¥${residueAmount}` }
- ].map((item) => (
-
- {item.label}
- {item.value}
+
+ 头像:
+
- ))}
-
- copyData(`${location.origin}/?inviterId=${userInfo?._id}`, '已复制邀请链接')
- }
- >
- 复制邀请链接
-
- }
- px={4}
- title={residueAmount < 50 ? '最低提现额度为50元' : ''}
- isDisabled={residueAmount < 50}
- variant={'base'}
- colorScheme={'myBlue'}
- onClick={onOpenWxConcat}
- >
- {residueAmount < 50 ? '50元起提' : '提现'}
-
-
-
+
+ 账号:
+ {userInfo?.username}
+
+ {feConfigs.show_userDetail && (
+
+
+ 余额:
+
+ {userInfo?.balance} 元
+
+
+ 充值
+
+
+
+ )}
+
+ {feConfigs.show_userDetail && (
+
+
+ 我的邀请
+
+ {[
+ { label: '佣金比例', value: `${userInfo?.promotion.rate || 15}%` },
+ { label: '已注册用户数', value: `${invitedAmount}人` },
+ { label: '可用佣金', value: `¥${residueAmount}` }
+ ].map((item) => (
+
+ {item.label}
+ {item.value}
+
+ ))}
+
+ copyData(`${location.origin}/?inviterId=${userInfo?._id}`, '已复制邀请链接')
+ }
+ >
+ 复制邀请链接
+
+ }
+ px={4}
+ title={residueAmount < 50 ? '最低提现额度为50元' : ''}
+ isDisabled={residueAmount < 50}
+ variant={'base'}
+ colorScheme={'myBlue'}
+ onClick={onOpenWxConcat}
+ >
+ {residueAmount < 50 ? '50元起提' : '提现'}
+
+
+ )}
+
-
- router.replace(`/number?type=${id}`)}
- />
-
- {(() => {
- const item = tableList.current.find((item) => item.id === tableType);
-
- return item ? item.Component : null;
- })()}
-
-
+ {feConfigs.show_userDetail && (
+
+ router.replace(`/number?type=${id}`)}
+ />
+
+ {(() => {
+ const item = tableList.current.find((item) => item.id === tableType);
+ return item ? item.Component : null;
+ })()}
+
+
+ )}
+
{isOpenPayModal && }
{isOpenWxConcat && }
-
+ >
);
};
diff --git a/client/src/pages/tools/index.tsx b/client/src/pages/tools/index.tsx
index 71150b91b..5deb9f498 100644
--- a/client/src/pages/tools/index.tsx
+++ b/client/src/pages/tools/index.tsx
@@ -3,6 +3,7 @@ import { Box, Flex } from '@chakra-ui/react';
import { ChevronRightIcon } from '@chakra-ui/icons';
import MyIcon from '@/components/Icon';
import { useRouter } from 'next/router';
+import { feConfigs } from '@/store/static';
const list = [
{
@@ -10,16 +11,24 @@ const list = [
label: '我的知识库',
link: '/kb/list'
},
- {
- icon: 'appStoreLight',
- label: 'AI应用市场',
- link: '/appStore'
- },
- {
- icon: 'git',
- label: 'Git项目地址',
- link: 'https://github.com/labring/FastGPT'
- }
+ ...(feConfigs.show_appStore
+ ? [
+ {
+ icon: 'appStoreLight',
+ label: 'AI应用市场',
+ link: '/appStore'
+ }
+ ]
+ : []),
+ ...(feConfigs.show_git
+ ? [
+ {
+ icon: 'git',
+ label: 'Git项目地址',
+ link: 'https://github.com/labring/FastGPT'
+ }
+ ]
+ : [])
];
const Tools = () => {
diff --git a/client/src/service/mongo.ts b/client/src/service/mongo.ts
index 7805be576..1e06c3a9d 100644
--- a/client/src/service/mongo.ts
+++ b/client/src/service/mongo.ts
@@ -1,8 +1,7 @@
import mongoose from 'mongoose';
import tunnel from 'tunnel';
import { startQueue } from './utils/tools';
-import { updateSystemEnv } from '@/pages/api/system/updateEnv';
-import { initSystemModels } from '@/pages/api/system/getInitData';
+import { updateSystemEnv, initSystemModels, initFeConfig } from '@/pages/api/system/updateEnv';
/**
* 连接 MongoDB 数据库
@@ -24,6 +23,7 @@ export async function connectToDatabase(): Promise {
};
global.sendInformQueue = [];
global.sendInformQueueLen = 0;
+ global.feConfigs = {};
// proxy obj
if (process.env.AXIOS_PROXY_HOST && process.env.AXIOS_PROXY_PORT) {
global.httpsAgent = tunnel.httpsOverHttp({
@@ -33,7 +33,10 @@ export async function connectToDatabase(): Promise {
}
});
}
+
+ // init function
initSystemModels();
+ initFeConfig();
updateSystemEnv();
try {
diff --git a/client/src/store/static.ts b/client/src/store/static.ts
index ffdbbf64b..c98cab820 100644
--- a/client/src/store/static.ts
+++ b/client/src/store/static.ts
@@ -6,6 +6,7 @@ import {
import type { InitDateResponse } from '@/pages/api/system/getInitData';
import { getInitData } from '@/api/system';
import { delay } from '@/utils/tools';
+import { FeConfigsType } from '@/types';
export let beianText = '';
export let googleVerKey = '';
@@ -13,6 +14,7 @@ export let baiduTongji = '';
export let chatModelList: ChatModelItemType[] = [];
export let qaModelList: QAModelItemType[] = [];
export let vectorModelList: VectorModelItemType[] = [];
+export let feConfigs: FeConfigsType = {};
let retryTimes = 3;
@@ -23,6 +25,7 @@ export const clientInitData = async (): Promise => {
chatModelList = res.chatModels;
qaModelList = res.qaModels;
vectorModelList = res.vectorModels;
+ feConfigs = res.feConfigs;
beianText = res.beianText;
googleVerKey = res.googleVerKey;
baiduTongji = res.baiduTongji;
diff --git a/client/src/types/index.d.ts b/client/src/types/index.d.ts
index ac226047f..38b912c16 100644
--- a/client/src/types/index.d.ts
+++ b/client/src/types/index.d.ts
@@ -13,6 +13,16 @@ export type PagingData = {
export type RequestPaging = { pageNum: number; pageSize: number; [key]: any };
+export type FeConfigsType = {
+ show_emptyChat?: boolean;
+ show_register?: boolean;
+ show_appStore?: boolean;
+ show_promotion?: boolean;
+ show_userDetail?: boolean;
+ show_git?: false;
+ authorText?: string;
+};
+
declare global {
var mongodb: Mongoose | string | null;
var pgClient: Pool | null;
@@ -31,6 +41,7 @@ declare global {
var chatModels: ChatModelItemType[];
var qaModels: QAModelItemType[];
var vectorModels: VectorModelItemType[];
+ var feConfigs: FeConfigsType;
interface Window {
['pdfjs-dist/build/pdf']: any;
diff --git a/docs/zh/modules/imgs/intro1.png b/docs/zh/modules/imgs/intro1.png
new file mode 100644
index 000000000..4aff501a5
Binary files /dev/null and b/docs/zh/modules/imgs/intro1.png differ
diff --git a/docs/zh/modules/intro.md b/docs/zh/modules/intro.md
new file mode 100644
index 000000000..45e825f61
--- /dev/null
+++ b/docs/zh/modules/intro.md
@@ -0,0 +1,19 @@
+# 模块编排介绍
+
+FastGpt V4 后将采用新的交互方式来构建 AI 应用。使用了“节点”编排的方式去掉原先的表单方式。提高可玩性和扩展性的同时也提高了上手的门槛,这篇文章就来简单介绍一下 “预览版” 的模块编排基本使用方法。
+
+
+
+预览版仅包含了 8 个模块,你可以利用它们来完全实现 V3 的知识库功能。此外,预览版还加入了问题分类模块,可以实现多路线任务。
+
+## 基础知识
+
+### 什么是模块
+
+在程序中,模块可以理解为一个个 function 或者接口。对于非技术背景同学,可以理解为它就是一个**步骤**。将多个模块一个个拼接起来,即可一步步的去实现最终的 AI 输出。
+
+### 如何阅读和理解
+
+1. 建议从左往右阅读。
+2. 从 **用户问题** 模块开始。用户问题模块,代表的是用户发送了一段文本,触发任务开始。
+3.