Fix share page whisper auth (#1161)

Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
Archer 2024-04-09 21:38:47 +08:00 committed by GitHub
parent adfad8ff7f
commit 2991c07467
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
55 changed files with 466 additions and 251 deletions

View File

@ -20,7 +20,7 @@ llm模型全部合并
```json ```json
{ {
"feConfigs": { "feConfigs": {
"lafEnv": "https://laf.dev" // laf环境 "lafEnv": "https://laf.dev" // laf环境。 https://laf.run (杭州阿里云) ,或者私有化的laf环境。如果使用 Laf openapi 功能,需要最新版的 laf 。
}, },
"systemEnv": { "systemEnv": {
"vectorMaxProcess": 15, "vectorMaxProcess": 15,

View File

@ -100,7 +100,7 @@ docker run -d --name reranker -p 6006:6006 -e ACCESS_TOKEN=mytoken --gpus all re
version: "3" version: "3"
services: services:
reranker: reranker:
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/rerank:v0.2 image: registry.cn-hangzhou.aliyuncs.com/fastgpt/bge-rerank-base:v0.1
container_name: reranker container_name: reranker
# GPU运行环境如果宿主机未安装将deploy配置隐藏即可 # GPU运行环境如果宿主机未安装将deploy配置隐藏即可
deploy: deploy:

View File

@ -19,10 +19,16 @@ curl --location --request POST 'https://{{host}}/api/admin/clearInvalidData' \
该请求会执行脏数据清理(清理无效的文件、清理无效的图片、清理无效的知识库集合、清理无效的向量) 该请求会执行脏数据清理(清理无效的文件、清理无效的图片、清理无效的知识库集合、清理无效的向量)
## 修改配置文件
增加了Laf环境配置[点击查看最新的配置文件](/docs/development/configuration/)
## V4.7.1 更新说明 ## V4.7.1 更新说明
1. 新增 - 语音输入完整配置。支持选择是否打开语音输入(包括分享页面),支持语音输入后自动发送,支持语音输入后自动语音播放(流式)。 1. 新增 - 语音输入完整配置。支持选择是否打开语音输入(包括分享页面),支持语音输入后自动发送,支持语音输入后自动语音播放(流式)。
2. 新增 - Pptx 和 xlsx 文件读取。但所有文件读取都放服务端,会消耗更多的服务器资源,以及无法在上传时预览更多内容。 2. 新增 - pptx 和 xlsx 文件读取。但所有文件读取都放服务端,会消耗更多的服务器资源,以及无法在上传时预览更多内容。
3. 新增 - 集成 Laf 云函数,可以读取 Laf 账号中的云函数作为 HTTP 模块。 3. 新增 - 集成 Laf 云函数,可以读取 Laf 账号中的云函数作为 HTTP 模块。
4. 新增 - 定时器清理垃圾数据。采用小范围清理会清理最近n个小时的所以请保证服务持续运行长时间不允许可以继续执行 clearInvalidData 的接口进行全量清理。) 4. 新增 - 定时器清理垃圾数据。采用小范围清理会清理最近n个小时的所以请保证服务持续运行长时间不允许可以继续执行 clearInvalidData 的接口进行全量清理。)
5. 商业版新增 - 后台配置系统通知。 5. 商业版新增 - 后台配置系统通知。

View File

@ -88,7 +88,7 @@ Response:
[ [
{ {
"moduleId": "userGuide", "moduleId": "userGuide",
"name": "core.module.template.User guide", "name": "core.module.template.App system setting",
"flowType": "userGuide", "flowType": "userGuide",
"position": { "position": {
"x": 454.98510354678695, "x": 454.98510354678695,

View File

@ -27,7 +27,7 @@ weight: 404
[ [
{ {
"moduleId": "userGuide", "moduleId": "userGuide",
"name": "core.module.template.User guide", "name": "core.module.template.App system setting",
"intro": "core.app.tip.userGuideTip", "intro": "core.app.tip.userGuideTip",
"avatar": "/imgs/module/userGuide.png", "avatar": "/imgs/module/userGuide.png",
"flowType": "userGuide", "flowType": "userGuide",

View File

@ -84,7 +84,7 @@ export default async function (ctx: FunctionContext) {
[ [
{ {
"moduleId": "userGuide", "moduleId": "userGuide",
"name": "core.module.template.User guide", "name": "core.module.template.App system setting",
"intro": "core.app.tip.userGuideTip", "intro": "core.app.tip.userGuideTip",
"avatar": "/imgs/module/userGuide.png", "avatar": "/imgs/module/userGuide.png",
"flowType": "userGuide", "flowType": "userGuide",

View File

@ -27,8 +27,6 @@ Laf 提供了 PAT(访问凭证) 来实现 Laf 平台外的快捷登录,可以
填入 PAT 验证后,选择需要绑定的应用(应用需要是 Running 状态),即可调用该应用下的云函数。 填入 PAT 验证后,选择需要绑定的应用(应用需要是 Running 状态),即可调用该应用下的云函数。
> 如果需要解绑则取消绑定后,点击“更新”即可
![](/imgs/laf2.webp) ![](/imgs/laf2.webp)
## 编写云函数 ## 编写云函数

View File

@ -1,5 +1,5 @@
import { getErrText } from '../error/utils'; import { getErrText } from '../error/utils';
import { countPromptTokens } from './tiktoken'; import { replaceRegChars } from './tools';
/** /**
* text split into chunks * text split into chunks
@ -31,7 +31,7 @@ export const splitText2Chunks = (props: {
// The larger maxLen is, the next sentence is less likely to trigger splitting // The larger maxLen is, the next sentence is less likely to trigger splitting
const stepReges: { reg: RegExp; maxLen: number }[] = [ const stepReges: { reg: RegExp; maxLen: number }[] = [
...customReg.map((text) => ({ ...customReg.map((text) => ({
reg: new RegExp(`(${text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'g'), reg: new RegExp(`(${replaceRegChars(text)})`, 'g'),
maxLen: chunkLen * 1.4 maxLen: chunkLen * 1.4
})), })),
{ reg: /^(#\s[^\n]+)\n/gm, maxLen: chunkLen * 1.2 }, { reg: /^(#\s[^\n]+)\n/gm, maxLen: chunkLen * 1.2 },

View File

@ -51,3 +51,5 @@ export const replaceSensitiveText = (text: string) => {
export const getNanoid = (size = 12) => { export const getNanoid = (size = 12) => {
return customAlphabet('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890', size)(); return customAlphabet('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890', size)();
}; };
export const replaceRegChars = (text: string) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');

View File

@ -1,6 +1,5 @@
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '../../node/constant'; import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '../../node/constant';
import { FlowNodeTemplateType } from '../../type.d'; import { FlowNodeTemplateType } from '../../type.d';
import { userGuideTip } from '../tip';
import { import {
ModuleIOValueTypeEnum, ModuleIOValueTypeEnum,
ModuleInputKeyEnum, ModuleInputKeyEnum,
@ -12,8 +11,8 @@ export const UserGuideModule: FlowNodeTemplateType = {
templateType: FlowNodeTemplateTypeEnum.userGuide, templateType: FlowNodeTemplateTypeEnum.userGuide,
flowType: FlowNodeTypeEnum.userGuide, flowType: FlowNodeTypeEnum.userGuide,
avatar: '/imgs/module/userGuide.png', avatar: '/imgs/module/userGuide.png',
name: '全局配置', name: '系统配置',
intro: userGuideTip, intro: '可以配置应用的系统参数。',
inputs: [ inputs: [
{ {
key: ModuleInputKeyEnum.welcomeText, key: ModuleInputKeyEnum.welcomeText,

View File

@ -1,4 +1,3 @@
export const chatNodeSystemPromptTip = 'core.app.tip.chatNodeSystemPromptTip'; export const chatNodeSystemPromptTip = 'core.app.tip.chatNodeSystemPromptTip';
export const userGuideTip = 'core.app.tip.userGuideTip';
export const welcomeTextTip = 'core.app.tip.welcomeTextTip'; export const welcomeTextTip = 'core.app.tip.welcomeTextTip';
export const variableTip = 'core.app.tip.variableTip'; export const variableTip = 'core.app.tip.variableTip';

View File

@ -81,4 +81,5 @@ export type TeamTagItemType = {
export type LafAccountType = { export type LafAccountType = {
token: string; token: string;
appid: string; appid: string;
pat: string;
}; };

View File

@ -18,6 +18,7 @@ export type BillSchemaType = {
month?: number; month?: number;
datasetSize?: number; datasetSize?: number;
extraPoints?: number; extraPoints?: number;
invoice: boolean;
}; };
username: string; username: string;
}; };

View File

@ -35,6 +35,7 @@ try {
ImageSchema.index({ expiredTime: 1 }, { expireAfterSeconds: 60 }); ImageSchema.index({ expiredTime: 1 }, { expireAfterSeconds: 60 });
ImageSchema.index({ type: 1 }); ImageSchema.index({ type: 1 });
ImageSchema.index({ createTime: 1 }); ImageSchema.index({ createTime: 1 });
// delete related img
ImageSchema.index({ teamId: 1, 'metadata.relatedId': 1 }); ImageSchema.index({ teamId: 1, 'metadata.relatedId': 1 });
} catch (error) { } catch (error) {
console.log(error); console.log(error);

View File

@ -1,10 +1,9 @@
export type DeleteDatasetVectorProps = { export type DeleteDatasetVectorProps = (
| { id: string }
| { datasetIds: string[]; collectionIds?: string[] }
| { idList: string[] }
) & {
teamId: string; teamId: string;
id?: string;
datasetIds?: string[];
collectionIds?: string[];
idList?: string[];
}; };
export type InsertVectorProps = { export type InsertVectorProps = {

View File

@ -26,13 +26,7 @@ export async function initPg() {
`CREATE INDEX CONCURRENTLY IF NOT EXISTS vector_index ON ${PgDatasetTableName} USING hnsw (vector vector_ip_ops) WITH (m = 32, ef_construction = 64);` `CREATE INDEX CONCURRENTLY IF NOT EXISTS vector_index ON ${PgDatasetTableName} USING hnsw (vector vector_ip_ops) WITH (m = 32, ef_construction = 64);`
); );
await PgClient.query( await PgClient.query(
`CREATE INDEX CONCURRENTLY IF NOT EXISTS team_dataset_index ON ${PgDatasetTableName} USING btree(team_id, dataset_id);` `CREATE INDEX CONCURRENTLY IF NOT EXISTS team_dataset_collection_index ON ${PgDatasetTableName} USING btree(team_id, dataset_id, collection_id);`
);
await PgClient.query(
` CREATE INDEX CONCURRENTLY IF NOT EXISTS team_collection_index ON ${PgDatasetTableName} USING btree(team_id, collection_id);`
);
await PgClient.query(
`CREATE INDEX CONCURRENTLY IF NOT EXISTS team_id_index ON ${PgDatasetTableName} USING btree(team_id, id);`
); );
await PgClient.query( await PgClient.query(
`CREATE INDEX CONCURRENTLY IF NOT EXISTS create_time_index ON ${PgDatasetTableName} USING btree(createtime);` `CREATE INDEX CONCURRENTLY IF NOT EXISTS create_time_index ON ${PgDatasetTableName} USING btree(createtime);`
@ -83,31 +77,33 @@ export const deleteDatasetDataVector = async (
retry?: number; retry?: number;
} }
): Promise<any> => { ): Promise<any> => {
const { teamId, id, datasetIds, collectionIds, idList, retry = 2 } = props; const { teamId, retry = 2 } = props;
const teamIdWhere = `team_id='${String(teamId)}' AND`; const teamIdWhere = `team_id='${String(teamId)}' AND`;
const where = await (() => { const where = await (() => {
if (id) return `${teamIdWhere} id=${id}`; if ('id' in props && props.id) return `${teamIdWhere} id=${props.id}`;
if (datasetIds) { if ('datasetIds' in props && props.datasetIds) {
return `${teamIdWhere} dataset_id IN (${datasetIds const datasetIdWhere = `dataset_id IN (${props.datasetIds
.map((id) => `'${String(id)}'`)
.join(',')})`;
if ('collectionIds' in props && props.collectionIds) {
return `${teamIdWhere} ${datasetIdWhere} AND collection_id IN (${props.collectionIds
.map((id) => `'${String(id)}'`) .map((id) => `'${String(id)}'`)
.join(',')})`; .join(',')})`;
} }
if (collectionIds) { return `${teamIdWhere} ${datasetIdWhere}`;
return `${teamIdWhere} collection_id IN (${collectionIds
.map((id) => `'${String(id)}'`)
.join(',')})`;
} }
if (idList) { if ('idList' in props && props.idList) {
return `${teamIdWhere} id IN (${idList.map((id) => `'${String(id)}'`).join(',')})`; return `${teamIdWhere} id IN (${props.idList.map((id) => `'${String(id)}'`).join(',')})`;
} }
return Promise.reject('deleteDatasetData: no where'); return Promise.reject('deleteDatasetData: no where');
})(); })();
console.log(where, '===');
try { try {
await PgClient.delete(PgDatasetTableName, { await PgClient.delete(PgDatasetTableName, {
where: [where] where: [where]

View File

@ -118,6 +118,37 @@ export function createDefaultCollection({
); );
} }
/* delete collection related images/files */
export const delCollectionRelatedSource = async ({
collections,
session
}: {
collections: (CollectionWithDatasetType | DatasetCollectionSchemaType)[];
session: ClientSession;
}) => {
if (collections.length === 0) return;
const teamId = collections[0].teamId;
if (!teamId) return Promise.reject('teamId is not exist');
const fileIdList = collections.map((item) => item?.fileId || '').filter(Boolean);
const relatedImageIds = collections
.map((item) => item?.metadata?.relatedImgId || '')
.filter(Boolean);
// delete images
await delImgByRelatedId({
teamId,
relateIds: relatedImageIds,
session
});
// delete files
await delFileByFileIdList({
bucketName: BucketNameEnum.dataset,
fileIdList
});
};
/** /**
* delete collection and it related data * delete collection and it related data
*/ */
@ -134,26 +165,32 @@ export async function delCollectionAndRelatedSources({
if (!teamId) return Promise.reject('teamId is not exist'); if (!teamId) return Promise.reject('teamId is not exist');
const datasetIds = Array.from(
new Set(
collections.map((item) => {
if (typeof item.datasetId === 'string') {
return String(item.datasetId);
}
return String(item.datasetId._id);
})
)
);
const collectionIds = collections.map((item) => String(item._id)); const collectionIds = collections.map((item) => String(item._id));
const fileIdList = collections.map((item) => item?.fileId || '').filter(Boolean);
const relatedImageIds = collections await delCollectionRelatedSource({ collections, session });
.map((item) => item?.metadata?.relatedImgId || '')
.filter(Boolean);
// delete training data // delete training data
await MongoDatasetTraining.deleteMany({ await MongoDatasetTraining.deleteMany({
teamId, teamId,
datasetIds: { $in: datasetIds },
collectionId: { $in: collectionIds } collectionId: { $in: collectionIds }
}); });
// delete dataset.datas // delete dataset.datas
await MongoDatasetData.deleteMany({ teamId, collectionId: { $in: collectionIds } }, { session }); await MongoDatasetData.deleteMany(
// delete imgs { teamId, datasetIds: { $in: datasetIds }, collectionId: { $in: collectionIds } },
await delImgByRelatedId({ { session }
teamId, );
relateIds: relatedImageIds,
session
});
// delete collections // delete collections
await MongoDatasetCollection.deleteMany( await MongoDatasetCollection.deleteMany(
{ {
@ -163,9 +200,5 @@ export async function delCollectionAndRelatedSources({
); );
// no session delete: delete files, vector data // no session delete: delete files, vector data
await deleteDatasetDataVector({ teamId, collectionIds }); await deleteDatasetDataVector({ teamId, datasetIds, collectionIds });
await delFileByFileIdList({
bucketName: BucketNameEnum.dataset,
fileIdList
});
} }

View File

@ -1,8 +1,11 @@
import { CollectionWithDatasetType, DatasetSchemaType } from '@fastgpt/global/core/dataset/type'; import { CollectionWithDatasetType, DatasetSchemaType } from '@fastgpt/global/core/dataset/type';
import { MongoDatasetCollection } from './collection/schema'; import { MongoDatasetCollection } from './collection/schema';
import { MongoDataset } from './schema'; import { MongoDataset } from './schema';
import { delCollectionAndRelatedSources } from './collection/controller'; import { delCollectionRelatedSource } from './collection/controller';
import { ClientSession } from '../../common/mongo'; import { ClientSession } from '../../common/mongo';
import { MongoDatasetTraining } from './training/schema';
import { MongoDatasetData } from './data/schema';
import { deleteDatasetDataVector } from '../../common/vectorStore/controller';
/* ============= dataset ========== */ /* ============= dataset ========== */
/* find all datasetId by top datasetId */ /* find all datasetId by top datasetId */
@ -82,5 +85,26 @@ export async function delDatasetRelevantData({
'_id teamId fileId metadata' '_id teamId fileId metadata'
).lean(); ).lean();
await delCollectionAndRelatedSources({ collections, session }); // image and file
await delCollectionRelatedSource({ collections, session });
// delete training data
await MongoDatasetTraining.deleteMany({
teamId,
datasetId: { $in: datasetIds }
});
// delete dataset.datas
await MongoDatasetData.deleteMany({ teamId, datasetId: { $in: datasetIds } }, { session });
// delete collections
await MongoDatasetCollection.deleteMany(
{
teamId,
datasetId: { $in: datasetIds }
},
{ session }
);
// no session delete: delete files, vector data
await deleteDatasetDataVector({ teamId, datasetIds });
} }

View File

@ -1,2 +0,0 @@
import { MongoDatasetData } from './schema';
import { deleteDatasetDataVector } from '../../../common/vectorStore/controller';

View File

@ -77,17 +77,18 @@ const DatasetDataSchema = new Schema({
}); });
try { try {
// list collection and count data; list data // list collection and count data; list data; delete collection(relate data)
DatasetDataSchema.index( DatasetDataSchema.index(
{ teamId: 1, datasetId: 1, collectionId: 1, chunkIndex: 1, updateTime: -1 }, { teamId: 1, datasetId: 1, collectionId: 1, chunkIndex: 1, updateTime: -1 },
{ background: true } { background: true }
); );
// same data check
DatasetDataSchema.index({ teamId: 1, collectionId: 1, q: 1, a: 1 }, { background: true });
// full text index // full text index
DatasetDataSchema.index({ teamId: 1, datasetId: 1, fullTextToken: 'text' }, { background: true }); DatasetDataSchema.index({ teamId: 1, datasetId: 1, fullTextToken: 'text' }, { background: true });
// Recall vectors after data matching // Recall vectors after data matching
DatasetDataSchema.index({ teamId: 1, datasetId: 1, 'indexes.dataId': 1 }, { background: true }); DatasetDataSchema.index(
{ teamId: 1, datasetId: 1, collectionId: 1, 'indexes.dataId': 1 },
{ background: true }
);
DatasetDataSchema.index({ updateTime: 1 }, { background: true }); DatasetDataSchema.index({ updateTime: 1 }, { background: true });
} catch (error) { } catch (error) {
console.log(error); console.log(error);

View File

@ -93,6 +93,7 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
{ {
teamId, teamId,
datasetId: { $in: datasetIds }, datasetId: { $in: datasetIds },
collectionId: { $in: results.map((item) => item.collectionId) },
'indexes.dataId': { $in: results.map((item) => item.id?.trim()) } 'indexes.dataId': { $in: results.map((item) => item.id?.trim()) }
}, },
'datasetId collectionId q a chunkIndex indexes' 'datasetId collectionId q a chunkIndex indexes'

View File

@ -6,7 +6,6 @@ import type {
} from '@fastgpt/global/core/dataset/api.d'; } from '@fastgpt/global/core/dataset/api.d';
import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constants'; import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constants';
import { simpleText } from '@fastgpt/global/common/string/tools'; import { simpleText } from '@fastgpt/global/common/string/tools';
import { countPromptTokens } from '@fastgpt/global/common/string/tiktoken';
import { ClientSession } from '../../../common/mongo'; import { ClientSession } from '../../../common/mongo';
import { getLLMModel, getVectorModel } from '../../ai/model'; import { getLLMModel, getVectorModel } from '../../ai/model';
import { addLog } from '../../../common/system/log'; import { addLog } from '../../../common/system/log';

View File

@ -92,8 +92,8 @@ const TrainingDataSchema = new Schema({
}); });
try { try {
// lock training data; delete training data // lock training data(teamId); delete training data
TrainingDataSchema.index({ teamId: 1, collectionId: 1 }); TrainingDataSchema.index({ teamId: 1, datasetId: 1 });
// get training data and sort // get training data and sort
TrainingDataSchema.index({ mode: 1, lockTime: 1, weight: -1 }); TrainingDataSchema.index({ mode: 1, lockTime: 1, weight: -1 });
TrainingDataSchema.index({ expireAt: 1 }, { expireAfterSeconds: 7 * 24 * 60 * 60 }); // 7 days TrainingDataSchema.index({ expireAt: 1 }, { expireAfterSeconds: 7 * 24 * 60 * 60 }); // 7 days

View File

@ -42,6 +42,9 @@ const TeamSchema = new Schema({
}, },
appid: { appid: {
type: String type: String
},
pat: {
type: String
} }
} }
}); });

View File

@ -107,6 +107,7 @@ export const iconPaths = {
'core/dataset/tableCollection': () => import('./icons/core/dataset/tableCollection.svg'), 'core/dataset/tableCollection': () => import('./icons/core/dataset/tableCollection.svg'),
'core/dataset/websiteDataset': () => import('./icons/core/dataset/websiteDataset.svg'), 'core/dataset/websiteDataset': () => import('./icons/core/dataset/websiteDataset.svg'),
'core/modules/basicNode': () => import('./icons/core/modules/basicNode.svg'), 'core/modules/basicNode': () => import('./icons/core/modules/basicNode.svg'),
'core/modules/fixview': () => import('./icons/core/modules/fixview.svg'),
'core/modules/flowLight': () => import('./icons/core/modules/flowLight.svg'), 'core/modules/flowLight': () => import('./icons/core/modules/flowLight.svg'),
'core/modules/previewLight': () => import('./icons/core/modules/previewLight.svg'), 'core/modules/previewLight': () => import('./icons/core/modules/previewLight.svg'),
'core/modules/systemPlugin': () => import('./icons/core/modules/systemPlugin.svg'), 'core/modules/systemPlugin': () => import('./icons/core/modules/systemPlugin.svg'),

View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 30">
<path
d="M3.692 4.63c0-.53.4-.938.939-.938h5.215V0H4.708C2.13 0 0 2.054 0 4.63v5.216h3.692V4.631zM27.354 0h-5.2v3.692h5.17c.53 0 .984.4.984.939v5.215H32V4.631A4.624 4.624 0 0027.354 0zm.954 24.83c0 .532-.4.94-.939.94h-5.215v3.768h5.215c2.577 0 4.631-2.13 4.631-4.707v-5.139h-3.692v5.139zm-23.677.94c-.531 0-.939-.4-.939-.94v-5.138H0v5.139c0 2.577 2.13 4.707 4.708 4.707h5.138V25.77H4.631z">
</path>
</svg>

After

Width:  |  Height:  |  Size: 482 B

View File

@ -1,4 +1,4 @@
### FastGPT V4.7 ### FastGPT V4.7.1
1. 新增 - 语音输入完整配置。支持选择是否打开语音输入(包括分享页面),支持语音输入后自动发送,支持语音输入后自动语音播放(流式)。 1. 新增 - 语音输入完整配置。支持选择是否打开语音输入(包括分享页面),支持语音输入后自动发送,支持语音输入后自动语音播放(流式)。
2. 新增 - Pptx 和 xlsx 文件读取。但所有文件读取都放服务端,会消耗更多的服务器资源,以及无法在上传时预览更多内容。 2. 新增 - Pptx 和 xlsx 文件读取。但所有文件读取都放服务端,会消耗更多的服务器资源,以及无法在上传时预览更多内容。

View File

@ -33,7 +33,7 @@ function embedChatbot() {
ChatBtnDiv.draggable = false; ChatBtnDiv.draggable = false;
const iframe = document.createElement('iframe'); const iframe = document.createElement('iframe');
iframe.allow = 'fullscreen;microphone'; iframe.allow = '*';
iframe.referrerPolicy = 'no-referrer'; iframe.referrerPolicy = 'no-referrer';
iframe.title = 'FastGPT Chat Window'; iframe.title = 'FastGPT Chat Window';
iframe.id = chatWindowId; iframe.id = chatWindowId;

View File

@ -230,7 +230,8 @@
"Amount": "{{amount}}{{unit}}" "Amount": "{{amount}}{{unit}}"
}, },
"speech": { "speech": {
"error tip": "Speech Failed" "error tip": "Speech Failed",
"not support": "Your browser does not support voice input"
}, },
"system": { "system": {
"Commercial version function": "Commercial version special function", "Commercial version function": "Commercial version special function",
@ -250,7 +251,7 @@
}, },
"core": { "core": {
"Chat": "Chat", "Chat": "Chat",
"Chat test": "Chat test", "Chat test": "Test",
"Max Token": "MaxTokens", "Max Token": "MaxTokens",
"Start chat": "Start chat", "Start chat": "Start chat",
"Total chars": "Total chars: {{total}}", "Total chars": "Total chars: {{total}}",
@ -423,6 +424,7 @@
"Converting to text": "Converting to text...", "Converting to text": "Converting to text...",
"Custom History Title": "Custom history title", "Custom History Title": "Custom history title",
"Custom History Title Description": "If set to empty, chat history will be followed automatically.", "Custom History Title Description": "If set to empty, chat history will be followed automatically.",
"Debug test": "Test",
"Exit Chat": "Exit", "Exit Chat": "Exit",
"Failed to initialize chat": "Failed to initialize chat", "Failed to initialize chat": "Failed to initialize chat",
"Feedback Failed": "Feedback Failed", "Feedback Failed": "Feedback Failed",

View File

@ -230,7 +230,8 @@
"Amount": "{{amount}}{{unit}}" "Amount": "{{amount}}{{unit}}"
}, },
"speech": { "speech": {
"error tip": "语音转文字失败" "error tip": "语音转文字失败",
"not support": "您的浏览器不支持语音输入"
}, },
"system": { "system": {
"Commercial version function": "商业版特有功能", "Commercial version function": "商业版特有功能",
@ -250,7 +251,7 @@
}, },
"core": { "core": {
"Chat": "对话", "Chat": "对话",
"Chat test": "测试对话", "Chat test": "测试",
"Max Token": "单条数据上限", "Max Token": "单条数据上限",
"Start chat": "立即对话", "Start chat": "立即对话",
"Total chars": "总字数: {{total}}", "Total chars": "总字数: {{total}}",
@ -423,6 +424,7 @@
"Converting to text": "正在转换为文本...", "Converting to text": "正在转换为文本...",
"Custom History Title": "自定义历史记录标题", "Custom History Title": "自定义历史记录标题",
"Custom History Title Description": "如果设置为空,会自动跟随聊天记录。", "Custom History Title Description": "如果设置为空,会自动跟随聊天记录。",
"Debug test": "调试预览",
"Exit Chat": "退出聊天", "Exit Chat": "退出聊天",
"Failed to initialize chat": "初始化聊天失败", "Failed to initialize chat": "初始化聊天失败",
"Feedback Failed": "提交反馈异常", "Feedback Failed": "提交反馈异常",
@ -608,8 +610,7 @@
"success": "开始同步" "success": "开始同步"
} }
}, },
"training": { "training": {}
}
}, },
"data": { "data": {
"Auxiliary Data": "辅助数据", "Auxiliary Data": "辅助数据",
@ -996,6 +997,7 @@
"Tool module": "工具", "Tool module": "工具",
"UnKnow Module": "未知模块", "UnKnow Module": "未知模块",
"User guide": "用户引导", "User guide": "用户引导",
"App system setting": "系统配置",
"http body placeholder": "与APIFox相同的语法", "http body placeholder": "与APIFox相同的语法",
"textEditor": "文本加工", "textEditor": "文本加工",
"textEditor intro": "可对固定或传入的文本进行加工后输出,非字符串类型数据最终会转成字符串类型。" "textEditor intro": "可对固定或传入的文本进行加工后输出,非字符串类型数据最终会转成字符串类型。"

View File

@ -94,7 +94,7 @@ const ChatItem = ({
/* AI */ /* AI */
return ( return (
<Flex flexDirection={'column'} gap={2}> <Flex flexDirection={'column'} key={chat.dataId} gap={2}>
{chat.value.map((value, i) => { {chat.value.map((value, i) => {
const key = `${chat.dataId}-ai-${i}`; const key = `${chat.dataId}-ai-${i}`;
if (value.text) { if (value.text) {

View File

@ -8,6 +8,7 @@ import React, {
useImperativeHandle, useImperativeHandle,
ForwardedRef ForwardedRef
} from 'react'; } from 'react';
import { SmallCloseIcon } from '@chakra-ui/icons';
import { Box, Flex, IconButton } from '@chakra-ui/react'; import { Box, Flex, IconButton } from '@chakra-ui/react';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import { streamFetch } from '@/web/common/api/fetch'; import { streamFetch } from '@/web/common/api/fetch';
@ -18,6 +19,7 @@ import type { ComponentRef, StartChatFnProps } from '@/components/ChatBox/type.d
import { getGuideModule } from '@fastgpt/global/core/module/utils'; import { getGuideModule } from '@fastgpt/global/core/module/utils';
import { checkChatSupportSelectFileByModules } from '@/web/core/chat/utils'; import { checkChatSupportSelectFileByModules } from '@/web/core/chat/utils';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants'; import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import { useTranslation } from 'next-i18next';
export type ChatTestComponentRef = { export type ChatTestComponentRef = {
resetChatTest: () => void; resetChatTest: () => void;
@ -35,6 +37,7 @@ const ChatTest = (
}, },
ref: ForwardedRef<ChatTestComponentRef> ref: ForwardedRef<ChatTestComponentRef>
) => { ) => {
const { t } = useTranslation();
const ChatBoxRef = useRef<ComponentRef>(null); const ChatBoxRef = useRef<ComponentRef>(null);
const { userInfo } = useUserStore(); const { userInfo } = useUserStore();
const isOpen = useMemo(() => modules && modules.length > 0, [modules]); const isOpen = useMemo(() => modules && modules.length > 0, [modules]);
@ -100,9 +103,9 @@ const ChatTest = (
> >
<Flex py={4} px={5} whiteSpace={'nowrap'}> <Flex py={4} px={5} whiteSpace={'nowrap'}>
<Box fontSize={'xl'} fontWeight={'bold'} flex={1}> <Box fontSize={'xl'} fontWeight={'bold'} flex={1}>
{t('core.chat.Debug test')}
</Box> </Box>
<MyTooltip label={'重置'}> <MyTooltip label={t('core.chat.Restart')}>
<IconButton <IconButton
className="chat" className="chat"
size={'smSquare'} size={'smSquare'}
@ -117,6 +120,16 @@ const ChatTest = (
}} }}
/> />
</MyTooltip> </MyTooltip>
<MyTooltip label={t('common.Close')}>
<IconButton
ml={[3, 6]}
icon={<SmallCloseIcon fontSize={'22px'} />}
variant={'grayBase'}
size={'smSquare'}
aria-label={''}
onClick={onClose}
/>
</MyTooltip>
</Flex> </Flex>
<Box flex={1}> <Box flex={1}>
<ChatBox <ChatBox
@ -132,7 +145,7 @@ const ChatTest = (
/> />
</Box> </Box>
</Flex> </Flex>
<Box {/* <Box
zIndex={2} zIndex={2}
display={isOpen ? 'block' : 'none'} display={isOpen ? 'block' : 'none'}
position={'fixed'} position={'fixed'}
@ -141,7 +154,7 @@ const ChatTest = (
bottom={0} bottom={0}
right={0} right={0}
onClick={onClose} onClick={onClose}
/> /> */}
</> </>
); );
}; };

View File

@ -166,7 +166,7 @@ export const FlowProvider = ({
}, [nodes]); }, [nodes]);
const onFixView = useCallback(() => { const onFixView = useCallback(() => {
const btn = document.querySelector('.react-flow__controls-fitview') as HTMLButtonElement; const btn = document.querySelector('.custom-workflow-fix_view') as HTMLButtonElement;
setTimeout(() => { setTimeout(() => {
btn && btn.click(); btn && btn.click();

View File

@ -15,8 +15,10 @@ import NodeCard from '../render/NodeCard';
import type { VariableItemType } from '@fastgpt/global/core/app/type.d'; import type { VariableItemType } from '@fastgpt/global/core/app/type.d';
import QGSwitch from '@/components/core/app/QGSwitch'; import QGSwitch from '@/components/core/app/QGSwitch';
import TTSSelect from '@/components/core/app/TTSSelect'; import TTSSelect from '@/components/core/app/TTSSelect';
import WhisperConfig from '@/components/core/app/WhisperConfig';
import { splitGuideModule } from '@fastgpt/global/core/module/utils'; import { splitGuideModule } from '@fastgpt/global/core/module/utils';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { TTSTypeEnum } from '@/constants/app';
const NodeUserGuide = ({ data, selected }: NodeProps<FlowModuleItemType>) => { const NodeUserGuide = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
const theme = useTheme(); const theme = useTheme();
@ -31,6 +33,9 @@ const NodeUserGuide = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
<Box pt={3} borderTop={theme.borders.base}> <Box pt={3} borderTop={theme.borders.base}>
<TTSGuide data={data} /> <TTSGuide data={data} />
</Box> </Box>
<Box mt={3} pt={3} borderTop={theme.borders.base}>
<WhisperGuide data={data} />
</Box>
<Box mt={3} pt={3} borderTop={theme.borders.base}> <Box mt={3} pt={3} borderTop={theme.borders.base}>
<QuestionGuide data={data} /> <QuestionGuide data={data} />
</Box> </Box>
@ -164,3 +169,26 @@ function TTSGuide({ data }: { data: FlowModuleItemType }) {
/> />
); );
} }
function WhisperGuide({ data }: { data: FlowModuleItemType }) {
const { inputs, moduleId } = data;
const { ttsConfig, whisperConfig } = splitGuideModule({ inputs } as ModuleItemType);
return (
<WhisperConfig
isOpenAudio={ttsConfig.type !== TTSTypeEnum.none}
value={whisperConfig}
onChange={(e) => {
onChangeNode({
moduleId,
key: ModuleInputKeyEnum.whisper,
type: 'updateInput',
value: {
...inputs.find((item) => item.key === ModuleInputKeyEnum.whisper),
value: e
}
});
}}
/>
);
}

View File

@ -261,7 +261,7 @@ const NodeCard = (props: Props) => {
}} }}
> >
{Header} {Header}
{children} <Box className="nowheel">{children}</Box>
{RenderModal} {RenderModal}
</Box> </Box>
); );

View File

@ -3,8 +3,11 @@ import ReactFlow, {
Background, Background,
Connection, Connection,
Controls, Controls,
ControlButton,
MiniMap,
NodeProps, NodeProps,
ReactFlowProvider ReactFlowProvider,
useReactFlow
} from 'reactflow'; } from 'reactflow';
import { Box, Flex, IconButton, useDisclosure } from '@chakra-ui/react'; import { Box, Flex, IconButton, useDisclosure } from '@chakra-ui/react';
import { SmallCloseIcon } from '@chakra-ui/icons'; import { SmallCloseIcon } from '@chakra-ui/icons';
@ -20,6 +23,8 @@ import 'reactflow/dist/style.css';
import { useToast } from '@fastgpt/web/hooks/useToast'; import { useToast } from '@fastgpt/web/hooks/useToast';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { FlowModuleItemType } from '@fastgpt/global/core/module/type'; import { FlowModuleItemType } from '@fastgpt/global/core/module/type';
import MyIcon from '@fastgpt/web/components/common/Icon';
import MyTooltip from '@/components/MyTooltip';
const NodeSimple = dynamic(() => import('./components/nodes/NodeSimple')); const NodeSimple = dynamic(() => import('./components/nodes/NodeSimple'));
const nodeTypes: Record<`${FlowNodeTypeEnum}`, any> = { const nodeTypes: Record<`${FlowNodeTypeEnum}`, any> = {
@ -54,19 +59,10 @@ const edgeTypes = {
const Container = React.memo(function Container() { const Container = React.memo(function Container() {
const { toast } = useToast(); const { toast } = useToast();
const { t } = useTranslation(); const { t } = useTranslation();
const { reactFlowWrapper, nodes, onNodesChange, edges, onEdgesChange, onConnect } = const { reactFlowWrapper, nodes, onNodesChange, edges, onEdgesChange, onConnect } =
useFlowProviderStore(); useFlowProviderStore();
const memoRenderTools = useMemo(
() => (
<>
<Background />
<Controls position={'bottom-right'} style={{ display: 'flex' }} showInteractive={false} />
</>
),
[]
);
const customOnConnect = useCallback( const customOnConnect = useCallback(
(connect: Connection) => { (connect: Connection) => {
if (!connect.sourceHandle || !connect.targetHandle) { if (!connect.sourceHandle || !connect.targetHandle) {
@ -105,7 +101,7 @@ const Container = React.memo(function Container() {
onEdgesChange={onEdgesChange} onEdgesChange={onEdgesChange}
onConnect={customOnConnect} onConnect={customOnConnect}
> >
{memoRenderTools} <FlowController />
</ReactFlow> </ReactFlow>
); );
}); });
@ -168,3 +164,40 @@ const Flow = ({ Header, ...data }: { Header: React.ReactNode }) => {
}; };
export default React.memo(Flow); export default React.memo(Flow);
const FlowController = React.memo(function FlowController() {
const { fitView } = useReactFlow();
return (
<>
<MiniMap
style={{
height: 78,
width: 126,
marginBottom: 35
}}
pannable
/>
<Controls
position={'bottom-right'}
style={{
display: 'flex',
marginBottom: 5,
background: 'white',
borderRadius: '6px',
overflow: 'hidden',
boxShadow:
'0px 0px 1px 0px rgba(19, 51, 107, 0.20), 0px 12px 16px -4px rgba(19, 51, 107, 0.20)'
}}
showInteractive={false}
showFitView={false}
>
<MyTooltip label={'页面居中'}>
<ControlButton className="custom-workflow-fix_view" onClick={() => fitView()}>
<MyIcon name={'core/modules/fixview'} w={'14px'} />
</ControlButton>
</MyTooltip>
</Controls>
<Background />
</>
);
});

View File

@ -18,7 +18,8 @@ import { getDocPath } from '@/web/common/system/doc';
const LafAccountModal = ({ const LafAccountModal = ({
defaultData = { defaultData = {
token: '', token: '',
appid: '' appid: '',
pat: ''
}, },
onClose onClose
}: { }: {
@ -140,7 +141,7 @@ const LafAccountModal = ({
onResetForm(); onResetForm();
putUpdateTeam({ putUpdateTeam({
teamId: userInfo?.team.teamId || '', teamId: userInfo?.team.teamId || '',
lafAccount: { token: '', appid: '' } lafAccount: { token: '', appid: '', pat: '' }
}); });
}} }}
> >

View File

@ -0,0 +1,29 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { PgClient } from '@fastgpt/service/common/vectorStore/pg';
/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
await authCert({ req, authRoot: true });
// 删除索引
await PgClient.query(`DROP INDEX IF EXISTS team_dataset_index;`);
await PgClient.query(`DROP INDEX IF EXISTS team_collection_index;`);
await PgClient.query(`DROP INDEX IF EXISTS team_id_index;`);
jsonRes(res, {
message: 'success'
});
} catch (error) {
console.log(error);
jsonRes(res, {
code: 500,
error
});
}
}

View File

@ -29,7 +29,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
teamId, teamId,
datasetId: collection.datasetId._id, datasetId: collection.datasetId._id,
collectionId, collectionId,
fields: '_id teamId fileId metadata' fields: '_id teamId datasetId fileId metadata'
}); });
// delete // delete

View File

@ -70,6 +70,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
// Duplicate data check // Duplicate data check
await hasSameValue({ await hasSameValue({
teamId, teamId,
datasetId,
collectionId, collectionId,
q: formatQ, q: formatQ,
a: formatA a: formatA

View File

@ -6,6 +6,7 @@ import type { GetDatasetDataListProps } from '@/global/core/api/datasetReq';
import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/dataset'; import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/dataset';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema'; import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { PagingData } from '@/types'; import { PagingData } from '@/types';
import { replaceRegChars } from '@fastgpt/global/common/string/tools';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) { export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { try {
@ -28,7 +29,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
per: 'r' per: 'r'
}); });
searchText = searchText.replace(/'/g, ''); searchText = replaceRegChars(searchText).replace(/'/g, '');
const match = { const match = {
teamId, teamId,

View File

@ -9,6 +9,7 @@ import { pushWhisperUsage } from '@/service/support/wallet/usage/push';
import { authChatCert } from '@/service/support/permission/auth/chat'; import { authChatCert } from '@/service/support/permission/auth/chat';
import { MongoApp } from '@fastgpt/service/core/app/schema'; import { MongoApp } from '@fastgpt/service/core/app/schema';
import { getGuideModule, splitGuideModule } from '@fastgpt/global/core/module/utils'; import { getGuideModule, splitGuideModule } from '@fastgpt/global/core/module/utils';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
const upload = getUploadModel({ const upload = getUploadModel({
maxSize: 2 maxSize: 2
@ -20,15 +21,16 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
try { try {
const { const {
file, file,
data: { appId, duration, teamId: spaceTeamId, teamToken } data: { appId, duration, shareId, outLinkUid, teamId: spaceTeamId, teamToken }
} = await upload.doUpload<{ } = await upload.doUpload<
OutLinkChatAuthProps & {
appId: string; appId: string;
duration: number; duration: number;
shareId?: string; }
teamId?: string; >(req, res);
teamToken?: string;
}>(req, res);
req.body.shareId = shareId;
req.body.outLinkUid = outLinkUid;
req.body.teamId = spaceTeamId; req.body.teamId = spaceTeamId;
req.body.teamToken = teamToken; req.body.teamToken = teamToken;

View File

@ -1,6 +1,5 @@
import React, { useCallback, useRef, useState } from 'react'; import React, { useCallback, useRef, useState } from 'react';
import { Box, Flex, IconButton, useTheme, useDisclosure } from '@chakra-ui/react'; import { Box, Flex, IconButton, useTheme, useDisclosure, Button } from '@chakra-ui/react';
import { SmallCloseIcon } from '@chakra-ui/icons';
import { ModuleItemType } from '@fastgpt/global/core/module/type'; import { ModuleItemType } from '@fastgpt/global/core/module/type';
import { useRequest } from '@fastgpt/web/hooks/useRequest'; import { useRequest } from '@fastgpt/web/hooks/useRequest';
import { AppSchema } from '@fastgpt/global/core/app/type.d'; import { AppSchema } from '@fastgpt/global/core/app/type.d';
@ -18,6 +17,7 @@ import { useAppStore } from '@/web/core/app/store/useAppStore';
import { useToast } from '@fastgpt/web/hooks/useToast'; import { useToast } from '@fastgpt/web/hooks/useToast';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { getErrText } from '@fastgpt/global/common/error/utils'; import { getErrText } from '@fastgpt/global/common/error/utils';
import MyMenu from '@/components/MyMenu';
const ImportSettings = dynamic(() => import('@/components/core/module/Flow/ImportSettings')); const ImportSettings = dynamic(() => import('@/components/core/module/Flow/ImportSettings'));
@ -143,48 +143,36 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
{app.name} {app.name}
</Box> </Box>
<MyTooltip label={t('app.Import Configs')}> <MyMenu
Button={
<IconButton <IconButton
mr={[3, 6]} mr={[3, 5]}
size={'smSquare'} icon={<MyIcon name={'more'} w={'14px'} p={2} />}
icon={<MyIcon name={'common/importLight'} w={['14px', '16px']} />} aria-label={''}
size={'sm'}
variant={'whitePrimary'} variant={'whitePrimary'}
aria-label={'save'}
onClick={onOpenImport}
/> />
</MyTooltip> }
<MyTooltip label={t('app.Export Configs')}> menuList={[
<IconButton { label: t('app.Import Configs'), icon: 'common/importLight', onClick: onOpenImport },
mr={[3, 6]} {
icon={<MyIcon name={'export'} w={['14px', '16px']} />} label: t('app.Export Configs'),
size={'smSquare'} icon: 'export',
variant={'whitePrimary'} onClick: async () => {
aria-label={'save'}
onClick={async () => {
const modules = await flow2ModulesAndCheck(); const modules = await flow2ModulesAndCheck();
if (modules) { if (modules) {
copyData(filterExportModules(modules), t('app.Export Config Successful')); copyData(filterExportModules(modules), t('app.Export Config Successful'));
} }
}} }
}
]}
/> />
</MyTooltip>
{testModules ? ( {!testModules && (
<IconButton <Button
mr={[3, 6]} mr={[3, 5]}
icon={<SmallCloseIcon fontSize={'25px'} />} size={'sm'}
variant={'whitePrimary'} leftIcon={<MyIcon name={'core/chat/chatLight'} w={['14px', '16px']} />}
size={'smSquare'}
aria-label={''}
onClick={() => setTestModules(undefined)}
/>
) : (
<MyTooltip label={t('core.Chat test')}>
<IconButton
mr={[3, 6]}
icon={<MyIcon name={'core/chat/chatLight'} w={['14px', '16px']} />}
size={'smSquare'}
aria-label={'save'}
variant={'whitePrimary'} variant={'whitePrimary'}
onClick={async () => { onClick={async () => {
const modules = await flow2ModulesAndCheck(); const modules = await flow2ModulesAndCheck();
@ -192,24 +180,24 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
setTestModules(modules); setTestModules(modules);
} }
}} }}
/> >
</MyTooltip> {t('core.Chat test')}
</Button>
)} )}
<MyTooltip label={t('common.Save')}> <Button
<IconButton size={'sm'}
icon={<MyIcon name={'common/saveFill'} w={['14px', '16px']} />}
size={'smSquare'}
isLoading={isSaving} isLoading={isSaving}
aria-label={'save'} leftIcon={<MyIcon name={'common/saveFill'} w={['14px', '16px']} />}
onClick={async () => { onClick={async () => {
const modules = await flow2ModulesAndCheck(); const modules = await flow2ModulesAndCheck();
if (modules) { if (modules) {
onclickSave(modules); onclickSave(modules);
} }
}} }}
/> >
</MyTooltip> {t('common.Save')}
</Button>
</Flex> </Flex>
{isOpenImport && <ImportSettings onClose={onCloseImport} />} {isOpenImport && <ImportSettings onClose={onCloseImport} />}
<ConfirmModal <ConfirmModal

View File

@ -88,7 +88,7 @@ const SelectUsingWayModal = ({ share, onClose }: { share: OutLinkSchema; onClose
src="${linkUrl}" src="${linkUrl}"
style="width: 100%; height: 100%;" style="width: 100%; height: 100%;"
frameborder="0" frameborder="0"
allow="microphone" allow="*"
/>` />`
}, },
[UsingWayEnum.script]: { [UsingWayEnum.script]: {

View File

@ -1,7 +1,7 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { useChatBox } from '@/components/ChatBox/hooks/useChatBox'; import { useChatBox } from '@/components/ChatBox/hooks/useChatBox';
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d'; import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
import { Menu, MenuButton, MenuList, MenuItem, Box, IconButton } from '@chakra-ui/react'; import { Box, IconButton } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';

View File

@ -1,5 +1,5 @@
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { Box, Flex, IconButton, useTheme, useDisclosure } from '@chakra-ui/react'; import { Box, Flex, IconButton, useTheme, useDisclosure, Button } from '@chakra-ui/react';
import { PluginItemSchema } from '@fastgpt/global/core/plugin/type'; import { PluginItemSchema } from '@fastgpt/global/core/plugin/type';
import { useRequest } from '@fastgpt/web/hooks/useRequest'; import { useRequest } from '@fastgpt/web/hooks/useRequest';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
@ -14,6 +14,7 @@ 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 { useToast } from '@fastgpt/web/hooks/useToast'; import { useToast } from '@fastgpt/web/hooks/useToast';
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants'; import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import MyMenu from '@/components/MyMenu';
const ImportSettings = dynamic(() => import('@/components/core/module/Flow/ImportSettings')); const ImportSettings = dynamic(() => import('@/components/core/module/Flow/ImportSettings'));
const PreviewPlugin = dynamic(() => import('./Preview')); const PreviewPlugin = dynamic(() => import('./Preview'));
@ -137,38 +138,37 @@ const Header = ({ plugin, onClose }: Props) => {
}} }}
/> />
</MyTooltip> </MyTooltip>
<Box ml={[3, 6]} fontSize={['md', '2xl']} flex={1}> <Box ml={[3, 5]} fontSize={['md', '2xl']} flex={1}>
{plugin.name} {plugin.name}
</Box> </Box>
<MyTooltip label={t('app.Import Configs')}> <MyMenu
Button={
<IconButton <IconButton
mr={[3, 6]} mr={[3, 5]}
icon={<MyIcon name={'common/importLight'} w={['14px', '16px']} />} icon={<MyIcon name={'more'} w={'14px'} p={2} />}
aria-label={''}
size={'sm'}
variant={'whitePrimary'} variant={'whitePrimary'}
size={'smSquare'}
aria-label={'save'}
onClick={onOpenImport}
/> />
</MyTooltip> }
<MyTooltip label={t('app.Export Configs')}> menuList={[
<IconButton { label: t('app.Import Configs'), icon: 'common/importLight', onClick: onOpenImport },
mr={[3, 6]} {
icon={<MyIcon name={'export'} w={['14px', '16px']} />} label: t('app.Export Configs'),
size={'smSquare'} icon: 'export',
variant={'whitePrimary'} onClick: async () => {
aria-label={'save'}
onClick={async () => {
const modules = await flow2ModulesAndCheck(); const modules = await flow2ModulesAndCheck();
if (modules) { if (modules) {
copyData(filterExportModules(modules), t('app.Export Config Successful')); copyData(filterExportModules(modules), t('app.Export Config Successful'));
} }
}} }
}
]}
/> />
</MyTooltip>
<MyTooltip label={t('module.Preview Plugin')}> <MyTooltip label={t('module.Preview Plugin')}>
<IconButton <IconButton
mr={[3, 6]} mr={[3, 5]}
icon={<MyIcon name={'core/modules/previewLight'} w={['14px', '16px']} />} icon={<MyIcon name={'core/modules/previewLight'} w={['14px', '16px']} />}
size={'smSquare'} size={'smSquare'}
aria-label={'save'} aria-label={'save'}
@ -181,20 +181,19 @@ const Header = ({ plugin, onClose }: Props) => {
}} }}
/> />
</MyTooltip> </MyTooltip>
<MyTooltip label={t('module.Save Config')}> <Button
<IconButton size={'sm'}
icon={<MyIcon name={'save'} w={['14px', '16px']} />}
size={'smSquare'}
isLoading={isLoading} isLoading={isLoading}
aria-label={'save'} leftIcon={<MyIcon name={'common/saveFill'} w={['14px', '16px']} />}
onClick={async () => { onClick={async () => {
const modules = await flow2ModulesAndCheck(); const modules = await flow2ModulesAndCheck();
if (modules) { if (modules) {
onclickSave(modules); onclickSave(modules);
} }
}} }}
/> >
</MyTooltip> {t('common.Save')}
</Button>
</Flex> </Flex>
{isOpenImport && <ImportSettings onClose={onCloseImport} />} {isOpenImport && <ImportSettings onClose={onCloseImport} />}
{!!previewModules && ( {!!previewModules && (

View File

@ -2,6 +2,7 @@ import {
delFileByFileIdList, delFileByFileIdList,
getGFSCollection getGFSCollection
} from '@fastgpt/service/common/file/gridfs/controller'; } from '@fastgpt/service/common/file/gridfs/controller';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
import { addLog } from '@fastgpt/service/common/system/log'; import { addLog } from '@fastgpt/service/common/system/log';
import { import {
deleteDatasetDataVector, deleteDatasetDataVector,
@ -72,15 +73,19 @@ export async function checkInvalidDatasetData(start: Date, end: Date) {
$lte: end $lte: end
} }
}, },
'_id teamId collectionId' '_id teamId datasetId collectionId'
).lean(); ).lean();
// 2. 合并所有的collectionId // 2. 合并所有的collectionId
const map = new Map<string, { teamId: string; collectionId: string }>(); const map = new Map<string, { teamId: string; datasetId: string; collectionId: string }>();
for (const item of rows) { for (const item of rows) {
const collectionId = String(item.collectionId); const collectionId = String(item.collectionId);
if (!map.has(collectionId)) { if (!map.has(collectionId)) {
map.set(collectionId, { teamId: item.teamId, collectionId }); map.set(collectionId, {
teamId: item.teamId,
datasetId: item.datasetId,
collectionId
});
} }
} }
const list = Array.from(map.values()); const list = Array.from(map.values());
@ -92,21 +97,28 @@ export async function checkInvalidDatasetData(start: Date, end: Date) {
// 3. 查看该collection是否存在不存在则删除对应的数据 // 3. 查看该collection是否存在不存在则删除对应的数据
const collection = await MongoDatasetCollection.findOne({ _id: item.collectionId }); const collection = await MongoDatasetCollection.findOne({ _id: item.collectionId });
if (!collection) { if (!collection) {
const result = await Promise.all([ await mongoSessionRun(async (session) => {
MongoDatasetTraining.deleteMany({ await MongoDatasetTraining.deleteMany(
{
teamId: item.teamId, teamId: item.teamId,
collectionId: item.collectionId collectionId: item.collectionId
}), },
MongoDatasetData.deleteMany({ { session }
);
await MongoDatasetData.deleteMany(
{
teamId: item.teamId, teamId: item.teamId,
collectionId: item.collectionId collectionId: item.collectionId
}), },
deleteDatasetDataVector({ { session }
);
await deleteDatasetDataVector({
teamId: item.teamId, teamId: item.teamId,
collectionIds: [String(item.collectionId)] datasetIds: [item.datasetId],
}) collectionIds: [item.collectionId]
]); });
console.log(result); });
console.log('collection is not found', item); console.log('collection is not found', item);
continue; continue;
} }

View File

@ -5,17 +5,20 @@ import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
*/ */
export async function hasSameValue({ export async function hasSameValue({
teamId, teamId,
datasetId,
collectionId, collectionId,
q, q,
a = '' a = ''
}: { }: {
teamId: string; teamId: string;
datasetId: string;
collectionId: string; collectionId: string;
q: string; q: string;
a?: string; a?: string;
}) { }) {
const count = await MongoDatasetData.countDocuments({ const count = await MongoDatasetData.countDocuments({
teamId, teamId,
datasetId,
collectionId, collectionId,
q, q,
a a

View File

@ -5,6 +5,8 @@ import axios, {
AxiosProgressEvent AxiosProgressEvent
} from 'axios'; } from 'axios';
import { useUserStore } from '@/web/support/user/useUserStore'; import { useUserStore } from '@/web/support/user/useUserStore';
import { putUpdateTeam } from '@/web/support/user/team/api';
import { LafAccountType } from '@fastgpt/global/support/user/team/type';
interface ConfigType { interface ConfigType {
headers?: { [key: string]: string }; headers?: { [key: string]: string };
@ -72,21 +74,33 @@ function responseSuccess(response: AxiosResponse<ResponseDataType>) {
/** /**
* *
*/ */
function checkRes(data: ResponseDataType) { function checkRes(
if (data === undefined) { res: ResponseDataType,
console.log('error->', data, 'data is empty'); url: string,
data: any,
requestConfig: ConfigType,
method: Method
) {
if (res === undefined) {
console.log('error->', res, 'res is empty');
return Promise.reject('服务器异常'); return Promise.reject('服务器异常');
} else if (data.error) { } else if (res.error) {
return responseError(data.error); return responseError(data.error, url, data, requestConfig, method);
} }
return data.data; return res.data;
} }
/** /**
* *
*/ */
function responseError(err: any) { function responseError(
err: any,
url: string,
data: any,
requestConfig: ConfigType,
method: Method
) {
console.log('error->', '请求错误', err); console.log('error->', '请求错误', err);
if (!err) { if (!err) {
@ -97,6 +111,25 @@ function responseError(err: any) {
} }
if (err?.response?.data) { if (err?.response?.data) {
const code = err?.response?.data?.statusCode;
if (code === 401) {
return POST<string>(`/v1/auth/pat2token`, {
pat: useUserStore.getState().userInfo?.team?.lafAccount?.pat
})
.then((res) => {
putUpdateTeam({
teamId: useUserStore.getState().userInfo?.team.teamId || '',
lafAccount: {
...useUserStore.getState().userInfo?.team?.lafAccount,
token: res
} as LafAccountType
});
return request(url, data, requestConfig, method);
})
.catch((err) => {
return Promise.reject({ message: '登录凭证过期' });
});
}
return Promise.reject(err?.response?.data); return Promise.reject(err?.response?.data);
} }
return Promise.reject(err); return Promise.reject(err);
@ -115,12 +148,9 @@ instance.interceptors.request.use(startInterceptors, (err) => Promise.reject(err
/* 响应拦截 */ /* 响应拦截 */
instance.interceptors.response.use(responseSuccess, (err) => Promise.reject(err)); instance.interceptors.response.use(responseSuccess, (err) => Promise.reject(err));
function request( function request(url: string, data: any, requestConfig: ConfigType, method: Method): any {
url: string, const { cancelToken, maxQuantity, ...config } = requestConfig;
data: any,
{ cancelToken, maxQuantity, ...config }: ConfigType,
method: Method
): any {
/* 去空 */ /* 去空 */
for (const key in data) { for (const key in data) {
if (data[key] === null || data[key] === undefined) { if (data[key] === null || data[key] === undefined) {
@ -140,8 +170,8 @@ function request(
signal: cancelToken?.signal, signal: cancelToken?.signal,
...config // 用户自定义配置,可以覆盖前面的配置 ...config // 用户自定义配置,可以覆盖前面的配置
}) })
.then((res) => checkRes(res.data)) .then((res) => checkRes(res.data, url, data, requestConfig, method))
.catch((err) => responseError(err)) .catch((err) => responseError(err, url, data, requestConfig, method))
.finally(() => requestFinish({ url })); .finally(() => requestFinish({ url }));
} }

View File

@ -51,6 +51,12 @@ export const useSpeech = (props?: OutLinkChatAuthProps & { appId?: string }) =>
}, []); }, []);
const startSpeak = async (onFinish: (text: string) => void) => { const startSpeak = async (onFinish: (text: string) => void) => {
if (!navigator.mediaDevices.getUserMedia) {
return toast({
status: 'warning',
title: t('common.speech.not support')
});
}
try { try {
cancelWhisperSignal.current = false; cancelWhisperSignal.current = false;

View File

@ -17,7 +17,7 @@ export const appTemplates: (AppItemType & {
modules: [ modules: [
{ {
moduleId: 'userGuide', moduleId: 'userGuide',
name: 'core.module.template.User guide', name: 'core.module.template.App system setting',
avatar: '/imgs/module/userGuide.png', avatar: '/imgs/module/userGuide.png',
flowType: 'userGuide', flowType: 'userGuide',
position: { position: {
@ -300,7 +300,7 @@ export const appTemplates: (AppItemType & {
modules: [ modules: [
{ {
moduleId: 'userGuide', moduleId: 'userGuide',
name: 'core.module.template.User guide', name: 'core.module.template.App system setting',
avatar: '/imgs/module/userGuide.png', avatar: '/imgs/module/userGuide.png',
flowType: 'userGuide', flowType: 'userGuide',
position: { position: {
@ -616,7 +616,7 @@ export const appTemplates: (AppItemType & {
modules: [ modules: [
{ {
moduleId: 'userGuide', moduleId: 'userGuide',
name: 'core.module.template.User guide', name: 'core.module.template.App system setting',
intro: 'core.app.tip.userGuideTip', intro: 'core.app.tip.userGuideTip',
avatar: '/imgs/module/userGuide.png', avatar: '/imgs/module/userGuide.png',
flowType: 'userGuide', flowType: 'userGuide',
@ -1651,7 +1651,7 @@ export const appTemplates: (AppItemType & {
}, },
{ {
moduleId: 'q9equb', moduleId: 'q9equb',
name: 'core.module.template.User guide', name: 'core.module.template.App system setting',
flowType: 'userGuide', flowType: 'userGuide',
position: { position: {
x: -272.66416216517086, x: -272.66416216517086,

View File

@ -14,7 +14,7 @@ export async function postForm2Modules(data: AppSimpleEditFormType) {
function userGuideTemplate(formData: AppSimpleEditFormType): ModuleItemType[] { function userGuideTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
return [ return [
{ {
name: 'core.module.template.User guide', name: '系统配置',
flowType: FlowNodeTypeEnum.userGuide, flowType: FlowNodeTypeEnum.userGuide,
inputs: [ inputs: [
{ {

View File

@ -1,6 +1,4 @@
.react-flow__panel {
display: none;
}
.react-flow__panel.react-flow__attribution { .react-flow__panel.react-flow__attribution {
display: none !important; z-index: 0;
left: 0;
} }

View File

@ -1,4 +1,4 @@
import { GET, POST, PUT } from '@/web/common/api/lafRequest'; import { GET, POST } from '@/web/common/api/lafRequest';
export const postLafPat2Token = (pat: string) => POST<string>(`/v1/auth/pat2token`, { pat }); export const postLafPat2Token = (pat: string) => POST<string>(`/v1/auth/pat2token`, { pat });

View File

@ -92,7 +92,7 @@ docker run -d --name reranker -p 6006:6006 -e ACCESS_TOKEN=mytoken --gpus all re
version: "3" version: "3"
services: services:
reranker: reranker:
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/rerank:v0.2 image: registry.cn-hangzhou.aliyuncs.com/fastgpt/bge-rerank-base:v0.1
container_name: reranker container_name: reranker
# GPU运行环境如果宿主机未安装将deploy配置隐藏即可 # GPU运行环境如果宿主机未安装将deploy配置隐藏即可
deploy: deploy: