* chat item table

* perf: chat item save

* docs

* limit

* docs

* docs

* perf: node card

* docs

* docs
This commit is contained in:
Archer
2023-08-17 16:57:22 +08:00
committed by GitHub
parent ce61ac3fac
commit 324e4a0e75
49 changed files with 617 additions and 359 deletions

View File

@@ -27,14 +27,6 @@ FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开
| ![Demo](./.github/imgs/intro1.png) | ![Demo](./.github/imgs/intro2.png) |
| ![Demo](./.github/imgs/intro3.png) | ![Demo](./.github/imgs/intro4.png) |
## ⚡快速部署
> Sealos 的服务器在国外,不需要额外处理网络问题,无需服务器、无需魔法、无需域名,支持高并发 & 动态伸缩。点击以下按钮即可一键部署 👇
[![](https://cdn.jsdelivr.us/gh/labring-actions/templates@main/Deploy-on-Sealos.svg)](https://cloud.sealos.io/?openapp=system-fastdeploy%3FtemplateName%3Dfastgpt)
由于需要部署数据库,部署完后需要等待 2~4 分钟才能正常访问。默认用了最低配置,首次访问时会有些慢。
## 💡 功能
1. 强大的可视化编排,轻松构建 AI 应用
@@ -76,12 +68,21 @@ FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开
项目技术栈: NextJs + TS + ChakraUI + Mongo + PostgresVector 插件)
- [快开始本地开发](https://doc.fastgpt.run/docs/develop/dev)
- [部署 FastGPT](https://doc.fastgpt.run/docs/category/deploy)
- [系统配置文件说明](https://doc.fastgpt.run/docs/category/data-config)
- [多模型配置](https://doc.fastgpt.run/docs/develop/data_config/chat_models)
- [V3 升级 V4 初始化](https://doc.fastgpt.run/docs/develop/deploy/v4init)
- [API 文档](https://kjqvjse66l.feishu.cn/docx/DmLedTWtUoNGX8xui9ocdUEjnNh?pre_pathname=%2Fdrive%2Fhome%2F)
- **⚡ 快速部署**
> Sealos 的服务器在国外,不需要额外处理网络问题,无需服务器、无需魔法、无需域名,支持高并发 & 动态伸缩。点击以下按钮即可一键部署 👇
[![](https://cdn.jsdelivr.us/gh/labring-actions/templates@main/Deploy-on-Sealos.svg)](https://cloud.sealos.io/?openapp=system-fastdeploy%3FtemplateName%3Dfastgpt)
由于需要部署数据库,部署完后需要等待 2~4 分钟才能正常访问。默认用了最低配置,首次访问时会有些慢。
* [快开始本地开发](https://doc.fastgpt.run/docs/develop/dev)
* [部署 FastGPT](https://doc.fastgpt.run/docs/category/deploy)
* [系统配置文件说明](https://doc.fastgpt.run/docs/category/data-config)
* [多模型配置](https://doc.fastgpt.run/docs/develop/data_config/chat_models)
* [V3 升级 V4 初始化](https://doc.fastgpt.run/docs/develop/deploy/v4init)
* [升级 v4.1 初始化](https://doc.fastgpt.run/docs/develop/deploy/initv4.1)
* [API 文档](https://kjqvjse66l.feishu.cn/docx/DmLedTWtUoNGX8xui9ocdUEjnNh?pre_pathname=%2Fdrive%2Fhome%2F)
## 🏘️ 社区交流群

View File

@@ -11,6 +11,7 @@
"Confirm Save App Tip": "The application may be in advanced orchestration mode, and the advanced orchestration configuration will be overwritten after saving, please confirm!",
"Connection is invalid": "Connecting is invalid",
"Connection type is different": "Connection type is different",
"Copy Module Config": "Copy config",
"Export Config Successful": "The configuration has been copied. Please check for important data",
"Export Configs": "Export Configs",
"Import Config": "Import Config",

View File

@@ -11,6 +11,7 @@
"Confirm Save App Tip": "该应用可能为高级编排模式,保存后将会覆盖高级编排配置,请确认!",
"Connection is invalid": "连接无效",
"Connection type is different": "连接的类型不一致",
"Copy Module Config": "复制配置",
"Export Config Successful": "已复制配置,请注意检查是否有重要数据",
"Export Configs": "导出配置",
"Import Config": "导入配置",

View File

@@ -27,20 +27,10 @@ export const delChatHistoryById = (chatId: string) => DELETE(`/chat/removeHistor
*/
export const clearChatHistoryByAppId = (appId: string) => DELETE(`/chat/removeHistory`, { appId });
/**
* update history quote status
*/
export const updateHistoryQuote = (params: {
chatId: string;
contentId: string;
quoteId: string;
sourceText: string;
}) => PUT(`/chat/history/updateHistoryQuote`, params);
/**
* 删除一句对话
*/
export const delChatRecordByIndex = (data: { chatId: string; contentId: string }) =>
export const delChatRecordById = (data: { chatId: string; contentId: string }) =>
DELETE(`/chat/delChatRecordByContentId`, data);
/**

View File

@@ -40,6 +40,7 @@ import { useRouter } from 'next/router';
import { useGlobalStore } from '@/store/global';
import { TaskResponseKeyEnum, getDefaultChatVariables } from '@/constants/chat';
import { useTranslation } from 'react-i18next';
import { customAlphabet } from 'nanoid';
import MyIcon from '@/components/Icon';
import Avatar from '@/components/Avatar';
@@ -51,6 +52,8 @@ const ResponseTags = dynamic(() => import('./ResponseTags'));
import styles from './index.module.scss';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24);
const textareaMinH = '22px';
type generatingMessageProps = { text?: string; name?: string; status?: 'running' | 'finish' };
export type StartChatFnProps = {
@@ -282,13 +285,13 @@ const ChatBox = (
const newChatList: ChatSiteItemType[] = [
...chatHistory,
{
_id: String(new Types.ObjectId()),
dataId: nanoid(),
obj: 'Human',
value: val,
status: 'finish'
},
{
_id: String(new Types.ObjectId()),
dataId: nanoid(),
obj: 'AI',
value: '',
status: 'loading'
@@ -552,7 +555,7 @@ const ChatBox = (
{chatHistory.map((item, index) => (
<Flex
position={'relative'}
key={item._id}
key={item.dataId}
flexDirection={'column'}
alignItems={item.obj === 'Human' ? 'flex-end' : 'flex-start'}
py={5}
@@ -583,10 +586,10 @@ const ChatBox = (
_hover={{ color: 'red.600' }}
onClick={() => {
setChatHistory((state) =>
state.filter((chat) => chat._id !== item._id)
state.filter((chat) => chat.dataId !== item.dataId)
);
onDelMessage({
contentId: item._id,
contentId: item.dataId,
index
});
}}
@@ -630,10 +633,10 @@ const ChatBox = (
_hover={{ color: 'red.600' }}
onClick={() => {
setChatHistory((state) =>
state.filter((chat) => chat._id !== item._id)
state.filter((chat) => chat.dataId !== item.dataId)
);
onDelMessage({
contentId: item._id,
contentId: item.dataId,
index
});
}}
@@ -682,7 +685,7 @@ const ChatBox = (
/>
<ResponseTags
chatId={chatId}
contentId={item._id}
contentId={item.dataId}
responseData={item.responseData}
/>
</Card>

View File

@@ -45,9 +45,7 @@ async function init(limit: number, skip: number) {
chatId: { $exists: false }
},
'_id'
)
.limit(limit)
.skip(skip);
).limit(limit);
await Promise.all(
chats.map((chat) =>

View File

@@ -0,0 +1,98 @@
// 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

@@ -408,9 +408,7 @@ async function init(limit: number, skip: number) {
// userId: '63f9a14228d2a688d8dc9e1b'
},
'_id chat'
)
.limit(limit)
.skip(skip);
).limit(limit);
return Promise.all(
apps.map(async (app) => {

View File

@@ -1,35 +1,23 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, Chat } from '@/service/mongo';
import { connectToDatabase, ChatItem } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { chatId, contentId } = req.query as { chatId: string; contentId: string };
if (!chatId || !contentId) {
throw new Error('缺少参数');
}
await connectToDatabase();
// 凭证校验
const { userId } = await authUser({ req, authToken: true });
const chatRecord = await Chat.findOne({ chatId });
if (!chatRecord) {
throw new Error('找不到对话');
}
// 删除一条数据库记录
await Chat.updateOne(
{
await ChatItem.deleteOne({
dataId: contentId,
chatId,
userId
},
{ $pull: { content: { _id: contentId } } }
);
});
jsonRes(res);
} catch (err) {

View File

@@ -1,57 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, Chat } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { Types } from 'mongoose';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
let {
chatId,
contentId,
quoteId,
sourceText = ''
} = req.body as {
chatId: string;
contentId: string;
quoteId: string;
sourceText: string;
};
await connectToDatabase();
const { userId } = await authUser({ req, authToken: true });
if (!contentId || !chatId || !quoteId) {
throw new Error('params is error');
}
await Chat.updateOne(
{
chatId,
userId: new Types.ObjectId(userId),
'content._id': new Types.ObjectId(contentId)
},
{
$set: {
'content.$.rawSearch.$[quoteElem].source': sourceText
}
},
{
arrayFilters: [
{
'quoteElem.id': quoteId
}
]
}
);
jsonRes(res, {
data: ''
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -1,11 +1,10 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, Chat } from '@/service/mongo';
import { Chat, ChatItem } from '@/service/mongo';
import type { InitChatResponse } from '@/api/response/chat';
import { authUser } from '@/service/utils/auth';
import { ChatItemType } from '@/types/chat';
import { authApp } from '@/service/utils/auth';
import mongoose from 'mongoose';
import type { ChatSchema } from '@/types/mongoSchema';
import { getSpecialModule, getChatModelNameList } from '@/components/ChatBox/utils';
import { TaskResponseKeyEnum } from '@/constants/chat';
@@ -27,8 +26,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
});
}
await connectToDatabase();
// 校验使用权限
const app = (
await authApp({
@@ -39,49 +36,42 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
})
).app;
// 历史记录
// get app and history
const { chat, history = [] }: { chat?: ChatSchema; history?: ChatItemType[] } =
await (async () => {
if (chatId) {
// auth chatId
const [chat, history] = await Promise.all([
Chat.findOne({
Chat.findOne(
{
chatId,
userId
}),
Chat.aggregate([
},
'title variables'
),
ChatItem.find(
{
$match: {
chatId,
userId: new mongoose.Types.ObjectId(userId)
}
userId
},
{
$project: {
content: {
$slice: ['$content', -30] // 返回 content 数组的最后 30 个元素
}
}
},
{ $unwind: '$content' },
{
$project: {
_id: '$content._id',
obj: '$content.obj',
value: '$content.value',
[TaskResponseKeyEnum.responseData]: `$content.${TaskResponseKeyEnum.responseData}`
}
}
])
`dataId obj value ${TaskResponseKeyEnum.responseData}`
)
.sort({ _id: -1 })
.limit(30)
]);
if (!chat) {
throw new Error('聊天框不存在');
}
return { history, chat };
history.reverse();
return { app, history, chat };
}
return {};
})();
if (!app) {
throw new Error('Auth App Error');
}
const isOwner = String(app.userId) === userId;
jsonRes<InitChatResponse>(res, {
@@ -108,3 +98,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
});
}
}
export const config = {
api: {
bodyParser: {
sizeLimit: '10mb'
}
}
};

View File

@@ -1,6 +1,6 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, Chat } from '@/service/mongo';
import { connectToDatabase, Chat, ChatItem } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
type Props = {
@@ -17,16 +17,28 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
await connectToDatabase();
if (chatId) {
await Chat.findOneAndRemove({
await Promise.all([
Chat.findOneAndRemove({
chatId,
userId
});
}),
ChatItem.deleteMany({
userId,
chatId
})
]);
}
if (appId) {
await Chat.deleteMany({
await Promise.all([
Chat.deleteMany({
appId,
userId
});
}),
ChatItem.deleteMany({
userId,
appId
})
]);
}
jsonRes(res);

View File

@@ -28,7 +28,7 @@ import { BillSourceEnum } from '@/constants/user';
import { ChatHistoryItemResType } from '@/types/chat';
import { UserModelSchema } from '@/types/mongoSchema';
export type MessageItemType = ChatCompletionRequestMessage & { _id?: string };
export type MessageItemType = ChatCompletionRequestMessage & { dataId?: string };
type FastGptWebChatProps = {
chatId?: string; // undefined: nonuse history, '': new chat, 'xxxxx': use history
appId?: string;
@@ -172,7 +172,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
content: [
prompt,
{
_id: messages[messages.length - 1]._id,
dataId: messages[messages.length - 1].dataId,
obj: ChatRoleEnum.AI,
value: answerText,
responseData

View File

@@ -2,10 +2,9 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authUser } from '@/service/utils/auth';
import { connectToDatabase, Chat } from '@/service/mongo';
import { connectToDatabase, ChatItem } from '@/service/mongo';
import { Types } from 'mongoose';
import type { ChatItemType } from '@/types/chat';
import { TaskResponseKeyEnum } from '@/constants/chat';
export type Props = {
chatId?: string;
@@ -37,30 +36,37 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
export async function getChatHistory({
chatId,
userId,
limit = 20
limit = 30
}: Props & { userId: string }): Promise<Response> {
if (!chatId) {
return { history: [] };
}
const history = await Chat.aggregate([
{ $match: { chatId, userId: new Types.ObjectId(userId) } },
const history = await ChatItem.aggregate([
{
$project: {
content: {
$slice: ['$content', -limit] // 返回 content 数组的最后20个元素
}
$match: {
chatId,
userId: new Types.ObjectId(userId)
}
},
{ $unwind: '$content' },
{
$sort: {
_id: -1
}
},
{
$limit: limit
},
{
$project: {
obj: '$content.obj',
value: '$content.value',
[TaskResponseKeyEnum.responseData]: `$content.responseData`
dataId: 1,
obj: 1,
value: 1
}
}
]);
history.reverse();
return { history };
}

View File

@@ -2,15 +2,13 @@ import React from 'react';
import { NodeProps } from 'reactflow';
import NodeCard from '../modules/NodeCard';
import { FlowModuleItemType } from '@/types/flow';
import Divider from '../modules/Divider';
import Container from '../modules/Container';
import RenderInput from '../render/RenderInput';
const NodeAnswer = ({
data: { moduleId, inputs, outputs, onChangeNode, ...props }
}: NodeProps<FlowModuleItemType>) => {
const NodeAnswer = ({ data }: NodeProps<FlowModuleItemType>) => {
const { moduleId, inputs, outputs, onChangeNode } = data;
return (
<NodeCard minW={'400px'} moduleId={moduleId} {...props}>
<NodeCard minW={'400px'} {...data}>
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'}>
<RenderInput moduleId={moduleId} onChangeNode={onChangeNode} flowInputList={inputs} />
</Container>

View File

@@ -13,11 +13,10 @@ import MyIcon from '@/components/Icon';
import { FlowOutputItemTypeEnum, FlowValueTypeEnum, SpecialInputKeyEnum } from '@/constants/flow';
import SourceHandle from '../render/SourceHandle';
const NodeCQNode = ({
data: { moduleId, inputs, outputs, onChangeNode, ...props }
}: NodeProps<FlowModuleItemType>) => {
const NodeCQNode = ({ data }: NodeProps<FlowModuleItemType>) => {
const { moduleId, inputs, outputs, onChangeNode } = data;
return (
<NodeCard minW={'400px'} moduleId={moduleId} {...props}>
<NodeCard minW={'400px'} {...data}>
<Divider text="Input" />
<Container>
<RenderInput

View File

@@ -13,16 +13,15 @@ import MySlider from '@/components/Slider';
import { Box } from '@chakra-ui/react';
import { formatPrice } from '@/utils/user';
const NodeChat = ({
data: { moduleId, inputs, outputs, onChangeNode, ...props }
}: NodeProps<FlowModuleItemType>) => {
const NodeChat = ({ data }: NodeProps<FlowModuleItemType>) => {
const { moduleId, inputs, outputs, onChangeNode } = data;
const outputsLen = useMemo(
() => outputs.filter((item) => item.type !== FlowOutputItemTypeEnum.hidden).length,
[outputs]
);
return (
<NodeCard minW={'400px'} moduleId={moduleId} {...props}>
<NodeCard minW={'400px'} {...data}>
<Divider text="Input" />
<Container>
<RenderInput

View File

@@ -3,7 +3,7 @@ import { NodeProps } from 'reactflow';
import NodeCard from '../modules/NodeCard';
import { FlowModuleItemType } from '@/types/flow';
const NodeAnswer = ({ data: { ...props } }: NodeProps<FlowModuleItemType>) => {
return <NodeCard {...props}></NodeCard>;
const NodeAnswer = ({ data }: NodeProps<FlowModuleItemType>) => {
return <NodeCard {...data}></NodeCard>;
};
export default React.memo(NodeAnswer);

View File

@@ -15,14 +15,13 @@ import ExtractFieldModal from '../modules/ExtractFieldModal';
import { ContextExtractEnum } from '@/constants/flow/flowField';
import { FlowOutputItemTypeEnum, FlowValueTypeEnum } from '@/constants/flow';
const NodeExtract = ({
data: { inputs, outputs, moduleId, onChangeNode, onDelEdge, ...props }
}: NodeProps<FlowModuleItemType>) => {
const NodeExtract = ({ data }: NodeProps<FlowModuleItemType>) => {
const { inputs, outputs, moduleId, onChangeNode, onDelEdge } = data;
const { t } = useTranslation();
const [editExtractFiled, setEditExtractField] = useState<ContextExtractAgentItemType>();
return (
<NodeCard minW={'400px'} moduleId={moduleId} {...props}>
<NodeCard minW={'400px'} {...data}>
<Divider text="Input" />
<Container>
<RenderInput

View File

@@ -7,22 +7,17 @@ import Container from '../modules/Container';
import RenderInput from '../render/RenderInput';
import RenderOutput from '../render/RenderOutput';
const NodeHistory = ({
data: { inputs, outputs, onChangeNode, ...props }
}: NodeProps<FlowModuleItemType>) => {
const NodeHistory = ({ data }: NodeProps<FlowModuleItemType>) => {
const { inputs, outputs, moduleId, onChangeNode } = data;
return (
<NodeCard minW={'300px'} {...props}>
<NodeCard minW={'300px'} {...data}>
<Divider text="Input" />
<Container>
<RenderInput moduleId={props.moduleId} onChangeNode={onChangeNode} flowInputList={inputs} />
<RenderInput moduleId={moduleId} onChangeNode={onChangeNode} flowInputList={inputs} />
</Container>
<Divider text="Output" />
<Container>
<RenderOutput
onChangeNode={onChangeNode}
moduleId={props.moduleId}
flowOutputList={outputs}
/>
<RenderOutput onChangeNode={onChangeNode} moduleId={moduleId} flowOutputList={outputs} />
</Container>
</NodeCard>
);

View File

@@ -13,11 +13,10 @@ import { FlowInputItemTypeEnum, FlowOutputItemTypeEnum, FlowValueTypeEnum } from
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
const NodeHttp = ({
data: { moduleId, inputs, outputs, onChangeNode, ...props }
}: NodeProps<FlowModuleItemType>) => {
const NodeHttp = ({ data }: NodeProps<FlowModuleItemType>) => {
const { moduleId, inputs, outputs, onChangeNode } = data;
return (
<NodeCard minW={'350px'} moduleId={moduleId} {...props}>
<NodeCard minW={'350px'} {...data}>
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'}>
<RenderInput moduleId={moduleId} onChangeNode={onChangeNode} flowInputList={inputs} />
<Button

View File

@@ -69,11 +69,10 @@ const KBSelect = ({
);
};
const NodeKbSearch = ({
data: { moduleId, inputs, outputs, onChangeNode, ...props }
}: NodeProps<FlowModuleItemType>) => {
const NodeKbSearch = ({ data }: NodeProps<FlowModuleItemType>) => {
const { moduleId, inputs, outputs, onChangeNode } = data;
return (
<NodeCard minW={'400px'} moduleId={moduleId} {...props}>
<NodeCard minW={'400px'} {...data}>
<Divider text="Input" />
<Container>
<RenderInput

View File

@@ -8,11 +8,9 @@ import { SystemInputEnum } from '@/constants/app';
import { FlowValueTypeEnum } from '@/constants/flow';
import SourceHandle from '../render/SourceHandle';
const QuestionInputNode = ({
data: { inputs, outputs, ...props }
}: NodeProps<FlowModuleItemType>) => {
const QuestionInputNode = ({ data }: NodeProps<FlowModuleItemType>) => {
return (
<NodeCard minW={'240px'} {...props}>
<NodeCard minW={'240px'} {...data}>
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'} textAlign={'end'}>
<Box position={'relative'}>

View File

@@ -8,9 +8,9 @@ import Divider from '../modules/Divider';
import Container from '../modules/Container';
import Label from '../modules/Label';
const NodeTFSwitch = ({ data: { inputs, outputs, ...props } }: NodeProps<FlowModuleItemType>) => {
const NodeTFSwitch = ({ data }: NodeProps<FlowModuleItemType>) => {
return (
<NodeCard minW={'220px'} {...props}>
<NodeCard minW={'220px'} {...data}>
<Divider text="输入输出" />
<Container h={'100px'} py={0} px={0} display={'flex'} alignItems={'center'}>
<Box flex={1} pl={'12px'}>

View File

@@ -10,9 +10,8 @@ import MyIcon from '@/components/Icon';
import MyTooltip from '@/components/MyTooltip';
import { welcomeTextTip } from '@/constants/flow/ModuleTemplate';
const NodeUserGuide = ({
data: { inputs, outputs, onChangeNode, ...props }
}: NodeProps<FlowModuleItemType>) => {
const NodeUserGuide = ({ data }: NodeProps<FlowModuleItemType>) => {
const { inputs, moduleId, onChangeNode } = data;
const welcomeText = useMemo(
() => inputs.find((item) => item.key === SystemInputEnum.welcomeText),
[inputs]
@@ -20,7 +19,7 @@ const NodeUserGuide = ({
return (
<>
<NodeCard minW={'300px'} {...props}>
<NodeCard minW={'300px'} {...data}>
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'}>
<>
<Flex mb={1} alignItems={'center'}>
@@ -40,7 +39,7 @@ const NodeUserGuide = ({
placeholder={welcomeTextTip}
onChange={(e) => {
onChangeNode({
moduleId: props.moduleId,
moduleId,
key: SystemInputEnum.welcomeText,
type: 'inputs',
value: {

View File

@@ -22,9 +22,8 @@ export const defaultVariable: VariableItemType = {
enums: [{ value: '' }]
};
const NodeUserGuide = ({
data: { inputs, outputs, onChangeNode, ...props }
}: NodeProps<FlowModuleItemType>) => {
const NodeUserGuide = ({ data }: NodeProps<FlowModuleItemType>) => {
const { inputs, moduleId, onChangeNode } = data;
const variables = useMemo(
() =>
(inputs.find((item) => item.key === SystemInputEnum.variables)
@@ -37,7 +36,7 @@ const NodeUserGuide = ({
const updateVariables = useCallback(
(value: VariableItemType[]) => {
onChangeNode({
moduleId: props.moduleId,
moduleId,
key: SystemInputEnum.variables,
type: 'inputs',
value: {
@@ -46,7 +45,7 @@ const NodeUserGuide = ({
}
});
},
[inputs, onChangeNode, props.moduleId]
[inputs, onChangeNode, moduleId]
);
const onclickSubmit = useCallback(
@@ -59,7 +58,7 @@ const NodeUserGuide = ({
return (
<>
<NodeCard minW={'300px'} {...props}>
<NodeCard minW={'300px'} {...data}>
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'}>
<TableContainer>
<Table>

View File

@@ -6,20 +6,15 @@ import type { FlowModuleItemType } from '@/types/flow';
import MyTooltip from '@/components/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { useTranslation } from 'react-i18next';
import { useCopyData } from '@/utils/tools';
type Props = {
type Props = FlowModuleItemType & {
children?: React.ReactNode | React.ReactNode[] | string;
logo: string;
name: string;
description?: string;
intro: string;
minW?: string | number;
moduleId: string;
onDelNode: FlowModuleItemType['onDelNode'];
onCopyNode: FlowModuleItemType['onCopyNode'];
};
const NodeCard = ({
const NodeCard = (props: Props) => {
const {
children,
logo = '/icon/logo.svg',
name = '未知模块',
@@ -28,7 +23,8 @@ const NodeCard = ({
onCopyNode,
onDelNode,
moduleId
}: Props) => {
} = props;
const { copyData } = useCopyData();
const { t } = useTranslation();
const theme = useTheme();
@@ -39,16 +35,22 @@ const NodeCard = ({
label: t('common.Copy'),
onClick: () => onCopyNode(moduleId)
},
// {
// icon: 'settingLight',
// label: t('app.Copy Module Config'),
// onClick: () => {
// const copyProps = { ...props };
// delete copyProps.children;
// delete copyProps.children;
// console.log(copyProps);
// }
// },
{
icon: 'delete',
label: t('common.Delete'),
onClick: () => onDelNode(moduleId)
},
// {
// icon: 'collectionLight',
// label: t('common.Collect'),
// onClick: () => {}
// },
{
icon: 'back',
label: t('common.Cancel'),

View File

@@ -1,7 +1,7 @@
import React, { useCallback, useRef } from 'react';
import Head from 'next/head';
import { useRouter } from 'next/router';
import { getInitChatSiteInfo, delChatRecordByIndex, putChatHistory } from '@/api/chat';
import { getInitChatSiteInfo, delChatRecordById, putChatHistory } from '@/api/chat';
import {
Box,
Flex,
@@ -128,7 +128,7 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
...state,
history: state.history.filter((_, i) => i !== index)
}));
await delChatRecordByIndex({ chatId, contentId });
await delChatRecordById({ chatId, contentId });
} catch (err) {
console.log(err);
}

View File

@@ -147,10 +147,6 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
onClick={onclickGit}
/>
</Flex>
<Box mt={3} textAlign={'center'} fontSize={'sm'} color={'myGray.600'}>
Git Git 使 Git
</Box>
</>
)}
</form>

View File

@@ -1,7 +1,7 @@
import { Schema, model, models, Model } from 'mongoose';
import { ChatSchema as ChatType } from '@/types/mongoSchema';
import { ChatRoleMap, TaskResponseKeyEnum } from '@/constants/chat';
import { ChatSourceEnum, ChatSourceMap } from '@/constants/chat';
import { ChatSourceMap } from '@/constants/chat';
const ChatSchema = new Schema({
chatId: {
@@ -45,6 +45,10 @@ const ChatSchema = new Schema({
shareId: {
type: String
},
isInit: {
type: Boolean,
default: false
},
content: {
type: [
{

View File

@@ -0,0 +1,72 @@
import { Schema, model, models, Model } from 'mongoose';
import { ChatItemSchema as ChatItemType } from '@/types/mongoSchema';
import { ChatRoleMap, TaskResponseKeyEnum } from '@/constants/chat';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24);
const ChatItemSchema = new Schema({
dataId: {
type: String,
require: true,
default: () => nanoid()
},
chatId: {
type: String,
require: true
},
userId: {
type: Schema.Types.ObjectId,
ref: 'user',
required: true
},
appId: {
type: Schema.Types.ObjectId,
ref: 'model',
required: true
},
time: {
type: Date,
default: () => new Date()
},
obj: {
type: String,
required: true,
enum: Object.keys(ChatRoleMap)
},
value: {
type: String,
default: ''
},
[TaskResponseKeyEnum.responseData]: {
type: [
{
moduleName: String,
price: String,
model: String,
tokens: Number,
question: String,
answer: String,
temperature: Number,
maxToken: Number,
quoteList: Array,
completeMessages: Array,
similarity: Number,
limit: Number,
cqList: Array,
cqResult: String
}
],
default: []
}
});
try {
ChatItemSchema.index({ time: -1 });
ChatItemSchema.index({ userId: 1 });
ChatItemSchema.index({ appId: 1 });
} catch (error) {
console.log(error);
}
export const ChatItem: Model<ChatItemType> =
models['chatItem'] || model('chatItem', ChatItemSchema);

View File

@@ -63,7 +63,6 @@ export const dispatchChatCompletion = async (props: Record<string, any>): Promis
}
const { filterQuoteQA, quotePrompt } = filterQuote({
history,
quoteQA,
model: modelConstantsData
});
@@ -91,6 +90,7 @@ export const dispatchChatCompletion = async (props: Record<string, any>): Promis
maxToken,
filterMessages
});
// console.log(messages);
// FastGpt temperature range: 1~10
temperature = +(modelConstantsData.maxTemperature * (temperature / 10)).toFixed(2);
@@ -182,32 +182,23 @@ export const dispatchChatCompletion = async (props: Record<string, any>): Promis
};
function filterQuote({
history = [],
quoteQA = [],
model
}: {
history: ChatProps['history'];
quoteQA: ChatProps['quoteQA'];
model: ChatModelItemType;
}) {
// concat history quote
const historyQuote =
history[history.length - 1]?.responseData
?.find((item) => item.moduleName === ChatModuleEnum.AIChat)
?.quoteList?.filter((item) => !quoteQA.find((quote) => quote.id === item.id)) || [];
const concatQuote = quoteQA.concat(historyQuote.slice(0, 3));
const sliceResult = modelToolMap.tokenSlice({
model: model.model,
maxToken: model.quoteMaxToken,
messages: concatQuote.map((item, i) => ({
messages: quoteQA.map((item) => ({
obj: ChatRoleEnum.System,
value: item.a ? `{instruction:${item.q},output:${item.a}}` : `{instruction:${item.q}}`
}))
});
// slice filterSearch
const filterQuoteQA = concatQuote.slice(0, sliceResult.length);
const filterQuoteQA = quoteQA.slice(0, sliceResult.length);
const quotePrompt =
filterQuoteQA.length > 0

View File

@@ -113,6 +113,7 @@ async function initPg() {
}
export * from './models/chat';
export * from './models/chatItem';
export * from './models/app';
export * from './models/user';
export * from './models/bill';

View File

@@ -1,5 +1,5 @@
import { ChatItemType } from '@/types/chat';
import { Chat, App } from '@/service/mongo';
import { Chat, App, ChatItem } from '@/service/mongo';
import { ChatSourceEnum } from '@/constants/chat';
type Props = {
@@ -32,26 +32,30 @@ export async function saveChat({
'_id'
);
const promise = [];
const promise: any[] = [
ChatItem.insertMany(
content.map((item) => ({
chatId,
userId,
appId,
...item
}))
)
];
if (chatHistory) {
promise.push(
promise.push([
Chat.updateOne(
{ chatId, userId },
{
$push: {
content: {
$each: content,
$slice: -40
}
},
title: content[0].value.slice(0, 20),
updateTime: new Date()
}
)
);
]);
} else {
promise.push(
...[
Chat.create({
chatId,
userId,
@@ -59,9 +63,9 @@ export async function saveChat({
variables,
title: content[0].value.slice(0, 20),
source,
shareId,
content: content
shareId
})
]
);
}

View File

@@ -6,7 +6,7 @@ import { ClassifyQuestionAgentItemType } from './app';
export type ExportChatType = 'md' | 'pdf' | 'html';
export type ChatItemType = {
_id?: string;
dataId?: string;
obj: `${ChatRoleEnum}`;
value: string;
[TaskResponseKeyEnum.responseData]?: ChatHistoryItemResType[];

View File

@@ -53,9 +53,9 @@ export type FlowModuleTemplateType = {
description?: string;
intro: string;
flowType: `${FlowModuleTypeEnum}`;
showStatus?: boolean;
inputs: FlowInputItemType[];
outputs: FlowOutputItemType[];
showStatus?: boolean;
};
export type FlowModuleItemType = FlowModuleTemplateType & {
moduleId: string;

View File

@@ -92,9 +92,18 @@ export interface ChatSchema {
variables: Record<string, any>;
source: `${ChatSourceEnum}`;
shareId?: string;
isInit: boolean;
content: ChatItemType[];
}
export interface ChatItemSchema extends ChatItemType {
dataId: string;
chatId: string;
userId: string;
appId: string;
time: Date;
}
export type BillListItemType = {
moduleName: string;
amount: number;

View File

@@ -33,7 +33,7 @@ export const gptMessage2ChatType = (messages: MessageItemType[]): ChatItemType[]
};
return messages.map((item) => ({
_id: item._id,
dataId: item.dataId,
obj: roleMap[item.role],
value: item.content || ''
}));

View File

@@ -41,7 +41,7 @@ export const adaptChatItem_openAI = ({
[ChatRoleEnum.System]: ChatCompletionRequestMessageRoleEnum.System
};
return messages.map((item) => ({
...(reserveId && { _id: item._id }),
...(reserveId && { dataId: item.dataId }),
role: map[item.obj] || ChatCompletionRequestMessageRoleEnum.System,
content: item.value || ''
}));

View File

@@ -1,7 +1,3 @@
---
sidebar_position: 2
---
# Config Chat Model
By default, FastGPT is only configured with 3 models of GPT. If you need to integrate other models, you need to do some additional configuration.

View File

@@ -0,0 +1,72 @@
---
sidebar_position: 1
---
# Default Config.json
```json
{
"FeConfig": {
"show_emptyChat": true,
"show_register": false,
"show_appStore": false,
"show_userDetail": false,
"show_git": true,
"systemTitle": "FastGPT",
"authorText": "Made by FastGPT Team.",
"gitLoginKey": "",
"scripts": []
},
"SystemParams": {
"gitLoginSecret": "",
"vectorMaxProcess": 15,
"qaMaxProcess": 15,
"pgIvfflatProbe": 20
},
"plugins": {},
"ChatModels": [
{
"model": "gpt-3.5-turbo",
"name": "GPT35-4k",
"contextMaxToken": 4000,
"quoteMaxToken": 2000,
"maxTemperature": 1.2,
"price": 0,
"defaultSystem": ""
},
{
"model": "gpt-3.5-turbo-16k",
"name": "GPT35-16k",
"contextMaxToken": 16000,
"quoteMaxToken": 8000,
"maxTemperature": 1.2,
"price": 0,
"defaultSystem": ""
},
{
"model": "gpt-4",
"name": "GPT4-8k",
"contextMaxToken": 8000,
"quoteMaxToken": 4000,
"maxTemperature": 1.2,
"price": 0,
"defaultSystem": ""
}
],
"QAModels": [
{
"model": "gpt-3.5-turbo-16k",
"name": "GPT35-16k",
"maxToken": 16000,
"price": 0
}
],
"VectorModels": [
{
"model": "text-embedding-ada-002",
"name": "Embedding-2",
"price": 0
}
]
}
```

View File

@@ -10,6 +10,8 @@ In the development environment, you need to make a copy of `config.json` as `con
This configuration file contains customization of the frontend page, system-level parameters, and AI dialogue models, etc.
**Note: The configuration instructions below are only a partial introduction. You need to mount the entire config.json file and not just a part of it. You can directly modify the provided config.json file based on the instructions below.**
## Brief Explanation of Basic Fields
Here are some basic configuration fields.

View File

@@ -70,7 +70,7 @@ services:
fastgpt:
container_name: fastgpt
# image: c121914yu/fast-gpt:latest # docker hub
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:latest # 阿里云
image: ghcr.io/labring/fastgpt:latest # 阿里云
ports:
- 3000:3000
networks:
@@ -89,7 +89,8 @@ services:
- TOKEN_KEY=any
- ROOT_KEY=root_key
# mongo 配置,不需要改
- MONGODB_URI=mongodb://username:password@mongo:27017/?authSource=admin
- MONGODB_URI=mongodb://username:password@mongo:27017
# - MONGODB_URI=mongodb://username:password@mongo:27017/?authSource=admin
- MONGODB_NAME=fastgpt
# pg配置.
- PG_HOST=pg
@@ -105,42 +106,38 @@ networks:
# host 版本, 不推荐。
version: '3.3'
services:
pg:
image: ankane/pgvector:v0.4.2 # dockerhub
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/pgvector:v0.4.2 # 阿里云
container_name: pg
restart: always
ports: # 生产环境建议不要暴露
pg:
image: ankane/pgvector:v0.4.2 # dockerhub
container_name: pg
restart: always
ports: # 生产环境建议不要暴露
- 5432:5432
environment:
environment:
# 这里的配置只有首次运行生效。修改后,重启镜像是不会生效的。需要把持久化数据删除再重启,才有效果
- POSTGRES_USER=username
- POSTGRES_PASSWORD=password
- POSTGRES_DB=postgres
volumes:
volumes:
- ./pg/data:/var/lib/postgresql/data
mongo:
image: mongo:5.0.18
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/mongo:5.0.18 # 阿里云
container_name: mongo
restart: always
ports: # 生产环境建议不要暴露
mongo:
image: mongo:5.0.18
container_name: mongo
restart: always
ports: # 生产环境建议不要暴露
- 27017:27017
environment:
environment:
# 这里的配置只有首次运行生效。修改后,重启镜像是不会生效的。需要把持久化数据删除再重启,才有效果
- MONGO_INITDB_ROOT_USERNAME=username
- MONGO_INITDB_ROOT_PASSWORD=password
volumes:
volumes:
- ./mongo/data:/data/db
- ./mongo/logs:/var/log/mongodb
fastgpt:
# image: ghcr.io/c121914yu/fastgpt:latest # github
# image: c121914yu/fast-gpt:latest # docker hub
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:latest # 阿里云
network_mode: host
restart: always
container_name: fastgpt
environment:
fastgpt:
image: ghcr.io/c121914yu/fastgpt:latest # github
network_mode: host
restart: always
container_name: fastgpt
environment:
# root 密码,用户名为: root
- DEFAULT_ROOT_PSW=1234
# 中转地址,如果是用官方号,不需要管
@@ -152,7 +149,8 @@ environment:
# root key, 最高权限,可以内部接口互相调用
- ROOT_KEY=root_key
# mongo 配置,不需要改
- MONGODB_URI=mongodb://username:password@0.0.0.0:27017/?authSource=admin
- MONGODB_URI=mongodb://username:password@0.0.0.0:27017
# - MONGODB_URI=mongodb://username:password@0.0.0.0:27017/?authSource=admin
- MONGODB_NAME=fastgpt
# pg 配置
- PG_HOST=0.0.0.0

View File

@@ -0,0 +1,9 @@
# V4.1 版本初始化
新版重新设置了对话存储结构,需要初始化原来的存储内容
## 执行初始化 API
部署新版项目,并发起 3 个 HTTP 请求(记得携带 headers.rootkey这个值是环境变量里的
https://xxxxx/api/admin/initChatItem

View File

@@ -1,7 +1,3 @@
---
sidebar_position: 2
---
# 配置其他对话模型
默认情况下FastGPT 只配置了 GPT 的 3 个模型,如果你需要接入其他模型,需要进行一些额外配置。

View File

@@ -0,0 +1,72 @@
---
sidebar_position: 1
---
# 默认配置文件
```json
{
"FeConfig": {
"show_emptyChat": true,
"show_register": false,
"show_appStore": false,
"show_userDetail": false,
"show_git": true,
"systemTitle": "FastGPT",
"authorText": "Made by FastGPT Team.",
"gitLoginKey": "",
"scripts": []
},
"SystemParams": {
"gitLoginSecret": "",
"vectorMaxProcess": 15,
"qaMaxProcess": 15,
"pgIvfflatProbe": 20
},
"plugins": {},
"ChatModels": [
{
"model": "gpt-3.5-turbo",
"name": "GPT35-4k",
"contextMaxToken": 4000,
"quoteMaxToken": 2000,
"maxTemperature": 1.2,
"price": 0,
"defaultSystem": ""
},
{
"model": "gpt-3.5-turbo-16k",
"name": "GPT35-16k",
"contextMaxToken": 16000,
"quoteMaxToken": 8000,
"maxTemperature": 1.2,
"price": 0,
"defaultSystem": ""
},
{
"model": "gpt-4",
"name": "GPT4-8k",
"contextMaxToken": 8000,
"quoteMaxToken": 4000,
"maxTemperature": 1.2,
"price": 0,
"defaultSystem": ""
}
],
"QAModels": [
{
"model": "gpt-3.5-turbo-16k",
"name": "GPT35-16k",
"maxToken": 16000,
"price": 0
}
],
"VectorModels": [
{
"model": "text-embedding-ada-002",
"name": "Embedding-2",
"price": 0
}
]
}
```

View File

@@ -1,5 +1,5 @@
---
sidebar_position: 1
sidebar_position: 2
---
# 快速介绍
@@ -10,6 +10,8 @@ sidebar_position: 1
这个配置文件中包含了前端页面定制、系统级参数、AI 对话的模型等……
**注意:下面的配置介绍仅是局部介绍,你需要完整挂载整个 config.jso ,不能仅挂载一部分。你可以直接在给的 config.json 基础上根据下面的介绍进行修改。**
## 基础字段粗略说明
这里会介绍一些基础的配置字段。

View File

@@ -37,7 +37,7 @@ docker-compose -v
version: '3.3'
services:
pg:
image: ankane/pgvector:v0.4.2 # git
image: ankane/pgvector:v0.4.2 # docker
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/pgvector:v0.4.2 # 阿里云
container_name: pg
restart: always
@@ -70,7 +70,7 @@ services:
fastgpt:
container_name: fastgpt
# image: c121914yu/fast-gpt:latest # docker hub
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:latest # 阿里云
image: ghcr.io/labring/fastgpt:latest # 阿里云
ports:
- 3000:3000
networks:
@@ -89,7 +89,8 @@ services:
- TOKEN_KEY=any
- ROOT_KEY=root_key
# mongo 配置,不需要改
- MONGODB_URI=mongodb://username:password@mongo:27017/?authSource=admin
- MONGODB_URI=mongodb://username:password@mongo:27017 # 如果这个连不上,尝试下面的
# - MONGODB_URI=mongodb://username:password@mongo:27017/?authSource=admin
- MONGODB_NAME=fastgpt
# pg配置.
- PG_HOST=pg
@@ -136,7 +137,7 @@ volumes:
fastgpt:
# image: ghcr.io/c121914yu/fastgpt:latest # github
# image: c121914yu/fast-gpt:latest # docker hub
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:latest # 阿里云
image: ghcr.io/labring/fastgpt:latest # 阿里云
network_mode: host
restart: always
container_name: fastgpt
@@ -152,7 +153,8 @@ environment:
# root key, 最高权限,可以内部接口互相调用
- ROOT_KEY=root_key
# mongo 配置,不需要改
- MONGODB_URI=mongodb://username:password@0.0.0.0:27017/?authSource=admin
- MONGODB_URI=mongodb://username:password@0.0.0.0:27017
# - MONGODB_URI=mongodb://username:password@0.0.0.0:27017/?authSource=admin
- MONGODB_NAME=fastgpt
# pg 配置
- PG_HOST=0.0.0.0

View File

@@ -0,0 +1,9 @@
# V4.1 版本初始化
新版重新设置了对话存储结构,需要初始化原来的存储内容
## 执行初始化 API
部署新版项目,并发起 3 个 HTTP 请求(记得携带 headers.rootkey这个值是环境变量里的
https://xxxxx/api/admin/initChatItem