v4.6.2-alpah (#511)

This commit is contained in:
Archer
2023-11-24 15:29:43 +08:00
committed by GitHub
parent 60f752629f
commit 9cb4280a16
208 changed files with 5396 additions and 3500 deletions

View File

@@ -27,6 +27,7 @@ Router.events.on('routeChangeError', () => NProgress.done());
const queryClient = new QueryClient({
defaultOptions: {
queries: {
keepPreviousData: true,
refetchOnWindowFocus: false,
retry: false,
cacheTime: 10

View File

@@ -26,7 +26,12 @@ const BillDetail = ({ bill, onClose }: { bill: BillItemType; onClose: () => void
);
return (
<MyModal isOpen={true} onClose={onClose} title={t('user.Bill Detail')}>
<MyModal
isOpen={true}
onClose={onClose}
iconSrc="/imgs/modal/bill.svg"
title={t('user.Bill Detail')}
>
<ModalBody>
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 80px'}>:</Box>
@@ -63,15 +68,15 @@ const BillDetail = ({ bill, onClose }: { bill: BillItemType; onClose: () => void
<Th></Th>
<Th>AI模型</Th>
<Th>Token长度</Th>
<Th></Th>
<Th>()</Th>
</Tr>
</Thead>
<Tbody>
{filterBillList.map((item, i) => (
<Tr key={i}>
<Td>{t(item.moduleName)}</Td>
<Td>{item.model}</Td>
<Td>{item.tokenLen}</Td>
<Td>{item.model || '-'}</Td>
<Td>{item.tokenLen || '-'}</Td>
<Td>{formatPrice(item.amount)}</Td>
</Tr>
))}

View File

@@ -29,7 +29,12 @@ const OpenAIAccountModal = ({
});
return (
<MyModal isOpen onClose={onClose} title={t('user.OpenAI Account Setting')}>
<MyModal
isOpen
onClose={onClose}
iconSrc="/imgs/modal/openai.svg"
title={t('user.OpenAI Account Setting')}
>
<ModalBody>
<Box fontSize={'sm'} color={'myGray.500'}>
OpenAI/OneAPI 线使 OpenAI Chat

View File

@@ -67,9 +67,10 @@ const PayModal = ({ onClose }: { onClose: () => void }) => {
isOpen={true}
onClose={payId ? undefined : onClose}
title={t('user.Pay')}
iconSrc="/imgs/modal/pay.svg"
isCentered={!payId}
>
<ModalBody p={0} minH={payId ? 'auto' : '70vh'} display={'flex'} flexDirection={'column'}>
<ModalBody px={0} minH={payId ? 'auto' : '70vh'} display={'flex'} flexDirection={'column'}>
{!payId && (
<>
<Grid gridTemplateColumns={'repeat(4,1fr)'} gridGap={5} mb={4} px={6}>

View File

@@ -37,7 +37,12 @@ const UpdatePswModal = ({ onClose }: { onClose: () => void }) => {
});
return (
<MyModal isOpen onClose={onClose} title={t('user.Update Password')}>
<MyModal
isOpen
onClose={onClose}
iconSrc="/imgs/modal/password.svg"
title={t('user.Update Password')}
>
<ModalBody>
<Flex alignItems={'center'}>
<Box flex={'0 0 70px'}>:</Box>

View File

@@ -106,7 +106,6 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
(tab: string) => {
if (tab === TabEnum.loginout) {
openConfirm(() => {
clearToken();
setUserInfo(null);
router.replace('/login');
})();

View File

@@ -41,7 +41,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
]);
} catch (error) {}
await initPgData();
try {
await initPgData();
} catch (error) {}
await MongoDataset.updateMany(
{},

View File

@@ -0,0 +1,40 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { delay } from '@/utils/tools';
import { PgClient } from '@fastgpt/service/common/pg';
import {
DatasetDataIndexTypeEnum,
PgDatasetTableName
} from '@fastgpt/global/core/dataset/constant';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { getUserDefaultTeam } from '@fastgpt/service/support/user/team/controller';
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
import { defaultQAModels } from '@fastgpt/global/core/ai/model';
import { MongoApp } from '@fastgpt/service/core/app/schema';
let success = 0;
/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { limit = 50 } = req.body as { limit: number };
await authCert({ req, authRoot: true });
await connectToDatabase();
await initFullTextToken(limit);
jsonRes(res, {
message: 'success'
});
} catch (error) {
console.log(error);
jsonRes(res, {
code: 500,
error
});
}
}
export async function initFullTextToken(limit = 50) {}

View File

@@ -0,0 +1,615 @@
/*
universal mode.
@author: FastGpt Team
*/
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import type { AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d';
import type { ModuleItemType } from '@fastgpt/global/core/module/type';
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { FormatForm2ModulesProps } from '@fastgpt/global/core/app/api';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { formData, chatModelMaxToken, chatModelList } = req.body as FormatForm2ModulesProps;
const modules = [
...(formData.dataset.datasets.length > 0
? datasetTemplate({ formData, maxToken: chatModelMaxToken })
: simpleChatTemplate({ formData, maxToken: chatModelMaxToken }))
];
jsonRes<ModuleItemType[]>(res, {
data: modules
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
function simpleChatTemplate({
formData,
maxToken
}: {
formData: AppSimpleEditFormType;
maxToken: number;
}): ModuleItemType[] {
return [
{
moduleId: 'userChatInput',
name: '用户问题(对话入口)',
logo: '/imgs/module/userChatInput.png',
flowType: 'questionInput',
position: {
x: 464.32198615344566,
y: 1602.2698463081606
},
inputs: [
{
key: 'userChatInput',
type: 'systemInput',
label: '用户问题',
connected: true
}
],
outputs: [
{
key: 'userChatInput',
label: '用户问题',
type: 'source',
valueType: 'string',
targets: [
{
moduleId: 'chatModule',
key: 'userChatInput'
}
]
}
]
},
{
moduleId: 'history',
name: '聊天记录',
logo: '/imgs/module/history.png',
flowType: 'historyNode',
position: {
x: 452.5466249541586,
y: 1276.3930310334215
},
inputs: [
{
key: 'maxContext',
type: 'numberInput',
label: '最长记录数',
value: 10,
min: 0,
max: 50,
connected: true
},
{
key: 'history',
type: 'hidden',
label: '聊天记录',
connected: true
}
],
outputs: [
{
key: 'history',
label: '聊天记录',
valueType: 'chatHistory',
type: 'source',
targets: [
{
moduleId: 'chatModule',
key: 'history'
}
]
}
]
},
{
moduleId: 'chatModule',
name: 'AI 对话',
logo: '/imgs/module/AI.png',
flowType: 'chatNode',
showStatus: true,
position: {
x: 981.9682828103937,
y: 890.014595014464
},
inputs: [
{
key: 'switch',
type: 'target',
label: 'core.module.input.label.switch',
valueType: 'any',
connected: false
},
{
key: 'model',
type: 'selectChatModel',
label: '对话模型',
required: true,
value: formData.aiSettings.model,
connected: true
},
{
key: 'temperature',
type: 'hidden',
label: '温度',
value: 1,
min: 0,
max: 10,
step: 1,
markList: [
{
label: '严谨',
value: 0
},
{
label: '发散',
value: 10
}
],
connected: true
},
{
key: 'maxToken',
type: 'hidden',
label: '回复上限',
value: maxToken,
min: 100,
max: 4000,
step: 50,
markList: [
{
label: '100',
value: 100
},
{
label: '4000',
value: 4000
}
],
connected: true
},
{
key: 'isResponseAnswerText',
type: 'hidden',
label: '返回AI内容',
valueType: 'boolean',
value: true,
connected: true
},
{
key: 'quoteTemplate',
type: 'hidden',
label: '引用内容模板',
valueType: 'string',
value: '',
connected: true
},
{
key: 'quotePrompt',
type: 'hidden',
label: '引用内容提示词',
valueType: 'string',
value: '',
connected: true
},
{
key: 'aiSettings',
type: 'aiSettings',
label: '',
connected: false
},
{
key: 'systemPrompt',
type: 'textarea',
label: '系统提示词',
max: 300,
valueType: 'string',
description:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
placeholder:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
value: formData.aiSettings.systemPrompt,
connected: true
},
{
key: 'quoteQA',
type: 'target',
label: '引用内容',
description: "对象数组格式,结构:\n [{q:'问题',a:'回答'}]",
valueType: 'datasetQuote',
connected: false
},
{
key: 'history',
type: 'target',
label: 'core.module.input.label.chat history',
valueType: 'chatHistory',
connected: true
},
{
key: 'userChatInput',
type: 'target',
label: 'core.module.input.label.user question',
required: true,
valueType: 'string',
connected: true
}
],
outputs: [
{
key: 'answerText',
label: 'AI回复',
description: '将在 stream 回复完毕后触发',
valueType: 'string',
type: 'source',
targets: []
},
{
key: 'finish',
label: 'core.module.output.label.running done',
description: 'core.module.output.description.running done',
valueType: 'boolean',
type: 'source',
targets: []
},
{
key: 'history',
label: '新的上下文',
description: '将本次回复内容拼接上历史记录,作为新的上下文返回',
valueType: 'chatHistory',
type: 'source',
targets: []
}
]
}
];
}
function datasetTemplate({
formData,
maxToken
}: {
formData: AppSimpleEditFormType;
maxToken: number;
}): ModuleItemType[] {
return [
{
moduleId: 'userChatInput',
name: '用户问题(对话入口)',
logo: '/imgs/module/userChatInput.png',
flowType: 'questionInput',
position: {
x: 464.32198615344566,
y: 1602.2698463081606
},
inputs: [
{
key: 'userChatInput',
type: 'systemInput',
label: '用户问题',
connected: true
}
],
outputs: [
{
key: 'userChatInput',
label: '用户问题',
type: 'source',
valueType: 'string',
targets: [
{
moduleId: 'chatModule',
key: 'userChatInput'
},
{
moduleId: 'datasetSearch',
key: 'userChatInput'
}
]
}
]
},
{
moduleId: 'history',
name: '聊天记录',
logo: '/imgs/module/history.png',
flowType: 'historyNode',
position: {
x: 452.5466249541586,
y: 1276.3930310334215
},
inputs: [
{
key: 'maxContext',
type: 'numberInput',
label: '最长记录数',
value: 6,
min: 0,
max: 50,
connected: true
},
{
key: 'history',
type: 'hidden',
label: '聊天记录',
connected: true
}
],
outputs: [
{
key: 'history',
label: '聊天记录',
valueType: 'chatHistory',
type: 'source',
targets: [
{
moduleId: 'chatModule',
key: 'history'
}
]
}
]
},
{
moduleId: 'datasetSearch',
name: '知识库搜索',
logo: '/imgs/module/db.png',
flowType: 'datasetSearchNode',
showStatus: true,
position: {
x: 956.0838440206068,
y: 887.462827870246
},
inputs: [
{
key: 'datasets',
value: formData.dataset.datasets,
type: FlowNodeInputTypeEnum.custom,
label: '关联的知识库',
connected: true
},
{
key: 'similarity',
value: 0.5,
type: FlowNodeInputTypeEnum.slider,
label: '相似度',
connected: true
},
{
key: 'limit',
value: 8,
type: FlowNodeInputTypeEnum.slider,
label: '单次搜索上限',
connected: true
},
{
key: 'switch',
type: FlowNodeInputTypeEnum.target,
label: '触发器',
connected: false
},
{
key: 'userChatInput',
type: FlowNodeInputTypeEnum.target,
label: '用户问题',
connected: true
},
{
key: 'rerank',
type: FlowNodeInputTypeEnum.switch,
label: '结果重排',
description: '将召回的结果进行进一步重排,可增加召回率',
plusField: true,
connected: true,
value: true
}
],
outputs: [
{
key: 'isEmpty',
label: '搜索结果为空',
type: 'source',
valueType: 'boolean',
targets: []
},
{
key: 'unEmpty',
label: '搜索结果不为空',
type: 'source',
valueType: 'boolean',
targets: []
},
{
key: 'quoteQA',
label: '引用内容',
description:
'始终返回数组,如果希望搜索结果为空时执行额外操作,需要用到上面的两个输入以及目标模块的触发器',
type: 'source',
valueType: 'datasetQuote',
targets: [
{
moduleId: 'chatModule',
key: 'quoteQA'
}
]
},
{
key: 'finish',
label: 'core.module.output.label.running done',
description: 'core.module.output.description.running done',
valueType: 'boolean',
type: 'source',
targets: []
}
]
},
{
moduleId: 'chatModule',
name: 'AI 对话',
logo: '/imgs/module/AI.png',
flowType: 'chatNode',
showStatus: true,
position: {
x: 1551.71405495818,
y: 977.4911578918461
},
inputs: [
{
key: 'switch',
type: 'target',
label: 'core.module.input.label.switch',
valueType: 'any',
connected: false
},
{
key: 'model',
type: 'selectChatModel',
label: '对话模型',
required: true,
value: formData.aiSettings.model,
connected: true
},
{
key: 'temperature',
type: 'hidden',
label: '温度',
value: 0,
min: 0,
max: 10,
step: 1,
markList: [
{
label: '严谨',
value: 0
},
{
label: '发散',
value: 10
}
],
connected: true
},
{
key: 'maxToken',
type: 'hidden',
label: '回复上限',
value: maxToken,
min: 100,
max: 4000,
step: 50,
markList: [
{
label: '100',
value: 100
},
{
label: '4000',
value: 4000
}
],
connected: true
},
{
key: 'isResponseAnswerText',
type: 'hidden',
label: '返回AI内容',
valueType: 'boolean',
value: true,
connected: true
},
{
key: 'quoteTemplate',
type: 'hidden',
label: '引用内容模板',
valueType: 'string',
value: '',
connected: true
},
{
key: 'quotePrompt',
type: 'hidden',
label: '引用内容提示词',
valueType: 'string',
value: '',
connected: true
},
{
key: 'aiSettings',
type: 'aiSettings',
label: '',
connected: false
},
{
key: 'systemPrompt',
type: 'textarea',
label: '系统提示词',
max: 300,
valueType: 'string',
description:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
placeholder:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
value: formData.aiSettings.systemPrompt,
connected: true
},
{
key: 'quoteQA',
type: 'target',
label: '引用内容',
description: "对象数组格式,结构:\n [{q:'问题',a:'回答'}]",
valueType: 'datasetQuote',
connected: true
},
{
key: 'history',
type: 'target',
label: 'core.module.input.label.chat history',
valueType: 'chatHistory',
connected: true
},
{
key: 'userChatInput',
type: 'target',
label: 'core.module.input.label.user question',
required: true,
valueType: 'string',
connected: true
}
],
outputs: [
{
key: 'answerText',
label: 'AI回复',
description: '将在 stream 回复完毕后触发',
valueType: 'string',
type: 'source',
targets: []
},
{
key: 'finish',
label: 'core.module.output.label.running done',
description: 'core.module.output.description.running done',
valueType: 'boolean',
type: 'source',
targets: []
},
{
key: 'history',
label: '新的上下文',
description: '将本次回复内容拼接上历史记录,作为新的上下文返回',
valueType: 'chatHistory',
type: 'source',
targets: []
}
]
}
];
}

View File

@@ -0,0 +1,422 @@
/*
universal mode.
@author: FastGpt Team
*/
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import type { AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d';
import type { ModuleItemType } from '@fastgpt/global/core/module/type';
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { ModuleDataTypeEnum } from '@fastgpt/global/core/module/constants';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import type { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type.d';
import { FormatForm2ModulesProps } from '@fastgpt/global/core/app/api';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { formData, chatModelList } = req.body as FormatForm2ModulesProps;
const modules =
formData.dataset.datasets.length > 0
? datasetTemplate(formData)
: simpleChatTemplate(formData);
jsonRes<ModuleItemType[]>(res, {
data: modules
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
function chatModelInput(formData: AppSimpleEditFormType): FlowNodeInputItemType[] {
return [
{
key: 'model',
value: formData.aiSettings.model,
type: 'custom',
label: '对话模型',
connected: true
},
{
key: 'temperature',
value: formData.aiSettings.temperature,
type: 'slider',
label: '温度',
connected: true
},
{
key: 'maxToken',
value: formData.aiSettings.maxToken,
type: 'custom',
label: '回复上限',
connected: true
},
{
key: 'systemPrompt',
value: formData.aiSettings.systemPrompt || '',
type: 'textarea',
label: '系统提示词',
connected: true
},
{
key: ModuleInputKeyEnum.aiChatIsResponseText,
value: true,
type: 'hidden',
label: '返回AI内容',
connected: true
},
{
key: 'quoteTemplate',
value: formData.aiSettings.quoteTemplate || '',
type: 'hidden',
label: '引用内容模板',
connected: true
},
{
key: 'quotePrompt',
value: formData.aiSettings.quotePrompt || '',
type: 'hidden',
label: '引用内容提示词',
connected: true
},
{
key: 'switch',
type: 'target',
label: '触发器',
connected: formData.dataset.datasets.length > 0 && !!formData.dataset.searchEmptyText
},
{
key: 'quoteQA',
type: 'target',
label: '引用内容',
connected: formData.dataset.datasets.length > 0
},
{
key: 'history',
type: 'target',
label: '聊天记录',
connected: true
},
{
key: 'userChatInput',
type: 'target',
label: '用户问题',
connected: true
}
];
}
function simpleChatTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
return [
{
name: '用户问题(对话入口)',
flowType: FlowNodeTypeEnum.questionInput,
inputs: [
{
key: 'userChatInput',
connected: true,
label: '用户问题',
type: 'target'
}
],
outputs: [
{
key: 'userChatInput',
targets: [
{
moduleId: 'chatModule',
key: 'userChatInput'
}
]
}
],
position: {
x: 464.32198615344566,
y: 1602.2698463081606
},
moduleId: 'userChatInput'
},
{
name: '聊天记录',
flowType: FlowNodeTypeEnum.historyNode,
inputs: [
{
key: 'maxContext',
value: 6,
connected: true,
type: 'numberInput',
label: '最长记录数'
},
{
key: 'history',
type: 'hidden',
label: '聊天记录',
connected: true
}
],
outputs: [
{
key: 'history',
targets: [
{
moduleId: 'chatModule',
key: 'history'
}
]
}
],
position: {
x: 452.5466249541586,
y: 1276.3930310334215
},
moduleId: 'history'
},
{
name: 'AI 对话',
flowType: FlowNodeTypeEnum.chatNode,
inputs: chatModelInput(formData),
showStatus: true,
outputs: [
{
key: 'answerText',
label: 'AI回复',
description: '直接响应,无需配置',
type: 'hidden',
targets: []
},
{
key: 'finish',
label: '回复结束',
description: 'AI 回复完成后触发',
valueType: 'boolean',
type: 'source',
targets: []
}
],
position: {
x: 981.9682828103937,
y: 890.014595014464
},
moduleId: 'chatModule'
}
];
}
function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
return [
{
name: '用户问题(对话入口)',
flowType: FlowNodeTypeEnum.questionInput,
inputs: [
{
key: 'userChatInput',
label: '用户问题',
type: 'target',
connected: true
}
],
outputs: [
{
key: 'userChatInput',
targets: [
{
moduleId: 'chatModule',
key: 'userChatInput'
},
{
moduleId: 'datasetSearch',
key: 'userChatInput'
}
]
}
],
position: {
x: 464.32198615344566,
y: 1602.2698463081606
},
moduleId: 'userChatInput'
},
{
name: '聊天记录',
flowType: FlowNodeTypeEnum.historyNode,
inputs: [
{
key: 'maxContext',
value: 6,
connected: true,
type: 'numberInput',
label: '最长记录数'
},
{
key: 'history',
type: 'hidden',
label: '聊天记录',
connected: true
}
],
outputs: [
{
key: 'history',
targets: [
{
moduleId: 'chatModule',
key: 'history'
}
]
}
],
position: {
x: 452.5466249541586,
y: 1276.3930310334215
},
moduleId: 'history'
},
{
name: '知识库搜索',
flowType: FlowNodeTypeEnum.datasetSearchNode,
showStatus: true,
inputs: [
{
key: 'datasets',
value: formData.dataset.datasets,
type: FlowNodeInputTypeEnum.custom,
label: '关联的知识库',
connected: true
},
{
key: 'similarity',
value: formData.dataset.similarity,
type: FlowNodeInputTypeEnum.slider,
label: '相似度',
connected: true
},
{
key: 'limit',
value: formData.dataset.limit,
type: FlowNodeInputTypeEnum.slider,
label: '单次搜索上限',
connected: true
},
{
key: 'switch',
type: FlowNodeInputTypeEnum.target,
label: '触发器',
connected: false
},
{
key: 'userChatInput',
type: FlowNodeInputTypeEnum.target,
label: '用户问题',
connected: true
},
{
key: 'rerank',
type: FlowNodeInputTypeEnum.switch,
label: '结果重排',
description: '将召回的结果进行进一步重排,可增加召回率',
plusField: true,
connected: true,
value: formData.dataset.rerank
}
],
outputs: [
{
key: 'isEmpty',
targets: formData.dataset.searchEmptyText
? [
{
moduleId: 'emptyText',
key: 'switch'
}
]
: []
},
{
key: 'unEmpty',
targets: formData.dataset.searchEmptyText
? [
{
moduleId: 'chatModule',
key: 'switch'
}
]
: []
},
{
key: 'quoteQA',
targets: [
{
moduleId: 'chatModule',
key: 'quoteQA'
}
]
}
],
position: {
x: 956.0838440206068,
y: 887.462827870246
},
moduleId: 'datasetSearch'
},
...(formData.dataset.searchEmptyText
? [
{
name: '指定回复',
flowType: FlowNodeTypeEnum.answerNode,
inputs: [
{
key: ModuleInputKeyEnum.switch,
type: FlowNodeInputTypeEnum.target,
label: '触发器',
connected: true
},
{
key: ModuleInputKeyEnum.answerText,
value: formData.dataset.searchEmptyText,
type: FlowNodeInputTypeEnum.textarea,
valueType: ModuleDataTypeEnum.string,
label: '回复的内容',
connected: true
}
],
outputs: [],
position: {
x: 1553.5815811529146,
y: 637.8753731306779
},
moduleId: 'emptyText'
}
]
: []),
{
name: 'AI 对话',
flowType: FlowNodeTypeEnum.chatNode,
inputs: chatModelInput(formData),
showStatus: true,
outputs: [
{
key: 'answerText',
label: 'AI回复',
description: '直接响应,无需配置',
type: 'hidden',
targets: []
},
{
key: 'finish',
label: '回复结束',
description: 'AI 回复完成后触发',
valueType: 'boolean',
type: 'source',
targets: []
}
],
position: {
x: 1551.71405495818,
y: 977.4911578918461
},
moduleId: 'chatModule'
}
];
}

View File

@@ -4,13 +4,14 @@ import { connectToDatabase } from '@/service/mongo';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import type { AppUpdateParams } from '@fastgpt/global/core/app/api';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { SystemOutputEnum } from '@/constants/app';
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
/* 获取我的模型 */
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { name, avatar, type, intro, modules, permission } = req.body as AppUpdateParams;
const { name, avatar, type, simpleTemplateId, intro, modules, permission } =
req.body as AppUpdateParams;
const { appId } = req.query as { appId: string };
if (!appId) {
@@ -28,19 +29,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
{
name,
type,
simpleTemplateId,
avatar,
intro,
permission,
...(modules && {
modules: modules.map((modules) => ({
...modules,
outputs: modules.outputs.sort((a, b) => {
// finish output always at last
if (a.key === SystemOutputEnum.finish) return 1;
if (b.key === SystemOutputEnum.finish) return -1;
return 0;
})
}))
modules
})
}
);

View File

@@ -52,7 +52,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
/* start process */
const { responseData } = await dispatchModules({
res,
modules: modules,
appId,
modules,
variables,
teamId,
tmbId,

View File

@@ -1,16 +1,14 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import type { InitChatResponse } from '@fastgpt/global/core/chat/api.d';
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
import type { ChatSchema } from '@fastgpt/global/core/chat/type.d';
import { getGuideModule } from '@/global/core/app/modules/utils';
import { getGuideModule } from '@fastgpt/global/core/module/utils';
import { getChatModelNameListByModules } from '@/service/core/app/module';
import { TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants';
import { authChat } from '@fastgpt/service/support/permission/auth/chat';
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
/* 初始化我的聊天框,需要身份验证 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
@@ -55,7 +53,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
{
chatId
},
`dataId obj value adminFeedback userFeedback ${TaskResponseKeyEnum.responseData}`
`dataId obj value adminFeedback userFeedback ${ModuleOutputKeyEnum.responseData}`
)
.sort({ _id: -1 })
.limit(30);

View File

@@ -9,8 +9,15 @@ import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/use
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { name, tags, avatar, vectorModel, agentModel, parentId, type } =
req.body as CreateDatasetParams;
const {
name,
tags,
avatar,
vectorModel = global.vectorModels[0].model,
agentModel,
parentId,
type
} = req.body as CreateDatasetParams;
// 凭证校验
const { teamId, tmbId } = await authUserNotVisitor({ req, authToken: true });

View File

@@ -51,8 +51,9 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
// token check
const token = countPromptTokens(formatQ, 'system');
const vectorModelData = getVectorModel(vectorModel);
if (token > getVectorModel(vectorModel).maxToken) {
if (token > vectorModelData.maxToken) {
return Promise.reject('Q Over Tokens');
}
@@ -70,7 +71,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
collectionId,
q: formatQ,
a: formatA,
model: vectorModel,
model: vectorModelData.model,
indexes
});
@@ -78,7 +79,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
teamId,
tmbId,
tokenLen: tokenLen,
model: vectorModel
model: vectorModelData.model
});
jsonRes<string>(res, {

View File

@@ -0,0 +1,28 @@
/*
get plugin preview modules
*/
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { getPluginPreviewModule } from '@fastgpt/service/core/plugin/controller';
import { authPluginCanUse } from '@fastgpt/service/support/permission/auth/plugin';
import { FlowModuleTemplateType } from '@fastgpt/global/core/module/type';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { id } = req.query as { id: string };
await connectToDatabase();
const { teamId, tmbId } = await authCert({ req, authToken: true });
await authPluginCanUse({ id, teamId, tmbId });
jsonRes<FlowModuleTemplateType>(res, {
data: await getPluginPreviewModule({ id })
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -1,22 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { getPluginModuleDetail } from '@fastgpt/service/core/plugin/controller';
import { authPluginCrud } from '@fastgpt/service/support/permission/auth/plugin';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { id } = req.query as { id: string };
await connectToDatabase();
await authPluginCrud({ req, authToken: true, id, per: 'r' });
jsonRes(res, {
data: await getPluginModuleDetail({ id })
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -1,21 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { getUserPlugins2Templates } from '@fastgpt/service/core/plugin/controller';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { teamId } = await authCert({ req, authToken: true });
jsonRes(res, {
data: await getUserPlugins2Templates({ teamId })
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -0,0 +1,56 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoPlugin } from '@fastgpt/service/core/plugin/schema';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { FlowModuleTemplateType } from '@fastgpt/global/core/module/type';
import { ModuleTemplateTypeEnum } from '@fastgpt/global/core/module/constants';
import { GET } from '@fastgpt/service/common/api/plusRequest';
import type { PluginTemplateType } from '@fastgpt/global/core/plugin/type.d';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { teamId } = await authCert({ req, authToken: true });
const [userPlugins, plusPlugins] = await Promise.all([
MongoPlugin.find({ teamId }).lean(),
GET<PluginTemplateType[]>('/core/plugin/getTemplates')
]);
const data: FlowModuleTemplateType[] = [
...userPlugins.map((plugin) => ({
id: String(plugin._id),
templateType: ModuleTemplateTypeEnum.personalPlugin,
flowType: FlowNodeTypeEnum.pluginModule,
avatar: plugin.avatar,
name: plugin.name,
intro: plugin.intro,
showStatus: false,
inputs: [],
outputs: []
})),
...(global.communityPlugins?.map((plugin) => ({
id: plugin.id,
templateType: ModuleTemplateTypeEnum.communityPlugin,
flowType: FlowNodeTypeEnum.pluginModule,
avatar: plugin.avatar,
name: plugin.name,
intro: plugin.intro,
showStatus: true,
inputs: [],
outputs: []
})) || [])
];
jsonRes<FlowModuleTemplateType[]>(res, {
data
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -4,7 +4,7 @@ import { connectToDatabase } from '@/service/mongo';
import { MongoUser } from '@fastgpt/service/support/user/schema';
import type { InitShareChatResponse } from '@fastgpt/global/support/outLink/api.d';
import { HUMAN_ICON } from '@fastgpt/global/core/chat/constants';
import { getGuideModule } from '@/global/core/app/modules/utils';
import { getGuideModule } from '@fastgpt/global/core/module/utils';
import { authShareChatInit } from '@/service/support/outLink/auth';
import { getChatModelNameListByModules } from '@/service/core/app/module';
import { authOutLinkValid } from '@fastgpt/service/support/permission/auth/outLink';

View File

@@ -1,7 +1,7 @@
import type { FeConfigsType, SystemEnvType } from '@fastgpt/global/common/system/types/index.d';
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { readFileSync } from 'fs';
import { readFileSync, readdirSync } from 'fs';
import type { ConfigFileType, InitDateResponse } from '@/global/common/api/systemRes';
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
import { getTikTokenEnc } from '@fastgpt/global/common/string/tiktoken';
@@ -16,10 +16,12 @@ import {
defaultAudioSpeechModels,
defaultWhisperModel
} from '@fastgpt/global/core/ai/model';
import { SimpleModeTemplate_FastGPT_Universal } from '@/global/core/app/constants';
import { getSimpleTemplatesFromPlus } from '@/service/core/app/utils';
import { PluginTypeEnum } from '@fastgpt/global/core/plugin/constants';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
getInitConfig();
getModelPrice();
await getInitConfig();
jsonRes<InitDateResponse>(res, {
data: {
@@ -35,7 +37,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
key: undefined
})),
priceMd: global.priceMd,
systemVersion: global.systemVersion || '0.0.0'
systemVersion: global.systemVersion || '0.0.0',
simpleModeTemplates: global.simpleModeTemplates
}
});
}
@@ -60,31 +63,35 @@ const defaultFeConfigs: FeConfigsType = {
favicon: '/favicon.ico'
};
export function initGlobal() {
// init tikToken
getTikTokenEnc();
initHttpAgent();
global.qaQueueLen = 0;
global.vectorQueueLen = 0;
}
export function getInitConfig() {
export async function getInitConfig() {
try {
if (global.feConfigs) return;
getSystemVersion();
initGlobal();
const filename =
process.env.NODE_ENV === 'development' ? 'data/config.local.json' : '/app/data/config.json';
const res = JSON.parse(readFileSync(filename, 'utf-8')) as ConfigFileType;
console.log(`System Version: ${global.systemVersion}`);
setDefaultData(res);
} catch (error) {
setDefaultData();
console.log('get init config error, set default', error);
}
await getSimpleModeTemplates();
getSystemVersion();
getModelPrice();
getSystemPlugin();
}
export function initGlobal() {
// init tikToken
getTikTokenEnc();
initHttpAgent();
global.communityPlugins = [];
global.simpleModeTemplates = [];
global.qaQueueLen = global.qaQueueLen ?? 0;
global.vectorQueueLen = global.vectorQueueLen ?? 0;
}
export function setDefaultData(res?: ConfigFileType) {
@@ -109,18 +116,19 @@ export function setDefaultData(res?: ConfigFileType) {
global.priceMd = '';
console.log(global);
console.log(res);
}
export function getSystemVersion() {
try {
if (process.env.NODE_ENV === 'development') {
global.systemVersion = process.env.npm_package_version || '0.0.0';
return;
}
const packageJson = JSON.parse(readFileSync('/app/package.json', 'utf-8'));
} else {
const packageJson = JSON.parse(readFileSync('/app/package.json', 'utf-8'));
global.systemVersion = packageJson?.version;
global.systemVersion = packageJson?.version;
}
console.log(`System Version: ${global.systemVersion}`);
} catch (error) {
console.log(error);
@@ -157,3 +165,67 @@ ${`| 语音输入-${global.whisperModel.name} | ${global.whisperModel.price}/分
`;
console.log(global.priceMd);
}
async function getSimpleModeTemplates() {
if (global.simpleModeTemplates && global.simpleModeTemplates.length > 0) return;
try {
const basePath =
process.env.NODE_ENV === 'development'
? 'public/simpleTemplates'
: '/app/projects/app/public/simpleTemplates';
// read data/simpleTemplates directory, get all json file
const files = readdirSync(basePath);
// filter json file
const filterFiles = files.filter((item) => item.endsWith('.json'));
// read json file
const fileTemplates = filterFiles.map((item) => {
const content = readFileSync(`${basePath}/${item}`, 'utf-8');
return {
id: item.replace('.json', ''),
...JSON.parse(content)
};
});
// fetch templates from plus
const plusTemplates = await getSimpleTemplatesFromPlus();
global.simpleModeTemplates = [
SimpleModeTemplate_FastGPT_Universal,
...plusTemplates,
...fileTemplates
];
} catch (error) {
global.simpleModeTemplates = [SimpleModeTemplate_FastGPT_Universal];
}
console.log('simple mode templates: ');
console.log(global.simpleModeTemplates);
}
function getSystemPlugin() {
if (global.communityPlugins && global.communityPlugins.length > 0) return;
const basePath =
process.env.NODE_ENV === 'development'
? 'public/pluginTemplates'
: '/app/projects/app/public/pluginTemplates';
// read data/pluginTemplates directory, get all json file
const files = readdirSync(basePath);
// filter json file
const filterFiles = files.filter((item) => item.endsWith('.json'));
// read json file
const fileTemplates = filterFiles.map((item) => {
const content = readFileSync(`${basePath}/${item}`, 'utf-8');
return {
id: `${PluginTypeEnum.community}-${item.replace('.json', '')}`,
type: PluginTypeEnum.community,
...JSON.parse(content)
};
});
global.communityPlugins = fileTemplates;
console.log('community plugins: ');
console.log(fileTemplates);
}

View File

@@ -179,6 +179,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
/* start flow controller */
const { responseData, answerText } = await dispatchModules({
res,
appId: String(app._id),
chatId,
modules: app.modules,
user,

View File

@@ -1,12 +1,9 @@
import React, { useCallback, useRef, useState } from 'react';
import React, { useRef, useState } from 'react';
import { Box, Flex, IconButton, useTheme, useDisclosure } from '@chakra-ui/react';
import { SmallCloseIcon } from '@chakra-ui/icons';
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { FlowNodeOutputTargetItemType } from '@fastgpt/global/core/module/node/type';
import { ModuleItemType } from '@fastgpt/global/core/module/type';
import { useRequest } from '@/web/common/hooks/useRequest';
import { AppSchema } from '@fastgpt/global/core/app/type.d';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useTranslation } from 'next-i18next';
import { useCopyData } from '@/web/common/hooks/useCopyData';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
@@ -47,11 +44,14 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
// check required connect
for (let i = 0; i < modules.length; i++) {
const item = modules[i];
if (item.inputs.find((input) => input.required && !input.connected)) {
return Promise.reject(`${item.name}】存在未连接的必填输入`);
}
if (item.inputs.find((input) => input.valueCheck && !input.valueCheck(input.value))) {
return Promise.reject(`${item.name}】存在为填写的必填项`);
if (
item.inputs.find((input) => {
if (!input.required || input.connected) return false;
if (!input.value || input.value === '' || input.value?.length === 0) return true;
return false;
})
) {
return Promise.reject(`${item.name}】存在未填或未连接参数`);
}
}

View File

@@ -3,8 +3,8 @@ import { AppSchema } from '@fastgpt/global/core/app/type.d';
import Header from './Header';
import Flow from '@/components/core/module/Flow';
import FlowProvider, { useFlowProviderStore } from '@/components/core/module/Flow/FlowProvider';
import { SystemModuleTemplateType } from '@fastgpt/global/core/module/type.d';
import { SystemModuleTemplates } from '@/constants/flow/ModuleTemplate';
import type { FlowModuleTemplateType } from '@fastgpt/global/core/module/type.d';
import { appSystemModuleTemplates } from '@/web/core/modules/template/system';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { usePluginStore } from '@/web/core/plugin/store/plugin';
import { useQuery } from '@tanstack/react-query';
@@ -13,24 +13,24 @@ type Props = { app: AppSchema; onClose: () => void };
const Render = ({ app, onClose }: Props) => {
const { nodes } = useFlowProviderStore();
const { pluginModuleTemplates, loadPluginModuleTemplates } = usePluginStore();
const { pluginModuleTemplates, loadPluginTemplates } = usePluginStore();
const filterTemplates = useMemo(() => {
const copyTemplates: SystemModuleTemplateType = JSON.parse(
JSON.stringify(SystemModuleTemplates)
const copyTemplates: FlowModuleTemplateType[] = JSON.parse(
JSON.stringify(appSystemModuleTemplates)
);
const filterType: Record<string, 1> = {
[FlowNodeTypeEnum.userGuide]: 1
};
// filter some template
nodes.forEach((node) => {
if (node.type && filterType[node.type]) {
copyTemplates.forEach((item) => {
item.list.forEach((module, index) => {
if (module.flowType === node.type) {
item.list.splice(index, 1);
}
});
copyTemplates.forEach((module, index) => {
if (module.flowType === node.type) {
copyTemplates.splice(index, 1);
}
});
}
});
@@ -38,13 +38,12 @@ const Render = ({ app, onClose }: Props) => {
return copyTemplates;
}, [nodes]);
useQuery(['getUserPlugs2ModuleTemplates'], () => loadPluginModuleTemplates());
useQuery(['getPlugTemplates'], () => loadPluginTemplates());
return (
<Flow
systemTemplates={filterTemplates}
pluginTemplates={[{ label: '', list: pluginModuleTemplates }]}
show2Plugin
pluginTemplates={pluginModuleTemplates}
modules={app.modules}
Header={<Header app={app} onClose={onClose} />}
/>
@@ -53,7 +52,7 @@ const Render = ({ app, onClose }: Props) => {
export default React.memo(function AdEdit(props: Props) {
return (
<FlowProvider filterAppIds={[props.app._id]}>
<FlowProvider mode={'app'} filterAppIds={[props.app._id]}>
<Render {...props} />
</FlowProvider>
);

View File

@@ -7,7 +7,8 @@ import {
Input,
Textarea,
ModalFooter,
ModalBody
ModalBody,
Image
} from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { AppSchema } from '@fastgpt/global/core/app/type.d';
@@ -118,7 +119,12 @@ const InfoModal = ({
);
return (
<MyModal isOpen={true} onClose={onClose} title={'应用信息设置'}>
<MyModal
isOpen={true}
onClose={onClose}
iconSrc="/imgs/module/ai.svg"
title={t('core.app.setting')}
>
<ModalBody>
<Box> & </Box>
<Flex mt={2} alignItems={'center'}>

View File

@@ -277,7 +277,11 @@ function EditLinkModal({
});
return (
<MyModal isOpen={true} title={isEdit ? t('outlink.Edit Link') : t('outlink.Create Link')}>
<MyModal
isOpen={true}
iconSrc="/imgs/modal/shareLight.svg"
title={isEdit ? t('outlink.Edit Link') : t('outlink.Create Link')}
>
<ModalBody>
<Flex alignItems={'center'}>
<Box flex={'0 0 90px'}>{t('Name')}:</Box>

View File

@@ -1,23 +0,0 @@
import MyIcon from '@/components/Icon';
import MyTooltip from '@/components/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { Box, Flex, Switch, type SwitchProps } from '@chakra-ui/react';
import React from 'react';
import { useTranslation } from 'next-i18next';
const QGSwitch = (props: SwitchProps) => {
const { t } = useTranslation();
return (
<Flex alignItems={'center'}>
<MyIcon name={'core/app/questionGuide'} mr={2} w={'16px'} />
<Box>{t('core.app.Next Step Guide')}</Box>
<MyTooltip label={t('core.app.Question Guide Tip')} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
<Box flex={1} />
<Switch {...props} />
</Flex>
);
};
export default QGSwitch;

View File

@@ -6,32 +6,22 @@ import {
BoxProps,
Textarea,
useTheme,
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer,
useDisclosure,
Button,
IconButton
IconButton,
Image
} from '@chakra-ui/react';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useQuery } from '@tanstack/react-query';
import { QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons';
import { useForm, useFieldArray } from 'react-hook-form';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import {
appModules2Form,
getDefaultAppForm,
appForm2Modules,
type EditFormType
} from '@/web/core/app/basicSettings';
import { chatModelList } from '@/web/common/system/staticData';
import { appModules2Form, getDefaultAppForm } from '@fastgpt/global/core/app/utils';
import type { AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d';
import { chatModelList, simpleModeTemplates } from '@/web/common/system/staticData';
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
import { ChatModelSystemTip, welcomeTextTip } from '@/constants/flow/ModuleTemplate';
import { VariableItemType } from '@/types/app';
import { chatNodeSystemPromptTip, welcomeTextTip } from '@fastgpt/global/core/module/template/tip';
import type { VariableItemType } from '@fastgpt/global/core/module/type.d';
import type { ModuleItemType } from '@fastgpt/global/core/module/type';
import { useRequest } from '@/web/common/hooks/useRequest';
import { useConfirm } from '@/web/common/hooks/useConfirm';
@@ -42,7 +32,16 @@ import { useToast } from '@/web/common/hooks/useToast';
import { AppSchema } from '@fastgpt/global/core/app/type.d';
import { delModelById } from '@/web/core/app/api';
import { useTranslation } from 'next-i18next';
import { getGuideModule } from '@/global/core/app/modules/utils';
import { getGuideModule } from '@fastgpt/global/core/module/utils';
import { DatasetParamsModal } from '@/components/core/module/DatasetSelectModal';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import PermissionIconText from '@/components/support/permission/IconText';
import { checkChatSupportSelectFileByModules } from '@/web/core/chat/utils';
import { useSticky } from '@/web/common/hooks/useSticky';
import { postForm2Modules } from '@/web/core/app/utils';
import dynamic from 'next/dynamic';
import MySelect from '@/components/Select';
@@ -50,19 +49,11 @@ import MyTooltip from '@/components/MyTooltip';
import Avatar from '@/components/Avatar';
import MyIcon from '@/components/Icon';
import ChatBox, { type ComponentRef, type StartChatFnProps } from '@/components/ChatBox';
import { SimpleModeTemplate_FastGPT_Universal } from '@/global/core/app/constants';
import QGSwitch from '@/components/core/module/Flow/components/modules/QGSwitch';
import TTSSelect from '@/components/core/module/Flow/components/modules/TTSSelect';
import VariableEdit from '@/components/core/module/Flow/components/modules/VariableEdit';
import { addVariable } from '@/components/core/module/VariableEditModal';
import { DatasetParamsModal } from '@/components/core/module/DatasetSelectModal';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import PermissionIconText from '@/components/support/permission/IconText';
import QGSwitch from '../QGSwitch';
import TTSSelect from '../TTSSelect';
import { checkChatSupportSelectFileByModules } from '@/web/core/chat/utils';
import { useSticky } from '@/web/common/hooks/useSticky';
const VariableEditModal = dynamic(() => import('@/components/core/module/VariableEditModal'));
const InfoModal = dynamic(() => import('../InfoModal'));
const DatasetSelectModal = dynamic(() => import('@/components/core/module/DatasetSelectModal'));
const AIChatSettingsModal = dynamic(() => import('@/components/core/module/AIChatSettingsModal'));
@@ -84,22 +75,14 @@ function ConfigForm({
const [editVariable, setEditVariable] = useState<VariableItemType>();
const [refresh, setRefresh] = useState(false);
const { register, setValue, getValues, reset, handleSubmit, control } = useForm<EditFormType>({
defaultValues: getDefaultAppForm()
});
const { register, setValue, getValues, reset, handleSubmit, control } =
useForm<AppSimpleEditFormType>({
defaultValues: getDefaultAppForm()
});
const {
fields: variables,
append: appendVariable,
remove: removeVariable,
replace: replaceVariables
} = useFieldArray({
control,
name: 'variables'
});
const { fields: datasets, replace: replaceKbList } = useFieldArray({
control,
name: 'dataset.list'
name: 'dataset.datasets'
});
const {
@@ -120,7 +103,7 @@ function ConfigForm({
const { openConfirm: openConfirmSave, ConfirmModal: ConfirmSaveModal } = useConfirm({
content: t('app.Confirm Save App Tip'),
bg: appDetail.type === AppTypeEnum.basic ? '' : 'red.600'
bg: appDetail.type === AppTypeEnum.simple ? '' : 'red.600'
});
const chatModelSelectList = useMemo(() => {
@@ -135,13 +118,21 @@ function ConfigForm({
[allDatasets, datasets]
);
const selectSimpleTemplate = useMemo(
() =>
simpleModeTemplates?.find((item) => item.id === getValues('templateId')) ||
SimpleModeTemplate_FastGPT_Universal,
[getValues, refresh]
);
const { mutate: onSubmitSave, isLoading: isSaving } = useRequest({
mutationFn: async (data: EditFormType) => {
const modules = appForm2Modules(data);
mutationFn: async (data: AppSimpleEditFormType) => {
const modules = await postForm2Modules(data, data.templateId);
await updateAppDetail(appDetail._id, {
modules,
type: AppTypeEnum.basic,
type: AppTypeEnum.simple,
simpleTemplateId: data.templateId,
permission: undefined
});
},
@@ -150,18 +141,20 @@ function ConfigForm({
});
const appModule2Form = useCallback(() => {
const formVal = appModules2Form(appDetail.modules);
const formVal = appModules2Form({
templateId: appDetail.simpleTemplateId,
modules: appDetail.modules
});
reset(formVal);
setTimeout(() => {
setRefresh((state) => !state);
}, 100);
}, [appDetail.modules, reset]);
useQuery(['loadAllDatasets'], loadAllDatasets);
}, [appDetail.modules, appDetail.simpleTemplateId, reset]);
useEffect(() => {
appModule2Form();
}, [appModule2Form]);
useQuery(['loadAllDatasets'], loadAllDatasets);
const BoxStyles: BoxProps = {
bg: 'myWhite.200',
@@ -213,9 +206,9 @@ function ConfigForm({
isLoading={isSaving}
fontSize={'sm'}
size={['sm', 'md']}
variant={appDetail.type === AppTypeEnum.basic ? 'primary' : 'base'}
variant={appDetail.type === AppTypeEnum.simple ? 'primary' : 'base'}
onClick={() => {
if (appDetail.type !== AppTypeEnum.basic) {
if (appDetail.type !== AppTypeEnum.simple) {
openConfirmSave(handleSubmit((data) => onSubmitSave(data)))();
} else {
handleSubmit((data) => onSubmitSave(data))();
@@ -227,255 +220,234 @@ function ConfigForm({
</Flex>
<Box px={4}>
{/* welcome */}
<Box {...BoxStyles}>
<Flex alignItems={'center'}>
<Avatar src={'/imgs/module/userGuide.png'} w={'18px'} />
<Box mx={2}></Box>
<MyTooltip label={welcomeTextTip} forceShow>
<QuestionOutlineIcon />
</MyTooltip>
{/* simple mode select */}
<Flex {...BoxStyles}>
<Flex alignItems={'center'} flex={'1 0 0'}>
<Image alt={''} src={'/imgs/module/templates.png'} w={'18px'} />
<Box mx={2}>{t('core.app.simple.mode template select')}</Box>
</Flex>
<Textarea
mt={2}
rows={5}
placeholder={welcomeTextTip}
borderColor={'myGray.100'}
{...register('guide.welcome.text')}
<MySelect
w={['200px', '250px']}
list={
simpleModeTemplates?.map((item) => ({
alias: item.name,
label: item.desc,
value: item.id
})) || []
}
value={getValues('templateId')}
onchange={(val) => {
setValue('templateId', val);
setRefresh(!refresh);
}}
/>
</Box>
</Flex>
{/* welcome */}
{selectSimpleTemplate?.systemForm?.userGuide?.welcomeText && (
<Box {...BoxStyles} mt={2}>
<Flex alignItems={'center'}>
<Image alt={''} src={'/imgs/module/userGuide.png'} w={'18px'} />
<Box mx={2}></Box>
<MyTooltip label={welcomeTextTip} forceShow>
<QuestionOutlineIcon />
</MyTooltip>
</Flex>
<Textarea
mt={2}
rows={5}
placeholder={welcomeTextTip}
borderColor={'myGray.100'}
{...register('userGuide.welcomeText')}
/>
</Box>
)}
{/* variable */}
<Box mt={2} {...BoxStyles}>
<Flex alignItems={'center'}>
<Avatar src={'/imgs/module/variable.png'} objectFit={'contain'} w={'18px'} />
<Box ml={2} flex={1}>
</Box>
<Flex {...BoxBtnStyles} onClick={() => setEditVariable(addVariable())}>
+&ensp;
</Flex>
</Flex>
{variables.length > 0 && (
<Box
mt={2}
borderRadius={'lg'}
overflow={'hidden'}
borderWidth={'1px'}
borderBottom="none"
>
<TableContainer>
<Table bg={'white'}>
<Thead>
<Tr>
<Th></Th>
<Th> key</Th>
<Th></Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{variables.map((item, index) => (
<Tr key={item.id}>
<Td>{item.label} </Td>
<Td>{item.key}</Td>
<Td>{item.required ? '✔' : ''}</Td>
<Td>
<MyIcon
mr={3}
name={'settingLight'}
w={'16px'}
cursor={'pointer'}
onClick={() => setEditVariable(item)}
/>
<MyIcon
name={'delete'}
w={'16px'}
cursor={'pointer'}
onClick={() => removeVariable(index)}
/>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Box>
)}
</Box>
{selectSimpleTemplate?.systemForm?.userGuide?.variables && (
<Box mt={2} {...BoxStyles}>
<VariableEdit
variables={getValues('userGuide.variables')}
onChange={(e) => {
setValue('userGuide.variables', e);
setRefresh(!refresh);
}}
/>
</Box>
)}
{/* ai */}
<Box mt={5} {...BoxStyles}>
<Flex alignItems={'center'}>
<Avatar src={'/imgs/module/AI.png'} w={'18px'} />
<Box ml={2} flex={1}>
{t('app.AI Settings')}
</Box>
<Flex {...BoxBtnStyles} onClick={onOpenAIChatSetting}>
<MyIcon mr={1} name={'settingLight'} w={'14px'} />
{t('app.Open AI Advanced Settings')}
{selectSimpleTemplate?.systemForm?.aiSettings && (
<Box mt={5} {...BoxStyles}>
<Flex alignItems={'center'}>
<Image alt={''} src={'/imgs/module/AI.png'} w={'18px'} />
<Box ml={2} flex={1}>
{t('app.AI Settings')}
</Box>
{(selectSimpleTemplate.systemForm.aiSettings.maxToken ||
selectSimpleTemplate.systemForm.aiSettings.temperature ||
selectSimpleTemplate.systemForm.aiSettings.quoteTemplate ||
selectSimpleTemplate.systemForm.aiSettings.quotePrompt) && (
<Flex {...BoxBtnStyles} onClick={onOpenAIChatSetting}>
<MyIcon mr={1} name={'settingLight'} w={'14px'} />
{t('app.Open AI Advanced Settings')}
</Flex>
)}
</Flex>
</Flex>
{selectSimpleTemplate.systemForm.aiSettings?.model && (
<Flex alignItems={'center'} mt={5}>
<Box {...LabelStyles}>{t('core.ai.Model')}</Box>
<Box flex={'1 0 0'}>
<MySelect
width={'100%'}
value={getValues(`aiSettings.model`)}
list={chatModelSelectList}
onchange={(val: any) => {
setValue('aiSettings.model', val);
const maxToken =
chatModelList.find((item) => item.model === getValues('aiSettings.model'))
?.maxResponse || 4000;
const token = maxToken / 2;
setValue('aiSettings.maxToken', token);
setRefresh(!refresh);
}}
/>
</Box>
</Flex>
)}
<Flex alignItems={'center'} mt={5}>
<Box {...LabelStyles}>{t('core.ai.Model')}</Box>
<Box flex={'1 0 0'}>
<MySelect
width={'100%'}
value={getValues('chatModel.model')}
list={chatModelSelectList}
onchange={(val: any) => {
setValue('chatModel.model', val);
const maxToken =
chatModelList.find((item) => item.model === getValues('chatModel.model'))
?.maxResponse || 4000;
const token = maxToken / 2;
setValue('chatModel.maxToken', token);
setRefresh(!refresh);
}}
/>
</Box>
</Flex>
<Flex mt={10} alignItems={'flex-start'}>
<Box {...LabelStyles}>
{t('core.ai.Prompt')}
<MyTooltip label={ChatModelSystemTip} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Box>
<Textarea
rows={5}
minH={'60px'}
placeholder={ChatModelSystemTip}
borderColor={'myGray.100'}
{...register('chatModel.systemPrompt')}
></Textarea>
</Flex>
</Box>
{selectSimpleTemplate.systemForm.aiSettings?.systemPrompt && (
<Flex mt={10} alignItems={'flex-start'}>
<Box {...LabelStyles}>
{t('core.ai.Prompt')}
<MyTooltip label={chatNodeSystemPromptTip} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Box>
<Textarea
rows={5}
minH={'60px'}
placeholder={chatNodeSystemPromptTip}
borderColor={'myGray.100'}
{...register('aiSettings.systemPrompt')}
></Textarea>
</Flex>
)}
</Box>
)}
{/* dataset */}
<Box mt={5} {...BoxStyles}>
<Flex alignItems={'center'}>
<Flex alignItems={'center'} flex={1}>
<Avatar src={'/imgs/module/db.png'} w={'18px'} />
<Box ml={2}>{t('core.dataset.Choose Dataset')}</Box>
</Flex>
<Flex alignItems={'center'} mr={3} {...BoxBtnStyles} onClick={onOpenKbSelect}>
<SmallAddIcon />
{t('common.Choose')}
</Flex>
<Flex alignItems={'center'} {...BoxBtnStyles} onClick={onOpenKbParams}>
<MyIcon name={'edit'} w={'14px'} mr={1} />
{t('common.Params')}
</Flex>
</Flex>
<Flex mt={1} color={'myGray.600'} fontSize={['sm', 'md']}>
{t('core.dataset.Similarity')}: {getValues('dataset.searchSimilarity')},{' '}
{t('core.dataset.Search Top K')}: {getValues('dataset.searchLimit')}
{getValues('dataset.searchEmptyText') === ''
? ''
: t('core.dataset.Set Empty Result Tip')}
</Flex>
<Grid
gridTemplateColumns={['repeat(2, minmax(0, 1fr))', 'repeat(3, minmax(0, 1fr))']}
my={2}
gridGap={[2, 4]}
>
{selectDatasets.map((item) => (
<MyTooltip key={item._id} label={t('core.dataset.Read Dataset')}>
<Flex
overflow={'hidden'}
alignItems={'center'}
p={2}
bg={'white'}
boxShadow={'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'}
borderRadius={'md'}
border={theme.borders.base}
cursor={'pointer'}
onClick={() =>
router.push({
pathname: '/dataset/detail',
query: {
datasetId: item._id
}
})
}
>
<Avatar src={item.avatar} w={'18px'} mr={1} />
<Box flex={'1 0 0'} w={0} className={'textEllipsis'} fontSize={'sm'}>
{item.name}
</Box>
{selectSimpleTemplate?.systemForm?.dataset && (
<Box mt={5} {...BoxStyles}>
<Flex alignItems={'center'}>
<Flex alignItems={'center'} flex={1}>
<Image alt={''} src={'/imgs/module/db.png'} w={'18px'} />
<Box ml={2}>{t('core.dataset.Choose Dataset')}</Box>
</Flex>
{selectSimpleTemplate.systemForm.dataset.datasets && (
<Flex alignItems={'center'} {...BoxBtnStyles} onClick={onOpenKbSelect}>
<SmallAddIcon />
{t('common.Choose')}
</Flex>
</MyTooltip>
))}
</Grid>
</Box>
)}
{(selectSimpleTemplate.systemForm.dataset.limit ||
selectSimpleTemplate.systemForm.dataset.rerank ||
selectSimpleTemplate.systemForm.dataset.searchEmptyText ||
selectSimpleTemplate.systemForm.dataset.similarity) && (
<Flex alignItems={'center'} ml={3} {...BoxBtnStyles} onClick={onOpenKbParams}>
<MyIcon name={'edit'} w={'14px'} mr={1} />
{t('common.Params')}
</Flex>
)}
</Flex>
<Flex mt={1} color={'myGray.600'} fontSize={['sm', 'md']}>
{t('core.dataset.Similarity')}: {getValues('dataset.similarity')},{' '}
{t('core.dataset.Search Top K')}: {getValues('dataset.limit')}
{getValues('dataset.searchEmptyText') === ''
? ''
: t('core.dataset.Set Empty Result Tip')}
</Flex>
<Grid
gridTemplateColumns={['repeat(2, minmax(0, 1fr))', 'repeat(3, minmax(0, 1fr))']}
my={2}
gridGap={[2, 4]}
>
{selectDatasets.map((item) => (
<MyTooltip key={item._id} label={t('core.dataset.Read Dataset')}>
<Flex
overflow={'hidden'}
alignItems={'center'}
p={2}
bg={'white'}
boxShadow={'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'}
borderRadius={'md'}
border={theme.borders.base}
cursor={'pointer'}
onClick={() =>
router.push({
pathname: '/dataset/detail',
query: {
datasetId: item._id
}
})
}
>
<Image alt={''} src={item.avatar} w={'18px'} mr={1} />
<Box flex={'1 0 0'} w={0} className={'textEllipsis'} fontSize={'sm'}>
{item.name}
</Box>
</Flex>
</MyTooltip>
))}
</Grid>
</Box>
)}
{/* tts */}
<Box mt={5} {...BoxStyles}>
<TTSSelect
value={getValues('tts')}
onChange={(e) => {
setValue('tts', e);
setRefresh((state) => !state);
}}
/>
</Box>
{selectSimpleTemplate?.systemForm?.userGuide?.tts && (
<Box mt={5} {...BoxStyles}>
<TTSSelect
value={getValues('userGuide.tts')}
onChange={(e) => {
setValue('userGuide.tts', e);
setRefresh((state) => !state);
}}
/>
</Box>
)}
{/* whisper */}
<Box mt={5} {...BoxStyles}>
<QGSwitch
isChecked={getValues('questionGuide')}
size={'lg'}
onChange={(e) => {
const value = e.target.checked;
setValue('questionGuide', value);
setRefresh((state) => !state);
}}
/>
</Box>
{/* question guide */}
{selectSimpleTemplate?.systemForm?.userGuide?.questionGuide && (
<Box mt={5} {...BoxStyles}>
<QGSwitch
isChecked={getValues('userGuide.questionGuide')}
size={'lg'}
onChange={(e) => {
const value = e.target.checked;
setValue('userGuide.questionGuide', value);
setRefresh((state) => !state);
}}
/>
</Box>
)}
</Box>
<ConfirmSaveModal />
{editVariable && (
<VariableEditModal
defaultVariable={editVariable}
onClose={() => setEditVariable(undefined)}
onSubmit={({ variable }) => {
const record = variables.find((item) => item.id === variable.id);
if (record) {
replaceVariables(
variables.map((item) => (item.id === variable.id ? variable : item))
);
} else {
// auth same key
if (variables.find((item) => item.key === variable.key)) {
return toast({
status: 'warning',
title: t('app.Variable Key Repeat Tip')
});
}
appendVariable(variable);
}
setEditVariable(undefined);
}}
/>
)}
{isOpenAIChatSetting && (
<AIChatSettingsModal
onClose={onCloseAIChatSetting}
onSuccess={(e) => {
setValue('chatModel', e);
setValue('aiSettings', e);
onCloseAIChatSetting();
}}
defaultData={getValues('chatModel')}
defaultData={getValues('aiSettings')}
simpleModeTemplate={selectSimpleTemplate}
/>
)}
{isOpenDatasetSelect && (
<DatasetSelectModal
isOpen={isOpenDatasetSelect}
activeDatasets={selectDatasets.map((item) => ({
defaultSelectedDatasets={selectDatasets.map((item) => ({
datasetId: item._id,
vectorModel: item.vectorModel
}))}
@@ -689,9 +661,8 @@ function ChatTest({ appId }: { appId: string }) {
}, []);
useEffect(() => {
const formVal = appModules2Form(appDetail.modules);
setModules(appForm2Modules(formVal));
resetChatBox();
setModules(appDetail.modules);
}, [appDetail, resetChatBox]);
return (
@@ -727,7 +698,7 @@ function ChatTest({ appId }: { appId: string }) {
onDelMessage={() => {}}
/>
</Box>
{appDetail.type !== AppTypeEnum.basic && (
{appDetail.type !== AppTypeEnum.simple && (
<Flex
position={'absolute'}
top={0}
@@ -750,7 +721,7 @@ function ChatTest({ appId }: { appId: string }) {
);
}
const BasicEdit = ({ appId }: { appId: string }) => {
const SimpleEdit = ({ appId }: { appId: string }) => {
const { isPc } = useSystemStore();
return (
<Grid gridTemplateColumns={['1fr', '550px 1fr']} h={'100%'}>
@@ -760,4 +731,4 @@ const BasicEdit = ({ appId }: { appId: string }) => {
);
};
export default BasicEdit;
export default SimpleEdit;

View File

@@ -1,167 +0,0 @@
import MyIcon from '@/components/Icon';
import MyTooltip from '@/components/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { Box, Button, Flex, ModalBody, useDisclosure, Image } from '@chakra-ui/react';
import React, { useCallback, useMemo } from 'react';
import { useTranslation } from 'next-i18next';
import MySelect from '@/components/Select';
import { TTSTypeEnum } from '@/constants/app';
import { AppTTSConfigType } from '@/types/app';
import { useAudioPlay } from '@/web/common/utils/voice';
import { audioSpeechModels } from '@/web/common/system/staticData';
import MyModal from '@/components/MyModal';
import MySlider from '@/components/Slider';
const TTSSelect = ({
value,
onChange
}: {
value: AppTTSConfigType;
onChange: (e: AppTTSConfigType) => void;
}) => {
const { t } = useTranslation();
const { isOpen, onOpen, onClose } = useDisclosure();
const list = useMemo(
() => [
{ label: t('core.app.tts.Close'), value: TTSTypeEnum.none },
{ label: t('core.app.tts.Web'), value: TTSTypeEnum.web },
...audioSpeechModels.map((item) => item?.voices || []).flat()
],
[t]
);
const formatValue = useMemo(() => {
if (!value || !value.type) {
return TTSTypeEnum.none;
}
if (value.type === TTSTypeEnum.none || value.type === TTSTypeEnum.web) {
return value.type;
}
return value.voice;
}, [value]);
const formLabel = useMemo(
() => list.find((item) => item.value === formatValue)?.label || t('common.UnKnow'),
[formatValue, list, t]
);
const { playAudio, cancelAudio, audioLoading, audioPlaying } = useAudioPlay({ ttsConfig: value });
const onclickChange = useCallback(
(e: string) => {
if (e === TTSTypeEnum.none || e === TTSTypeEnum.web) {
onChange({ type: e as `${TTSTypeEnum}` });
} else {
const audioModel = audioSpeechModels.find(
(item) => item.voices?.find((voice) => voice.value === e)
);
if (!audioModel) {
return;
}
onChange({
...value,
type: TTSTypeEnum.model,
model: audioModel.model,
voice: e
});
}
},
[onChange, value]
);
return (
<Flex alignItems={'center'}>
<MyIcon name={'core/app/tts'} mr={2} w={'16px'} />
<Box>{t('core.app.TTS')}</Box>
<MyTooltip label={t('core.app.TTS Tip')} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
<Box flex={1} />
<MyTooltip label={t('core.app.Select TTS')}>
<Box
cursor={'pointer'}
_hover={{ bg: 'myGray.100' }}
py={2}
px={3}
borderRadius={'md'}
onClick={onOpen}
color={'myGray.600'}
>
{formLabel}
</Box>
</MyTooltip>
<MyModal
title={
<>
<MyIcon name={'core/app/tts'} mr={2} w={'20px'} />
{t('core.app.TTS')}
</>
}
isOpen={isOpen}
onClose={onClose}
w={'500px'}
>
<ModalBody px={[5, 16]} py={[4, 8]}>
<Flex justifyContent={'space-between'} alignItems={'center'}>
{t('core.app.tts.Speech model')}
<MySelect w={'220px'} value={formatValue} list={list} onchange={onclickChange} />
</Flex>
<Flex mt={8} justifyContent={'space-between'} alignItems={'center'}>
{t('core.app.tts.Speech speed')}
<MySlider
markList={[
{ label: '0.3', value: 0.3 },
{ label: '2', value: 2 }
]}
width={'220px'}
min={0.3}
max={2}
step={0.1}
value={value.speed || 1}
onChange={(e) => {
onChange({
...value,
speed: e
});
}}
/>
</Flex>
{formatValue !== TTSTypeEnum.none && (
<Flex mt={10} justifyContent={'end'}>
{audioPlaying ? (
<Flex>
<Image src="/icon/speaking.gif" w={'24px'} alt={''} />
<Button
ml={2}
variant={'gray'}
isLoading={audioLoading}
leftIcon={<MyIcon name={'core/chat/stopSpeech'} w={'16px'} />}
onClick={() => {
cancelAudio();
}}
>
{t('core.chat.tts.Stop Speech')}
</Button>
</Flex>
) : (
<Button
isLoading={audioLoading}
leftIcon={<MyIcon name={'core/app/headphones'} w={'16px'} />}
onClick={() => {
playAudio({
text: t('core.app.tts.Test Listen Text')
});
}}
>
{t('core.app.tts.Test Listen')}
</Button>
)}
</Flex>
)}
</ModalBody>
</MyModal>
</Flex>
);
};
export default TTSSelect;

View File

@@ -12,7 +12,7 @@ import Avatar from '@/components/Avatar';
import MyIcon from '@/components/Icon';
import PageContainer from '@/components/PageContainer';
import Loading from '@/components/Loading';
import BasicEdit from './components/BasicEdit';
import SimpleEdit from './components/SimpleEdit';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { useAppStore } from '@/web/core/app/store/useAppStore';
@@ -23,7 +23,7 @@ const OutLink = dynamic(() => import('./components/OutLink'), {});
const Logs = dynamic(() => import('./components/Logs'), {});
enum TabEnum {
'basicEdit' = 'basicEdit',
'simpleEdit' = 'simpleEdit',
'adEdit' = 'adEdit',
'outLink' = 'outLink',
'logs' = 'logs',
@@ -51,7 +51,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
const tabList = useMemo(
() => [
{ label: '简易配置', id: TabEnum.basicEdit, icon: 'overviewLight' },
{ label: '简易配置', id: TabEnum.simpleEdit, icon: 'overviewLight' },
...(feConfigs?.hide_app_flow
? []
: [{ label: '高级编排', id: TabEnum.adEdit, icon: 'settingLight' }]),
@@ -167,9 +167,9 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
/>
</Box>
<Box flex={'1 0 0'} h={[0, '100%']} overflow={['overlay', '']}>
{currentTab === TabEnum.basicEdit && <BasicEdit appId={appId} />}
{currentTab === TabEnum.simpleEdit && <SimpleEdit appId={appId} />}
{currentTab === TabEnum.adEdit && appDetail && (
<AdEdit app={appDetail} onClose={() => setCurrentTab(TabEnum.basicEdit)} />
<AdEdit app={appDetail} onClose={() => setCurrentTab(TabEnum.simpleEdit)} />
)}
{currentTab === TabEnum.logs && <Logs appId={appId} />}
{currentTab === TabEnum.outLink && <OutLink appId={appId} />}
@@ -180,7 +180,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
};
export async function getServerSideProps(context: any) {
const currentTab = context?.query?.currentTab || TabEnum.basicEdit;
const currentTab = context?.query?.currentTab || TabEnum.simpleEdit;
return {
props: { currentTab, ...(await serviceSideProps(context)) }

View File

@@ -18,13 +18,14 @@ import { getErrText } from '@fastgpt/global/common/error/utils';
import { useToast } from '@/web/common/hooks/useToast';
import { postCreateApp } from '@/web/core/app/api';
import { useRouter } from 'next/router';
import { appTemplates } from '@/constants/flow/ModuleTemplate';
import { appTemplates } from '@/web/core/app/templates';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useRequest } from '@/web/common/hooks/useRequest';
import { feConfigs } from '@/web/common/system/staticData';
import Avatar from '@/components/Avatar';
import MyTooltip from '@/components/MyTooltip';
import MyModal from '@/components/MyModal';
import { useTranslation } from 'next-i18next';
type FormType = {
avatar: string;
@@ -33,6 +34,7 @@ type FormType = {
};
const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: () => void }) => {
const { t } = useTranslation();
const [refresh, setRefresh] = useState(false);
const { toast } = useToast();
const router = useRouter();
@@ -96,8 +98,13 @@ const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: (
});
return (
<MyModal isOpen onClose={onClose} isCentered={!isPc}>
<ModalHeader fontSize={'2xl'}> AI </ModalHeader>
<MyModal
iconSrc="/imgs/module/ai.svg"
title={t('core.app.create app')}
isOpen
onClose={onClose}
isCentered={!isPc}
>
<ModalBody>
<Box color={'myGray.800'} fontWeight={'bold'}>

View File

@@ -189,6 +189,14 @@ const MyApps = () => {
</MyTooltip>
))}
</Grid>
{myApps.length === 0 && (
<Flex mt={'35vh'} flexDirection={'column'} alignItems={'center'}>
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
<Box mt={2} color={'myGray.500'}>
</Box>
</Flex>
)}
<ConfirmModal />
{isOpenCreateModal && (
<CreateModal onClose={onCloseCreateModal} onSuccess={() => loadMyApps(true)} />

View File

@@ -254,7 +254,7 @@ const ChatHistorySlider = ({
}}
>
<MyIcon mr={2} name={'customTitle'} w={'16px'}></MyIcon>
{t('common.Custom Title')}
</MenuItem>
)}
<MenuItem

View File

@@ -42,7 +42,7 @@ const EditFolderModal = ({
});
return (
<MyModal isOpen onClose={onClose} title={typeMap.title}>
<MyModal isOpen onClose={onClose} iconSrc="/imgs/modal/folder.svg" title={typeMap.title}>
<ModalBody>
<Input
ref={inputRef}

View File

@@ -28,7 +28,13 @@ const CreateFileModal = ({
});
return (
<MyModal title={t('file.Create File')} isOpen w={'600px'} top={'15vh'}>
<MyModal
title={t('file.Create File')}
iconSrc="/imgs/modal/txt.svg"
isOpen
w={'600px'}
top={'15vh'}
>
<ModalBody>
<Box mb={1} fontSize={'sm'}>

View File

@@ -71,6 +71,7 @@ const ImportData = ({
return (
<MyModal
iconSrc="/imgs/modal/import.svg"
title={<Box {...TitleStyle}>{t('dataset.data.File import')}</Box>}
isOpen
isCentered
@@ -79,7 +80,7 @@ const ImportData = ({
h={'90vh'}
>
<ModalCloseButton onClick={onClose} />
<Flex flexDirection={'column'} flex={'1 0 0'}>
<Flex mt={2} flexDirection={'column'} flex={'1 0 0'}>
<Box pb={[5, 7]} px={[4, 8]} borderBottom={theme.borders.base}>
<MyRadio
gridTemplateColumns={['repeat(1,1fr)', 'repeat(3,1fr)']}

View File

@@ -30,13 +30,14 @@ const UrlFetchModal = ({
return (
<MyModal
iconSrc="/imgs/modal/network.svg"
title={
<>
<Box>
<Box>{t('file.Fetch Url')}</Box>
<Box fontWeight={'normal'} fontSize={'sm'} color={'myGray.500'} mt={1}>
</Box>
</>
</Box>
}
top={'15vh'}
isOpen

View File

@@ -1,5 +1,14 @@
import React, { useCallback, useState, useRef } from 'react';
import { Box, Flex, Button, ModalHeader, ModalFooter, ModalBody, Input } from '@chakra-ui/react';
import {
Box,
Flex,
Button,
ModalHeader,
ModalFooter,
ModalBody,
Input,
Image
} from '@chakra-ui/react';
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
import { useForm } from 'react-hook-form';
import { compressImgAndUpload } from '@/web/common/file/controller';
@@ -79,8 +88,14 @@ const CreateModal = ({ onClose, parentId }: { onClose: () => void; parentId?: st
});
return (
<MyModal isOpen onClose={onClose} isCentered={!isPc} w={'450px'}>
<ModalHeader fontSize={'2xl'}></ModalHeader>
<MyModal
iconSrc="/imgs/module/db.png"
title={t('core.dataset.Create dataset')}
isOpen
onClose={onClose}
isCentered={!isPc}
w={'450px'}
>
<ModalBody>
<Box color={'myGray.800'} fontWeight={'bold'}>

View File

@@ -59,9 +59,13 @@ const MoveModal = ({
});
return (
<MyModal isOpen={true} maxW={['90vw', '800px']} w={'800px'} onClose={onClose}>
<Flex flexDirection={'column'} h={['90vh', 'auto']}>
<ModalHeader>
<MyModal
isOpen={true}
maxW={['90vw', '800px']}
w={'800px'}
iconSrc="/imgs/modal/move.svg"
title={
<>
{!!parentId ? (
<Flex flex={1} userSelect={'none'} fontSize={['sm', 'lg']} fontWeight={'normal'}>
{paths.map((item, i) => (
@@ -93,8 +97,11 @@ const MoveModal = ({
) : (
<Box>我的知识库</Box>
)}
</ModalHeader>
</>
}
onClose={onClose}
>
<Flex flexDirection={'column'} h={['90vh', 'auto']}>
<ModalBody
flex={['1 0 0', '0 0 auto']}
maxH={'80vh'}

View File

@@ -7,7 +7,7 @@ import { useSendCode } from '@/web/support/user/hooks/useSendCode';
import type { ResLogin } from '@/global/support/api/userRes';
import { useToast } from '@/web/common/hooks/useToast';
import { postCreateApp } from '@/web/core/app/api';
import { appTemplates } from '@/constants/flow/ModuleTemplate';
import { appTemplates } from '@/web/core/app/templates';
import { feConfigs } from '@/web/common/system/staticData';
interface Props {

View File

@@ -0,0 +1,95 @@
import React, { useCallback, useEffect } from 'react';
import { useRouter } from 'next/router';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import type { ResLogin } from '@/global/support/api/userRes.d';
import { useChatStore } from '@/web/core/chat/storeChat';
import { useUserStore } from '@/web/support/user/useUserStore';
import { clearToken, setToken } from '@/web/support/user/auth';
import { postFastLogin } from '@/web/support/user/api';
import { useToast } from '@/web/common/hooks/useToast';
import Loading from '@/components/Loading';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { useQuery } from '@tanstack/react-query';
import { getErrText } from '@fastgpt/global/common/error/utils';
const FastLogin = ({
code,
token,
callbackUrl
}: {
code: string;
token: string;
callbackUrl: string;
}) => {
const { setLastChatId, setLastChatAppId } = useChatStore();
const { setUserInfo } = useUserStore();
const router = useRouter();
const { toast } = useToast();
const loginSuccess = useCallback(
(res: ResLogin) => {
setToken(res.token);
setUserInfo(res.user);
// init store
setLastChatId('');
setLastChatAppId('');
setTimeout(() => {
router.push(decodeURIComponent(callbackUrl));
}, 100);
},
[setLastChatId, setLastChatAppId, setUserInfo, router, callbackUrl]
);
const authCode = useCallback(
async (code: string, token: string) => {
try {
const res = await postFastLogin({
code,
token
});
if (!res) {
toast({
status: 'warning',
title: '登录异常'
});
return setTimeout(() => {
router.replace('/login');
}, 1000);
}
loginSuccess(res);
} catch (error) {
toast({
status: 'warning',
title: getErrText(error, '登录异常')
});
setTimeout(() => {
router.replace('/login');
}, 1000);
}
},
[loginSuccess, router, toast]
);
useEffect(() => {
clearToken();
router.prefetch(callbackUrl);
authCode(code, token);
}, []);
return <Loading />;
};
export async function getServerSideProps(content: any) {
return {
props: {
code: content?.query?.code || '',
token: content?.query?.token || '',
callbackUrl: content?.query?.callbackUrl || '/app/list',
...(await serviceSideProps(content))
}
};
}
export default FastLogin;

View File

@@ -9,10 +9,11 @@ import { useChatStore } from '@/web/core/chat/storeChat';
import LoginForm from './components/LoginForm';
import dynamic from 'next/dynamic';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { setToken } from '@/web/support/user/auth';
import { clearToken, setToken } from '@/web/support/user/auth';
import { feConfigs } from '@/web/common/system/staticData';
import CommunityModal from '@/components/CommunityModal';
import Script from 'next/script';
import { loginOut } from '@/web/support/user/api';
const RegisterForm = dynamic(() => import('./components/RegisterForm'));
const ForgetPasswordForm = dynamic(() => import('./components/ForgetPasswordForm'));
@@ -53,6 +54,7 @@ const Login = () => {
}
useEffect(() => {
clearToken();
router.prefetch('/app/list');
}, []);

View File

@@ -4,7 +4,7 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
import type { ResLogin } from '@/global/support/api/userRes.d';
import { useChatStore } from '@/web/core/chat/storeChat';
import { useUserStore } from '@/web/support/user/useUserStore';
import { setToken } from '@/web/support/user/auth';
import { clearToken, setToken } from '@/web/support/user/auth';
import { oauthLogin } from '@/web/support/user/api';
import { useToast } from '@/web/common/hooks/useToast';
import Loading from '@/components/Loading';
@@ -21,12 +21,13 @@ const provider = ({ code, state }: { code: string; state: string }) => {
const loginSuccess = useCallback(
(res: ResLogin) => {
setToken(res.token);
setUserInfo(res.user);
// init store
setLastChatId('');
setLastChatAppId('');
setUserInfo(res.user);
setToken(res.token);
setTimeout(() => {
router.push(
loginStore?.lastRoute ? decodeURIComponent(loginStore?.lastRoute) : '/app/list'
@@ -72,8 +73,11 @@ const provider = ({ code, state }: { code: string; state: string }) => {
[loginStore, loginSuccess, router, toast]
);
useQuery(['init', code], () => {
useEffect(() => {
clearToken();
router.prefetch('/app/list');
if (!code) return;
if (state !== loginStore?.state) {
toast({
status: 'warning',
@@ -85,11 +89,6 @@ const provider = ({ code, state }: { code: string; state: string }) => {
return;
}
authCode(code);
return null;
});
useEffect(() => {
router.prefetch('/app/list');
}, []);
return <Loading />;

View File

@@ -1,4 +1,4 @@
import React, { useCallback } from 'react';
import React from 'react';
import { Box, Flex, IconButton, useTheme, useDisclosure } from '@chakra-ui/react';
import { PluginItemSchema } from '@fastgpt/global/core/plugin/type';
import { useRequest } from '@/web/common/hooks/useRequest';
@@ -42,12 +42,21 @@ const Header = ({ plugin, onClose }: Props) => {
return Promise.reject(t('module.Plugin input must connect'));
}
}
if (item.inputs.find((input) => input.required && !input.connected)) {
return Promise.reject(`${item.name}】存在未连接的必填输入`);
if (
item.flowType === FlowNodeTypeEnum.pluginOutput &&
item.inputs.find((input) => !input.connected)
) {
return Promise.reject(t('core.module.Plugin output must connect'));
}
if (item.inputs.find((input) => input.valueCheck && !input.valueCheck(input.value))) {
return Promise.reject(`${item.name}】存在为填写的必填项`);
if (
item.inputs.find((input) => {
if (!input.required || input.connected) return false;
if (!input.value || input.value === '' || input.value?.length === 0) return true;
return false;
})
) {
return Promise.reject(`${item.name}】存在未填或未连接参数`);
}
}

View File

@@ -3,7 +3,7 @@ import ReactFlow, { Background, ReactFlowProvider, useNodesState } from 'reactfl
import { FlowModuleItemType, ModuleItemType } from '@fastgpt/global/core/module/type';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import dynamic from 'next/dynamic';
import { formatPluginIOModules } from '@fastgpt/global/core/module/utils';
import { formatPluginToPreviewModule } from '@fastgpt/global/core/module/utils';
import MyModal from '@/components/MyModal';
import { Box } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
@@ -12,7 +12,7 @@ import { appModule2FlowNode } from '@/utils/adapt';
const nodeTypes = {
[FlowNodeTypeEnum.pluginModule]: dynamic(
() => import('@/components/core/module/Flow/components/nodes/NodePreviewPlugin')
() => import('@/components/core/module/Flow/components/nodes/NodeSimple')
)
};
@@ -36,16 +36,21 @@ const PreviewPlugin = ({
flowType: FlowNodeTypeEnum.pluginModule,
logo: plugin.avatar,
name: plugin.name,
description: plugin.intro,
intro: plugin.intro,
...formatPluginIOModules(plugin._id, modules)
...formatPluginToPreviewModule(plugin._id, modules)
}
})
]);
}, [modules, plugin, setNodes]);
return (
<MyModal isOpen title={t('module.Preview Plugin')} onClose={onClose} isCentered>
<MyModal
isOpen
title={t('module.Preview Plugin')}
iconSrc="/imgs/modal/preview.svg"
onClose={onClose}
isCentered
>
<Box h={'400px'} w={'400px'}>
<ReactFlowProvider>
<ReactFlow

View File

@@ -3,12 +3,12 @@ import { useRouter } from 'next/router';
import Header from './Header';
import Flow from '@/components/core/module/Flow';
import FlowProvider, { useFlowProviderStore } from '@/components/core/module/Flow/FlowProvider';
import { SystemModuleTemplateType } from '@fastgpt/global/core/module/type.d';
import { PluginModuleTemplates } from '@/constants/flow/ModuleTemplate';
import { FlowModuleTemplateType } from '@fastgpt/global/core/module/type.d';
import { pluginSystemModuleTemplates } from '@/web/core/modules/template/system';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { useQuery } from '@tanstack/react-query';
import { getOnePlugin, getUserPlugs2ModuleTemplates } from '@/web/core/plugin/api';
import { getOnePlugin } from '@/web/core/plugin/api';
import { useToast } from '@/web/common/hooks/useToast';
import Loading from '@/components/Loading';
import { getErrText } from '@fastgpt/global/common/error/utils';
@@ -22,11 +22,11 @@ const Render = ({ pluginId }: Props) => {
const router = useRouter();
const { toast } = useToast();
const { nodes = [] } = useFlowProviderStore();
const { pluginModuleTemplates, loadPluginModuleTemplates } = usePluginStore();
const { pluginModuleTemplates, loadPluginTemplates } = usePluginStore();
const filterTemplates = useMemo(() => {
const copyTemplates: SystemModuleTemplateType = JSON.parse(
JSON.stringify(PluginModuleTemplates)
const copyTemplates: FlowModuleTemplateType[] = JSON.parse(
JSON.stringify(pluginSystemModuleTemplates)
);
const filterType: Record<string, 1> = {
[FlowNodeTypeEnum.userGuide]: 1,
@@ -37,12 +37,10 @@ const Render = ({ pluginId }: Props) => {
// filter some template
nodes.forEach((node) => {
if (node.type && filterType[node.type]) {
copyTemplates.forEach((item) => {
item.list.forEach((module, index) => {
if (module.flowType === node.type) {
item.list.splice(index, 1);
}
});
copyTemplates.forEach((module, index) => {
if (module.flowType === node.type) {
copyTemplates.splice(index, 1);
}
});
}
});
@@ -60,16 +58,15 @@ const Render = ({ pluginId }: Props) => {
}
});
useQuery(['getUserPlugs2ModuleTemplates'], () => loadPluginModuleTemplates());
const filterPlugins = useMemo(
() => pluginModuleTemplates.filter((item) => item.id !== pluginId),
[pluginId, pluginModuleTemplates]
);
useQuery(['getPlugTemplates'], () => loadPluginTemplates());
const filterPlugins = useMemo(() => {
return pluginModuleTemplates.filter((item) => item.id !== pluginId);
}, [pluginId, pluginModuleTemplates]);
return data ? (
<Flow
systemTemplates={filterTemplates}
pluginTemplates={[{ label: '', list: filterPlugins }]}
pluginTemplates={filterPlugins}
modules={data?.modules || []}
Header={<Header plugin={data} onClose={() => router.back()} />}
/>
@@ -80,7 +77,7 @@ const Render = ({ pluginId }: Props) => {
export default function AdEdit(props: any) {
return (
<FlowProvider filterAppIds={[]}>
<FlowProvider mode={'plugin'} filterAppIds={[]}>
<Render {...props} />
</FlowProvider>
);

View File

@@ -134,10 +134,13 @@ const CreateModal = ({
}, [defaultValue.id, onClose, toast, t, onDelete]);
return (
<MyModal isOpen onClose={onClose} isCentered={!isPc}>
<ModalHeader fontSize={'2xl'}>
{defaultValue.id ? t('plugin.Update Your Plugin') : t('plugin.Create Your Plugin')}
</ModalHeader>
<MyModal
isOpen
onClose={onClose}
iconSrc="/imgs/modal/edit.svg"
title={defaultValue.id ? t('plugin.Update Your Plugin') : t('plugin.Create Your Plugin')}
isCentered={!isPc}
>
<ModalBody>
<Box color={'myGray.800'} fontWeight={'bold'}>
{t('plugin.Set Name')}