v4.6.9-alpha (#918)

Co-authored-by: Mufei <327958099@qq.com>
Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
Archer
2024-03-04 00:05:25 +08:00
committed by GitHub
parent f9f0b4bffd
commit 42a8184ea0
153 changed files with 4906 additions and 4307 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "app",
"version": "4.6.8",
"version": "4.6.9",
"private": false,
"scripts": {
"dev": "next dev",
@@ -33,7 +33,7 @@
"formidable": "^2.1.1",
"framer-motion": "^9.0.6",
"hyperdown": "^2.4.29",
"i18next": "^22.5.1",
"i18next": "23.10.0",
"immer": "^9.0.19",
"js-yaml": "^4.1.0",
"jschardet": "^3.0.0",
@@ -42,13 +42,13 @@
"mermaid": "^10.2.3",
"nanoid": "^4.0.1",
"next": "13.5.2",
"next-i18next": "^13.3.0",
"next-i18next": "15.2.0",
"nprogress": "^0.2.0",
"react": "18.2.0",
"react-day-picker": "^8.7.1",
"react-dom": "18.2.0",
"react-hook-form": "^7.43.1",
"react-i18next": "^12.3.1",
"react-i18next": "13.5.0",
"react-markdown": "^8.0.7",
"react-syntax-highlighter": "^15.5.0",
"reactflow": "^11.7.4",

View File

@@ -1,10 +1,9 @@
### Fast GPT V4.6.8
1. 新增 - 知识库搜索合并模块
2. 新增 - 新的 Http 模块,支持更加灵活的参数传入。同时支持了输入输出自动数据类型转化,例如:接口输出的 JSON 类型会自动转成字符串类型,直接给其他模块使用。此外,还补充了一些例子,可在文档中查看
3. 优化 - 问题优化并入知识库搜索模块无需单独配置。并且问题优化的同时实现了问题扩展丰富搜索的语义。知识库模块会看到有2个参数配置有一个是多余的如果想让它消失可以删除模块重新增加一个
4. 修复 - 语音输入文件无法上传
5. 修复 - 对话框重新生成无法使用。
6. [点击查看高级编排介绍文档](https://doc.fastgpt.in/docs/workflow/intro)
7. [使用文档](https://doc.fastgpt.in/docs/intro/)
8. [点击查看商业版](https://doc.fastgpt.in/docs/commercial/)
1. 新增 - 知识库新增“增强处理”训练模式,可生成更多类型索引
2. 新增 - 完善了HTTP模块的变量提示
3. 新增 - HTTP模块支持OpenAI单接口导入。
4. 优化 - 问题补全。增加英文类型。同时可以设置为单独模块,方便复用
5. [点击查看高级编排介绍文档](https://doc.fastgpt.in/docs/workflow/intro)
6. [使用文档](https://doc.fastgpt.in/docs/intro/)
7. [点击查看商业版](https://doc.fastgpt.in/docs/commercial/)

View File

@@ -268,6 +268,7 @@
"Quote templates": "Quote templates",
"Random": "Random",
"Save and preview": "Save",
"Search team tags": "Search tags",
"Select TTS": "Select TTS",
"Select app from template": "Select from the template",
"Select quote template": "Select quote template",
@@ -278,6 +279,7 @@
"Simple Config Tip": "Only basic functions are included. For complex agent functions, use advanced orchestration.",
"TTS": "Audio Speech",
"TTS Tip": "After this function is enabled, the voice playback function can be used after each conversation. Use of this feature may incur additional charges.",
"Team tags": "Team tags",
"Temperature": "Temperature",
"Welcome Text": "Welcome Text",
"create app": "Create App",
@@ -409,7 +411,7 @@
"Stop Speak": "Stop Speak",
"Type a message": "Input problem",
"Unpin": "Unpin",
"You need to a chat app": "You need to a chat app",
"You need to a chat app": "You don't have apps available",
"error": {
"Chat error": "Chat error",
"Messages empty": "Interface content is empty, maybe the text is too long ~",
@@ -468,7 +470,7 @@
"module similarity": "Similarity",
"module temperature": "Temperature",
"module time": "Running Time",
"module tokens": "Tokens",
"module tokens": "Total Tokens",
"plugin output": "Plugin Output",
"search using reRank": "ReRank",
"text output": "Text Output"
@@ -594,6 +596,7 @@
"file": "File",
"folder": "Folder",
"import": {
"Auto mode Estimated Price Tips": "Enhanced processing calls the file processing model: {{price}} integral /1k Tokens",
"Auto process": "Auto",
"Auto process desc": "Automatically set segmentation and preprocessing rules",
"CSV Import": "CSV QA Import",
@@ -615,9 +618,9 @@
"Data file progress": "Data upload progress",
"Data process params": "Data process params",
"Down load csv template": "Down load csv template",
"Embedding Estimated Price Tips": "Index billing: {{price}}/1k chars",
"Embedding Estimated Price Tips": "Index billing: {{price}}/1k Tokens",
"Estimated Price": "Estimated Price: : {{amount}}{{unit}}",
"Estimated Price Tips": "QA charges\nInput: 1k chars={{charsPointsPrice}} points",
"Estimated Price Tips": "QA charges: {{charsPointsPrice}} points/1k Tokens",
"Estimated points": "About {{points}} points",
"Fetch Error": "Get link failed",
"Fetch Url": "Url",
@@ -638,7 +641,7 @@
"Preview chunks": "Chunks",
"Preview raw text": "Preview file text (max show 10000 words)",
"Process way": "Process way",
"QA Estimated Price Tips": "QA billing: {{price}}/1k characters (including input and output)",
"QA Estimated Price Tips": "QA billing: {{price}}/1k Tokens (including input and output)",
"QA Import": "QA Split",
"QA Import Tip": "According to certain rules, the text is broken into a larger paragraph, and the AI is invoked to generate a question and answer pair for the paragraph.",
"Re Preview": "RePreview",
@@ -737,6 +740,8 @@
},
"training": {
"Agent queue": "QA wait list",
"Auto mode": "Enhancement process",
"Auto mode Tip": "Subindex and call model are used to generate relevant questions and abstracts to increase the semantic richness of data blocks and facilitate retrieval. It consumes more storage space and increases the number of AI calls.",
"Chunk mode": "Chunk split",
"Full": "Expect more than 5 minutes",
"Leisure": "Leisure",
@@ -1217,13 +1222,33 @@
"Sending Code": "Sending"
},
"login": {
"And": "&",
"Email": "Email",
"Forget Password": "Forget Password?",
"Github": "Github",
"Google": "Google",
"Provider error": "Login exception, please try again"
"Password": "Password",
"Password login": "Password login",
"Phone": "Phone Login",
"Phone number": "Phone",
"Policy tip": "By using it, you agree with us",
"Privacy": "Privacy",
"Provider error": "Login exception, please try again",
"Register": "Register",
"Root login": "Log in as the root user",
"Root password placeholder": "root password is the environment variable you set",
"Terms": "Terms",
"Username": "Username",
"Wechat": "Wechat",
"Wx qr login": "Wechat scan code login"
},
"team": {
"Dataset usage": "Dataset usage",
"member": "Member"
"Team Tags Async Success": "Team async success",
"member": "Member",
"tag": {
"Have not opened": "Team chat is not enabled"
}
}
},
"wallet": {

View File

@@ -268,6 +268,7 @@
"Quote templates": "引用内容模板",
"Random": "发散",
"Save and preview": "保存并预览",
"Search team tags": "搜索标签",
"Select TTS": "选择语音播放模式",
"Select app from template": "从模板中选择",
"Select quote template": "选择引用提示模板",
@@ -278,6 +279,7 @@
"Simple Config Tip": "仅包含基础功能,复杂 agent 功能请使用高级编排。",
"TTS": "语音播报",
"TTS Tip": "开启后,每次对话后可使用语音播放功能。使用该功能可能产生额外费用。",
"Team tags": "团队标签",
"Temperature": "温度",
"Welcome Text": "对话开场白",
"create app": "创建属于你的 AI 应用",
@@ -409,7 +411,7 @@
"Stop Speak": "停止录音",
"Type a message": "输入问题",
"Unpin": "取消置顶",
"You need to a chat app": "鉴权失败,暂无权限访问应用",
"You need to a chat app": "你没有可用的应用",
"error": {
"Chat error": "对话出现异常",
"Messages empty": "接口内容为空,可能文本超长了~",
@@ -468,7 +470,7 @@
"module similarity": "相似度",
"module temperature": "温度",
"module time": "运行时长",
"module tokens": "Tokens",
"module tokens": "Tokens",
"plugin output": "插件输出值",
"search using reRank": "结果重排",
"text output": "文本输出"
@@ -596,13 +598,14 @@
"file": "文件",
"folder": "目录",
"import": {
"Auto mode Estimated Price Tips": "增强处理需调用文件处理模型: {{price}}积分/1k Tokens",
"Auto process": "自动",
"Auto process desc": "自动设置分割和预处理规则",
"CSV Import": "CSV 导入",
"CSV Import Tip": "通过批量导入问答对,要求提前整理好数据",
"Chunk Range": "范围: {{min}}~{{max}}",
"Chunk Split": "直接分段",
"Chunk Split Tip": "将文本按一定的规则进行分段处理后,转成可进行语义搜索的格式,适合绝大多数场景。",
"Chunk Split Tip": "将文本按一定的规则进行分段处理后,转成可进行语义搜索的格式,适合绝大多数场景。不需要调用模型额外处理,成本低。",
"Chunk length": "分块总量",
"Csv format error": "csv 文件格式有误,请确保 index 和 content 两列",
"Custom file": "自定义文本",
@@ -617,9 +620,9 @@
"Data file progress": "数据上传进度",
"Data process params": "数据处理参数",
"Down load csv template": "点击下载 CSV 模板",
"Embedding Estimated Price Tips": "索引计费: {{price}}积分/1k字符",
"Embedding Estimated Price Tips": "索引计费: {{price}}积分/1k Tokens",
"Estimated Price": "预估价格: {{amount}}{{unit}}",
"Estimated Price Tips": "QA计费为\n输入: 1k字符 = {{charsPointsPrice}}积分",
"Estimated Price Tips": "QA计费为\n输入: {{charsPointsPrice}}积分/1k Tokens",
"Estimated points": "预估消耗 {{points}} 积分",
"Fetch Error": "获取链接失败",
"Fetch Url": "网络链接",
@@ -640,9 +643,9 @@
"Preview chunks": "分段预览",
"Preview raw text": "预览源文本最多展示10000字",
"Process way": "处理方式",
"QA Estimated Price Tips": "QA计费为: {{price}}积分/1k 字符(包含输入和输出)",
"QA Estimated Price Tips": "QA计费为: {{price}}积分/1k Tokens(包含输入和输出)",
"QA Import": "QA拆分",
"QA Import Tip": "根据一定规则,将文本拆成一段较大的段落,调用 AI 为该段落生成问答对。",
"QA Import Tip": "根据一定规则,将文本拆成一段较大的段落,调用 AI 为该段落生成问答对。有非常高的检索精度,但是会丢失很多内容细节。",
"Re Preview": "重新生成预览",
"Select file": "选择文件",
"Select source": "选择来源",
@@ -739,6 +742,8 @@
},
"training": {
"Agent queue": "QA训练排队",
"Auto mode": "增强处理(实验)",
"Auto mode Tip": "通过子索引以及调用模型生成相关问题与摘要来增加数据块的语义丰富度更利于检索。需要消耗更多的存储空间和增加AI调用次数。",
"Chunk mode": "直接分段",
"Full": "预计5分钟以上",
"Leisure": "空闲",
@@ -1219,13 +1224,33 @@
"Sending Code": "正在发送"
},
"login": {
"And": "和",
"Email": "邮箱",
"Forget Password": "忘记密码?",
"Github": "Github 登录",
"Google": "Google 登录",
"Provider error": "登录异常,请重试"
"Password": "密码",
"Password login": "密码登录",
"Phone": "手机号登录",
"Phone number": "手机号",
"Policy tip": "使用即代表你同意我们的",
"Privacy": "隐私政策",
"Provider error": "登录异常,请重试",
"Register": "注册账号",
"Root login": "使用root用户登录",
"Root password placeholder": "root密码为你设置的环境变量",
"Terms": "服务协议",
"Username": "用户名",
"Wechat": "微信登录",
"Wx qr login": "微信扫码登录"
},
"team": {
"Dataset usage": "知识库容量",
"member": "成员"
"Team Tags Async Success": "同步完成",
"member": "成员",
"tag": {
"Have not opened": "未开通团队聊天功能"
}
}
},
"wallet": {

View File

@@ -13,6 +13,7 @@ import { IMG_BLOCK_KEY } from '@fastgpt/global/core/chat/constants';
import { addDays } from 'date-fns';
import { useRequest } from '@/web/common/hooks/useRequest';
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
enum FileTypeEnum {
@@ -35,8 +36,12 @@ const MessageInput = ({
isChatting,
TextareaDom,
showFileSelector = false,
resetInputVal
}: {
resetInputVal,
shareId,
outLinkUid,
teamId,
teamToken
}: OutLinkChatAuthProps & {
onChange?: (e: string) => void;
onSendMessage: (e: string) => void;
onStop: () => void;
@@ -47,7 +52,6 @@ const MessageInput = ({
}) => {
const [, startSts] = useTransition();
const { shareId } = useRouter().query as { shareId?: string };
const {
isSpeaking,
isTransCription,
@@ -56,7 +60,7 @@ const MessageInput = ({
speakingTimeString,
renderAudioGraph,
stream
} = useSpeech({ shareId });
} = useSpeech({ shareId, outLinkUid, teamId, teamToken });
const { isPc } = useSystemStore();
const canvasRef = useRef<HTMLCanvasElement>(null);
const { t } = useTranslation();
@@ -82,7 +86,10 @@ const MessageInput = ({
maxSize: 1024 * 1024 * 5,
// 30 day expired.
expiredTime: addDays(new Date(), 7),
shareId
shareId,
outLinkUid,
teamId,
teamToken
});
setFileList((state) =>
state.map((item) =>
@@ -320,7 +327,7 @@ ${images.map((img) => JSON.stringify({ src: img.src })).join('\n')}
rows={1}
height={'22px'}
lineHeight={'22px'}
maxHeight={'150px'}
maxHeight={'50vh'}
maxLength={-1}
overflowY={'auto'}
whiteSpace={'pre-wrap'}

View File

@@ -1,4 +1,4 @@
import React, { useMemo, useState } from 'react';
import React, { useMemo } from 'react';
import { ModalBody, Box, useTheme } from '@chakra-ui/react';
import MyModal from '../MyModal';
@@ -10,12 +10,12 @@ import RawSourceBox from '../core/dataset/RawSourceBox';
const QuoteModal = ({
rawSearch = [],
onClose,
isShare,
showDetail,
metadata
}: {
rawSearch: SearchDataResponseItemType[];
onClose: () => void;
isShare: boolean;
showDetail: boolean;
metadata?: {
collectionId: string;
sourceId?: string;
@@ -57,7 +57,7 @@ const QuoteModal = ({
}
>
<ModalBody>
<QuoteList rawSearch={filterResults} isShare={isShare} />
<QuoteList rawSearch={filterResults} showDetail={showDetail} />
</ModalBody>
</MyModal>
</>
@@ -68,10 +68,10 @@ export default QuoteModal;
export const QuoteList = React.memo(function QuoteList({
rawSearch = [],
isShare
showDetail
}: {
rawSearch: SearchDataResponseItemType[];
isShare: boolean;
showDetail: boolean;
}) {
const theme = useTheme();
@@ -88,7 +88,7 @@ export const QuoteList = React.memo(function QuoteList({
_hover={{ '& .hover-data': { display: 'flex' } }}
bg={i % 2 === 0 ? 'white' : 'myWhite.500'}
>
<QuoteItem quoteItem={item} canViewSource={!isShare} linkToDataset={!isShare} />
<QuoteItem quoteItem={item} canViewSource={showDetail} linkToDataset={showDetail} />
</Box>
))}
</>

View File

@@ -1,7 +1,7 @@
import React, { useMemo, useState } from 'react';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
import type { ChatItemType } from '@fastgpt/global/core/chat/type';
import { Flex, BoxProps, useDisclosure, Image, useTheme, Box } from '@chakra-ui/react';
import { Flex, BoxProps, useDisclosure, useTheme, Box } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
@@ -20,10 +20,10 @@ const WholeResponseModal = dynamic(() => import('./WholeResponseModal'), { ssr:
const ResponseTags = ({
responseData = [],
isShare
showDetail
}: {
responseData?: ChatHistoryItemResType[];
isShare: boolean;
showDetail: boolean;
}) => {
const theme = useTheme();
const { isPc } = useSystemStore();
@@ -76,13 +76,13 @@ const ResponseTags = ({
sourceName: item.sourceName,
sourceId: item.sourceId,
icon: getSourceNameIcon({ sourceId: item.sourceId, sourceName: item.sourceName }),
canReadQuote: !isShare || strIsLink(item.sourceId),
canReadQuote: showDetail || strIsLink(item.sourceId),
collectionId: item.collectionId
})),
historyPreview: chatData?.historyPreview,
runningTime: +responseData.reduce((sum, item) => sum + (item.runningTime || 0), 0).toFixed(2)
};
}, [isShare, responseData]);
}, [showDetail, responseData]);
const TagStyles: BoxProps = {
mr: 2,
@@ -134,7 +134,7 @@ const ResponseTags = ({
</Flex>
</>
)}
{!isShare && (
{showDetail && (
<Flex alignItems={'center'} mt={3} flexWrap={'wrap'}>
{quoteList.length > 0 && (
<MyTooltip label="查看引用">
@@ -187,7 +187,7 @@ const ResponseTags = ({
{!!quoteModalData && (
<QuoteModal
{...quoteModalData}
isShare={isShare}
showDetail={showDetail}
onClose={() => setQuoteModalData(undefined)}
/>
)}
@@ -195,7 +195,11 @@ const ResponseTags = ({
<ContextModal context={contextModalData} onClose={() => setContextModalData(undefined)} />
)}
{isOpenWholeModal && (
<WholeResponseModal response={responseData} isShare={isShare} onClose={onCloseWholeModal} />
<WholeResponseModal
response={responseData}
showDetail={showDetail}
onClose={onCloseWholeModal}
/>
)}
</>
);

View File

@@ -51,11 +51,11 @@ function Row({
const WholeResponseModal = ({
response,
isShare,
showDetail,
onClose
}: {
response: ChatHistoryItemResType[];
isShare: boolean;
showDetail: boolean;
onClose: () => void;
}) => {
const { t } = useTranslation();
@@ -78,7 +78,7 @@ const WholeResponseModal = ({
}
>
<Flex h={'100%'} flexDirection={'column'}>
<ResponseBox response={response} isShare={isShare} />
<ResponseBox response={response} showDetail={showDetail} />
</Flex>
</MyModal>
);
@@ -88,10 +88,10 @@ export default WholeResponseModal;
const ResponseBox = React.memo(function ResponseBox({
response,
isShare
showDetail
}: {
response: ChatHistoryItemResType[];
isShare: boolean;
showDetail: boolean;
}) {
const theme = useTheme();
const { t } = useTranslation();
@@ -142,10 +142,7 @@ const ResponseBox = React.memo(function ResponseBox({
value={`${activeModule?.runningTime || 0}s`}
/>
<Row label={t('core.chat.response.module model')} value={activeModule?.model} />
<Row
label={t('support.wallet.usage.Chars length')}
value={`${activeModule?.charsLength}`}
/>
<Row label={t('core.chat.response.module tokens')} value={`${activeModule?.tokens}`} />
<Row label={t('core.chat.response.module query')} value={activeModule?.query} />
<Row
label={t('core.chat.response.context total length')}
@@ -188,7 +185,7 @@ const ResponseBox = React.memo(function ResponseBox({
{activeModule.quoteList && activeModule.quoteList.length > 0 && (
<Row
label={t('core.chat.response.module quoteList')}
rawDom={<QuoteList isShare={isShare} rawSearch={activeModule.quoteList} />}
rawDom={<QuoteList showDetail={showDetail} rawSearch={activeModule.quoteList} />}
/>
)}
</>
@@ -280,7 +277,7 @@ const ResponseBox = React.memo(function ResponseBox({
{activeModule?.pluginDetail && activeModule?.pluginDetail.length > 0 && (
<Row
label={t('core.chat.response.Plugin Resonse Detail')}
rawDom={<ResponseBox response={activeModule.pluginDetail} isShare={isShare} />}
rawDom={<ResponseBox response={activeModule.pluginDetail} showDetail={showDetail} />}
/>
)}
</>

View File

@@ -68,6 +68,7 @@ import type { AppTTSConfigType, VariableItemType } from '@fastgpt/global/core/mo
import MessageInput from './MessageInput';
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import ChatBoxDivider from '../core/chat/Divider';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24);
@@ -106,7 +107,7 @@ const MessageCardStyle: BoxProps = {
maxW: ['calc(100% - 25px)', 'calc(100% - 40px)']
};
type Props = {
type Props = OutLinkChatAuthProps & {
feedbackType?: `${FeedbackTypeEnum}`;
showMarkIcon?: boolean; // admin mark dataset
showVoiceIcon?: boolean;
@@ -120,9 +121,6 @@ type Props = {
// not chat test params
appId?: string;
chatId?: string;
shareId?: string;
shareTeamId?: string;
outLinkUid?: string;
onUpdateVariable?: (e: Record<string, any>) => void;
onStartChat?: (e: StartChatFnProps) => Promise<{
@@ -147,8 +145,9 @@ const ChatBox = (
appId,
chatId,
shareId,
shareTeamId,
outLinkUid,
teamId,
teamToken,
onUpdateVariable,
onStartChat,
onDelMessage
@@ -288,7 +287,10 @@ const ChatBox = (
const result = await postQuestionGuide(
{
messages: adaptChat2GptMessages({ messages: history, reserveId: false }).slice(-6),
shareId
shareId,
outLinkUid,
teamId,
teamToken
},
abortSignal
);
@@ -300,7 +302,7 @@ const ChatBox = (
}
} catch (error) {}
},
[questionGuide, shareId]
[questionGuide, shareId, outLinkUid, teamId, teamToken]
);
/**
@@ -398,22 +400,20 @@ const ChatBox = (
};
})
);
if (!shareTeamId) {
setTimeout(() => {
createQuestionGuide({
history: newChatList.map((item, i) =>
i === newChatList.length - 1
? {
...item,
value: responseText
}
: item
)
});
generatingScroll();
isPc && TextareaDom.current?.focus();
}, 100);
}
setTimeout(() => {
createQuestionGuide({
history: newChatList.map((item, i) =>
i === newChatList.length - 1
? {
...item,
value: responseText
}
: item
)
});
generatingScroll();
isPc && TextareaDom.current?.focus();
}, 100);
} catch (err: any) {
toast({
title: t(getErrText(err, 'core.chat.error.Chat error')),
@@ -622,6 +622,7 @@ const ChatBox = (
{/* control icon */}
<Flex w={'100%'} alignItems={'center'} justifyContent={'flex-end'}>
<ChatControllerComponent
isChatting={isChatting}
chat={item}
onDelete={
onDelMessage
@@ -654,12 +655,17 @@ const ChatBox = (
<ChatAvatar src={appAvatar} type={'AI'} />
{/* control icon */}
<ChatControllerComponent
isChatting={isChatting}
ml={2}
chat={item}
setChatHistory={setChatHistory}
display={index === chatHistory.length - 1 && isChatting ? 'none' : 'flex'}
showVoiceIcon={showVoiceIcon}
ttsConfig={ttsConfig}
shareId={shareId}
outLinkUid={outLinkUid}
teamId={teamId}
teamToken={teamToken}
onDelete={
onDelMessage
? () => {
@@ -829,7 +835,10 @@ const ChatBox = (
isChatting={index === chatHistory.length - 1 && isChatting}
/>
<ResponseTags responseData={item.responseData} isShare={!!shareId} />
<ResponseTags
responseData={item.responseData}
showDetail={!shareId && !teamId}
/>
{/* custom feedback */}
{item.customFeedbacks && item.customFeedbacks.length > 0 && (
@@ -909,6 +918,10 @@ const ChatBox = (
TextareaDom={TextareaDom}
resetInputVal={resetInputVal}
showFileSelector={showFileSelector}
shareId={shareId}
outLinkUid={outLinkUid}
teamId={teamId}
teamToken={teamToken}
/>
)}
{/* user feedback modal */}
@@ -1236,6 +1249,7 @@ function Empty() {
}
const ChatControllerComponent = React.memo(function ChatControllerComponent({
isChatting,
chat,
setChatHistory,
display,
@@ -1249,8 +1263,13 @@ const ChatControllerComponent = React.memo(function ChatControllerComponent({
onAddUserDislike,
onAddUserLike,
ml,
mr
}: {
mr,
shareId,
outLinkUid,
teamId,
teamToken
}: OutLinkChatAuthProps & {
isChatting: boolean;
chat: ChatSiteItemType;
setChatHistory?: React.Dispatch<React.SetStateAction<ChatSiteItemType[]>>;
showVoiceIcon?: boolean;
@@ -1267,7 +1286,11 @@ const ChatControllerComponent = React.memo(function ChatControllerComponent({
const { t } = useTranslation();
const { copyData } = useCopyData();
const { audioLoading, audioPlaying, hasAudio, playAudio, cancelAudio } = useAudioPlay({
ttsConfig
ttsConfig,
shareId,
outLinkUid,
teamId,
teamToken
});
const controlIconStyle = {
w: '14px',
@@ -1296,7 +1319,7 @@ const ChatControllerComponent = React.memo(function ChatControllerComponent({
onClick={() => copyData(chat.value)}
/>
</MyTooltip>
{!!onDelete && (
{!!onDelete && !isChatting && (
<>
{onRetry && (
<MyTooltip label={t('core.chat.retry')}>

View File

@@ -1,103 +0,0 @@
import React, { useEffect, useMemo, useState } from 'react';
import {
Menu,
MenuButton,
MenuList,
MenuItemOption,
MenuOptionGroup,
Flex,
TagLabel,
TagCloseButton,
HStack,
Tag,
Input
} from '@chakra-ui/react';
import type { TeamTagsSchema } from '@fastgpt/global/support/user/team/type';
const TagEdit = ({
defaultValues,
teamsTags,
setSelectedTags
}: {
defaultValues: [];
teamsTags: Array<TeamTagsSchema>;
setSelectedTags: (item: Array<string>) => void;
}) => {
const [teamTagsOptions, setTeamTagsOptions] = useState(teamsTags);
const setSelectTeamsTags = (item: any) => {
setSelectedTags(item);
};
useMemo(() => {
setTeamTagsOptions(teamsTags);
}, [teamsTags]);
return (
<>
<Menu closeOnSelect={false}>
<MenuButton className="menu-btn" maxHeight={'250'} minWidth={'80%'}>
<HStack
style={{
border: 'solid 2px #f3f3f3',
borderRadius: '5px',
padding: '3px',
flexWrap: 'wrap',
minHeight: '40px'
}}
>
{teamsTags.map((item: TeamTagsSchema, index: number) => {
const key: string = item?.key;
if (defaultValues.indexOf(key as never) > -1) {
return (
<Tag
key={index}
size={'md'}
colorScheme="red"
// maxWidth={"100px"}
borderRadius="full"
>
<TagLabel> {item.label}</TagLabel>
<TagCloseButton />
</Tag>
);
}
})}
</HStack>
</MenuButton>
<MenuList style={{ height: '300px', overflow: 'scroll' }}>
<Input
style={{ border: 'none', borderBottom: 'solid 1px #f6f6f6' }}
placeholder="pleace "
onChange={(e: any) => {
// 对用户输入的搜索文本进行小写转换,以实现不区分大小写的搜索
const searchLower: string = e?.nativeEvent?.data || '';
// 使用filter方法来过滤列表只返回包含搜索文本的项
const resultList = teamsTags.filter((item) => {
const searchValue = item.label || '';
// 对列表中的每一项也进行小写转换
return searchValue.includes(searchLower);
});
!searchLower ? setTeamTagsOptions(teamsTags) : setTeamTagsOptions(resultList);
}}
/>
<MenuOptionGroup
defaultValue={defaultValues}
type="checkbox"
style={{ height: '300px', overflow: 'scroll' }}
onChange={(e) => {
setSelectTeamsTags(e);
}}
>
{teamTagsOptions.map((item, index) => {
return (
<MenuItemOption key={index} value={item.key}>
{item?.label}
</MenuItemOption>
);
})}
</MenuOptionGroup>
</MenuList>
</Menu>
</>
);
};
export default TagEdit;

View File

@@ -8,7 +8,7 @@ import { DatasetTypeMap } from '@fastgpt/global/core/dataset/constants';
const DatasetTypeTag = ({ type, ...props }: { type: `${DatasetTypeEnum}` } & FlexProps) => {
const { t } = useTranslation();
const item = DatasetTypeMap[type];
const item = DatasetTypeMap[type] || DatasetTypeMap['dataset'];
return (
<Flex

View File

@@ -301,10 +301,22 @@ function RenderHttpProps({
headers &&
jsonBody &&
{
[TabEnum.params]: <RenderForm moduleId={moduleId} input={params} variables={variables} />,
[TabEnum.params]: (
<RenderForm
moduleId={moduleId}
input={params}
variables={variables}
tabType={TabEnum.params}
/>
),
[TabEnum.body]: <RenderJson moduleId={moduleId} variables={variables} input={jsonBody} />,
[TabEnum.headers]: (
<RenderForm moduleId={moduleId} input={headers} variables={variables} />
<RenderForm
moduleId={moduleId}
input={headers}
variables={variables}
tabType={TabEnum.headers}
/>
)
}[selectedTab]}
</Box>
@@ -313,11 +325,13 @@ function RenderHttpProps({
const RenderForm = ({
moduleId,
input,
variables
variables,
tabType
}: {
moduleId: string;
input: FlowNodeInputItemType;
variables: EditorVariablePickerType[];
tabType?: TabEnum;
}) => {
const { t } = useTranslation();
const { toast } = useToast();
@@ -327,11 +341,52 @@ const RenderForm = ({
const [shouldUpdateNode, setShouldUpdateNode] = useState(false);
const leftVariables = useMemo(() => {
return variables.filter((variable) => {
const HttpHeaders = [
{ key: 'A-IM', label: 'A-IM' },
{ key: 'Accept', label: 'Accept' },
{ key: 'Accept-Charset', label: 'Accept-Charset' },
{ key: 'Accept-Encoding', label: 'Accept-Encoding' },
{ key: 'Accept-Language', label: 'Accept-Language' },
{ key: 'Accept-Datetime', label: 'Accept-Datetime' },
{ key: 'Access-Control-Request-Method', label: 'Access-Control-Request-Method' },
{ key: 'Access-Control-Request-Headers', label: 'Access-Control-Request-Headers' },
{ key: 'Authorization', label: 'Authorization' },
{ key: 'Cache-Control', label: 'Cache-Control' },
{ key: 'Connection', label: 'Connection' },
{ key: 'Content-Length', label: 'Content-Length' },
{ key: 'Content-Type', label: 'Content-Type' },
{ key: 'Cookie', label: 'Cookie' },
{ key: 'Date', label: 'Date' },
{ key: 'Expect', label: 'Expect' },
{ key: 'Forwarded', label: 'Forwarded' },
{ key: 'From', label: 'From' },
{ key: 'Host', label: 'Host' },
{ key: 'If-Match', label: 'If-Match' },
{ key: 'If-Modified-Since', label: 'If-Modified-Since' },
{ key: 'If-None-Match', label: 'If-None-Match' },
{ key: 'If-Range', label: 'If-Range' },
{ key: 'If-Unmodified-Since', label: 'If-Unmodified-Since' },
{ key: 'Max-Forwards', label: 'Max-Forwards' },
{ key: 'Origin', label: 'Origin' },
{ key: 'Pragma', label: 'Pragma' },
{ key: 'Proxy-Authorization', label: 'Proxy-Authorization' },
{ key: 'Range', label: 'Range' },
{ key: 'Referer', label: 'Referer' },
{ key: 'TE', label: 'TE' },
{ key: 'User-Agent', label: 'User-Agent' },
{ key: 'Upgrade', label: 'Upgrade' },
{ key: 'Via', label: 'Via' },
{ key: 'Warning', label: 'Warning' },
{ key: 'Dnt', label: 'Dnt' },
{ key: 'X-Requested-With', label: 'X-Requested-With' },
{ key: 'X-CSRF-Token', label: 'X-CSRF-Token' }
];
return (tabType === TabEnum.headers ? HttpHeaders : variables).filter((variable) => {
const existVariables = list.map((item) => item.key);
return !existVariables.includes(variable.key);
});
}, [list, variables]);
}, [list, tabType, variables]);
useEffect(() => {
setList(input.value || []);
@@ -378,16 +433,23 @@ const RenderForm = ({
};
const handleAddNewProps = (key: string, value: string = '') => {
const checkExist = list.find((item) => item.key === key);
if (checkExist) {
return toast({
status: 'warning',
title: t('core.module.http.Key already exists')
});
}
if (!key) return;
setList((prevList) => {
if (!key) {
return prevList;
}
const checkExist = prevList.find((item) => item.key === key);
if (checkExist) {
setUpdateTrigger((prev) => !prev);
toast({
status: 'warning',
title: t('core.module.http.Key already exists')
});
return prevList;
}
return [...prevList, { key, type: 'string', value }];
});
setList((prevList) => [...prevList, { key, type: 'string', value }]);
setShouldUpdateNode(true);
};
@@ -406,7 +468,7 @@ const RenderForm = ({
<Td p={0} w={'150px'}>
<HttpInput
hasVariablePlugin={false}
hasDropDownPlugin={true}
hasDropDownPlugin={tabType === TabEnum.headers}
setDropdownValue={(value) => {
handleKeyChange(index, value);
setUpdateTrigger((prev) => !prev);
@@ -450,16 +512,19 @@ const RenderForm = ({
<Tr>
<Td p={0} w={'150px'}>
<HttpInput
hasDropDownPlugin={true}
hasVariablePlugin={false}
hasDropDownPlugin={tabType === TabEnum.headers}
setDropdownValue={(val) => {
handleAddNewProps(val);
setUpdateTrigger((prev) => !prev);
}}
placeholder={t('core.module.http.Add props')}
value={''}
h={40}
variables={leftVariables}
updateTrigger={updateTrigger}
onBlur={(val) => {
handleAddNewProps(val);
setUpdateTrigger((prev) => !prev);
}}
/>
</Td>
@@ -490,7 +555,7 @@ const RenderJson = ({
<Box mt={1}>
<JSONEditor
bg={'myGray.50'}
height={200}
defaultHeight={200}
resize
value={input.value}
placeholder={t('core.module.template.http body placeholder')}

View File

@@ -9,9 +9,7 @@ import {
putSwitchTeam,
putUpdateMember,
delRemoveMember,
delLeaveTeam,
getTeamsTags,
insertTeamsTags
delLeaveTeam
} from '@/web/support/user/team/api';
import {
Box,
@@ -49,7 +47,7 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
const EditModal = dynamic(() => import('./EditModal'));
const InviteModal = dynamic(() => import('./InviteModal'));
const TeamTagsAsync = dynamic(() => import('../TeamTagsAsync'));
const TeamTagModal = dynamic(() => import('../TeamTagModal'));
const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
const { t } = useTranslation();
@@ -57,7 +55,6 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
const { toast } = useToast();
const { teamPlanStatus } = useUserStore();
const { feConfigs } = useSystemStore();
const [teamsTags, setTeamTags] = useState<any>();
const { ConfirmModal: ConfirmRemoveMemberModal, openConfirm: openRemoveMember } = useConfirm();
const { ConfirmModal: ConfirmLeaveTeamModal, openConfirm: openLeaveConfirm } = useConfirm({
@@ -87,8 +84,6 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
mutationFn: async (teamId: string) => {
const token = await putSwitchTeam(teamId);
token && setToken(token);
// get team tags
await getTeamsTags(teamId);
return initUserInfo();
},
errorToast: t('user.team.Switch Team Failed')
@@ -99,11 +94,6 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
['getMembers', userInfo?.team?.teamId],
() => {
if (!userInfo?.team?.teamId) return [];
// get team tags
getTeamsTags(userInfo.team.teamId).then((res: any) => {
setTeamTags(res);
});
return getTeamMembers(userInfo.team.teamId);
}
);
@@ -217,17 +207,6 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
: {})}
>
{team.teamName}
{/* {userInfo?.team?.teamId === team.teamId && (
<HStack spacing={1}>
{teamsTags.slice(0, 3).map((item: any, index) => {
return (
<Tag key={index} size={'sm'} variant="outline" colorScheme="blue">
{item.label}
</Tag>
);
})}
</HStack>
)} */}
</Box>
{userInfo?.team?.teamId === team.teamId ? (
<MyIcon name={'common/tickFill'} w={'16px'} color={'primary.500'} />
@@ -290,31 +269,32 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
<Box ml={2} bg={'myGray.100'} borderRadius={'20px'} px={3} fontSize={'xs'}>
{members.length}
</Box>
{userInfo.team.role === TeamMemberRoleEnum.owner &&
teamPlanStatus?.standardConstants &&
teamPlanStatus.standardConstants.maxTeamMember > members.length && (
<Button
variant={'whitePrimary'}
size="sm"
borderRadius={'md'}
ml={3}
leftIcon={
<MyIcon name={'common/inviteLight'} w={'14px'} color={'primary.500'} />
{userInfo.team.role === TeamMemberRoleEnum.owner && (
<Button
variant={'whitePrimary'}
size="sm"
borderRadius={'md'}
ml={3}
leftIcon={<MyIcon name={'common/inviteLight'} w={'14px'} color={'primary.500'} />}
onClick={() => {
if (
teamPlanStatus?.standardConstants?.maxTeamMember &&
teamPlanStatus.standardConstants.maxTeamMember <= members.length
) {
toast({
status: 'warning',
title: t('user.team.Over Max Member Tip', {
max: teamPlanStatus.standardConstants.maxTeamMember
})
});
} else {
onOpenInvite();
}
onClick={() => {
if (userInfo.team.maxSize <= members.length) {
toast({
status: 'warning',
title: t('user.team.Over Max Member Tip', { max: userInfo.team.maxSize })
});
} else {
onOpenInvite();
}
}}
>
{t('user.team.Invite Member')}
</Button>
)}
}}
>
{t('user.team.Invite Member')}
</Button>
)}
{userInfo.team.role === TeamMemberRoleEnum.owner && feConfigs?.show_team_chat && (
<Button
variant={'whitePrimary'}
@@ -323,14 +303,7 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
ml={3}
leftIcon={<DragHandleIcon w={'14px'} color={'primary.500'} />}
onClick={() => {
if (userInfo.team.maxSize <= members.length) {
toast({
status: 'warning',
title: t('user.team.Team Tags Async', { max: userInfo.team.maxSize })
});
} else {
onOpenTeamTagsAsync();
}
onOpenTeamTagsAsync();
}}
>
{t('user.team.Team Tags Async')}
@@ -492,13 +465,7 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
onSuccess={refetchMembers}
/>
)}
{isOpenTeamTagsAsync && (
<TeamTagsAsync
teamInfo={teamsTags?.tagsUrl}
teamsTags={teamsTags?.list || []}
onClose={onCloseTeamTagsAsync}
/>
)}
{isOpenTeamTagsAsync && <TeamTagModal onClose={onCloseTeamTagsAsync} />}
<ConfirmRemoveMemberModal />
<ConfirmLeaveTeamModal />
</>

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from 'react';
import React from 'react';
import MyModal from '@/components/MyModal';
import {
Box,
@@ -11,61 +11,74 @@ import {
HStack,
Avatar
} from '@chakra-ui/react';
import { AttachmentIcon, CopyIcon, DragHandleIcon } from '@chakra-ui/icons';
import { putUpdateTeamTags, updateTags } from '@/web/support/user/team/api';
import { useForm } from 'react-hook-form';
import { putUpdateTeam } from '@/web/support/user/team/api';
import { useFieldArray, useForm } from 'react-hook-form';
import { useTranslation } from 'next-i18next';
import type { TeamTagsSchema } from '@fastgpt/global/support/user/team/type';
import type { TeamTagItemType } from '@fastgpt/global/support/user/team/type';
import { useRequest } from '@/web/common/hooks/useRequest';
import { RepeatIcon } from '@chakra-ui/icons';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useCopyData } from '@/web/common/hooks/useCopyData';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useQuery } from '@tanstack/react-query';
import { getTeamsTags, loadTeamTagsByDomain } from '@/web/support/user/team/api';
const TeamTagsAsync = ({
teamsTags,
teamInfo,
onClose
}: {
teamsTags: Array<TeamTagsSchema>;
teamInfo: any;
onClose: () => void;
}) => {
type FormType = {
teamDomain: string;
tags: TeamTagItemType[];
};
const TeamTagsAsync = ({ onClose }: { onClose: () => void }) => {
const { t } = useTranslation();
const { toast } = useToast();
const [_teamsTags, setTeamTags] = useState<Array<TeamTagsSchema>>(teamsTags);
const { register, setValue, getValues, handleSubmit } = useForm<any>({
defaultValues: { ...teamInfo }
});
const { userInfo, initUserInfo } = useUserStore();
const { copyData } = useCopyData();
const teamInfo = userInfo?.team;
if (!teamInfo) {
onClose();
return null;
}
const { register, control, handleSubmit } = useForm<FormType>({
defaultValues: {
teamDomain: teamInfo.teamDomain,
tags: []
}
});
const { fields: teamTags, replace: replaceTeamTags } = useFieldArray({
control,
name: 'tags'
});
const baseUrl = global.feConfigs?.customSharePageDomain || location?.origin;
const linkUrl = `${baseUrl}/chat/team?shareTeamId=${teamInfo?._id}${
getValues('showHistory') ? '' : '&showHistory=0'
}`;
const linkUrl = `${baseUrl}/chat/team?teamId=${teamInfo.teamId}&teamToken=`;
// tags Async
const { mutate: onclickAsync, isLoading: creating } = useRequest({
mutationFn: async (data: any) => {
return putUpdateTeamTags({ tagsUrl: data.tagsUrl, teamId: teamInfo?._id });
const { mutate: onclickUpdate, isLoading: isUpdating } = useRequest({
mutationFn: async (data: FormType) => {
return putUpdateTeam({ teamDomain: data.teamDomain, teamId: teamInfo?.teamId });
},
onSuccess(id: string) {
onSuccess() {
initUserInfo();
onClose();
},
successToast: t('user.team.Team Tags Async Success'),
errorToast: t('common.Create Failed')
});
const asyncTags = async () => {
console.log('getValues', getValues());
const res: Array<TeamTagsSchema> = await updateTags(teamInfo?._id, getValues().tagsUrl);
setTeamTags(res);
toast({ status: 'success', title: '团队标签同步成功' });
};
useEffect(() => {
console.log('teamInfo', teamInfo);
}, []);
const { mutate: onclickTagAsync, isLoading: isSyncing } = useRequest({
mutationFn: (data: FormType) => loadTeamTagsByDomain(data.teamDomain),
onSuccess(res) {
replaceTeamTags(res);
},
successToast: t('support.user.team.Team Tags Async Success')
});
useQuery(['getTeamsTags'], getTeamsTags, {
onSuccess: (data) => {
replaceTeamTags(data);
}
});
// 获取
return (
<>
<MyModal
@@ -80,7 +93,7 @@ const TeamTagsAsync = ({
overflow={'hidden'}
title={
<Box>
<Box>{teamInfo?.name}</Box>
<Box>{teamInfo?.teamName}</Box>
<Box color={'myGray.500'} fontSize={'xs'} fontWeight={'normal'}>
{'填写标签同步链接,点击同步按钮即可同步'}
</Box>
@@ -98,8 +111,8 @@ const TeamTagsAsync = ({
autoFocus
bg={'myWhite.600'}
placeholder="请输入同步标签"
{...register('tagsUrl', {
required: t('core.app.error.App name can not be empty')
{...register('teamDomain', {
required: true
})}
/>
</Flex>
@@ -146,7 +159,7 @@ const TeamTagsAsync = ({
}}
spacing={1}
>
{_teamsTags.map((item, index) => {
{teamTags.map((item, index) => {
return (
<Tag key={index} mt={2} size={'md'} colorScheme="red" borderRadius="full">
<Avatar
@@ -161,7 +174,13 @@ const TeamTagsAsync = ({
);
})}
</HStack>
<Button ml={4} size="md" leftIcon={<RepeatIcon />} onClick={asyncTags}>
<Button
isLoading={isSyncing}
ml={4}
size="md"
leftIcon={<RepeatIcon />}
onClick={handleSubmit((data) => onclickTagAsync(data))}
>
</Button>
</Flex>
@@ -170,7 +189,7 @@ const TeamTagsAsync = ({
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
{t('common.Close')}
</Button>
<Button isLoading={creating} onClick={handleSubmit((data) => onclickAsync(data))}>
<Button isLoading={isUpdating} onClick={handleSubmit((data) => onclickUpdate(data))}>
{t('user.team.Tags Async')}
</Button>
</ModalFooter>

View File

@@ -1,7 +1,8 @@
export enum PageTypeEnum {
login = 'login',
export enum LoginPageTypeEnum {
passwordLogin = 'passwordLogin',
register = 'register',
forgetPassword = 'forgetPassword'
forgetPassword = 'forgetPassword',
wechat = 'wechat'
}
export enum PromotionEnum {

View File

@@ -1,6 +1,6 @@
import { ChatMessageItemType } from '@fastgpt/global/core/ai/type.d';
import type { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat.d';
export type CreateQuestionGuideParams = {
export type CreateQuestionGuideParams = OutLinkChatAuthProps & {
messages: ChatMessageItemType[];
shareId?: string;
};

View File

@@ -1,6 +1,7 @@
import type { AppTTSConfigType } from '@fastgpt/global/core/module/type.d';
import { ModuleItemType } from '../module/type';
import { AdminFbkType, ChatItemType, moduleDispatchResType } from '@fastgpt/global/core/chat/type';
import type { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat.d';
export type GetChatSpeechProps = {
ttsConfig: AppTTSConfigType;
@@ -14,16 +15,16 @@ export type InitChatProps = {
chatId?: string;
loadCustomFeedbacks?: boolean;
};
/* ---------- chat ----------- */
export type chatByTeamProps = {
teamId?: string;
appId?: string;
outLinkUid?: string;
};
export type InitOutLinkChatProps = {
chatId?: string;
shareId?: string;
outLinkUid?: string;
shareId: string;
outLinkUid: string;
};
export type InitTeamChatProps = {
teamId: string;
appId: string;
chatId?: string;
teamToken: string;
};
export type InitChatResponse = {
chatId?: string;
@@ -43,42 +44,30 @@ export type InitChatResponse = {
};
/* ---------- history ----------- */
export type getHistoriesProps = {
export type GetHistoriesProps = OutLinkChatAuthProps & {
appId?: string;
authToken?: string;
// share chat
shareId?: string;
outLinkUid?: string; // authToken/uid
};
export type UpdateHistoryProps = {
export type UpdateHistoryProps = OutLinkChatAuthProps & {
appId: string;
chatId: string;
customTitle?: string;
top?: boolean;
shareId?: string;
outLinkUid?: string;
};
export type DelHistoryProps = {
export type DelHistoryProps = OutLinkChatAuthProps & {
appId: string;
chatId: string;
shareId?: string;
outLinkUid?: string;
};
export type ClearHistoriesProps = {
export type ClearHistoriesProps = OutLinkChatAuthProps & {
appId?: string;
shareId?: string;
outLinkUid?: string;
};
/* -------- chat item ---------- */
export type DeleteChatItemProps = {
export type DeleteChatItemProps = OutLinkChatAuthProps & {
appId: string;
chatId: string;
contentId?: string;
shareId?: string;
outLinkUid?: string;
};
export type AdminUpdateFeedbackParams = AdminFbkType & {

View File

@@ -1,20 +1,26 @@
export const Prompt_AgentQA = {
description: `<context></context> 标记中是一段文本,学习和分析它,并整理学习成果:
description: `<Context></Context> 标记中是一段文本,学习和分析它,并整理学习成果:
- 提出问题并给出每个问题的答案。
- 答案需详细完整,给出相关原文描述。
- 答案可以包含普通文字、链接、代码、表格、公示、媒体链接等 markdown 元素。
- 答案需详细完整,尽可能保留原文描述。
- 答案可以包含普通文字、链接、代码、表格、公示、媒体链接等 Markdown 元素。
- 最多提出 30 个问题。
`,
fixedText: `最后,你需要按下面的格式返回多个问题和答案:
fixedText: `请按以下格式整理学习成果:
<Context>
文本
</Context>
Q1: 问题。
A1: 答案。
Q2:
A2:
……
<context>
------
我们开始吧!
<Context>
{{text}}
<context/>
<Context/>
`
};

View File

@@ -56,8 +56,8 @@ const UpdatePswModal = ({ onClose }: { onClose: () => void }) => {
{...register('newPsw', {
required: true,
maxLength: {
value: 20,
message: '密码最少 4 位最多 20 位'
value: 60,
message: '密码最少 4 位最多 60 位'
}
})}
></Input>
@@ -70,8 +70,8 @@ const UpdatePswModal = ({ onClose }: { onClose: () => void }) => {
{...register('confirmPsw', {
required: true,
maxLength: {
value: 20,
message: '密码最少 4 位最多 20 位'
value: 60,
message: '密码最少 4 位最多 60 位'
}
})}
></Input>

View File

@@ -25,8 +25,9 @@ const UsageDetail = ({ usage, onClose }: { usage: UsageItemType; onClose: () =>
[usage.list]
);
const { hasModel, hasCharsLen, hasDuration } = useMemo(() => {
const { hasModel, hasToken, hasCharsLen, hasDuration } = useMemo(() => {
let hasModel = false;
let hasToken = false;
let hasCharsLen = false;
let hasDuration = false;
let hasDataLen = false;
@@ -36,6 +37,9 @@ const UsageDetail = ({ usage, onClose }: { usage: UsageItemType; onClose: () =>
hasModel = true;
}
if (typeof item.tokens === 'number') {
hasToken = true;
}
if (typeof item.charsLength === 'number') {
hasCharsLen = true;
}
@@ -46,6 +50,7 @@ const UsageDetail = ({ usage, onClose }: { usage: UsageItemType; onClose: () =>
return {
hasModel,
hasToken,
hasCharsLen,
hasDuration,
hasDataLen
@@ -91,9 +96,9 @@ const UsageDetail = ({ usage, onClose }: { usage: UsageItemType; onClose: () =>
<Tr>
<Th>{t('support.wallet.usage.Module name')}</Th>
{hasModel && <Th>{t('support.wallet.usage.Ai model')}</Th>}
{hasToken && <Th>{t('support.wallet.usage.Token Length')}</Th>}
{hasCharsLen && <Th>{t('support.wallet.usage.Text Length')}</Th>}
{hasDuration && <Th>{t('support.wallet.usage.Duration')}</Th>}
<Th>{t('support.wallet.usage.Total points')}</Th>
</Tr>
</Thead>
@@ -102,6 +107,7 @@ const UsageDetail = ({ usage, onClose }: { usage: UsageItemType; onClose: () =>
<Tr key={i}>
<Td>{t(item.moduleName)}</Td>
{hasModel && <Td>{item.model ?? '-'}</Td>}
{hasToken && <Td>{item.tokens ?? '-'}</Td>}
{hasCharsLen && <Td>{item.charsLength ?? '-'}</Td>}
{hasDuration && <Td>{item.duration ?? '-'}</Td>}
<Td>{formatNumber(item.amount)}</Td>

View File

@@ -4,6 +4,11 @@ import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoUsage } from '@fastgpt/service/support/wallet/usage/schema';
import { connectionMongo } from '@fastgpt/service/common/mongo';
import { checkFiles } from '../timerTask/dataset/checkInValidDatasetFiles';
import { addHours } from 'date-fns';
import { checkInvalid as checkInvalidImg } from '../timerTask/dataset/checkInvalidDatasetImage';
import { checkInvalidCollection } from '../timerTask/dataset/checkInvalidMongoCollection';
import { checkInvalidVector } from '../timerTask/dataset/checkInvalidVector';
/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
@@ -21,6 +26,21 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
});
}
(async () => {
try {
console.log('执行脏数据清理任务');
const end = addHours(new Date(), -1);
const start = addHours(new Date(), -360 * 24);
await checkFiles(start, end);
await checkInvalidImg(start, end);
await checkInvalidCollection(start, end);
await checkInvalidVector(start, end);
console.log('执行脏数据清理任务完毕');
} catch (error) {
console.log('执行脏数据清理任务出错了');
}
})();
jsonRes(res, {
message: 'success'
});

View File

@@ -1,16 +1,16 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authCertOrShareId } from '@fastgpt/service/support/permission/auth/common';
import { authChatCert } from '@/service/support/permission/auth/chat';
import { uploadMongoImg } from '@fastgpt/service/common/file/image/controller';
import { UploadImgProps } from '@fastgpt/global/common/file/api';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { shareId, ...body } = req.body as UploadImgProps;
const body = req.body as UploadImgProps;
const { teamId } = await authCertOrShareId({ req, shareId, authToken: true });
const { teamId } = await authChatCert({ req, authToken: true });
const data = await uploadMongoImg({
teamId,

View File

@@ -4,8 +4,6 @@ import { jsonRes } from '@fastgpt/service/common/response';
import { readFileSync, readdirSync } from 'fs';
import type { InitDateResponse } from '@/global/common/api/systemRes';
import type { FastGPTConfigFileType } from '@fastgpt/global/common/system/types/index.d';
import { getTikTokenEnc } from '@fastgpt/global/common/string/tiktoken';
import { initHttpAgent } from '@fastgpt/service/common/middle/httpAgent';
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
import { getFastGPTConfigFromDB } from '@fastgpt/service/common/system/config/controller';
import { connectToDatabase } from '@/service/mongo';
@@ -63,7 +61,6 @@ export async function getInitConfig() {
await connectToDatabase();
await Promise.all([
initGlobal(),
initSystemConfig(),
// getSimpleModeTemplates(),
getSystemVersion(),
@@ -84,18 +81,6 @@ export async function getInitConfig() {
}
}
export function initGlobal() {
if (global.communityPlugins) return;
global.communityPlugins = [];
global.simpleModeTemplates = [];
global.qaQueueLen = global.qaQueueLen ?? 0;
global.vectorQueueLen = global.vectorQueueLen ?? 0;
// init tikToken
getTikTokenEnc();
initHttpAgent();
}
export async function initSystemConfig() {
// load config
const [dbConfig, fileConfig] = await Promise.all([
@@ -125,7 +110,6 @@ export async function initSystemConfig() {
// set config
initFastGPTConfig(config);
global.systemEnv = config.systemEnv;
console.log({
feConfigs: global.feConfigs,

View File

@@ -1,14 +1,14 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { startQueue } from '@/service/utils/tools';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { startTrainingQueue } from '@/service/core/dataset/training/utils';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
await authCert({ req, authToken: true });
startQueue();
startTrainingQueue();
} catch (error) {}
jsonRes(res);
}

View File

@@ -4,22 +4,21 @@ import { connectToDatabase } from '@/service/mongo';
import type { CreateQuestionGuideParams } from '@/global/core/ai/api.d';
import { pushQuestionGuideUsage } from '@/service/support/wallet/usage/push';
import { createQuestionGuide } from '@fastgpt/service/core/ai/functions/createQuestionGuide';
import { authCertOrShareId } from '@fastgpt/service/support/permission/auth/common';
import { authChatCert } from '@/service/support/permission/auth/chat';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { messages, shareId } = req.body as CreateQuestionGuideParams;
const { messages } = req.body as CreateQuestionGuideParams;
const { tmbId, teamId } = await authCertOrShareId({
const { tmbId, teamId } = await authChatCert({
req,
authToken: true,
shareId
authToken: true
});
const qgModel = global.llmModels[0];
const { result, charsLength } = await createQuestionGuide({
const { result, tokens } = await createQuestionGuide({
messages,
model: qgModel.model
});
@@ -29,7 +28,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
});
pushQuestionGuideUsage({
charsLength,
tokens,
teamId,
tmbId
});

View File

@@ -7,12 +7,13 @@ import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { ClearHistoriesProps } from '@/global/core/chat/api';
import { authOutLink } from '@/service/support/permission/auth/outLink';
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
import { authTeamSpaceToken } from '@/service/support/permission/auth/team';
/* clear chat history */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { appId, shareId, outLinkUid } = req.query as ClearHistoriesProps;
const { appId, shareId, outLinkUid, teamId, teamToken } = req.query as ClearHistoriesProps;
let chatAppId = appId;
@@ -26,6 +27,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
outLinkUid: uid
};
}
if (teamId && teamToken) {
const { uid } = await authTeamSpaceToken({ teamId, teamToken });
return {
teamId,
appId,
outLinkUid: uid
};
}
if (appId) {
const { tmbId } = await authCert({ req, authToken: true });

View File

@@ -4,14 +4,15 @@ import { connectToDatabase } from '@/service/mongo';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import type { ChatHistoryItemType } from '@fastgpt/global/core/chat/type.d';
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
import { getHistoriesProps } from '@/global/core/chat/api';
import { GetHistoriesProps } from '@/global/core/chat/api';
import { authOutLink } from '@/service/support/permission/auth/outLink';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { authTeamSpaceToken } from '@/service/support/permission/auth/team';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { appId, shareId, outLinkUid } = req.body as getHistoriesProps;
const { appId, shareId, outLinkUid, teamId, teamToken } = req.body as GetHistoriesProps;
const limit = shareId && outLinkUid ? 20 : 30;
@@ -28,10 +29,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
};
}
if (appId && outLinkUid) {
if (appId && teamId && teamToken) {
const { uid } = await authTeamSpaceToken({ teamId, teamToken });
return {
shareId,
outLinkUid: outLinkUid,
teamId,
appId,
outLinkUid: uid,
source: ChatSourceEnum.team
};
}

View File

@@ -4,7 +4,7 @@ import { connectToDatabase } from '@/service/mongo';
import { GetChatSpeechProps } from '@/global/core/chat/api.d';
import { text2Speech } from '@fastgpt/service/core/ai/audio/speech';
import { pushAudioSpeechUsage } from '@/service/support/wallet/usage/push';
import { authCertOrShareId } from '@fastgpt/service/support/permission/auth/common';
import { authChatCert } from '@/service/support/permission/auth/chat';
import { authType2UsageSource } from '@/service/support/wallet/usage/utils';
import { getAudioSpeechModel } from '@fastgpt/service/core/ai/model';
import { MongoTTSBuffer } from '@fastgpt/service/common/buffer/tts/schema';
@@ -19,13 +19,13 @@ import { MongoTTSBuffer } from '@fastgpt/service/common/buffer/tts/schema';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { ttsConfig, input, shareId } = req.body as GetChatSpeechProps;
const { ttsConfig, input } = req.body as GetChatSpeechProps;
if (!ttsConfig.model || !ttsConfig.voice) {
throw new Error('model or voice not found');
}
const { teamId, tmbId, authType } = await authCertOrShareId({ req, authToken: true, shareId });
const { teamId, tmbId, authType } = await authChatCert({ req, authToken: true });
const ttsModel = getAudioSpeechModel(ttsConfig.model);
const voiceData = ttsModel.voices?.find((item) => item.value === ttsConfig.voice);

View File

@@ -1,37 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import type { chatByTeamProps } from '@/global/core/chat/api.d';
import axios from 'axios';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { getChatItems } from '@fastgpt/service/core/chat/controller';
import { selectShareResponse } from '@/utils/service/core/chat';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
let { teamId, appId, outLinkUid } = req.query as chatByTeamProps;
const history = await MongoChatItem.find({
appId: appId,
outLinkUid: outLinkUid,
teamId: teamId
});
jsonRes(res, {
data: history
});
} catch (err) {
jsonRes(res, {
code: 500,
data: req.query,
error: err
});
}
}
export const config = {
api: {
responseLimit: '10mb'
}
};

View File

@@ -9,7 +9,7 @@ import { getChatItems } from '@fastgpt/service/core/chat/controller';
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
import { authOutLink } from '@/service/support/permission/auth/outLink';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { selectShareResponse } from '@/utils/service/core/chat';
import { selectSimpleChatResponse } from '@/utils/service/core/chat';
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
@@ -50,7 +50,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
// pick share response field
history.forEach((item) => {
item.responseData = selectShareResponse({ responseData: item.responseData });
item.responseData = selectSimpleChatResponse({ responseData: item.responseData });
});
jsonRes<InitChatResponse>(res, {

View File

@@ -4,59 +4,57 @@ import { connectToDatabase } from '@/service/mongo';
import { getGuideModule } from '@fastgpt/global/core/module/utils';
import { getChatModelNameListByModules } from '@/service/core/app/module';
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import type { InitChatProps, InitChatResponse } from '@/global/core/chat/api.d';
import type { InitChatResponse, InitTeamChatProps } from '@/global/core/chat/api.d';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { getChatItems } from '@fastgpt/service/core/chat/controller';
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
import { authTeamSpaceToken } from '@/service/support/permission/auth/team';
import { MongoTeam } from '@fastgpt/service/support/user/team/teamSchema';
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
import { selectSimpleChatResponse } from '@/utils/service/core/chat';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
let { appId, chatId, outLinkUid } = req.query as {
chatId?: string;
appId?: string;
outLinkUid?: string;
};
let { teamId, appId, chatId, teamToken } = req.query as InitTeamChatProps;
if (!appId) {
return jsonRes(res, {
code: 501,
message: "You don't have an app yet"
});
if (!teamId || !appId || !teamToken) {
throw new Error('teamId, appId, teamToken are required');
}
// auth app permission
const [chat, app] = await Promise.all([
// authApp({
// req,
// authToken: false,
// appId,
// per: 'r'
// }),
chatId ? MongoChat.findOne({ appId, chatId }) : undefined,
const { uid } = await authTeamSpaceToken({
teamId,
teamToken
});
const [team, chat, app] = await Promise.all([
MongoTeam.findById(teamId, 'name avatar').lean(),
MongoChat.findOne({ teamId, appId, chatId }).lean(),
MongoApp.findById(appId).lean()
]);
if (!app) {
throw new Error(AppErrEnum.unExist);
}
// auth chat permission
// if (chat && chat.outLinkUid !== outLinkUid) {
// throw new Error(ChatErrEnum.unAuthChat);
// }
// // auth chat permission
// if (chat && !app.canWrite && String(tmbId) !== String(chat?.tmbId)) {
// throw new Error(ChatErrEnum.unAuthChat);
// }
if (chat && chat.outLinkUid !== uid) {
throw new Error(ChatErrEnum.unAuthChat);
}
// get app and history
const { history } = await getChatItems({
appId,
chatId,
limit: 30,
field: `dataId obj value adminFeedback userBadFeedback userGoodFeedback ${ModuleOutputKeyEnum.responseData}`
field: `dataId obj value userGoodFeedback userBadFeedback adminFeedback ${ModuleOutputKeyEnum.responseData}`
});
// pick share response field
history.forEach((item) => {
item.responseData = selectSimpleChatResponse({ responseData: item.responseData });
});
jsonRes<InitChatResponse>(res, {
@@ -64,7 +62,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
chatId,
appId,
title: chat?.title || '新对话',
userAvatar: undefined,
userAvatar: team?.avatar,
variables: chat?.variables || {},
history,
app: {

View File

@@ -8,9 +8,9 @@ import type { GetDatasetCollectionsProps } from '@/global/core/api/datasetReq';
import { PagingData } from '@/types';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constants';
import { startQueue } from '@/service/utils/tools';
import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';
import { DatasetDataCollectionName } from '@fastgpt/service/core/dataset/data/schema';
import { startTrainingQueue } from '@/service/core/dataset/training/utils';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -158,7 +158,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
);
if (data.find((item) => item.trainingAmount > 0)) {
startQueue();
startTrainingQueue();
}
// count collections

View File

@@ -75,7 +75,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
a: formatA
});
const { insertId, charsLength } = await insertData2Dataset({
const { insertId, tokens } = await insertData2Dataset({
teamId,
tmbId,
datasetId,
@@ -90,7 +90,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
pushGenerateVectorUsage({
teamId,
tmbId,
charsLength,
tokens,
model: vectorModelData.model
});

View File

@@ -34,7 +34,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
insertLen: 1
});
const { charsLength } = await updateData2Dataset({
const { tokens } = await updateData2Dataset({
dataId: id,
q,
a,
@@ -45,7 +45,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
pushGenerateVectorUsage({
teamId,
tmbId,
charsLength,
tokens,
model: vectorModel
});

View File

@@ -58,7 +58,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
extensionBg: datasetSearchExtensionBg
});
const { searchRes, charsLength, ...result } = await searchDatasetData({
const { searchRes, tokens, ...result } = await searchDatasetData({
teamId,
reRankQuery: rewriteQuery,
queries: concatQueries,
@@ -74,14 +74,14 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
const { totalPoints } = pushGenerateVectorUsage({
teamId,
tmbId,
charsLength,
tokens,
model: dataset.vectorModel,
source: apikey ? UsageSourceEnum.api : UsageSourceEnum.fastgpt,
...(aiExtensionResult &&
extensionModel && {
extensionModel: extensionModel.name,
extensionCharsLength: aiExtensionResult.charsLength
extensionTokens: aiExtensionResult.tokens
})
});
if (apikey) {

View File

@@ -0,0 +1,91 @@
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 {
delFileByFileIdList,
getGFSCollection
} from '@fastgpt/service/common/file/gridfs/controller';
import { addLog } from '@fastgpt/service/common/system/log';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { addHours } from 'date-fns';
/*
check dataset.files data. If there is no match in dataset.collections, delete it
可能异常情况
1. 上传了文件,未成功创建集合
*/
let deleteFileAmount = 0;
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { startHour = 24, endHour = 1 } = req.body as {
startHour?: number;
endHour?: number;
limit?: number;
};
await authCert({ req, authRoot: true });
await connectToDatabase();
// start: now - maxDay, end: now - 3 day
const start = addHours(new Date(), -startHour);
const end = addHours(new Date(), -endHour);
deleteFileAmount = 0;
console.log(start, end);
await checkFiles(start, end);
jsonRes(res, {
data: deleteFileAmount,
message: 'success'
});
} catch (error) {
addLog.error(`check valid dataset files error`, error);
jsonRes(res, {
code: 500,
error
});
}
}
export async function checkFiles(start: Date, end: Date) {
const collection = getGFSCollection('dataset');
const where = {
uploadDate: { $gte: start, $lte: end }
};
// 1. get all file _id
const files = await collection
.find(where, {
projection: {
metadata: 1,
_id: 1
}
})
.toArray();
console.log('total files', files.length);
let index = 0;
for await (const file of files) {
try {
// 2. find fileId in dataset.collections
const hasCollection = await MongoDatasetCollection.countDocuments({
teamId: file.metadata.teamId,
fileId: file._id
});
// 3. if not found, delete file
if (hasCollection === 0) {
await delFileByFileIdList({ bucketName: 'dataset', fileIdList: [String(file._id)] });
console.log('delete file', file._id);
deleteFileAmount++;
}
index++;
index % 100 === 0 && console.log(index);
} catch (error) {
console.log(error);
}
}
console.log(`检测完成,共删除 ${deleteFileAmount} 个无效文件`);
}

View File

@@ -0,0 +1,88 @@
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 { addLog } from '@fastgpt/service/common/system/log';
import { addHours } from 'date-fns';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { MongoImage } from '@fastgpt/service/common/file/image/schema';
/*
检测无效的数据集图片
可能异常情况:
1. 上传文件过程中,上传了图片,但是最终没有创建数据集。
*/
let deleteImageAmount = 0;
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const {
startHour = 72,
endHour = 24,
limit = 10
} = req.body as { startHour?: number; endHour?: number; limit?: number };
await authCert({ req, authRoot: true });
await connectToDatabase();
// start: now - maxDay, end: now - 3 day
const start = addHours(new Date(), -startHour);
const end = addHours(new Date(), -endHour);
deleteImageAmount = 0;
await checkInvalid(start, end, limit);
jsonRes(res, {
data: deleteImageAmount
});
} catch (error) {
addLog.error(`check Invalid user error`, error);
jsonRes(res, {
code: 500,
error
});
}
}
export async function checkInvalid(start: Date, end: Date, limit = 50) {
const images = await MongoImage.find(
{
createTime: {
$gte: start,
$lte: end
},
'metadata.relatedId': { $exists: true }
},
'_id teamId metadata'
);
console.log('total images', images.length);
let index = 0;
for await (const image of images) {
try {
// 1. 检测是否有对应的集合
const collection = await MongoDatasetCollection.findOne(
{
teamId: image.teamId,
'metadata.relatedImgId': image.metadata?.relatedId
},
'_id'
);
if (!collection) {
await image.deleteOne();
deleteImageAmount++;
}
index++;
index % 100 === 0 && console.log(index);
} catch (error) {
console.log(error);
}
}
console.log(`检测完成,共删除 ${deleteImageAmount} 个无效图片`);
}

View File

@@ -0,0 +1,96 @@
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 { addLog } from '@fastgpt/service/common/system/log';
import { deleteDatasetDataVector } from '@fastgpt/service/common/vectorStore/controller';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { addHours } from 'date-fns';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema';
/*
检测无效的 Mongo 数据
异常情况:
1. 训练过程删除知识库,可能导致还会有新的数据插入,导致无效。
*/
let deleteAmount = 0;
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { startHour = 3, endHour = 1 } = req.body as { startHour?: number; endHour?: number };
await authCert({ req, authRoot: true });
await connectToDatabase();
// start: now - maxDay, end: now - endHour
const start = addHours(new Date(), -startHour);
const end = addHours(new Date(), -endHour);
deleteAmount = 0;
await checkInvalidCollection(start, end);
jsonRes(res, {
data: deleteAmount,
message: 'success'
});
} catch (error) {
addLog.error(`check Invalid user error`, error);
jsonRes(res, {
code: 500,
error
});
}
}
export async function checkInvalidCollection(start: Date, end: Date) {
// 1. 获取时间范围的所有data
const rows = await MongoDatasetData.find(
{
updateTime: {
$gte: start,
$lte: end
}
},
'_id teamId collectionId'
).lean();
// 2. 合并所有的collectionId
const map = new Map<string, { teamId: string; collectionId: string }>();
for (const item of rows) {
const collectionId = String(item.collectionId);
if (!map.has(collectionId)) {
map.set(collectionId, { teamId: item.teamId, collectionId });
}
}
const list = Array.from(map.values());
console.log('total collections', list.length);
let index = 0;
for await (const item of list) {
try {
// 3. 查看该collection是否存在不存在则删除对应的数据
const collection = await MongoDatasetCollection.findOne({ _id: item.collectionId });
if (!collection) {
const result = await Promise.all([
MongoDatasetTraining.deleteMany({
teamId: item.teamId,
collectionId: item.collectionId
}),
MongoDatasetData.deleteMany({
teamId: item.teamId,
collectionId: item.collectionId
}),
deleteDatasetDataVector({
teamId: item.teamId,
collectionIds: [String(item.collectionId)]
})
]);
console.log(result);
console.log('collection is not found', item);
continue;
}
} catch (error) {}
console.log(++index);
}
}

View File

@@ -0,0 +1,86 @@
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 { addLog } from '@fastgpt/service/common/system/log';
import {
deleteDatasetDataVector,
getVectorDataByTime
} from '@fastgpt/service/common/vectorStore/controller';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { addHours } from 'date-fns';
/*
检测无效的 Vector 数据.
异常情况:
1. 插入数据时vector成功mongo失败
2. 更新数据,也会有插入 vector
*/
let deletedVectorAmount = 0;
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { startHour = 5, endHour = 1 } = req.body as { startHour?: number; endHour?: number };
await authCert({ req, authRoot: true });
await connectToDatabase();
// start: now - maxDay, end: now - endHour
const start = addHours(new Date(), -startHour);
const end = addHours(new Date(), -endHour);
deletedVectorAmount = 0;
await checkInvalidVector(start, end);
jsonRes(res, {
data: deletedVectorAmount,
message: 'success'
});
} catch (error) {
addLog.error(`check Invalid user error`, error);
jsonRes(res, {
code: 500,
error
});
}
}
export async function checkInvalidVector(start: Date, end: Date) {
// 1. get all vector data
const rows = await getVectorDataByTime(start, end);
console.log('total data', rows.length);
let index = 0;
for await (const item of rows) {
if (!item.teamId || !item.datasetId || !item.id) {
console.log('error data', item);
continue;
}
try {
// 2. find dataset.data
const hasData = await MongoDatasetData.countDocuments({
teamId: item.teamId,
datasetId: item.datasetId,
'indexes.dataId': item.id
});
// 3. if not found, delete vector
if (hasData === 0) {
await deleteDatasetDataVector({
teamId: item.teamId,
id: item.id
});
console.log('delete vector data', item.id);
deletedVectorAmount++;
}
index++;
index % 100 === 0 && console.log(index);
} catch (error) {
console.log(error);
}
}
console.log(`检测完成,共删除 ${deletedVectorAmount} 个无效 向量 数据`);
}

View File

@@ -1,12 +1,12 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { withNextCors } from '@fastgpt/service/common/middle/cors';
import { getUploadModel } from '@fastgpt/service/common/file/multer';
import { removeFilesByPaths } from '@fastgpt/service/common/file/utils';
import fs from 'fs';
import { getAIApi } from '@fastgpt/service/core/ai/config';
import { pushWhisperUsage } from '@/service/support/wallet/usage/push';
import { authChatCert } from '@/service/support/permission/auth/chat';
const upload = getUploadModel({
maxSize: 2
@@ -18,12 +18,20 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
try {
const {
file,
data: { duration }
} = await upload.doUpload<{ duration: number; shareId?: string }>(req, res);
data: { duration, teamId: spaceTeamId, teamToken }
} = await upload.doUpload<{
duration: number;
shareId?: string;
teamId?: string;
teamToken?: string;
}>(req, res);
req.body.teamId = spaceTeamId;
req.body.teamToken = teamToken;
filePaths = [file.path];
const { teamId, tmbId } = await authCert({ req, authToken: true });
const { teamId, tmbId } = await authChatCert({ req, authToken: true });
if (!global.whisperModel) {
throw new Error('whisper model not found');

View File

@@ -18,31 +18,28 @@ import { authOutLinkChatStart } from '@/service/support/permission/auth/outLink'
import { pushResult2Remote, addOutLinkUsage } from '@fastgpt/service/support/outLink/tools';
import requestIp from 'request-ip';
import { getUsageSourceByAuthType } from '@fastgpt/global/support/wallet/usage/tools';
import { authTeamShareChatStart } from '@/service/support/permission/auth/teamChat';
import { selectShareResponse } from '@/utils/service/core/chat';
import { authTeamSpaceToken } from '@/service/support/permission/auth/team';
import { selectSimpleChatResponse } from '@/utils/service/core/chat';
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
import { connectToDatabase } from '@/service/mongo';
import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team';
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { autChatCrud } from '@/service/support/permission/auth/chat';
import { UserModelSchema } from '@fastgpt/global/support/user/type';
import { AppSchema } from '@fastgpt/global/core/app/type';
import { AuthOutLinkChatProps } from '@fastgpt/global/support/outLink/api';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
type FastGptWebChatProps = {
chatId?: string; // undefined: nonuse history, '': new chat, 'xxxxx': use history
appId?: string;
};
type FastGptShareChatProps = {
shareId?: string;
outLinkUid?: string;
};
type FastGptTeamShareChatProps = {
shareTeamId?: string;
outLinkUid?: string;
};
export type Props = ChatCompletionCreateParams &
FastGptWebChatProps &
FastGptShareChatProps &
FastGptTeamShareChatProps & {
OutLinkChatAuthProps & {
messages: ChatMessageItemType[];
stream?: boolean;
detail?: boolean;
@@ -53,6 +50,18 @@ export type ChatResponseType = {
quoteLen?: number;
};
type AuthResponseType = {
teamId: string;
tmbId: string;
user: UserModelSchema;
app: AppSchema;
responseDetail?: boolean;
authType: `${AuthUserTypeEnum}`;
apikey?: string;
canWrite: boolean;
outLinkUserId?: string;
};
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) {
res.on('close', () => {
res.end();
@@ -65,9 +74,12 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
const {
chatId,
appId,
shareTeamId,
// share chat
shareId,
outLinkUid,
// team chat
teamId: spaceTeamId,
teamToken,
stream = false,
detail = false,
messages = [],
@@ -100,136 +112,44 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
if (!question) {
throw new Error('Question is empty');
}
/* auth app permission */
/*
1. auth app permission
2. auth balance
3. get app
4. parse outLink token
*/
const { teamId, tmbId, user, app, responseDetail, authType, apikey, canWrite, outLinkUserId } =
await (async () => {
// share chat
if (shareId && outLinkUid) {
const { teamId, tmbId, user, appId, authType, responseDetail, uid } =
await authOutLinkChatStart({
shareId,
ip: originIp,
outLinkUid,
question: question.value
});
const app = await MongoApp.findById(appId);
if (!app) {
return Promise.reject('app is empty');
}
return {
teamId,
tmbId,
user,
app,
responseDetail,
apikey: '',
authType,
canWrite: false,
outLinkUserId: uid
};
}
// team Apps share
if (shareTeamId && appId && outLinkUid) {
const { user, uid, tmbId } = await authTeamShareChatStart({
teamId: shareTeamId,
ip: originIp,
return authShareChat({
shareId,
outLinkUid,
chatId,
ip: originIp,
question: question.value
});
const app = await MongoApp.findById(appId);
if (!app) {
return Promise.reject('app is empty');
}
return {
teamId: shareTeamId,
tmbId,
user,
app,
responseDetail: detail,
authType: AuthUserTypeEnum.token,
apikey: '',
canWrite: false,
outLinkUserId: uid
};
}
// team space chat
if (spaceTeamId && appId && teamToken) {
return authTeamSpaceChat({
teamId: spaceTeamId,
teamToken,
appId,
chatId
});
}
const {
appId: apiKeyAppId,
teamId,
tmbId,
authType,
apikey
} = await authCert({
/* parse req: api or token */
return authHeaderRequest({
req,
authToken: true,
authApiKey: true
});
const { user } = await getUserChatInfoAndAuthTeamPoints(tmbId);
// openapi key
if (authType === AuthUserTypeEnum.apikey) {
if (!apiKeyAppId) {
return Promise.reject(
'Key is error. You need to use the app key rather than the account key.'
);
}
const app = await MongoApp.findById(apiKeyAppId);
if (!app) {
return Promise.reject('app is empty');
}
return {
teamId,
tmbId,
user,
app,
responseDetail: detail,
apikey,
authType,
canWrite: true
};
}
// token auth
if (!appId) {
return Promise.reject('appId is empty');
}
const { app, canWrite } = await authApp({
req,
authToken: true,
appId,
per: 'r'
chatId,
detail
});
return {
teamId,
tmbId,
user,
app,
responseDetail: detail,
apikey,
authType,
canWrite: canWrite || false
};
})();
// auth chat permission
await autChatCrud({
req,
authToken: true,
authApiKey: true,
appId: app._id,
chatId,
shareId,
shareTeamId,
outLinkUid,
per: 'w'
});
// get and concat history
const { history } = await getChatItems({
appId: app._id,
@@ -237,7 +157,6 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
limit: 30,
field: `dataId obj value`
});
const concatHistories = history.concat(chatMessages);
const responseChatItemId: string | undefined = messages[messages.length - 1].dataId;
@@ -263,13 +182,14 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
// save chat
if (chatId) {
const isOwnerUse = !shareId && !spaceTeamId && String(tmbId) === String(app.tmbId);
await saveChat({
chatId,
appId: app._id,
teamId,
tmbId: tmbId,
variables,
updateUseTime: !shareId && String(tmbId) === String(app.tmbId), // owner update use time
updateUseTime: isOwnerUse, // owner update use time
shareId,
outLinkUid: outLinkUserId,
source: (() => {
@@ -279,6 +199,9 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
if (authType === 'apikey') {
return ChatSourceEnum.api;
}
if (spaceTeamId) {
return ChatSourceEnum.team;
}
return ChatSourceEnum.online;
})(),
content: [
@@ -299,7 +222,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
addLog.info(`completions running time: ${(Date.now() - startTime) / 1000}s`);
/* select fe response field */
const feResponseData = canWrite ? responseData : selectShareResponse({ responseData });
const feResponseData = canWrite ? responseData : selectSimpleChatResponse({ responseData });
if (stream) {
responseWrite({
@@ -382,3 +305,162 @@ export const config = {
responseLimit: '20mb'
}
};
const authShareChat = async ({
chatId,
...data
}: AuthOutLinkChatProps & {
shareId: string;
chatId?: string;
}): Promise<AuthResponseType> => {
const { teamId, tmbId, user, appId, authType, responseDetail, uid } =
await authOutLinkChatStart(data);
const app = await MongoApp.findById(appId).lean();
if (!app) {
return Promise.reject('app is empty');
}
// get chat
const chat = await MongoChat.findOne({ appId, chatId }).lean();
if (chat && (chat.shareId !== data.shareId || chat.outLinkUid !== uid)) {
return Promise.reject(ChatErrEnum.unAuthChat);
}
return {
teamId,
tmbId,
user,
app,
responseDetail,
apikey: '',
authType,
canWrite: false,
outLinkUserId: uid
};
};
const authTeamSpaceChat = async ({
appId,
teamId,
teamToken,
chatId
}: {
appId: string;
teamId: string;
teamToken: string;
chatId?: string;
}): Promise<AuthResponseType> => {
const { uid } = await authTeamSpaceToken({
teamId,
teamToken
});
const app = await MongoApp.findById(appId).lean();
if (!app) {
return Promise.reject('app is empty');
}
const [chat, { user }] = await Promise.all([
MongoChat.findOne({ appId, chatId }).lean(),
getUserChatInfoAndAuthTeamPoints(app.tmbId)
]);
if (chat && (String(chat.teamId) !== teamId || chat.outLinkUid !== uid)) {
return Promise.reject(ChatErrEnum.unAuthChat);
}
return {
teamId,
tmbId: app.tmbId,
user,
app,
responseDetail: true,
authType: AuthUserTypeEnum.outLink,
apikey: '',
canWrite: false,
outLinkUserId: uid
};
};
const authHeaderRequest = async ({
req,
appId,
chatId,
detail
}: {
req: NextApiRequest;
appId?: string;
chatId?: string;
detail?: boolean;
}): Promise<AuthResponseType> => {
const {
appId: apiKeyAppId,
teamId,
tmbId,
authType,
apikey,
canWrite: apiKeyCanWrite
} = await authCert({
req,
authToken: true,
authApiKey: true
});
const { app, canWrite } = await (async () => {
if (authType === AuthUserTypeEnum.apikey) {
if (!apiKeyAppId) {
return Promise.reject(
'Key is error. You need to use the app key rather than the account key.'
);
}
const app = await MongoApp.findById(apiKeyAppId);
if (!app) {
return Promise.reject('app is empty');
}
appId = String(app._id);
return {
app,
canWrite: apiKeyCanWrite
};
} else {
// token auth
if (!appId) {
return Promise.reject('appId is empty');
}
const { app, canWrite } = await authApp({
req,
authToken: true,
appId,
per: 'r'
});
return {
app,
canWrite: canWrite
};
}
})();
const [{ user }, chat] = await Promise.all([
getUserChatInfoAndAuthTeamPoints(tmbId),
MongoChat.findOne({ appId, chatId }).lean()
]);
if (chat && (String(chat.teamId) !== teamId || String(chat.tmbId) !== tmbId)) {
return Promise.reject(ChatErrEnum.unAuthChat);
}
return {
teamId,
tmbId,
user,
app,
responseDetail: detail,
apikey,
authType,
canWrite
};
};

View File

@@ -36,7 +36,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
await checkTeamAIPoints(teamId);
const { charsLength, vectors } = await getVectorsByText({
const { tokens, vectors } = await getVectorsByText({
input: query,
model: getVectorModel(model)
});
@@ -50,15 +50,15 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
})),
model,
usage: {
prompt_tokens: charsLength,
total_tokens: charsLength
prompt_tokens: tokens,
total_tokens: tokens
}
});
const { totalPoints } = pushGenerateVectorUsage({
teamId,
tmbId,
charsLength,
tokens,
model,
billId,
source: getUsageSourceByAuthType({ authType })

View File

@@ -136,7 +136,7 @@ const SelectUsingWayModal = ({ share, onClose }: { share: OutLinkSchema; onClose
/>
{/* config */}
<Grid gridTemplateColumns={['repeat(3,1fr)']} gridGap={4} my={5}>
<Grid gridTemplateColumns={['repeat(2,1fr)', 'repeat(3,1fr)']} gridGap={4} my={5}>
<Flex {...gridItemStyle}>
<Box flex={1}>{t('core.app.outLink.Show History')}</Box>
<Switch {...register('showHistory')} />

View File

@@ -14,10 +14,6 @@ import {
ModalBody,
Input,
Switch,
Menu,
MenuButton,
MenuList,
MenuItem,
Link
} from '@chakra-ui/react';
import { QuestionOutlineIcon } from '@chakra-ui/icons';

View File

@@ -13,7 +13,7 @@ import PermissionIconText from '@/components/support/permission/IconText';
import dynamic from 'next/dynamic';
import Avatar from '@/components/Avatar';
import MyIcon from '@fastgpt/web/components/common/Icon';
import TagsEditModal from './tagsEditModal';
import TagsEditModal from './TagsEditModal';
import { useSystemStore } from '@/web/common/system/useSystemStore';
const InfoModal = dynamic(() => import('../InfoModal'));
@@ -156,9 +156,7 @@ const AppCard = ({ appId }: { appId: string }) => {
{settingAppInfo && (
<InfoModal defaultApp={settingAppInfo} onClose={() => setSettingAppInfo(undefined)} />
)}
{TeamTagsSet && (
<TagsEditModal appDetail={appDetail} onClose={() => setTeamTagsSet(undefined)} />
)}
{TeamTagsSet && <TagsEditModal onClose={() => setTeamTagsSet(undefined)} />}
</>
);
};

View File

@@ -0,0 +1,143 @@
import React, { useState } from 'react';
import MyModal from '@/components/MyModal';
import { useTranslation } from 'next-i18next';
import {
Button,
Flex,
Box,
ModalFooter,
ModalBody,
Menu,
MenuButton,
HStack,
Tag,
TagCloseButton,
MenuList,
Input,
MenuOptionGroup,
MenuItemOption,
TagLabel
} from '@chakra-ui/react';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import { useRequest } from '@/web/common/hooks/useRequest';
import { getTeamsTags } from '@/web/support/user/team/api';
import { useQuery } from '@tanstack/react-query';
const TagsEditModal = ({ onClose }: { onClose: () => void }) => {
const { t } = useTranslation();
const { appDetail } = useAppStore();
const { toast } = useToast();
const { replaceAppDetail } = useAppStore();
const [selectedTags, setSelectedTags] = useState<string[]>(appDetail?.teamTags || []);
// submit config
const { mutate: saveSubmitSuccess, isLoading: btnLoading } = useRequest({
mutationFn: async () => {
await replaceAppDetail(appDetail._id, {
teamTags: selectedTags
});
},
onSuccess() {
onClose();
toast({
title: t('common.Update Success'),
status: 'success'
});
},
errorToast: t('common.Update Failed')
});
const { data: teamTags = [] } = useQuery(['getTeamsTags'], getTeamsTags);
const [searchKey, setSearchKey] = useState('');
const filterTeamTags = teamTags.filter((item) => {
return item.label.includes(searchKey);
}, []);
return (
<MyModal
style={{ width: '900px' }}
isOpen
onClose={onClose}
iconSrc="/imgs/module/ai.svg"
title={t('core.app.Team tags')}
>
<ModalBody>
<Box mb={3} fontWeight="semibold">
{t('团队标签')}
</Box>
<Menu closeOnSelect={false}>
<MenuButton className="menu-btn" maxHeight={'250'} w={'100%'}>
<Flex
alignItems={'center'}
borderWidth={'1px'}
borderColor={'borderColor.base'}
borderRadius={'md'}
px={3}
py={2}
flexWrap={'wrap'}
minH={'50px'}
gap={3}
>
{teamTags.map((item, index) => {
const key: string = item?.key;
if (selectedTags.indexOf(key as never) > -1) {
return (
<Tag key={index} size={'md'} colorScheme="blue" borderRadius="full">
<TagLabel>{item.label}</TagLabel>
<TagCloseButton />
</Tag>
);
}
})}
</Flex>
</MenuButton>
<MenuList>
<Box px={2}>
<Input
m={'auto'}
placeholder={t('core.app.Search team tags')}
value={searchKey}
onChange={(e) => {
setSearchKey(e.target.value);
}}
/>
</Box>
<Box maxH={'300px'} overflow={'auto'} mt={1}>
<MenuOptionGroup
defaultValue={selectedTags}
type="checkbox"
onChange={(e) => {
//@ts-ignore
setSelectedTags(e);
}}
>
{filterTeamTags.map((item) => {
return (
<MenuItemOption
key={item.key}
value={item.key}
borderRadius={'md'}
_hover={{ bg: 'myGray.100' }}
>
{item?.label}
</MenuItemOption>
);
})}
</MenuOptionGroup>
</Box>
</MenuList>
</Menu>
</ModalBody>
<ModalFooter>
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
{t('common.Close')}
</Button>
<Button isLoading={btnLoading} onClick={(e) => saveSubmitSuccess(e)}>
{t('common.Save')}
</Button>
</ModalFooter>
</MyModal>
);
};
export default TagsEditModal;

View File

@@ -1,103 +0,0 @@
import React, { useCallback, useState, useEffect } from 'react';
import MyModal from '@/components/MyModal';
import { useTranslation } from 'next-i18next';
import { Button, Flex, Box, ModalFooter, ModalBody } from '@chakra-ui/react';
import TagsEdit from '@/components/TagEdit';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { AppSchema } from '@fastgpt/global/core/app/type.d';
import { TeamTagsSchema } from '@fastgpt/global/support/user/team/type';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import { useRequest } from '@/web/common/hooks/useRequest';
import { getTeamsTags } from '@/web/support/user/team/api';
const TagsEditModal = ({ appDetail, onClose }: { appDetail?: any; onClose: () => void }) => {
const { t } = useTranslation();
const [teamsTags, setTeamTags] = useState<Array<TeamTagsSchema>>([]);
const [selectedTags, setSelectedTags] = useState(appDetail?.teamTags);
const { toast } = useToast();
const { replaceAppDetail } = useAppStore();
// submit config
const { mutate: saveSubmitSuccess, isLoading: btnLoading } = useRequest({
mutationFn: async () => {
await replaceAppDetail(appDetail._id, {
teamTags: selectedTags
});
},
onSuccess() {
onClose();
toast({
title: t('common.Update Success'),
status: 'success'
});
},
errorToast: t('common.Update Failed')
});
//
// // 点击选择标签
// const clickTag = (tagId :Number) => {
// const index = selectedTags.indexOf(tagId);
// if (index === -1) {
// // 如果 num 不在数组 arr 中,添加它
// setSelectedTags([tagId,...selectedTags])
// } else {
// const _selectedTags = [...selectedTags];
// _selectedTags.splice(index, 1);
// console.log('_selectedTags',_selectedTags);
// // 如果 num 已经在数组 arr 中,移除它
// setSelectedTags(_selectedTags);
// }
// }
useEffect(() => {
// get team tags
getTeamsTags(appDetail?.teamId).then((res: any) => {
setTeamTags(res?.list);
});
}, []);
return (
<MyModal
style={{ width: '900px' }}
isOpen
onClose={onClose}
iconSrc="/imgs/module/ai.svg"
title={'标签管理'}
>
<ModalBody>
{/* <HStack spacing={2}>
{teamsTags.map((item,index) => {
return <Tag
key={index}
size={'md'}
variant='outline'
colorScheme={selectedTags.indexOf(item._id) > -1 ? 'green':'blue' }
onClick={() => clickTag(item._id)}
>
{item.label}
</Tag>
})}
</HStack> */}
<Flex width={'100%'} alignItems={'center'}>
<Box mb={3} mr={3} fontWeight="semibold">
{t('团队标签')}
</Box>
<TagsEdit
defaultValues={selectedTags}
teamsTags={teamsTags}
setSelectedTags={(item: Array<string>) => setSelectedTags(item)}
/>
</Flex>
<ModalFooter>
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
{t('common.Close')}
</Button>
<Button isLoading={btnLoading} onClick={(e) => saveSubmitSuccess(e)}>
{t('common.Save')}
</Button>
</ModalFooter>
</ModalBody>
</MyModal>
);
};
export default TagsEditModal;

View File

@@ -1,45 +1,52 @@
import React from 'react';
import { Flex, Box, IconButton } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import { useQuery } from '@tanstack/react-query';
import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon';
import Avatar from '@/components/Avatar';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import { AppListItemType } from '@fastgpt/global/core/app/type';
const SliderApps = ({ appId }: { appId: string }) => {
const SliderApps = ({
showExist = true,
apps,
activeAppId
}: {
showExist?: boolean;
apps: AppListItemType[];
activeAppId: string;
}) => {
const { t } = useTranslation();
const router = useRouter();
const { myApps, loadMyApps } = useAppStore();
useQuery(['loadModels'], () => loadMyApps(false));
return (
<Flex flexDirection={'column'} h={'100%'}>
<Box px={5} py={4}>
<Flex
alignItems={'center'}
cursor={'pointer'}
py={2}
px={3}
borderRadius={'md'}
_hover={{ bg: 'myGray.200' }}
onClick={() => router.push('/app/list')}
>
<IconButton
mr={3}
icon={<MyIcon name={'common/backFill'} w={'18px'} color={'primary.500'} />}
bg={'white'}
boxShadow={'1px 1px 9px rgba(0,0,0,0.15)'}
size={'smSquare'}
borderRadius={'50%'}
aria-label={''}
/>
{t('core.chat.Exit Chat')}
</Flex>
{showExist && (
<Flex
alignItems={'center'}
cursor={'pointer'}
py={2}
px={3}
borderRadius={'md'}
_hover={{ bg: 'myGray.200' }}
onClick={() => router.push('/app/list')}
>
<IconButton
mr={3}
icon={<MyIcon name={'common/backFill'} w={'18px'} color={'primary.500'} />}
bg={'white'}
boxShadow={'1px 1px 9px rgba(0,0,0,0.15)'}
size={'smSquare'}
borderRadius={'50%'}
aria-label={''}
/>
{t('core.chat.Exit Chat')}
</Flex>
)}
</Box>
<Box flex={'1 0 0'} h={0} px={5} overflow={'overlay'}>
{myApps.map((item) => (
{apps.map((item) => (
<Flex
key={item._id}
py={2}
@@ -48,7 +55,7 @@ const SliderApps = ({ appId }: { appId: string }) => {
cursor={'pointer'}
borderRadius={'md'}
alignItems={'center'}
{...(item._id === appId
{...(item._id === activeAppId
? {
bg: 'white',
boxShadow: 'md'
@@ -60,6 +67,8 @@ const SliderApps = ({ appId }: { appId: string }) => {
onClick: () => {
router.replace({
query: {
...router.query,
chatId: '',
appId: item._id
}
});

View File

@@ -129,6 +129,8 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
[appId, chatId, histories, pushHistory, router, setChatData, t, updateHistory]
);
useQuery(['loadModels'], () => loadMyApps(false));
// get chat app info
const loadChatInfo = useCallback(
async ({
@@ -251,7 +253,7 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
{/* pc show myself apps */}
{isPc && (
<Box borderRight={theme.borders.base} w={'220px'} flexShrink={0}>
<SliderApps appId={appId} />
<SliderApps apps={myApps} activeAppId={appId} />
</Box>
)}

View File

@@ -22,22 +22,29 @@ import { serviceSideProps } from '@/web/common/utils/i18n';
import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils';
import { useTranslation } from 'next-i18next';
import { getInitOutLinkChatInfo } from '@/web/core/chat/api';
import { POST } from '@/web/common/api/request';
import { chatContentReplaceBlock } from '@fastgpt/global/core/chat/utils';
import { useChatStore } from '@/web/core/chat/storeChat';
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
import MyBox from '@/components/common/MyBox';
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
import { OutLinkWithAppType } from '@fastgpt/global/support/outLink/type';
const OutLink = ({
shareId,
chatId,
showHistory,
authToken
authToken,
appName,
appIntro,
appAvatar
}: {
shareId: string;
chatId: string;
showHistory: '0' | '1';
authToken?: string;
appName?: string;
appIntro?: string;
appAvatar?: string;
}) => {
const { t } = useTranslation();
const router = useRouter();
@@ -150,7 +157,18 @@ const OutLink = ({
return { responseText, responseData, isNewChat: forbidRefresh.current };
},
[chatId, shareId, outLinkUid, setChatData, appId, pushHistory, router, histories, updateHistory]
[
chatId,
shareId,
outLinkUid,
t,
setChatData,
appId,
pushHistory,
router,
histories,
updateHistory
]
);
const loadChatInfo = useCallback(
@@ -235,28 +253,6 @@ const OutLink = ({
setIdEmbed(window !== top);
}, []);
// todo:4.6.4 init: update local chat history, add outLinkUid
useEffect(() => {
const activeHistory = shareChatHistory.filter((item) => !item.delete);
if (!localUId || !shareId || activeHistory.length === 0) return;
(async () => {
try {
await POST('/core/chat/initLocalShareHistoryV464', {
shareId,
outLinkUid: localUId,
chatIds: shareChatHistory.map((item) => item.chatId)
});
clearLocalHistory();
// router.reload();
} catch (error) {
toast({
status: 'warning',
title: getErrText(error, t('core.shareChat.Init Error'))
});
}
})();
}, [clearLocalHistory, localUId, router, shareChatHistory, shareId, t, toast]);
return (
<PageContainer
{...(isEmbed
@@ -264,7 +260,9 @@ const OutLink = ({
: { p: [0, 5] })}
>
<Head>
<title>{chatData.app.name}</title>
<title>{appName || chatData.app?.name}</title>
<meta name="description" content={appIntro} />
<link rel="icon" href={appAvatar || chatData.app?.avatar} />
</Head>
<MyBox
isLoading={isFetching}
@@ -397,12 +395,31 @@ export async function getServerSideProps(context: any) {
const showHistory = context?.query?.showHistory || '1';
const authToken = context?.query?.authToken || '';
const app = await (async () => {
try {
const app = (await MongoOutLink.findOne(
{
shareId
},
'appId'
)
.populate('appId', 'name avatar intro')
.lean()) as OutLinkWithAppType;
return app;
} catch (error) {
return undefined;
}
})();
return {
props: {
shareId,
chatId,
showHistory,
authToken,
appName: app?.appId?.name,
appAvatar: app?.appId?.avatar,
appIntro: app?.appId?.intro,
...(await serviceSideProps(context))
}
};

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import React, { useCallback, useEffect, useRef } from 'react';
import Head from 'next/head';
import { getTeamChatInfo } from '@/web/core/chat/api';
import { useRouter } from 'next/router';
@@ -11,13 +11,12 @@ import {
DrawerContent,
useTheme
} from '@chakra-ui/react';
import Avatar from '@/components/Avatar';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useQuery } from '@tanstack/react-query';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import SideBar from '@/components/SideBar';
import PageContainer from '@/components/PageContainer';
import { getChatListById } from '@/web/core/chat/api';
import { getMyTokensApps } from '@/web/core/chat/api';
import ChatHistorySlider from './components/ChatHistorySlider';
import ChatHeader from './components/ChatHeader';
import { serviceSideProps } from '@/web/common/utils/i18n';
@@ -25,112 +24,49 @@ import { useTranslation } from 'next-i18next';
import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils';
import { useChatStore } from '@/web/core/chat/storeChat';
import { customAlphabet } from 'nanoid';
import { useLoading } from '@/web/common/hooks/useLoading';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
import ChatBox, { type ComponentRef, type StartChatFnProps } from '@/components/ChatBox';
import { streamFetch } from '@/web/common/api/fetch';
import { useTeamShareChatStore } from '@/web/core/chat/storeTeamChat';
import type {
ChatHistoryItemType,
chatAppListSchema,
teamInfoType
} from '@fastgpt/global/core/chat/type.d';
import type { ChatHistoryItemType } from '@fastgpt/global/core/chat/type.d';
import { chatContentReplaceBlock } from '@fastgpt/global/core/chat/utils';
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
import { POST } from '@/web/common/api/request';
import { getErrText } from '@fastgpt/global/common/error/utils';
import MyBox from '@/components/common/MyBox';
import SliderApps from './components/SliderApps';
const OutLink = ({
shareTeamId,
teamId,
appId,
chatId,
authToken
teamToken
}: {
shareTeamId: string;
teamId: string;
appId: string;
chatId: string;
authToken: string;
teamToken: string;
}) => {
type routerQueryType = {
chatId?: string;
appId?: string;
shareTeamId: string;
authToken?: string;
};
const { t } = useTranslation();
const router = useRouter();
const { toast } = useToast();
const theme = useTheme();
const [myApps, setMyApps] = useState<Array<any>>([]);
const { isPc } = useSystemStore();
const ChatBoxRef = useRef<ComponentRef>(null);
const [teamInfo, setTeamInfo] = useState<teamInfoType>();
const { Loading, setIsLoading } = useLoading();
const forbidRefresh = useRef(false);
const { isOpen: isOpenSlider, onClose: onCloseSlider, onOpen: onOpenSlider } = useDisclosure();
const {
chatData,
setChatData,
histories,
loadHistories,
lastChatAppId,
setLastChatAppId,
lastChatId,
setLastChatId,
pushHistory,
updateHistory,
delOneHistory,
chatData,
setChatData,
delOneHistoryItem,
clearHistories
} = useChatStore();
const {
localUId,
teamShareChatHistory, // abandon
clearLocalHistory // abandon
} = useTeamShareChatStore();
const outLinkUid: string = authToken || localUId;
// 纯网络获取流程
const loadApps = useCallback(async () => {
try {
if (!shareTeamId) {
toast({
status: 'error',
title: t('core.chat.You need to a chat app')
});
return;
}
// 根据获取历史记录列表
const res = await getChatListById({ shareTeamId, authToken });
const { apps, teamInfo } = res;
setMyApps(apps);
setTeamInfo(teamInfo);
if (apps.length <= 0) {
return toast({
status: 'error',
title: t('core.chat.You need to a chat app')
});
}
if (!apps.find((obj) => obj._id === appId)) {
toast({
status: 'warning',
title: 'you do not have this App'
});
router.replace({
query: {
appId: apps[0]?._id,
shareTeamId,
authToken: authToken
} as routerQueryType
});
}
} catch (error: any) {
toast({
status: 'warning',
title: error?.message
});
}
}, [appId, authToken, router, shareTeamId, t, toast]);
const startChat = useCallback(
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
@@ -142,14 +78,14 @@ const OutLink = ({
messages: prompts,
variables,
appId,
shareTeamId,
outLinkUid: outLinkUid,
teamId,
teamToken,
chatId: completionChatId
},
onMessage: generatingMessage,
abortCtrl: controller
});
console.log(responseData);
const newTitle =
chatContentReplaceBlock(prompts[0].content).slice(0, 20) ||
prompts[1]?.value?.slice(0, 20) ||
@@ -169,11 +105,9 @@ const OutLink = ({
forbidRefresh.current = true;
router.replace({
query: {
chatId: completionChatId,
appId,
shareTeamId,
authToken: authToken
} as routerQueryType
...router.query,
chatId: completionChatId
}
});
}
} else {
@@ -183,7 +117,9 @@ const OutLink = ({
updateHistory({
...currentChat,
updateTime: new Date(),
title: newTitle
title: newTitle,
teamId,
teamToken
});
}
// update chat window
@@ -195,227 +131,146 @@ const OutLink = ({
return { responseText, responseData, isNewChat: forbidRefresh.current };
},
[appId, chatId, histories, pushHistory, router, setChatData, updateHistory]
[
appId,
teamToken,
chatId,
histories,
pushHistory,
router,
setChatData,
teamId,
t,
updateHistory
]
);
const { isFetching } = useQuery(['init', appId, shareTeamId], async () => {
console.log('res', 3);
if (!shareTeamId) {
/* replace router query to last chat */
useEffect(() => {
if ((!chatId || !appId) && (lastChatId || lastChatAppId)) {
router.replace({
query: {
...router.query,
chatId: chatId || lastChatId,
appId: appId || lastChatAppId
}
});
}
}, []);
// get chat app list
const loadApps = useCallback(async () => {
try {
const apps = await getMyTokensApps({ teamId, teamToken });
if (apps.length <= 0) {
toast({
status: 'error',
title: t('core.chat.You need to a chat app')
});
return [];
}
// if app id not exist, redirect to first app
if (!appId || !apps.find((item) => item._id === appId)) {
router.replace({
query: {
...router.query,
appId: apps[0]?._id
}
});
}
return apps;
} catch (error: any) {
toast({
status: 'warning',
title: getErrText(error)
});
}
return [];
}, [appId, teamToken, router, teamId, t, toast]);
const { data: myApps = [], isLoading: isLoadingApps } = useQuery(['initApps', teamId], () => {
if (!teamId) {
toast({
status: 'error',
title: t('core.chat.You need to a chat app')
title: t('support.user.team.tag.Have not opened')
});
return;
}
return shareTeamId && loadApps();
return loadApps();
});
// load histories
useQuery(['loadHistories', appId], () => {
if (shareTeamId && appId) {
return loadHistories({ appId, outLinkUid });
if (teamId && appId) {
return loadHistories({ teamId, appId, teamToken: teamToken });
}
return;
});
// 初始化聊天框
useQuery(['init', { appId, chatId }], () => {
if (!shareTeamId) {
toast({
status: 'error',
title: t('core.chat.You need to a chat app')
});
return;
}
if (myApps.length > 0 && myApps.findIndex((obj) => obj._id === appId) === -1) {
toast({
status: 'warning',
title: 'you do not have this App'
});
return;
}
// pc: redirect to latest model chat
if (!appId && lastChatAppId) {
return router.replace({
query: {
appId: lastChatAppId,
chatId: lastChatId,
shareTeamId,
authToken: authToken
} as routerQueryType
});
}
if (!appId && myApps[0]) {
return router.replace({
query: {
appId: myApps[0]._id,
chatId: lastChatId,
shareTeamId,
authToken: authToken
} as routerQueryType
});
}
if (!appId) {
(async () => {
const { apps = [] } = await getChatListById({ shareTeamId, authToken });
setMyApps(apps);
if (apps.length === 0) {
toast({
status: 'error',
title: t('core.chat.You need to a chat app')
});
} else {
router.replace({
query: {
appId: apps[0]._id,
chatId: lastChatId,
shareTeamId,
authToken: authToken
} as routerQueryType
});
}
})();
return;
}
// store id
appId && setLastChatAppId(appId);
setLastChatId(chatId);
return loadChatInfo({
appId,
chatId,
loading: appId !== chatData.appId
});
});
// get chat app info
const loadChatInfo = useCallback(
async ({
appId,
chatId,
loading = false
}: {
appId: string;
chatId: string;
loading?: boolean;
}) => {
try {
if (!shareTeamId) {
toast({
status: 'error',
title: t('core.chat.You need to a chat app')
});
return;
}
loading && setIsLoading(true);
const res = await getTeamChatInfo({ appId, chatId, outLinkUid });
console.log('res', res);
const history = res.history.map((item) => ({
...item,
status: ChatStatusEnum.finish
}));
const loadChatInfo = useCallback(async () => {
try {
const res = await getTeamChatInfo({ teamId, appId, chatId, teamToken: teamToken });
setChatData({
...res,
history
});
const history = res.history.map((item) => ({
...item,
status: ChatStatusEnum.finish
}));
// have records.
ChatBoxRef.current?.resetHistory(history);
ChatBoxRef.current?.resetVariables(res.variables);
if (res.history.length > 0) {
setTimeout(() => {
ChatBoxRef.current?.scrollToBottom('auto');
}, 500);
}
} catch (e: any) {
// reset all chat tore
setLastChatAppId('');
setLastChatId('');
toast({
title: t('core.chat.Failed to initialize chat'),
status: 'error'
});
if (e?.code === 501) {
//router.replace('/app/list');
} else if (chatId) {
router.replace({
query: {
...router.query,
chatId: ''
} as routerQueryType
});
}
setChatData({
...res,
history
});
// have records.
ChatBoxRef.current?.resetHistory(history);
ChatBoxRef.current?.resetVariables(res.variables);
if (res.history.length > 0) {
setTimeout(() => {
ChatBoxRef.current?.scrollToBottom('auto');
}, 500);
}
setIsLoading(false);
} catch (e: any) {
toast({
title: t('core.chat.Failed to initialize chat'),
status: 'error'
});
if (chatId) {
router.replace({
query: {
...router.query,
chatId: ''
}
});
}
}
return null;
}, [teamId, appId, chatId, teamToken, setChatData, toast, t, router]);
const { isFetching } = useQuery(['init', teamId, appId, chatId], () => {
if (forbidRefresh.current) {
forbidRefresh.current = false;
return null;
},
[setIsLoading, setChatData, router, setLastChatAppId, setLastChatId, toast]
);
// 监测路由改变
useEffect(() => {
const activeHistory = teamShareChatHistory.filter((item) => !item.delete);
if (!localUId || !shareTeamId || activeHistory.length === 0) return;
(async () => {
try {
await POST('/core/chat/initLocalShareHistoryV464', {
outLinkUid: localUId,
chatIds: teamShareChatHistory.map((item) => item.chatId)
});
clearLocalHistory();
// router.reload();
} catch (error) {
toast({
status: 'warning',
title: t('core.shareChat.Init Error')
});
}
})();
}, [clearLocalHistory, localUId, router, teamShareChatHistory, shareTeamId, t, toast]);
}
if (teamId && appId) {
return loadChatInfo();
}
return null;
});
return (
<Flex h={'100%'}>
<MyBox display={'flex'} h={'100%'} isLoading={isLoadingApps || isFetching}>
<Head>
<title>{chatData.app.name}</title>
</Head>
{/* pc show myself apps */}
<Box borderRight={theme.borders.base} w={'220px'} flexShrink={0}>
<Flex flexDirection={'column'} h={'100%'}>
<Box flex={'1 0 0'} h={0} px={5} py={4} overflow={'overlay'}>
{myApps &&
myApps.map((item) => (
<Flex
key={item._id}
py={2}
px={3}
mb={3}
cursor={'pointer'}
borderRadius={'md'}
alignItems={'center'}
{...(item._id === appId
? {
bg: 'white',
boxShadow: 'md'
}
: {
_hover: {
bg: 'myGray.200'
},
onClick: () => {
router.replace({
query: {
appId: item._id,
shareTeamId,
authToken: authToken
} as routerQueryType
});
}
})}
>
<Avatar src={item.avatar} w={'24px'} />
<Box ml={2} className={'textEllipsis'}>
{item.name}
</Box>
</Flex>
))}
</Box>
</Flex>
</Box>
{isPc && (
<Box borderRight={theme.borders.base} w={'220px'} flexShrink={0}>
<SliderApps showExist={false} apps={myApps} activeAppId={appId} />
</Box>
)}
<PageContainer flex={'1 0 0'} w={0} p={[0, '16px']} position={'relative'}>
<Flex h={'100%'} flexDirection={['column', 'row']} bg={'white'}>
{((children: React.ReactNode) => {
@@ -449,36 +304,35 @@ const OutLink = ({
onChangeChat={(chatId) => {
router.replace({
query: {
chatId: chatId || '',
appId,
shareTeamId,
authToken: authToken
} as routerQueryType
...router.query,
chatId: chatId || ''
}
});
if (!isPc) {
onCloseSlider();
}
}}
onDelHistory={(e) => delOneHistory({ ...e, appId })}
onDelHistory={(e) => delOneHistory({ ...e, appId, teamId, teamToken })}
onClearHistory={() => {
clearHistories({ appId });
clearHistories({ appId, teamId, teamToken });
router.replace({
query: {
appId,
shareTeamId,
authToken: authToken
} as routerQueryType
...router.query,
chatId: ''
}
});
}}
onSetHistoryTop={(e) => {
updateHistory({ ...e, appId });
updateHistory({ ...e, teamId, teamToken, appId });
}}
onSetCustomTitle={async (e) => {
updateHistory({
appId,
chatId: e.chatId,
title: e.title,
customTitle: e.title
customTitle: e.title,
teamId,
teamToken
});
}}
/>
@@ -496,8 +350,8 @@ const OutLink = ({
appAvatar={chatData.app.avatar}
appName={chatData.app.name}
history={chatData.history}
showHistory={true}
onOpenSlider={onOpenSlider}
showHistory
/>
{/* chat box */}
<Box flex={1}>
@@ -512,33 +366,33 @@ const OutLink = ({
onUpdateVariable={(e) => {}}
onStartChat={startChat}
onDelMessage={(e) =>
delOneHistoryItem({ ...e, appId: chatData.appId, chatId, outLinkUid })
delOneHistoryItem({ ...e, appId: chatData.appId, chatId, teamId, teamToken })
}
appId={chatData.appId}
shareTeamId={shareTeamId}
chatId={chatId}
outLinkUid={outLinkUid}
teamId={teamId}
teamToken={teamToken}
/>
</Box>
</Flex>
</Flex>
</PageContainer>
</Flex>
</MyBox>
);
};
export async function getServerSideProps(context: any) {
const shareTeamId = context?.query?.shareTeamId || '';
const teamId = context?.query?.teamId || '';
const appId = context?.query?.appId || '';
const chatId = context?.query?.chatId || '';
const authToken: string = context?.query?.authToken || '';
const teamToken: string = context?.query?.teamToken || '';
return {
props: {
shareTeamId,
teamId,
appId,
chatId,
authToken,
teamToken,
...(await serviceSideProps(context))
}
};

View File

@@ -95,6 +95,20 @@ const Provider = ({
const customSplitChar = processParamsForm.watch('customSplitChar');
const modeStaticParams = {
[TrainingModeEnum.auto]: {
chunkOverlapRatio: 0.2,
maxChunkSize: 2048,
minChunkSize: 100,
autoChunkSize: vectorModel?.defaultToken ? vectorModel?.defaultToken * 2 : 1024,
chunkSize: vectorModel?.defaultToken ? vectorModel?.defaultToken * 2 : 1024,
showChunkInput: false,
showPromptInput: false,
charsPointsPrice: agentModel.charsPointsPrice,
priceTip: t('core.dataset.import.Auto mode Estimated Price Tips', {
price: agentModel.charsPointsPrice
}),
uploadRate: 100
},
[TrainingModeEnum.chunk]: {
chunkSizeField: 'embeddingChunkSize' as ChunkSizeFieldType,
chunkOverlapRatio: 0.2,
@@ -149,10 +163,17 @@ const Provider = ({
[sources]
);
const predictPoints = useMemo(() => {
if (mode === TrainingModeEnum.qa) {
return +(((totalChunkChars * 1.5) / 1000) * agentModel.charsPointsPrice).toFixed(2);
const totalTokensPredict = totalChunkChars / 1000;
if (mode === TrainingModeEnum.auto) {
const price = totalTokensPredict * 1.3 * agentModel.charsPointsPrice;
return +price.toFixed(2);
}
return +((totalChunkChars / 1000) * vectorModel.charsPointsPrice).toFixed(2);
if (mode === TrainingModeEnum.qa) {
const price = totalTokensPredict * 1.2 * agentModel.charsPointsPrice;
return +price.toFixed(2);
}
return +(totalTokensPredict * vectorModel.charsPointsPrice).toFixed(2);
}, [agentModel.charsPointsPrice, mode, totalChunkChars, vectorModel.charsPointsPrice]);
const totalChunks = useMemo(
() => sources.reduce((sum, file) => sum + file.chunks.length, 0),

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from 'react';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import {
Box,
Flex,
@@ -60,6 +60,15 @@ function DataProcess({
onClose: onCloseCustomPrompt
} = useDisclosure();
const trainingModeList = useMemo(() => {
const list = Object.entries(TrainingTypeMap);
return list.filter(([key, value]) => {
if (feConfigs?.isPlus) return true;
return value.isPlus;
});
}, [feConfigs?.isPlus]);
useEffect(() => {
if (showPreviewChunks) {
splitSources2Chunks();
@@ -79,7 +88,7 @@ function DataProcess({
{t('core.dataset.import.Training mode')}
</Box>
<LeftRadio
list={Object.entries(TrainingTypeMap).map(([key, value]) => ({
list={trainingModeList.map(([key, value]) => ({
title: t(value.label),
value: key,
tooltip: t(value.tooltip)
@@ -91,7 +100,7 @@ function DataProcess({
setValue('mode', e);
setRefresh(!refresh);
}}
gridTemplateColumns={'1fr 1fr'}
gridTemplateColumns={'repeat(3,1fr)'}
defaultBg="white"
activeBg="white"
/>

View File

@@ -1,7 +1,7 @@
import React, { useState, Dispatch, useCallback } from 'react';
import { FormControl, Box, Input, Button } from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { PageTypeEnum } from '@/constants/user';
import { LoginPageTypeEnum } from '@/constants/user';
import { postFindPassword } from '@/web/support/user/api';
import { useSendCode } from '@/web/support/user/hooks/useSendCode';
import type { ResLogin } from '@/global/support/api/userRes.d';
@@ -9,7 +9,7 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
import { useSystemStore } from '@/web/common/system/useSystemStore';
interface Props {
setPageType: Dispatch<`${PageTypeEnum}`>;
setPageType: Dispatch<`${LoginPageTypeEnum}`>;
loginSuccess: (e: ResLogin) => void;
}
@@ -181,7 +181,7 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
color={'primary.700'}
cursor={'pointer'}
_hover={{ textDecoration: 'underline' }}
onClick={() => setPageType('login')}
onClick={() => setPageType(LoginPageTypeEnum.passwordLogin)}
>
</Box>

View File

@@ -1,251 +0,0 @@
import React, { useState, Dispatch, useCallback, useRef } from 'react';
import {
FormControl,
Flex,
Input,
Button,
Divider,
AbsoluteCenter,
Box,
Link,
useTheme
} from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { useRouter } from 'next/router';
import { PageTypeEnum } from '@/constants/user';
import { OAuthEnum } from '@fastgpt/global/support/user/constant';
import { postLogin } from '@/web/support/user/api';
import type { ResLogin } from '@/global/support/api/userRes';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { customAlphabet } from 'nanoid';
import { getDocPath } from '@/web/common/system/doc';
import Avatar from '@/components/Avatar';
import { LOGO_ICON } from '@fastgpt/global/common/system/constants';
import { useTranslation } from 'next-i18next';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 8);
interface Props {
setPageType: Dispatch<`${PageTypeEnum}`>;
loginSuccess: (e: ResLogin) => void;
}
interface LoginFormType {
username: string;
password: string;
}
const LoginForm = ({ setPageType, loginSuccess }: Props) => {
const { t } = useTranslation();
const router = useRouter();
const theme = useTheme();
const { lastRoute = '/app/list' } = router.query as { lastRoute: string };
const { toast } = useToast();
const { setLoginStore, feConfigs } = useSystemStore();
const {
register,
handleSubmit,
formState: { errors }
} = useForm<LoginFormType>();
const [requesting, setRequesting] = useState(false);
const onclickLogin = useCallback(
async ({ username, password }: LoginFormType) => {
setRequesting(true);
try {
loginSuccess(
await postLogin({
username,
password
})
);
toast({
title: '登录成功',
status: 'success'
});
} catch (error: any) {
toast({
title: error.message || '登录异常',
status: 'error'
});
}
setRequesting(false);
},
[loginSuccess, toast]
);
const redirectUri = `${location.origin}/login/provider`;
const state = useRef(nanoid());
const oAuthList = [
...(feConfigs?.oauth?.github
? [
{
label: t('support.user.login.Github'),
provider: OAuthEnum.github,
icon: 'common/gitFill',
redirectUrl: `https://github.com/login/oauth/authorize?client_id=${feConfigs?.oauth?.github}&redirect_uri=${redirectUri}&state=${state.current}&scope=user:email%20read:user`
}
]
: []),
...(feConfigs?.oauth?.google
? [
{
label: t('support.user.login.Google'),
provider: OAuthEnum.google,
icon: 'common/googleFill',
redirectUrl: `https://accounts.google.com/o/oauth2/v2/auth?client_id=${feConfigs?.oauth?.google}&redirect_uri=${redirectUri}&state=${state.current}&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email%20openid&include_granted_scopes=true`
}
]
: [])
];
const isCommunityVersion = feConfigs?.show_register === false && feConfigs?.show_git;
return (
<Flex flexDirection={'column'} h={'100%'}>
<Flex alignItems={'center'}>
<Flex
w={['48px', '56px']}
h={['48px', '56px']}
bg={'myGray.25'}
borderRadius={'xl'}
borderWidth={'1.5px'}
borderColor={'borderColor.base'}
alignItems={'center'}
justifyContent={'center'}
>
<Avatar src={LOGO_ICON} w={'30px'} />
</Flex>
<Box ml={3} fontSize={['2xl', '3xl']} fontWeight={'bold'}>
{feConfigs?.systemTitle}
</Box>
</Flex>
<Box
mt={'42px'}
onKeyDown={(e) => {
if (e.keyCode === 13 && !e.shiftKey && !requesting) {
handleSubmit(onclickLogin)();
}
}}
>
<FormControl isInvalid={!!errors.username}>
<Input
bg={'myGray.50'}
placeholder={isCommunityVersion ? '使用root用户登录' : '邮箱/手机号/用户名'}
{...register('username', {
required: '邮箱/手机号/用户名不能为空'
})}
></Input>
</FormControl>
<FormControl mt={6} isInvalid={!!errors.password}>
<Input
bg={'myGray.50'}
type={'password'}
placeholder={isCommunityVersion ? 'root密码为你设置的环境变量' : '密码'}
{...register('password', {
required: '密码不能为空',
maxLength: {
value: 20,
message: '密码最多 20 位'
}
})}
></Input>
</FormControl>
{feConfigs?.docUrl && (
<Box mt={7} fontSize={'sm'}>
使{' '}
<Link
href={getDocPath('/docs/agreement/disclaimer/')}
target={'_blank'}
color={'primary.500'}
>
</Link>
</Box>
)}
<Button
type="submit"
my={6}
w={'100%'}
size={['md', 'lg']}
colorScheme="blue"
isLoading={requesting}
onClick={handleSubmit(onclickLogin)}
>
{t('home.Login')}
</Button>
{feConfigs?.show_register && (
<>
<Flex align={'center'} justifyContent={'flex-end'} color={'primary.700'}>
<Box
cursor={'pointer'}
_hover={{ textDecoration: 'underline' }}
onClick={() => setPageType('forgetPassword')}
fontSize="sm"
>
?
</Box>
<Box mx={3} h={'16px'} w={'1.5px'} bg={'myGray.250'}></Box>
<Box
cursor={'pointer'}
_hover={{ textDecoration: 'underline' }}
onClick={() => setPageType('register')}
fontSize="sm"
>
</Box>
</Flex>
</>
)}
</Box>
<Box flex={1} />
{/* oauth */}
{feConfigs?.show_register && oAuthList.length > 0 && (
<>
<Box position={'relative'}>
<Divider />
<AbsoluteCenter bg="white" px="4" color={'myGray.500'}>
or
</AbsoluteCenter>
</Box>
<Box mt={8}>
{oAuthList.map((item) => (
<Box key={item.provider} _notFirst={{ mt: 4 }}>
<Button
variant={'whitePrimary'}
w={'100%'}
h={'42px'}
leftIcon={
<MyIcon
name={item.icon as any}
w={'20px'}
cursor={'pointer'}
color={'myGray.800'}
/>
}
onClick={() => {
setLoginStore({
provider: item.provider,
lastRoute,
state: state.current
});
router.replace(item.redirectUrl, '_self');
}}
>
{item.label}
</Button>
</Box>
))}
</Box>
</>
)}
</Flex>
);
};
export default LoginForm;

View File

@@ -0,0 +1,171 @@
import React, { useState, Dispatch, useCallback } from 'react';
import { FormControl, Flex, Input, Button, Box, Link } from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { LoginPageTypeEnum } from '@/constants/user';
import { postLogin } from '@/web/support/user/api';
import type { ResLogin } from '@/global/support/api/userRes';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { getDocPath } from '@/web/common/system/doc';
import { useTranslation } from 'next-i18next';
import FormLayout from './components/FormLayout';
interface Props {
setPageType: Dispatch<`${LoginPageTypeEnum}`>;
loginSuccess: (e: ResLogin) => void;
}
interface LoginFormType {
username: string;
password: string;
}
const LoginForm = ({ setPageType, loginSuccess }: Props) => {
const { t } = useTranslation();
const { toast } = useToast();
const { feConfigs } = useSystemStore();
const {
register,
handleSubmit,
formState: { errors }
} = useForm<LoginFormType>();
const [requesting, setRequesting] = useState(false);
const onclickLogin = useCallback(
async ({ username, password }: LoginFormType) => {
setRequesting(true);
try {
loginSuccess(
await postLogin({
username,
password
})
);
toast({
title: '登录成功',
status: 'success'
});
} catch (error: any) {
toast({
title: error.message || '登录异常',
status: 'error'
});
}
setRequesting(false);
},
[loginSuccess, toast]
);
const isCommunityVersion = feConfigs?.show_register === false && !feConfigs?.isPlus;
const loginOptions = [
feConfigs?.show_phoneLogin ? t('support.user.login.Phone number') : '',
feConfigs?.show_emailLogin ? t('support.user.login.Email') : '',
t('support.user.login.Username')
].filter(Boolean);
const placeholder = isCommunityVersion
? t('support.user.login.Root login')
: loginOptions.join('/');
return (
<FormLayout setPageType={setPageType} pageType={LoginPageTypeEnum.passwordLogin}>
<Box
mt={'42px'}
onKeyDown={(e) => {
if (e.keyCode === 13 && !e.shiftKey && !requesting) {
handleSubmit(onclickLogin)();
}
}}
>
<FormControl isInvalid={!!errors.username}>
<Input
bg={'myGray.50'}
placeholder={placeholder}
{...register('username', {
required: true
})}
></Input>
</FormControl>
<FormControl mt={6} isInvalid={!!errors.password}>
<Input
bg={'myGray.50'}
type={'password'}
placeholder={
isCommunityVersion
? t('support.user.login.Root password placeholder')
: t('support.user.login.Password')
}
{...register('password', {
required: true,
maxLength: {
value: 60,
message: '密码最多 60 位'
}
})}
></Input>
</FormControl>
{feConfigs?.docUrl && (
<Flex alignItems={'center'} mt={7} fontSize={'sm'}>
{t('support.user.login.Policy tip')}
<Link
ml={1}
href={getDocPath('/docs/agreement/terms/')}
target={'_blank'}
color={'primary.500'}
>
{t('support.user.login.Terms')}
</Link>
<Box mx={1}>{t('support.user.login.And')}</Box>
<Link
href={getDocPath('/docs/agreement/privacy/')}
target={'_blank'}
color={'primary.500'}
>
{t('support.user.login.Privacy')}
</Link>
</Flex>
)}
<Button
type="submit"
my={6}
w={'100%'}
size={['md', 'lg']}
colorScheme="blue"
isLoading={requesting}
onClick={handleSubmit(onclickLogin)}
>
{t('home.Login')}
</Button>
{feConfigs?.show_register && (
<>
<Flex align={'center'} justifyContent={'flex-end'} color={'primary.700'}>
<Box
cursor={'pointer'}
_hover={{ textDecoration: 'underline' }}
onClick={() => setPageType('forgetPassword')}
fontSize="sm"
>
{t('support.user.login.Forget Password')}
</Box>
<Box mx={3} h={'16px'} w={'1.5px'} bg={'myGray.250'}></Box>
<Box
cursor={'pointer'}
_hover={{ textDecoration: 'underline' }}
onClick={() => setPageType('register')}
fontSize="sm"
>
{t('support.user.login.Register')}
</Box>
</Flex>
</>
)}
</Box>
</FormLayout>
);
};
export default LoginForm;

View File

@@ -0,0 +1,60 @@
import React, { Dispatch } from 'react';
import { LoginPageTypeEnum } from '@/constants/user';
import type { ResLogin } from '@/global/support/api/userRes';
import { Box, Center, Image } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { getWXLoginQR, getWXLoginResult } from '@/web/support/user/api';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useRouter } from 'next/router';
import { useToast } from '@fastgpt/web/hooks/useToast';
import FormLayout from './components/FormLayout';
import { useTranslation } from 'next-i18next';
import Loading from '@/components/Loading';
interface Props {
loginSuccess: (e: ResLogin) => void;
setPageType: Dispatch<`${LoginPageTypeEnum}`>;
}
const WechatForm = ({ setPageType, loginSuccess }: Props) => {
const { t } = useTranslation();
const { toast } = useToast();
const { data: wechatInfo } = useQuery(['getWXLoginQR'], getWXLoginQR, {
onError(err) {
toast({
status: 'warning',
title: getErrText(err, '获取二维码失败')
});
}
});
useQuery(['getWXLoginResult', wechatInfo?.code], () => getWXLoginResult(wechatInfo?.code || ''), {
refetchInterval: 3 * 1000,
enabled: !!wechatInfo?.code,
onSuccess(data: ResLogin) {
loginSuccess(data);
}
});
return (
<FormLayout setPageType={setPageType} pageType={LoginPageTypeEnum.wechat}>
<Box>
<Box w={'full'} textAlign={'center'} pt={5}>
{t('support.user.login.Wx qr login')}
</Box>
<Box p={5} display={'flex'} w={'full'} justifyContent={'center'}>
{wechatInfo?.codeUrl ? (
<Image w="200px" src={wechatInfo?.codeUrl} alt="qrcode"></Image>
) : (
<Center w={200} h={200} position={'relative'}>
<Loading fixed={false} />
</Center>
)}
</Box>
</Box>
</FormLayout>
);
};
export default WechatForm;

View File

@@ -0,0 +1,136 @@
import Divider from '@/components/core/module/Flow/components/modules/Divider';
import { LoginPageTypeEnum } from '@/constants/user';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { AbsoluteCenter, Box, Button, Flex, Image } from '@chakra-ui/react';
import { LOGO_ICON } from '@fastgpt/global/common/system/constants';
import { OAuthEnum } from '@fastgpt/global/support/user/constant';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { customAlphabet } from 'nanoid';
import { useRouter } from 'next/router';
import { Dispatch, useRef } from 'react';
import { useTranslation } from 'react-i18next';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 8);
interface Props {
children: React.ReactNode;
setPageType: Dispatch<`${LoginPageTypeEnum}`>;
pageType: `${LoginPageTypeEnum}`;
}
const FormLayout = ({ children, setPageType, pageType }: Props) => {
const { t } = useTranslation();
const router = useRouter();
const { setLoginStore, feConfigs } = useSystemStore();
const { lastRoute = '/app/list' } = router.query as { lastRoute: string };
const state = useRef(nanoid());
const redirectUri = `${location.origin}/login/provider`;
const oAuthList = [
...(feConfigs?.oauth?.github
? [
{
label: t('support.user.login.Github'),
provider: OAuthEnum.github,
icon: 'common/gitFill',
redirectUrl: `https://github.com/login/oauth/authorize?client_id=${feConfigs?.oauth?.github}&redirect_uri=${redirectUri}&state=${state.current}&scope=user:email%20read:user`
}
]
: []),
...(feConfigs?.oauth?.google
? [
{
label: t('support.user.login.Google'),
provider: OAuthEnum.google,
icon: 'common/googleFill',
redirectUrl: `https://accounts.google.com/o/oauth2/v2/auth?client_id=${feConfigs?.oauth?.google}&redirect_uri=${redirectUri}&state=${state.current}&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email%20openid&include_granted_scopes=true`
}
]
: []),
...(feConfigs?.oauth?.wechat && pageType !== LoginPageTypeEnum.wechat
? [
{
label: t('support.user.login.Wechat'),
provider: OAuthEnum.wechat,
icon: 'common/wechatFill',
pageType: LoginPageTypeEnum.wechat
}
]
: []),
...(pageType !== LoginPageTypeEnum.passwordLogin
? [
{
label: t('support.user.login.Password login'),
provider: LoginPageTypeEnum.passwordLogin,
icon: 'support/account/passwordLogin',
pageType: LoginPageTypeEnum.passwordLogin
}
]
: [])
];
return (
<Flex flexDirection={'column'} h={'100%'}>
<Flex alignItems={'center'}>
<Flex
w={['48px', '56px']}
h={['48px', '56px']}
bg={'myGray.25'}
borderRadius={'xl'}
borderWidth={'1.5px'}
borderColor={'borderColor.base'}
alignItems={'center'}
justifyContent={'center'}
>
<Image src={LOGO_ICON} w={'24px'} alt={'icon'} />
</Flex>
<Box ml={3} fontSize={['2xl', '3xl']} fontWeight={'bold'}>
{feConfigs?.systemTitle}
</Box>
</Flex>
{children}
<Box flex={1} />
{feConfigs?.show_register && oAuthList.length > 0 && (
<>
<Box position={'relative'}>
<Divider />
<AbsoluteCenter bg="white" px="4" color={'myGray.500'}>
or
</AbsoluteCenter>
</Box>
<Box mt={8}>
{oAuthList.map((item) => (
<Box key={item.provider} _notFirst={{ mt: 4 }}>
<Button
variant={'whitePrimary'}
w={'100%'}
h={'42px'}
leftIcon={
<MyIcon
name={item.icon as any}
w={'20px'}
cursor={'pointer'}
color={'myGray.800'}
/>
}
onClick={() => {
item.redirectUrl &&
setLoginStore({
provider: item.provider,
lastRoute,
state: state.current
});
item.redirectUrl && router.replace(item.redirectUrl, '_self');
item.pageType && setPageType(item.pageType);
}}
>
{item.label}
</Button>
</Box>
))}
</Box>
</>
)}
</Flex>
);
};
export default FormLayout;

View File

@@ -1,7 +1,7 @@
import React, { useState, Dispatch, useCallback } from 'react';
import { FormControl, Box, Input, Button } from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { PageTypeEnum } from '@/constants/user';
import { LoginPageTypeEnum } from '@/constants/user';
import { postRegister } from '@/web/support/user/api';
import { useSendCode } from '@/web/support/user/hooks/useSendCode';
import type { ResLogin } from '@/global/support/api/userRes';
@@ -13,7 +13,7 @@ import { useTranslation } from 'next-i18next';
interface Props {
loginSuccess: (e: ResLogin) => void;
setPageType: Dispatch<`${PageTypeEnum}`>;
setPageType: Dispatch<`${LoginPageTypeEnum}`>;
}
interface RegisterType {
@@ -196,7 +196,7 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
color={'primary.700'}
cursor={'pointer'}
_hover={{ textDecoration: 'underline' }}
onClick={() => setPageType('login')}
onClick={() => setPageType(LoginPageTypeEnum.passwordLogin)}
>
</Box>

View File

@@ -1,25 +1,28 @@
import React, { useState, useCallback, useEffect } from 'react';
import { Box, Flex, Image, useDisclosure } from '@chakra-ui/react';
import { PageTypeEnum } from '@/constants/user';
import { Box, Center, Flex, useDisclosure } from '@chakra-ui/react';
import { LoginPageTypeEnum } from '@/constants/user';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import type { ResLogin } from '@/global/support/api/userRes.d';
import { useRouter } from 'next/router';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useChatStore } from '@/web/core/chat/storeChat';
import LoginForm from './components/LoginForm';
import LoginForm from './components/LoginForm/LoginForm';
import dynamic from 'next/dynamic';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { clearToken, setToken } from '@/web/support/user/auth';
import CommunityModal from '@/components/CommunityModal';
import Script from 'next/script';
import Loading from '@/components/Loading';
const RegisterForm = dynamic(() => import('./components/RegisterForm'));
const ForgetPasswordForm = dynamic(() => import('./components/ForgetPasswordForm'));
const WechatForm = dynamic(() => import('./components/LoginForm/WechatForm'));
const Login = () => {
const router = useRouter();
const { lastRoute = '' } = router.query as { lastRoute: string };
const { feConfigs } = useSystemStore();
const [pageType, setPageType] = useState<`${PageTypeEnum}`>(PageTypeEnum.login);
const [pageType, setPageType] = useState<`${LoginPageTypeEnum}`>();
const { setUserInfo } = useUserStore();
const { setLastChatId, setLastChatAppId } = useChatStore();
const { isOpen, onOpen, onClose } = useDisclosure();
@@ -39,11 +42,12 @@ const Login = () => {
[lastRoute, router, setLastChatId, setLastChatAppId, setUserInfo]
);
function DynamicComponent({ type }: { type: `${PageTypeEnum}` }) {
function DynamicComponent({ type }: { type: `${LoginPageTypeEnum}` }) {
const TypeMap = {
[PageTypeEnum.login]: LoginForm,
[PageTypeEnum.register]: RegisterForm,
[PageTypeEnum.forgetPassword]: ForgetPasswordForm
[LoginPageTypeEnum.passwordLogin]: LoginForm,
[LoginPageTypeEnum.register]: RegisterForm,
[LoginPageTypeEnum.forgetPassword]: ForgetPasswordForm,
[LoginPageTypeEnum.wechat]: WechatForm
};
const Component = TypeMap[type];
@@ -51,6 +55,13 @@ const Login = () => {
return <Component setPageType={setPageType} loginSuccess={loginSuccess} />;
}
/* default login type */
useEffect(() => {
if (!feConfigs.oauth) return;
setPageType(
feConfigs.oauth?.wechat ? LoginPageTypeEnum.wechat : LoginPageTypeEnum.passwordLogin
);
}, [feConfigs.oauth, feConfigs.oauth?.wechat]);
useEffect(() => {
clearToken();
router.prefetch('/app/list');
@@ -87,7 +98,13 @@ const Login = () => {
]}
>
<Box w={['100%', '380px']} flex={'1 0 0'}>
<DynamicComponent type={pageType} />
{pageType ? (
<DynamicComponent type={pageType} />
) : (
<Center w={'full'} h={'full'} position={'relative'}>
<Loading fixed={false} />
</Center>
)}
</Box>
{feConfigs?.concatMd && (
<Box

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { Box, Button, Flex, Grid } from '@chakra-ui/react';
import { Box, Flex, Grid } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
const FAQ = () => {
@@ -15,7 +15,7 @@ const FAQ = () => {
},
{
title: '什么是AI积分',
desc: '每次调用AI模型时都会消耗一定的AI积分。具体的计算标准可参考上方的“AI 积分计算标准”。\n1 字符=1中英文字符和标点符号会去掉换行和空格符号计算字符时包含对话上下文与知识库引用。'
desc: '每次调用AI模型时都会消耗一定的AI积分。具体的计算标准可参考上方的“AI 积分计算标准”。\nToken计算采用GPT3.5相同公式1Token≈0.7中文字符≈0.9英文单词连续出现的字符可能被认为是1个Tokens。'
},
{
title: 'AI积分会过期么',

View File

@@ -42,7 +42,7 @@ const Points = () => {
{llmModelList?.map((item, i) => (
<Flex key={item.model} py={4} bg={i % 2 !== 0 ? 'myGray.50' : ''}>
<Box flex={'1 0 0'}>{item.name}</Box>
<Box flex={'1 0 0'}>{item.charsPointsPrice} / 1000</Box>
<Box flex={'1 0 0'}>{item.charsPointsPrice} / 1000 Tokens</Box>
</Flex>
))}
</Box>
@@ -67,7 +67,7 @@ const Points = () => {
{vectorModelList?.map((item, i) => (
<Flex key={item.model} py={4} bg={i % 2 !== 0 ? 'myGray.50' : ''}>
<Box flex={'1 0 0'}>{item.name}</Box>
<Box flex={'1 0 0'}>{item.charsPointsPrice} / 1000</Box>
<Box flex={'1 0 0'}>{item.charsPointsPrice} / 1000 Tokens</Box>
</Flex>
))}
</Box>

View File

@@ -1,21 +1,12 @@
import { initSystemConfig } from '@/pages/api/common/system/getInitData';
import { startQueue } from '@/service/utils/tools';
import { setCron } from '@fastgpt/service/common/system/cron';
import { startTrainingQueue } from '@/service/core/dataset/training/utils';
export const startCron = () => {
setUpdateSystemConfigCron();
setTrainingQueueCron();
};
export const setUpdateSystemConfigCron = () => {
setCron('*/5 * * * *', () => {
initSystemConfig();
console.log('refresh system config');
});
};
export const setTrainingQueueCron = () => {
setCron('*/1 * * * *', () => {
startQueue();
startTrainingQueue();
});
};

View File

@@ -1,3 +1,5 @@
import { getTikTokenEnc } from '@fastgpt/global/common/string/tiktoken';
import { initHttpAgent } from '@fastgpt/service/common/middle/httpAgent';
import { existsSync, readFileSync } from 'fs';
export const readConfigData = (name: string) => {
@@ -23,3 +25,15 @@ export const readConfigData = (name: string) => {
return content;
};
export function initGlobal() {
if (global.communityPlugins) return;
global.communityPlugins = [];
global.simpleModeTemplates = [];
global.qaQueueLen = global.qaQueueLen ?? 0;
global.vectorQueueLen = global.vectorQueueLen ?? 0;
// init tikToken
getTikTokenEnc();
initHttpAgent();
}

View File

@@ -0,0 +1,21 @@
import { initSystemConfig } from '@/pages/api/common/system/getInitData';
import { createDatasetTrainingMongoWatch } from '@/service/core/dataset/training/utils';
import { MongoSystemConfigs } from '@fastgpt/service/common/system/config/schema';
export const startMongoWatch = async () => {
reloadConfigWatch();
createDatasetTrainingMongoWatch();
};
const reloadConfigWatch = () => {
const changeStream = MongoSystemConfigs.watch();
changeStream.on('change', async (change) => {
try {
if (change.operationType === 'insert') {
await initSystemConfig();
console.log('refresh system config');
}
} catch (error) {}
});
};

View File

@@ -35,7 +35,6 @@ import type {
import { pushDataListToTrainingQueue } from '@fastgpt/service/core/dataset/training/controller';
import { getVectorModel } from '@fastgpt/service/core/ai/model';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
import { startQueue } from '@/service/utils/tools';
export async function pushDataToTrainingQueue(
props: {
@@ -49,8 +48,6 @@ export async function pushDataToTrainingQueue(
datasetModelList: global.llmModels
});
startQueue();
return result;
}
@@ -129,7 +126,7 @@ export async function insertData2Dataset({
return {
insertId: _id,
charsLength: result.reduce((acc, cur) => acc + cur.charsLength, 0)
tokens: result.reduce((acc, cur) => acc + cur.tokens, 0)
};
}
@@ -240,11 +237,11 @@ export async function updateData2Dataset({
return result;
}
return {
charsLength: 0
tokens: 0
};
})
);
const charsLength = insertResult.reduce((acc, cur) => acc + cur.charsLength, 0);
const tokens = insertResult.reduce((acc, cur) => acc + cur.tokens, 0);
// console.log(clonePatchResult2Insert);
await mongoSessionRun(async (session) => {
// update mongo
@@ -273,7 +270,7 @@ export async function updateData2Dataset({
});
return {
charsLength
tokens
};
}
@@ -343,7 +340,7 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
};
};
const embeddingRecall = async ({ query, limit }: { query: string; limit: number }) => {
const { vectors, charsLength } = await getVectorsByText({
const { vectors, tokens } = await getVectorsByText({
model: getVectorModel(model),
input: query
});
@@ -407,7 +404,7 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
return {
embeddingRecallResults: formatResult,
charsLength
tokens
};
};
const fullTextRecall = async ({
@@ -552,22 +549,21 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
// multi query recall
const embeddingRecallResList: SearchDataResponseItemType[][] = [];
const fullTextRecallResList: SearchDataResponseItemType[][] = [];
let totalCharsLength = 0;
let totalTokens = 0;
await Promise.all(
queries.map(async (query) => {
const [{ charsLength, embeddingRecallResults }, { fullTextRecallResults }] =
await Promise.all([
embeddingRecall({
query,
limit: embeddingLimit
}),
fullTextRecall({
query,
limit: fullTextLimit
})
]);
totalCharsLength += charsLength;
const [{ tokens, embeddingRecallResults }, { fullTextRecallResults }] = await Promise.all([
embeddingRecall({
query,
limit: embeddingLimit
}),
fullTextRecall({
query,
limit: fullTextLimit
})
]);
totalTokens += tokens;
embeddingRecallResList.push(embeddingRecallResults);
fullTextRecallResList.push(fullTextRecallResults);
@@ -583,7 +579,7 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
).slice(0, fullTextLimit);
return {
charsLength: totalCharsLength,
tokens: totalTokens,
embeddingRecallResults: rrfEmbRecall,
fullTextRecallResults: rrfFTRecall
};
@@ -594,7 +590,7 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
const { embeddingLimit, fullTextLimit } = countRecallLimit();
// recall
const { embeddingRecallResults, fullTextRecallResults, charsLength } = await multiQueryRecall({
const { embeddingRecallResults, fullTextRecallResults, tokens } = await multiQueryRecall({
embeddingLimit,
fullTextLimit
});
@@ -666,7 +662,7 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
return {
searchRes: filterResultsByMaxTokens(scoreFilter, maxTokens),
charsLength,
tokens,
searchMode,
limit: maxTokens,
similarity,

View File

@@ -0,0 +1,31 @@
import { generateQA } from '@/service/events/generateQA';
import { generateVector } from '@/service/events/generateVector';
import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constants';
import { DatasetTrainingSchemaType } from '@fastgpt/global/core/dataset/type';
import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema';
export const createDatasetTrainingMongoWatch = () => {
const changeStream = MongoDatasetTraining.watch();
changeStream.on('change', async (change) => {
try {
if (change.operationType === 'insert') {
const fullDocument = change.fullDocument as DatasetTrainingSchemaType;
const { mode } = fullDocument;
if (mode === TrainingModeEnum.qa) {
generateQA();
} else if (mode === TrainingModeEnum.chunk) {
generateVector();
}
}
} catch (error) {}
});
};
export const startTrainingQueue = (fast?: boolean) => {
const max = global.systemEnv?.qaMaxProcess || 10;
for (let i = 0; i < max; i++) {
generateQA();
generateVector();
}
};

View File

@@ -10,8 +10,10 @@ import { Prompt_AgentQA } from '@/global/core/prompt/agent';
import type { PushDatasetDataChunkProps } from '@fastgpt/global/core/dataset/api.d';
import { pushDataToTrainingQueue } from '@/service/core/dataset/data/controller';
import { getLLMModel } from '@fastgpt/service/core/ai/model';
import { checkInvalidChunkAndLock, checkTeamAiPointsAndLock } from './utils';
import { countGptMessagesChars } from '@fastgpt/service/core/chat/utils';
import { checkTeamAiPointsAndLock } from './utils';
import { checkInvalidChunkAndLock } from '@fastgpt/service/core/dataset/training/utils';
import { addMinutes } from 'date-fns';
import { countGptMessagesTokens } from '@fastgpt/global/common/string/tiktoken';
const reduceQueue = () => {
global.qaQueueLen = global.qaQueueLen > 0 ? global.qaQueueLen - 1 : 0;
@@ -20,9 +22,11 @@ const reduceQueue = () => {
};
export async function generateQA(): Promise<any> {
if (global.qaQueueLen >= global.systemEnv.qaMaxProcess) return;
const max = global.systemEnv?.qaMaxProcess || 10;
if (global.qaQueueLen >= max) return;
global.qaQueueLen++;
const startTime = Date.now();
// get training data
const {
data,
@@ -33,7 +37,7 @@ export async function generateQA(): Promise<any> {
try {
const data = await MongoDatasetTraining.findOneAndUpdate(
{
lockTime: { $lte: new Date(Date.now() - 6 * 60 * 1000) },
lockTime: { $lte: addMinutes(new Date(), -6) },
mode: TrainingModeEnum.qa
},
{
@@ -66,7 +70,7 @@ export async function generateQA(): Promise<any> {
text: data.q
};
} catch (error) {
console.log(`Get Training Data error`, error);
addLog.error(`[QA Queue] Error`, error);
return {
error: true
};
@@ -75,7 +79,7 @@ export async function generateQA(): Promise<any> {
if (done || !data) {
if (reduceQueue()) {
console.log(`QA】Task Done`);
addLog.info(`[QA Queue] Done`);
}
return;
}
@@ -83,17 +87,15 @@ export async function generateQA(): Promise<any> {
reduceQueue();
return generateQA();
}
console.log('Start QA Training');
// auth balance
if (!(await checkTeamAiPointsAndLock(data.teamId, data.tmbId))) {
console.log('balance not enough');
reduceQueue();
return generateQA();
}
addLog.info(`[QA Queue] Start`);
try {
const startTime = Date.now();
const model = getLLMModel(data.model)?.model;
const prompt = `${data.prompt || Prompt_AgentQA.description}
${replaceVariable(Prompt_AgentQA.fixedText, { text })}`;
@@ -119,8 +121,8 @@ ${replaceVariable(Prompt_AgentQA.fixedText, { text })}`;
const qaArr = formatSplitText(answer, text); // 格式化后的QA对
addLog.info(`QA Training Finish`, {
time: `${(Date.now() - startTime) / 1000}s`,
addLog.info(`[QA Queue] Finish`, {
time: Date.now() - startTime,
splitLength: qaArr.length,
usage: chatResponse.usage
});
@@ -146,7 +148,7 @@ ${replaceVariable(Prompt_AgentQA.fixedText, { text })}`;
pushQAUsage({
teamId: data.teamId,
tmbId: data.tmbId,
charsLength: countGptMessagesChars(messages).length,
tokens: countGptMessagesTokens(messages),
billId: data.billId,
model
});

View File

@@ -2,8 +2,10 @@ import { insertData2Dataset } from '@/service/core/dataset/data/controller';
import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema';
import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constants';
import { pushGenerateVectorUsage } from '@/service/support/wallet/usage/push';
import { checkInvalidChunkAndLock, checkTeamAiPointsAndLock } from './utils';
import { delay } from '@fastgpt/global/common/system/utils';
import { checkTeamAiPointsAndLock } from './utils';
import { checkInvalidChunkAndLock } from '@fastgpt/service/core/dataset/training/utils';
import { addMinutes } from 'date-fns';
import { addLog } from '@fastgpt/service/common/system/log';
const reduceQueue = () => {
global.vectorQueueLen = global.vectorQueueLen > 0 ? global.vectorQueueLen - 1 : 0;
@@ -13,7 +15,8 @@ const reduceQueue = () => {
/* 索引生成队列。每导入一次,就是一个单独的线程 */
export async function generateVector(): Promise<any> {
if (global.vectorQueueLen >= global.systemEnv.vectorMaxProcess) return;
const max = global.systemEnv?.vectorMaxProcess || 10;
if (global.vectorQueueLen >= max) return;
global.vectorQueueLen++;
const start = Date.now();
@@ -27,7 +30,7 @@ export async function generateVector(): Promise<any> {
try {
const data = await MongoDatasetTraining.findOneAndUpdate(
{
lockTime: { $lte: new Date(Date.now() - 1 * 60 * 1000) },
lockTime: { $lte: addMinutes(new Date(), -1) },
mode: TrainingModeEnum.chunk
},
{
@@ -68,7 +71,7 @@ export async function generateVector(): Promise<any> {
}
};
} catch (error) {
console.log(`Get Training Data error`, error);
addLog.error(`Get Training Data error`, error);
return {
error: true
};
@@ -77,11 +80,12 @@ export async function generateVector(): Promise<any> {
if (done || !data) {
if (reduceQueue()) {
console.log(`【index】Task done`);
addLog.info(`[Vector Queue] Done`);
}
return;
}
if (error) {
addLog.error(`[Vector Queue] Error`, { error });
reduceQueue();
return generateVector();
}
@@ -92,6 +96,8 @@ export async function generateVector(): Promise<any> {
return generateVector();
}
addLog.info(`[Vector Queue] Start`);
// create vector and insert
try {
// invalid data
@@ -103,7 +109,7 @@ export async function generateVector(): Promise<any> {
}
// insert to dataset
const { charsLength } = await insertData2Dataset({
const { tokens } = await insertData2Dataset({
teamId: data.teamId,
tmbId: data.tmbId,
datasetId: data.datasetId,
@@ -119,7 +125,7 @@ export async function generateVector(): Promise<any> {
pushGenerateVectorUsage({
teamId: data.teamId,
tmbId: data.tmbId,
charsLength,
tokens,
model: data.model,
billId: data.billId
});
@@ -129,7 +135,9 @@ export async function generateVector(): Promise<any> {
reduceQueue();
generateVector();
console.log(`embedding finished, time: ${Date.now() - start}ms`);
addLog.info(`[Vector Queue] Finish`, {
time: Date.now() - start
});
} catch (err: any) {
reduceQueue();

View File

@@ -2,10 +2,6 @@ import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
import { checkTeamAIPoints } from '@fastgpt/service/support/permission/teamLimit';
import { sendOneInform } from '../support/user/inform/api';
import { lockTrainingDataByTeamId } from '@fastgpt/service/core/dataset/training/controller';
import { DatasetTrainingSchemaType } from '@fastgpt/global/core/dataset/type';
import { addLog } from '@fastgpt/service/common/system/log';
import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema';
import { getErrText } from '@fastgpt/global/common/error/utils';
export const checkTeamAiPointsAndLock = async (teamId: string, tmbId: string) => {
try {
@@ -29,41 +25,3 @@ export const checkTeamAiPointsAndLock = async (teamId: string, tmbId: string) =>
return false;
}
};
export const checkInvalidChunkAndLock = async ({
err,
errText,
data
}: {
err: any;
errText: string;
data: DatasetTrainingSchemaType;
}) => {
if (err?.response) {
addLog.info(`openai error: ${errText}`, {
status: err.response?.status,
stateusText: err.response?.statusText,
data: err.response?.data
});
} else {
console.log(err);
addLog.error(getErrText(err, errText));
}
if (
err?.message === 'invalid message format' ||
err?.type === 'invalid_request_error' ||
err?.code === 500
) {
addLog.info('Lock training data');
console.log(err);
try {
await MongoDatasetTraining.findByIdAndUpdate(data._id, {
lockTime: new Date('2998/5/5')
});
} catch (error) {}
return true;
}
return false;
};

View File

@@ -1,6 +1,7 @@
import { adaptChat2GptMessages } from '@fastgpt/global/core/chat/adapt';
import { ChatContextFilter, countMessagesChars } from '@fastgpt/service/core/chat/utils';
import type { moduleDispatchResType, ChatItemType } from '@fastgpt/global/core/chat/type.d';
import { ChatContextFilter } from '@fastgpt/service/core/chat/utils';
import { countMessagesTokens } from '@fastgpt/global/common/string/tiktoken';
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { getAIApi } from '@fastgpt/service/core/ai/config';
import type {
@@ -14,7 +15,7 @@ import { Prompt_CQJson } from '@/global/core/prompt/agent';
import { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
import { ModelTypeEnum, getLLMModel } from '@fastgpt/service/core/ai/model';
import { getHistories } from '../utils';
import { formatModelChars2Points } from '@/service/support/wallet/usage/utils';
import { formatModelChars2Points } from '@fastgpt/service/support/wallet/usage/utils';
type Props = ModuleDispatchProps<{
[ModuleInputKeyEnum.aiModel]: string;
@@ -46,7 +47,7 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
const chatHistories = getHistories(history, histories);
const { arg, charsLength } = await (async () => {
const { arg, tokens } = await (async () => {
if (cqModel.toolChoice) {
return toolChoice({
...props,
@@ -65,7 +66,7 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
const { totalPoints, modelName } = formatModelChars2Points({
model: cqModel.model,
charsLength,
tokens,
modelType: ModelTypeEnum.llm
});
@@ -75,7 +76,7 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
totalPoints: user.openaiAccount?.key ? 0 : totalPoints,
model: modelName,
query: userChatInput,
charsLength,
tokens,
cqList: agents,
cqResult: result.value,
contextTotalLen: chatHistories.length + 2
@@ -85,7 +86,7 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
moduleName: name,
totalPoints: user.openaiAccount?.key ? 0 : totalPoints,
model: modelName,
charsLength
tokens
}
]
};
@@ -136,6 +137,13 @@ ${systemPrompt}
required: ['type']
}
};
const tools: any = [
{
type: 'function',
function: agentFunction
}
];
const ai = getAIApi({
userKey: user.openaiAccount,
timeout: 480000
@@ -144,13 +152,8 @@ ${systemPrompt}
const response = await ai.chat.completions.create({
model: cqModel.model,
temperature: 0,
messages: [...adaptMessages],
tools: [
{
type: 'function',
function: agentFunction
}
],
messages: adaptMessages,
tools,
tool_choice: { type: 'function', function: { name: agentFunName } }
});
@@ -158,13 +161,10 @@ ${systemPrompt}
const arg = JSON.parse(
response?.choices?.[0]?.message?.tool_calls?.[0]?.function?.arguments || ''
);
const functionChars =
agentFunction.description.length +
agentFunction.parameters.properties.type.description.length;
return {
arg,
charsLength: countMessagesChars(messages) + functionChars
tokens: countMessagesTokens(messages, tools)
};
} catch (error) {
console.log(agentFunction.parameters);
@@ -174,7 +174,7 @@ ${systemPrompt}
return {
arg: {},
charsLength: 0
tokens: 0
};
}
}
@@ -216,7 +216,7 @@ async function completions({
agents.find((item) => answer.includes(item.key) || answer.includes(item.value))?.key || '';
return {
charsLength: countMessagesChars(messages),
tokens: countMessagesTokens(messages),
arg: { type: id }
};
}

View File

@@ -1,6 +1,7 @@
import { adaptChat2GptMessages } from '@fastgpt/global/core/chat/adapt';
import { ChatContextFilter, countMessagesChars } from '@fastgpt/service/core/chat/utils';
import type { moduleDispatchResType, ChatItemType } from '@fastgpt/global/core/chat/type.d';
import { ChatContextFilter } from '@fastgpt/service/core/chat/utils';
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
import { countMessagesTokens } from '@fastgpt/global/common/string/tiktoken';
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { getAIApi } from '@fastgpt/service/core/ai/config';
import type {
@@ -14,7 +15,7 @@ import { replaceVariable } from '@fastgpt/global/common/string/tools';
import { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
import { getHistories } from '../utils';
import { ModelTypeEnum, getLLMModel } from '@fastgpt/service/core/ai/model';
import { formatModelChars2Points } from '@/service/support/wallet/usage/utils';
import { formatModelChars2Points } from '@fastgpt/service/support/wallet/usage/utils';
type Props = ModuleDispatchProps<{
[ModuleInputKeyEnum.history]?: ChatItemType[];
@@ -46,7 +47,7 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
const extractModel = getLLMModel(model);
const chatHistories = getHistories(history, histories);
const { arg, charsLength } = await (async () => {
const { arg, tokens } = await (async () => {
if (extractModel.toolChoice) {
return toolChoice({
...props,
@@ -85,7 +86,7 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
const { totalPoints, modelName } = formatModelChars2Points({
model: extractModel.model,
charsLength,
tokens,
modelType: ModelTypeEnum.llm
});
@@ -98,7 +99,7 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
totalPoints: user.openaiAccount?.key ? 0 : totalPoints,
model: modelName,
query: content,
charsLength,
tokens,
extractDescription: description,
extractResult: arg,
contextTotalLen: chatHistories.length + 2
@@ -108,7 +109,7 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
moduleName: name,
totalPoints: user.openaiAccount?.key ? 0 : totalPoints,
model: modelName,
charsLength
tokens
}
]
};
@@ -170,6 +171,12 @@ ${description || '根据用户要求获取适当的 JSON 字符串。'}
required: extractKeys.filter((item) => item.required).map((item) => item.key)
}
};
const tools: any = [
{
type: 'function',
function: agentFunction
}
];
const ai = getAIApi({
userKey: user.openaiAccount,
@@ -180,12 +187,7 @@ ${description || '根据用户要求获取适当的 JSON 字符串。'}
model: extractModel.model,
temperature: 0,
messages: [...adaptMessages],
tools: [
{
type: 'function',
function: agentFunction
}
],
tools,
tool_choice: { type: 'function', function: { name: agentFunName } }
});
@@ -202,12 +204,9 @@ ${description || '根据用户要求获取适当的 JSON 字符串。'}
}
})();
const functionChars =
description.length + extractKeys.reduce((sum, item) => sum + item.desc.length, 0);
return {
rawResponse: response?.choices?.[0]?.message?.tool_calls?.[0]?.function?.arguments || '',
charsLength: countMessagesChars(messages) + functionChars,
tokens: countMessagesTokens(messages, tools),
arg
};
}
@@ -257,7 +256,7 @@ Human: ${content}`
if (start === -1 || end === -1)
return {
rawResponse: answer,
charsLength: countMessagesChars(messages),
tokens: countMessagesTokens(messages),
arg: {}
};
@@ -269,14 +268,14 @@ Human: ${content}`
try {
return {
rawResponse: answer,
charsLength: countMessagesChars(messages),
tokens: countMessagesTokens(messages),
arg: JSON.parse(jsonStr) as Record<string, any>
};
} catch (error) {
return {
rawResponse: answer,
charsLength: countMessagesChars(messages),
tokens: countMessagesTokens(messages),
arg: {}
};
}

View File

@@ -1,17 +1,17 @@
import type { NextApiResponse } from 'next';
import { ChatContextFilter, countMessagesChars } from '@fastgpt/service/core/chat/utils';
import type { moduleDispatchResType, ChatItemType } from '@fastgpt/global/core/chat/type.d';
import { ChatContextFilter } from '@fastgpt/service/core/chat/utils';
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { sseResponseEventEnum } from '@fastgpt/service/common/response/constant';
import { textAdaptGptResponse } from '@/utils/adapt';
import { getAIApi } from '@fastgpt/service/core/ai/config';
import type { ChatCompletion, StreamChatType } from '@fastgpt/global/core/ai/type.d';
import { formatModelChars2Points } from '@/service/support/wallet/usage/utils';
import { formatModelChars2Points } from '@fastgpt/service/support/wallet/usage/utils';
import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
import { postTextCensor } from '@/service/common/censor';
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constant';
import type { ModuleDispatchResponse, ModuleItemType } from '@fastgpt/global/core/module/type.d';
import { countMessagesTokens, sliceMessagesTB } from '@fastgpt/global/common/string/tiktoken';
import { countMessagesTokens } from '@fastgpt/global/common/string/tiktoken';
import { adaptChat2GptMessages } from '@fastgpt/global/core/chat/adapt';
import { Prompt_QuotePromptList, Prompt_QuoteTemplateList } from '@/global/core/prompt/AIChat';
import type { AIChatModuleProps } from '@fastgpt/global/core/module/node/type.d';
@@ -98,7 +98,7 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
userChatInput,
systemPrompt
});
const { max_tokens } = getMaxTokens({
const { max_tokens } = await getMaxTokens({
model: modelConstantsData,
maxToken,
filterMessages
@@ -137,8 +137,6 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
const response = await ai.chat.completions.create(
{
presence_penalty: 0,
frequency_penalty: 0,
...modelConstantsData?.defaultConfig,
model: modelConstantsData.model,
temperature,
@@ -189,10 +187,10 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
}
})();
const charsLength = countMessagesChars(completeMessages);
const tokens = countMessagesTokens(completeMessages);
const { totalPoints, modelName } = formatModelChars2Points({
model,
charsLength,
tokens,
modelType: ModelTypeEnum.llm
});
@@ -201,7 +199,7 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
[ModuleOutputKeyEnum.responseData]: {
totalPoints: user.openaiAccount?.key ? 0 : totalPoints,
model: modelName,
charsLength,
tokens,
query: `${userChatInput}`,
maxToken: max_tokens,
quoteList: filterQuoteQA,
@@ -213,7 +211,7 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
moduleName: name,
totalPoints: user.openaiAccount?.key ? 0 : totalPoints,
model: modelName,
charsLength
tokens
}
],
history: completeMessages
@@ -292,7 +290,7 @@ function getChatMessages({
const filterMessages = ChatContextFilter({
messages,
maxTokens: Math.ceil(model.maxContext - 300) // filter token. not response maxToken
maxTokens: model.maxContext - 300 // filter token. not response maxToken
});
const adaptMessages = adaptChat2GptMessages({ messages: filterMessages, reserveId: false });
@@ -315,11 +313,12 @@ function getMaxTokens({
const tokensLimit = model.maxContext;
/* count response max token */
const promptsToken = countMessagesTokens({
messages: filterMessages
});
const promptsToken = countMessagesTokens(filterMessages);
maxToken = promptsToken + maxToken > tokensLimit ? tokensLimit - promptsToken : maxToken;
if (maxToken <= 0) {
return Promise.reject('Over max token');
}
return {
max_tokens: maxToken
};

View File

@@ -1,5 +1,5 @@
import type { moduleDispatchResType } from '@fastgpt/global/core/chat/type.d';
import { formatModelChars2Points } from '@/service/support/wallet/usage/utils';
import { formatModelChars2Points } from '@fastgpt/service/support/wallet/usage/utils';
import type { SelectedDatasetType } from '@fastgpt/global/core/module/api.d';
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
import type {
@@ -12,7 +12,7 @@ import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/mo
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
import { getHistories } from '../utils';
import { datasetSearchQueryExtension } from '@fastgpt/service/core/dataset/search/utils';
import { ChatModuleBillType } from '@fastgpt/global/support/wallet/bill/type';
import { ChatModuleUsageType } from '@fastgpt/global/support/wallet/bill/type';
import { checkTeamReRankPermission } from '@fastgpt/service/support/permission/teamLimit';
type DatasetSearchProps = ModuleDispatchProps<{
@@ -85,7 +85,7 @@ export async function dispatchDatasetSearch(
// start search
const {
searchRes,
charsLength,
tokens,
usingSimilarityFilter,
usingReRank: searchUsingReRank
} = await searchDatasetData({
@@ -104,37 +104,37 @@ export async function dispatchDatasetSearch(
// vector
const { totalPoints, modelName } = formatModelChars2Points({
model: vectorModel.model,
charsLength,
tokens,
modelType: ModelTypeEnum.vector
});
const responseData: moduleDispatchResType & { totalPoints: number } = {
totalPoints,
query: concatQueries.join('\n'),
model: modelName,
charsLength,
tokens,
similarity: usingSimilarityFilter ? similarity : undefined,
limit,
searchMode,
searchUsingReRank: searchUsingReRank
};
const moduleDispatchBills: ChatModuleBillType[] = [
const moduleDispatchBills: ChatModuleUsageType[] = [
{
totalPoints,
moduleName: module.name,
model: modelName,
charsLength
tokens
}
];
if (aiExtensionResult) {
const { totalPoints, modelName } = formatModelChars2Points({
model: aiExtensionResult.model,
charsLength: aiExtensionResult.charsLength,
tokens: aiExtensionResult.tokens,
modelType: ModelTypeEnum.llm
});
responseData.totalPoints += totalPoints;
responseData.charsLength = aiExtensionResult.charsLength;
responseData.tokens = aiExtensionResult.tokens;
responseData.extensionModel = modelName;
responseData.extensionResult =
aiExtensionResult.extensionQueries?.join('\n') ||
@@ -144,7 +144,7 @@ export async function dispatchDatasetSearch(
totalPoints,
moduleName: 'core.module.template.Query extension',
model: modelName,
charsLength: aiExtensionResult.charsLength
tokens: aiExtensionResult.tokens
});
}

View File

@@ -28,7 +28,7 @@ import { dispatchRunPlugin } from './plugin/run';
import { dispatchPluginInput } from './plugin/runInput';
import { dispatchPluginOutput } from './plugin/runOutput';
import { valueTypeFormat } from './utils';
import { ChatModuleBillType } from '@fastgpt/global/support/wallet/bill/type';
import { ChatModuleUsageType } from '@fastgpt/global/support/wallet/bill/type';
const callbackMap: Record<`${FlowNodeTypeEnum}`, Function> = {
[FlowNodeTypeEnum.historyNode]: dispatchHistory,
@@ -83,7 +83,7 @@ export async function dispatchModules({
// let storeData: Record<string, any> = {}; // after module used
let chatResponse: ChatHistoryItemResType[] = []; // response request and save to database
let chatAnswerText = ''; // AI answer
let chatModuleBills: ChatModuleBillType[] = [];
let chatModuleBills: ChatModuleUsageType[] = [];
let runningTime = Date.now();
function pushStore(
@@ -95,7 +95,7 @@ export async function dispatchModules({
}: {
answerText?: string;
responseData?: ChatHistoryItemResType | ChatHistoryItemResType[];
moduleDispatchBills?: ChatModuleBillType[];
moduleDispatchBills?: ChatModuleUsageType[];
}
) {
const time = Date.now();
@@ -165,7 +165,6 @@ export async function dispatchModules({
const filterModules = nextRunModules.filter((module) => {
if (set.has(module.moduleId)) return false;
set.add(module.moduleId);
``;
return true;
});

View File

@@ -95,7 +95,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
moduleName: plugin.name,
totalPoints: moduleDispatchBills.reduce((sum, item) => sum + (item.totalPoints || 0), 0),
model: plugin.name,
charsLength: 0
tokens: 0
}
],
...(output ? output.pluginOutput : {})

View File

@@ -10,6 +10,7 @@ import {
import axios from 'axios';
import { valueTypeFormat } from '../utils';
import { SERVICE_LOCAL_HOST } from '@fastgpt/service/common/system/tools';
import { addLog } from '@fastgpt/service/common/system/log';
type PropsArrType = {
key: string;
@@ -130,7 +131,7 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
...results
};
} catch (error) {
const err = httpRequestErrorResponseData(error)
addLog.error('Http request error', error);
return {
[ModuleOutputKeyEnum.failed]: true,
[ModuleOutputKeyEnum.responseData]: {
@@ -138,7 +139,7 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
params: Object.keys(params).length > 0 ? params : undefined,
body: Object.keys(requestBody).length > 0 ? requestBody : undefined,
headers: Object.keys(headers).length > 0 ? headers : undefined,
httpResult: { error: err }
httpResult: { error: formatHttpError(error) }
}
};
}
@@ -280,21 +281,14 @@ function removeUndefinedSign(obj: Record<string, any>) {
}
return obj;
}
function httpRequestErrorResponseData(error: any) {
try {
return {
message: error?.message || undefined,
name: error?.name || undefined,
method: error?.config?.method || undefined,
baseURL: error?.config?.baseURL || undefined,
url: error?.config?.url || undefined,
code: error?.code || undefined,
status: error?.status || undefined
}
} catch (error) {
return {
message: 'Request Failed',
name: "AxiosError",
};
}
function formatHttpError(error: any) {
return {
message: error?.message,
name: error?.name,
method: error?.config?.method,
baseURL: error?.config?.baseURL,
url: error?.config?.url,
code: error?.code,
status: error?.status
};
}

View File

@@ -5,7 +5,7 @@ import type {
} from '@fastgpt/global/core/module/type.d';
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import { ModelTypeEnum, getLLMModel } from '@fastgpt/service/core/ai/model';
import { formatModelChars2Points } from '@/service/support/wallet/usage/utils';
import { formatModelChars2Points } from '@fastgpt/service/support/wallet/usage/utils';
import { queryExtension } from '@fastgpt/service/core/ai/functions/queryExtension';
import { getHistories } from '../utils';
import { hashStr } from '@fastgpt/global/common/string/tools';
@@ -32,7 +32,7 @@ export const dispatchQueryExtension = async ({
const queryExtensionModel = getLLMModel(model);
const chatHistories = getHistories(history, histories);
const { extensionQueries, charsLength } = await queryExtension({
const { extensionQueries, tokens } = await queryExtension({
chatBg: systemPrompt,
query: userChatInput,
histories: chatHistories,
@@ -43,7 +43,7 @@ export const dispatchQueryExtension = async ({
const { totalPoints, modelName } = formatModelChars2Points({
model: queryExtensionModel.model,
charsLength,
tokens,
modelType: ModelTypeEnum.llm
});
@@ -60,7 +60,7 @@ export const dispatchQueryExtension = async ({
[ModuleOutputKeyEnum.responseData]: {
totalPoints,
model: modelName,
charsLength,
tokens,
query: userChatInput,
textOutput: JSON.stringify(filterSameQueries)
},
@@ -69,7 +69,7 @@ export const dispatchQueryExtension = async ({
moduleName: module.name,
totalPoints,
model: modelName,
charsLength
tokens
}
],
[ModuleOutputKeyEnum.text]: JSON.stringify(filterSameQueries)

View File

@@ -1,4 +1,3 @@
import { startQueue } from './utils/tools';
import { PRICE_SCALE } from '@fastgpt/global/support/wallet/constants';
import { MongoUser } from '@fastgpt/service/support/user/schema';
import { connectMongo } from '@fastgpt/service/common/mongo/init';
@@ -9,22 +8,29 @@ import { initVectorStore } from '@fastgpt/service/common/vectorStore/controller'
import { getInitConfig } from '@/pages/api/common/system/getInitData';
import { startCron } from './common/system/cron';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
import { initGlobal } from './common/system';
import { startMongoWatch } from './common/system/volumnMongoWatch';
import { startTrainingQueue } from './core/dataset/training/utils';
/**
* connect MongoDB and init data
*/
export function connectToDatabase(): Promise<void> {
return connectMongo({
beforeHook: () => {},
beforeHook: () => {
initGlobal();
},
afterHook: async () => {
initVectorStore();
// start queue
startQueue();
startMongoWatch();
// cron
startCron();
// init system config
getInitConfig();
// cron
startCron();
// init vector database
await initVectorStore();
// start queue
startTrainingQueue(true);
initRootUser();
}
@@ -62,7 +68,7 @@ async function initRootUser() {
rootId = _id;
}
// init root team
await createDefaultTeam({ userId: rootId, maxSize: 1, balance: 9999 * PRICE_SCALE, session });
await createDefaultTeam({ userId: rootId, balance: 9999 * PRICE_SCALE, session });
});
console.log(`root user init:`, {

View File

@@ -1,11 +1,16 @@
import { ChatSchema } from '@fastgpt/global/core/chat/type';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { AuthModeType } from '@fastgpt/service/support/permission/type';
import { authOutLink } from './outLink';
import { authOutLink, authOutLinkInit } from './outLink';
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
import { authUserRole } from '@fastgpt/service/support/permission/auth/user';
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import { AuthResponseType } from '@fastgpt/global/support/permission/type';
import { authTeamSpaceToken } from './team';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { authOutLinkValid } from '@fastgpt/service/support/permission/auth/outLink';
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
/*
outLink: Must be the owner
token: team owner and chat owner have all permissions
@@ -14,46 +19,51 @@ export async function autChatCrud({
appId,
chatId,
shareId,
shareTeamId,
outLinkUid,
teamId: spaceTeamId,
teamToken,
per = 'owner',
...props
}: AuthModeType & {
appId: string;
chatId?: string;
shareTeamId?: string;
shareId?: string;
outLinkUid?: string;
teamId?: string;
teamToken?: string;
}): Promise<{
chat?: ChatSchema;
isOutLink: boolean;
uid?: string;
}> {
const isOutLink = Boolean((shareId || shareTeamId) && outLinkUid);
const isOutLink = Boolean((shareId || spaceTeamId) && outLinkUid);
if (!chatId) return { isOutLink, uid: outLinkUid };
const chat = await MongoChat.findOne({ appId, chatId }).lean();
if (!chat) return { isOutLink, uid: outLinkUid };
const { uid } = await (async () => {
// outLink Auth
if (shareId && outLinkUid) {
const { uid } = await authOutLink({ shareId, outLinkUid });
// auth outLinkUid
if (chat.shareId === shareId && chat.outLinkUid === uid) {
if (!chat || (chat.shareId === shareId && chat.outLinkUid === uid)) {
return { uid };
}
return Promise.reject(ChatErrEnum.unAuthChat);
}
if (shareTeamId && outLinkUid) {
if (chat.teamId == shareTeamId && chat.outLinkUid === outLinkUid) {
return { uid: outLinkUid };
// auth team space chat
if (spaceTeamId && teamToken) {
const { uid } = await authTeamSpaceToken({ teamId: spaceTeamId, teamToken });
if (!chat || (String(chat.teamId) === String(spaceTeamId) && chat.outLinkUid === uid)) {
return { uid };
}
return Promise.reject(ChatErrEnum.unAuthChat);
}
// req auth
if (!chat) return { id: outLinkUid };
// auth req
const { teamId, tmbId, role } = await authUserRole(props);
if (String(teamId) !== String(chat.teamId)) return Promise.reject(ChatErrEnum.unAuthChat);
@@ -67,9 +77,61 @@ export async function autChatCrud({
return Promise.reject(ChatErrEnum.unAuthChat);
})();
if (!chat) return { isOutLink, uid };
return {
chat,
isOutLink,
uid
};
}
/*
Different chat source
1. token (header)
2. apikey (header)
3. share page (body: shareId outLinkUid)
4. team chat page (body: teamId teamToken)
*/
export async function authChatCert(props: AuthModeType) {
const { teamId, teamToken, shareId, outLinkUid } = props.req.body as OutLinkChatAuthProps;
if (shareId && outLinkUid) {
const { shareChat } = await authOutLinkValid({ shareId });
const { uid } = await authOutLinkInit({
outLinkUid,
tokenUrl: shareChat.limit?.hookUrl
});
return {
teamId: String(shareChat.teamId),
tmbId: String(shareChat.tmbId),
authType: AuthUserTypeEnum.outLink,
apikey: '',
isOwner: false,
canWrite: false,
outLinkUid: uid
};
}
if (teamId && teamToken) {
const { uid } = await authTeamSpaceToken({ teamId, teamToken });
const tmb = await MongoTeamMember.findOne(
{ teamId, role: TeamMemberRoleEnum.owner },
'tmbId'
).lean();
if (!tmb) return Promise.reject(ChatErrEnum.unAuthChat);
return {
teamId,
tmbId: String(tmb._id),
authType: AuthUserTypeEnum.teamDomain,
apikey: '',
isOwner: false,
canWrite: false,
outLinkUid: uid
};
}
return authCert(props);
}

View File

@@ -1,9 +1,12 @@
import { UserErrEnum } from '@fastgpt/global/common/error/code/user';
import { TeamMemberWithUserSchema } from '@fastgpt/global/support/user/team/type';
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
import { MongoTeam } from '@fastgpt/service/support/user/team/teamSchema';
import { checkTeamAIPoints } from '@fastgpt/service/support/permission/teamLimit';
import axios from 'axios';
import { GET } from '@fastgpt/service/common/api/plusRequest';
import {
AuthTeamTagTokenProps,
AuthTokenFromTeamDomainResponse
} from '@fastgpt/global/support/user/team/tag';
export async function getUserChatInfoAndAuthTeamPoints(tmbId: string) {
const tmb = (await MongoTeamMember.findById(tmbId, 'teamId userId').populate(
@@ -19,25 +22,21 @@ export async function getUserChatInfoAndAuthTeamPoints(tmbId: string) {
};
}
type UserInfoType = {
data: {
uid: string;
tags: string[];
};
};
export async function getShareTeamUid(shareTeamId: string, authToken: string) {
try {
const teamInfo = await MongoTeam.findById(shareTeamId);
const tagsUrl = teamInfo?.tagsUrl;
const { data: userInfo } = await axios.post(tagsUrl + `/getUserInfo`, { autoken: authToken });
const uid = userInfo?.data?.uid;
if (uid) {
throw new Error('uid null');
}
return uid;
} catch (err) {
return '';
}
export function authTeamTagToken(data: AuthTeamTagTokenProps) {
return GET<AuthTokenFromTeamDomainResponse['data']>('/support/user/team/tag/authTeamToken', data);
}
export async function authTeamSpaceToken({
teamId,
teamToken
}: {
teamId: string;
teamToken: string;
}) {
// get outLink and app
const data = await authTeamTagToken({ teamId, teamToken });
const uid = data.uid;
return {
uid
};
}

View File

@@ -1,36 +0,0 @@
import { POST } from '@fastgpt/service/common/api/plusRequest';
import type { AuthOutLinkChatProps } from '@fastgpt/global/support/outLink/api.d';
import type { chatAppListSchema } from '@fastgpt/global/core/chat/type.d';
import { getUserChatInfoAndAuthTeamPoints } from './team';
import { MongoTeam } from '@fastgpt/service/support/user/team/teamSchema';
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
export function authChatTeamInfo(data: { shareTeamId: string; authToken: string }) {
return POST<chatAppListSchema>('/core/chat/init', data);
}
export async function authTeamShareChatStart({
teamId,
ip,
outLinkUid,
question
}: AuthOutLinkChatProps & {
teamId: string;
}) {
// get outLink and app
const { teamInfo, uid } = await authChatTeamInfo({ shareTeamId: teamId, authToken: outLinkUid });
// check balance and chat limit
const tmb = await MongoTeamMember.findOne({ teamId, userId: String(teamInfo.ownerId) });
if (!tmb) {
throw new Error('can not find it');
}
const { user } = await getUserChatInfoAndAuthTeamPoints(String(tmb._id));
return {
user,
tmbId: String(tmb._id),
uid: uid
};
}

View File

@@ -1,10 +1,9 @@
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
import { ModelTypeEnum } from '@fastgpt/service/core/ai/model';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
import { addLog } from '@fastgpt/service/common/system/log';
import { createUsage, concatUsage } from './controller';
import { formatModelChars2Points } from '@/service/support/wallet/usage/utils';
import { ChatModuleBillType } from '@fastgpt/global/support/wallet/bill/type';
import { formatModelChars2Points } from '@fastgpt/service/support/wallet/usage/utils';
import { ChatModuleUsageType } from '@fastgpt/global/support/wallet/bill/type';
export const pushChatUsage = ({
appName,
@@ -19,7 +18,7 @@ export const pushChatUsage = ({
teamId: string;
tmbId: string;
source: `${UsageSourceEnum}`;
moduleDispatchBills: ChatModuleBillType[];
moduleDispatchBills: ChatModuleUsageType[];
}) => {
const totalPoints = moduleDispatchBills.reduce((sum, item) => sum + (item.totalPoints || 0), 0);
@@ -34,7 +33,7 @@ export const pushChatUsage = ({
moduleName: item.moduleName,
amount: item.totalPoints || 0,
model: item.model,
charsLength: item.charsLength
tokens: item.tokens
}))
});
addLog.info(`finish completions`, {
@@ -50,20 +49,20 @@ export const pushQAUsage = async ({
teamId,
tmbId,
model,
charsLength,
tokens,
billId
}: {
teamId: string;
tmbId: string;
model: string;
charsLength: number;
tokens: number;
billId: string;
}) => {
// 计算价格
const { totalPoints } = formatModelChars2Points({
model,
modelType: ModelTypeEnum.llm,
charsLength
tokens
});
concatUsage({
@@ -71,7 +70,7 @@ export const pushQAUsage = async ({
teamId,
tmbId,
totalPoints,
charsLength,
tokens,
listIndex: 1
});
@@ -82,30 +81,30 @@ export const pushGenerateVectorUsage = ({
billId,
teamId,
tmbId,
charsLength,
tokens,
model,
source = UsageSourceEnum.fastgpt,
extensionModel,
extensionCharsLength
extensionTokens
}: {
billId?: string;
teamId: string;
tmbId: string;
charsLength: number;
tokens: number;
model: string;
source?: `${UsageSourceEnum}`;
extensionModel?: string;
extensionCharsLength?: number;
extensionTokens?: number;
}) => {
const { totalPoints: totalVector, modelName: vectorModelName } = formatModelChars2Points({
modelType: ModelTypeEnum.vector,
model,
charsLength
tokens
});
const { extensionTotalPoints, extensionModelName } = (() => {
if (!extensionModel || !extensionCharsLength)
if (!extensionModel || !extensionTokens)
return {
extensionTotalPoints: 0,
extensionModelName: ''
@@ -113,7 +112,7 @@ export const pushGenerateVectorUsage = ({
const { totalPoints, modelName } = formatModelChars2Points({
modelType: ModelTypeEnum.llm,
model: extensionModel,
charsLength: extensionCharsLength
tokens: extensionTokens
});
return {
extensionTotalPoints: totalPoints,
@@ -130,7 +129,7 @@ export const pushGenerateVectorUsage = ({
tmbId,
totalPoints,
billId,
charsLength,
tokens,
listIndex: 0
});
} else {
@@ -145,7 +144,7 @@ export const pushGenerateVectorUsage = ({
moduleName: 'support.wallet.moduleName.index',
amount: totalVector,
model: vectorModelName,
charsLength
tokens
},
...(extensionModel !== undefined
? [
@@ -153,7 +152,7 @@ export const pushGenerateVectorUsage = ({
moduleName: 'core.module.template.Query extension',
amount: extensionTotalPoints,
model: extensionModelName,
charsLength: extensionCharsLength
tokens: extensionTokens
}
]
: [])
@@ -164,17 +163,17 @@ export const pushGenerateVectorUsage = ({
};
export const pushQuestionGuideUsage = ({
charsLength,
tokens,
teamId,
tmbId
}: {
charsLength: number;
tokens: number;
teamId: string;
tmbId: string;
}) => {
const qgModel = global.llmModels[0];
const { totalPoints, modelName } = formatModelChars2Points({
charsLength,
tokens,
model: qgModel.model,
modelType: ModelTypeEnum.llm
});
@@ -190,14 +189,14 @@ export const pushQuestionGuideUsage = ({
moduleName: 'core.app.Next Step Guide',
amount: totalPoints,
model: modelName,
charsLength
tokens
}
]
});
};
export function pushAudioSpeechUsage({
appName = 'support.wallet.bill.Audio Speech',
appName = 'support.wallet.usage.Audio Speech',
model,
charsLength,
teamId,
@@ -213,7 +212,7 @@ export function pushAudioSpeechUsage({
}) {
const { totalPoints, modelName } = formatModelChars2Points({
model,
charsLength,
tokens: charsLength,
modelType: ModelTypeEnum.audioSpeech
});
@@ -249,12 +248,12 @@ export function pushWhisperUsage({
const { totalPoints, modelName } = formatModelChars2Points({
model: whisperModel.model,
charsLength: duration,
tokens: duration,
modelType: ModelTypeEnum.whisper,
multiple: 60
});
const name = 'support.wallet.bill.Whisper';
const name = 'support.wallet.usage.Whisper';
createUsage({
teamId,

View File

@@ -1,4 +1,3 @@
import { ModelTypeEnum, getModelMap } from '@fastgpt/service/core/ai/model';
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
@@ -16,29 +15,3 @@ export function authType2UsageSource({
if (authType === AuthUserTypeEnum.apikey) return UsageSourceEnum.api;
return UsageSourceEnum.fastgpt;
}
export const formatModelChars2Points = ({
model,
charsLength = 0,
modelType,
multiple = 1000
}: {
model: string;
charsLength: number;
modelType: `${ModelTypeEnum}`;
multiple?: number;
}) => {
const modelData = getModelMap?.[modelType]?.(model);
if (!modelData)
return {
totalPoints: 0,
modelName: ''
};
const totalPoints = (modelData.charsPointsPrice || 0) * (charsLength / multiple);
return {
modelName: modelData.name,
totalPoints
};
};

View File

@@ -1,10 +0,0 @@
import { generateQA } from '../events/generateQA';
import { generateVector } from '../events/generateVector';
/* start task */
export const startQueue = () => {
if (!global.systemEnv) return;
generateQA();
generateVector();
};

View File

@@ -9,7 +9,6 @@ import {
} from '@fastgpt/global/core/ai/model.d';
import { TrackEventName } from '@/constants/common';
import { AppSimpleEditConfigTemplateType } from '@fastgpt/global/core/app/type';
import { FastGPTFeConfigsType, SystemEnvType } from '@fastgpt/global/common/system/types';
import { SubPlanType } from '@fastgpt/global/support/wallet/sub/type';
export type PagingData<T> = {
@@ -22,7 +21,6 @@ export type PagingData<T> = {
export type RequestPaging = { pageNum: number; pageSize: number; [key]: any };
declare global {
var systemEnv: SystemEnvType;
var systemInitd: boolean;
var qaQueueLen: number;

View File

@@ -1,13 +1,14 @@
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
export const selectShareResponse = ({
export const selectSimpleChatResponse = ({
responseData = []
}: {
responseData?: ChatHistoryItemResType[];
}) => {
const filedList = ['quoteList', 'moduleType'];
const filterModuleTypeList: any[] = [FlowNodeTypeEnum.chatNode];
return responseData
.filter((item) => filterModuleTypeList.includes(item.moduleType))
.map((item) => {

View File

@@ -3,9 +3,9 @@ import { POST } from '../api/request';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useTranslation } from 'next-i18next';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
export const useSpeech = (props?: { shareId?: string }) => {
const { shareId } = props || {};
export const useSpeech = (props?: OutLinkChatAuthProps) => {
const { t } = useTranslation();
const mediaRecorder = useRef<MediaRecorder>();
// const mediaStream = useRef<MediaStream>();
@@ -79,7 +79,13 @@ export const useSpeech = (props?: { shareId?: string }) => {
const duration = Math.round((Date.now() - startTimestamp.current) / 1000);
formData.append('file', blob, 'recording.webm');
formData.append('metadata', JSON.stringify({ duration, shareId }));
formData.append(
'data',
JSON.stringify({
...props,
duration
})
);
setIsTransCription(true);
try {

View File

@@ -3,6 +3,6 @@ export const getDocPath = (path: string) => {
const feConfigs = useSystemStore.getState().feConfigs;
if (!feConfigs?.docUrl) return '';
if (feConfigs.docUrl.endsWith('/')) return feConfigs.docUrl;
if (feConfigs.docUrl.endsWith('/')) return feConfigs.docUrl.slice(0, -1);
return feConfigs.docUrl + path;
};

View File

@@ -4,12 +4,11 @@ import { getErrText } from '@fastgpt/global/common/error/utils';
import type { AppTTSConfigType } from '@fastgpt/global/core/module/type.d';
import { TTSTypeEnum } from '@/constants/app';
import { useTranslation } from 'next-i18next';
import { useRouter } from 'next/router';
import type { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat.d';
export const useAudioPlay = (props?: { ttsConfig?: AppTTSConfigType }) => {
export const useAudioPlay = (props?: OutLinkChatAuthProps & { ttsConfig?: AppTTSConfigType }) => {
const { t } = useTranslation();
const { shareId } = useRouter().query as { shareId?: string };
const { ttsConfig } = props || {};
const { ttsConfig, shareId, outLinkUid, teamId, teamToken } = props || {};
const { toast } = useToast();
const [audio, setAudio] = useState<HTMLAudioElement>();
const [audioLoading, setAudioLoading] = useState(false);
@@ -63,7 +62,10 @@ export const useAudioPlay = (props?: { ttsConfig?: AppTTSConfigType }) => {
chatItemId,
ttsConfig,
input: text,
shareId
shareId,
outLinkUid,
teamId,
teamToken
})
});
setAudioLoading(false);

View File

@@ -1,12 +1,13 @@
import { GET, POST, DELETE, PUT } from '@/web/common/api/request';
import type { ChatHistoryItemType, chatAppListSchema } from '@fastgpt/global/core/chat/type.d';
import type { ChatHistoryItemType, ChatAppListSchema } from '@fastgpt/global/core/chat/type.d';
import type {
CloseCustomFeedbackParams,
InitChatProps,
InitChatResponse,
InitOutLinkChatProps,
getHistoriesProps
GetHistoriesProps,
InitTeamChatProps
} from '@/global/core/chat/api.d';
import type {
AdminUpdateFeedbackParams,
@@ -16,37 +17,23 @@ import type {
UpdateHistoryProps
} from '@/global/core/chat/api.d';
import { UpdateChatFeedbackProps } from '@fastgpt/global/core/chat/api';
/**
* 根据队伍ID和获取
*/
export const getChatListById = (data: { shareTeamId: string; authToken: string }) =>
POST<chatAppListSchema>(`/proApi/core/chat/init`, data);
/**
* 获取团队分享的对话列表 initTeamChat
* @param data
* @returns
*/
export const getinitTeamChat = (data: { teamId: string; authToken: string; appId: string }) =>
GET(`/proApi/core/chat/initTeamChat`, data);
import { AuthTeamTagTokenProps } from '@fastgpt/global/support/user/team/tag';
import { AppListItemType } from '@fastgpt/global/core/app/type';
/**
* 获取初始化聊天内容
*/
export const getInitChatInfo = (data: InitChatProps) =>
GET<InitChatResponse>(`/core/chat/init`, data);
export const getInitChatInfoTeam = (data: InitChatProps) =>
GET<InitChatResponse>(`/core/chat/init`, data);
export const getInitOutLinkChatInfo = (data: InitOutLinkChatProps) =>
GET<InitChatResponse>(`/core/chat/outLink/init`, data);
export const getTeamChatInfo = (data: { appId: string; chatId: string; outLinkUid?: string }) =>
export const getTeamChatInfo = (data: InitTeamChatProps) =>
GET<InitChatResponse>(`/core/chat/team/init`, data);
/**
* get current window history(appid or shareId)
*/
export const getChatHistories = (data: getHistoriesProps) =>
export const getChatHistories = (data: GetHistoriesProps) =>
POST<ChatHistoryItemType[]>('/core/chat/getHistories', data);
/**
@@ -79,3 +66,18 @@ export const updateChatAdminFeedback = (data: AdminUpdateFeedbackParams) =>
export const closeCustomFeedback = (data: CloseCustomFeedbackParams) =>
POST('/core/chat/feedback/closeCustom', data).catch();
/* team chat */
/**
* Get the app that can be used with this token
*/
export const getMyTokensApps = (data: AuthTeamTagTokenProps) =>
GET<AppListItemType[]>(`/proApi/support/user/team/tag/getAppsByTeamTokens`, data);
/**
* 获取团队分享的对话列表 initTeamChat
* @param data
* @returns
*/
export const getinitTeamChat = (data: { teamId: string; authToken: string; appId: string }) =>
GET(`/proApi/core/chat/initTeamChat`, data);

View File

@@ -4,7 +4,7 @@ import { immer } from 'zustand/middleware/immer';
import type { ChatHistoryItemType } from '@fastgpt/global/core/chat/type.d';
import type {
InitChatResponse,
getHistoriesProps,
GetHistoriesProps,
ClearHistoriesProps,
DelHistoryProps,
UpdateHistoryProps,
@@ -21,7 +21,7 @@ import { defaultChatData } from '@/global/core/chat/constants';
type State = {
histories: ChatHistoryItemType[];
loadHistories: (data: getHistoriesProps) => Promise<null>;
loadHistories: (data: GetHistoriesProps) => Promise<null>;
delOneHistory(data: DelHistoryProps): Promise<void>;
clearHistories(data: ClearHistoriesProps): Promise<void>;
pushHistory: (history: ChatHistoryItemType) => void;

View File

@@ -1,42 +0,0 @@
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import type { ChatHistoryItemType } from '@fastgpt/global/core/chat/type.d';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWSYZ1234567890_',
24
);
type State = {
localUId: string;
teamShareChatHistory: (ChatHistoryItemType & { delete?: boolean })[];
clearLocalHistory: (shareId?: string) => void;
};
export const useTeamShareChatStore = create<State>()(
devtools(
persist(
immer((set, get) => ({
localUId: `shareChat-${Date.now()}-${nanoid()}`,
teamShareChatHistory: [], // old version field
clearLocalHistory() {
// abandon
set((state) => {
state.teamShareChatHistory = state.teamShareChatHistory.map((item) => ({
...item,
delete: true
}));
});
}
})),
{
name: 'shareChatStore',
partialize: (state) => ({
localUId: state.localUId,
shareChatHistory: state.teamShareChatHistory
})
}
)
)
);

View File

@@ -1,7 +1,7 @@
import { GET, POST, PUT } from '@/web/common/api/request';
import { hashStr } from '@fastgpt/global/common/string/tools';
import type { ResLogin } from '@/global/support/api/userRes.d';
import { UserAuthTypeEnum } from '@fastgpt/global/support/user/constant';
import { UserAuthTypeEnum } from '@fastgpt/global/support/user/auth/constants';
import { UserUpdateParams } from '@/types/user';
import { UserType } from '@fastgpt/global/support/user/type.d';
import type {
@@ -9,6 +9,7 @@ import type {
OauthLoginProps,
PostLoginProps
} from '@fastgpt/global/support/user/api.d';
import { GetWXLoginQRResponse } from '@fastgpt/global/support/user/login/api.d';
export const sendAuthCode = (data: {
username: string;
@@ -71,3 +72,9 @@ export const postLogin = ({ password, ...props }: PostLoginProps) =>
export const loginOut = () => GET('/support/user/account/loginout');
export const putUserInfo = (data: UserUpdateParams) => PUT('/support/user/account/update', data);
export const getWXLoginQR = () =>
GET<GetWXLoginQRResponse>('/proApi/support/user/account/login/wx/getQR');
export const getWXLoginResult = (code: string) =>
GET<ResLogin>(`/proApi/support/user/account/login/wx/getResult`, { code });

View File

@@ -1,6 +1,6 @@
import { useState, useMemo, useCallback } from 'react';
import { sendAuthCode } from '@/web/support/user/api';
import { UserAuthTypeEnum } from '@fastgpt/global/support/user/constant';
import { UserAuthTypeEnum } from '@fastgpt/global/support/user/auth/constants';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useTranslation } from 'next-i18next';

View File

@@ -8,7 +8,7 @@ import {
UpdateTeamMemberProps,
UpdateTeamProps
} from '@fastgpt/global/support/user/team/controller.d';
import type { TeamTagsSchema } from '@fastgpt/global/support/user/team/type';
import type { TeamTagItemType, TeamTagSchema } from '@fastgpt/global/support/user/team/type';
import {
TeamItemType,
TeamMemberItemType,
@@ -25,14 +25,6 @@ export const putUpdateTeam = (data: UpdateTeamProps) =>
PUT(`/proApi/support/user/team/update`, data);
export const putSwitchTeam = (teamId: string) =>
PUT<string>(`/proApi/support/user/team/switch`, { teamId });
export const updateTags = (teamId: string, tagsUrl: string) =>
POST<TeamTagsSchema[]>(`/proApi/support/user/team/tags/asyncTags`, { teamId, tagsUrl });
export const getTeamsTags = (teamId: string) =>
GET(`/proApi/support/user/team/tags/list`, { teamId });
export const putUpdateTeamTags = (data: any) =>
PUT(`/proApi/support/user/team/tags/updateUrl`, data);
export const insertTeamsTags = (tags: Array<any>) =>
POST(`/proApi/support/user/team/tags/create`, tags);
/* --------------- team member ---------------- */
export const getTeamMembers = (teamId: string) =>
@@ -50,6 +42,11 @@ export const updateInviteResult = (data: UpdateInviteProps) =>
export const delLeaveTeam = (teamId: string) =>
DELETE('/proApi/support/user/team/member/leave', { teamId });
/* --------------- team tags ---------------- */
export const getTeamsTags = () => GET<TeamTagSchema[]>(`/proApi/support/user/team/tag/list`);
export const loadTeamTagsByDomain = (domain: string) =>
GET<TeamTagItemType[]>(`/proApi/support/user/team/tag/async`, { domain });
/* team limit */
export const checkTeamExportDatasetLimit = (datasetId: string) =>
GET(`/support/user/team/limit/exportDatasetLimit`, { datasetId });