V4.6.9-first commit (#899)

* perf: insert mongo dataset data session

* perf: dataset data index

* remove delay

* rename bill schema

* rename bill record

* perf: bill table

* perf: prompt

* perf: sub plan

* change the usage count

* feat: usage bill

* publish usages

* doc

* 新增团队聊天功能 (#20)

* perf: doc

* feat 添加标签部分

feat 信息团队标签配置

feat 新增团队同步管理

feat team分享页面

feat 完成team分享页面

feat 实现模糊搜索

style 格式化

fix 修复迷糊匹配

style 样式修改

fix 团队标签功能修复

* fix 修复鉴权功能

* merge 合并代码

* fix 修复引用错误

* fix 修复pr问题

* fix 修复ts格式问题

---------

Co-authored-by: archer <545436317@qq.com>
Co-authored-by: liuxingwan <liuxingwan.lxw@alibaba-inc.com>

* update extra plan

* fix: ts

* format

* perf: bill field

* feat: standard plan

* fix: ts

* feat 个人账号页面修改 (#22)

* feat 添加标签部分

feat 信息团队标签配置

feat 新增团队同步管理

feat team分享页面

feat 完成team分享页面

feat 实现模糊搜索

style 格式化

fix 修复迷糊匹配

style 样式修改

fix 团队标签功能修复

* fix 修复鉴权功能

* merge 合并代码

* fix 修复引用错误

* fix 修复pr问题

* fix 修复ts格式问题

* feat 修改个人账号页

---------

Co-authored-by: liuxingwan <liuxingwan.lxw@alibaba-inc.com>

* sub plan page (#23)

* fix chunk index; error page text

* feat: dataset process Integral prediction

* feat: stand plan field

* feat: sub plan limit

* perf: index

* query extension

* perf: share link push app name

* perf: plan point unit

* perf: get sub plan

* perf: account page

* feat 新增套餐详情弹窗代码 (#24)

* merge 合并代码

* fix 新增套餐详情弹框

* fix 修复pr问题

* feat: change http node input to prompt editor (#21)

* feat: change http node input to prompt editor

* fix

* split PromptEditor to HttpInput

* Team plans (#25)

* perf: pay check

* perf: team plan test

* plan limit check

* replace sensitive text

* perf: fix some null

* collection null check

* perf: plans modal

* perf: http module

* pacakge (#26)

* individuation page and pay modal amount (#27)

* feat: individuation page

* team chat config

* pay modal

* plan count and replace invalid chars (#29)

* fix: user oneapi

* fix: training queue

* fix: qa queue

* perf: remove space chars

* replace invalid chars

* change httpinput dropdown menu (#28)

* perf: http

* reseet free plan

* perf: plan code to packages

* remove llm config to package

* perf: code

* perf: faq

* fix: get team plan

---------

Co-authored-by: yst <77910600+yu-and-liu@users.noreply.github.com>
Co-authored-by: liuxingwan <liuxingwan.lxw@alibaba-inc.com>
Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
Archer
2024-02-28 13:19:15 +08:00
committed by GitHub
parent 32686f9e3e
commit 064c64e74c
282 changed files with 7223 additions and 4731 deletions

View File

@@ -3,7 +3,7 @@ import { sseResponseEventEnum } from './constant';
import { proxyError, ERROR_RESPONSE, ERROR_ENUM } from '@fastgpt/global/common/error/errorCode';
import { addLog } from '../system/log';
import { clearCookie } from '../../support/permission/controller';
import { replaceSensitiveLink } from '@fastgpt/global/common/string/tools';
import { replaceSensitiveText } from '@fastgpt/global/common/string/tools';
export interface ResponseType<T = any> {
code: number;
@@ -53,7 +53,7 @@ export const jsonRes = <T = any>(
res.status(code).json({
code,
statusText: '',
message: replaceSensitiveLink(message || msg),
message: replaceSensitiveText(message || msg),
data: data !== undefined ? data : null
});
};
@@ -91,7 +91,7 @@ export const sseErrRes = (res: NextApiResponse, error: any) => {
responseWrite({
res,
event: sseResponseEventEnum.error,
data: JSON.stringify({ message: replaceSensitiveLink(msg) })
data: JSON.stringify({ message: replaceSensitiveText(msg) })
});
};

View File

@@ -1,3 +1,4 @@
import { FastGPTConfigFileType } from '@fastgpt/global/common/system/types';
import { isIPv6 } from 'net';
export const SERVICE_LOCAL_PORT = `${process.env.PORT || 3000}`;
@@ -5,3 +6,16 @@ export const SERVICE_LOCAL_HOST =
process.env.HOSTNAME && isIPv6(process.env.HOSTNAME)
? `[${process.env.HOSTNAME}]:${SERVICE_LOCAL_PORT}`
: `${process.env.HOSTNAME || 'localhost'}:${SERVICE_LOCAL_PORT}`;
export const initFastGPTConfig = (config?: FastGPTConfigFileType) => {
if (!config) return;
global.feConfigs = config.feConfigs;
global.subPlans = config.subPlans;
global.llmModels = config.llmModels;
global.vectorModels = config.vectorModels;
global.audioSpeechModels = config.audioSpeechModels;
global.whisperModel = config.whisperModel;
global.reRankModels = config.reRankModels;
};

View File

@@ -11,7 +11,6 @@ const getVectorObj = () => {
export const initVectorStore = getVectorObj().init;
export const deleteDatasetDataVector = getVectorObj().delete;
export const recallFromVectorStore = getVectorObj().recall;
export const checkVectorDataExist = getVectorObj().checkDataExist;
export const getVectorDataByTime = getVectorObj().getVectorDataByTime;
export const getVectorCountByTeamId = getVectorObj().getVectorCountByTeamId;
@@ -38,22 +37,22 @@ export const insertDatasetDataVector = async ({
};
};
export const updateDatasetDataVector = async ({
id,
...props
}: InsertVectorProps & {
id: string;
query: string;
model: VectorModelItemType;
}) => {
// insert new vector
const { charsLength, insertId } = await insertDatasetDataVector(props);
// export const updateDatasetDataVector = async ({
// id,
// ...props
// }: InsertVectorProps & {
// id: string;
// query: string;
// model: VectorModelItemType;
// }) => {
// // insert new vector
// const { charsLength, insertId } = await insertDatasetDataVector(props);
// delete old vector
await deleteDatasetDataVector({
teamId: props.teamId,
id
});
// // delete old vector
// await deleteDatasetDataVector({
// teamId: props.teamId,
// id
// });
return { charsLength, insertId };
};
// return { charsLength, insertId };
// };

View File

@@ -4,8 +4,7 @@ import {
deleteDatasetDataVector,
embeddingRecall,
getVectorDataByTime,
getVectorCountByTeamId,
checkDataExist
getVectorCountByTeamId
} from './controller';
export class PgVector {
@@ -14,7 +13,6 @@ export class PgVector {
insert = insertDatasetDataVector;
delete = deleteDatasetDataVector;
recall = embeddingRecall;
checkDataExist = checkDataExist;
getVectorCountByTeamId = getVectorCountByTeamId;
getVectorDataByTime = getVectorDataByTime;
}

View File

@@ -25,6 +25,18 @@ export async function initPg() {
await PgClient.query(
`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(
`CREATE INDEX CONCURRENTLY IF NOT EXISTS team_dataset_index ON ${PgDatasetTableName} USING btree(team_id, dataset_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(
`CREATE INDEX CONCURRENTLY IF NOT EXISTS create_time_index ON ${PgDatasetTableName} USING btree(createtime);`
);
console.log('init pg successful');
} catch (error) {
@@ -152,11 +164,6 @@ export const embeddingRecall = async (
}
};
export const checkDataExist = async (id: string) => {
const { rows } = await PgClient.query(`SELECT id FROM ${PgDatasetTableName} WHERE id=${id};`);
return rows.length > 0;
};
export const getVectorCountByTeamId = async (teamId: string) => {
const total = await PgClient.count(PgDatasetTableName, {
where: [['team_id', String(teamId)]]

View File

@@ -11,6 +11,7 @@ export const getAIApi = (props?: {
timeout?: number;
}) => {
const { userKey, timeout } = props || {};
return new OpenAI({
apiKey: userKey?.key || systemAIChatKey,
baseURL: userKey?.baseUrl || baseUrl,

View File

@@ -1,5 +1,6 @@
import { VectorModelItemType } from '@fastgpt/global/core/ai/model.d';
import { getAIApi } from '../config';
import { replaceValidChars } from '../../chat/utils';
type GetVectorProps = {
model: VectorModelItemType;
@@ -36,7 +37,7 @@ export async function getVectorsByText({ model, input }: GetVectorProps) {
}
return {
charsLength: input.length,
charsLength: replaceValidChars(input).length,
vectors: await Promise.all(res.data.map((item) => unityDimensional(item.embedding)))
};
});

View File

@@ -1,159 +0,0 @@
import { replaceVariable } from '@fastgpt/global/common/string/tools';
import { getAIApi } from '../config';
import { ChatItemType } from '@fastgpt/global/core/chat/type';
/*
cfr: coreference resolution - 指代消除
可以根据上下文,完事当前问题指代内容,利于检索。
*/
const defaultPrompt = `请不要回答任何问题。
你的任务是结合历史记录,为当前问题,实现代词替换,确保问题描述的对象清晰明确。例如:
历史记录:
"""
Q: 对话背景。
A: 关于 FatGPT 的介绍和使用等问题。
"""
当前问题: 怎么下载
输出: FastGPT 怎么下载?
----------------
历史记录:
"""
Q: 报错 "no connection"
A: FastGPT 报错"no connection"可能是因为……
"""
当前问题: 怎么解决
输出: FastGPT 报错"no connection"如何解决?
----------------
历史记录:
"""
Q: 作者是谁?
A: FastGPT 的作者是 labring。
"""
当前问题: 介绍下他
输出: 介绍下 FastGPT 的作者 labring。
----------------
历史记录:
"""
Q: 作者是谁?
A: FastGPT 的作者是 labring。
"""
当前问题: 我想购买商业版。
输出: FastGPT 商业版如何购买?
----------------
历史记录:
"""
Q: 对话背景。
A: 关于 FatGPT 的介绍和使用等问题。
"""
当前问题: nh
输出: nh
----------------
历史记录:
"""
Q: FastGPT 如何收费?
A: FastGPT 收费可以参考……
"""
当前问题: 你知道 laf 么?
输出: 你知道 laf 么?
----------------
历史记录:
"""
Q: FastGPT 的优势
A: 1. 开源
2. 简便
3. 扩展性强
"""
当前问题: 介绍下第2点。
输出: 介绍下 FastGPT 简便的优势。
----------------
历史记录:
"""
Q: 什么是 FastGPT
A: FastGPT 是一个 RAG 平台。
Q: 什么是 Sealos
A: Sealos 是一个云操作系统。
"""
当前问题: 它们有什么关系?
输出: FastGPT 和 Sealos 有什么关系?
----------------
历史记录:
"""
{{histories}}
"""
当前问题: {{query}}
输出: `;
export const queryCfr = async ({
chatBg,
query,
histories = [],
model
}: {
chatBg?: string;
query: string;
histories: ChatItemType[];
model: string;
}) => {
if (histories.length === 0 && !chatBg) {
return {
rawQuery: query,
cfrQuery: query,
model,
inputTokens: 0,
outputTokens: 0
};
}
const systemFewShot = chatBg
? `Q: 对话背景。
A: ${chatBg}
`
: '';
const historyFewShot = histories
.map((item) => {
const role = item.obj === 'Human' ? 'Q' : 'A';
return `${role}: ${item.value}`;
})
.join('\n');
const concatFewShot = `${systemFewShot}${historyFewShot}`.trim();
const ai = getAIApi({
timeout: 480000
});
const result = await ai.chat.completions.create({
model: model,
temperature: 0.01,
max_tokens: 150,
messages: [
{
role: 'user',
content: replaceVariable(defaultPrompt, {
query: `${query}`,
histories: concatFewShot
})
}
],
stream: false
});
const answer = result.choices?.[0]?.message?.content || '';
if (!answer) {
return {
rawQuery: query,
cfrQuery: query,
model,
inputTokens: 0,
outputTokens: 0
};
}
return {
rawQuery: query,
cfrQuery: answer,
model,
inputTokens: result.usage?.prompt_tokens || 0,
outputTokens: result.usage?.completion_tokens || 0
};
};

View File

@@ -1,5 +1,6 @@
import type { ChatMessageItemType } from '@fastgpt/global/core/ai/type.d';
import { getAIApi } from '../config';
import { countGptMessagesChars } from '../../chat/utils';
export const Prompt_QuestionGuide = `我不太清楚问你什么问题,请帮我生成 3 个问题引导我继续提问。问题的长度应小于20个字符按 JSON 格式返回: ["问题1", "问题2", "问题3"]`;
@@ -10,6 +11,13 @@ export async function createQuestionGuide({
messages: ChatMessageItemType[];
model: string;
}) {
const concatMessages: ChatMessageItemType[] = [
...messages,
{
role: 'user',
content: Prompt_QuestionGuide
}
];
const ai = getAIApi({
timeout: 480000
});
@@ -17,28 +25,21 @@ export async function createQuestionGuide({
model: model,
temperature: 0.1,
max_tokens: 200,
messages: [
...messages,
{
role: 'user',
content: Prompt_QuestionGuide
}
],
messages: concatMessages,
stream: false
});
const answer = data.choices?.[0]?.message?.content || '';
const inputTokens = data.usage?.prompt_tokens || 0;
const outputTokens = data.usage?.completion_tokens || 0;
const start = answer.indexOf('[');
const end = answer.lastIndexOf(']');
const charsLength = countGptMessagesChars(concatMessages);
if (start === -1 || end === -1) {
return {
result: [],
inputTokens,
outputTokens
charsLength: 0
};
}
@@ -50,14 +51,12 @@ export async function createQuestionGuide({
try {
return {
result: JSON.parse(jsonStr),
inputTokens,
outputTokens
charsLength
};
} catch (error) {
return {
result: [],
inputTokens,
outputTokens
charsLength: 0
};
}
}

View File

@@ -1,18 +1,19 @@
import { replaceVariable } from '@fastgpt/global/common/string/tools';
import { getAIApi } from '../config';
import { ChatItemType } from '@fastgpt/global/core/chat/type';
import { countGptMessagesChars } from '../../chat/utils';
/*
query extension - 问题扩展
可以根据上下文,消除指代性问题以及扩展问题,利于检索。
*/
const defaultPrompt = `作为一个向量检索助手,你的任务是结合历史记录,从不同角度,为“原问题”生成个不同版本的“检索词”,从而提高向量检索的语义丰富度,提高向量检索的精度。生成的问题要求指向对象清晰明确。例如:
const defaultPrompt = `作为一个向量检索助手,你的任务是结合历史记录,从不同角度,为“原问题”生成个不同版本的“检索词”,从而提高向量检索的语义丰富度,提高向量检索的精度。生成的问题要求指向对象清晰明确,并与原问题语言相同。例如:
历史记录:
"""
"""
原问题: 介绍下剧情。
检索词: ["发生了什么故事?","故事梗概是什么?","讲述了什么故事"]
检索词: ["介绍下故事的背景和主要人物。","故事的主题是什么?","剧情是是如何发展的"]
----------------
历史记录:
"""
@@ -20,7 +21,7 @@ Q: 对话背景。
A: 当前对话是关于 FatGPT 的介绍和使用等。
"""
原问题: 怎么下载
检索词: ["FastGPT 怎么下载?","下载 FastGPT 需要什么条件?","有哪些渠道可以下载 FastGPT"]
检索词: ["FastGPT 如何下载?","下载 FastGPT 需要什么条件?","有哪些渠道可以下载 FastGPT"]
----------------
历史记录:
"""
@@ -30,15 +31,15 @@ Q: 报错 "no connection"
A: 报错"no connection"可能是因为……
"""
原问题: 怎么解决
检索词: ["FastGPT 报错"no connection"如何解决?", "报错 'no connection' 是什么原因", "FastGPT提示'no connection',要怎么办?"]
检索词: ["FastGPT 报错"no connection"如何解决?", "造成 'no connection' 报错的原因", "FastGPT提示'no connection',要怎么办?"]
----------------
历史记录:
"""
Q: 作者是谁?
A: FastGPT 的作者是 labring。
"""
原问题: 介绍下他
检索词: ["介绍下 FastGPT 的作者 labring。","作者 labring 的背景信息。","labring 为什么要做 FastGPT?"]
原问题: Tell me about him
检索词: ["Introduce labring, the author of FastGPT." ," Background information on author labring." "," Why does labring do FastGPT?"]
----------------
历史记录:
"""
@@ -105,8 +106,7 @@ export const queryExtension = async ({
rawQuery: string;
extensionQueries: string[];
model: string;
inputTokens: number;
outputTokens: number;
charsLength: number;
}> => {
const systemFewShot = chatBg
? `Q: 对话背景。
@@ -125,18 +125,20 @@ A: ${chatBg}
timeout: 480000
});
const messages = [
{
role: 'user',
content: replaceVariable(defaultPrompt, {
query: `${query}`,
histories: concatFewShot
})
}
];
const result = await ai.chat.completions.create({
model: model,
temperature: 0.01,
messages: [
{
role: 'user',
content: replaceVariable(defaultPrompt, {
query: `${query}`,
histories: concatFewShot
})
}
],
// @ts-ignore
messages,
stream: false
});
@@ -146,8 +148,7 @@ A: ${chatBg}
rawQuery: query,
extensionQueries: [],
model,
inputTokens: 0,
outputTokens: 0
charsLength: 0
};
}
@@ -160,8 +161,7 @@ A: ${chatBg}
rawQuery: query,
extensionQueries: queries,
model,
inputTokens: result.usage?.prompt_tokens || 0,
outputTokens: result.usage?.completion_tokens || 0
charsLength: countGptMessagesChars(messages)
};
} catch (error) {
console.log(error);
@@ -169,8 +169,7 @@ A: ${chatBg}
rawQuery: query,
extensionQueries: [],
model,
inputTokens: 0,
outputTokens: 0
charsLength: 0
};
}
};

View File

@@ -0,0 +1,42 @@
export const getLLMModel = (model?: string) => {
return global.llmModels.find((item) => item.model === model) ?? global.llmModels[0];
};
export const getDatasetModel = (model?: string) => {
return (
global.llmModels?.filter((item) => item.datasetProcess)?.find((item) => item.model === model) ??
global.llmModels[0]
);
};
export const getVectorModel = (model?: string) => {
return global.vectorModels.find((item) => item.model === model) || global.vectorModels[0];
};
export function getAudioSpeechModel(model?: string) {
return (
global.audioSpeechModels.find((item) => item.model === model) || global.audioSpeechModels[0]
);
}
export function getWhisperModel(model?: string) {
return global.whisperModel;
}
export function getReRankModel(model?: string) {
return global.reRankModels.find((item) => item.model === model);
}
export enum ModelTypeEnum {
llm = 'llm',
vector = 'vector',
audioSpeech = 'audioSpeech',
whisper = 'whisper',
rerank = 'rerank'
}
export const getModelMap = {
[ModelTypeEnum.llm]: getLLMModel,
[ModelTypeEnum.vector]: getVectorModel,
[ModelTypeEnum.audioSpeech]: getAudioSpeechModel,
[ModelTypeEnum.whisper]: getWhisperModel,
[ModelTypeEnum.rerank]: getReRankModel
};

View File

@@ -61,6 +61,9 @@ const AppSchema = new Schema({
type: String,
enum: Object.keys(PermissionTypeMap),
default: PermissionTypeEnum.private
},
teamTags: {
type: [String]
}
});

View File

@@ -92,6 +92,8 @@ try {
ChatItemSchema.index({ appId: 1, chatId: 1, dataId: 1 }, { background: true });
// admin charts
ChatItemSchema.index({ time: -1, obj: 1 }, { background: true });
// timer, clear history
ChatItemSchema.index({ teamId: 1, time: -1 }, { background: true });
} catch (error) {
console.log(error);
}

View File

@@ -83,6 +83,9 @@ try {
ChatSchema.index({ teamId: 1, appId: 1, updateTime: -1 }, { background: true });
// get share chat history
ChatSchema.index({ shareId: 1, outLinkUid: 1, updateTime: -1, source: 1 }, { background: true });
// timer, clear history
ChatSchema.index({ teamId: 1, updateTime: -1 }, { background: true });
} catch (error) {
console.log(error);
}

View File

@@ -2,7 +2,10 @@ import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
import { ChatRoleEnum, IMG_BLOCK_KEY } from '@fastgpt/global/core/chat/constants';
import { countMessagesTokens, countPromptTokens } from '@fastgpt/global/common/string/tiktoken';
import { adaptRole_Chat2Message } from '@fastgpt/global/core/chat/adapt';
import type { ChatCompletionContentPart } from '@fastgpt/global/core/ai/type.d';
import type {
ChatCompletionContentPart,
ChatMessageItemType
} from '@fastgpt/global/core/ai/type.d';
import axios from 'axios';
/* slice chat context by tokens */
@@ -56,6 +59,16 @@ export function ChatContextFilter({
return [...systemPrompts, ...chats];
}
export const replaceValidChars = (str: string) => {
const reg = /[\s\r\n]+/g;
return str.replace(reg, '');
};
export const countMessagesChars = (messages: ChatItemType[]) => {
return messages.reduce((sum, item) => sum + replaceValidChars(item.value).length, 0);
};
export const countGptMessagesChars = (messages: ChatMessageItemType[]) =>
messages.reduce((sum, item) => sum + replaceValidChars(item.content).length, 0);
/**
string to vision model. Follow the markdown code block rule for interception:

View File

@@ -147,8 +147,6 @@ export async function delCollectionAndRelatedSources({
collectionId: { $in: collectionIds }
});
await delay(2000);
// delete dataset.datas
await MongoDatasetData.deleteMany({ teamId, collectionId: { $in: collectionIds } }, { session });
// delete imgs

View File

@@ -66,6 +66,11 @@ export async function delDatasetRelevantData({
if (!datasets.length) return;
const teamId = datasets[0].teamId;
if (!teamId) {
return Promise.reject('teamId is required');
}
const datasetIds = datasets.map((item) => String(item._id));
// Get _id, teamId, fileId, metadata.relatedImgId for all collections

View File

@@ -7,10 +7,6 @@ import {
} from '@fastgpt/global/support/user/team/constant';
import { DatasetCollectionName } from '../schema';
import { DatasetColCollectionName } from '../collection/schema';
import {
DatasetDataIndexTypeEnum,
DatasetDataIndexTypeMap
} from '@fastgpt/global/core/dataset/constants';
export const DatasetDataCollectionName = 'dataset.datas';
@@ -54,11 +50,6 @@ const DatasetDataSchema = new Schema({
type: Boolean,
default: false
},
type: {
type: String,
enum: Object.keys(DatasetDataIndexTypeMap),
default: DatasetDataIndexTypeEnum.custom
},
dataId: {
type: String,
required: true

View File

@@ -14,22 +14,54 @@ export const datasetSearchQueryExtension = async ({
extensionBg?: string;
histories?: ChatItemType[];
}) => {
// concat query
let queries = [query];
let rewriteQuery =
histories.length > 0
? `${histories
.map((item) => {
return `${item.obj}: ${item.value}`;
})
.join('\n')}
Human: ${query}
`
: query;
const filterSamQuery = (queries: string[]) => {
const set = new Set<string>();
const filterSameQueries = queries.filter((item) => {
// 删除所有的标点符号与空格等,只对文本进行比较
const str = hashStr(item.replace(/[^\p{L}\p{N}]/gu, ''));
if (set.has(str)) return false;
set.add(str);
return true;
});
return filterSameQueries;
};
let { queries, rewriteQuery, alreadyExtension } = (() => {
// concat query
let rewriteQuery =
histories.length > 0
? `${histories
.map((item) => {
return `${item.obj}: ${item.value}`;
})
.join('\n')}
Human: ${query}
`
: query;
/* if query already extension, direct parse */
try {
const jsonParse = JSON.parse(query);
const queries: string[] = Array.isArray(jsonParse) ? filterSamQuery(jsonParse) : [query];
const alreadyExtension = Array.isArray(jsonParse);
return {
queries,
rewriteQuery: alreadyExtension ? queries.join('\n') : rewriteQuery,
alreadyExtension: alreadyExtension
};
} catch (error) {
return {
queries: [query],
rewriteQuery,
alreadyExtension: false
};
}
})();
// ai extension
const aiExtensionResult = await (async () => {
if (!extensionModel) return;
if (!extensionModel || alreadyExtension) return;
const result = await queryExtension({
chatBg: extensionBg,
query,
@@ -39,23 +71,13 @@ export const datasetSearchQueryExtension = async ({
if (result.extensionQueries?.length === 0) return;
return result;
})();
if (aiExtensionResult) {
queries = queries.concat(aiExtensionResult.extensionQueries);
queries = filterSamQuery(queries.concat(aiExtensionResult.extensionQueries));
rewriteQuery = queries.join('\n');
}
const set = new Set<string>();
const filterSameQueries = queries.filter((item) => {
// 删除所有的标点符号与空格等,只对文本进行比较
const str = hashStr(item.replace(/[^\p{L}\p{N}]/gu, ''));
if (set.has(str)) return false;
set.add(str);
return true;
});
return {
concatQueries: filterSameQueries,
concatQueries: queries,
rewriteQuery,
aiExtensionResult
};

View File

@@ -11,7 +11,7 @@ import { simpleText } from '@fastgpt/global/common/string/tools';
import { countPromptTokens } from '@fastgpt/global/common/string/tiktoken';
import type { VectorModelItemType, LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
export const lockTrainingDataByTeamId = async (teamId: string, retry = 3): Promise<any> => {
export const lockTrainingDataByTeamId = async (teamId: string): Promise<any> => {
try {
await MongoDatasetTraining.updateMany(
{
@@ -21,13 +21,7 @@ export const lockTrainingDataByTeamId = async (teamId: string, retry = 3): Promi
lockTime: new Date('2999/5/5')
}
);
} catch (error) {
if (retry > 0) {
await delay(1000);
return lockTrainingDataByTeamId(teamId, retry - 1);
}
return Promise.reject(error);
}
} catch (error) {}
};
export async function pushDataListToTrainingQueue({
@@ -51,17 +45,15 @@ export async function pushDataListToTrainingQueue({
datasetId: { _id: datasetId, vectorModel, agentModel }
} = await getCollectionWithDataset(collectionId);
const checkModelValid = async ({ collectionId }: { collectionId: string }) => {
if (!collectionId) return Promise.reject(`CollectionId is empty`);
const checkModelValid = async () => {
if (trainingMode === TrainingModeEnum.chunk) {
const vectorModelData = vectorModelList?.find((item) => item.model === vectorModel);
if (!vectorModelData) {
return Promise.reject(`Model ${vectorModel} is inValid`);
return Promise.reject(`File model ${vectorModel} is inValid`);
}
return {
maxToken: vectorModelData.maxToken * 1.5,
maxToken: vectorModelData.maxToken * 1.3,
model: vectorModelData.model,
weight: vectorModelData.weight
};
@@ -70,7 +62,7 @@ export async function pushDataListToTrainingQueue({
if (trainingMode === TrainingModeEnum.qa) {
const qaModelData = datasetModelList?.find((item) => item.model === agentModel);
if (!qaModelData) {
return Promise.reject(`Model ${agentModel} is inValid`);
return Promise.reject(`Vector model ${agentModel} is inValid`);
}
return {
maxToken: qaModelData.maxContext * 0.8,
@@ -81,9 +73,7 @@ export async function pushDataListToTrainingQueue({
return Promise.reject(`Training mode "${trainingMode}" is inValid`);
};
const { model, maxToken, weight } = await checkModelValid({
collectionId
});
const { model, maxToken, weight } = await checkModelValid();
// format q and a, remove empty char
data.forEach((item) => {

View File

@@ -2,7 +2,7 @@
import { connectionMongo, type Model } from '../../../common/mongo';
const { Schema, model, models } = connectionMongo;
import { DatasetTrainingSchemaType } from '@fastgpt/global/core/dataset/type';
import { DatasetDataIndexTypeMap, TrainingTypeMap } from '@fastgpt/global/core/dataset/constants';
import { TrainingTypeMap } from '@fastgpt/global/core/dataset/constants';
import { DatasetColCollectionName } from '../collection/schema';
import { DatasetCollectionName } from '../schema';
import {
@@ -86,11 +86,6 @@ const TrainingDataSchema = new Schema({
indexes: {
type: [
{
type: {
type: String,
enum: Object.keys(DatasetDataIndexTypeMap),
required: true
},
text: {
type: String,
required: true

View File

@@ -15,7 +15,7 @@
"nextjs-cors": "^2.1.2",
"node-cron": "^3.0.3",
"pg": "^8.10.0",
"date-fns": "^2.30.0",
"date-fns": "2.30.0",
"tunnel": "^0.0.6"
},
"devDependencies": {

View File

@@ -19,14 +19,15 @@ export async function authOpenApiKey({ apikey }: { apikey: string }) {
// auth limit
// @ts-ignore
if (global.feConfigs?.isPlus) {
await POST('/support/openapi/authLimit', { openApi } as AuthOpenApiLimitProps);
await POST('/support/openapi/authLimit', {
openApi: openApi.toObject()
} as AuthOpenApiLimitProps);
}
updateApiKeyUsedTime(openApi._id);
return {
apikey,
userId: String(openApi.userId),
teamId: String(openApi.teamId),
tmbId: String(openApi.tmbId),
appId: openApi.appId || ''

View File

@@ -1,8 +1,6 @@
import { connectionMongo, type Model } from '../../common/mongo';
const { Schema, model, models } = connectionMongo;
import type { OpenApiSchema } from '@fastgpt/global/support/openapi/type';
import { PRICE_SCALE } from '@fastgpt/global/support/wallet/bill/constants';
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
import {
TeamCollectionName,
TeamMemberCollectionName
@@ -10,10 +8,6 @@ import {
const OpenApiSchema = new Schema(
{
userId: {
type: Schema.Types.ObjectId,
ref: 'user'
},
teamId: {
type: Schema.Types.ObjectId,
ref: TeamCollectionName,
@@ -44,22 +38,17 @@ const OpenApiSchema = new Schema(
type: String,
default: 'Api Key'
},
usage: {
// total usage. value from bill total
usagePoints: {
type: Number,
default: 0,
get: (val: number) => formatStorePrice2Read(val)
default: 0
},
limit: {
expiredTime: {
type: Date
},
credit: {
// value from user settings
maxUsagePoints: {
type: Number,
default: -1,
set: (val: number) => val * PRICE_SCALE,
get: (val: number) => formatStorePrice2Read(val)
default: -1
}
}
},

View File

@@ -8,15 +8,21 @@ export function updateApiKeyUsedTime(id: string) {
});
}
export function updateApiKeyUsage({ apikey, usage }: { apikey: string; usage: number }) {
export function updateApiKeyUsage({
apikey,
totalPoints
}: {
apikey: string;
totalPoints: number;
}) {
MongoOpenApi.findOneAndUpdate(
{ apiKey: apikey },
{
$inc: {
usage
usagePoints: totalPoints
}
}
).catch((err) => {
console.log('update apiKey usage error', err);
console.log('update apiKey totalPoints error', err);
});
}

View File

@@ -35,8 +35,7 @@ const OutLinkSchema = new Schema({
type: String,
required: true
},
total: {
// total amount
usagePoints: {
type: Number,
default: 0
},
@@ -48,6 +47,10 @@ const OutLinkSchema = new Schema({
default: false
},
limit: {
maxUsagePoints: {
type: Number,
default: -1
},
expiredTime: {
type: Date
},
@@ -55,16 +58,18 @@ const OutLinkSchema = new Schema({
type: Number,
default: 1000
},
credit: {
type: Number,
default: -1
},
hookUrl: {
type: String
}
}
});
try {
OutLinkSchema.index({ shareId: -1 });
} catch (error) {
console.log(error);
}
export const MongoOutLink: Model<SchemaType> =
models['outlinks'] || model('outlinks', OutLinkSchema);

View File

@@ -1,18 +1,19 @@
import axios from 'axios';
import { MongoOutLink } from './schema';
import { FastGPTProUrl } from '../../common/system/constants';
import { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type';
export const updateOutLinkUsage = async ({
export const addOutLinkUsage = async ({
shareId,
total
totalPoints
}: {
shareId: string;
total: number;
totalPoints: number;
}) => {
MongoOutLink.findOneAndUpdate(
{ shareId },
{
$inc: { total },
$inc: { usagePoints: totalPoints },
lastTime: new Date()
}
).catch((err) => {
@@ -23,11 +24,13 @@ export const updateOutLinkUsage = async ({
export const pushResult2Remote = async ({
outLinkUid,
shareId,
appName,
responseData
}: {
outLinkUid?: string; // raw id, not parse
shareId?: string;
responseData?: any[];
appName: string;
responseData?: ChatHistoryItemResType[];
}) => {
if (!shareId || !outLinkUid || !FastGPTProUrl) return;
try {
@@ -42,6 +45,7 @@ export const pushResult2Remote = async ({
url: '/shareAuth/finish',
data: {
token: outLinkUid,
appName,
responseData
}
});

View File

@@ -107,7 +107,7 @@ export async function authDatasetCollection({
collection: CollectionWithDatasetType;
}
> {
const { userId, teamId, tmbId } = await parseHeaderCert(props);
const { teamId, tmbId } = await parseHeaderCert(props);
const { role } = await getTmbInfoByTmbId({ tmbId });
const { collection, isOwner, canWrite } = await (async () => {
@@ -143,7 +143,6 @@ export async function authDatasetCollection({
})();
return {
userId,
teamId,
tmbId,
collection,
@@ -163,7 +162,7 @@ export async function authDatasetFile({
file: DatasetFileSchema;
}
> {
const { userId, teamId, tmbId } = await parseHeaderCert(props);
const { teamId, tmbId } = await parseHeaderCert(props);
const [file, collection] = await Promise.all([
getFileById({ bucketName: BucketNameEnum.dataset, fileId }),
@@ -190,7 +189,6 @@ export async function authDatasetFile({
});
return {
userId,
teamId,
tmbId,
file,
@@ -200,4 +198,4 @@ export async function authDatasetFile({
} catch (error) {
return Promise.reject(DatasetErrEnum.unAuthDatasetFile);
}
}
}

View File

@@ -12,7 +12,7 @@ export async function authUserNotVisitor(props: AuthModeType): Promise<
role: `${TeamMemberRoleEnum}`;
}
> {
const { userId, teamId, tmbId } = await parseHeaderCert(props);
const { teamId, tmbId } = await parseHeaderCert(props);
const team = await getTmbInfoByTmbId({ tmbId });
if (team.role === TeamMemberRoleEnum.visitor) {
@@ -20,7 +20,6 @@ export async function authUserNotVisitor(props: AuthModeType): Promise<
}
return {
userId,
teamId,
tmbId,
team,

View File

@@ -94,10 +94,10 @@ export async function parseHeaderCert({
})();
// auth apikey
const { userId, teamId, tmbId, appId: apiKeyAppId = '' } = await authOpenApiKey({ apikey });
const { teamId, tmbId, appId: apiKeyAppId = '' } = await authOpenApiKey({ apikey });
return {
uid: userId,
uid: '',
teamId,
tmbId,
apikey,
@@ -217,4 +217,4 @@ export const authFileToken = (token?: string) =>
fileId: decoded.fileId
});
});
});
});

View File

@@ -1,23 +0,0 @@
import { StandSubPlanLevelMapType } from '@fastgpt/global/support/wallet/sub/type';
import { getVectorCountByTeamId } from '../../../common/vectorStore/controller';
import { getTeamDatasetMaxSize } from '../../wallet/sub/utils';
export const checkDatasetLimit = async ({
teamId,
insertLen = 0,
standardPlans
}: {
teamId: string;
insertLen?: number;
standardPlans?: StandSubPlanLevelMapType;
}) => {
const [{ maxSize }, usedSize] = await Promise.all([
getTeamDatasetMaxSize({ teamId, standardPlans }),
getVectorCountByTeamId(teamId)
]);
if (usedSize + insertLen >= maxSize) {
return Promise.reject(`数据库容量不足,无法继续添加。可以在账号页面进行扩容。`);
}
return;
};

View File

@@ -0,0 +1,91 @@
import { getVectorCountByTeamId } from '../../common/vectorStore/controller';
import { getTeamPlanStatus, getTeamStandPlan } from '../../support/wallet/sub/utils';
import { MongoApp } from '../../core/app/schema';
import { MongoPlugin } from '../../core/plugin/schema';
import { MongoDataset } from '../../core/dataset/schema';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
export const checkDatasetLimit = async ({
teamId,
insertLen = 0
}: {
teamId: string;
insertLen?: number;
}) => {
const [{ standardConstants, totalPoints, usedPoints, datasetMaxSize }, usedSize] =
await Promise.all([getTeamPlanStatus({ teamId }), getVectorCountByTeamId(teamId)]);
if (!standardConstants) return;
if (usedSize + insertLen >= datasetMaxSize) {
return Promise.reject(TeamErrEnum.datasetSizeNotEnough);
}
if (usedPoints >= totalPoints) {
return Promise.reject(TeamErrEnum.aiPointsNotEnough);
}
return;
};
export const checkTeamAIPoints = async (teamId: string) => {
const { standardConstants, totalPoints, usedPoints } = await getTeamPlanStatus({
teamId
});
if (!standardConstants) return;
if (usedPoints >= totalPoints) {
return Promise.reject(TeamErrEnum.aiPointsNotEnough);
}
return {
totalPoints,
usedPoints
};
};
export const checkTeamDatasetLimit = async (teamId: string) => {
const [{ standardConstants }, datasetCount] = await Promise.all([
getTeamStandPlan({ teamId }),
MongoDataset.countDocuments({
teamId,
type: { $ne: DatasetTypeEnum.folder }
})
]);
if (standardConstants && datasetCount >= standardConstants.maxDatasetAmount) {
return Promise.reject(TeamErrEnum.datasetAmountNotEnough);
}
};
export const checkTeamAppLimit = async (teamId: string) => {
const [{ standardConstants }, appCount] = await Promise.all([
getTeamStandPlan({ teamId }),
MongoApp.count({ teamId })
]);
if (standardConstants && appCount >= standardConstants.maxAppAmount) {
return Promise.reject(TeamErrEnum.appAmountNotEnough);
}
};
export const checkTeamPluginLimit = async (teamId: string) => {
const [{ standardConstants }, pluginCount] = await Promise.all([
getTeamStandPlan({ teamId }),
MongoPlugin.count({ teamId })
]);
if (standardConstants && pluginCount >= standardConstants.maxAppAmount) {
return Promise.reject(TeamErrEnum.pluginAmountNotEnough);
}
};
export const checkTeamReRankPermission = async (teamId: string) => {
const { standardConstants } = await getTeamStandPlan({
teamId
});
if (standardConstants && !standardConstants?.permissionReRank) {
return false;
}
return true;
};

View File

@@ -2,7 +2,6 @@ import { UserType } from '@fastgpt/global/support/user/type';
import { MongoUser } from './schema';
import { getTmbInfoByTmbId, getUserDefaultTeam } from './team/controller';
import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode';
import { UserErrEnum } from '@fastgpt/global/common/error/code/user';
export async function authUserExist({ userId, username }: { userId?: string; username?: string }) {
if (userId) {
@@ -47,22 +46,3 @@ export async function getUserDetail({
team: tmb
};
}
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;
}

View File

@@ -1,7 +1,7 @@
import { connectionMongo, type Model } from '../../common/mongo';
const { Schema, model, models } = connectionMongo;
import { hashStr } from '@fastgpt/global/common/string/tools';
import { PRICE_SCALE } from '@fastgpt/global/support/wallet/bill/constants';
import { PRICE_SCALE } from '@fastgpt/global/support/wallet/constants';
import type { UserModelSchema } from '@fastgpt/global/support/user/type';
import { UserStatusEnum, userStatusMap } from '@fastgpt/global/support/user/constant';
@@ -63,6 +63,8 @@ const UserSchema = new Schema({
});
try {
// login
UserSchema.index({ username: 1, password: 1 });
UserSchema.index({ createTime: -1 });
} catch (error) {
console.log(error);

View File

@@ -36,7 +36,7 @@ export async function getTmbInfoByTmbId({ tmbId }: { tmbId: string }) {
return Promise.reject('tmbId or userId is required');
}
return getTeamMember({
_id: new Types.ObjectId(tmbId),
_id: new Types.ObjectId(String(tmbId)),
status: notLeaveStatus
});
}

View File

@@ -27,7 +27,10 @@ const TeamSchema = new Schema({
},
maxSize: {
type: Number,
default: 3
default: 1
},
tagsUrl: {
type: String
},
limit: {
lastExportDatasetTime: {

View File

@@ -0,0 +1,35 @@
import { connectionMongo, type Model } from '../../../common/mongo';
const { Schema, model, models } = connectionMongo;
import { TeamTagsSchema as TeamTagsSchemaType } from '@fastgpt/global/support/user/team/type.d';
import {
TeamCollectionName,
TeamTagsCollectionName
} from '@fastgpt/global/support/user/team/constant';
const TeamTagsSchema = new Schema({
label: {
type: String,
required: true
},
teamId: {
type: Schema.Types.ObjectId,
ref: TeamCollectionName,
required: true
},
key: {
type: String
},
createTime: {
type: Date,
default: () => new Date()
}
});
try {
TeamTagsSchema.index({ teamId: 1 });
} catch (error) {
console.log(error);
}
export const MongoTeamTags: Model<TeamTagsSchemaType> =
models[TeamTagsCollectionName] || model(TeamTagsCollectionName, TeamTagsSchema);

View File

@@ -1,3 +1,8 @@
/*
user sub plan
1. type=standard: There will only be 1, and each team will have one
2. type=extraDatasetSize/extraPoints: Can buy multiple
*/
import { connectionMongo, type Model } from '../../../common/mongo';
const { Schema, model, models } = connectionMongo;
import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant';
@@ -23,25 +28,10 @@ const SubSchema = new Schema({
required: true
},
status: {
// active: continue sub; canceled: canceled sub;
type: String,
enum: Object.keys(subStatusMap),
required: true
},
mode: {
type: String,
enum: Object.keys(subModeMap)
},
currentMode: {
type: String,
enum: Object.keys(subModeMap),
required: true
},
nextMode: {
type: String,
enum: Object.keys(subModeMap),
required: true
},
startTime: {
type: Date,
default: () => new Date()
@@ -55,12 +45,16 @@ const SubSchema = new Schema({
type: Number,
required: true
},
pointPrice: {
// stand level point total price
type: Number
},
// sub content
// standard sub
currentMode: {
type: String,
enum: Object.keys(subModeMap)
},
nextMode: {
type: String,
enum: Object.keys(subModeMap)
},
currentSubLevel: {
type: String,
enum: Object.keys(standardSubLevelMap)
@@ -69,79 +63,32 @@ const SubSchema = new Schema({
type: String,
enum: Object.keys(standardSubLevelMap)
},
// stand sub and extra points sub. Plan total points
totalPoints: {
type: Number
},
pointPrice: {
// stand level point total price
type: Number
},
surplusPoints: {
// plan surplus points
type: Number
},
// extra dataset size
currentExtraDatasetSize: {
type: Number
},
nextExtraDatasetSize: {
type: Number
},
currentExtraPoints: {
type: Number
},
nextExtraPoints: {
type: Number
},
// standard sub limit
// maxTeamMember: {
// type: Number
// },
// maxAppAmount: {
// type: Number
// },
// maxDatasetAmount: {
// type: Number
// },
// chatHistoryStoreDuration: {
// // n day
// type: Number
// },
// maxDatasetSize: {
// type: Number
// },
// trainingWeight: {
// // 0 1 2 3
// type: Number
// },
// customApiKey: {
// type: Boolean
// },
// customCopyright: {
// type: Boolean
// },
// websiteSyncInterval: {
// // hours
// type: Number
// },
// reRankWeight: {
// // 0 1 2 3
// type: Number
// },
// totalPoints: {
// // record standard sub points
// type: Number
// },
surplusPoints: {
// standard sub / extra points sub
type: Number
},
// abandon
renew: Boolean, //决定是否续费
datasetStoreAmount: Number
}
});
try {
SubSchema.index({ teamId: 1 });
SubSchema.index({ status: 1 });
SubSchema.index({ type: 1 });
SubSchema.index({ expiredTime: -1 });
// get team plan
SubSchema.index({ teamId: 1, type: 1, expiredTime: -1 });
// timer task. check expired plan; update standard plan;
SubSchema.index({ type: 1, currentSubLevel: 1, expiredTime: -1 });
} catch (error) {
console.log(error);
}

View File

@@ -1,87 +1,147 @@
import { SubTypeEnum } from '@fastgpt/global/support/wallet/sub/constants';
import {
StandardSubLevelEnum,
SubModeEnum,
SubStatusEnum,
SubTypeEnum
} from '@fastgpt/global/support/wallet/sub/constants';
import { MongoTeamSub } from './schema';
import { addHours } from 'date-fns';
import { FeTeamSubType, StandSubPlanLevelMapType } from '@fastgpt/global/support/wallet/sub/type.d';
import { FeTeamPlanStatusType } from '@fastgpt/global/support/wallet/sub/type.d';
import { getVectorCountByTeamId } from '../../../common/vectorStore/controller';
import dayjs from 'dayjs';
import { ClientSession } from '../../../common/mongo';
import { addMonths } from 'date-fns';
/* get team dataset max size */
export const getTeamDatasetMaxSize = async ({
teamId,
standardPlans
}: {
teamId: string;
standardPlans?: StandSubPlanLevelMapType;
}) => {
if (!standardPlans) {
return {
maxSize: Infinity,
sub: null
};
}
export const getStandardPlans = () => {
return global?.subPlans?.standard;
};
export const getStandardPlan = (level: `${StandardSubLevelEnum}`) => {
return global.subPlans?.standard?.[level];
};
const plans = await MongoTeamSub.find({
teamId,
expiredTime: { $gte: addHours(new Date(), -3) }
}).lean();
const standard = plans.find((plan) => plan.type === SubTypeEnum.standard);
const extraDatasetSize = plans.find((plan) => plan.type === SubTypeEnum.extraDatasetSize);
const standardMaxDatasetSize =
standard?.currentSubLevel && standardPlans
? standardPlans[standard.currentSubLevel]?.maxDatasetSize || Infinity
: Infinity;
const totalDatasetSize =
standardMaxDatasetSize + (extraDatasetSize?.currentExtraDatasetSize || 0);
export const getTeamStandPlan = async ({ teamId }: { teamId: string }) => {
const standardPlans = global.subPlans?.standard;
const standard = await MongoTeamSub.findOne({ teamId, type: SubTypeEnum.standard }).lean();
return {
maxSize: totalDatasetSize,
sub: extraDatasetSize
[SubTypeEnum.standard]: standard,
standardConstants:
standard?.currentSubLevel && standardPlans
? standardPlans[standard.currentSubLevel]
: undefined
};
};
export const getTeamSubPlanStatus = async ({
export const initTeamStandardPlan2Free = async ({
teamId,
standardPlans
session
}: {
teamId: string;
standardPlans?: StandSubPlanLevelMapType;
}): Promise<FeTeamSubType> => {
session?: ClientSession;
}) => {
const freePoints = global?.subPlans?.standard?.free?.totalPoints || 100;
const teamStandardSub = await MongoTeamSub.findOne({ teamId, type: SubTypeEnum.standard });
if (teamStandardSub) {
teamStandardSub.status = SubStatusEnum.active;
teamStandardSub.currentMode = SubModeEnum.month;
teamStandardSub.nextMode = SubModeEnum.month;
teamStandardSub.startTime = new Date();
teamStandardSub.expiredTime = addMonths(new Date(), 1);
teamStandardSub.currentSubLevel = StandardSubLevelEnum.free;
teamStandardSub.nextSubLevel = StandardSubLevelEnum.free;
teamStandardSub.price = 0;
teamStandardSub.pointPrice = 0;
teamStandardSub.totalPoints = freePoints;
teamStandardSub.surplusPoints =
teamStandardSub.surplusPoints && teamStandardSub.surplusPoints < 0
? teamStandardSub.surplusPoints + freePoints
: freePoints;
return teamStandardSub.save({ session });
}
return MongoTeamSub.create(
[
{
teamId,
type: SubTypeEnum.standard,
status: SubStatusEnum.active,
currentMode: SubModeEnum.month,
nextMode: SubModeEnum.month,
startTime: new Date(),
expiredTime: addMonths(new Date(), 1),
price: 0,
pointPrice: 0,
currentSubLevel: StandardSubLevelEnum.free,
nextSubLevel: StandardSubLevelEnum.free,
totalPoints: freePoints,
surplusPoints: freePoints
}
],
{ session }
);
};
export const getTeamPlanStatus = async ({
teamId
}: {
teamId: string;
}): Promise<FeTeamPlanStatusType> => {
const standardPlans = global.subPlans?.standard;
const [plans, usedDatasetSize] = await Promise.all([
MongoTeamSub.find({ teamId }).lean(),
getVectorCountByTeamId(teamId)
]);
const standard = plans.find((plan) => plan.type === SubTypeEnum.standard);
const extraDatasetSize = plans.find((plan) => plan.type === SubTypeEnum.extraDatasetSize);
const extraPoints = plans.find((plan) => plan.type === SubTypeEnum.extraPoints);
const extraDatasetSize = plans.filter((plan) => plan.type === SubTypeEnum.extraDatasetSize);
const extraPoints = plans.filter((plan) => plan.type === SubTypeEnum.extraPoints);
// Free user, first login after expiration. The free subscription plan will be reset
if (
standard &&
standard.expiredTime &&
standard.currentSubLevel === StandardSubLevelEnum.free &&
dayjs(standard.expiredTime).isBefore(new Date())
) {
console.log('Init free stand plan', { teamId });
await initTeamStandardPlan2Free({ teamId });
return getTeamPlanStatus({ teamId });
}
const totalPoints = standardPlans
? (standard?.totalPoints || 0) +
extraPoints.reduce((acc, cur) => acc + (cur.totalPoints || 0), 0)
: Infinity;
const surplusPoints =
(standard?.surplusPoints || 0) +
extraPoints.reduce((acc, cur) => acc + (cur.surplusPoints || 0), 0);
const standardMaxDatasetSize =
standard?.currentSubLevel && standardPlans
? standardPlans[standard.currentSubLevel]?.maxDatasetSize || Infinity
: Infinity;
const totalDatasetSize =
standardMaxDatasetSize + (extraDatasetSize?.currentExtraDatasetSize || 0);
const standardMaxPoints =
standard?.currentSubLevel && standardPlans
? standardPlans[standard.currentSubLevel]?.totalPoints || Infinity
: Infinity;
const totalPoints = standardMaxPoints + (extraPoints?.currentExtraPoints || 0);
const surplusPoints = (standard?.surplusPoints || 0) + (extraPoints?.surplusPoints || 0);
standardMaxDatasetSize +
extraDatasetSize.reduce((acc, cur) => acc + (cur.currentExtraDatasetSize || 0), 0);
return {
[SubTypeEnum.standard]: standard,
[SubTypeEnum.extraDatasetSize]: extraDatasetSize,
[SubTypeEnum.extraPoints]: extraPoints,
standardConstants:
standard?.currentSubLevel && standardPlans
? standardPlans[standard.currentSubLevel]
: undefined,
standardMaxDatasetSize,
datasetMaxSize: totalDatasetSize,
usedDatasetSize,
standardMaxPoints,
totalPoints,
usedPoints: totalPoints - surplusPoints
usedPoints: totalPoints - surplusPoints,
datasetMaxSize: totalDatasetSize,
usedDatasetSize
};
};

View File

@@ -1,8 +1,8 @@
import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants';
import { MongoBill } from './schema';
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
import { MongoUsage } from './schema';
import { ClientSession } from '../../../common/mongo';
export const createTrainingBill = async ({
export const createTrainingUsage = async ({
teamId,
tmbId,
appName,
@@ -14,33 +14,33 @@ export const createTrainingBill = async ({
teamId: string;
tmbId: string;
appName: string;
billSource: `${BillSourceEnum}`;
billSource: `${UsageSourceEnum}`;
vectorModel: string;
agentModel: string;
session?: ClientSession;
}) => {
const [{ _id }] = await MongoBill.create(
const [{ _id }] = await MongoUsage.create(
[
{
teamId,
tmbId,
appName,
source: billSource,
totalPoints: 0,
list: [
{
moduleName: 'wallet.moduleName.index',
moduleName: 'support.wallet.moduleName.index',
model: vectorModel,
charsLength: 0,
amount: 0
},
{
moduleName: 'wallet.moduleName.qa',
moduleName: 'support.wallet.moduleName.qa',
model: agentModel,
charsLength: 0,
amount: 0
}
],
total: 0
]
}
],
{ session }

View File

@@ -1,13 +1,15 @@
import { connectionMongo, type Model } from '../../../common/mongo';
const { Schema, model, models } = connectionMongo;
import { BillSchema as BillType } from '@fastgpt/global/support/wallet/bill/type';
import { BillSourceMap } from '@fastgpt/global/support/wallet/bill/constants';
import { UsageSchemaType } from '@fastgpt/global/support/wallet/usage/type';
import { UsageSourceMap } from '@fastgpt/global/support/wallet/usage/constants';
import {
TeamCollectionName,
TeamMemberCollectionName
} from '@fastgpt/global/support/user/team/constant';
const BillSchema = new Schema({
export const UsageCollectionName = 'usages';
const UsageSchema = new Schema({
teamId: {
type: Schema.Types.ObjectId,
ref: TeamCollectionName,
@@ -18,6 +20,11 @@ const BillSchema = new Schema({
ref: TeamMemberCollectionName,
required: true
},
source: {
type: String,
enum: Object.keys(UsageSourceMap),
required: true
},
appName: {
type: String,
default: ''
@@ -31,16 +38,16 @@ const BillSchema = new Schema({
type: Date,
default: () => new Date()
},
total: {
// 1 * PRICE_SCALE
totalPoints: {
// total points
type: Number,
required: true
},
source: {
type: String,
enum: Object.keys(BillSourceMap),
required: true
},
// total: {
// // total points
// type: Number,
// required: true
// },
list: {
type: Array,
default: []
@@ -48,11 +55,15 @@ const BillSchema = new Schema({
});
try {
BillSchema.index({ teamId: 1, tmbId: 1, source: 1, time: -1 }, { background: true });
BillSchema.index({ time: 1 }, { expireAfterSeconds: 180 * 24 * 60 * 60 });
UsageSchema.index({ teamId: 1, tmbId: 1, source: 1, time: -1 }, { background: true });
// timer task. clear dead team
UsageSchema.index({ teamId: 1, time: -1 }, { background: true });
UsageSchema.index({ time: 1 }, { expireAfterSeconds: 180 * 24 * 60 * 60 });
} catch (error) {
console.log(error);
}
export const MongoBill: Model<BillType> = models['bill'] || model('bill', BillSchema);
MongoBill.syncIndexes();
export const MongoUsage: Model<UsageSchemaType> =
models[UsageCollectionName] || model(UsageCollectionName, UsageSchema);
MongoUsage.syncIndexes();

View File

@@ -1,3 +1,20 @@
import { FastGPTFeConfigsType } from '@fastgpt/global/common/system/types';
import {
AudioSpeechModelType,
ReRankModelItemType,
WhisperModelType,
VectorModelItemType,
LLMModelItemType
} from '@fastgpt/global/core/ai/model.d';
import { SubPlanType } from '@fastgpt/global/support/wallet/sub/type';
declare global {
var defaultTeamDatasetLimit: number;
var feConfigs: FastGPTFeConfigsType;
var subPlans: SubPlanType | undefined;
var llmModels: LLMModelItemType[];
var vectorModels: VectorModelItemType[];
var audioSpeechModels: AudioSpeechModelType[];
var whisperModel: WhisperModelType;
var reRankModels: ReRankModelItemType[];
}