diff --git a/docSite/content/docs/development/openapi/share.md b/docSite/content/docs/development/openapi/share.md index 906d7a3d1..4a8aa4f24 100644 --- a/docSite/content/docs/development/openapi/share.md +++ b/docSite/content/docs/development/openapi/share.md @@ -1,94 +1,175 @@ --- -title: '分享链接鉴权' -description: 'FastGPT 分享链接鉴权' +title: '分享链接身份鉴权' +description: 'FastGPT 分享链接身份鉴权' icon: 'share' draft: false toc: true weight: 860 --- +## 介绍 + +在 FastGPT V4.6.4 中,我们修改了分享链接的数据读取方式,为每个用户生成一个 localId,用于标识用户,从云端拉取对话记录。但是这种方式仅能保障用户在同一设备同一浏览器中使用,如果切换设备或者清空浏览器缓存则会丢失这些记录。这种方式存在一定的风险,因此我们仅允许用户拉取近`30天`的`20条`记录。 + +分享链接身份鉴权设计的目的在于,将 FastGPT 的对话框快速、安全的接入到你现有的系统中,仅需 2 个接口即可实现。 + ## 使用说明 -分享链接鉴权设计的目的在于,将 FastGPT 的对话框安全的接入你现有的系统中。 +免登录链接配置中,你可以选择填写`身份验证`栏。这是一个`POST`请求的根地址。在填写该地址后,分享链接的初始化、开始对话以及对话结束都会向该地址的特定接口发送一条请求。下面以`host`来表示`凭身份验证根地址`。服务器接口仅需返回是否校验成功即可,不需要返回其他数据,格式如下: -免登录链接配置中,增加了`凭证校验服务器`后,使用分享链接时会向服务器发起请求,校验链接是否可用,并在每次对话结束后,向服务器发送对话结果。下面以`host`来表示`凭证校验服务器`。服务器接口仅需返回是否校验成功即可,不需要返回其他数据,格式如下: +### 接口统一响应格式 ```json { "success": true, "message": "错误提示", - "msg": "同message, 错误提示" + "msg": "同message, 错误提示", + "uid": "用户唯一凭证" } ``` +`FastGPT` 将会判断`success`是否为`true`决定是允许用户继续操作。`message`与`msg`是等同的,你可以选择返回其中一个,当`success`不为`true`时,将会提示这个错误。 + +`uid`是用户的唯一凭证,将会用于拉取对话记录以及保存对话记录。可参考下方实践案例。 + +### 触发流程 + ![](/imgs/sharelinkProcess.png) -## 配置校验地址和校验token - -### 1. 配置校验地址的`BaseURL`、 +## 配置教程 +### 1. 配置身份校验地址 ![](/imgs/share-setlink.jpg) 配置校验地址后,在每次分享链接使用时,都会向对应的地址发起校验和上报请求。 +{{% alert icon="🤖" %}} +这里仅需配置根地址,无需具体到完整请求路径。 +{{% /alert %}} + ### 2. 分享链接中增加额外 query 在分享链接的地址中,增加一个额外的参数: authToken。例如: -原始的链接:https://fastgpt.run/chat/share?shareId=648aaf5ae121349a16d62192 -完整链接: https://fastgpt.run/chat/share?shareId=648aaf5ae121349a16d62192&authToken=userid12345 +原始的链接:`https://fastgpt.run/chat/share?shareId=648aaf5ae121349a16d62192` -这个`token`通常是你系统生成的,在发出校验请求时,FastGPT 会在`body`中携带 token={{authToken}} 的参数。 +完整链接: `https://fastgpt.run/chat/share?shareId=648aaf5ae121349a16d62192&authToken=userid12345` -## 聊天初始化校验 +这个`authToken`通常是你系统生成的用户唯一凭证(Token之类的)。FastGPT 会在鉴权接口的`body`中携带 token={{authToken}} 的参数。 -**FastGPT 发出的请求** +### 3. 编写聊天初始化校验接口 + +{{< tabs tabTotal="3" >}} +{{< tab tabName="请求示例" >}} +{{< markdownify >}} ```bash curl --location --request POST '{{host}}/shareAuth/init' \ --header 'Content-Type: application/json' \ --data-raw '{ - "token": "sintdolore" + "token": "{{authToken}}" }' ``` -**响应示例** +{{< /markdownify >}} +{{< /tab >}} + +{{< tab tabName="鉴权成功" >}} +{{< markdownify >}} + +```json +{ + "success": true, + "uid": "username123", +} +``` + +系统会拉取该分享链接下,uid 为 username123 的对话记录。 + +{{< /markdownify >}} +{{< /tab >}} + +{{< tab tabName="鉴权失败" >}} +{{< markdownify >}} ```json { "success": false, - "message": "分享链接无效", + "message": "身份错误", } ``` -## 对话前校验 +{{< /markdownify >}} +{{< /tab >}} +{{< /tabs >}} -**FastGPT 发出的请求** + + +### 4. 编写对话前校验接口 + +{{< tabs tabTotal="3" >}} +{{< tab tabName="请求示例" >}} +{{< markdownify >}} ```bash curl --location --request POST '{{host}}/shareAuth/start' \ --header 'Content-Type: application/json' \ --data-raw '{ - "token": "sintdolore", + "token": "{{authToken}}", "question": "用户问题", }' ``` -**响应示例** +{{< /markdownify >}} +{{< /tab >}} + +{{< tab tabName="鉴权成功" >}} +{{< markdownify >}} ```json { - "success": true + "success": true, + "uid": "username123", } ``` -## 对话结果上报 +{{< /markdownify >}} +{{< /tab >}} + +{{< tab tabName="鉴权失败" >}} +{{< markdownify >}} + +```json +{ + "success": false, + "message": "身份验证失败", +} +``` + +```json +{ + "success": false, + "message": "存在违规词", +} +``` + +{{< /markdownify >}} +{{< /tab >}} +{{< /tabs >}} + +### 5. 编写对话结果上报接口(可选) + +该接口无规定返回值。 + +响应值与[chat 接口格式相同](/docs/development/openapi/chat/#响应),仅多了一个`token`。 + +可以重点关注`responseData`里的`price`值,`price`与实际价格的倍率为`100000`,即 100000=1元。 ```bash curl --location --request POST '{{host}}/shareAuth/finish' \ --header 'Content-Type: application/json' \ --data-raw '{ - "token": "sint dolore", + "token": "{{authToken}}", "responseData": [ { "moduleName": "KB Search", @@ -156,18 +237,18 @@ curl --location --request POST '{{host}}/shareAuth/finish' \ }' ``` -响应值与 chat 接口相同,增加了一个 token。可以重点关注`responseData`里的值,price 与实际价格的倍率为`100000`。 -**此接口无需响应值** -## 使用示例 +## 实践案例 -我们以[Laf作为服务器为例](https://laf.dev/),展示这 3 个接口的使用方式。 +我们以[Laf作为服务器为例](https://laf.dev/),简单展示这 3 个接口的使用方式。 ### 1. 创建3个Laf接口 ![](/imgs/share-auth1.jpg) + + {{< tabs tabTotal="3" >}} {{< tab tabName="/shareAuth/init" >}} {{< markdownify >}} @@ -179,13 +260,15 @@ import cloud from '@lafjs/cloud' export default async function (ctx: FunctionContext) { const { token } = ctx.body - + + // 此处省略 token 解码过程 if (token === 'fastgpt') { - return { success: true } + return { success: true, data: { uid: "user1" } } } - return { success: false,message: "身份错误" } + return { success: false,message:"身份错误" } } + ``` {{< /markdownify >}} @@ -201,8 +284,8 @@ import cloud from '@lafjs/cloud' export default async function (ctx: FunctionContext) { const { token, question } = ctx.body - console.log(token, question, 'start') + // 此处省略 token 解码过程 if (token !== 'fastgpt') { return { success: false, message: "身份错误" } @@ -212,8 +295,9 @@ export default async function (ctx: FunctionContext) { return { success: false, message: "内容不合规" } } - return { success: true } + return { success: true, data: { uid: "user1" } } } + ``` {{< /markdownify >}} @@ -229,7 +313,12 @@ import cloud from '@lafjs/cloud' export default async function (ctx: FunctionContext) { const { token, responseData } = ctx.body - console.log(token,responseData,'=====') + + const total = responseData.reduce((sum,item) => sum + item.price,0) + const amount = total / 100000 + + // 省略数据库操作 + return { } } ``` @@ -241,17 +330,24 @@ export default async function (ctx: FunctionContext) { ### 2. 配置校验地址 -我们随便复制3个地址中一个接口:https://d8dns0.laf.dev/shareAuth/finish , 去除 /shareAuth/finish 后填入 FastGPT 中: https://d8dns0.laf.dev +我们随便复制3个地址中一个接口: `https://d8dns0.laf.dev/shareAuth/finish`, 去除`/shareAuth/finish`后填入`身份校验`:`https://d8dns0.laf.dev` ![](/imgs/share-auth2.jpg) ### 3. 修改分享链接参数 -源分享链接:[https://fastgpt.run/chat/share?shareId=64be36376a438af0311e599c](https://fastgpt.run/chat/share?shareId=64be36376a438af0311e599c) +源分享链接:`https://fastgpt.run/chat/share?shareId=64be36376a438af0311e599c` -修改后:[https://fastgpt.run/chat/share?shareId=64be36376a438af0311e599c&authToken=fastgpt](https://fastgpt.run/chat/share?shareId=64be36376a438af0311e599c&authToken=fastgpt) +修改后:`https://fastgpt.run/chat/share?shareId=64be36376a438af0311e599c&authToken=fastgpt` ### 4. 测试效果 -1. 打开源链接或者`authToken`不等于 `fastgpt`的链接会提示身份错误。 -2. 发送内容中包含你字,会提示内容不合规。 \ No newline at end of file +1. 打开源链接或者`authToken`不等于`fastgpt`的链接会提示身份错误。 +2. 发送内容中包含你字,会提示内容不合规。 + + +## 使用场景 + +这个鉴权方式通常是帮助你直接嵌入`分享链接`到你的应用中,在你的应用打开分享链接前,应做`authToken`的拼接后再打开。 + +除了对接已有系统的用户外,你还可以对接`余额`功能,通过`结果上报`接口扣除用户余额,通过`对话前校验`接口检查用户的余额。 \ No newline at end of file diff --git a/packages/global/common/error/code/outLink.ts b/packages/global/common/error/code/outLink.ts index ce726add1..81130e10f 100644 --- a/packages/global/common/error/code/outLink.ts +++ b/packages/global/common/error/code/outLink.ts @@ -4,7 +4,9 @@ import { ErrType } from '../errorCode'; export enum OutLinkErrEnum { unExist = 'unExist', unAuthLink = 'unAuthLink', - linkUnInvalid = 'linkUnInvalid' + linkUnInvalid = 'linkUnInvalid', + + unAuthUser = 'unAuthUser' } const errList = [ { @@ -19,6 +21,10 @@ const errList = [ code: 501, statusText: OutLinkErrEnum.linkUnInvalid, message: '分享链接无效' + }, + { + statusText: OutLinkErrEnum.unAuthUser, + message: '身份校验失败' } ]; export default errList.reduce((acc, cur, index) => { diff --git a/packages/global/core/chat/api.d.ts b/packages/global/core/chat/api.d.ts deleted file mode 100644 index 9ec93d581..000000000 --- a/packages/global/core/chat/api.d.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { ModuleItemType } from '../module/type'; -import { AdminFbkType, ChatItemType, moduleDispatchResType } from './type'; - -export type UpdateHistoryProps = { - chatId: string; - customTitle?: string; - top?: boolean; -}; - -export type AdminUpdateFeedbackParams = AdminFbkType & { - chatItemId: string; -}; - -export type InitChatResponse = { - chatId: string; - appId: string; - app: { - userGuideModule?: ModuleItemType; - chatModels?: string[]; - name: string; - avatar: string; - intro: string; - canUse?: boolean; - }; - title: string; - variables: Record; - history: ChatItemType[]; -}; - -export type ChatHistoryItemResType = moduleDispatchResType & { - moduleType: `${FlowNodeTypeEnum}`; - moduleName: string; -}; diff --git a/packages/global/core/chat/constants.ts b/packages/global/core/chat/constants.ts index 3e490abdc..000884eec 100644 --- a/packages/global/core/chat/constants.ts +++ b/packages/global/core/chat/constants.ts @@ -44,6 +44,12 @@ export const ChatSourceMap = { } }; +export enum ChatStatusEnum { + loading = 'loading', + running = 'running', + finish = 'finish' +} + export const HUMAN_ICON = `/icon/human.svg`; export const LOGO_ICON = `/icon/logo.svg`; diff --git a/packages/global/core/chat/type.d.ts b/packages/global/core/chat/type.d.ts index 61daac176..7df878637 100644 --- a/packages/global/core/chat/type.d.ts +++ b/packages/global/core/chat/type.d.ts @@ -1,6 +1,6 @@ import { ClassifyQuestionAgentItemType } from '../module/type'; import { SearchDataResponseItemType } from '../dataset/type'; -import { ChatRoleEnum, ChatSourceEnum } from './constants'; +import { ChatRoleEnum, ChatSourceEnum, ChatStatusEnum } from './constants'; import { FlowNodeTypeEnum } from '../module/node/constant'; import { ModuleOutputKeyEnum } from '../module/constants'; import { AppSchema } from '../app/type'; @@ -20,7 +20,7 @@ export type ChatSchema = { variables: Record; source: `${ChatSourceEnum}`; shareId?: string; - isInit: boolean; + outLinkUid?: string; content: ChatItemType[]; }; @@ -51,6 +51,7 @@ export type AdminFbkType = { a?: string; }; +/* --------- chat item ---------- */ export type ChatItemType = { dataId?: string; obj: ChatItemSchema['obj']; @@ -61,11 +62,12 @@ export type ChatItemType = { }; export type ChatSiteItemType = ChatItemType & { - status: 'loading' | 'running' | 'finish'; + status: `${ChatStatusEnum}`; moduleName?: string; ttsBuffer?: Uint8Array; }; +/* ---------- history ------------- */ export type HistoryItemType = { chatId: string; updateTime: Date; @@ -77,10 +79,10 @@ export type ChatHistoryItemType = HistoryItemType & { top: boolean; }; -// response data +/* ------- response data ------------ */ export type moduleDispatchResType = { moduleLogo?: string; - price: number; + price?: number; runningTime?: number; tokens?: number; model?: string; @@ -112,3 +114,8 @@ export type moduleDispatchResType = { // plugin output pluginOutput?: Record; }; + +export type ChatHistoryItemResType = moduleDispatchResType & { + moduleType: `${FlowNodeTypeEnum}`; + moduleName: string; +}; diff --git a/packages/global/core/dataset/api.d.ts b/packages/global/core/dataset/api.d.ts index e1906b725..2cf50439f 100644 --- a/packages/global/core/dataset/api.d.ts +++ b/packages/global/core/dataset/api.d.ts @@ -6,9 +6,9 @@ import type { LLMModelItemType } from '../ai/model.d'; export type DatasetUpdateBody = { id: string; parentId?: string; - tags?: string[]; name?: string; avatar?: string; + intro?: string; permission?: DatasetSchemaType['permission']; agentModel?: LLMModelItemType; websiteConfig?: DatasetSchemaType['websiteConfig']; diff --git a/packages/global/support/outLink/api.d.ts b/packages/global/support/outLink/api.d.ts index 6523e2b5b..c53532bae 100644 --- a/packages/global/support/outLink/api.d.ts +++ b/packages/global/support/outLink/api.d.ts @@ -1,27 +1,12 @@ import type { HistoryItemType, ChatSiteItemType } from '../../core/chat/type.d'; -import type { InitChatResponse } from '../../core/chat/api.d'; import { OutLinkSchema } from '@fastgpt/global/support/outLink/type'; -export type InitShareChatResponse = { - userAvatar: string; - app: InitChatResponse['app']; -}; - -/* one page type */ -export type ShareChatType = InitShareChatResponse & { - history: ShareChatHistoryItemType; -}; - -/* history list item type */ -export type ShareChatHistoryItemType = HistoryItemType & { - shareId: string; - variables?: Record; - chats: ChatSiteItemType[]; -}; - -export type AuthLinkChatProps = { ip?: string | null; authToken?: string; question: string }; -export type AuthLinkLimitProps = AuthLinkChatProps & { outLink: OutLinkSchema }; -export type AuthShareChatInitProps = { - authToken?: string; +export type AuthOutLinkInitProps = { + outLinkUid: string; tokenUrl?: string; }; +export type AuthOutLinkChatProps = { ip?: string | null; outLinkUid: string; question: string }; +export type AuthOutLinkLimitProps = AuthOutLinkChatProps & { outLink: OutLinkSchema }; +export type AuthOutLinkResponse = { + uid: string; +}; diff --git a/packages/global/support/outLink/type.d.ts b/packages/global/support/outLink/type.d.ts index 02e09e164..e6e19c5c5 100644 --- a/packages/global/support/outLink/type.d.ts +++ b/packages/global/support/outLink/type.d.ts @@ -3,7 +3,6 @@ import { OutLinkTypeEnum } from './constant'; export type OutLinkSchema = { _id: string; shareId: string; - userId: string; teamId: string; tmbId: string; appId: string; diff --git a/packages/global/support/user/team/constant.ts b/packages/global/support/user/team/constant.ts index 09b62b96d..b9840c2d0 100644 --- a/packages/global/support/user/team/constant.ts +++ b/packages/global/support/user/team/constant.ts @@ -45,4 +45,4 @@ export const TeamMemberStatusMap = { color: 'red.600' } }; -export const leaveStatus = { $ne: TeamMemberStatusEnum.leave }; +export const notLeaveStatus = { $ne: TeamMemberStatusEnum.leave }; diff --git a/packages/global/support/user/team/type.d.ts b/packages/global/support/user/team/type.d.ts index a711910d6..17f2c520b 100644 --- a/packages/global/support/user/team/type.d.ts +++ b/packages/global/support/user/team/type.d.ts @@ -1,5 +1,5 @@ -import { UserModelSchema } from '../type'; -import { TeamMemberRoleEnum, TeamMemberStatusEnum } from './constant'; +import type { UserModelSchema } from '../type'; +import type { TeamMemberRoleEnum, TeamMemberStatusEnum } from './constant'; export type TeamSchema = { _id: string; @@ -22,6 +22,16 @@ export type TeamMemberSchema = { defaultTeam: boolean; }; +export type TeamMemberWithUserSchema = TeamMemberSchema & { + userId: UserModelSchema; +}; +export type TeamMemberWithTeamSchema = TeamMemberSchema & { + teamId: TeamSchema; +}; +export type TeamMemberWithTeamAndUserSchema = TeamMemberWithTeamSchema & { + userId: UserModelSchema; +}; + export type TeamItemType = { userId: string; teamId: string; diff --git a/packages/service/common/mongo/init.ts b/packages/service/common/mongo/init.ts index 81526c140..d62c773f0 100644 --- a/packages/service/common/mongo/init.ts +++ b/packages/service/common/mongo/init.ts @@ -26,9 +26,13 @@ export async function connectMongo({ bufferCommands: true, maxConnecting: Number(process.env.DB_MAX_LINK || 5), maxPoolSize: Number(process.env.DB_MAX_LINK || 5), - minPoolSize: Number(process.env.DB_MAX_LINK || 10) * 0.5, + minPoolSize: Math.min(10, Number(process.env.DB_MAX_LINK || 10)), connectTimeoutMS: 60000, - waitQueueTimeoutMS: 60000 + waitQueueTimeoutMS: 60000, + socketTimeoutMS: 60000, + maxIdleTimeMS: 300000, + retryWrites: true, + retryReads: true }); console.log('mongo connected'); diff --git a/packages/service/core/chat/chatSchema.ts b/packages/service/core/chat/chatSchema.ts index 33e154e59..b94e1ba29 100644 --- a/packages/service/core/chat/chatSchema.ts +++ b/packages/service/core/chat/chatSchema.ts @@ -50,10 +50,6 @@ const ChatSchema = new Schema({ top: { type: Boolean }, - variables: { - type: Object, - default: {} - }, source: { type: String, enum: Object.keys(ChatSourceMap), @@ -62,9 +58,17 @@ const ChatSchema = new Schema({ shareId: { type: String }, - isInit: { - type: Boolean, - default: false + outLinkUid: { + type: String + }, + variables: { + type: Object, + default: {} + }, + metadata: { + //For special storage + type: Object, + default: {} }, content: { type: [ @@ -89,9 +93,10 @@ const ChatSchema = new Schema({ }); try { - ChatSchema.index({ tmbId: 1 }); - ChatSchema.index({ updateTime: -1 }); ChatSchema.index({ appId: 1 }); + ChatSchema.index({ tmbId: 1 }); + ChatSchema.index({ shareId: 1 }); + ChatSchema.index({ updateTime: -1 }); } catch (error) { console.log(error); } diff --git a/packages/service/core/chat/controller.ts b/packages/service/core/chat/controller.ts new file mode 100644 index 000000000..5063ec2fa --- /dev/null +++ b/packages/service/core/chat/controller.ts @@ -0,0 +1,22 @@ +import type { ChatItemType } from '@fastgpt/global/core/chat/type'; +import { MongoChatItem } from './chatItemSchema'; + +export async function getChatItems({ + chatId, + limit = 30, + field +}: { + chatId?: string; + limit?: number; + field: string; +}): Promise<{ history: ChatItemType[] }> { + if (!chatId) { + return { history: [] }; + } + + const history = await MongoChatItem.find({ chatId }, field).sort({ _id: -1 }).limit(limit); + + history.reverse(); + + return { history }; +} diff --git a/packages/service/support/outLink/schema.ts b/packages/service/support/outLink/schema.ts index 598a82902..acb7526ab 100644 --- a/packages/service/support/outLink/schema.ts +++ b/packages/service/support/outLink/schema.ts @@ -12,10 +12,6 @@ const OutLinkSchema = new Schema({ type: String, required: true }, - userId: { - type: Schema.Types.ObjectId, - ref: 'user' - }, teamId: { type: Schema.Types.ObjectId, ref: TeamCollectionName, diff --git a/packages/service/support/outLink/tools.ts b/packages/service/support/outLink/tools.ts index 71e2457c8..24ef6e5cc 100644 --- a/packages/service/support/outLink/tools.ts +++ b/packages/service/support/outLink/tools.ts @@ -22,15 +22,15 @@ export const updateOutLinkUsage = async ({ }; export const pushResult2Remote = async ({ - authToken, + outLinkUid, shareId, responseData }: { - authToken?: string; + outLinkUid?: string; // raw id, not parse shareId?: string; responseData?: any[]; }) => { - if (!shareId || !authToken || !global.systemEnv.pluginBaseUrl) return; + if (!shareId || !outLinkUid || !global.systemEnv.pluginBaseUrl) return; try { const outLink = await MongoOutLink.findOne({ shareId @@ -42,7 +42,7 @@ export const pushResult2Remote = async ({ baseURL: outLink.limit.hookUrl, url: '/shareAuth/finish', data: { - token: authToken, + token: outLinkUid, responseData } }); diff --git a/packages/service/support/permission/auth/app.ts b/packages/service/support/permission/auth/app.ts index 13cd86cd3..5970a0a56 100644 --- a/packages/service/support/permission/auth/app.ts +++ b/packages/service/support/permission/auth/app.ts @@ -19,6 +19,7 @@ export async function authApp({ AuthResponseType & { teamOwner: boolean; app: AppDetailType; + role: `${TeamMemberRoleEnum}`; } > { const result = await parseHeaderCert(props); @@ -65,6 +66,7 @@ export async function authApp({ return { ...result, app, + role, isOwner, canWrite, teamOwner: role === TeamMemberRoleEnum.owner diff --git a/packages/service/support/permission/auth/chat.ts b/packages/service/support/permission/auth/chat.ts deleted file mode 100644 index db89a4add..000000000 --- a/packages/service/support/permission/auth/chat.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { AuthResponseType } from '@fastgpt/global/support/permission/type'; -import { AuthModeType } from '../type'; -import type { ChatWithAppSchema } from '@fastgpt/global/core/chat/type'; -import { parseHeaderCert } from '../controller'; -import { MongoChat } from '../../../core/chat/chatSchema'; -import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat'; -import { getTeamInfoByTmbId } from '../../user/team/controller'; -import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; -import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant'; - -export async function authChat({ - chatId, - per = 'owner', - ...props -}: AuthModeType & { - chatId: string; -}): Promise< - AuthResponseType & { - chat: ChatWithAppSchema; - } -> { - const { userId, teamId, tmbId } = await parseHeaderCert(props); - const { role } = await getTeamInfoByTmbId({ tmbId }); - - const { chat, isOwner, canWrite } = await (async () => { - // get chat - const chat = (await MongoChat.findOne({ chatId, teamId }) - .populate('appId') - .lean()) as ChatWithAppSchema; - - if (!chat) { - return Promise.reject('Chat is not exists'); - } - - const isOwner = role === TeamMemberRoleEnum.owner || String(chat.tmbId) === tmbId; - const canWrite = isOwner; - - if ( - per === 'r' && - role !== TeamMemberRoleEnum.owner && - chat.appId.permission !== PermissionTypeEnum.public - ) { - return Promise.reject(ChatErrEnum.unAuthChat); - } - if (per === 'w' && !canWrite) { - return Promise.reject(ChatErrEnum.unAuthChat); - } - if (per === 'owner' && !isOwner) { - return Promise.reject(ChatErrEnum.unAuthChat); - } - - return { - chat, - isOwner, - canWrite - }; - })(); - - return { - userId, - teamId, - tmbId, - chat, - isOwner, - canWrite - }; -} diff --git a/packages/service/support/permission/auth/common.ts b/packages/service/support/permission/auth/common.ts index 7527a161c..682a81287 100644 --- a/packages/service/support/permission/auth/common.ts +++ b/packages/service/support/permission/auth/common.ts @@ -20,11 +20,11 @@ export async function authCertAndShareId({ return authCert(props); } - const { app } = await authOutLinkValid({ shareId }); + const { shareChat } = await authOutLinkValid({ shareId }); return { - teamId: String(app.teamId), - tmbId: String(app.tmbId), + teamId: String(shareChat.teamId), + tmbId: String(shareChat.tmbId), authType: AuthUserTypeEnum.outLink, apikey: '', isOwner: false, diff --git a/packages/service/support/permission/auth/outLink.ts b/packages/service/support/permission/auth/outLink.ts index 4250f03d7..030f5d90f 100644 --- a/packages/service/support/permission/auth/outLink.ts +++ b/packages/service/support/permission/auth/outLink.ts @@ -78,21 +78,19 @@ export async function authOutLinkCrud({ }; } +/* outLink exist and it app exist */ export async function authOutLinkValid({ shareId }: { shareId?: string }) { + if (!shareId) { + return Promise.reject(OutLinkErrEnum.linkUnInvalid); + } const shareChat = await MongoOutLink.findOne({ shareId }); if (!shareChat) { return Promise.reject(OutLinkErrEnum.linkUnInvalid); } - const app = await MongoApp.findById(shareChat.appId); - - if (!app) { - return Promise.reject(AppErrEnum.unExist); - } - return { - app, + appId: shareChat.appId, shareChat }; } diff --git a/packages/service/support/user/controller.ts b/packages/service/support/user/controller.ts index 5e4720604..b2020d193 100644 --- a/packages/service/support/user/controller.ts +++ b/packages/service/support/user/controller.ts @@ -1,4 +1,8 @@ +import { UserType } from '@fastgpt/global/support/user/type'; import { MongoUser } from './schema'; +import { getTeamInfoByTmbId, getUserDefaultTeam } from './team/controller'; +import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode'; +import { UserErrEnum } from '@fastgpt/global/common/error/code/user'; export async function authUserExist({ userId, username }: { userId?: string; username?: string }) { if (userId) { @@ -9,3 +13,56 @@ export async function authUserExist({ userId, username }: { userId?: string; use } return null; } + +export async function getUserDetail({ + tmbId, + userId +}: { + tmbId?: string; + userId?: string; +}): Promise { + 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 + }; +} + +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 && user.team.balance < minBalance) { + return Promise.reject(UserErrEnum.balanceNotEnough); + } + + return user; +} diff --git a/packages/service/support/user/team/controller.ts b/packages/service/support/user/team/controller.ts index 66eca315a..9efd97009 100644 --- a/packages/service/support/user/team/controller.ts +++ b/packages/service/support/user/team/controller.ts @@ -1,35 +1,15 @@ -import { TeamItemType } from '@fastgpt/global/support/user/team/type'; -import { connectionMongo, Types } from '../../../common/mongo'; +import { TeamItemType, TeamMemberWithTeamSchema } from '@fastgpt/global/support/user/team/type'; +import { Types } from '../../../common/mongo'; import { TeamMemberRoleEnum, TeamMemberStatusEnum, - TeamCollectionName, - TeamMemberCollectionName, - leaveStatus + notLeaveStatus } from '@fastgpt/global/support/user/team/constant'; +import { MongoTeamMember } from './teamMemberSchema'; +import { MongoTeam } from './teamSchema'; async function getTeam(match: Record): Promise { - const db = connectionMongo?.connection?.db; - - const TeamMember = db.collection(TeamMemberCollectionName); - - const results = await TeamMember.aggregate([ - { - $match: match - }, - { - $lookup: { - from: TeamCollectionName, - localField: 'teamId', - foreignField: '_id', - as: 'team' - } - }, - { - $unwind: '$team' - } - ]).toArray(); - const tmb = results[0]; + const tmb = (await MongoTeamMember.findOne(match).populate('teamId')) as TeamMemberWithTeamSchema; if (!tmb) { return Promise.reject('member not exist'); @@ -37,17 +17,17 @@ async function getTeam(match: Record): Promise { return { userId: String(tmb.userId), - teamId: String(tmb.teamId), - teamName: tmb.team.name, + teamId: String(tmb.teamId._id), + teamName: tmb.teamId.name, memberName: tmb.name, - avatar: tmb.team.avatar, - balance: tmb.team.balance, + avatar: tmb.teamId.avatar, + balance: tmb.teamId.balance, tmbId: String(tmb._id), role: tmb.role, status: tmb.status, defaultTeam: tmb.defaultTeam, canWrite: tmb.role !== TeamMemberRoleEnum.visitor, - maxSize: tmb.team.maxSize + maxSize: tmb.teamId.maxSize }; } @@ -57,7 +37,7 @@ export async function getTeamInfoByTmbId({ tmbId }: { tmbId: string }) { } return getTeam({ _id: new Types.ObjectId(tmbId), - status: leaveStatus + status: notLeaveStatus }); } @@ -83,12 +63,8 @@ export async function createDefaultTeam({ balance?: number; maxSize?: number; }) { - const db = connectionMongo.connection.db; - const Team = db.collection(TeamCollectionName); - const TeamMember = db.collection(TeamMemberCollectionName); - // auth default team - const tmb = await TeamMember.findOne({ + const tmb = await MongoTeamMember.findOne({ userId: new Types.ObjectId(userId), defaultTeam: true }); @@ -97,7 +73,7 @@ export async function createDefaultTeam({ console.log('create default team', userId); // create - const { insertedId } = await Team.insertOne({ + const { _id: insertedId } = await MongoTeam.create({ ownerId: userId, name: teamName, avatar, @@ -105,7 +81,7 @@ export async function createDefaultTeam({ maxSize, createTime: new Date() }); - await TeamMember.insertOne({ + await MongoTeamMember.create({ teamId: insertedId, userId, name: 'Owner', @@ -116,16 +92,11 @@ export async function createDefaultTeam({ }); } else { console.log('default team exist', userId); - await Team.updateOne( - { - _id: new Types.ObjectId(tmb.teamId) - }, - { - $set: { - ...(balance !== undefined && { balance }), - maxSize - } + await MongoTeam.findByIdAndUpdate(tmb.teamId, { + $set: { + ...(balance !== undefined && { balance }), + maxSize } - ); + }); } } diff --git a/packages/service/support/user/team/teamMemberSchema.ts b/packages/service/support/user/team/teamMemberSchema.ts new file mode 100644 index 000000000..183f435f2 --- /dev/null +++ b/packages/service/support/user/team/teamMemberSchema.ts @@ -0,0 +1,51 @@ +import { connectionMongo, type Model } from '../../../common/mongo'; +const { Schema, model, models } = connectionMongo; +import { TeamMemberSchema as TeamMemberType } from '@fastgpt/global/support/user/team/type.d'; +import { userCollectionName } from '../../user/schema'; +import { + TeamMemberRoleMap, + TeamMemberStatusMap, + TeamMemberCollectionName, + TeamCollectionName +} from '@fastgpt/global/support/user/team/constant'; + +const TeamMemberSchema = new Schema({ + teamId: { + type: Schema.Types.ObjectId, + ref: TeamCollectionName, + required: true + }, + userId: { + type: Schema.Types.ObjectId, + ref: userCollectionName, + required: true + }, + name: { + type: String, + default: 'Member' + }, + role: { + type: String, + enum: Object.keys(TeamMemberRoleMap) + }, + status: { + type: String, + enum: Object.keys(TeamMemberStatusMap) + }, + createTime: { + type: Date, + default: () => new Date() + }, + defaultTeam: { + type: Boolean, + default: false + } +}); + +try { +} catch (error) { + console.log(error); +} + +export const MongoTeamMember: Model = + models[TeamMemberCollectionName] || model(TeamMemberCollectionName, TeamMemberSchema); diff --git a/packages/service/support/user/team/teamSchema.ts b/packages/service/support/user/team/teamSchema.ts new file mode 100644 index 000000000..2098a4389 --- /dev/null +++ b/packages/service/support/user/team/teamSchema.ts @@ -0,0 +1,41 @@ +import { connectionMongo, type Model } from '../../../common/mongo'; +const { Schema, model, models } = connectionMongo; +import { TeamSchema as TeamType } from '@fastgpt/global/support/user/team/type.d'; +import { userCollectionName } from '../../user/schema'; +import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant'; +import { PRICE_SCALE } from '@fastgpt/global/support/wallet/bill/constants'; + +const TeamSchema = new Schema({ + name: { + type: String, + required: true + }, + ownerId: { + type: Schema.Types.ObjectId, + ref: userCollectionName + }, + avatar: { + type: String, + default: '/icon/logo.svg' + }, + createTime: { + type: Date, + default: () => Date.now() + }, + balance: { + type: Number, + default: 2 * PRICE_SCALE + }, + maxSize: { + type: Number, + default: 5 + } +}); + +try { +} catch (error) { + console.log(error); +} + +export const MongoTeam: Model = + models[TeamCollectionName] || model(TeamCollectionName, TeamSchema); diff --git a/projects/app/public/locales/en/common.json b/projects/app/public/locales/en/common.json index 756dcd4bb..4a98f6424 100644 --- a/projects/app/public/locales/en/common.json +++ b/projects/app/public/locales/en/common.json @@ -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": { diff --git a/projects/app/public/locales/zh/common.json b/projects/app/public/locales/zh/common.json index a8bff476a..48912a765 100644 --- a/projects/app/public/locales/zh/common.json +++ b/projects/app/public/locales/zh/common.json @@ -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": { diff --git a/projects/app/src/components/ChatBox/ResponseTags.tsx b/projects/app/src/components/ChatBox/ResponseTags.tsx index 6116b7a6b..014ed7114 100644 --- a/projects/app/src/components/ChatBox/ResponseTags.tsx +++ b/projects/app/src/components/ChatBox/ResponseTags.tsx @@ -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'; diff --git a/projects/app/src/components/ChatBox/WholeResponseModal.tsx b/projects/app/src/components/ChatBox/WholeResponseModal.tsx index a2d173c6c..6707f4ef2 100644 --- a/projects/app/src/components/ChatBox/WholeResponseModal.tsx +++ b/projects/app/src/components/ChatBox/WholeResponseModal.tsx @@ -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'; diff --git a/projects/app/src/components/ChatBox/index.tsx b/projects/app/src/components/ChatBox/index.tsx index 98301138b..a57246017 100644 --- a/projects/app/src/components/ChatBox/index.tsx +++ b/projects/app/src/components/ChatBox/index.tsx @@ -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) => 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(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 = {}; variableModules?.forEach((item) => { diff --git a/projects/app/src/components/core/module/DatasetParamsModal.tsx b/projects/app/src/components/core/module/DatasetParamsModal.tsx index 32055267b..7d63690fb 100644 --- a/projects/app/src/components/core/module/DatasetParamsModal.tsx +++ b/projects/app/src/components/core/module/DatasetParamsModal.tsx @@ -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); diff --git a/projects/app/src/global/core/chat/api.d.ts b/projects/app/src/global/core/chat/api.d.ts index b46958e9a..b4c54929a 100644 --- a/projects/app/src/global/core/chat/api.d.ts +++ b/projects/app/src/global/core/chat/api.d.ts @@ -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; + 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; +}; diff --git a/projects/app/src/global/core/chat/constants.ts b/projects/app/src/global/core/chat/constants.ts new file mode 100644 index 000000000..72f3b5363 --- /dev/null +++ b/projects/app/src/global/core/chat/constants.ts @@ -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: [] +}; diff --git a/projects/app/src/global/core/prompt/AIChat.ts b/projects/app/src/global/core/prompt/AIChat.ts index 980617f16..a44c132fa 100644 --- a/projects/app/src/global/core/prompt/AIChat.ts +++ b/projects/app/src/global/core/prompt/AIChat.ts @@ -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}}"` }, { diff --git a/projects/app/src/pages/account/components/PayModal.tsx b/projects/app/src/pages/account/components/PayModal.tsx index 5eeb16073..a30b7d51a 100644 --- a/projects/app/src/pages/account/components/PayModal.tsx +++ b/projects/app/src/pages/account/components/PayModal.tsx @@ -54,7 +54,7 @@ const PayModal = ({ onClose }: { onClose: () => void }) => { onSuccess(res) { if (!res) return; toast({ - title: '充值成功', + title: res, status: 'success' }); router.reload(); diff --git a/projects/app/src/pages/api/common/system/unlockTask.ts b/projects/app/src/pages/api/common/system/unlockTask.ts index 1fae29b38..b89768883 100644 --- a/projects/app/src/pages/api/common/system/unlockTask.ts +++ b/projects/app/src/pages/api/common/system/unlockTask.ts @@ -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) { 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); - } -} diff --git a/projects/app/src/pages/api/core/app/del.ts b/projects/app/src/pages/api/core/app/del.ts index ec1114804..c3e6eb11c 100644 --- a/projects/app/src/pages/api/core/app/del.ts +++ b/projects/app/src/pages/api/core/app/del.ts @@ -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) { @@ -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 }); diff --git a/projects/app/src/pages/api/core/app/form2Modules/fastgpt-simple.ts b/projects/app/src/pages/api/core/app/form2Modules/fastgpt-simple.ts index ca65f345f..651acd1d4 100644 --- a/projects/app/src/pages/api/core/app/form2Modules/fastgpt-simple.ts +++ b/projects/app/src/pages/api/core/app/form2Modules/fastgpt-simple.ts @@ -381,7 +381,7 @@ function datasetTemplate({ key: 'similarity', value: 0.4, type: FlowNodeInputTypeEnum.slider, - label: '相似度', + label: '相关度', connected: true }, { diff --git a/projects/app/src/pages/api/core/app/form2Modules/fastgpt-universal.ts b/projects/app/src/pages/api/core/app/form2Modules/fastgpt-universal.ts index 1f157f974..1c7b2d6d3 100644 --- a/projects/app/src/pages/api/core/app/form2Modules/fastgpt-universal.ts +++ b/projects/app/src/pages/api/core/app/form2Modules/fastgpt-universal.ts @@ -289,7 +289,7 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] { key: 'similarity', value: formData.dataset.similarity, type: FlowNodeInputTypeEnum.slider, - label: '相似度', + label: '相关度', connected: false }, { diff --git a/projects/app/src/pages/api/core/chat/chatTest.ts b/projects/app/src/pages/api/core/chat/chatTest.ts index 0f9169c2a..9d0898e88 100644 --- a/projects/app/src/pages/api/core/chat/chatTest.ts +++ b/projects/app/src/pages/api/core/chat/chatTest.ts @@ -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, diff --git a/projects/app/src/pages/api/core/chat/clearHistories.ts b/projects/app/src/pages/api/core/chat/clearHistories.ts new file mode 100644 index 000000000..05cbb1ca1 --- /dev/null +++ b/projects/app/src/pages/api/core/chat/clearHistories.ts @@ -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 + }); + } +} diff --git a/projects/app/src/pages/api/core/chat/delHistory.ts b/projects/app/src/pages/api/core/chat/delHistory.ts new file mode 100644 index 000000000..1b67c4287 --- /dev/null +++ b/projects/app/src/pages/api/core/chat/delHistory.ts @@ -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 + }); + } +} diff --git a/projects/app/src/pages/api/core/chat/delete.ts b/projects/app/src/pages/api/core/chat/delete.ts deleted file mode 100644 index 4a74dc3df..000000000 --- a/projects/app/src/pages/api/core/chat/delete.ts +++ /dev/null @@ -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 - }); - } -} diff --git a/projects/app/src/pages/api/core/chat/feedback/adminUpdate.ts b/projects/app/src/pages/api/core/chat/feedback/adminUpdate.ts index aa4f8613d..b19b72145 100644 --- a/projects/app/src/pages/api/core/chat/feedback/adminUpdate.ts +++ b/projects/app/src/pages/api/core/chat/feedback/adminUpdate.ts @@ -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'; /* 初始化我的聊天框,需要身份验证 */ diff --git a/projects/app/src/pages/api/core/chat/getHistories.ts b/projects/app/src/pages/api/core/chat/getHistories.ts new file mode 100644 index 000000000..fe68beea8 --- /dev/null +++ b/projects/app/src/pages/api/core/chat/getHistories.ts @@ -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(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 + }); + } +} diff --git a/projects/app/src/pages/api/core/chat/init.ts b/projects/app/src/pages/api/core/chat/init.ts index 9fc2bd96d..b24c9cfd6 100644 --- a/projects/app/src/pages/api/core/chat/init.ts +++ b/projects/app/src/pages/api/core/chat/init.ts @@ -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(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) { diff --git a/projects/app/src/pages/api/core/chat/initLocalShareHistoryV464.ts b/projects/app/src/pages/api/core/chat/initLocalShareHistoryV464.ts new file mode 100644 index 000000000..81fbef43d --- /dev/null +++ b/projects/app/src/pages/api/core/chat/initLocalShareHistoryV464.ts @@ -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 + }); + } +} diff --git a/projects/app/src/pages/api/core/chat/item/delete.ts b/projects/app/src/pages/api/core/chat/item/delete.ts index c26945829..f9ec4263b 100644 --- a/projects/app/src/pages/api/core/chat/item/delete.ts +++ b/projects/app/src/pages/api/core/chat/item/delete.ts @@ -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, diff --git a/projects/app/src/pages/api/core/chat/list.ts b/projects/app/src/pages/api/core/chat/list.ts deleted file mode 100644 index 3a47e058d..000000000 --- a/projects/app/src/pages/api/core/chat/list.ts +++ /dev/null @@ -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(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 - }); - } -} diff --git a/projects/app/src/pages/api/core/chat/outLink/init.ts b/projects/app/src/pages/api/core/chat/outLink/init.ts new file mode 100644 index 000000000..41ee92915 --- /dev/null +++ b/projects/app/src/pages/api/core/chat/outLink/init.ts @@ -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(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' + } +}; diff --git a/projects/app/src/pages/api/core/chat/update.ts b/projects/app/src/pages/api/core/chat/updateHistory.ts similarity index 62% rename from projects/app/src/pages/api/core/chat/update.ts rename to projects/app/src/pages/api/core/chat/updateHistory.ts index 428cab250..f3b647436 100644 --- a/projects/app/src/pages/api/core/chat/update.ts +++ b/projects/app/src/pages/api/core/chat/updateHistory.ts @@ -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 }, diff --git a/projects/app/src/pages/api/core/dataset/update.ts b/projects/app/src/pages/api/core/dataset/update.ts index fa2f02714..83b93a70b 100644 --- a/projects/app/src/pages/api/core/dataset/update.ts +++ b/projects/app/src/pages/api/core/dataset/update.ts @@ -8,7 +8,7 @@ import { authDataset } from '@fastgpt/service/support/permission/auth/dataset'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { 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 }) } ); diff --git a/projects/app/src/pages/api/support/outLink/init.ts b/projects/app/src/pages/api/support/outLink/init.ts deleted file mode 100644 index 6d5427ef8..000000000 --- a/projects/app/src/pages/api/support/outLink/init.ts +++ /dev/null @@ -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(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 - }); - } -} diff --git a/projects/app/src/pages/api/user/account/loginByPassword.ts b/projects/app/src/pages/api/user/account/loginByPassword.ts index f2f431d37..626322abf 100644 --- a/projects/app/src/pages/api/user/account/loginByPassword.ts +++ b/projects/app/src/pages/api/user/account/loginByPassword.ts @@ -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) { diff --git a/projects/app/src/pages/api/user/account/tokenLogin.ts b/projects/app/src/pages/api/user/account/tokenLogin.ts index cd2db7e13..fca3069b0 100644 --- a/projects/app/src/pages/api/user/account/tokenLogin.ts +++ b/projects/app/src/pages/api/user/account/tokenLogin.ts @@ -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 { diff --git a/projects/app/src/pages/api/v1/audio/transcriptions.ts b/projects/app/src/pages/api/v1/audio/transcriptions.ts index bf0ba0fac..7db10abdf 100644 --- a/projects/app/src/pages/api/v1/audio/transcriptions.ts +++ b/projects/app/src/pages/api/v1/audio/transcriptions.ts @@ -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'; diff --git a/projects/app/src/pages/api/v1/chat/completions.ts b/projects/app/src/pages/api/v1/chat/completions.ts index a73d4657a..40c350c2f 100644 --- a/projects/app/src/pages/api/v1/chat/completions.ts +++ b/projects/app/src/pages/api/v1/chat/completions.ts @@ -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 diff --git a/projects/app/src/pages/api/v1/chat/getHistory.ts b/projects/app/src/pages/api/v1/chat/getHistory.ts deleted file mode 100644 index 3c2f109a8..000000000 --- a/projects/app/src/pages/api/v1/chat/getHistory.ts +++ /dev/null @@ -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(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 { - 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 }; -} diff --git a/projects/app/src/pages/app/detail/components/Logs.tsx b/projects/app/src/pages/app/detail/components/Logs.tsx index d7c34416b..c5a746a1d 100644 --- a/projects/app/src/pages/app/detail/components/Logs.tsx +++ b/projects/app/src/pages/app/detail/components/Logs.tsx @@ -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) => ({ diff --git a/projects/app/src/pages/app/detail/index.tsx b/projects/app/src/pages/app/detail/index.tsx index 90c08ad2d..c9cfb6f80 100644 --- a/projects/app/src/pages/app/detail/index.tsx +++ b/projects/app/src/pages/app/detail/index.tsx @@ -104,7 +104,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => { > - + {appDetail.name} diff --git a/projects/app/src/pages/chat/components/ChatHistorySlider.tsx b/projects/app/src/pages/chat/components/ChatHistorySlider.tsx index 4e546978b..184f893cf 100644 --- a/projects/app/src/pages/chat/components/ChatHistorySlider.tsx +++ b/projects/app/src/pages/chat/components/ChatHistorySlider.tsx @@ -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(); } diff --git a/projects/app/src/pages/chat/index.tsx b/projects/app/src/pages/chat/index.tsx index f6a94b672..a7f8cb0f4 100644 --- a/projects/app/src/pages/chat/index.tsx +++ b/projects/app/src/pages/chat/index.tsx @@ -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 ( @@ -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 })} /> diff --git a/projects/app/src/pages/chat/share.tsx b/projects/app/src/pages/chat/share.tsx index de0e404d4..cdaa20a92 100644 --- a/projects/app/src/pages/chat/share.tsx +++ b/projects/app/src/pages/chat/share.tsx @@ -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(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 ( - {shareChatData.app.name} + {chatData.app.name} {showHistory === '1' @@ -199,12 +277,14 @@ const OutLink = ({ ); })( ({ + 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 */} {/* chat box */} { - setShareChatData((state) => ({ - ...state, - history: { - ...state.history, - variables: e - } - })); - }} + onUpdateVariable={(e) => {}} onStartChat={startChat} - onDelMessage={({ contentId, index }) => - delShareChatHistoryItemById({ chatId, contentId, index }) - } + onDelMessage={(e) => delOneHistoryItem({ ...e, chatId })} /> diff --git a/projects/app/src/service/core/dataset/data/pg.ts b/projects/app/src/service/core/dataset/data/pg.ts index 0744e0310..e2c02cae5 100644 --- a/projects/app/src/service/core/dataset/data/pg.ts +++ b/projects/app/src/service/core/dataset/data/pg.ts @@ -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(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 ------------------ diff --git a/projects/app/src/service/moduleDispatch/index.ts b/projects/app/src/service/moduleDispatch/index.ts index c974e6919..213b22d4e 100644 --- a/projects/app/src/service/moduleDispatch/index.ts +++ b/projects/app/src/service/moduleDispatch/index.ts @@ -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'; diff --git a/projects/app/src/service/moduleDispatch/plugin/run.ts b/projects/app/src/service/moduleDispatch/plugin/run.ts index 361823baf..fbb0852ad 100644 --- a/projects/app/src/service/moduleDispatch/plugin/run.ts +++ b/projects/app/src/service/moduleDispatch/plugin/run.ts @@ -48,7 +48,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise 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 }, diff --git a/projects/app/src/service/support/outLink/auth.ts b/projects/app/src/service/support/outLink/auth.ts deleted file mode 100644 index 4a0a9c123..000000000 --- a/projects/app/src/service/support/outLink/auth.ts +++ /dev/null @@ -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); -} diff --git a/projects/app/src/service/support/permission/auth/chat.ts b/projects/app/src/service/support/permission/auth/chat.ts new file mode 100644 index 000000000..ce63422c2 --- /dev/null +++ b/projects/app/src/service/support/permission/auth/chat.ts @@ -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 + }; +} diff --git a/projects/app/src/service/support/permission/auth/outLink.ts b/projects/app/src/service/support/permission/auth/outLink.ts index 3559e6fe3..5a32e4ea4 100644 --- a/projects/app/src/service/support/permission/auth/outLink.ts +++ b/projects/app/src/service/support/permission/auth/outLink.ts @@ -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 { + if (!global.feConfigs?.isPlus) return Promise.resolve({ uid: data.outLinkUid }); + return POST('/support/outLink/authInit', data); +} +export function authOutLinkChatLimit(data: AuthOutLinkLimitProps): Promise { + if (!global.feConfigs?.isPlus) return Promise.resolve({ uid: data.outLinkUid }); + return POST('/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 }; } diff --git a/projects/app/src/service/support/permission/auth/user.ts b/projects/app/src/service/support/permission/auth/user.ts deleted file mode 100644 index 44fd222ae..000000000 --- a/projects/app/src/service/support/permission/auth/user.ts +++ /dev/null @@ -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 - }; -} diff --git a/projects/app/src/service/support/user/controller.ts b/projects/app/src/service/support/user/controller.ts deleted file mode 100644 index 7b0ea27aa..000000000 --- a/projects/app/src/service/support/user/controller.ts +++ /dev/null @@ -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 { - 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 - }; -} diff --git a/projects/app/src/service/support/wallet/bill/push.ts b/projects/app/src/service/support/wallet/bill/push.ts index f853b68f9..16d9a41f1 100644 --- a/projects/app/src/service/support/wallet/bill/push.ts +++ b/projects/app/src/service/support/wallet/bill/push.ts @@ -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, diff --git a/projects/app/src/service/utils/chat/saveChat.ts b/projects/app/src/service/utils/chat/saveChat.ts index 8b11f0ba7..aaab85424 100644 --- a/projects/app/src/service/utils/chat/saveChat.ts +++ b/projects/app/src/service/utils/chat/saveChat.ts @@ -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 }) ); } diff --git a/projects/app/src/utils/service/core/chat/index.ts b/projects/app/src/utils/service/core/chat/index.ts index 180fc9af1..d481b212b 100644 --- a/projects/app/src/utils/service/core/chat/index.ts +++ b/projects/app/src/utils/service/core/chat/index.ts @@ -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; }); } diff --git a/projects/app/src/web/common/api/fetch.ts b/projects/app/src/web/common/api/fetch.ts index ce7a7adfc..76e110030 100644 --- a/projects/app/src/web/common/api/fetch.ts +++ b/projects/app/src/web/common/api/fetch.ts @@ -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'; diff --git a/projects/app/src/web/core/app/templates.ts b/projects/app/src/web/core/app/templates.ts index 382e60527..f4b408200 100644 --- a/projects/app/src/web/core/app/templates.ts +++ b/projects/app/src/web/core/app/templates.ts @@ -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, diff --git a/projects/app/src/web/core/chat/api.ts b/projects/app/src/web/core/chat/api.ts index 5050a611b..dc1dd3cde 100644 --- a/projects/app/src/web/core/chat/api.ts +++ b/projects/app/src/web/core/chat/api.ts @@ -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(`/core/chat/init`, data); +export const getInitOutLinkChatInfo = (data: InitOutLinkChatProps) => + GET(`/core/chat/outLink/init`, data); /** - * 获取历史记录 + * get current window history(appid or shareId) */ -export const getChatHistory = (data: RequestPaging & { appId: string }) => - POST('/core/chat/list', data); +export const getChatHistories = (data: getHistoriesProps) => + POST('/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); diff --git a/projects/app/src/web/core/chat/storeChat.ts b/projects/app/src/web/core/chat/storeChat.ts index 98f49d2d9..620b09538 100644 --- a/projects/app/src/web/core/chat/storeChat.ts +++ b/projects/app/src/web/core/chat/storeChat.ts @@ -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; - delHistory(history: string): Promise; - clearHistory(appId: string): Promise; - updateHistory: (history: ChatHistoryItemType) => void; + histories: ChatHistoryItemType[]; + loadHistories: (data: getHistoriesProps) => Promise; + delOneHistory(data: DelHistoryProps): Promise; + clearHistories(data: ClearHistoriesProps): Promise; + pushHistory: (history: ChatHistoryItemType) => void; + updateHistory: (e: UpdateHistoryProps & { updateTime?: Date; title?: string }) => Promise; 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; }; export const useChatStore = create()( @@ -49,49 +50,62 @@ export const useChatStore = create()( 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.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); + } } })), { diff --git a/projects/app/src/web/core/chat/storeShareChat.ts b/projects/app/src/web/core/chat/storeShareChat.ts index e38004bb5..00391408a 100644 --- a/projects/app/src/web/core/chat/storeShareChat.ts +++ b/projects/app/src/web/core/chat/storeShareChat.ts @@ -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; - 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()( 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 }) } diff --git a/projects/app/src/web/core/dataset/store/dataset.ts b/projects/app/src/web/core/dataset/store/dataset.ts index 14b93e5f2..749dd081c 100644 --- a/projects/app/src/web/core/dataset/store/dataset.ts +++ b/projects/app/src/web/core/dataset/store/dataset.ts @@ -79,8 +79,7 @@ export const useDatasetStore = create()( item._id === data.id ? { ...item, - ...data, - tags: data.tags || [] + ...data } : item ); diff --git a/projects/app/src/web/support/outLink/api.ts b/projects/app/src/web/support/outLink/api.ts index 847024672..6cf1763c8 100644 --- a/projects/app/src/web/support/outLink/api.ts +++ b/projects/app/src/web/support/outLink/api.ts @@ -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(`/support/outLink/init`, data); - /** * create a shareChat */ diff --git a/projects/app/src/web/support/wallet/pay/api.ts b/projects/app/src/web/support/wallet/pay/api.ts index 506979dda..867630c66 100644 --- a/projects/app/src/web/support/wallet/pay/api.ts +++ b/projects/app/src/web/support/wallet/pay/api.ts @@ -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(`/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(`/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(`/plusApi/support/wallet/pay/checkPayResult`, { payId }).then((data) => { + try { + GET('/common/system/unlockTask'); + } catch (error) {} + return data; });