This commit is contained in:
Archer
2023-09-26 14:31:37 +08:00
committed by GitHub
parent 38d4db5d5f
commit f6552d0d4f
48 changed files with 536 additions and 399 deletions

View File

@@ -0,0 +1,23 @@
---
title: '升级到 V4.4.5'
description: 'FastGPT 从旧版本升级到 V4.4.5 操作指南'
icon: 'upgrade'
draft: false
toc: true
weight: 992
---
## 执行初始化 API
发起 1 个 HTTP 请求(记得携带 `headers.rootkey`,这个值是环境变量里的)
1. https://xxxxx/api/admin/initv445
```bash
curl --location --request POST 'https://{{host}}/api/admin/initv445' \
--header 'rootkey: {{rootkey}}' \
--header 'Content-Type: application/json'
```
初始化了 variable 模块,将其合并到用户引导模块中。

View File

@@ -1,4 +1,4 @@
import { UserModelSchema } from '@/types/mongoSchema';
import { UserModelSchema } from '../user/type';
import { Configuration, OpenAIApi } from 'openai';
export const openaiBaseUrl = process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1';

View File

@@ -0,0 +1 @@
export { ChatCompletionRequestMessageRoleEnum } from 'openai';

1
packages/core/aiApi/type.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export type { CreateChatCompletionRequest, ChatCompletionRequestMessage } from 'openai';

13
packages/core/init.ts Normal file
View File

@@ -0,0 +1,13 @@
import tunnel from 'tunnel';
export function initHttpAgent() {
// proxy obj
if (process.env.AXIOS_PROXY_HOST && process.env.AXIOS_PROXY_PORT) {
global.httpsAgent = tunnel.httpsOverHttp({
proxy: {
host: process.env.AXIOS_PROXY_HOST,
port: +process.env.AXIOS_PROXY_PORT
}
});
}
}

View File

@@ -1,4 +1,11 @@
{
"name": "@fastgpt/core",
"version": "1.0.0"
"version": "1.0.0",
"dependencies": {
"openai": "^3.3.0",
"tunnel": "^0.0.6"
},
"devDependencies": {
"@types/tunnel": "^0.0.4"
}
}

5
packages/core/types/index.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
import type { Agent } from 'http';
declare global {
var httpsAgent: Agent;
}

19
packages/core/user/type.d.ts vendored Normal file
View File

@@ -0,0 +1,19 @@
export type UserModelSchema = {
_id: string;
username: string;
password: string;
avatar: string;
balance: number;
promotionRate: number;
inviterId?: string;
openaiKey: string;
createTime: number;
timezone: string;
openaiAccount?: {
key: string;
baseUrl: string;
};
limit: {
exportKbTime?: Date;
};
};

28
pnpm-lock.yaml generated
View File

@@ -29,7 +29,18 @@ importers:
packages/common: {}
packages/core: {}
packages/core:
dependencies:
openai:
specifier: ^3.3.0
version: registry.npmmirror.com/openai@3.3.0
tunnel:
specifier: ^0.0.6
version: registry.npmmirror.com/tunnel@0.0.6
devDependencies:
'@types/tunnel':
specifier: ^0.0.4
version: registry.npmmirror.com/@types/tunnel@0.0.4
packages/support: {}
@@ -152,9 +163,6 @@ importers:
nprogress:
specifier: ^0.2.0
version: registry.npmmirror.com/nprogress@0.2.0
openai:
specifier: ^3.3.0
version: registry.npmmirror.com/openai@3.3.0
papaparse:
specifier: ^5.4.1
version: registry.npmmirror.com/papaparse@5.4.1
@@ -209,9 +217,6 @@ importers:
timezones-list:
specifier: ^3.0.2
version: registry.npmmirror.com/timezones-list@3.0.2
tunnel:
specifier: ^0.0.6
version: registry.npmmirror.com/tunnel@0.0.6
winston:
specifier: ^3.10.0
version: registry.npmmirror.com/winston@3.10.0
@@ -270,9 +275,6 @@ importers:
'@types/request-ip':
specifier: ^0.0.37
version: registry.npmmirror.com/@types/request-ip@0.0.37
'@types/tunnel':
specifier: ^0.0.3
version: registry.npmmirror.com/@types/tunnel@0.0.3
eslint:
specifier: 8.34.0
version: registry.npmmirror.com/eslint@8.34.0
@@ -5611,10 +5613,10 @@ packages:
version: 1.3.3
dev: false
registry.npmmirror.com/@types/tunnel@0.0.3:
resolution: {integrity: sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/tunnel/-/tunnel-0.0.3.tgz}
registry.npmmirror.com/@types/tunnel@0.0.4:
resolution: {integrity: sha512-bQgDBL5XiqrrPUaZd9bZ2esOXcU4GTmgg0n6LHDqoMJezO3VFRZsW8qN6Gp64/LAmjtzNU3iAHBfV3Z2ht5DSg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/tunnel/-/tunnel-0.0.4.tgz}
name: '@types/tunnel'
version: 0.0.3
version: 0.0.4
dependencies:
'@types/node': registry.npmmirror.com/@types/node@18.14.0
dev: true

View File

