Commit e01c38ef by Archer Committed by GitHub

4.6.4-alpha (#569)

parent 71afe711
--- ---
title: '分享链接鉴权' title: '分享链接身份鉴权'
description: 'FastGPT 分享链接鉴权' description: 'FastGPT 分享链接身份鉴权'
icon: 'share' icon: 'share'
draft: false draft: false
toc: true toc: true
weight: 860 weight: 860
--- ---
## 介绍
在 FastGPT V4.6.4 中,我们修改了分享链接的数据读取方式,为每个用户生成一个 localId,用于标识用户,从云端拉取对话记录。但是这种方式仅能保障用户在同一设备同一浏览器中使用,如果切换设备或者清空浏览器缓存则会丢失这些记录。这种方式存在一定的风险,因此我们仅允许用户拉取近`30天``20条`记录。
分享链接身份鉴权设计的目的在于,将 FastGPT 的对话框快速、安全的接入到你现有的系统中,仅需 2 个接口即可实现。
## 使用说明 ## 使用说明
分享链接鉴权设计的目的在于,将 FastGPT 的对话框安全的接入你现有的系统中。 免登录链接配置中,你可以选择填写`身份验证`栏。这是一个`POST`请求的根地址。在填写该地址后,分享链接的初始化、开始对话以及对话结束都会向该地址的特定接口发送一条请求。下面以`host`来表示`凭身份验证根地址`。服务器接口仅需返回是否校验成功即可,不需要返回其他数据,格式如下:
免登录链接配置中,增加了`凭证校验服务器`后,使用分享链接时会向服务器发起请求,校验链接是否可用,并在每次对话结束后,向服务器发送对话结果。下面以`host`来表示`凭证校验服务器`。服务器接口仅需返回是否校验成功即可,不需要返回其他数据,格式如下: ### 接口统一响应格式
```json ```json
{ {
"success": true, "success": true,
"message": "错误提示", "message": "错误提示",
"msg": "同message, 错误提示" "msg": "同message, 错误提示",
"uid": "用户唯一凭证"
} }
``` ```
![](/imgs/sharelinkProcess.png) `FastGPT` 将会判断`success`是否为`true`决定是允许用户继续操作。`message``msg`是等同的,你可以选择返回其中一个,当`success`不为`true`时,将会提示这个错误。
`uid`是用户的唯一凭证,将会用于拉取对话记录以及保存对话记录。可参考下方实践案例。
## 配置校验地址和校验token ### 触发流程
### 1. 配置校验地址的`BaseURL`、 ![](/imgs/sharelinkProcess.png)
## 配置教程
### 1. 配置身份校验地址
![](/imgs/share-setlink.jpg) ![](/imgs/share-setlink.jpg)
配置校验地址后,在每次分享链接使用时,都会向对应的地址发起校验和上报请求。 配置校验地址后,在每次分享链接使用时,都会向对应的地址发起校验和上报请求。
{{% alert icon="🤖" %}}
这里仅需配置根地址,无需具体到完整请求路径。
{{% /alert %}}
### 2. 分享链接中增加额外 query ### 2. 分享链接中增加额外 query
在分享链接的地址中,增加一个额外的参数: authToken。例如: 在分享链接的地址中,增加一个额外的参数: authToken。例如:
原始的链接:https://fastgpt.run/chat/share?shareId=648aaf5ae121349a16d62192 原始的链接:`https://fastgpt.run/chat/share?shareId=648aaf5ae121349a16d62192`
完整链接: https://fastgpt.run/chat/share?shareId=648aaf5ae121349a16d62192&authToken=userid12345
这个`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 ```bash
curl --location --request POST '{{host}}/shareAuth/init' \ curl --location --request POST '{{host}}/shareAuth/init' \
--header 'Content-Type: application/json' \ --header 'Content-Type: application/json' \
--data-raw '{ --data-raw '{
"token": "sintdolore" "token": "{{authToken}}"
}' }'
``` ```
**响应示例** {{< /markdownify >}}
{{< /tab >}}
{{< tab tabName="鉴权成功" >}}
{{< markdownify >}}
```json
{
"success": true,
"uid": "username123",
}
```
系统会拉取该分享链接下,uid 为 username123 的对话记录。
{{< /markdownify >}}
{{< /tab >}}
{{< tab tabName="鉴权失败" >}}
{{< markdownify >}}
```json ```json
{ {
"success": false, "success": false,
"message": "分享链接无效", "message": "身份错误",
} }
``` ```
## 对话前校验 {{< /markdownify >}}
{{< /tab >}}
{{< /tabs >}}
**FastGPT 发出的请求** ### 4. 编写对话前校验接口
{{< tabs tabTotal="3" >}}
{{< tab tabName="请求示例" >}}
{{< markdownify >}}
```bash ```bash
curl --location --request POST '{{host}}/shareAuth/start' \ curl --location --request POST '{{host}}/shareAuth/start' \
--header 'Content-Type: application/json' \ --header 'Content-Type: application/json' \
--data-raw '{ --data-raw '{
"token": "sintdolore", "token": "{{authToken}}",
"question": "用户问题", "question": "用户问题",
}' }'
``` ```
**响应示例** {{< /markdownify >}}
{{< /tab >}}
{{< tab tabName="鉴权成功" >}}
{{< markdownify >}}
```json
{
"success": true,
"uid": "username123",
}
```
{{< /markdownify >}}
{{< /tab >}}
{{< tab tabName="鉴权失败" >}}
{{< markdownify >}}
```json ```json
{ {
"success": true "success": false,
"message": "身份验证失败",
}
```
```json
{
"success": false,
"message": "存在违规词",
} }
``` ```
## 对话结果上报 {{< /markdownify >}}
{{< /tab >}}
{{< /tabs >}}
### 5. 编写对话结果上报接口(可选)
该接口无规定返回值。
响应值与[chat 接口格式相同](/docs/development/openapi/chat/#响应),仅多了一个`token`
可以重点关注`responseData`里的`price`值,`price`与实际价格的倍率为`100000`,即 100000=1元。
```bash ```bash
curl --location --request POST '{{host}}/shareAuth/finish' \ curl --location --request POST '{{host}}/shareAuth/finish' \
--header 'Content-Type: application/json' \ --header 'Content-Type: application/json' \
--data-raw '{ --data-raw '{
"token": "sint dolore", "token": "{{authToken}}",
"responseData": [ "responseData": [
{ {
"moduleName": "KB Search", "moduleName": "KB Search",
...@@ -156,18 +237,18 @@ curl --location --request POST '{{host}}/shareAuth/finish' \ ...@@ -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接口 ### 1. 创建3个Laf接口
![](/imgs/share-auth1.jpg) ![](/imgs/share-auth1.jpg)
{{< tabs tabTotal="3" >}} {{< tabs tabTotal="3" >}}
{{< tab tabName="/shareAuth/init" >}} {{< tab tabName="/shareAuth/init" >}}
{{< markdownify >}} {{< markdownify >}}
...@@ -179,13 +260,15 @@ import cloud from '@lafjs/cloud' ...@@ -179,13 +260,15 @@ import cloud from '@lafjs/cloud'
export default async function (ctx: FunctionContext) { export default async function (ctx: FunctionContext) {
const { token } = ctx.body const { token } = ctx.body
// 此处省略 token 解码过程
if (token === 'fastgpt') { if (token === 'fastgpt') {
return { success: true } return { success: true, data: { uid: "user1" } }
} }
return { success: false,message: "身份错误" } return { success: false,message:"身份错误" }
} }
``` ```
{{< /markdownify >}} {{< /markdownify >}}
...@@ -201,8 +284,8 @@ import cloud from '@lafjs/cloud' ...@@ -201,8 +284,8 @@ import cloud from '@lafjs/cloud'
export default async function (ctx: FunctionContext) { export default async function (ctx: FunctionContext) {
const { token, question } = ctx.body const { token, question } = ctx.body
console.log(token, question, 'start')
// 此处省略 token 解码过程
if (token !== 'fastgpt') { if (token !== 'fastgpt') {
return { success: false, message: "身份错误" } return { success: false, message: "身份错误" }
...@@ -212,8 +295,9 @@ export default async function (ctx: FunctionContext) { ...@@ -212,8 +295,9 @@ export default async function (ctx: FunctionContext) {
return { success: false, message: "内容不合规" } return { success: false, message: "内容不合规" }
} }
return { success: true } return { success: true, data: { uid: "user1" } }
} }
``` ```
{{< /markdownify >}} {{< /markdownify >}}
...@@ -229,7 +313,12 @@ import cloud from '@lafjs/cloud' ...@@ -229,7 +313,12 @@ import cloud from '@lafjs/cloud'
export default async function (ctx: FunctionContext) { export default async function (ctx: FunctionContext) {
const { token, responseData } = ctx.body const { token, responseData } = ctx.body
console.log(token,responseData,'=====')
const total = responseData.reduce((sum,item) => sum + item.price,0)
const amount = total / 100000
// 省略数据库操作
return { } return { }
} }
``` ```
...@@ -241,17 +330,24 @@ export default async function (ctx: FunctionContext) { ...@@ -241,17 +330,24 @@ export default async function (ctx: FunctionContext) {
### 2. 配置校验地址 ### 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) ![](/imgs/share-auth2.jpg)
### 3. 修改分享链接参数 ### 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. 测试效果 ### 4. 测试效果
1. 打开源链接或者`authToken`不等于 `fastgpt`的链接会提示身份错误。 1. 打开源链接或者`authToken`不等于`fastgpt`的链接会提示身份错误。
2. 发送内容中包含你字,会提示内容不合规。 2. 发送内容中包含你字,会提示内容不合规。
\ No newline at end of file
## 使用场景
这个鉴权方式通常是帮助你直接嵌入`分享链接`到你的应用中,在你的应用打开分享链接前,应做`authToken`的拼接后再打开。
除了对接已有系统的用户外,你还可以对接`余额`功能,通过`结果上报`接口扣除用户余额,通过`对话前校验`接口检查用户的余额。
\ No newline at end of file
...@@ -4,7 +4,9 @@ import { ErrType } from '../errorCode'; ...@@ -4,7 +4,9 @@ import { ErrType } from '../errorCode';
export enum OutLinkErrEnum { export enum OutLinkErrEnum {
unExist = 'unExist', unExist = 'unExist',
unAuthLink = 'unAuthLink', unAuthLink = 'unAuthLink',
linkUnInvalid = 'linkUnInvalid' linkUnInvalid = 'linkUnInvalid',
unAuthUser = 'unAuthUser'
} }
const errList = [ const errList = [
{ {
...@@ -19,6 +21,10 @@ const errList = [ ...@@ -19,6 +21,10 @@ const errList = [
code: 501, code: 501,
statusText: OutLinkErrEnum.linkUnInvalid, statusText: OutLinkErrEnum.linkUnInvalid,
message: '分享链接无效' message: '分享链接无效'
},
{
statusText: OutLinkErrEnum.unAuthUser,
message: '身份校验失败'
} }
]; ];
export default errList.reduce((acc, cur, index) => { export default errList.reduce((acc, cur, index) => {
......
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<string, any>;
history: ChatItemType[];
};
export type ChatHistoryItemResType = moduleDispatchResType & {
moduleType: `${FlowNodeTypeEnum}`;
moduleName: string;
};
...@@ -44,6 +44,12 @@ export const ChatSourceMap = { ...@@ -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 HUMAN_ICON = `/icon/human.svg`;
export const LOGO_ICON = `/icon/logo.svg`; export const LOGO_ICON = `/icon/logo.svg`;
......
import { ClassifyQuestionAgentItemType } from '../module/type'; import { ClassifyQuestionAgentItemType } from '../module/type';
import { SearchDataResponseItemType } from '../dataset/type'; import { SearchDataResponseItemType } from '../dataset/type';
import { ChatRoleEnum, ChatSourceEnum } from './constants'; import { ChatRoleEnum, ChatSourceEnum, ChatStatusEnum } from './constants';
import { FlowNodeTypeEnum } from '../module/node/constant'; import { FlowNodeTypeEnum } from '../module/node/constant';
import { ModuleOutputKeyEnum } from '../module/constants'; import { ModuleOutputKeyEnum } from '../module/constants';
import { AppSchema } from '../app/type'; import { AppSchema } from '../app/type';
...@@ -20,7 +20,7 @@ export type ChatSchema = { ...@@ -20,7 +20,7 @@ export type ChatSchema = {
variables: Record<string, any>; variables: Record<string, any>;
source: `${ChatSourceEnum}`; source: `${ChatSourceEnum}`;
shareId?: string; shareId?: string;
isInit: boolean; outLinkUid?: string;
content: ChatItemType[]; content: ChatItemType[];
}; };
...@@ -51,6 +51,7 @@ export type AdminFbkType = { ...@@ -51,6 +51,7 @@ export type AdminFbkType = {
a?: string; a?: string;
}; };
/* --------- chat item ---------- */
export type ChatItemType = { export type ChatItemType = {
dataId?: string; dataId?: string;
obj: ChatItemSchema['obj']; obj: ChatItemSchema['obj'];
...@@ -61,11 +62,12 @@ export type ChatItemType = { ...@@ -61,11 +62,12 @@ export type ChatItemType = {
}; };
export type ChatSiteItemType = ChatItemType & { export type ChatSiteItemType = ChatItemType & {
status: 'loading' | 'running' | 'finish'; status: `${ChatStatusEnum}`;
moduleName?: string; moduleName?: string;
ttsBuffer?: Uint8Array; ttsBuffer?: Uint8Array;
}; };
/* ---------- history ------------- */
export type HistoryItemType = { export type HistoryItemType = {
chatId: string; chatId: string;
updateTime: Date; updateTime: Date;
...@@ -77,10 +79,10 @@ export type ChatHistoryItemType = HistoryItemType & { ...@@ -77,10 +79,10 @@ export type ChatHistoryItemType = HistoryItemType & {
top: boolean; top: boolean;
}; };
// response data /* ------- response data ------------ */
export type moduleDispatchResType = { export type moduleDispatchResType = {
moduleLogo?: string; moduleLogo?: string;
price: number; price?: number;
runningTime?: number; runningTime?: number;
tokens?: number; tokens?: number;
model?: string; model?: string;
...@@ -112,3 +114,8 @@ export type moduleDispatchResType = { ...@@ -112,3 +114,8 @@ export type moduleDispatchResType = {
// plugin output // plugin output
pluginOutput?: Record<string, any>; pluginOutput?: Record<string, any>;
}; };
export type ChatHistoryItemResType = moduleDispatchResType & {
moduleType: `${FlowNodeTypeEnum}`;
moduleName: string;
};
...@@ -6,9 +6,9 @@ import type { LLMModelItemType } from '../ai/model.d'; ...@@ -6,9 +6,9 @@ import type { LLMModelItemType } from '../ai/model.d';
export type DatasetUpdateBody = { export type DatasetUpdateBody = {
id: string; id: string;
parentId?: string; parentId?: string;
tags?: string[];
name?: string; name?: string;
avatar?: string; avatar?: string;
intro?: string;
permission?: DatasetSchemaType['permission']; permission?: DatasetSchemaType['permission'];
agentModel?: LLMModelItemType; agentModel?: LLMModelItemType;
websiteConfig?: DatasetSchemaType['websiteConfig']; websiteConfig?: DatasetSchemaType['websiteConfig'];
......
import type { HistoryItemType, ChatSiteItemType } from '../../core/chat/type.d'; 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'; import { OutLinkSchema } from '@fastgpt/global/support/outLink/type';
export type InitShareChatResponse = { export type AuthOutLinkInitProps = {
userAvatar: string; outLinkUid: 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<string, any>;
chats: ChatSiteItemType[];
};
export type AuthLinkChatProps = { ip?: string | null; authToken?: string; question: string };
export type AuthLinkLimitProps = AuthLinkChatProps & { outLink: OutLinkSchema };
export type AuthShareChatInitProps = {
authToken?: string;
tokenUrl?: string; tokenUrl?: string;
}; };
export type AuthOutLinkChatProps = { ip?: string | null; outLinkUid: string; question: string };
export type AuthOutLinkLimitProps = AuthOutLinkChatProps & { outLink: OutLinkSchema };
export type AuthOutLinkResponse = {
uid: string;
};
...@@ -3,7 +3,6 @@ import { OutLinkTypeEnum } from './constant'; ...@@ -3,7 +3,6 @@ import { OutLinkTypeEnum } from './constant';
export type OutLinkSchema = { export type OutLinkSchema = {
_id: string; _id: string;
shareId: string; shareId: string;
userId: string;
teamId: string; teamId: string;
tmbId: string; tmbId: string;
appId: string; appId: string;
......
...@@ -45,4 +45,4 @@ export const TeamMemberStatusMap = { ...@@ -45,4 +45,4 @@ export const TeamMemberStatusMap = {
color: 'red.600' color: 'red.600'
} }
}; };
export const leaveStatus = { $ne: TeamMemberStatusEnum.leave }; export const notLeaveStatus = { $ne: TeamMemberStatusEnum.leave };
import { UserModelSchema } from '../type'; import type { UserModelSchema } from '../type';
import { TeamMemberRoleEnum, TeamMemberStatusEnum } from './constant'; import type { TeamMemberRoleEnum, TeamMemberStatusEnum } from './constant';
export type TeamSchema = { export type TeamSchema = {
_id: string; _id: string;
...@@ -22,6 +22,16 @@ export type TeamMemberSchema = { ...@@ -22,6 +22,16 @@ export type TeamMemberSchema = {
defaultTeam: boolean; defaultTeam: boolean;
}; };
export type TeamMemberWithUserSchema = TeamMemberSchema & {
userId: UserModelSchema;
};
export type TeamMemberWithTeamSchema = TeamMemberSchema & {
teamId: TeamSchema;
};
export type TeamMemberWithTeamAndUserSchema = TeamMemberWithTeamSchema & {
userId: UserModelSchema;
};
export type TeamItemType = { export type TeamItemType = {
userId: string; userId: string;
teamId: string; teamId: string;
......
...@@ -26,9 +26,13 @@ export async function connectMongo({ ...@@ -26,9 +26,13 @@ export async function connectMongo({
bufferCommands: true, bufferCommands: true,
maxConnecting: Number(process.env.DB_MAX_LINK || 5), maxConnecting: Number(process.env.DB_MAX_LINK || 5),
maxPoolSize: 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, connectTimeoutMS: 60000,
waitQueueTimeoutMS: 60000 waitQueueTimeoutMS: 60000,
socketTimeoutMS: 60000,
maxIdleTimeMS: 300000,
retryWrites: true,
retryReads: true
}); });
console.log('mongo connected'); console.log('mongo connected');
......
...@@ -50,10 +50,6 @@ const ChatSchema = new Schema({ ...@@ -50,10 +50,6 @@ const ChatSchema = new Schema({
top: { top: {
type: Boolean type: Boolean
}, },
variables: {
type: Object,
default: {}
},
source: { source: {
type: String, type: String,
enum: Object.keys(ChatSourceMap), enum: Object.keys(ChatSourceMap),
...@@ -62,9 +58,17 @@ const ChatSchema = new Schema({ ...@@ -62,9 +58,17 @@ const ChatSchema = new Schema({
shareId: { shareId: {
type: String type: String
}, },
isInit: { outLinkUid: {
type: Boolean, type: String
default: false },
variables: {
type: Object,
default: {}
},
metadata: {
//For special storage
type: Object,
default: {}
}, },
content: { content: {
type: [ type: [
...@@ -89,9 +93,10 @@ const ChatSchema = new Schema({ ...@@ -89,9 +93,10 @@ const ChatSchema = new Schema({
}); });
try { try {
ChatSchema.index({ appId: 1 });
ChatSchema.index({ tmbId: 1 }); ChatSchema.index({ tmbId: 1 });
ChatSchema.index({ shareId: 1 });
ChatSchema.index({ updateTime: -1 }); ChatSchema.index({ updateTime: -1 });
ChatSchema.index({ appId: 1 });
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
......
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 };
}
...@@ -12,10 +12,6 @@ const OutLinkSchema = new Schema({ ...@@ -12,10 +12,6 @@ const OutLinkSchema = new Schema({
type: String, type: String,
required: true required: true
}, },
userId: {
type: Schema.Types.ObjectId,
ref: 'user'
},
teamId: { teamId: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: TeamCollectionName, ref: TeamCollectionName,
......
...@@ -22,15 +22,15 @@ export const updateOutLinkUsage = async ({ ...@@ -22,15 +22,15 @@ export const updateOutLinkUsage = async ({
}; };
export const pushResult2Remote = async ({ export const pushResult2Remote = async ({
authToken, outLinkUid,
shareId, shareId,
responseData responseData
}: { }: {
authToken?: string; outLinkUid?: string; // raw id, not parse
shareId?: string; shareId?: string;
responseData?: any[]; responseData?: any[];
}) => { }) => {
if (!shareId || !authToken || !global.systemEnv.pluginBaseUrl) return; if (!shareId || !outLinkUid || !global.systemEnv.pluginBaseUrl) return;
try { try {
const outLink = await MongoOutLink.findOne({ const outLink = await MongoOutLink.findOne({
shareId shareId
...@@ -42,7 +42,7 @@ export const pushResult2Remote = async ({ ...@@ -42,7 +42,7 @@ export const pushResult2Remote = async ({
baseURL: outLink.limit.hookUrl, baseURL: outLink.limit.hookUrl,
url: '/shareAuth/finish', url: '/shareAuth/finish',
data: { data: {
token: authToken, token: outLinkUid,
responseData responseData
} }
}); });
......
...@@ -19,6 +19,7 @@ export async function authApp({ ...@@ -19,6 +19,7 @@ export async function authApp({
AuthResponseType & { AuthResponseType & {
teamOwner: boolean; teamOwner: boolean;
app: AppDetailType; app: AppDetailType;
role: `${TeamMemberRoleEnum}`;
} }
> { > {
const result = await parseHeaderCert(props); const result = await parseHeaderCert(props);
...@@ -65,6 +66,7 @@ export async function authApp({ ...@@ -65,6 +66,7 @@ export async function authApp({
return { return {
...result, ...result,
app, app,
role,
isOwner, isOwner,
canWrite, canWrite,
teamOwner: role === TeamMemberRoleEnum.owner teamOwner: role === TeamMemberRoleEnum.owner
......
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
};
}
...@@ -20,11 +20,11 @@ export async function authCertAndShareId({ ...@@ -20,11 +20,11 @@ export async function authCertAndShareId({
return authCert(props); return authCert(props);
} }
const { app } = await authOutLinkValid({ shareId }); const { shareChat } = await authOutLinkValid({ shareId });
return { return {
teamId: String(app.teamId), teamId: String(shareChat.teamId),
tmbId: String(app.tmbId), tmbId: String(shareChat.tmbId),
authType: AuthUserTypeEnum.outLink, authType: AuthUserTypeEnum.outLink,
apikey: '', apikey: '',
isOwner: false, isOwner: false,
......
...@@ -78,21 +78,19 @@ export async function authOutLinkCrud({ ...@@ -78,21 +78,19 @@ export async function authOutLinkCrud({
}; };
} }
/* outLink exist and it app exist */
export async function authOutLinkValid({ shareId }: { shareId?: string }) { export async function authOutLinkValid({ shareId }: { shareId?: string }) {
if (!shareId) {
return Promise.reject(OutLinkErrEnum.linkUnInvalid);
}
const shareChat = await MongoOutLink.findOne({ shareId }); const shareChat = await MongoOutLink.findOne({ shareId });
if (!shareChat) { if (!shareChat) {
return Promise.reject(OutLinkErrEnum.linkUnInvalid); return Promise.reject(OutLinkErrEnum.linkUnInvalid);
} }
const app = await MongoApp.findById(shareChat.appId);
if (!app) {
return Promise.reject(AppErrEnum.unExist);
}
return { return {
app, appId: shareChat.appId,
shareChat shareChat
}; };
} }
import { UserType } from '@fastgpt/global/support/user/type';
import { MongoUser } from './schema'; 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 }) { export async function authUserExist({ userId, username }: { userId?: string; username?: string }) {
if (userId) { if (userId) {
...@@ -9,3 +13,56 @@ export async function authUserExist({ userId, username }: { userId?: string; use ...@@ -9,3 +13,56 @@ export async function authUserExist({ userId, username }: { userId?: string; use
} }
return null; return null;
} }
export async function getUserDetail({
tmbId,
userId
}: {
tmbId?: string;
userId?: string;
}): Promise<UserType> {
const team = await (async () => {
if (tmbId) {
return getTeamInfoByTmbId({ tmbId });
}
if (userId) {
return getUserDefaultTeam({ userId });
}
return Promise.reject(ERROR_ENUM.unAuthorization);
})();
const user = await MongoUser.findById(team.userId);
if (!user) {
return Promise.reject(ERROR_ENUM.unAuthorization);
}
return {
_id: user._id,
username: user.username,
avatar: user.avatar,
balance: user.balance,
timezone: user.timezone,
promotionRate: user.promotionRate,
openaiAccount: user.openaiAccount,
team
};
}
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;
}
import { TeamItemType } from '@fastgpt/global/support/user/team/type'; import { TeamItemType, TeamMemberWithTeamSchema } from '@fastgpt/global/support/user/team/type';
import { connectionMongo, Types } from '../../../common/mongo'; import { Types } from '../../../common/mongo';
import { import {
TeamMemberRoleEnum, TeamMemberRoleEnum,
TeamMemberStatusEnum, TeamMemberStatusEnum,
TeamCollectionName, notLeaveStatus
TeamMemberCollectionName,
leaveStatus
} from '@fastgpt/global/support/user/team/constant'; } from '@fastgpt/global/support/user/team/constant';
import { MongoTeamMember } from './teamMemberSchema';
import { MongoTeam } from './teamSchema';
async function getTeam(match: Record<string, any>): Promise<TeamItemType> { async function getTeam(match: Record<string, any>): Promise<TeamItemType> {
const db = connectionMongo?.connection?.db; const tmb = (await MongoTeamMember.findOne(match).populate('teamId')) as TeamMemberWithTeamSchema;
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];
if (!tmb) { if (!tmb) {
return Promise.reject('member not exist'); return Promise.reject('member not exist');
...@@ -37,17 +17,17 @@ async function getTeam(match: Record<string, any>): Promise<TeamItemType> { ...@@ -37,17 +17,17 @@ async function getTeam(match: Record<string, any>): Promise<TeamItemType> {
return { return {
userId: String(tmb.userId), userId: String(tmb.userId),
teamId: String(tmb.teamId), teamId: String(tmb.teamId._id),
teamName: tmb.team.name, teamName: tmb.teamId.name,
memberName: tmb.name, memberName: tmb.name,
avatar: tmb.team.avatar, avatar: tmb.teamId.avatar,
balance: tmb.team.balance, balance: tmb.teamId.balance,
tmbId: String(tmb._id), tmbId: String(tmb._id),
role: tmb.role, role: tmb.role,
status: tmb.status, status: tmb.status,
defaultTeam: tmb.defaultTeam, defaultTeam: tmb.defaultTeam,
canWrite: tmb.role !== TeamMemberRoleEnum.visitor, canWrite: tmb.role !== TeamMemberRoleEnum.visitor,
maxSize: tmb.team.maxSize maxSize: tmb.teamId.maxSize
}; };
} }
...@@ -57,7 +37,7 @@ export async function getTeamInfoByTmbId({ tmbId }: { tmbId: string }) { ...@@ -57,7 +37,7 @@ export async function getTeamInfoByTmbId({ tmbId }: { tmbId: string }) {
} }
return getTeam({ return getTeam({
_id: new Types.ObjectId(tmbId), _id: new Types.ObjectId(tmbId),
status: leaveStatus status: notLeaveStatus
}); });
} }
...@@ -83,12 +63,8 @@ export async function createDefaultTeam({ ...@@ -83,12 +63,8 @@ export async function createDefaultTeam({
balance?: number; balance?: number;
maxSize?: number; maxSize?: number;
}) { }) {
const db = connectionMongo.connection.db;
const Team = db.collection(TeamCollectionName);
const TeamMember = db.collection(TeamMemberCollectionName);
// auth default team // auth default team
const tmb = await TeamMember.findOne({ const tmb = await MongoTeamMember.findOne({
userId: new Types.ObjectId(userId), userId: new Types.ObjectId(userId),
defaultTeam: true defaultTeam: true
}); });
...@@ -97,7 +73,7 @@ export async function createDefaultTeam({ ...@@ -97,7 +73,7 @@ export async function createDefaultTeam({
console.log('create default team', userId); console.log('create default team', userId);
// create // create
const { insertedId } = await Team.insertOne({ const { _id: insertedId } = await MongoTeam.create({
ownerId: userId, ownerId: userId,
name: teamName, name: teamName,
avatar, avatar,
...@@ -105,7 +81,7 @@ export async function createDefaultTeam({ ...@@ -105,7 +81,7 @@ export async function createDefaultTeam({
maxSize, maxSize,
createTime: new Date() createTime: new Date()
}); });
await TeamMember.insertOne({ await MongoTeamMember.create({
teamId: insertedId, teamId: insertedId,
userId, userId,
name: 'Owner', name: 'Owner',
...@@ -116,16 +92,11 @@ export async function createDefaultTeam({ ...@@ -116,16 +92,11 @@ export async function createDefaultTeam({
}); });
} else { } else {
console.log('default team exist', userId); console.log('default team exist', userId);
await Team.updateOne( await MongoTeam.findByIdAndUpdate(tmb.teamId, {
{ $set: {
_id: new Types.ObjectId(tmb.teamId) ...(balance !== undefined && { balance }),
}, maxSize
{
$set: {
...(balance !== undefined && { balance }),
maxSize
}
} }
); });
} }
} }
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<TeamMemberType> =
models[TeamMemberCollectionName] || model(TeamMemberCollectionName, TeamMemberSchema);
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<TeamType> =
models[TeamCollectionName] || model(TeamCollectionName, TeamSchema);
...@@ -327,12 +327,12 @@ ...@@ -327,12 +327,12 @@
"QA Prompt": "QA Prompt", "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!", "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": "Data Sync",
"Sync Collection": "Data Sync",
"Website Create Success": "Created successfully, data is being synchronized", "Website Create Success": "Created successfully, data is being synchronized",
"Website Empty Tip": "No associated website yet", "Website Empty Tip": "No associated website yet",
"Website Link": "Website Link", "Website Link": "Website Link",
"Website Sync": "Website", "Website Sync": "Website",
"id": "Id", "id": "Id",
"Sync Collection": "Data Sync",
"metadata": { "metadata": {
"Chunk Size": "Chunk Size", "Chunk Size": "Chunk Size",
"Createtime": "Create Time", "Createtime": "Create Time",
...@@ -510,6 +510,10 @@ ...@@ -510,6 +510,10 @@
"variable options": "Options" "variable options": "Options"
}, },
"variable add option": "Add Option" "variable add option": "Add Option"
},
"shareChat": {
"Init Error": "Init Chat Error",
"Init History Error": "Init History Error"
} }
}, },
"dataset": { "dataset": {
......
...@@ -316,7 +316,7 @@ ...@@ -316,7 +316,7 @@
"Search Top K": "单次搜索数量", "Search Top K": "单次搜索数量",
"Set Empty Result Tip": ",未搜索到内容时回复指定内容", "Set Empty Result Tip": ",未搜索到内容时回复指定内容",
"Set Website Config": "开始配置网站信息", "Set Website Config": "开始配置网站信息",
"Similarity": "相度", "Similarity": "相度",
"Sync Time": "最后更新时间", "Sync Time": "最后更新时间",
"Virtual File": "虚拟文件", "Virtual File": "虚拟文件",
"Website Dataset": "Web 站点同步", "Website Dataset": "Web 站点同步",
...@@ -327,12 +327,12 @@ ...@@ -327,12 +327,12 @@
"QA Prompt": "QA 拆分引导词", "QA Prompt": "QA 拆分引导词",
"Start Sync Tip": "确认开始同步数据?将会删除旧数据后重新获取,请确认!", "Start Sync Tip": "确认开始同步数据?将会删除旧数据后重新获取,请确认!",
"Sync": "同步数据", "Sync": "同步数据",
"Sync Collection": "数据同步",
"Website Create Success": "创建成功,正在同步数据", "Website Create Success": "创建成功,正在同步数据",
"Website Empty Tip": "还没有关联网站", "Website Empty Tip": "还没有关联网站",
"Website Link": "Web 站点地址", "Website Link": "Web 站点地址",
"Website Sync": "Web 站点同步", "Website Sync": "Web 站点同步",
"id": "集合ID", "id": "集合ID",
"Sync Collection": "数据同步",
"metadata": { "metadata": {
"Chunk Size": "分割大小", "Chunk Size": "分割大小",
"Createtime": "创建时间", "Createtime": "创建时间",
...@@ -405,17 +405,17 @@ ...@@ -405,17 +405,17 @@
"search": { "search": {
"Empty result response": "空搜索回复", "Empty result response": "空搜索回复",
"Empty result response Tips": "若填写该内容,没有搜索到合适内容时,将直接回复填写的内容。", "Empty result response Tips": "若填写该内容,没有搜索到合适内容时,将直接回复填写的内容。",
"Min Similarity": "最低相度", "Min Similarity": "最低相度",
"Min Similarity Tips": "不同索引模型的相似度有区别,请通过搜索测试来选择合适的数值", "Min Similarity Tips": "不同索引模型的相关度有区别,请通过搜索测试来选择合适的数值,使用 ReRank 时,相关度可能会很低。",
"Params Setting": "搜索参数设置", "Params Setting": "搜索参数设置",
"Top K": "单次搜索上限", "Top K": "单次搜索上限",
"mode": { "mode": {
"embFullTextReRank": "混合检索", "embFullTextReRank": "混合检索",
"embFullTextReRank desc": "使用向量检索与全文检索混合结果进行 Rerank 进行重排,通常效果最佳", "embFullTextReRank desc": "使用向量检索与全文检索混合结果进行 Rerank 进行重排,相关度通常差异明显,推荐。",
"embedding": "语义检索", "embedding": "语义检索",
"embedding desc": "直接进行向量 topk 相关性查询", "embedding desc": "直接进行向量 topk 相关性查询",
"embeddingReRank": "增强语义检索", "embeddingReRank": "增强语义检索",
"embeddingReRank desc": "超额进行向量 topk 查询后再使用 Rerank 进行排序" "embeddingReRank desc": "超额进行向量 topk 查询后再使用 Rerank 进行排序,相关度通常差异明显。"
}, },
"search mode": "检索模式" "search mode": "检索模式"
}, },
...@@ -510,6 +510,10 @@ ...@@ -510,6 +510,10 @@
"variable options": "选项" "variable options": "选项"
}, },
"variable add option": "添加选项" "variable add option": "添加选项"
},
"shareChat": {
"Init Error": "初始化对话框失败",
"Init History Error": "初始化聊天记录失败"
} }
}, },
"dataset": { "dataset": {
......
import React, { useMemo, useState } from 'react'; 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 type { ChatItemType } from '@fastgpt/global/core/chat/type';
import { Flex, BoxProps, useDisclosure, Image, useTheme, Box } from '@chakra-ui/react'; import { Flex, BoxProps, useDisclosure, Image, useTheme, Box } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
......
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import { Box, useTheme, Flex, Image } from '@chakra-ui/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 { useTranslation } from 'next-i18next';
import { moduleTemplatesFlat } from '@/web/core/modules/template/system'; import { moduleTemplatesFlat } from '@/web/core/modules/template/system';
import Tabs from '../Tabs'; import Tabs from '../Tabs';
......
...@@ -12,7 +12,7 @@ import Script from 'next/script'; ...@@ -12,7 +12,7 @@ import Script from 'next/script';
import { throttle } from 'lodash'; import { throttle } from 'lodash';
import type { ExportChatType } from '@/types/chat.d'; import type { ExportChatType } from '@/types/chat.d';
import type { ChatItemType, ChatSiteItemType } from '@fastgpt/global/core/chat/type.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 { useToast } from '@/web/common/hooks/useToast';
import { useAudioPlay } from '@/web/common/utils/voice'; import { useAudioPlay } from '@/web/common/utils/voice';
import { getErrText } from '@fastgpt/global/common/error/utils'; import { getErrText } from '@fastgpt/global/common/error/utils';
...@@ -80,7 +80,7 @@ export type StartChatFnProps = { ...@@ -80,7 +80,7 @@ export type StartChatFnProps = {
}; };
export type ComponentRef = { export type ComponentRef = {
getChatHistory: () => ChatSiteItemType[]; getChatHistories: () => ChatSiteItemType[];
resetVariables: (data?: Record<string, any>) => void; resetVariables: (data?: Record<string, any>) => void;
resetHistory: (history: ChatSiteItemType[]) => void; resetHistory: (history: ChatSiteItemType[]) => void;
scrollToBottom: (behavior?: 'smooth' | 'auto') => void; scrollToBottom: (behavior?: 'smooth' | 'auto') => void;
...@@ -134,7 +134,7 @@ const ChatBox = ( ...@@ -134,7 +134,7 @@ const ChatBox = (
const router = useRouter(); const router = useRouter();
const { t } = useTranslation(); const { t } = useTranslation();
const { toast } = useToast(); const { toast } = useToast();
const { isPc } = useSystemStore(); const { isPc, setLoading } = useSystemStore();
const TextareaDom = useRef<HTMLTextAreaElement>(null); const TextareaDom = useRef<HTMLTextAreaElement>(null);
const chatController = useRef(new AbortController()); const chatController = useRef(new AbortController());
const questionGuideController = useRef(new AbortController()); const questionGuideController = useRef(new AbortController());
...@@ -415,15 +415,20 @@ const ChatBox = ( ...@@ -415,15 +415,20 @@ const ChatBox = (
async (index: number) => { async (index: number) => {
if (!onDelMessage) return; if (!onDelMessage) return;
const delHistory = chatHistory.slice(index); const delHistory = chatHistory.slice(index);
setChatHistory((state) => (index === 0 ? [] : state.slice(0, index)));
await Promise.all( setLoading(true);
delHistory.map((item, i) => onDelMessage({ contentId: item.dataId, index: index + i }))
);
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 // delete one message
const delOneMessage = useCallback( const delOneMessage = useCallback(
...@@ -439,7 +444,7 @@ const ChatBox = ( ...@@ -439,7 +444,7 @@ const ChatBox = (
// output data // output data
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
getChatHistory: () => chatHistory, getChatHistories: () => chatHistory,
resetVariables(e) { resetVariables(e) {
const defaultVal: Record<string, any> = {}; const defaultVal: Record<string, any> = {};
variableModules?.forEach((item) => { variableModules?.forEach((item) => {
......
...@@ -86,7 +86,7 @@ const DatasetParamsModal = ({ ...@@ -86,7 +86,7 @@ const DatasetParamsModal = ({
min={0} min={0}
max={1} max={1}
step={0.01} step={0.01}
value={getValues(ModuleInputKeyEnum.datasetSimilarity) || 0.5} value={getValues(ModuleInputKeyEnum.datasetSimilarity) ?? 0.5}
onChange={(val) => { onChange={(val) => {
setValue(ModuleInputKeyEnum.datasetSimilarity, val); setValue(ModuleInputKeyEnum.datasetSimilarity, val);
setRefresh(!refresh); setRefresh(!refresh);
...@@ -107,7 +107,7 @@ const DatasetParamsModal = ({ ...@@ -107,7 +107,7 @@ const DatasetParamsModal = ({
]} ]}
min={1} min={1}
max={30} max={30}
value={getValues(ModuleInputKeyEnum.datasetLimit) || 5} value={getValues(ModuleInputKeyEnum.datasetLimit) ?? 5}
onChange={(val) => { onChange={(val) => {
setValue(ModuleInputKeyEnum.datasetLimit, val); setValue(ModuleInputKeyEnum.datasetLimit, val);
setRefresh(!refresh); setRefresh(!refresh);
......
import type { AppTTSConfigType } from '@fastgpt/global/core/module/type.d'; 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 = { export type GetChatSpeechProps = {
ttsConfig: AppTTSConfigType; ttsConfig: AppTTSConfigType;
input: string; input: string;
shareId?: string; shareId?: string;
}; };
/* ---------- chat ----------- */
export type InitChatProps = {
appId?: string;
chatId?: string;
};
export type InitOutLinkChatProps = {
chatId?: string;
shareId?: string;
outLinkUid?: string;
};
export type InitChatResponse = {
chatId?: string;
appId: string;
userAvatar?: string;
title: string;
variables: Record<string, any>;
history: ChatItemType[];
app: {
userGuideModule?: ModuleItemType;
chatModels?: string[];
name: string;
avatar: string;
intro: string;
canUse?: boolean;
};
};
/* ---------- history ----------- */
export type getHistoriesProps = {
appId?: string;
// share chat
shareId?: string;
outLinkUid?: string; // authToken/uid
};
export type UpdateHistoryProps = {
chatId: string;
customTitle?: string;
top?: boolean;
shareId?: string;
outLinkUid?: string;
};
export type DelHistoryProps = {
chatId: string;
shareId?: string;
outLinkUid?: string;
};
export type ClearHistoriesProps = {
appId?: string;
shareId?: string;
outLinkUid?: string;
};
/* -------- chat item ---------- */
export type DeleteChatItemProps = {
chatId: string;
contentId: string;
shareId?: string;
outLinkUid?: string;
};
export type AdminUpdateFeedbackParams = AdminFbkType & {
chatItemId: string;
};
import { InitChatResponse } from './api';
export const defaultChatData: InitChatResponse = {
chatId: '',
appId: '',
app: {
name: 'Loading',
avatar: '/icon/logo.svg',
intro: '',
canUse: false
},
title: '新对话',
variables: {},
history: []
};
...@@ -33,9 +33,8 @@ export const Prompt_QuotePromptList: PromptTemplateItem[] = [ ...@@ -33,9 +33,8 @@ export const Prompt_QuotePromptList: PromptTemplateItem[] = [
""" """
回答要求: 回答要求:
1. 优先使用知识库内容回答问题。 1. 优先使用知识库内容回答问题。
2. 你可以回答我不知道。 2. 不要提及你是从知识库获取的知识。
3. 不要提及你是从知识库获取的知识。 3. 知识库包含 markdown 内容时,按 markdown 格式返回。
4. 知识库包含 markdown 内容时,按 markdown 格式返回。
我的问题是:"{{question}}"` 我的问题是:"{{question}}"`
}, },
{ {
...@@ -47,9 +46,8 @@ export const Prompt_QuotePromptList: PromptTemplateItem[] = [ ...@@ -47,9 +46,8 @@ export const Prompt_QuotePromptList: PromptTemplateItem[] = [
""" """
回答要求: 回答要求:
1. 优先使用知识库内容回答问题,其中 instruction 是相关介绍,output 是预期回答或补充。 1. 优先使用知识库内容回答问题,其中 instruction 是相关介绍,output 是预期回答或补充。
2. 你可以回答我不知道。 2. 不要提及你是从知识库获取的知识。
3. 不要提及你是从知识库获取的知识。 3. 知识库包含 markdown 内容时,按 markdown 格式返回。
4. 知识库包含 markdown 内容时,按 markdown 格式返回。
我的问题是:"{{question}}"` 我的问题是:"{{question}}"`
}, },
{ {
......
...@@ -54,7 +54,7 @@ const PayModal = ({ onClose }: { onClose: () => void }) => { ...@@ -54,7 +54,7 @@ const PayModal = ({ onClose }: { onClose: () => void }) => {
onSuccess(res) { onSuccess(res) {
if (!res) return; if (!res) return;
toast({ toast({
title: '充值成功', title: res,
status: 'success' status: 'success'
}); });
router.reload(); router.reload();
......
import type { NextApiRequest, NextApiResponse } from 'next'; import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response'; import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo'; import { connectToDatabase } from '@/service/mongo';
import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema';
import { startQueue } from '@/service/utils/tools'; import { startQueue } from '@/service/utils/tools';
import { authCert } from '@fastgpt/service/support/permission/auth/common'; import { authCert } from '@fastgpt/service/support/permission/auth/common';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) { export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { try {
await connectToDatabase(); await connectToDatabase();
const { userId } = await authCert({ req, authToken: true }); await authCert({ req, authToken: true });
await unlockTask(userId); startQueue();
} catch (error) {} } catch (error) {}
jsonRes(res); jsonRes(res);
} }
async function unlockTask(userId: string) {
try {
await MongoDatasetTraining.updateMany(
{
userId
},
{
lockTime: new Date('2000/1/1')
}
);
startQueue();
} catch (error) {
unlockTask(userId);
}
}
...@@ -5,6 +5,7 @@ import { MongoChat } from '@fastgpt/service/core/chat/chatSchema'; ...@@ -5,6 +5,7 @@ import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { MongoApp } from '@fastgpt/service/core/app/schema'; import { MongoApp } from '@fastgpt/service/core/app/schema';
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema'; import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
import { authApp } from '@fastgpt/service/support/permission/auth/app'; import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
/* 获取我的模型 */ /* 获取我的模型 */
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) { export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
...@@ -20,6 +21,9 @@ 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 authApp({ req, authToken: true, appId, per: 'owner' });
// 删除对应的聊天 // 删除对应的聊天
await MongoChatItem.deleteMany({
appId
});
await MongoChat.deleteMany({ await MongoChat.deleteMany({
appId appId
}); });
......
...@@ -381,7 +381,7 @@ function datasetTemplate({ ...@@ -381,7 +381,7 @@ function datasetTemplate({
key: 'similarity', key: 'similarity',
value: 0.4, value: 0.4,
type: FlowNodeInputTypeEnum.slider, type: FlowNodeInputTypeEnum.slider,
label: '相度', label: '相度',
connected: true connected: true
}, },
{ {
......
...@@ -289,7 +289,7 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] { ...@@ -289,7 +289,7 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
key: 'similarity', key: 'similarity',
value: formData.dataset.similarity, value: formData.dataset.similarity,
type: FlowNodeInputTypeEnum.slider, type: FlowNodeInputTypeEnum.slider,
label: '相度', label: '相度',
connected: false connected: false
}, },
{ {
......
...@@ -8,8 +8,9 @@ import { pushChatBill } from '@/service/support/wallet/bill/push'; ...@@ -8,8 +8,9 @@ import { pushChatBill } from '@/service/support/wallet/bill/push';
import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants'; import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants';
import type { ChatItemType } from '@fastgpt/global/core/chat/type'; import type { ChatItemType } from '@fastgpt/global/core/chat/type';
import { authApp } from '@fastgpt/service/support/permission/auth/app'; import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { authUser } from '@/service/support/permission/auth/user';
import { dispatchModules } from '@/service/moduleDispatch'; 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 = { export type Props = {
history: ChatItemType[]; history: ChatItemType[];
...@@ -40,15 +41,20 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) ...@@ -40,15 +41,20 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
} }
/* user auth */ /* user auth */
const [{ teamId, tmbId }, { user }] = await Promise.all([ const [_, { teamId, tmbId }] = await Promise.all([
authApp({ req, authToken: true, appId, per: 'r' }), authApp({ req, authToken: true, appId, per: 'r' }),
authUser({ authCert({
req, req,
authToken: true, authToken: true
minBalance: 0
}) })
]); ]);
// auth balance
const user = await getUserAndAuthBalance({
tmbId,
minBalance: 0
});
/* start process */ /* start process */
const { responseData } = await dispatchModules({ const { responseData } = await dispatchModules({
res, res,
......
...@@ -4,45 +4,49 @@ import { connectToDatabase } from '@/service/mongo'; ...@@ -4,45 +4,49 @@ import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common'; import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema'; import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema'; import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { ClearHistoriesProps } from '@/global/core/chat/api';
import { authOutLink } from '@/service/support/permission/auth/outLink';
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants'; import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
type Props = {
chatId?: string;
appId?: string;
};
/* clear chat history */ /* clear chat history */
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try { try {
await connectToDatabase(); await connectToDatabase();
const { chatId, appId } = req.query as Props; const { appId, shareId, outLinkUid } = req.query as ClearHistoriesProps;
const { tmbId } = await authCert({ req, authToken: true }); const match = await (async () => {
if (shareId && outLinkUid) {
if (chatId) { const { uid } = await authOutLink({ shareId, outLinkUid });
await MongoChatItem.deleteMany({
chatId, return {
tmbId shareId,
}); outLinkUid: uid
await MongoChat.findOneAndRemove({ };
chatId, }
tmbId if (appId) {
}); const { tmbId } = await authCert({ req, authToken: true });
}
if (appId) { return {
const chats = await MongoChat.find({ appId,
appId, tmbId,
tmbId, source: ChatSourceEnum.online
source: ChatSourceEnum.online };
}).select('_id'); }
const chatIds = chats.map((chat) => chat._id);
await MongoChatItem.deleteMany({ return Promise.reject('Param are error');
chatId: { $in: chatIds } })();
}); console.log(match);
await MongoChat.deleteMany({
_id: { $in: chatIds } // 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); jsonRes(res);
} catch (err) { } catch (err) {
......
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
});
}
}
...@@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next'; ...@@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response'; import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo'; import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common'; 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'; import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
/* 初始化我的聊天框,需要身份验证 */ /* 初始化我的聊天框,需要身份验证 */
......
...@@ -4,25 +4,44 @@ import { connectToDatabase } from '@/service/mongo'; ...@@ -4,25 +4,44 @@ import { connectToDatabase } from '@/service/mongo';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema'; import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import type { ChatHistoryItemType } from '@fastgpt/global/core/chat/type.d'; import type { ChatHistoryItemType } from '@fastgpt/global/core/chat/type.d';
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants'; import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
import { authApp } from '@fastgpt/service/support/permission/auth/app'; 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) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try { try {
await connectToDatabase(); await connectToDatabase();
const { appId } = req.body as { appId: string }; const { appId, shareId, outLinkUid } = req.body as getHistoriesProps;
const { tmbId } = await authApp({ req, authToken: true, appId, per: 'r' });
const data = await MongoChat.find( const limit = shareId && outLinkUid ? 20 : 30;
{
appId, const match = await (async () => {
tmbId, if (shareId && outLinkUid) {
source: ChatSourceEnum.online const { uid } = await authOutLink({ shareId, outLinkUid });
},
'chatId title top customTitle appId updateTime' 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 }) .sort({ top: -1, updateTime: -1 })
.limit(20); .limit(limit);
jsonRes<ChatHistoryItemType[]>(res, { jsonRes<ChatHistoryItemType[]>(res, {
data: data.map((item) => ({ data: data.map((item) => ({
......
import type { NextApiRequest, NextApiResponse } from 'next'; import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response'; import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo'; 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 { authApp } from '@fastgpt/service/support/permission/auth/app';
import { getGuideModule } from '@fastgpt/global/core/module/utils'; import { getGuideModule } from '@fastgpt/global/core/module/utils';
import { getChatModelNameListByModules } from '@/service/core/app/module'; 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 { 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) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try { try {
await connectToDatabase(); await connectToDatabase();
let { appId, chatId } = req.query as { let { appId, chatId } = req.query as InitChatProps;
appId: string;
chatId: '' | string;
};
if (!appId) { if (!appId) {
return jsonRes(res, { return jsonRes(res, {
...@@ -27,57 +23,44 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) ...@@ -27,57 +23,44 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}); });
} }
// 校验使用权限 // auth app permission
const [{ app }, autChatResult] = await Promise.all([ const [{ app, tmbId }, chat] = await Promise.all([
authApp({ authApp({
req, req,
authToken: true, authToken: true,
appId, appId,
per: 'r' per: 'r'
}), }),
chatId chatId ? MongoChat.findOne({ chatId }) : undefined
? authChat({
req,
authToken: true,
chatId,
per: 'r'
})
: undefined
]); ]);
// get app and history // auth chat permission
const { history = [] }: { history?: ChatItemType[] } = await (async () => { if (!app.canWrite && String(tmbId) !== String(chat?.tmbId)) {
if (chatId) { throw new Error(ChatErrEnum.unAuthChat);
// auth chatId }
const history = await MongoChatItem.find(
{
chatId
},
`dataId obj value adminFeedback userFeedback ${ModuleOutputKeyEnum.responseData}`
)
.sort({ _id: -1 })
.limit(30);
history.reverse(); // get app and history
return { history }; const { history } = await getChatItems({
} chatId,
return {}; limit: 30,
})(); field: `dataId obj value adminFeedback userFeedback ${ModuleOutputKeyEnum.responseData}`
});
jsonRes<InitChatResponse>(res, { jsonRes<InitChatResponse>(res, {
data: { data: {
chatId, chatId,
appId, appId,
title: chat?.title || '新对话',
userAvatar: undefined,
variables: chat?.variables || {},
history,
app: { app: {
userGuideModule: getGuideModule(app.modules), userGuideModule: getGuideModule(app.modules),
chatModels: getChatModelNameListByModules(app.modules), chatModels: getChatModelNameListByModules(app.modules),
name: app.name, name: app.name,
avatar: app.avatar, avatar: app.avatar,
intro: app.intro intro: app.intro
}, }
title: autChatResult?.chat?.title || '新对话',
variables: autChatResult?.chat?.variables || {},
history
} }
}); });
} catch (err) { } catch (err) {
......
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
});
}
}
...@@ -2,14 +2,22 @@ import type { NextApiRequest, NextApiResponse } from 'next'; ...@@ -2,14 +2,22 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response'; import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo'; import { connectToDatabase } from '@/service/mongo';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema'; 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) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try { try {
await connectToDatabase(); 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({ await MongoChatItem.deleteOne({
dataId: contentId, dataId: contentId,
......
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import type { InitChatResponse, InitOutLinkChatProps } from '@/global/core/chat/api.d';
import { getGuideModule } from '@fastgpt/global/core/module/utils';
import { getChatModelNameListByModules } from '@/service/core/app/module';
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import { getChatItems } from '@fastgpt/service/core/chat/controller';
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
import { authOutLink } from '@/service/support/permission/auth/outLink';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { selectShareResponse } from '@/utils/service/core/chat';
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
let { chatId, shareId, outLinkUid } = req.query as InitOutLinkChatProps;
// auth link permission
const { shareChat, uid, appId } = await authOutLink({ shareId, outLinkUid });
// auth app permission
const [tmb, chat, app] = await Promise.all([
MongoTeamMember.findById(shareChat.tmbId, '_id userId').populate('userId', 'avatar').lean(),
MongoChat.findOne({ chatId, shareId }).lean(),
MongoApp.findById(appId).lean()
]);
if (!app) {
throw new Error(AppErrEnum.unExist);
}
// auth chat permission
if (chat && chat.outLinkUid !== uid) {
throw new Error(ChatErrEnum.unAuthChat);
}
const { history } = await getChatItems({
chatId,
limit: 30,
field: `dataId obj value userFeedback ${
shareChat.responseDetail ? `adminFeedback ${ModuleOutputKeyEnum.responseData}` : ''
} `
});
// pick share response field
history.forEach((item) => {
item.responseData = selectShareResponse({ responseData: item.responseData });
});
jsonRes<InitChatResponse>(res, {
data: {
chatId,
appId: app._id,
title: chat?.title || '新对话',
//@ts-ignore
userAvatar: tmb?.userId?.avatar,
variables: chat?.variables || {},
history,
app: {
userGuideModule: getGuideModule(app.modules),
chatModels: getChatModelNameListByModules(app.modules),
name: app.name,
avatar: app.avatar,
intro: app.intro
}
}
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
export const config = {
api: {
responseLimit: '10mb'
}
};
import type { NextApiRequest, NextApiResponse } from 'next'; import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response'; import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo'; 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 { 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) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try { try {
await connectToDatabase(); 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( await MongoChat.findOneAndUpdate(
{ chatId }, { chatId },
......
...@@ -8,7 +8,7 @@ import { authDataset } from '@fastgpt/service/support/permission/auth/dataset'; ...@@ -8,7 +8,7 @@ import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) { export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { try {
await connectToDatabase(); 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; req.body as DatasetUpdateBody;
if (!id) { if (!id) {
...@@ -26,11 +26,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< ...@@ -26,11 +26,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
...(parentId !== undefined && { parentId: parentId || null }), ...(parentId !== undefined && { parentId: parentId || null }),
...(name && { name }), ...(name && { name }),
...(avatar && { avatar }), ...(avatar && { avatar }),
...(tags && { tags }),
...(permission && { permission }), ...(permission && { permission }),
...(agentModel && { agentModel: agentModel.model }), ...(agentModel && { agentModel: agentModel.model }),
...(websiteConfig && { websiteConfig }), ...(websiteConfig && { websiteConfig }),
...(status && { status }) ...(status && { status }),
...(intro && { intro })
} }
); );
......
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { MongoUser } from '@fastgpt/service/support/user/schema';
import type { InitShareChatResponse } from '@fastgpt/global/support/outLink/api.d';
import { HUMAN_ICON } from '@fastgpt/global/core/chat/constants';
import { getGuideModule } from '@fastgpt/global/core/module/utils';
import { authShareChatInit } from '@/service/support/outLink/auth';
import { getChatModelNameListByModules } from '@/service/core/app/module';
import { authOutLinkValid } from '@fastgpt/service/support/permission/auth/outLink';
/* init share chat window */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
let { shareId, authToken } = req.query as {
shareId: string;
authToken?: string;
};
// get shareChat
const { app, shareChat } = await authOutLinkValid({ shareId });
// 校验使用权限
const [user] = await Promise.all([
MongoUser.findById(shareChat.userId, 'avatar'),
authShareChatInit({
authToken,
tokenUrl: shareChat.limit?.hookUrl
})
]);
jsonRes<InitShareChatResponse>(res, {
data: {
userAvatar: user?.avatar || HUMAN_ICON,
app: {
userGuideModule: getGuideModule(app.modules),
chatModels: getChatModelNameListByModules(app.modules),
name: app.name,
avatar: app.avatar,
intro: app.intro
}
}
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
...@@ -3,7 +3,7 @@ import { jsonRes } from '@fastgpt/service/common/response'; ...@@ -3,7 +3,7 @@ import { jsonRes } from '@fastgpt/service/common/response';
import { MongoUser } from '@fastgpt/service/support/user/schema'; import { MongoUser } from '@fastgpt/service/support/user/schema';
import { createJWT, setCookie } from '@fastgpt/service/support/permission/controller'; import { createJWT, setCookie } from '@fastgpt/service/support/permission/controller';
import { connectToDatabase } from '@/service/mongo'; 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'; import type { PostLoginProps } from '@fastgpt/global/support/user/api.d';
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
......
...@@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next'; ...@@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response'; import { jsonRes } from '@fastgpt/service/common/response';
import { authCert } from '@fastgpt/service/support/permission/auth/common'; import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { connectToDatabase } from '@/service/mongo'; 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) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try { try {
......
import type { NextApiRequest, NextApiResponse } from 'next'; import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response'; 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 { withNextCors } from '@fastgpt/service/common/middle/cors';
import { getUploadModel } from '@fastgpt/service/common/file/upload/multer'; import { getUploadModel } from '@fastgpt/service/common/file/upload/multer';
import fs from 'fs'; import fs from 'fs';
......
...@@ -10,11 +10,11 @@ import { dispatchModules } from '@/service/moduleDispatch'; ...@@ -10,11 +10,11 @@ import { dispatchModules } from '@/service/moduleDispatch';
import type { ChatCompletionCreateParams } from '@fastgpt/global/core/ai/type.d'; import type { ChatCompletionCreateParams } from '@fastgpt/global/core/ai/type.d';
import type { ChatMessageItemType } from '@fastgpt/global/core/ai/type.d'; import type { ChatMessageItemType } from '@fastgpt/global/core/ai/type.d';
import { gptMessage2ChatType, textAdaptGptResponse } from '@/utils/adapt'; 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 { saveChat } from '@/service/utils/chat/saveChat';
import { responseWrite } from '@fastgpt/service/common/response'; import { responseWrite } from '@fastgpt/service/common/response';
import { pushChatBill } from '@/service/support/wallet/bill/push'; 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 { pushResult2Remote, updateOutLinkUsage } from '@fastgpt/service/support/outLink/tools';
import requestIp from 'request-ip'; import requestIp from 'request-ip';
import { getBillSourceByAuthType } from '@fastgpt/global/support/wallet/bill/tools'; import { getBillSourceByAuthType } from '@fastgpt/global/support/wallet/bill/tools';
...@@ -22,9 +22,10 @@ import { getBillSourceByAuthType } from '@fastgpt/global/support/wallet/bill/too ...@@ -22,9 +22,10 @@ import { getBillSourceByAuthType } from '@fastgpt/global/support/wallet/bill/too
import { selectShareResponse } from '@/utils/service/core/chat'; import { selectShareResponse } from '@/utils/service/core/chat';
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools'; import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
import { connectToDatabase } from '@/service/mongo'; 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 { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
import { MongoApp } from '@fastgpt/service/core/app/schema'; import { MongoApp } from '@fastgpt/service/core/app/schema';
import { autChatCrud } from '@/service/support/permission/auth/chat';
type FastGptWebChatProps = { type FastGptWebChatProps = {
chatId?: string; // undefined: nonuse history, '': new chat, 'xxxxx': use history chatId?: string; // undefined: nonuse history, '': new chat, 'xxxxx': use history
...@@ -32,7 +33,7 @@ type FastGptWebChatProps = { ...@@ -32,7 +33,7 @@ type FastGptWebChatProps = {
}; };
type FastGptShareChatProps = { type FastGptShareChatProps = {
shareId?: string; shareId?: string;
authToken?: string; outLinkUid?: string;
}; };
export type Props = ChatCompletionCreateParams & export type Props = ChatCompletionCreateParams &
FastGptWebChatProps & FastGptWebChatProps &
...@@ -56,11 +57,11 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex ...@@ -56,11 +57,11 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
res.end(); res.end();
}); });
let { const {
chatId, chatId,
appId, appId,
shareId, shareId,
authToken, outLinkUid,
stream = false, stream = false,
detail = false, detail = false,
messages = [], messages = [],
...@@ -93,22 +94,29 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex ...@@ -93,22 +94,29 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
throw new Error('Question is empty'); throw new Error('Question is empty');
} }
/* auth app permission */ /* auth app permission */
const { user, app, responseDetail, authType, apikey, canWrite } = await (async () => { const { user, app, responseDetail, authType, apikey, canWrite, uid } = await (async () => {
if (shareId) { if (shareId && outLinkUid) {
const { user, app, authType, responseDetail } = await authOutLinkChat({ const { user, appId, authType, responseDetail, uid } = await authOutLinkChatStart({
shareId, shareId,
ip: requestIp.getClientIp(req), ip: requestIp.getClientIp(req),
authToken, outLinkUid,
question: question.value question: question.value
}); });
const app = await MongoApp.findById(appId);
if (!app) {
return Promise.reject('app is empty');
}
return { return {
user, user,
app, app,
responseDetail, responseDetail,
apikey: '', apikey: '',
authType, authType,
canWrite: false canWrite: false,
uid
}; };
} }
...@@ -146,11 +154,10 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex ...@@ -146,11 +154,10 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
}; };
} }
// token auth
if (!appId) { if (!appId) {
return Promise.reject('appId is empty'); return Promise.reject('appId is empty');
} }
// token
const { app, canWrite } = await authApp({ const { app, canWrite } = await authApp({
req, req,
authToken: true, authToken: true,
...@@ -168,12 +175,19 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex ...@@ -168,12 +175,19 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
}; };
})(); })();
// auth app, get history // auth chat permission
const { history } = await getChatHistory({ chatId, tmbId: user.team.tmbId }); await autChatCrud({
req,
const isAppOwner = !shareId && String(user.team.tmbId) === String(app.tmbId); authToken: true,
authApiKey: true,
chatId,
shareId,
outLinkUid,
per: 'w'
});
/* format prompts */ // get and concat history
const { history } = await getChatItems({ chatId, limit: 30, field: `dataId obj value` });
const concatHistory = history.concat(chatMessages); const concatHistory = history.concat(chatMessages);
/* start flow controller */ /* start flow controller */
...@@ -202,8 +216,9 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex ...@@ -202,8 +216,9 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
teamId: user.team.teamId, teamId: user.team.teamId,
tmbId: user.team.tmbId, tmbId: user.team.tmbId,
variables, variables,
updateUseTime: isAppOwner, // owner update use time updateUseTime: !shareId && String(user.team.tmbId) === String(app.tmbId), // owner update use time
shareId, shareId,
outLinkUid: uid,
source: (() => { source: (() => {
if (shareId) { if (shareId) {
return ChatSourceEnum.share; return ChatSourceEnum.share;
...@@ -281,7 +296,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex ...@@ -281,7 +296,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
}); });
if (shareId) { if (shareId) {
pushResult2Remote({ authToken, shareId, responseData }); pushResult2Remote({ outLinkUid, shareId, responseData });
updateOutLinkUsage({ updateOutLinkUsage({
shareId, shareId,
total total
......
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { connectToDatabase } from '@/service/mongo';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { Types } from '@fastgpt/service/common/mongo';
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
export type Props = {
appId?: string;
chatId?: string;
limit?: number;
};
export type Response = { history: ChatItemType[] };
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { tmbId } = await authCert({ req, authToken: true });
const { chatId, limit } = req.body as Props;
jsonRes<Response>(res, {
data: await getChatHistory({
chatId,
tmbId,
limit
})
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
export async function getChatHistory({
chatId,
tmbId,
limit = 30
}: Props & { tmbId: string }): Promise<Response> {
if (!chatId) {
return { history: [] };
}
const history = await MongoChatItem.aggregate([
{
$match: {
chatId,
tmbId: new Types.ObjectId(tmbId)
}
},
{
$sort: {
_id: -1
}
},
{
$limit: limit
},
{
$project: {
dataId: 1,
obj: 1,
value: 1
}
}
]);
history.reverse();
return { history };
}
...@@ -23,7 +23,7 @@ import { AppLogsListItemType } from '@/types/app'; ...@@ -23,7 +23,7 @@ import { AppLogsListItemType } from '@/types/app';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import ChatBox, { type ComponentRef } from '@/components/ChatBox'; import ChatBox, { type ComponentRef } from '@/components/ChatBox';
import { useQuery } from '@tanstack/react-query'; 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 Tag from '@/components/Tag';
import MyModal from '@/components/MyModal'; import MyModal from '@/components/MyModal';
import DateRangePicker, { type DateRangeType } from '@/components/DateRangePicker'; import DateRangePicker, { type DateRangeType } from '@/components/DateRangePicker';
...@@ -199,7 +199,7 @@ function DetailLogsModal({ ...@@ -199,7 +199,7 @@ function DetailLogsModal({
const { data: chat } = useQuery( const { data: chat } = useQuery(
['getChatDetail', chatId], ['getChatDetail', chatId],
() => getInitChatSiteInfo({ appId, chatId }), () => getInitChatInfo({ appId, chatId }),
{ {
onSuccess(res) { onSuccess(res) {
const history = res.history.map((item) => ({ const history = res.history.map((item) => ({
......
...@@ -104,7 +104,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => { ...@@ -104,7 +104,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
> >
<Flex mb={4} alignItems={'center'}> <Flex mb={4} alignItems={'center'}>
<Avatar src={appDetail.avatar} w={'34px'} borderRadius={'lg'} /> <Avatar src={appDetail.avatar} w={'34px'} borderRadius={'lg'} />
<Box ml={2} fontWeight={'bold'}> <Box ml={2} fontWeight={'bold'} fontSize={'sm'}>
{appDetail.name} {appDetail.name}
</Box> </Box>
</Flex> </Flex>
......
...@@ -55,7 +55,7 @@ const ChatHistorySlider = ({ ...@@ -55,7 +55,7 @@ const ChatHistorySlider = ({
history: HistoryItemType[]; history: HistoryItemType[];
activeChatId: string; activeChatId: string;
onChangeChat: (chatId?: string) => void; onChangeChat: (chatId?: string) => void;
onDelHistory: (chatId: string) => void; onDelHistory: (e: { chatId: string }) => void;
onClearHistory: () => void; onClearHistory: () => void;
onSetHistoryTop?: (e: { chatId: string; top: boolean }) => void; onSetHistoryTop?: (e: { chatId: string; top: boolean }) => void;
onSetCustomTitle?: (e: { chatId: string; title: string }) => void; onSetCustomTitle?: (e: { chatId: string; title: string }) => void;
...@@ -261,7 +261,7 @@ const ChatHistorySlider = ({ ...@@ -261,7 +261,7 @@ const ChatHistorySlider = ({
_hover={{ color: 'red.500' }} _hover={{ color: 'red.500' }}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
onDelHistory(item.id); onDelHistory({ chatId: item.id });
if (item.id === activeChatId) { if (item.id === activeChatId) {
onChangeChat(); onChangeChat();
} }
......
import React, { useCallback, useRef } from 'react'; import React, { useCallback, useRef } from 'react';
import Head from 'next/head'; import Head from 'next/head';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { getInitChatSiteInfo, delChatRecordById, putChatHistory } from '@/web/core/chat/api'; import { getInitChatInfo, putChatHistory } from '@/web/core/chat/api';
import { import {
Box, Box,
Flex, Flex,
...@@ -34,6 +34,7 @@ import { serviceSideProps } from '@/web/common/utils/i18n'; ...@@ -34,6 +34,7 @@ import { serviceSideProps } from '@/web/common/utils/i18n';
import { useAppStore } from '@/web/core/app/store/useAppStore'; import { useAppStore } from '@/web/core/app/store/useAppStore';
import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils'; import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils';
import { chatContentReplaceBlock } from '@fastgpt/global/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 Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
const router = useRouter(); const router = useRouter();
...@@ -49,13 +50,15 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => { ...@@ -49,13 +50,15 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
setLastChatAppId, setLastChatAppId,
lastChatId, lastChatId,
setLastChatId, setLastChatId,
history, histories,
loadHistory, loadHistories,
pushHistory,
updateHistory, updateHistory,
delHistory, delOneHistory,
clearHistory, clearHistories,
chatData, chatData,
setChatData setChatData,
delOneHistoryItem
} = useChatStore(); } = useChatStore();
const { myApps, loadMyApps } = useAppStore(); const { myApps, loadMyApps } = useAppStore();
const { userInfo } = useUserStore(); const { userInfo } = useUserStore();
...@@ -85,7 +88,7 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => { ...@@ -85,7 +88,7 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
prompts[1]?.value?.slice(0, 20) || prompts[1]?.value?.slice(0, 20) ||
'新对话'; '新对话';
// update history // new chat
if (completionChatId !== chatId) { if (completionChatId !== chatId) {
const newHistory: ChatHistoryItemType = { const newHistory: ChatHistoryItemType = {
chatId: completionChatId, chatId: completionChatId,
...@@ -94,7 +97,7 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => { ...@@ -94,7 +97,7 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
appId, appId,
top: false top: false
}; };
updateHistory(newHistory); pushHistory(newHistory);
if (controller.signal.reason !== 'leave') { if (controller.signal.reason !== 'leave') {
forbidRefresh.current = true; forbidRefresh.current = true;
router.replace({ router.replace({
...@@ -105,7 +108,8 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => { ...@@ -105,7 +108,8 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
}); });
} }
} else { } else {
const currentChat = history.find((item) => item.chatId === chatId); // update chat
const currentChat = histories.find((item) => item.chatId === chatId);
currentChat && currentChat &&
updateHistory({ updateHistory({
...currentChat, ...currentChat,
...@@ -117,30 +121,12 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => { ...@@ -117,30 +121,12 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
setChatData((state) => ({ setChatData((state) => ({
...state, ...state,
title: newTitle, title: newTitle,
history: ChatBoxRef.current?.getChatHistory() || state.history history: ChatBoxRef.current?.getChatHistories() || state.history
})); }));
return { responseText, responseData, isNewChat: forbidRefresh.current }; return { responseText, responseData, isNewChat: forbidRefresh.current };
}, },
[appId, chatId, history, router, setChatData, updateHistory] [appId, chatId, histories, pushHistory, 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]
); );
// get chat app info // get chat app info
...@@ -156,10 +142,10 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => { ...@@ -156,10 +142,10 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
}) => { }) => {
try { try {
loading && setIsLoading(true); loading && setIsLoading(true);
const res = await getInitChatSiteInfo({ appId, chatId }); const res = await getInitChatInfo({ appId, chatId });
const history = res.history.map((item) => ({ const history = res.history.map((item) => ({
...item, ...item,
status: 'finish' as any status: ChatStatusEnum.finish
})); }));
setChatData({ setChatData({
...@@ -185,8 +171,13 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => { ...@@ -185,8 +171,13 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
}); });
if (e?.code === 501) { if (e?.code === 501) {
router.replace('/app/list'); router.replace('/app/list');
} else { } else if (chatId) {
router.replace('/chat'); router.replace({
query: {
...router.query,
chatId: ''
}
});
} }
} }
setIsLoading(false); setIsLoading(false);
...@@ -250,7 +241,7 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => { ...@@ -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 ( return (
<Flex h={'100%'}> <Flex h={'100%'}>
...@@ -289,7 +280,7 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => { ...@@ -289,7 +280,7 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
appAvatar={chatData.app.avatar} appAvatar={chatData.app.avatar}
activeChatId={chatId} activeChatId={chatId}
onClose={onCloseSlider} onClose={onCloseSlider}
history={history.map((item, i) => ({ history={histories.map((item, i) => ({
id: item.chatId, id: item.chatId,
title: item.title, title: item.title,
customTitle: item.customTitle, customTitle: item.customTitle,
...@@ -306,39 +297,24 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => { ...@@ -306,39 +297,24 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
onCloseSlider(); onCloseSlider();
} }
}} }}
onDelHistory={delHistory} onDelHistory={delOneHistory}
onClearHistory={() => { onClearHistory={() => {
clearHistory(appId); clearHistories({ appId });
router.replace({ router.replace({
query: { query: {
appId appId
} }
}); });
}} }}
onSetHistoryTop={async (e) => { onSetHistoryTop={(e) => {
try { updateHistory(e);
await putChatHistory(e);
const historyItem = history.find((item) => item.chatId === e.chatId);
if (!historyItem) return;
updateHistory({
...historyItem,
top: e.top
});
} catch (error) {}
}} }}
onSetCustomTitle={async (e) => { onSetCustomTitle={async (e) => {
try { updateHistory({
putChatHistory({ chatId: e.chatId,
chatId: e.chatId, title: e.title,
customTitle: e.title customTitle: e.title
}); });
const historyItem = history.find((item) => item.chatId === e.chatId);
if (!historyItem) return;
updateHistory({
...historyItem,
customTitle: e.title
});
} catch (error) {}
}} }}
/> />
)} )}
...@@ -372,7 +348,7 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => { ...@@ -372,7 +348,7 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
feedbackType={'user'} feedbackType={'user'}
onUpdateVariable={(e) => {}} onUpdateVariable={(e) => {}}
onStartChat={startChat} onStartChat={startChat}
onDelMessage={delOneHistoryItem} onDelMessage={(e) => delOneHistoryItem({ ...e, chatId })}
/> />
</Box> </Box>
</Flex> </Flex>
......
...@@ -189,15 +189,6 @@ export async function searchDatasetData(props: SearchProps) { ...@@ -189,15 +189,6 @@ export async function searchDatasetData(props: SearchProps) {
}) })
).filter((item) => item.score > similarity); ).filter((item) => item.score > similarity);
// (It's possible that rerank failed) concat rerank results and search results
set = new Set<string>(reRankResults.map((item) => item.id));
embeddingRecallResults.forEach((item) => {
if (!set.has(item.id) && item.score >= similarity) {
reRankResults.push(item);
set.add(item.id);
}
});
return { return {
searchRes: reRankResults.slice(0, limit), searchRes: reRankResults.slice(0, limit),
tokenLen tokenLen
...@@ -382,7 +373,7 @@ export async function reRankSearchResult({ ...@@ -382,7 +373,7 @@ export async function reRankSearchResult({
} catch (error) { } catch (error) {
console.log(error); console.log(error);
return []; return data;
} }
} }
// ------------------ search end ------------------ // ------------------ search end ------------------
...@@ -3,7 +3,7 @@ import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants'; ...@@ -3,7 +3,7 @@ import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants'; import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import { RunningModuleItemType } from '@/types/app'; import { RunningModuleItemType } from '@/types/app';
import { ModuleDispatchProps } from '@/types/core/chat/type'; 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 { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { ModuleItemType } from '@fastgpt/global/core/module/type'; import { ModuleItemType } from '@fastgpt/global/core/module/type';
import { UserType } from '@fastgpt/global/support/user/type'; import { UserType } from '@fastgpt/global/support/user/type';
......
...@@ -48,7 +48,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi ...@@ -48,7 +48,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
answerText, answerText,
responseData: { responseData: {
moduleLogo: plugin.avatar, moduleLogo: plugin.avatar,
price: responseData.reduce((sum, item) => sum + item.price, 0), price: responseData.reduce((sum, item) => sum + (item.price || 0), 0),
runningTime: responseData.reduce((sum, item) => sum + (item.runningTime || 0), 0), runningTime: responseData.reduce((sum, item) => sum + (item.runningTime || 0), 0),
pluginOutput: output?.pluginOutput pluginOutput: output?.pluginOutput
}, },
......
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);
}
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
};
}
import { authOutLinkLimit } from '@/service/support/outLink/auth'; import { POST } from '@fastgpt/service/common/api/plusRequest';
import { AuthLinkChatProps } from '@fastgpt/global/support/outLink/api.d'; import type {
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant'; AuthOutLinkChatProps,
import { getUserAndAuthBalance } from './user'; AuthOutLinkLimitProps,
AuthOutLinkInitProps,
AuthOutLinkResponse
} from '@fastgpt/global/support/outLink/api.d';
import { authOutLinkValid } from '@fastgpt/service/support/permission/auth/outLink'; 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 function authOutLinkInit(data: AuthOutLinkInitProps): Promise<AuthOutLinkResponse> {
if (!global.feConfigs?.isPlus) return Promise.resolve({ uid: data.outLinkUid });
return POST<AuthOutLinkResponse>('/support/outLink/authInit', data);
}
export function authOutLinkChatLimit(data: AuthOutLinkLimitProps): Promise<AuthOutLinkResponse> {
if (!global.feConfigs?.isPlus) return Promise.resolve({ uid: data.outLinkUid });
return POST<AuthOutLinkResponse>('/support/outLink/authChatStart', data);
}
export const authOutLink = async ({
shareId,
outLinkUid
}: {
shareId?: string;
outLinkUid?: string;
}): Promise<{
uid: string;
appId: string;
shareChat: OutLinkSchema;
}> => {
if (!outLinkUid) {
return Promise.reject(OutLinkErrEnum.linkUnInvalid);
}
const result = await authOutLinkValid({ shareId });
const { uid } = await authOutLinkInit({
outLinkUid,
tokenUrl: result.shareChat.limit?.hookUrl
});
return {
...result,
uid
};
};
export async function authOutLinkChat({ export async function authOutLinkChatStart({
shareId, shareId,
ip, ip,
authToken, outLinkUid,
question question
}: AuthLinkChatProps & { }: AuthOutLinkChatProps & {
shareId: string; shareId: string;
}) { }) {
// get outLink // get outLink and app
const { shareChat, app } = await authOutLinkValid({ shareId }); 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 }), getUserAndAuthBalance({ tmbId: shareChat.tmbId, minBalance: 0 }),
global.feConfigs?.isPlus authOutLinkChatLimit({ outLink: shareChat, ip, outLinkUid, question })
? authOutLinkLimit({ outLink: shareChat, ip, authToken, question })
: undefined
]); ]);
return { return {
authType: AuthUserTypeEnum.token, authType: AuthUserTypeEnum.token,
responseDetail: shareChat.responseDetail, responseDetail: shareChat.responseDetail,
user, user,
app appId,
uid
}; };
} }
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
};
}
import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode';
import { MongoUser } from '@fastgpt/service/support/user/schema';
import { UserType } from '@fastgpt/global/support/user/type';
import {
getTeamInfoByTmbId,
getUserDefaultTeam
} from '@fastgpt/service/support/user/team/controller';
export async function getUserDetail({
tmbId,
userId
}: {
tmbId?: string;
userId?: string;
}): Promise<UserType> {
const team = await (async () => {
if (tmbId) {
return getTeamInfoByTmbId({ tmbId });
}
if (userId) {
return getUserDefaultTeam({ userId });
}
return Promise.reject(ERROR_ENUM.unAuthorization);
})();
const user = await MongoUser.findById(team.userId);
if (!user) {
return Promise.reject(ERROR_ENUM.unAuthorization);
}
return {
_id: user._id,
username: user.username,
avatar: user.avatar,
balance: user.balance,
timezone: user.timezone,
promotionRate: user.promotionRate,
openaiAccount: user.openaiAccount,
team
};
}
import { BillSourceEnum, PRICE_SCALE } from '@fastgpt/global/support/wallet/bill/constants'; import { BillSourceEnum, PRICE_SCALE } from '@fastgpt/global/support/wallet/bill/constants';
import { getAudioSpeechModel, getQAModel } from '@/service/core/ai/model'; 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 { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
import { addLog } from '@fastgpt/service/common/mongo/controller'; import { addLog } from '@fastgpt/service/common/mongo/controller';
import type { ConcatBillProps, CreateBillProps } from '@fastgpt/global/support/wallet/bill/api.d'; import type { ConcatBillProps, CreateBillProps } from '@fastgpt/global/support/wallet/bill/api.d';
...@@ -41,7 +41,7 @@ export const pushChatBill = ({ ...@@ -41,7 +41,7 @@ export const pushChatBill = ({
source: `${BillSourceEnum}`; source: `${BillSourceEnum}`;
response: ChatHistoryItemResType[]; response: ChatHistoryItemResType[];
}) => { }) => {
const total = response.reduce((sum, item) => sum + item.price, 0); const total = response.reduce((sum, item) => sum + (item.price || 0), 0);
createBill({ createBill({
teamId, teamId,
......
...@@ -15,6 +15,7 @@ type Props = { ...@@ -15,6 +15,7 @@ type Props = {
updateUseTime: boolean; updateUseTime: boolean;
source: `${ChatSourceEnum}`; source: `${ChatSourceEnum}`;
shareId?: string; shareId?: string;
outLinkUid?: string;
content: [ChatItemType, ChatItemType]; content: [ChatItemType, ChatItemType];
}; };
...@@ -27,10 +28,11 @@ export async function saveChat({ ...@@ -27,10 +28,11 @@ export async function saveChat({
updateUseTime, updateUseTime,
source, source,
shareId, shareId,
outLinkUid,
content content
}: Props) { }: Props) {
try { try {
const chatHistory = await MongoChat.findOne( const chat = await MongoChat.findOne(
{ {
chatId, chatId,
teamId, teamId,
...@@ -57,7 +59,7 @@ export async function saveChat({ ...@@ -57,7 +59,7 @@ export async function saveChat({
content[1]?.value?.slice(0, 20) || content[1]?.value?.slice(0, 20) ||
'Chat'; 'Chat';
if (chatHistory) { if (chat) {
promise.push( promise.push(
MongoChat.updateOne( MongoChat.updateOne(
{ chatId }, { chatId },
...@@ -77,7 +79,8 @@ export async function saveChat({ ...@@ -77,7 +79,8 @@ export async function saveChat({
variables, variables,
title, title,
source, source,
shareId shareId,
outLinkUid
}) })
); );
} }
......
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 = [ const filedList = [
'moduleType', 'moduleType',
'moduleName', 'moduleName',
'moduleLogo', 'moduleLogo',
'runningTime', 'runningTime',
'historyPreview',
'quoteList', 'quoteList',
'question' 'question'
]; ];
...@@ -17,6 +22,6 @@ export function selectShareResponse({ responseData }: { responseData: ChatHistor ...@@ -17,6 +22,6 @@ export function selectShareResponse({ responseData }: { responseData: ChatHistor
obj[key] = item[key]; obj[key] = item[key];
} }
} }
return obj; return obj as ChatHistoryItemResType;
}); });
} }
import { sseResponseEventEnum } from '@fastgpt/service/common/response/constant'; import { sseResponseEventEnum } from '@fastgpt/service/common/response/constant';
import { getErrText } from '@fastgpt/global/common/error/utils'; import { getErrText } from '@fastgpt/global/common/error/utils';
import { parseStreamChunk, SSEParseData } from '@/utils/sse'; 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 { StartChatFnProps } from '@/components/ChatBox';
import { getToken } from '@/web/support/user/auth'; import { getToken } from '@/web/support/user/auth';
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants'; import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
......
...@@ -346,7 +346,7 @@ export const appTemplates: (AppItemType & { ...@@ -346,7 +346,7 @@ export const appTemplates: (AppItemType & {
{ {
key: 'similarity', key: 'similarity',
type: 'slider', type: 'slider',
label: '相度', label: '相度',
value: 0.4, value: 0.4,
min: 0, min: 0,
max: 1, max: 1,
...@@ -1398,7 +1398,7 @@ export const appTemplates: (AppItemType & { ...@@ -1398,7 +1398,7 @@ export const appTemplates: (AppItemType & {
{ {
key: 'similarity', key: 'similarity',
type: 'slider', type: 'slider',
label: '相度', label: '相度',
value: 0.76, value: 0.76,
min: 0, min: 0,
max: 1, max: 1,
......
import { GET, POST, DELETE, PUT } from '@/web/common/api/request'; import { GET, POST, DELETE, PUT } from '@/web/common/api/request';
import type { ChatHistoryItemType } from '@fastgpt/global/core/chat/type.d'; import type { ChatHistoryItemType } from '@fastgpt/global/core/chat/type.d';
import type { InitChatResponse } from '@fastgpt/global/core/chat/api.d'; import type {
import type { RequestPaging } from '@/types'; InitChatProps,
import { UpdateHistoryProps } from '@fastgpt/global/core/chat/api.d'; InitChatResponse,
import type { AdminUpdateFeedbackParams } from '@fastgpt/global/core/chat/api.d'; InitOutLinkChatProps,
import { GetChatSpeechProps } from '@/global/core/chat/api.d'; getHistoriesProps
} from '@/global/core/chat/api.d';
import type {
AdminUpdateFeedbackParams,
ClearHistoriesProps,
DelHistoryProps,
DeleteChatItemProps,
UpdateHistoryProps
} from '@/global/core/chat/api.d';
/** /**
* 获取初始化聊天内容 * 获取初始化聊天内容
*/ */
export const getInitChatSiteInfo = (data: { appId: string; chatId?: string }) => export const getInitChatInfo = (data: InitChatProps) =>
GET<InitChatResponse>(`/core/chat/init`, data); GET<InitChatResponse>(`/core/chat/init`, data);
export const getInitOutLinkChatInfo = (data: InitOutLinkChatProps) =>
GET<InitChatResponse>(`/core/chat/outLink/init`, data);
/** /**
* 获取历史记录 * get current window history(appid or shareId)
*/ */
export const getChatHistory = (data: RequestPaging & { appId: string }) => export const getChatHistories = (data: getHistoriesProps) =>
POST<ChatHistoryItemType[]>('/core/chat/list', data); POST<ChatHistoryItemType[]>('/core/chat/getHistories', data);
/** /**
* 删除一条历史记录 * delete one history
*/ */
export const delChatHistoryById = (chatId: string) => DELETE(`/core/chat/delete`, { chatId }); export const delChatHistoryById = (data: DelHistoryProps) => DELETE(`/core/chat/delHistory`, data);
/** /**
* clear all history by appid * 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); 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 }) => export const userUpdateChatFeedback = (data: { chatItemId: string; userFeedback?: string }) =>
POST('/core/chat/feedback/userUpdate', data); POST('/core/chat/feedback/userUpdate', data);
......
...@@ -2,35 +2,36 @@ import { create } from 'zustand'; ...@@ -2,35 +2,36 @@ import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware'; import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer'; import { immer } from 'zustand/middleware/immer';
import type { ChatHistoryItemType } from '@fastgpt/global/core/chat/type.d'; import type { ChatHistoryItemType } from '@fastgpt/global/core/chat/type.d';
import type { InitChatResponse } from '@fastgpt/global/core/chat/api'; import type {
import { delChatHistoryById, getChatHistory, clearChatHistoryByAppId } from '@/web/core/chat/api'; 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 = { type State = {
history: ChatHistoryItemType[]; histories: ChatHistoryItemType[];
loadHistory: (data: { appId: string }) => Promise<null>; loadHistories: (data: getHistoriesProps) => Promise<null>;
delHistory(history: string): Promise<void>; delOneHistory(data: DelHistoryProps): Promise<void>;
clearHistory(appId: string): Promise<void>; clearHistories(data: ClearHistoriesProps): Promise<void>;
updateHistory: (history: ChatHistoryItemType) => void; pushHistory: (history: ChatHistoryItemType) => void;
updateHistory: (e: UpdateHistoryProps & { updateTime?: Date; title?: string }) => Promise<any>;
chatData: InitChatResponse; chatData: InitChatResponse;
setChatData: (e: InitChatResponse | ((e: InitChatResponse) => InitChatResponse)) => void; setChatData: (e: InitChatResponse | ((e: InitChatResponse) => InitChatResponse)) => void;
lastChatAppId: string; lastChatAppId: string;
setLastChatAppId: (id: string) => void; setLastChatAppId: (id: string) => void;
lastChatId: string; lastChatId: string;
setLastChatId: (id: string) => void; setLastChatId: (id: string) => void;
}; delOneHistoryItem: (e: { chatId: string; contentId?: string; index: number }) => Promise<any>;
const defaultChatData: InitChatResponse = {
chatId: '',
appId: '',
app: {
name: 'Loading',
avatar: '/icon/logo.svg',
intro: '',
canUse: false
},
title: '新对话',
variables: {},
history: []
}; };
export const useChatStore = create<State>()( export const useChatStore = create<State>()(
...@@ -49,49 +50,62 @@ export const useChatStore = create<State>()( ...@@ -49,49 +50,62 @@ export const useChatStore = create<State>()(
state.lastChatId = id; state.lastChatId = id;
}); });
}, },
history: [], histories: [],
async loadHistory({ appId }) { async loadHistories(e) {
const oneHistory = get().history[0]; const data = await getChatHistories(e);
if (oneHistory && oneHistory.appId === appId) return null;
const data = await getChatHistory({
appId,
pageNum: 1,
pageSize: 20
});
set((state) => { set((state) => {
state.history = data; state.histories = data;
}); });
return null; return null;
}, },
async delHistory(chatId) { async delOneHistory(props) {
set((state) => { 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) => { set((state) => {
state.history = []; state.histories = [];
}); });
await clearChatHistoryByAppId(appId); await clearChatHistoryByAppId(data);
}, },
updateHistory(history) { pushHistory(history) {
const index = get().history.findIndex((item) => item.chatId === history.chatId);
set((state) => { set((state) => {
const newHistory = (() => { state.histories = [history, ...state.histories];
if (index > -1) { });
},
async updateHistory(props) {
const { chatId, customTitle, top, title, updateTime } = props;
const index = get().histories.findIndex((item) => item.chatId === chatId);
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 [ return [
history, newHistory,
...get().history.slice(0, index), ...get().histories.slice(0, index),
...get().history.slice(index + 1) ...get().histories.slice(index + 1)
]; ];
} else { })();
return [history, ...state.history];
}
})();
state.history = newHistory; state.histories = newHistories;
}); });
}
}, },
chatData: defaultChatData, chatData: defaultChatData,
setChatData(e = defaultChatData) { setChatData(e = defaultChatData) {
...@@ -104,6 +118,19 @@ export const useChatStore = create<State>()( ...@@ -104,6 +118,19 @@ export const useChatStore = create<State>()(
state.chatData = e; 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);
}
} }
})), })),
{ {
......
import { create } from 'zustand'; import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware'; import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer'; import { immer } from 'zustand/middleware/immer';
import type { import type { ChatHistoryItemType } from '@fastgpt/global/core/chat/type.d';
ShareChatHistoryItemType, import { customAlphabet } from 'nanoid';
ShareChatType const nanoid = customAlphabet(
} from '@fastgpt/global/support/outLink/api.d'; 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWSYZ1234567890_',
import type { ChatSiteItemType } from '@fastgpt/global/core/chat/type.d'; 24
import { HUMAN_ICON } from '@fastgpt/global/core/chat/constants'; );
import { chatContentReplaceBlock } from '@fastgpt/global/core/chat/utils';
type State = { type State = {
shareChatData: ShareChatType; localUId: string;
setShareChatData: (e: ShareChatType | ((e: ShareChatType) => ShareChatType)) => void; shareChatHistory: (ChatHistoryItemType & { delete?: boolean })[];
shareChatHistory: ShareChatHistoryItemType[]; clearLocalHistory: (shareId?: string) => void;
saveChatResponse: (e: {
chatId: string;
prompts: ChatSiteItemType[];
variables: Record<string, any>;
shareId: string;
}) => void;
delOneShareHistoryByChatId: (chatId: string) => void;
delShareChatHistoryItemById: (e: { chatId: string; contentId?: string; index: number }) => void;
delManyShareChatHistoryByShareId: (shareId?: string) => void;
};
export const defaultHistory: ShareChatHistoryItemType = {
chatId: `${Date.now()}`,
updateTime: new Date(),
title: '新对话',
shareId: '',
chats: []
};
const defaultShareChatData: ShareChatType = {
userAvatar: HUMAN_ICON,
app: {
name: '',
avatar: '/icon/logo.svg',
intro: ''
},
history: defaultHistory
}; };
export const useShareChatStore = create<State>()( export const useShareChatStore = create<State>()(
devtools( devtools(
persist( persist(
immer((set, get) => ({ immer((set, get) => ({
shareChatData: defaultShareChatData, localUId: `shareChat-${Date.now()}-${nanoid()}`,
setShareChatData(e) { shareChatHistory: [], // old version field
const val = (() => { clearLocalHistory() {
if (typeof e === 'function') { // abandon
return e(get().shareChatData);
} else {
return e;
}
})();
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) => { set((state) => {
if (shareId) { state.shareChatHistory = state.shareChatHistory.map((item) => ({
state.shareChatHistory = state.shareChatHistory.filter( ...item,
(item) => item.shareId !== shareId delete: true
); }));
} else {
state.shareChatHistory = [];
}
}); });
} }
})), })),
{ {
name: 'shareChatStore', name: 'shareChatStore',
partialize: (state) => ({ partialize: (state) => ({
localUId: state.localUId,
shareChatHistory: state.shareChatHistory shareChatHistory: state.shareChatHistory
}) })
} }
......
...@@ -79,8 +79,7 @@ export const useDatasetStore = create<State>()( ...@@ -79,8 +79,7 @@ export const useDatasetStore = create<State>()(
item._id === data.id item._id === data.id
? { ? {
...item, ...item,
...data, ...data
tags: data.tags || []
} }
: item : item
); );
......
import { GET, POST, DELETE } from '@/web/common/api/request'; 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'; import type { OutLinkEditType, OutLinkSchema } from '@fastgpt/global/support/outLink/type.d';
/** /**
* 初始化分享聊天
*/
export const initShareChatInfo = (data: { shareId: string; authToken?: string }) =>
GET<InitShareChatResponse>(`/support/outLink/init`, data);
/**
* create a shareChat * create a shareChat
*/ */
export const createShareChat = ( export const createShareChat = (
......
import { GET } from '@/web/common/api/request'; import { GET } from '@/web/common/api/request';
import type { PaySchema } from '@fastgpt/global/support/wallet/pay/type.d'; import type { PaySchema } from '@fastgpt/global/support/wallet/pay/type.d';
import { delay } from '@fastgpt/global/common/system/utils';
export const getPayOrders = () => GET<PaySchema[]>(`/plusApi/support/wallet/pay/getPayOrders`); export const getPayOrders = () => GET<PaySchema[]>(`/plusApi/support/wallet/pay/getPayOrders`);
export const getPayCode = (amount: number) => export const getPayCode = (amount: number) =>
...@@ -11,15 +9,9 @@ export const getPayCode = (amount: number) => ...@@ -11,15 +9,9 @@ export const getPayCode = (amount: number) =>
}>(`/plusApi/support/wallet/pay/getPayCode`, { amount }); }>(`/plusApi/support/wallet/pay/getPayCode`, { amount });
export const checkPayResult = (payId: string) => export const checkPayResult = (payId: string) =>
GET<number>(`/plusApi/support/wallet/pay/checkPayResult`, { payId }).then(() => { GET<string>(`/plusApi/support/wallet/pay/checkPayResult`, { payId }).then((data) => {
async function startQueue() { try {
try { GET('/common/system/unlockTask');
await GET('/common/system/unlockTask'); } catch (error) {}
} catch (error) { return data;
await delay(1000);
startQueue();
}
}
startQueue();
return 'success';
}); });
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment