4.6.4-alpha (#569)

This commit is contained in:
Archer
2023-12-07 13:43:08 +08:00
committed by GitHub
parent 71afe71192
commit e01c38efe0
80 changed files with 1401 additions and 1109 deletions

View File

@@ -327,12 +327,12 @@
"QA Prompt": "QA Prompt",
"Start Sync Tip": "Are you sure to start synchronizing data? The old data will be deleted and then re-acquired, please confirm!",
"Sync": "Data Sync",
"Sync Collection": "Data Sync",
"Website Create Success": "Created successfully, data is being synchronized",
"Website Empty Tip": "No associated website yet",
"Website Link": "Website Link",
"Website Sync": "Website",
"id": "Id",
"Sync Collection": "Data Sync",
"metadata": {
"Chunk Size": "Chunk Size",
"Createtime": "Create Time",
@@ -510,6 +510,10 @@
"variable options": "Options"
},
"variable add option": "Add Option"
},
"shareChat": {
"Init Error": "Init Chat Error",
"Init History Error": "Init History Error"
}
},
"dataset": {

View File

@@ -316,7 +316,7 @@
"Search Top K": "单次搜索数量",
"Set Empty Result Tip": ",未搜索到内容时回复指定内容",
"Set Website Config": "开始配置网站信息",
"Similarity": "相度",
"Similarity": "相度",
"Sync Time": "最后更新时间",
"Virtual File": "虚拟文件",
"Website Dataset": "Web 站点同步",
@@ -327,12 +327,12 @@
"QA Prompt": "QA 拆分引导词",
"Start Sync Tip": "确认开始同步数据?将会删除旧数据后重新获取,请确认!",
"Sync": "同步数据",
"Sync Collection": "数据同步",
"Website Create Success": "创建成功,正在同步数据",
"Website Empty Tip": "还没有关联网站",
"Website Link": "Web 站点地址",
"Website Sync": "Web 站点同步",
"id": "集合ID",
"Sync Collection": "数据同步",
"metadata": {
"Chunk Size": "分割大小",
"Createtime": "创建时间",
@@ -405,17 +405,17 @@
"search": {
"Empty result response": "空搜索回复",
"Empty result response Tips": "若填写该内容,没有搜索到合适内容时,将直接回复填写的内容。",
"Min Similarity": "最低相度",
"Min Similarity Tips": "不同索引模型的相度有区别,请通过搜索测试来选择合适的数值",
"Min Similarity": "最低相度",
"Min Similarity Tips": "不同索引模型的相度有区别,请通过搜索测试来选择合适的数值,使用 ReRank 时,相关度可能会很低。",
"Params Setting": "搜索参数设置",
"Top K": "单次搜索上限",
"mode": {
"embFullTextReRank": "混合检索",
"embFullTextReRank desc": "使用向量检索与全文检索混合结果进行 Rerank 进行重排,通常效果最佳",
"embFullTextReRank desc": "使用向量检索与全文检索混合结果进行 Rerank 进行重排,相关度通常差异明显,推荐。",
"embedding": "语义检索",
"embedding desc": "直接进行向量 topk 相关性查询",
"embeddingReRank": "增强语义检索",
"embeddingReRank desc": "超额进行向量 topk 查询后再使用 Rerank 进行排序"
"embeddingReRank desc": "超额进行向量 topk 查询后再使用 Rerank 进行排序,相关度通常差异明显。"
},
"search mode": "检索模式"
},
@@ -510,6 +510,10 @@
"variable options": "选项"
},
"variable add option": "添加选项"
},
"shareChat": {
"Init Error": "初始化对话框失败",
"Init History Error": "初始化聊天记录失败"
}
},
"dataset": {

View File

@@ -1,5 +1,5 @@
import React, { useMemo, useState } from 'react';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api.d';
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 { useTranslation } from 'next-i18next';

View File

@@ -1,6 +1,6 @@
import React, { useMemo, useState } from 'react';
import { Box, useTheme, Flex, Image } from '@chakra-ui/react';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api.d';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
import { useTranslation } from 'next-i18next';
import { moduleTemplatesFlat } from '@/web/core/modules/template/system';
import Tabs from '../Tabs';

View File

@@ -12,7 +12,7 @@ import Script from 'next/script';
import { throttle } from 'lodash';
import type { ExportChatType } from '@/types/chat.d';
import type { ChatItemType, ChatSiteItemType } from '@fastgpt/global/core/chat/type.d';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api.d';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
import { useToast } from '@/web/common/hooks/useToast';
import { useAudioPlay } from '@/web/common/utils/voice';
import { getErrText } from '@fastgpt/global/common/error/utils';
@@ -80,7 +80,7 @@ export type StartChatFnProps = {
};
export type ComponentRef = {
getChatHistory: () => ChatSiteItemType[];
getChatHistories: () => ChatSiteItemType[];
resetVariables: (data?: Record<string, any>) => void;
resetHistory: (history: ChatSiteItemType[]) => void;
scrollToBottom: (behavior?: 'smooth' | 'auto') => void;
@@ -134,7 +134,7 @@ const ChatBox = (
const router = useRouter();
const { t } = useTranslation();
const { toast } = useToast();
const { isPc } = useSystemStore();
const { isPc, setLoading } = useSystemStore();
const TextareaDom = useRef<HTMLTextAreaElement>(null);
const chatController = useRef(new AbortController());
const questionGuideController = useRef(new AbortController());
@@ -415,15 +415,20 @@ const ChatBox = (
async (index: number) => {
if (!onDelMessage) return;
const delHistory = chatHistory.slice(index);
setChatHistory((state) => (index === 0 ? [] : state.slice(0, index)));
await Promise.all(
delHistory.map((item, i) => onDelMessage({ contentId: item.dataId, index: index + i }))
);
setLoading(true);
sendPrompt(variables, delHistory[0].value, chatHistory.slice(0, index));
try {
await Promise.all(
delHistory.map((item, i) => onDelMessage({ contentId: item.dataId, index: index + i }))
);
setChatHistory((state) => (index === 0 ? [] : state.slice(0, index)));
sendPrompt(variables, delHistory[0].value, chatHistory.slice(0, index));
} catch (error) {}
setLoading(false);
},
[chatHistory, onDelMessage, sendPrompt, variables]
[chatHistory, onDelMessage, sendPrompt, setLoading, variables]
);
// delete one message
const delOneMessage = useCallback(
@@ -439,7 +444,7 @@ const ChatBox = (
// output data
useImperativeHandle(ref, () => ({
getChatHistory: () => chatHistory,
getChatHistories: () => chatHistory,
resetVariables(e) {
const defaultVal: Record<string, any> = {};
variableModules?.forEach((item) => {

View File

@@ -86,7 +86,7 @@ const DatasetParamsModal = ({
min={0}
max={1}
step={0.01}
value={getValues(ModuleInputKeyEnum.datasetSimilarity) || 0.5}
value={getValues(ModuleInputKeyEnum.datasetSimilarity) ?? 0.5}
onChange={(val) => {
setValue(ModuleInputKeyEnum.datasetSimilarity, val);
setRefresh(!refresh);
@@ -107,7 +107,7 @@ const DatasetParamsModal = ({
]}
min={1}
max={30}
value={getValues(ModuleInputKeyEnum.datasetLimit) || 5}
value={getValues(ModuleInputKeyEnum.datasetLimit) ?? 5}
onChange={(val) => {
setValue(ModuleInputKeyEnum.datasetLimit, val);
setRefresh(!refresh);

View File

@@ -1,7 +1,75 @@
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';
export type GetChatSpeechProps = {
ttsConfig: AppTTSConfigType;
input: string;
shareId?: string;
};
/* ---------- chat ----------- */
export type InitChatProps = {
appId?: string;
chatId?: string;
};
export type InitOutLinkChatProps = {
chatId?: string;
shareId?: string;
outLinkUid?: string;
};
export type InitChatResponse = {
chatId?: string;
appId: string;
userAvatar?: string;
title: string;
variables: Record<string, any>;
history: ChatItemType[];
app: {
userGuideModule?: ModuleItemType;
chatModels?: string[];
name: string;
avatar: string;
intro: string;
canUse?: boolean;
};
};
/* ---------- history ----------- */
export type getHistoriesProps = {
appId?: string;
// share chat
shareId?: string;
outLinkUid?: string; // authToken/uid
};
export type UpdateHistoryProps = {
chatId: string;
customTitle?: string;
top?: boolean;
shareId?: string;
outLinkUid?: string;
};
export type DelHistoryProps = {
chatId: string;
shareId?: string;
outLinkUid?: string;
};
export type ClearHistoriesProps = {
appId?: string;
shareId?: string;
outLinkUid?: string;
};
/* -------- chat item ---------- */
export type DeleteChatItemProps = {
chatId: string;
contentId: string;
shareId?: string;
outLinkUid?: string;
};
export type AdminUpdateFeedbackParams = AdminFbkType & {
chatItemId: string;
};

View File

@@ -0,0 +1,15 @@
import { InitChatResponse } from './api';
export const defaultChatData: InitChatResponse = {
chatId: '',
appId: '',
app: {
name: 'Loading',
avatar: '/icon/logo.svg',
intro: '',
canUse: false
},
title: '新对话',
variables: {},
history: []
};

View File

@@ -33,9 +33,8 @@ export const Prompt_QuotePromptList: PromptTemplateItem[] = [
"""
回答要求:
1. 优先使用知识库内容回答问题。
2. 你可以回答我不知道
3. 不要提及你是从知识库获取的知识
4. 知识库包含 markdown 内容时,按 markdown 格式返回。
2. 不要提及你是从知识库获取的知识
3. 知识库包含 markdown 内容时,按 markdown 格式返回
我的问题是:"{{question}}"`
},
{
@@ -47,9 +46,8 @@ export const Prompt_QuotePromptList: PromptTemplateItem[] = [
"""
回答要求:
1. 优先使用知识库内容回答问题,其中 instruction 是相关介绍output 是预期回答或补充。
2. 你可以回答我不知道
3. 不要提及你是从知识库获取的知识
4. 知识库包含 markdown 内容时,按 markdown 格式返回。
2. 不要提及你是从知识库获取的知识
3. 知识库包含 markdown 内容时,按 markdown 格式返回
我的问题是:"{{question}}"`
},
{

View File

@@ -54,7 +54,7 @@ const PayModal = ({ onClose }: { onClose: () => void }) => {
onSuccess(res) {
if (!res) return;
toast({
title: '充值成功',
title: res,
status: 'success'
});
router.reload();

View File

@@ -1,32 +1,14 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema';
import { startQueue } from '@/service/utils/tools';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { userId } = await authCert({ req, authToken: true });
await unlockTask(userId);
await authCert({ req, authToken: true });
startQueue();
} catch (error) {}
jsonRes(res);
}
async function unlockTask(userId: string) {
try {
await MongoDatasetTraining.updateMany(
{
userId
},
{
lockTime: new Date('2000/1/1')
}
);
startQueue();
} catch (error) {
unlockTask(userId);
}
}

View File

@@ -5,6 +5,7 @@ import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
/* 获取我的模型 */
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
@@ -20,6 +21,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
await authApp({ req, authToken: true, appId, per: 'owner' });
// 删除对应的聊天
await MongoChatItem.deleteMany({
appId
});
await MongoChat.deleteMany({
appId
});

View File

@@ -381,7 +381,7 @@ function datasetTemplate({
key: 'similarity',
value: 0.4,
type: FlowNodeInputTypeEnum.slider,
label: '相度',
label: '相度',
connected: true
},
{

View File

@@ -289,7 +289,7 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
key: 'similarity',
value: formData.dataset.similarity,
type: FlowNodeInputTypeEnum.slider,
label: '相度',
label: '相度',
connected: false
},
{

View File

@@ -8,8 +8,9 @@ import { pushChatBill } from '@/service/support/wallet/bill/push';
import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants';
import type { ChatItemType } from '@fastgpt/global/core/chat/type';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { authUser } from '@/service/support/permission/auth/user';
import { dispatchModules } from '@/service/moduleDispatch';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { getUserAndAuthBalance } from '@fastgpt/service/support/user/controller';
export type Props = {
history: ChatItemType[];
@@ -40,15 +41,20 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
/* user auth */
const [{ teamId, tmbId }, { user }] = await Promise.all([
const [_, { teamId, tmbId }] = await Promise.all([
authApp({ req, authToken: true, appId, per: 'r' }),
authUser({
authCert({
req,
authToken: true,
minBalance: 0
authToken: true
})
]);
// auth balance
const user = await getUserAndAuthBalance({
tmbId,
minBalance: 0
});
/* start process */
const { responseData } = await dispatchModules({
res,

View File

@@ -0,0 +1,58 @@
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 { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
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';
/* clear chat history */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { appId, shareId, outLinkUid } = req.query as ClearHistoriesProps;
const match = await (async () => {
if (shareId && outLinkUid) {
const { uid } = await authOutLink({ shareId, outLinkUid });
return {
shareId,
outLinkUid: uid
};
}
if (appId) {
const { tmbId } = await authCert({ req, authToken: true });
return {
appId,
tmbId,
source: ChatSourceEnum.online
};
}
return Promise.reject('Param are error');
})();
console.log(match);
// find chatIds
const list = await MongoChat.find(match, 'chatId').lean();
const idList = list.map((item) => item.chatId);
await MongoChatItem.deleteMany({
chatId: { $in: idList }
});
await MongoChat.deleteMany({
chatId: { $in: idList }
});
jsonRes(res);
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -0,0 +1,38 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { DelHistoryProps } from '@/global/core/chat/api';
import { autChatCrud } from '@/service/support/permission/auth/chat';
/* clear chat history */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { chatId, shareId, outLinkUid } = req.query as DelHistoryProps;
await autChatCrud({
req,
authToken: true,
chatId,
shareId,
outLinkUid,
per: 'w'
});
await MongoChatItem.deleteMany({
chatId
});
await MongoChat.findOneAndRemove({
chatId
});
jsonRes(res);
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -1,54 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
type Props = {
chatId?: string;
appId?: string;
};
/* clear chat history */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { chatId, appId } = req.query as Props;
const { tmbId } = await authCert({ req, authToken: true });
if (chatId) {
await MongoChatItem.deleteMany({
chatId,
tmbId
});
await MongoChat.findOneAndRemove({
chatId,
tmbId
});
}
if (appId) {
const chats = await MongoChat.find({
appId,
tmbId,
source: ChatSourceEnum.online
}).select('_id');
const chatIds = chats.map((chat) => chat._id);
await MongoChatItem.deleteMany({
chatId: { $in: chatIds }
});
await MongoChat.deleteMany({
_id: { $in: chatIds }
});
}
jsonRes(res);
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -2,7 +2,7 @@ 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 type { AdminUpdateFeedbackParams } from '@fastgpt/global/core/chat/api.d';
import type { AdminUpdateFeedbackParams } from '@/global/core/chat/api.d';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
/* 初始化我的聊天框,需要身份验证 */

View File

@@ -0,0 +1,62 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import 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 { authOutLink } from '@/service/support/permission/auth/outLink';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { appId, shareId, outLinkUid } = req.body as getHistoriesProps;
const limit = shareId && outLinkUid ? 20 : 30;
const match = await (async () => {
if (shareId && outLinkUid) {
const { uid } = await authOutLink({ shareId, outLinkUid });
return {
shareId,
outLinkUid: uid,
source: ChatSourceEnum.share,
updateTime: {
$gte: new Date(new Date().setDate(new Date().getDate() - 30))
}
};
}
if (appId) {
const { tmbId } = await authCert({ req, authToken: true });
return {
appId,
tmbId,
source: ChatSourceEnum.online
};
}
return Promise.reject('Params are error');
})();
const data = await MongoChat.find(match, 'chatId title top customTitle appId updateTime')
.sort({ top: -1, updateTime: -1 })
.limit(limit);
jsonRes<ChatHistoryItemType[]>(res, {
data: data.map((item) => ({
chatId: item.chatId,
updateTime: item.updateTime,
appId: item.appId,
customTitle: item.customTitle,
title: item.title,
top: item.top
}))
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -1,24 +1,20 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import type { InitChatResponse } from '@fastgpt/global/core/chat/api.d';
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { getGuideModule } from '@fastgpt/global/core/module/utils';
import { getChatModelNameListByModules } from '@/service/core/app/module';
import { authChat } from '@fastgpt/service/support/permission/auth/chat';
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import type { InitChatProps, InitChatResponse } from '@/global/core/chat/api.d';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { getChatItems } from '@fastgpt/service/core/chat/controller';
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
/* 初始化我的聊天框,需要身份验证 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
let { appId, chatId } = req.query as {
appId: string;
chatId: '' | string;
};
let { appId, chatId } = req.query as InitChatProps;
if (!appId) {
return jsonRes(res, {
@@ -27,57 +23,44 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
});
}
// 校验使用权限
const [{ app }, autChatResult] = await Promise.all([
// auth app permission
const [{ app, tmbId }, chat] = await Promise.all([
authApp({
req,
authToken: true,
appId,
per: 'r'
}),
chatId
? authChat({
req,
authToken: true,
chatId,
per: 'r'
})
: undefined
chatId ? MongoChat.findOne({ chatId }) : undefined
]);
// get app and history
const { history = [] }: { history?: ChatItemType[] } = await (async () => {
if (chatId) {
// auth chatId
const history = await MongoChatItem.find(
{
chatId
},
`dataId obj value adminFeedback userFeedback ${ModuleOutputKeyEnum.responseData}`
)
.sort({ _id: -1 })
.limit(30);
// auth chat permission
if (!app.canWrite && String(tmbId) !== String(chat?.tmbId)) {
throw new Error(ChatErrEnum.unAuthChat);
}
history.reverse();
return { history };
}
return {};
})();
// get app and history
const { history } = await getChatItems({
chatId,
limit: 30,
field: `dataId obj value adminFeedback userFeedback ${ModuleOutputKeyEnum.responseData}`
});
jsonRes<InitChatResponse>(res, {
data: {
chatId,
appId,
title: chat?.title || '新对话',
userAvatar: undefined,
variables: chat?.variables || {},
history,
app: {
userGuideModule: getGuideModule(app.modules),
chatModels: getChatModelNameListByModules(app.modules),
name: app.name,
avatar: app.avatar,
intro: app.intro
},
title: autChatResult?.chat?.title || '新对话',
variables: autChatResult?.chat?.variables || {},
history
}
}
});
} catch (err) {

View File

@@ -0,0 +1,42 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
/* clear chat history */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { outLinkUid, chatIds } = req.body as {
outLinkUid: string;
chatIds: string[];
};
if (!outLinkUid) {
throw new Error('shareId or outLinkUid is required');
}
const sliceIds = chatIds.slice(0, 50);
await MongoChat.updateMany(
{
chatId: { $in: sliceIds },
source: ChatSourceEnum.share,
outLinkUid: { $exists: false }
},
{
$set: {
outLinkUid
}
}
);
jsonRes(res);
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -2,14 +2,22 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { authChat } from '@fastgpt/service/support/permission/auth/chat';
import { autChatCrud } from '@/service/support/permission/auth/chat';
import type { DeleteChatItemProps } from '@/global/core/chat/api.d';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { chatId, contentId } = req.query as { chatId: string; contentId: string };
const { chatId, contentId, shareId, outLinkUid } = req.query as DeleteChatItemProps;
await authChat({ req, authToken: true, chatId, per: 'w' });
await autChatCrud({
req,
authToken: true,
chatId,
shareId,
outLinkUid,
per: 'w'
});
await MongoChatItem.deleteOne({
dataId: contentId,

View File

@@ -1,43 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import type { ChatHistoryItemType } from '@fastgpt/global/core/chat/type.d';
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
/* 获取历史记录 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { appId } = req.body as { appId: string };
const { tmbId } = await authApp({ req, authToken: true, appId, per: 'r' });
const data = await MongoChat.find(
{
appId,
tmbId,
source: ChatSourceEnum.online
},
'chatId title top customTitle appId updateTime'
)
.sort({ top: -1, updateTime: -1 })
.limit(20);
jsonRes<ChatHistoryItemType[]>(res, {
data: data.map((item) => ({
chatId: item.chatId,
updateTime: item.updateTime,
appId: item.appId,
customTitle: item.customTitle,
title: item.title,
top: item.top
}))
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -0,0 +1,85 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import type { InitChatResponse, InitOutLinkChatProps } from '@/global/core/chat/api.d';
import { getGuideModule } from '@fastgpt/global/core/module/utils';
import { getChatModelNameListByModules } from '@/service/core/app/module';
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
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 { 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';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
let { chatId, shareId, outLinkUid } = req.query as InitOutLinkChatProps;
// auth link permission
const { shareChat, uid, appId } = await authOutLink({ shareId, outLinkUid });
// auth app permission
const [tmb, chat, app] = await Promise.all([
MongoTeamMember.findById(shareChat.tmbId, '_id userId').populate('userId', 'avatar').lean(),
MongoChat.findOne({ chatId, shareId }).lean(),
MongoApp.findById(appId).lean()
]);
if (!app) {
throw new Error(AppErrEnum.unExist);
}
// auth chat permission
if (chat && chat.outLinkUid !== uid) {
throw new Error(ChatErrEnum.unAuthChat);
}
const { history } = await getChatItems({
chatId,
limit: 30,
field: `dataId obj value userFeedback ${
shareChat.responseDetail ? `adminFeedback ${ModuleOutputKeyEnum.responseData}` : ''
} `
});
// pick share response field
history.forEach((item) => {
item.responseData = selectShareResponse({ responseData: item.responseData });
});
jsonRes<InitChatResponse>(res, {
data: {
chatId,
appId: app._id,
title: chat?.title || '新对话',
//@ts-ignore
userAvatar: tmb?.userId?.avatar,
variables: chat?.variables || {},
history,
app: {
userGuideModule: getGuideModule(app.modules),
chatModels: getChatModelNameListByModules(app.modules),
name: app.name,
avatar: app.avatar,
intro: app.intro
}
}
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
export const config = {
api: {
responseLimit: '10mb'
}
};

View File

@@ -1,17 +1,24 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { UpdateHistoryProps } from '@fastgpt/global/core/chat/api.d';
import { UpdateHistoryProps } from '@/global/core/chat/api.d';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { authChat } from '@fastgpt/service/support/permission/auth/chat';
import { autChatCrud } from '@/service/support/permission/auth/chat';
/* 更新聊天标题 */
/* update chat top, custom title */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { chatId, customTitle, top } = req.body as UpdateHistoryProps;
const { chatId, shareId, outLinkUid, customTitle, top } = req.body as UpdateHistoryProps;
await authChat({ req, authToken: true, chatId });
await autChatCrud({
req,
authToken: true,
chatId,
shareId,
outLinkUid,
per: 'w'
});
await MongoChat.findOneAndUpdate(
{ chatId },

View File

@@ -8,7 +8,7 @@ import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { id, parentId, name, avatar, tags, permission, agentModel, websiteConfig, status } =
const { id, parentId, name, avatar, intro, permission, agentModel, websiteConfig, status } =
req.body as DatasetUpdateBody;
if (!id) {
@@ -26,11 +26,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
...(parentId !== undefined && { parentId: parentId || null }),
...(name && { name }),
...(avatar && { avatar }),
...(tags && { tags }),
...(permission && { permission }),
...(agentModel && { agentModel: agentModel.model }),
...(websiteConfig && { websiteConfig }),
...(status && { status })
...(status && { status }),
...(intro && { intro })
}
);

View File

@@ -1,51 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { MongoUser } from '@fastgpt/service/support/user/schema';
import type { InitShareChatResponse } from '@fastgpt/global/support/outLink/api.d';
import { HUMAN_ICON } from '@fastgpt/global/core/chat/constants';
import { getGuideModule } from '@fastgpt/global/core/module/utils';
import { authShareChatInit } from '@/service/support/outLink/auth';
import { getChatModelNameListByModules } from '@/service/core/app/module';
import { authOutLinkValid } from '@fastgpt/service/support/permission/auth/outLink';
/* init share chat window */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
let { shareId, authToken } = req.query as {
shareId: string;
authToken?: string;
};
// get shareChat
const { app, shareChat } = await authOutLinkValid({ shareId });
// 校验使用权限
const [user] = await Promise.all([
MongoUser.findById(shareChat.userId, 'avatar'),
authShareChatInit({
authToken,
tokenUrl: shareChat.limit?.hookUrl
})
]);
jsonRes<InitShareChatResponse>(res, {
data: {
userAvatar: user?.avatar || HUMAN_ICON,
app: {
userGuideModule: getGuideModule(app.modules),
chatModels: getChatModelNameListByModules(app.modules),
name: app.name,
avatar: app.avatar,
intro: app.intro
}
}
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -3,7 +3,7 @@ import { jsonRes } from '@fastgpt/service/common/response';
import { MongoUser } from '@fastgpt/service/support/user/schema';
import { createJWT, setCookie } from '@fastgpt/service/support/permission/controller';
import { connectToDatabase } from '@/service/mongo';
import { getUserDetail } from '@/service/support/user/controller';
import { getUserDetail } from '@fastgpt/service/support/user/controller';
import type { PostLoginProps } from '@fastgpt/global/support/user/api.d';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {

View File

@@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { connectToDatabase } from '@/service/mongo';
import { getUserDetail } from '@/service/support/user/controller';
import { getUserDetail } from '@fastgpt/service/support/user/controller';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {

View File

@@ -1,6 +1,6 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { authCert, authCertAndShareId } from '@fastgpt/service/support/permission/auth/common';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { withNextCors } from '@fastgpt/service/common/middle/cors';
import { getUploadModel } from '@fastgpt/service/common/file/upload/multer';
import fs from 'fs';

View File

@@ -10,11 +10,11 @@ import { dispatchModules } from '@/service/moduleDispatch';
import type { ChatCompletionCreateParams } from '@fastgpt/global/core/ai/type.d';
import type { ChatMessageItemType } from '@fastgpt/global/core/ai/type.d';
import { gptMessage2ChatType, textAdaptGptResponse } from '@/utils/adapt';
import { getChatHistory } from './getHistory';
import { getChatItems } from '@fastgpt/service/core/chat/controller';
import { saveChat } from '@/service/utils/chat/saveChat';
import { responseWrite } from '@fastgpt/service/common/response';
import { pushChatBill } from '@/service/support/wallet/bill/push';
import { authOutLinkChat } from '@/service/support/permission/auth/outLink';
import { authOutLinkChatStart } from '@/service/support/permission/auth/outLink';
import { pushResult2Remote, updateOutLinkUsage } from '@fastgpt/service/support/outLink/tools';
import requestIp from 'request-ip';
import { getBillSourceByAuthType } from '@fastgpt/global/support/wallet/bill/tools';
@@ -22,9 +22,10 @@ import { getBillSourceByAuthType } from '@fastgpt/global/support/wallet/bill/too
import { selectShareResponse } from '@/utils/service/core/chat';
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
import { connectToDatabase } from '@/service/mongo';
import { getUserAndAuthBalance } from '@/service/support/permission/auth/user';
import { getUserAndAuthBalance } from '@fastgpt/service/support/user/controller';
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { autChatCrud } from '@/service/support/permission/auth/chat';
type FastGptWebChatProps = {
chatId?: string; // undefined: nonuse history, '': new chat, 'xxxxx': use history
@@ -32,7 +33,7 @@ type FastGptWebChatProps = {
};
type FastGptShareChatProps = {
shareId?: string;
authToken?: string;
outLinkUid?: string;
};
export type Props = ChatCompletionCreateParams &
FastGptWebChatProps &
@@ -56,11 +57,11 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
res.end();
});
let {
const {
chatId,
appId,
shareId,
authToken,
outLinkUid,
stream = false,
detail = false,
messages = [],
@@ -93,22 +94,29 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
throw new Error('Question is empty');
}
/* auth app permission */
const { user, app, responseDetail, authType, apikey, canWrite } = await (async () => {
if (shareId) {
const { user, app, authType, responseDetail } = await authOutLinkChat({
/* auth app permission */
const { user, app, responseDetail, authType, apikey, canWrite, uid } = await (async () => {
if (shareId && outLinkUid) {
const { user, appId, authType, responseDetail, uid } = await authOutLinkChatStart({
shareId,
ip: requestIp.getClientIp(req),
authToken,
outLinkUid,
question: question.value
});
const app = await MongoApp.findById(appId);
if (!app) {
return Promise.reject('app is empty');
}
return {
user,
app,
responseDetail,
apikey: '',
authType,
canWrite: false
canWrite: false,
uid
};
}
@@ -146,11 +154,10 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
};
}
// token auth
if (!appId) {
return Promise.reject('appId is empty');
}
// token
const { app, canWrite } = await authApp({
req,
authToken: true,
@@ -168,12 +175,19 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
};
})();
// auth app, get history
const { history } = await getChatHistory({ chatId, tmbId: user.team.tmbId });
// auth chat permission
await autChatCrud({
req,
authToken: true,
authApiKey: true,
chatId,
shareId,
outLinkUid,
per: 'w'
});
const isAppOwner = !shareId && String(user.team.tmbId) === String(app.tmbId);
/* format prompts */
// get and concat history
const { history } = await getChatItems({ chatId, limit: 30, field: `dataId obj value` });
const concatHistory = history.concat(chatMessages);
/* start flow controller */
@@ -202,8 +216,9 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
teamId: user.team.teamId,
tmbId: user.team.tmbId,
variables,
updateUseTime: isAppOwner, // owner update use time
updateUseTime: !shareId && String(user.team.tmbId) === String(app.tmbId), // owner update use time
shareId,
outLinkUid: uid,
source: (() => {
if (shareId) {
return ChatSourceEnum.share;
@@ -281,7 +296,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
});
if (shareId) {
pushResult2Remote({ authToken, shareId, responseData });
pushResult2Remote({ outLinkUid, shareId, responseData });
updateOutLinkUsage({
shareId,
total

View File

@@ -1,73 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { connectToDatabase } from '@/service/mongo';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { Types } from '@fastgpt/service/common/mongo';
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
export type Props = {
appId?: string;
chatId?: string;
limit?: number;
};
export type Response = { history: ChatItemType[] };
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { tmbId } = await authCert({ req, authToken: true });
const { chatId, limit } = req.body as Props;
jsonRes<Response>(res, {
data: await getChatHistory({
chatId,
tmbId,
limit
})
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
export async function getChatHistory({
chatId,
tmbId,
limit = 30
}: Props & { tmbId: string }): Promise<Response> {
if (!chatId) {
return { history: [] };
}
const history = await MongoChatItem.aggregate([
{
$match: {
chatId,
tmbId: new Types.ObjectId(tmbId)
}
},
{
$sort: {
_id: -1
}
},
{
$limit: limit
},
{
$project: {
dataId: 1,
obj: 1,
value: 1
}
}
]);
history.reverse();
return { history };
}

View File

@@ -23,7 +23,7 @@ import { AppLogsListItemType } from '@/types/app';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import ChatBox, { type ComponentRef } from '@/components/ChatBox';
import { useQuery } from '@tanstack/react-query';
import { getInitChatSiteInfo } from '@/web/core/chat/api';
import { getInitChatInfo } from '@/web/core/chat/api';
import Tag from '@/components/Tag';
import MyModal from '@/components/MyModal';
import DateRangePicker, { type DateRangeType } from '@/components/DateRangePicker';
@@ -199,7 +199,7 @@ function DetailLogsModal({
const { data: chat } = useQuery(
['getChatDetail', chatId],
() => getInitChatSiteInfo({ appId, chatId }),
() => getInitChatInfo({ appId, chatId }),
{
onSuccess(res) {
const history = res.history.map((item) => ({

View File

@@ -104,7 +104,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
>
<Flex mb={4} alignItems={'center'}>
<Avatar src={appDetail.avatar} w={'34px'} borderRadius={'lg'} />
<Box ml={2} fontWeight={'bold'}>
<Box ml={2} fontWeight={'bold'} fontSize={'sm'}>
{appDetail.name}
</Box>
</Flex>

View File

@@ -55,7 +55,7 @@ const ChatHistorySlider = ({
history: HistoryItemType[];
activeChatId: string;
onChangeChat: (chatId?: string) => void;
onDelHistory: (chatId: string) => void;
onDelHistory: (e: { chatId: string }) => void;
onClearHistory: () => void;
onSetHistoryTop?: (e: { chatId: string; top: boolean }) => void;
onSetCustomTitle?: (e: { chatId: string; title: string }) => void;
@@ -261,7 +261,7 @@ const ChatHistorySlider = ({
_hover={{ color: 'red.500' }}
onClick={(e) => {
e.stopPropagation();
onDelHistory(item.id);
onDelHistory({ chatId: item.id });
if (item.id === activeChatId) {
onChangeChat();
}

View File

@@ -1,7 +1,7 @@
import React, { useCallback, useRef } from 'react';
import Head from 'next/head';
import { useRouter } from 'next/router';
import { getInitChatSiteInfo, delChatRecordById, putChatHistory } from '@/web/core/chat/api';
import { getInitChatInfo, putChatHistory } from '@/web/core/chat/api';
import {
Box,
Flex,
@@ -34,6 +34,7 @@ import { serviceSideProps } from '@/web/common/utils/i18n';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils';
import { chatContentReplaceBlock } from '@fastgpt/global/core/chat/utils';
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
const router = useRouter();
@@ -49,13 +50,15 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
setLastChatAppId,
lastChatId,
setLastChatId,
history,
loadHistory,
histories,
loadHistories,
pushHistory,
updateHistory,
delHistory,
clearHistory,
delOneHistory,
clearHistories,
chatData,
setChatData
setChatData,
delOneHistoryItem
} = useChatStore();
const { myApps, loadMyApps } = useAppStore();
const { userInfo } = useUserStore();
@@ -85,7 +88,7 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
prompts[1]?.value?.slice(0, 20) ||
'新对话';
// update history
// new chat
if (completionChatId !== chatId) {
const newHistory: ChatHistoryItemType = {
chatId: completionChatId,
@@ -94,7 +97,7 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
appId,
top: false
};
updateHistory(newHistory);
pushHistory(newHistory);
if (controller.signal.reason !== 'leave') {
forbidRefresh.current = true;
router.replace({
@@ -105,7 +108,8 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
});
}
} else {
const currentChat = history.find((item) => item.chatId === chatId);
// update chat
const currentChat = histories.find((item) => item.chatId === chatId);
currentChat &&
updateHistory({
...currentChat,
@@ -117,30 +121,12 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
setChatData((state) => ({
...state,
title: newTitle,
history: ChatBoxRef.current?.getChatHistory() || state.history
history: ChatBoxRef.current?.getChatHistories() || state.history
}));
return { responseText, responseData, isNewChat: forbidRefresh.current };
},
[appId, chatId, history, router, setChatData, updateHistory]
);
// del one chat content
const delOneHistoryItem = useCallback(
async ({ contentId, index }: { contentId?: string; index: number }) => {
if (!chatId || !contentId) return;
try {
setChatData((state) => ({
...state,
history: state.history.filter((_, i) => i !== index)
}));
await delChatRecordById({ chatId, contentId });
} catch (err) {
console.log(err);
}
},
[chatId, setChatData]
[appId, chatId, histories, pushHistory, router, setChatData, updateHistory]
);
// get chat app info
@@ -156,10 +142,10 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
}) => {
try {
loading && setIsLoading(true);
const res = await getInitChatSiteInfo({ appId, chatId });
const res = await getInitChatInfo({ appId, chatId });
const history = res.history.map((item) => ({
...item,
status: 'finish' as any
status: ChatStatusEnum.finish
}));
setChatData({
@@ -185,8 +171,13 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
});
if (e?.code === 501) {
router.replace('/app/list');
} else {
router.replace('/chat');
} else if (chatId) {
router.replace({
query: {
...router.query,
chatId: ''
}
});
}
}
setIsLoading(false);
@@ -250,7 +241,7 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
});
});
useQuery(['loadHistory', appId], () => (appId ? loadHistory({ appId }) : null));
useQuery(['loadHistories', appId], () => (appId ? loadHistories({ appId }) : null));
return (
<Flex h={'100%'}>
@@ -289,7 +280,7 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
appAvatar={chatData.app.avatar}
activeChatId={chatId}
onClose={onCloseSlider}
history={history.map((item, i) => ({
history={histories.map((item, i) => ({
id: item.chatId,
title: item.title,
customTitle: item.customTitle,
@@ -306,39 +297,24 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
onCloseSlider();
}
}}
onDelHistory={delHistory}
onDelHistory={delOneHistory}
onClearHistory={() => {
clearHistory(appId);
clearHistories({ appId });
router.replace({
query: {
appId
}
});
}}
onSetHistoryTop={async (e) => {
try {
await putChatHistory(e);
const historyItem = history.find((item) => item.chatId === e.chatId);
if (!historyItem) return;
updateHistory({
...historyItem,
top: e.top
});
} catch (error) {}
onSetHistoryTop={(e) => {
updateHistory(e);
}}
onSetCustomTitle={async (e) => {
try {
putChatHistory({
chatId: e.chatId,
customTitle: e.title
});
const historyItem = history.find((item) => item.chatId === e.chatId);
if (!historyItem) return;
updateHistory({
...historyItem,
customTitle: e.title
});
} catch (error) {}
updateHistory({
chatId: e.chatId,
title: e.title,
customTitle: e.title
});
}}
/>
)}
@@ -372,7 +348,7 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
feedbackType={'user'}
onUpdateVariable={(e) => {}}
onStartChat={startChat}
onDelMessage={delOneHistoryItem}
onDelMessage={(e) => delOneHistoryItem({ ...e, chatId })}
/>
</Box>
</Flex>

View File

@@ -1,17 +1,16 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import Head from 'next/head';
import { useRouter } from 'next/router';
import { initShareChatInfo } from '@/web/support/outLink/api';
import { Box, Flex, useDisclosure, Drawer, DrawerOverlay, DrawerContent } from '@chakra-ui/react';
import { useToast } from '@/web/common/hooks/useToast';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useQuery } from '@tanstack/react-query';
import { streamFetch } from '@/web/common/api/fetch';
import { useShareChatStore, defaultHistory } from '@/web/core/chat/storeShareChat';
import { useShareChatStore } from '@/web/core/chat/storeShareChat';
import SideBar from '@/components/SideBar';
import { gptMessage2ChatType } from '@/utils/adapt';
import { getErrText } from '@fastgpt/global/common/error/utils';
import type { ChatSiteItemType } from '@fastgpt/global/core/chat/type.d';
import type { ChatHistoryItemType, ChatSiteItemType } from '@fastgpt/global/core/chat/type.d';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
@@ -21,6 +20,13 @@ import ChatHeader from './components/ChatHeader';
import ChatHistorySlider from './components/ChatHistorySlider';
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 { OutLinkErrEnum } from '@fastgpt/global/common/error/code/outLink';
const OutLink = ({
shareId,
@@ -33,6 +39,7 @@ const OutLink = ({
showHistory: '0' | '1';
authToken?: string;
}) => {
const { t } = useTranslation();
const router = useRouter();
const { toast } = useToast();
const { isOpen: isOpenSlider, onClose: onCloseSlider, onOpen: onOpenSlider } = useDisclosure();
@@ -43,18 +50,23 @@ const OutLink = ({
const ChatBoxRef = useRef<ComponentRef>(null);
const {
shareChatData,
setShareChatData,
shareChatHistory,
saveChatResponse,
delShareChatHistoryItemById,
delOneShareHistoryByChatId,
delManyShareChatHistoryByShareId
localUId,
shareChatHistory, // abandon
clearLocalHistory // abandon
} = useShareChatStore();
const history = useMemo(
() => shareChatHistory.filter((item) => item.shareId === shareId),
[shareChatHistory, shareId]
);
const {
histories,
loadHistories,
pushHistory,
updateHistory,
delOneHistory,
chatData,
setChatData,
delOneHistoryItem,
clearHistories
} = useChatStore();
const appId = chatData.appId;
const outLinkUid: string = authToken || localUId;
const startChat = useCallback(
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
@@ -67,12 +79,55 @@ const OutLink = ({
variables,
shareId,
chatId: completionChatId,
authToken
outLinkUid
},
onMessage: generatingMessage,
abortSignal: controller
});
const newTitle =
chatContentReplaceBlock(prompts[0].content).slice(0, 20) ||
prompts[1]?.value?.slice(0, 20) ||
'新对话';
// new chat
if (completionChatId !== chatId) {
const newHistory: ChatHistoryItemType = {
chatId: completionChatId,
updateTime: new Date(),
title: newTitle,
appId,
top: false
};
pushHistory(newHistory);
if (controller.signal.reason !== 'leave') {
forbidRefresh.current = true;
router.replace({
query: {
...router.query,
chatId: completionChatId
}
});
}
} else {
// update chat
const currentChat = histories.find((item) => item.chatId === chatId);
currentChat &&
updateHistory({
...currentChat,
updateTime: new Date(),
title: newTitle
});
}
// update chat window
setChatData((state) => ({
...state,
title: newTitle,
history: ChatBoxRef.current?.getChatHistories() || state.history
}));
/* post message to report result */
const result: ChatSiteItemType[] = gptMessage2ChatType(prompts).map((item) => ({
...item,
status: 'finish'
@@ -80,24 +135,6 @@ const OutLink = ({
result[1].value = responseText;
result[1].responseData = responseData;
/* save chat */
saveChatResponse({
chatId: completionChatId,
prompts: result,
variables,
shareId
});
if (completionChatId !== chatId && controller.signal.reason !== 'leave') {
forbidRefresh.current = true;
router.replace({
query: {
...router.query,
chatId: completionChatId
}
});
}
window.top?.postMessage(
{
type: 'shareChatFinish',
@@ -111,61 +148,80 @@ const OutLink = ({
return { responseText, responseData, isNewChat: forbidRefresh.current };
},
[authToken, chatId, router, saveChatResponse, shareId]
[chatId, shareId, outLinkUid, setChatData, appId, updateHistory, router, histories]
);
const loadAppInfo = useCallback(
async (shareId: string, chatId: string, authToken?: string) => {
const loadChatInfo = useCallback(
async (shareId: string, chatId: string) => {
if (!shareId) return null;
const history = shareChatHistory.find((item) => item.chatId === chatId) || defaultHistory;
ChatBoxRef.current?.resetHistory(history.chats);
ChatBoxRef.current?.resetVariables(history.variables);
try {
const chatData = await (async () => {
if (shareChatData.app.name === '') {
return initShareChatInfo({
shareId,
authToken
});
}
return shareChatData;
})();
const res = await getInitOutLinkChatInfo({
chatId,
shareId,
outLinkUid: authToken || localUId
});
const history = res.history.map((item) => ({
...item,
status: ChatStatusEnum.finish
}));
setShareChatData({
...chatData,
setChatData({
...res,
history
});
ChatBoxRef.current?.resetHistory(history);
ChatBoxRef.current?.resetVariables(res.variables);
if (res.history.length > 0) {
setTimeout(() => {
ChatBoxRef.current?.scrollToBottom('auto');
}, 500);
}
} catch (e: any) {
toast({
status: 'error',
title: getErrText(e, '获取应用失败')
title: getErrText(e, t('core.shareChat.Init Error'))
});
if (e?.code === 501) {
delManyShareChatHistoryByShareId(shareId);
if (chatId) {
router.replace({
query: {
...router.query,
chatId: ''
}
});
}
if (e?.statusText === OutLinkErrEnum.linkUnInvalid) {
router.replace('/');
}
}
if (history.chats.length > 0) {
setTimeout(() => {
ChatBoxRef.current?.scrollToBottom('auto');
}, 500);
}
return history;
return null;
},
[delManyShareChatHistoryByShareId, setShareChatData, shareChatData, shareChatHistory, toast]
[authToken, localUId, router, setChatData, t, toast]
);
useQuery(['init', shareId, chatId, authToken], () => {
useQuery(['init', shareId, chatId], () => {
if (forbidRefresh.current) {
forbidRefresh.current = false;
return null;
}
return loadAppInfo(shareId, chatId, authToken);
return loadChatInfo(shareId, chatId);
});
// load histories
useQuery(['loadHistories', outLinkUid, shareId], () => {
if (shareId && outLinkUid) {
return loadHistories({
shareId,
outLinkUid
});
}
return null;
});
// check is embed
useEffect(() => {
if (window !== top) {
window.top?.postMessage({ type: 'shareChatReady' }, '*');
@@ -173,10 +229,32 @@ 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 ? { p: '0 !important', borderRadius: '0' } : {})}>
<Head>
<title>{shareChatData.app.name}</title>
<title>{chatData.app.name}</title>
</Head>
<Flex h={'100%'} flexDirection={['column', 'row']}>
{showHistory === '1'
@@ -199,12 +277,14 @@ const OutLink = ({
);
})(
<ChatHistorySlider
appName={shareChatData.app.name}
appAvatar={shareChatData.app.avatar}
appName={chatData.app.name}
appAvatar={chatData.app.avatar}
activeChatId={chatId}
history={history.map((item) => ({
history={histories.map((item) => ({
id: item.chatId,
title: item.title
title: item.title,
customTitle: item.customTitle,
top: item.top
}))}
onClose={onCloseSlider}
onChangeChat={(chatId) => {
@@ -218,9 +298,9 @@ const OutLink = ({
onCloseSlider();
}
}}
onDelHistory={delOneShareHistoryByChatId}
onDelHistory={({ chatId }) => delOneHistory({ chatId, shareId, outLinkUid })}
onClearHistory={() => {
delManyShareChatHistoryByShareId(shareId);
clearHistories({ shareId, outLinkUid });
router.replace({
query: {
...router.query,
@@ -228,6 +308,16 @@ const OutLink = ({
}
});
}}
onSetHistoryTop={(e) => {
updateHistory(e);
}}
onSetCustomTitle={async (e) => {
updateHistory({
chatId: e.chatId,
title: e.title,
customTitle: e.title
});
}}
/>
)
: null}
@@ -242,36 +332,24 @@ const OutLink = ({
>
{/* header */}
<ChatHeader
appAvatar={shareChatData.app.avatar}
appName={shareChatData.app.name}
history={shareChatData.history.chats}
appAvatar={chatData.app.avatar}
appName={chatData.app.name}
history={chatData.history}
onOpenSlider={onOpenSlider}
/>
{/* chat box */}
<Box flex={1}>
<ChatBox
active={!!shareChatData.app.name}
active={!!chatData.app.name}
ref={ChatBoxRef}
appAvatar={shareChatData.app.avatar}
userAvatar={shareChatData.userAvatar}
userGuideModule={shareChatData.app?.userGuideModule}
showFileSelector={checkChatSupportSelectFileByChatModels(
shareChatData.app.chatModels
)}
appAvatar={chatData.app.avatar}
userAvatar={chatData.userAvatar}
userGuideModule={chatData.app?.userGuideModule}
showFileSelector={checkChatSupportSelectFileByChatModels(chatData.app.chatModels)}
feedbackType={'user'}
onUpdateVariable={(e) => {
setShareChatData((state) => ({
...state,
history: {
...state.history,
variables: e
}
}));
}}
onUpdateVariable={(e) => {}}
onStartChat={startChat}
onDelMessage={({ contentId, index }) =>
delShareChatHistoryItemById({ chatId, contentId, index })
}
onDelMessage={(e) => delOneHistoryItem({ ...e, chatId })}
/>
</Box>
</Flex>

View File

@@ -189,15 +189,6 @@ export async function searchDatasetData(props: SearchProps) {
})
).filter((item) => item.score > similarity);
// (It's possible that rerank failed) concat rerank results and search results
set = new Set<string>(reRankResults.map((item) => item.id));
embeddingRecallResults.forEach((item) => {
if (!set.has(item.id) && item.score >= similarity) {
reRankResults.push(item);
set.add(item.id);
}
});
return {
searchRes: reRankResults.slice(0, limit),
tokenLen
@@ -382,7 +373,7 @@ export async function reRankSearchResult({
} catch (error) {
console.log(error);
return [];
return data;
}
}
// ------------------ search end ------------------

View File

@@ -3,7 +3,7 @@ import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import { RunningModuleItemType } from '@/types/app';
import { ModuleDispatchProps } from '@/types/core/chat/type';
import { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { ModuleItemType } from '@fastgpt/global/core/module/type';
import { UserType } from '@fastgpt/global/support/user/type';

View File

@@ -48,7 +48,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
answerText,
responseData: {
moduleLogo: plugin.avatar,
price: responseData.reduce((sum, item) => sum + item.price, 0),
price: responseData.reduce((sum, item) => sum + (item.price || 0), 0),
runningTime: responseData.reduce((sum, item) => sum + (item.runningTime || 0), 0),
pluginOutput: output?.pluginOutput
},

View File

@@ -1,14 +0,0 @@
import { POST } from '@fastgpt/service/common/api/plusRequest';
import type {
AuthLinkLimitProps,
AuthShareChatInitProps
} from '@fastgpt/global/support/outLink/api.d';
export function authOutLinkLimit(data: AuthLinkLimitProps) {
return POST('/support/outLink/authLimit', data);
}
export function authShareChatInit(data: AuthShareChatInitProps) {
if (!global.feConfigs?.isPlus) return;
return POST('/support/outLink/authShareChatInit', data);
}

View File

@@ -0,0 +1,59 @@
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 { 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';
/*
outLink: Must be the owner
token: team owner and chat owner have all permissions
*/
export async function autChatCrud({
chatId,
shareId,
outLinkUid,
per = 'owner',
...props
}: AuthModeType & {
chatId?: string;
shareId?: string;
outLinkUid?: string;
}): Promise<{
chat?: ChatSchema;
isOutLink: boolean;
uid?: string;
}> {
const isOutLink = Boolean(shareId && outLinkUid);
if (!chatId) return { isOutLink, uid: outLinkUid };
const chat = await MongoChat.findOne({ 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) {
return { uid };
}
return Promise.reject(ChatErrEnum.unAuthChat);
}
// req auth
const { tmbId, role } = await authUserRole(props);
if (role === TeamMemberRoleEnum.owner) return { uid: outLinkUid };
if (String(tmbId) === String(chat.tmbId)) return { uid: outLinkUid };
return Promise.reject(ChatErrEnum.unAuthChat);
})();
return {
chat,
isOutLink,
uid
};
}

View File

@@ -1,31 +1,74 @@
import { authOutLinkLimit } from '@/service/support/outLink/auth';
import { AuthLinkChatProps } from '@fastgpt/global/support/outLink/api.d';
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
import { getUserAndAuthBalance } from './user';
import { POST } from '@fastgpt/service/common/api/plusRequest';
import type {
AuthOutLinkChatProps,
AuthOutLinkLimitProps,
AuthOutLinkInitProps,
AuthOutLinkResponse
} from '@fastgpt/global/support/outLink/api.d';
import { authOutLinkValid } from '@fastgpt/service/support/permission/auth/outLink';
import { getUserAndAuthBalance } from '@fastgpt/service/support/user/controller';
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
import { OutLinkErrEnum } from '@fastgpt/global/common/error/code/outLink';
import { OutLinkSchema } from '@fastgpt/global/support/outLink/type';
export async function authOutLinkChat({
export function authOutLinkInit(data: AuthOutLinkInitProps): Promise<AuthOutLinkResponse> {
if (!global.feConfigs?.isPlus) return Promise.resolve({ uid: data.outLinkUid });
return POST<AuthOutLinkResponse>('/support/outLink/authInit', data);
}
export function authOutLinkChatLimit(data: AuthOutLinkLimitProps): Promise<AuthOutLinkResponse> {
if (!global.feConfigs?.isPlus) return Promise.resolve({ uid: data.outLinkUid });
return POST<AuthOutLinkResponse>('/support/outLink/authChatStart', data);
}
export const authOutLink = async ({
shareId,
outLinkUid
}: {
shareId?: string;
outLinkUid?: string;
}): Promise<{
uid: string;
appId: string;
shareChat: OutLinkSchema;
}> => {
if (!outLinkUid) {
return Promise.reject(OutLinkErrEnum.linkUnInvalid);
}
const result = await authOutLinkValid({ shareId });
const { uid } = await authOutLinkInit({
outLinkUid,
tokenUrl: result.shareChat.limit?.hookUrl
});
return {
...result,
uid
};
};
export async function authOutLinkChatStart({
shareId,
ip,
authToken,
outLinkUid,
question
}: AuthLinkChatProps & {
}: AuthOutLinkChatProps & {
shareId: string;
}) {
// get outLink
const { shareChat, app } = await authOutLinkValid({ shareId });
// get outLink and app
const { shareChat, appId } = await authOutLinkValid({ shareId });
const [user] = await Promise.all([
// check balance and chat limit
const [user, { uid }] = await Promise.all([
getUserAndAuthBalance({ tmbId: shareChat.tmbId, minBalance: 0 }),
global.feConfigs?.isPlus
? authOutLinkLimit({ outLink: shareChat, ip, authToken, question })
: undefined
authOutLinkChatLimit({ outLink: shareChat, ip, outLinkUid, question })
]);
return {
authType: AuthUserTypeEnum.token,
responseDetail: shareChat.responseDetail,
user,
app
appId,
uid
};
}

View File

@@ -1,46 +0,0 @@
import { AuthResponseType } from '@fastgpt/global/support/permission/type';
import { parseHeaderCert } from '@fastgpt/service/support/permission/controller';
import { AuthModeType } from '@fastgpt/service/support/permission/type';
import { UserErrEnum } from '@fastgpt/global/common/error/code/user';
import { UserType } from '@fastgpt/global/support/user/type';
import { getUserDetail } from '@/service/support/user/controller';
export async function getUserAndAuthBalance({
tmbId,
minBalance
}: {
tmbId: string;
minBalance?: number;
}) {
const user = await getUserDetail({ tmbId });
if (!user) {
return Promise.reject(UserErrEnum.unAuthUser);
}
if (minBalance !== undefined && global.feConfigs.isPlus && user.team.balance < minBalance) {
return Promise.reject(UserErrEnum.balanceNotEnough);
}
return user;
}
/* get user */
export async function authUser({
minBalance,
...props
}: AuthModeType & {
minBalance?: number;
}): Promise<
AuthResponseType & {
user: UserType;
}
> {
const result = await parseHeaderCert(props);
return {
...result,
user: await getUserAndAuthBalance({ tmbId: result.tmbId, minBalance }),
isOwner: true,
canWrite: true
};
}

View File

@@ -1,41 +0,0 @@
import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode';
import { MongoUser } from '@fastgpt/service/support/user/schema';
import { UserType } from '@fastgpt/global/support/user/type';
import {
getTeamInfoByTmbId,
getUserDefaultTeam
} from '@fastgpt/service/support/user/team/controller';
export async function getUserDetail({
tmbId,
userId
}: {
tmbId?: string;
userId?: string;
}): Promise<UserType> {
const team = await (async () => {
if (tmbId) {
return getTeamInfoByTmbId({ tmbId });
}
if (userId) {
return getUserDefaultTeam({ userId });
}
return Promise.reject(ERROR_ENUM.unAuthorization);
})();
const user = await MongoUser.findById(team.userId);
if (!user) {
return Promise.reject(ERROR_ENUM.unAuthorization);
}
return {
_id: user._id,
username: user.username,
avatar: user.avatar,
balance: user.balance,
timezone: user.timezone,
promotionRate: user.promotionRate,
openaiAccount: user.openaiAccount,
team
};
}

View File

@@ -1,6 +1,6 @@
import { BillSourceEnum, PRICE_SCALE } from '@fastgpt/global/support/wallet/bill/constants';
import { getAudioSpeechModel, getQAModel } from '@/service/core/ai/model';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api.d';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
import { addLog } from '@fastgpt/service/common/mongo/controller';
import type { ConcatBillProps, CreateBillProps } from '@fastgpt/global/support/wallet/bill/api.d';
@@ -41,7 +41,7 @@ export const pushChatBill = ({
source: `${BillSourceEnum}`;
response: ChatHistoryItemResType[];
}) => {
const total = response.reduce((sum, item) => sum + item.price, 0);
const total = response.reduce((sum, item) => sum + (item.price || 0), 0);
createBill({
teamId,

View File

@@ -15,6 +15,7 @@ type Props = {
updateUseTime: boolean;
source: `${ChatSourceEnum}`;
shareId?: string;
outLinkUid?: string;
content: [ChatItemType, ChatItemType];
};
@@ -27,10 +28,11 @@ export async function saveChat({
updateUseTime,
source,
shareId,
outLinkUid,
content
}: Props) {
try {
const chatHistory = await MongoChat.findOne(
const chat = await MongoChat.findOne(
{
chatId,
teamId,
@@ -57,7 +59,7 @@ export async function saveChat({
content[1]?.value?.slice(0, 20) ||
'Chat';
if (chatHistory) {
if (chat) {
promise.push(
MongoChat.updateOne(
{ chatId },
@@ -77,7 +79,8 @@ export async function saveChat({
variables,
title,
source,
shareId
shareId,
outLinkUid
})
);
}

View File

@@ -1,11 +1,16 @@
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api.d';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
export function selectShareResponse({ responseData }: { responseData: ChatHistoryItemResType[] }) {
export function selectShareResponse({
responseData = []
}: {
responseData?: ChatHistoryItemResType[];
}) {
const filedList = [
'moduleType',
'moduleName',
'moduleLogo',
'runningTime',
'historyPreview',
'quoteList',
'question'
];
@@ -17,6 +22,6 @@ export function selectShareResponse({ responseData }: { responseData: ChatHistor
obj[key] = item[key];
}
}
return obj;
return obj as ChatHistoryItemResType;
});
}

View File

@@ -1,7 +1,7 @@
import { sseResponseEventEnum } from '@fastgpt/service/common/response/constant';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { parseStreamChunk, SSEParseData } from '@/utils/sse';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api.d';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
import { StartChatFnProps } from '@/components/ChatBox';
import { getToken } from '@/web/support/user/auth';
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';

View File

@@ -346,7 +346,7 @@ export const appTemplates: (AppItemType & {
{
key: 'similarity',
type: 'slider',
label: '相度',
label: '相度',
value: 0.4,
min: 0,
max: 1,
@@ -1398,7 +1398,7 @@ export const appTemplates: (AppItemType & {
{
key: 'similarity',
type: 'slider',
label: '相度',
label: '相度',
value: 0.76,
min: 0,
max: 1,

View File

@@ -1,42 +1,53 @@
import { GET, POST, DELETE, PUT } from '@/web/common/api/request';
import type { ChatHistoryItemType } from '@fastgpt/global/core/chat/type.d';
import type { InitChatResponse } from '@fastgpt/global/core/chat/api.d';
import type { RequestPaging } from '@/types';
import { UpdateHistoryProps } from '@fastgpt/global/core/chat/api.d';
import type { AdminUpdateFeedbackParams } from '@fastgpt/global/core/chat/api.d';
import { GetChatSpeechProps } from '@/global/core/chat/api.d';
import type {
InitChatProps,
InitChatResponse,
InitOutLinkChatProps,
getHistoriesProps
} from '@/global/core/chat/api.d';
import type {
AdminUpdateFeedbackParams,
ClearHistoriesProps,
DelHistoryProps,
DeleteChatItemProps,
UpdateHistoryProps
} from '@/global/core/chat/api.d';
/**
* 获取初始化聊天内容
*/
export const getInitChatSiteInfo = (data: { appId: string; chatId?: string }) =>
export const getInitChatInfo = (data: InitChatProps) =>
GET<InitChatResponse>(`/core/chat/init`, data);
export const getInitOutLinkChatInfo = (data: InitOutLinkChatProps) =>
GET<InitChatResponse>(`/core/chat/outLink/init`, data);
/**
* 获取历史记录
* get current window history(appid or shareId)
*/
export const getChatHistory = (data: RequestPaging & { appId: string }) =>
POST<ChatHistoryItemType[]>('/core/chat/list', data);
export const getChatHistories = (data: getHistoriesProps) =>
POST<ChatHistoryItemType[]>('/core/chat/getHistories', data);
/**
* 删除一条历史记录
* delete one history
*/
export const delChatHistoryById = (chatId: string) => DELETE(`/core/chat/delete`, { chatId });
export const delChatHistoryById = (data: DelHistoryProps) => DELETE(`/core/chat/delHistory`, data);
/**
* clear all history by appid
*/
export const clearChatHistoryByAppId = (appId: string) => DELETE(`/core/chat/delete`, { appId });
export const clearChatHistoryByAppId = (data: ClearHistoriesProps) =>
DELETE(`/core/chat/clearHistories`, data);
/**
* 删除一句对话
* delete one chat record
*/
export const delChatRecordById = (data: { chatId: string; contentId: string }) =>
export const delChatRecordById = (data: DeleteChatItemProps) =>
DELETE(`/core/chat/item/delete`, data);
/**
* 修改历史记录: 标题/置顶
*/
export const putChatHistory = (data: UpdateHistoryProps) => PUT('/core/chat/update', data);
export const putChatHistory = (data: UpdateHistoryProps) => PUT('/core/chat/updateHistory', data);
export const userUpdateChatFeedback = (data: { chatItemId: string; userFeedback?: string }) =>
POST('/core/chat/feedback/userUpdate', data);

View File

@@ -2,35 +2,36 @@ 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 type { InitChatResponse } from '@fastgpt/global/core/chat/api';
import { delChatHistoryById, getChatHistory, clearChatHistoryByAppId } from '@/web/core/chat/api';
import type {
InitChatResponse,
getHistoriesProps,
ClearHistoriesProps,
DelHistoryProps,
UpdateHistoryProps
} from '@/global/core/chat/api';
import {
delChatHistoryById,
getChatHistories,
clearChatHistoryByAppId,
delChatRecordById,
putChatHistory
} from '@/web/core/chat/api';
import { defaultChatData } from '@/global/core/chat/constants';
type State = {
history: ChatHistoryItemType[];
loadHistory: (data: { appId: string }) => Promise<null>;
delHistory(history: string): Promise<void>;
clearHistory(appId: string): Promise<void>;
updateHistory: (history: ChatHistoryItemType) => void;
histories: ChatHistoryItemType[];
loadHistories: (data: getHistoriesProps) => Promise<null>;
delOneHistory(data: DelHistoryProps): Promise<void>;
clearHistories(data: ClearHistoriesProps): Promise<void>;
pushHistory: (history: ChatHistoryItemType) => void;
updateHistory: (e: UpdateHistoryProps & { updateTime?: Date; title?: string }) => Promise<any>;
chatData: InitChatResponse;
setChatData: (e: InitChatResponse | ((e: InitChatResponse) => InitChatResponse)) => void;
lastChatAppId: string;
setLastChatAppId: (id: string) => void;
lastChatId: string;
setLastChatId: (id: string) => void;
};
const defaultChatData: InitChatResponse = {
chatId: '',
appId: '',
app: {
name: 'Loading',
avatar: '/icon/logo.svg',
intro: '',
canUse: false
},
title: '新对话',
variables: {},
history: []
delOneHistoryItem: (e: { chatId: string; contentId?: string; index: number }) => Promise<any>;
};
export const useChatStore = create<State>()(
@@ -49,49 +50,62 @@ export const useChatStore = create<State>()(
state.lastChatId = id;
});
},
history: [],
async loadHistory({ appId }) {
const oneHistory = get().history[0];
if (oneHistory && oneHistory.appId === appId) return null;
const data = await getChatHistory({
appId,
pageNum: 1,
pageSize: 20
});
histories: [],
async loadHistories(e) {
const data = await getChatHistories(e);
set((state) => {
state.history = data;
state.histories = data;
});
return null;
},
async delHistory(chatId) {
async delOneHistory(props) {
set((state) => {
state.history = state.history.filter((item) => item.chatId !== chatId);
state.histories = state.histories.filter((item) => item.chatId !== props.chatId);
});
await delChatHistoryById(chatId);
await delChatHistoryById(props);
},
async clearHistory(appId) {
async clearHistories(data) {
set((state) => {
state.history = [];
state.histories = [];
});
await clearChatHistoryByAppId(appId);
await clearChatHistoryByAppId(data);
},
updateHistory(history) {
const index = get().history.findIndex((item) => item.chatId === history.chatId);
pushHistory(history) {
set((state) => {
const newHistory = (() => {
if (index > -1) {
return [
history,
...get().history.slice(0, index),
...get().history.slice(index + 1)
];
} else {
return [history, ...state.history];
}
})();
state.histories = [history, ...state.histories];
});
},
async updateHistory(props) {
const { chatId, customTitle, top, title, updateTime } = props;
const index = get().histories.findIndex((item) => item.chatId === chatId);
state.history = newHistory;
});
if (index > -1) {
const newHistory = {
...get().histories[index],
...(title && { title }),
...(updateTime && { updateTime }),
...(customTitle !== undefined && { customTitle }),
...(top !== undefined && { top })
};
if (customTitle !== undefined || top !== undefined) {
try {
putChatHistory(props);
} catch (error) {}
}
set((state) => {
const newHistories = (() => {
return [
newHistory,
...get().histories.slice(0, index),
...get().histories.slice(index + 1)
];
})();
state.histories = newHistories;
});
}
},
chatData: defaultChatData,
setChatData(e = defaultChatData) {
@@ -104,6 +118,19 @@ export const useChatStore = create<State>()(
state.chatData = e;
});
}
},
async delOneHistoryItem({ chatId, contentId, index }) {
if (!chatId || !contentId) return;
try {
get().setChatData((state) => ({
...state,
history: state.history.filter((_, i) => i !== index)
}));
await delChatRecordById({ chatId, contentId });
} catch (err) {
console.log(err);
}
}
})),
{

View File

@@ -1,142 +1,39 @@
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import type {
ShareChatHistoryItemType,
ShareChatType
} from '@fastgpt/global/support/outLink/api.d';
import type { ChatSiteItemType } from '@fastgpt/global/core/chat/type.d';
import { HUMAN_ICON } from '@fastgpt/global/core/chat/constants';
import { chatContentReplaceBlock } from '@fastgpt/global/core/chat/utils';
import type { ChatHistoryItemType } from '@fastgpt/global/core/chat/type.d';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWSYZ1234567890_',
24
);
type State = {
shareChatData: ShareChatType;
setShareChatData: (e: ShareChatType | ((e: ShareChatType) => ShareChatType)) => void;
shareChatHistory: ShareChatHistoryItemType[];
saveChatResponse: (e: {
chatId: string;
prompts: ChatSiteItemType[];
variables: Record<string, any>;
shareId: string;
}) => void;
delOneShareHistoryByChatId: (chatId: string) => void;
delShareChatHistoryItemById: (e: { chatId: string; contentId?: string; index: number }) => void;
delManyShareChatHistoryByShareId: (shareId?: string) => void;
};
export const defaultHistory: ShareChatHistoryItemType = {
chatId: `${Date.now()}`,
updateTime: new Date(),
title: '新对话',
shareId: '',
chats: []
};
const defaultShareChatData: ShareChatType = {
userAvatar: HUMAN_ICON,
app: {
name: '',
avatar: '/icon/logo.svg',
intro: ''
},
history: defaultHistory
localUId: string;
shareChatHistory: (ChatHistoryItemType & { delete?: boolean })[];
clearLocalHistory: (shareId?: string) => void;
};
export const useShareChatStore = create<State>()(
devtools(
persist(
immer((set, get) => ({
shareChatData: defaultShareChatData,
setShareChatData(e) {
const val = (() => {
if (typeof e === 'function') {
return e(get().shareChatData);
} else {
return e;
}
})();
localUId: `shareChat-${Date.now()}-${nanoid()}`,
shareChatHistory: [], // old version field
clearLocalHistory() {
// abandon
set((state) => {
state.shareChatData = val;
// update history
state.shareChatHistory = state.shareChatHistory.map((item) =>
item.chatId === val.history.chatId ? val.history : item
);
});
},
shareChatHistory: [],
saveChatResponse({ chatId, prompts, variables, shareId }) {
const chatHistory = get().shareChatHistory.find((item) => item.chatId === chatId);
const newTitle =
chatContentReplaceBlock(prompts[prompts.length - 2]?.value).slice(0, 20) ||
prompts[prompts.length - 1]?.value?.slice(0, 20) ||
'Chat';
const historyList = (() => {
if (chatHistory) {
return get().shareChatHistory.map((item) =>
item.chatId === chatId
? {
...item,
title: newTitle,
updateTime: new Date(),
chats: chatHistory.chats.concat(prompts).slice(-30),
variables
}
: item
);
}
return get().shareChatHistory.concat({
chatId,
shareId,
title: newTitle,
updateTime: new Date(),
chats: prompts,
variables
});
})();
// @ts-ignore
historyList.sort((a, b) => new Date(b.updateTime) - new Date(a.updateTime));
set((state) => {
state.shareChatHistory = historyList.slice(0, 50);
});
},
delOneShareHistoryByChatId(chatId: string) {
set((state) => {
state.shareChatHistory = state.shareChatHistory.filter(
(item) => item.chatId !== chatId
);
});
},
delShareChatHistoryItemById({ chatId, contentId }) {
set((state) => {
// update history store
const newHistoryList = state.shareChatHistory.map((item) =>
item.chatId === chatId
? {
...item,
chats: item.chats.filter((item) => item.dataId !== contentId)
}
: item
);
state.shareChatHistory = newHistoryList;
});
},
delManyShareChatHistoryByShareId(shareId?: string) {
set((state) => {
if (shareId) {
state.shareChatHistory = state.shareChatHistory.filter(
(item) => item.shareId !== shareId
);
} else {
state.shareChatHistory = [];
}
state.shareChatHistory = state.shareChatHistory.map((item) => ({
...item,
delete: true
}));
});
}
})),
{
name: 'shareChatStore',
partialize: (state) => ({
localUId: state.localUId,
shareChatHistory: state.shareChatHistory
})
}

View File

@@ -79,8 +79,7 @@ export const useDatasetStore = create<State>()(
item._id === data.id
? {
...item,
...data,
tags: data.tags || []
...data
}
: item
);

View File

@@ -1,13 +1,6 @@
import { GET, POST, DELETE } from '@/web/common/api/request';
import type { InitShareChatResponse } from '@fastgpt/global/support/outLink/api.d';
import type { OutLinkEditType, OutLinkSchema } from '@fastgpt/global/support/outLink/type.d';
/**
* 初始化分享聊天
*/
export const initShareChatInfo = (data: { shareId: string; authToken?: string }) =>
GET<InitShareChatResponse>(`/support/outLink/init`, data);
/**
* create a shareChat
*/

View File

@@ -1,7 +1,5 @@
import { GET } from '@/web/common/api/request';
import type { PaySchema } from '@fastgpt/global/support/wallet/pay/type.d';
import { delay } from '@fastgpt/global/common/system/utils';
export const getPayOrders = () => GET<PaySchema[]>(`/plusApi/support/wallet/pay/getPayOrders`);
export const getPayCode = (amount: number) =>
@@ -11,15 +9,9 @@ export const getPayCode = (amount: number) =>
}>(`/plusApi/support/wallet/pay/getPayCode`, { amount });
export const checkPayResult = (payId: string) =>
GET<number>(`/plusApi/support/wallet/pay/checkPayResult`, { payId }).then(() => {
async function startQueue() {
try {
await GET('/common/system/unlockTask');
} catch (error) {
await delay(1000);
startQueue();
}
}
startQueue();
return 'success';
GET<string>(`/plusApi/support/wallet/pay/checkPayResult`, { payId }).then((data) => {
try {
GET('/common/system/unlockTask');
} catch (error) {}
return data;
});