@@ -14,6 +14,9 @@
"@chakra-ui/system": "^2.5.8",
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6",
"@fastgpt/common": "workspace:*",
"@fastgpt/core": "workspace:*",
"@fastgpt/support": "workspace:*",
"@mozilla/readability": "^0.4.4",
"@tanstack/react-query": "^4.24.10",
"@types/nprogress": "^0.2.0",
@@ -45,7 +48,6 @@
"next-i18next": "^14.0.3",
"nextjs-cors": "^2.1.2",
"nprogress": "^0.2.0",
"openai": "^3.3.0",
"papaparse": "^5.4.1",
"pg": "^8.10.0",
"pg-query-stream": "^4.5.3",
@@ -64,13 +66,9 @@
"request-ip": "^3.3.0",
"sass": "^1.58.3",
"timezones-list": "^3.0.2",
"tunnel": "^0.0.6",
"winston": "^3.10.0",
"winston-mongodb": "^5.1.1",
"zustand": "^4.3.5",
"@fastgpt/support": "workspace:*",
"@fastgpt/common": "workspace:*",
"@fastgpt/core": "workspace:*"
"zustand": "^4.3.5"
},
"devDependencies": {
"@svgr/webpack": "^6.5.1",
@@ -89,7 +87,6 @@
"@types/react-dom": "18.0.11",
"@types/react-syntax-highlighter": "^15.5.6",
"@types/request-ip": "^0.0.37",
"@types/tunnel": "^0.0.3",
"eslint": "8.34.0",
"eslint-config-next": "13.1.6",
"typescript": "4.9.5"

View File

@@ -237,8 +237,10 @@
"outlink": {
"Copy Iframe": "Copy Iframe",
"Copy Link": "Copy",
"Create API Key": "Create Key",
"Create Link": "Create Link",
"Delete Link": "Delete",
"Edit API Key": "Edit Key",
"Edit Ifrme Link": "Edit Iframe Link",
"Edit Link": "Edit",
"Edit Share Window": "Edit Share Window",

View File

@@ -230,15 +230,17 @@
"Tools": "工具"
},
"openapi": {
"app key tips": "这些 Key 已有当前应用标识,可以直接外部接入使用。",
"app key tips": "这些 key 已有当前应用标识,具体使用可参考文档",
"key alias": "key 的别名,仅用于展示",
"key tips": "你可以使用 API 秘钥访问一些特定的接口"
},
"outlink": {
"Copy Iframe": "复制嵌入",
"Copy Link": "复制",
"Create API Key": "创建新 Key",
"Create Link": "创建链接",
"Delete Link": "删除链接",
"Edit API Key": "编辑 Key 信息",
"Edit Ifrme Link": "更新嵌入链接",
"Edit Link": "编辑",
"Edit Share Window": "更新分享窗口",

View File

@@ -3,16 +3,14 @@ import { FlowModuleTypeEnum } from '@/constants/flow';
import { getChatModel } from '@/service/utils/data';
import { AppModuleItemType, VariableItemType } from '@/types/app';
export const getSpecialModule = (modules: AppModuleItemType[]) => {
export const getGuideModules = (modules: AppModuleItemType[]) => {
const guideModules = modules.find((item) => item.flowType === FlowModuleTypeEnum.userGuide);
const welcomeText: string =
modules
.find((item) => item.flowType === FlowModuleTypeEnum.userGuide)
?.inputs?.find((item) => item.key === SystemInputEnum.welcomeText)?.value || '';
guideModules?.inputs?.find((item) => item.key === SystemInputEnum.welcomeText)?.value || '';
const variableModules: VariableItemType[] =
modules
.find((item) => item.flowType === FlowModuleTypeEnum.variable)
?.inputs.find((item) => item.key === SystemInputEnum.variables)?.value || [];
guideModules?.inputs.find((item) => item.key === SystemInputEnum.variables)?.value || [];
return {
welcomeText,

View File

@@ -1,12 +1,20 @@
import React from 'react';
import { Spinner, Flex, Box } from '@chakra-ui/react';
const Loading = ({ fixed = true, text = '' }: { fixed?: boolean; text?: string }) => {
const Loading = ({
fixed = true,
text = '',
bg = 'rgba(255,255,255,0.5)'
}: {
fixed?: boolean;
text?: string;
bg?: string;
}) => {
return (
<Flex
position={fixed ? 'fixed' : 'absolute'}
zIndex={1000}
backgroundColor={'rgba(255,255,255,0.5)'}
bg={bg}
top={0}
left={0}
right={0}

View File

@@ -106,7 +106,7 @@ const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => {
onClick={() => copyData(baseUrl, '已复制 API 地址')}
>
<Box border={theme.borders.md} px={2} borderRadius={'md'} fontSize={'sm'}>
API地址
API地址
</Box>
<Box ml={2} color={'myGray.900'} fontSize={['sm', 'md']}>
{baseUrl}
@@ -292,7 +292,7 @@ function EditKeyModal({
});
return (
<MyModal isOpen={true} title={isEdit ? t('outlink.Edit Link') : t('outlink.Create Link')}>
<MyModal isOpen={true} title={isEdit ? t('outlink.Edit API Key') : t('outlink.Create API Key')}>
<ModalBody>
<Flex alignItems={'center'}>
<Box flex={'0 0 90px'}>{t('Name')}:</Box>

View File

@@ -24,12 +24,14 @@ export const ChatModelLimitTip =
export const userGuideTip = '可以添加特殊的对话前后引导模块,更好的让用户进行对话';
export const welcomeTextTip =
'每次对话开始前,发送一个初始内容。支持标准 Markdown 语法,可使用的额外标记:\n[快捷按键]: 用户点击后可以直接发送该问题';
export const variableTip =
'可以在对话开始前,要求用户填写一些内容作为本轮对话的特定变量。该模块位于开场引导之后。\n变量可以通过 {{变量key}} 的形式注入到其他模块 string 类型的输入中,例如:提示词、限定词等';
export const VariableModule: FlowModuleTemplateType = {
flowType: FlowModuleTypeEnum.variable,
logo: '/imgs/module/variable.png',
name: '全局变量',
intro: '可以在对话开始前,要求用户填写一些内容作为本轮对话的变量。该模块位于开场引导之后。',
intro: variableTip,
description:
'全局变量可以通过 {{变量key}} 的形式注入到其他模块 string 类型的输入中,例如:提示词、限定词等',
inputs: [
@@ -52,6 +54,12 @@ export const UserGuideModule: FlowModuleTemplateType = {
key: SystemInputEnum.welcomeText,
type: FlowInputItemTypeEnum.input,
label: '开场白'
},
{
key: SystemInputEnum.variables,
type: FlowInputItemTypeEnum.systemInput,
label: '对话框变量',
value: []
}
],
outputs: []
@@ -476,7 +484,7 @@ export const ModuleTemplates = [
},
{
label: '引导模块',
list: [UserGuideModule, VariableModule]
list: [UserGuideModule]
},
{
label: '内容生成',
@@ -491,7 +499,19 @@ export const ModuleTemplates = [
list: [ClassifyQuestionModule, ContextExtractModule, HttpModule]
}
];
export const ModuleTemplatesFlat = ModuleTemplates.map((templates) => templates.list)?.flat();
export const ModuleTemplatesFlat = [
VariableModule,
UserGuideModule,
UserInputModule,
HistoryModule,
ChatModule,
KBSearchModule,
AnswerModule,
ClassifyQuestionModule,
ContextExtractModule,
HttpModule,
EmptyModule
];
// template
export const appTemplates: (AppItemType & { avatar: string; intro: string })[] = [

View File

@@ -1,58 +0,0 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authUser } from '@/service/utils/auth';
import { connectToDatabase, Chat } from '@/service/mongo';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await authUser({ req, authRoot: true });
await connectToDatabase();
const { limit = 1000 } = req.body as { limit: number };
let skip = 0;
const total = await Chat.countDocuments({
chatId: { $exists: false }
});
let promise = Promise.resolve();
console.log(total);
for (let i = 0; i < total; i += limit) {
const skipVal = skip;
skip += limit;
promise = promise
.then(() => init(limit, skipVal))
.then(() => {
console.log(skipVal);
});
}
await promise;
jsonRes(res, {});
} catch (error) {
jsonRes(res, {
code: 500,
error
});
}
}
async function init(limit: number, skip: number) {
// 遍历 app
const chats = await Chat.find(
{
chatId: { $exists: false }
},
'_id'
).limit(limit);
await Promise.all(
chats.map((chat) =>
Chat.findByIdAndUpdate(chat._id, {
chatId: String(chat._id),
source: 'online'
})
)
);
}

View File

@@ -1,98 +0,0 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authUser } from '@/service/utils/auth';
import { connectToDatabase, Chat, ChatItem } from '@/service/mongo';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24);
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await authUser({ req, authRoot: true });
await connectToDatabase();
const { limit = 100 } = req.body as { limit: number };
let skip = 0;
const total = await Chat.countDocuments({
content: { $exists: true, $not: { $size: 0 } },
isInit: { $ne: true }
});
const totalChat = await Chat.aggregate([
{
$project: {
contentLength: { $size: '$content' }
}
},
{
$group: {
_id: null,
totalLength: { $sum: '$contentLength' }
}
}
]);
console.log('chatLen:', total, totalChat);
let promise = Promise.resolve();
for (let i = 0; i < total; i += limit) {
const skipVal = skip;
skip += limit;
promise = promise
.then(() => init(limit))
.then(() => {
console.log(skipVal);
});
}
await promise;
jsonRes(res, {});
} catch (error) {
jsonRes(res, {
code: 500,
error
});
}
}
async function init(limit: number) {
// 遍历 app
const chats = await Chat.find(
{
content: { $exists: true, $not: { $size: 0 } },
isInit: { $ne: true }
},
'_id userId appId chatId content'
)
.sort({ updateTime: -1 })
.limit(limit);
await Promise.all(
chats.map(async (chat) => {
const inserts = chat.content
.map((item) => ({
dataId: nanoid(),
chatId: chat.chatId,
userId: chat.userId,
appId: chat.appId,
obj: item.obj,
value: item.value,
responseData: item.responseData
}))
.filter((item) => item.chatId && item.userId && item.appId && item.obj && item.value);
try {
await Promise.all(inserts.map((item) => ChatItem.create(item)));
await Chat.findByIdAndUpdate(chat._id, {
isInit: true
});
} catch (error) {
console.log(error);
await ChatItem.deleteMany({ chatId: chat.chatId });
}
})
);
}

View File

@@ -1,27 +0,0 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authUser } from '@/service/utils/auth';
import { connectToDatabase, OutLink } from '@/service/mongo';
import { OutLinkTypeEnum } from '@/constants/chat';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await authUser({ req, authRoot: true });
await connectToDatabase();
await OutLink.updateMany(
{},
{
$set: { type: OutLinkTypeEnum.share }
}
);
jsonRes(res, {});
} catch (error) {
jsonRes(res, {
code: 500,
error
});
}
}

View File

@@ -0,0 +1,104 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authUser } from '@/service/utils/auth';
import { connectToDatabase, App } from '@/service/mongo';
import { FlowInputItemTypeEnum, FlowModuleTypeEnum } from '@/constants/flow';
import { SystemInputEnum } from '@/constants/app';
const limit = 300;
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
await authUser({ req, authRoot: true });
const totalApps = await App.countDocuments();
// init app
await App.updateMany({}, { $set: { inited: false } });
for (let i = 0; i < totalApps; i += limit) {
await initVariable();
console.log(i + limit);
}
jsonRes(res, {
data: {
total: totalApps
}
});
} catch (error) {
jsonRes(res, {
code: 500,
error
});
}
}
async function initVariable(): Promise<any> {
try {
const apps = await App.find({ inited: false }).limit(limit);
await Promise.all(
apps.map(async (app) => {
const jsonAPP = app.toObject();
// @ts-ignore
app.inited = true;
const modules = jsonAPP.modules;
// 找到 variable
const variable = modules.find((item) => item.flowType === FlowModuleTypeEnum.variable);
if (!variable) return await app.save();
// 找到 guide 模块
const userGuideModule = modules.find(
(item) => item.flowType === FlowModuleTypeEnum.userGuide
);
if (userGuideModule) {
userGuideModule.inputs = [
userGuideModule.inputs[0],
{
key: SystemInputEnum.variables,
type: FlowInputItemTypeEnum.systemInput,
label: '对话框变量',
value: variable.inputs[0]?.value
}
];
} else {
modules.unshift({
moduleId: 'userGuide',
flowType: FlowModuleTypeEnum.userGuide,
name: '用户引导',
position: {
x: 447.98520778293346,
y: 721.4016845336229
},
inputs: [
{
key: SystemInputEnum.welcomeText,
type: FlowInputItemTypeEnum.input,
label: '开场白'
},
{
key: SystemInputEnum.variables,
type: FlowInputItemTypeEnum.systemInput,
label: '对话框变量',
value: variable.inputs[0]?.value
}
],
outputs: []
});
}
jsonAPP.modules = jsonAPP.modules.filter(
(item) => item.flowType !== FlowModuleTypeEnum.variable
);
app.modules = JSON.parse(JSON.stringify(jsonAPP.modules));
await app.save();
})
);
} catch (error) {
return initVariable();
}
}

View File

@@ -6,7 +6,7 @@ import { authUser } from '@/service/utils/auth';
import { ChatItemType } from '@/types/chat';
import { authApp } from '@/service/utils/auth';
import type { ChatSchema } from '@/types/mongoSchema';
import { getSpecialModule, getChatModelNameList } from '@/components/ChatBox/utils';
import { getGuideModules, getChatModelNameList } from '@/components/ChatBox/utils';
import { TaskResponseKeyEnum } from '@/constants/chat';
/* 初始化我的聊天框,需要身份验证 */
@@ -81,7 +81,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
chatId,
appId,
app: {
...getSpecialModule(app.modules),
...getGuideModules(app.modules),
chatModels: getChatModelNameList(app.modules),
name: app.name,
avatar: app.avatar,

View File

@@ -18,7 +18,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
await collection.updateMany(
{
_id: { $in: fileIds.map((id) => new Types.ObjectId(id)) },
_id: { $in: fileIds.filter((id) => !!id).map((id) => new Types.ObjectId(id)) },
['metadata.userId']: userId
},
{

View File

@@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authBalanceByUid, authUser } from '@/service/utils/auth';
import { withNextCors } from '@/service/utils/tools';
import { getAIChatApi, axiosConfig } from '@/service/lib/openai';
import { getAIChatApi, axiosConfig } from '@fastgpt/core/aiApi/config';
import { pushGenerateVectorBill } from '@/service/common/bill/push';
type Props = {

View File

@@ -14,12 +14,14 @@ import {
dispatchContentExtract,
dispatchHttpRequest
} from '@/service/moduleDispatch';
import type { CreateChatCompletionRequest } from 'openai';
import type {
CreateChatCompletionRequest,
ChatCompletionRequestMessage
} from '@fastgpt/core/aiApi/type';
import { gptMessage2ChatType, textAdaptGptResponse } from '@/utils/adapt';
import { getChatHistory } from './getHistory';
import { saveChat } from '@/service/utils/chat/saveChat';
import { sseResponse } from '@/service/utils/tools';
import { type ChatCompletionRequestMessage } from 'openai';
import { TaskResponseKeyEnum } from '@/constants/chat';
import { FlowModuleTypeEnum, initModuleType } from '@/constants/flow';
import { AppModuleItemType, RunningModuleItemType } from '@/types/app';

View File

@@ -4,7 +4,7 @@ import { connectToDatabase, OutLink, User } from '@/service/mongo';
import type { InitShareChatResponse } from '@/api/response/chat';
import { authApp } from '@/service/utils/auth';
import { HUMAN_ICON } from '@/constants/chat';
import { getChatModelNameList, getSpecialModule } from '@/components/ChatBox/utils';
import { getChatModelNameList, getGuideModules } from '@/components/ChatBox/utils';
import { authShareChatInit } from '@/service/support/outLink/auth';
/* init share chat window */
@@ -46,7 +46,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
data: {
userAvatar: user?.avatar || HUMAN_ICON,
app: {
...getSpecialModule(app.modules),
...getGuideModules(app.modules),
chatModels: getChatModelNameList(app.modules),
name: app.name,
avatar: app.avatar,

View File

@@ -5,7 +5,7 @@ import { User } from '@/service/models/user';
import { connectToDatabase } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { UserUpdateParams } from '@/types/user';
import { axiosConfig, getAIChatApi, openaiBaseUrl } from '@/service/lib/openai';
import { axiosConfig, getAIChatApi, openaiBaseUrl } from '@fastgpt/core/aiApi/config';
/* update user info */
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
@@ -34,7 +34,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
...axiosConfig(openaiAccount)
}
);
if (!response?.data?.choices?.[0]?.message?.content) {
if (response?.data?.choices?.[0]?.message?.content === undefined) {
throw new Error(JSON.stringify(response?.data));
}
}

View File

@@ -15,7 +15,7 @@ import { streamFetch } from '@/api/fetch';
import MyTooltip from '@/components/MyTooltip';
import { useUserStore } from '@/store/user';
import ChatBox, { type ComponentRef, type StartChatFnProps } from '@/components/ChatBox';
import { getSpecialModule } from '@/components/ChatBox/utils';
import { getGuideModules } from '@/components/ChatBox/utils';
export type ChatTestComponentRef = {
resetChatTest: () => void;
@@ -114,7 +114,7 @@ const ChatTest = (
appAvatar={app.avatar}
userAvatar={userInfo?.avatar}
showMarkIcon
{...getSpecialModule(modules)}
{...getGuideModules(modules)}
onStartChat={startChat}
onDelMessage={() => {}}
/>

View File

@@ -1,17 +1,49 @@
import React, { useMemo } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import { NodeProps } from 'reactflow';
import { Box, Flex, Textarea } from '@chakra-ui/react';
import {
Box,
Flex,
Textarea,
Button,
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer
} from '@chakra-ui/react';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import NodeCard from '../modules/NodeCard';
import { FlowModuleItemType } from '@/types/flow';
import Container from '../modules/Container';
import { SystemInputEnum } from '@/constants/app';
import { welcomeTextTip, variableTip } from '@/constants/flow/ModuleTemplate';
import VariableEditModal, { addVariable } from '../../../VariableEditModal';
import MyIcon from '@/components/Icon';
import MyTooltip from '@/components/MyTooltip';
import { welcomeTextTip } from '@/constants/flow/ModuleTemplate';
import Container from '../modules/Container';
import NodeCard from '../modules/NodeCard';
import { VariableItemType } from '@/types/app';
const NodeUserGuide = ({ data }: NodeProps<FlowModuleItemType>) => {
return (
<>
<NodeCard minW={'300px'} {...data}>
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'}>
<WelcomeText data={data} />
<Box mt={3}>
<ChatStartVariable data={data} />
</Box>
</Container>
</NodeCard>
</>
);
};
export default React.memo(NodeUserGuide);
export function WelcomeText({ data }: { data: FlowModuleItemType }) {
const { inputs, moduleId, onChangeNode } = data;
const welcomeText = useMemo(
() => inputs.find((item) => item.key === SystemInputEnum.welcomeText),
[inputs]
@@ -19,41 +51,148 @@ const NodeUserGuide = ({ data }: NodeProps<FlowModuleItemType>) => {
return (
<>
<NodeCard minW={'300px'} {...data}>
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'}>
<>
<Flex mb={1} alignItems={'center'}>
<MyIcon name={'welcomeText'} mr={2} w={'16px'} color={'#E74694'} />
<Box></Box>
<MyTooltip label={welcomeTextTip} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Flex>
{welcomeText && (
<Textarea
className="nodrag"
rows={6}
resize={'both'}
defaultValue={welcomeText.value}
bg={'myWhite.500'}
placeholder={welcomeTextTip}
onChange={(e) => {
onChangeNode({
moduleId,
key: SystemInputEnum.welcomeText,
type: 'inputs',
value: {
...welcomeText,
value: e.target.value
}
});
}}
/>
)}
</>
</Container>
</NodeCard>
<Flex mb={1} alignItems={'center'}>
<MyIcon name={'welcomeText'} mr={2} w={'16px'} color={'#E74694'} />
<Box></Box>
<MyTooltip label={welcomeTextTip} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Flex>
{welcomeText && (
<Textarea
className="nodrag"
rows={6}
resize={'both'}
defaultValue={welcomeText.value}
bg={'myWhite.500'}
placeholder={welcomeTextTip}
onChange={(e) => {
onChangeNode({
moduleId,
key: SystemInputEnum.welcomeText,
type: 'inputs',
value: {
...welcomeText,
value: e.target.value
}
});
}}
/>
)}
</>
);
};
export default React.memo(NodeUserGuide);
}
function ChatStartVariable({ data }: { data: FlowModuleItemType }) {
const { inputs, moduleId, onChangeNode } = data;
const variables = useMemo(
() =>
(inputs.find((item) => item.key === SystemInputEnum.variables)
?.value as VariableItemType[]) || [],
[inputs]
);
const [editVariable, setEditVariable] = useState<VariableItemType>();
const updateVariables = useCallback(
(value: VariableItemType[]) => {
onChangeNode({
moduleId,
key: SystemInputEnum.variables,
type: 'inputs',
value: {
...inputs.find((item) => item.key === SystemInputEnum.variables),
value
}
});
},
[inputs, onChangeNode, moduleId]
);
const onclickSubmit = useCallback(
({ variable }: { variable: VariableItemType }) => {
updateVariables(variables.map((item) => (item.id === variable.id ? variable : item)));
setEditVariable(undefined);
},
[updateVariables, variables]
);
return (
<>
<Flex mb={1} alignItems={'center'}>
<MyIcon name={'variable'} mr={2} w={'16px'} color={'#fb7c3d'} />
<Box></Box>
<MyTooltip label={variableTip} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
<Box flex={1} />
<Flex
ml={2}
textAlign={'right'}
cursor={'pointer'}
px={3}
py={'2px'}
borderRadius={'md'}
_hover={{ bg: 'myGray.200' }}
onClick={() => {
const newVariable = addVariable();
updateVariables(variables.concat(newVariable));
setEditVariable(newVariable);
}}
>
+&ensp;
</Flex>
</Flex>
{variables.length > 0 && (
<TableContainer borderWidth={'1px'} borderBottom="none" borderRadius={'lg'}>
<Table>
<Thead>
<Tr>
<Th></Th>
<Th> key</Th>
<Th></Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{variables.map((item, index) => (
<Tr key={index}>
<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={() =>
updateVariables(variables.filter((variable) => variable.id !== item.id))
}
/>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
)}
{!!editVariable && (
<VariableEditModal
defaultVariable={editVariable}
onClose={() => setEditVariable(undefined)}
onSubmit={onclickSubmit}
/>
)}
</>
);
}

View File

@@ -109,7 +109,7 @@ const ModuleTemplateList = ({
<Avatar src={item.logo} w={'34px'} objectFit={'contain'} borderRadius={'0'} />
<Box ml={5} flex={'1 0 0'}>
<Box color={'black'}>{item.name}</Box>
<Box color={'myGray.500'} fontSize={'sm'}>
<Box className="textEllipsis3" color={'myGray.500'} fontSize={'sm'}>
{item.intro}
</Box>
</Box>

View File

@@ -46,7 +46,7 @@ import { useToast } from '@/hooks/useToast';
import { AppSchema } from '@/types/mongoSchema';
import { delModelById } from '@/api/app';
import { useTranslation } from 'react-i18next';
import { getSpecialModule } from '@/components/ChatBox/utils';
import { getGuideModules } from '@/components/ChatBox/utils';
import dynamic from 'next/dynamic';
import MySelect from '@/components/Select';
@@ -410,20 +410,22 @@ const Settings = ({ appId }: { appId: string }) => {
<Flex alignItems={'center'} mt={5}>
<Box {...LabelStyles}></Box>
<MySelect
width={['100%', '300px']}
value={getValues('chatModel.model')}
list={chatModelSelectList}
onchange={(val: any) => {
setValue('chatModel.model', val);
const maxToken =
chatModelList.find((item) => item.model === getValues('chatModel.model'))
?.contextMaxToken || 4000;
const token = maxToken / 2;
setValue('chatModel.maxToken', token);
setRefresh(!refresh);
}}
/>
<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'))
?.contextMaxToken || 4000;
const token = maxToken / 2;
setValue('chatModel.maxToken', token);
setRefresh(!refresh);
}}
/>
</Box>
</Flex>
<Flex alignItems={'center'} my={10}>
<Box {...LabelStyles}></Box>
@@ -676,7 +678,7 @@ const ChatTest = ({ appId }: { appId: string }) => {
appAvatar={appDetail.avatar}
userAvatar={userInfo?.avatar}
showMarkIcon
{...getSpecialModule(modules)}
{...getGuideModules(modules)}
onStartChat={startChat}
onDelMessage={() => {}}
/>

View File

@@ -29,27 +29,24 @@ const Home = ({ homeUrl = '/' }: { homeUrl: string }) => {
<Head>
<title>{feConfigs?.systemTitle || 'FastGPT'}</title>
</Head>
{homeUrl === '/' ? (
<Box id="home" bg={'myWhite.600'} h={'100vh'} overflowY={'auto'} overflowX={'hidden'}>
<Box position={'fixed'} zIndex={10} top={0} left={0} right={0}>
<Navbar />
</Box>
<Box maxW={'1200px'} pt={'70px'} m={'auto'}>
<Hero />
<Ability />
<Box my={[4, 6]}>
<Choice />
</Box>
</Box>
{feConfigs?.show_git && (
<Box bg={'white'}>
<Footer />
</Box>
)}
<Box id="home" bg={'myWhite.600'} h={'100vh'} overflowY={'auto'} overflowX={'hidden'}>
<Box position={'fixed'} zIndex={10} top={0} left={0} right={0}>
<Navbar />
</Box>
) : (
<Loading />
)}
<Box maxW={'1200px'} pt={'70px'} m={'auto'}>
<Hero />
<Ability />
<Box my={[4, 6]}>
<Choice />
</Box>
</Box>
{feConfigs?.show_git && (
<Box bg={'white'}>
<Footer />
</Box>
)}
</Box>
{homeUrl !== '/' && <Loading bg={'white'} />}
</>
);
};

View File

@@ -113,7 +113,7 @@ const ChunkImport = ({ kbId }: { kbId: string }) => {
chunks: splitRes.chunks.map((chunk) => ({
a: '',
source: file.filename,
file_id: file.id,
file_id: file.chunks[0]?.file_id,
q: chunk
}))
};

View File

@@ -20,7 +20,7 @@ const CreateFileModal = ({
});
return (
<MyModal title={t('file.Create File')} isOpen onClose={() => {}} w={'600px'} top={'15vh'}>
<MyModal title={t('file.Create File')} isOpen w={'600px'} top={'15vh'}>
<ModalBody>
<Box mb={1} fontSize={'sm'}>

View File

@@ -113,7 +113,7 @@ const QAImport = ({ kbId }: { kbId: string }) => {
chunks: splitRes.chunks.map((chunk) => ({
a: '',
source: file.filename,
file_id: file.id,
file_id: file.chunks[0]?.file_id,
q: chunk
}))
};

View File

@@ -4,8 +4,8 @@ import { TrainingModeEnum } from '@/constants/plugin';
import { ERROR_ENUM } from '../errorCode';
import { sendInform } from '@/pages/api/user/inform/send';
import { authBalanceByUid } from '../utils/auth';
import { axiosConfig, getAIChatApi } from '../lib/openai';
import { ChatCompletionRequestMessage } from 'openai';
import { axiosConfig, getAIChatApi } from '@fastgpt/core/aiApi/config';
import type { ChatCompletionRequestMessage } from '@fastgpt/core/aiApi/type';
import { addLog } from '../utils/tools';
import { splitText2Chunks } from '@/utils/file';
import { replaceVariable } from '@/utils/common/tools/text';

View File

@@ -56,6 +56,9 @@ const AppSchema = new Schema({
type: Array,
default: []
},
inited: {
type: Boolean
},
// 弃
chat: Object
});

View File

@@ -2,7 +2,7 @@ import { adaptChat2GptMessages } from '@/utils/common/adapt/message';
import { ChatContextFilter } from '@/service/common/tiktoken';
import type { ChatHistoryItemResType, ChatItemType } from '@/types/chat';
import { ChatRoleEnum, TaskResponseKeyEnum } from '@/constants/chat';
import { getAIChatApi, axiosConfig } from '@/service/lib/openai';
import { getAIChatApi, axiosConfig } from '@fastgpt/core/aiApi/config';
import type { ClassifyQuestionAgentItemType } from '@/types/app';
import { SystemInputEnum } from '@/constants/app';
import { SpecialInputKeyEnum } from '@/constants/flow';
@@ -46,7 +46,7 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
return completions(props);
})();
const result = agents.find((item) => item.key === arg?.type) || agents[0];
const result = agents.find((item) => item.key === arg?.type) || agents[agents.length - 1];
return {
[result.key]: 1,
@@ -120,12 +120,21 @@ async function functionCall({
}
);
const arg = JSON.parse(response.data.choices?.[0]?.message?.function_call?.arguments || '');
try {
const arg = JSON.parse(response.data.choices?.[0]?.message?.function_call?.arguments || '');
return {
arg,
tokens: response.data.usage?.total_tokens || 0
};
return {
arg,
tokens: response.data.usage?.total_tokens || 0
};
} catch (error) {
console.log('Your model may not support function_call');
return {
arg: {},
tokens: 0
};
}
}
async function completions({

View File

@@ -2,7 +2,7 @@ import { adaptChat2GptMessages } from '@/utils/common/adapt/message';
import { ChatContextFilter } from '@/service/common/tiktoken';
import type { ChatHistoryItemResType, ChatItemType } from '@/types/chat';
import { ChatRoleEnum, TaskResponseKeyEnum } from '@/constants/chat';
import { getAIChatApi, axiosConfig } from '@/service/lib/openai';
import { getAIChatApi, axiosConfig } from '@fastgpt/core/aiApi/config';
import type { ContextExtractAgentItemType } from '@/types/app';
import { ContextExtractEnum } from '@/constants/flow/flowField';
import { FlowModuleTypeEnum } from '@/constants/flow';

View File

@@ -5,13 +5,13 @@ import type { ChatHistoryItemResType } from '@/types/chat';
import { ChatRoleEnum, sseResponseEventEnum } from '@/constants/chat';
import { SSEParseData, parseStreamChunk } from '@/utils/sse';
import { textAdaptGptResponse } from '@/utils/adapt';
import { getAIChatApi, axiosConfig } from '@/service/lib/openai';
import { getAIChatApi, axiosConfig } from '@fastgpt/core/aiApi/config';
import { TaskResponseKeyEnum } from '@/constants/chat';
import { getChatModel } from '@/service/utils/data';
import { countModelPrice } from '@/service/common/bill/push';
import { ChatModelItemType } from '@/types/model';
import { textCensor } from '@/api/service/plugins';
import { ChatCompletionRequestMessageRoleEnum } from 'openai';
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/core/aiApi/constant';
import { AppModuleItemType } from '@/types/app';
import { countMessagesTokens, sliceMessagesTB } from '@/utils/common/tiktoken';
import { adaptChat2GptMessages } from '@/utils/common/adapt/message';

View File

@@ -1,5 +1,4 @@
import mongoose from 'mongoose';
import tunnel from 'tunnel';
import { startQueue } from './utils/tools';
import { getInitConfig } from '@/pages/api/system/getInitData';
import { User } from './models/user';
@@ -9,6 +8,7 @@ import { createHashPassword } from '@/utils/tools';
import { createLogger, format, transports } from 'winston';
import 'winston-mongodb';
import { getTikTokenEnc } from '@/utils/common/tiktoken';
import { initHttpAgent } from '@fastgpt/core/init';
/**
* connect MongoDB and init data
@@ -24,15 +24,6 @@ export async function connectToDatabase(): Promise<void> {
global.vectorQueueLen = 0;
global.sendInformQueue = [];
global.sendInformQueueLen = 0;
// proxy obj
if (process.env.AXIOS_PROXY_HOST && process.env.AXIOS_PROXY_PORT) {
global.httpsAgent = tunnel.httpsOverHttp({
proxy: {
host: process.env.AXIOS_PROXY_HOST,
port: +process.env.AXIOS_PROXY_PORT
}
});
}
// logger
initLogger();
@@ -41,6 +32,7 @@ export async function connectToDatabase(): Promise<void> {
getInitConfig();
// init tikToken
getTikTokenEnc();
initHttpAgent();
try {
mongoose.set('strictQuery', true);

View File

@@ -19,6 +19,7 @@ export const connectPg = async (): Promise<Pool> => {
global.pgClient.on('error', (err) => {
console.log(err);
global.pgClient?.end();
global.pgClient = null;
connectPg();
});

View File

@@ -1,5 +1,4 @@
import type { Mongoose } from 'mongoose';
import type { Agent } from 'http';
import type { Pool } from 'pg';
import type { Tiktoken } from 'js-tiktoken';
import type { Logger } from 'winston';
@@ -55,7 +54,6 @@ export type SystemEnvType = {
declare global {
var mongodb: Mongoose | string | null;
var pgClient: Pool | null;
var httpsAgent: Agent;
var qaQueueLen: number;
var vectorQueueLen: number;
var TikToken: Tiktoken;

View File

@@ -2,7 +2,7 @@ import { formatPrice } from '@fastgpt/common/bill/index';
import type { BillSchema } from '@/types/common/bill';
import type { UserBillType } from '@/types/user';
import { ChatItemType } from '@/types/chat';
import { ChatCompletionRequestMessageRoleEnum } from 'openai';
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/core/aiApi/constant';
import { ChatRoleEnum } from '@/constants/chat';
import type { MessageItemType } from '@/pages/api/openapi/v1/chat/completions';
import type { AppModuleItemType } from '@/types/app';

View File

@@ -10,6 +10,7 @@ import { SystemInputEnum } from '@/constants/app';
import type { SelectedDatasetType } from '@/types/core/dataset';
import { FlowInputItemType } from '@/types/flow';
import type { AIChatProps } from '@/types/core/aiChat';
import { getGuideModules } from '@/components/ChatBox/utils';
export type EditFormType = {
chatModel: AIChatProps;
@@ -136,16 +137,13 @@ export const appModules2Form = (modules: AppModuleItemType[]) => {
target?.inputs?.find((item) => item.key === SpecialInputKeyEnum.answerText)?.value || '';
}
} else if (module.flowType === FlowModuleTypeEnum.userGuide) {
const val =
module.inputs.find((item) => item.key === SystemInputEnum.welcomeText)?.value || '';
if (val) {
const { welcomeText, variableModules } = getGuideModules(modules);
if (welcomeText) {
defaultAppForm.guide.welcome = {
text: val
text: welcomeText
};
}
} else if (module.flowType === FlowModuleTypeEnum.variable) {
defaultAppForm.variables =
module.inputs.find((item) => item.key === SystemInputEnum.variables)?.value || [];
defaultAppForm.variables = variableModules;
}
});
@@ -220,54 +218,32 @@ const chatModelInput = (formData: EditFormType): FlowInputItemType[] => [
connected: true
}
];
const welcomeTemplate = (formData: EditFormType): AppModuleItemType[] =>
formData.guide?.welcome?.text
? [
{
name: '用户引导',
flowType: FlowModuleTypeEnum.userGuide,
inputs: [
{
key: 'welcomeText',
type: 'input',
label: '开场白',
value: formData.guide.welcome.text,
connected: true
}
],
outputs: [],
position: {
x: 447.98520778293346,
y: 721.4016845336229
},
moduleId: 'userGuide'
}
]
: [];
const variableTemplate = (formData: EditFormType): AppModuleItemType[] =>
formData.variables.length > 0
? [
{
name: '全局变量',
flowType: FlowModuleTypeEnum.variable,
inputs: [
{
key: 'variables',
value: formData.variables,
type: 'systemInput',
label: '变量输入',
connected: true
}
],
outputs: [],
position: {
x: 444.0369195277651,
y: 1008.5185781784537
},
moduleId: 'variable'
}
]
: [];
const userGuideTemplate = (formData: EditFormType): AppModuleItemType[] => [
{
name: '用户引导',
flowType: FlowModuleTypeEnum.userGuide,
inputs: [
{
key: SystemInputEnum.welcomeText,
type: FlowInputItemTypeEnum.input,
label: '开场白',
value: formData.guide.welcome.text
},
{
key: SystemInputEnum.variables,
type: FlowInputItemTypeEnum.systemInput,
label: '对话框变量',
value: formData.variables
}
],
outputs: [],
position: {
x: 447.98520778293346,
y: 721.4016845336229
},
moduleId: 'userGuide'
}
];
const simpleChatTemplate = (formData: EditFormType): AppModuleItemType[] => [
{
name: '用户问题(对话入口)',
@@ -572,8 +548,7 @@ const kbTemplate = (formData: EditFormType): AppModuleItemType[] => [
export const appForm2Modules = (formData: EditFormType) => {
const modules = [
...welcomeTemplate(formData),
...variableTemplate(formData),
...userGuideTemplate(formData),
...(formData.kb.list.length > 0 ? kbTemplate(formData) : simpleChatTemplate(formData))
];

View File

@@ -1,6 +1,6 @@
import type { ChatItemType } from '@/types/chat';
import { ChatRoleEnum } from '@/constants/chat';
import { ChatCompletionRequestMessageRoleEnum } from 'openai';
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/core/aiApi/constant';
import type { MessageItemType } from '@/pages/api/openapi/v1/chat/completions';
const chat2Message = {

View File

@@ -2,7 +2,7 @@
import { ChatItemType } from '@/types/chat';
import { Tiktoken } from 'js-tiktoken/lite';
import { adaptChat2GptMessages } from '../adapt/message';
import { ChatCompletionRequestMessageRoleEnum } from 'openai';
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/core/aiApi/constant';
import encodingJson from './cl100k_base.json';
/* init tikToken obj */

View File

@@ -11,7 +11,7 @@ export const splitText2Chunks = ({ text, maxLen }: { text: string; maxLen: numbe
const overlapLen = Math.floor(maxLen * 0.25); // Overlap length
try {
const splitTexts = text.split(/(?<=[。!?;.!?;])/g);
const splitTexts = text.split(/(?<=[。!?;.!?;\n])/g);
const chunks: string[] = [];
let preChunk = '';

View File

@@ -19,6 +19,6 @@
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.d.ts"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.d.ts","../../packages/**/*.d.ts"],
"exclude": ["node_modules"]
}