Commit a71e1ce4 by 赵月辉

feat: 迁移 lufa 定制 - 核心服务层(packages/)

基于 FastGPT v4.14.30 三向合并(base=v4.12.1, ours=官方最新, theirs=lufa定制):
- AI 配置:重新应用 userKey 定制(embedding/speech/createQuestionGuide),适配新版 createLLMResponse API
- 权限系统:保留 lufa auth 函数(parseHeaderCert/setCookie/clearCookie),适配官方重构的权限计算逻辑
- vectorDB/dataset:合并双方改动,保留 userKey 与官方新版结构
- i18n:合并所有 key,保留 lufa 品牌文案定制
- outLink/user/workflow:按定制类型分别合并
parent 1951a03f
...@@ -8,10 +8,7 @@ export const parseParentIdInMongo = (parentId: ParentIdType) => { ...@@ -8,10 +8,7 @@ export const parseParentIdInMongo = (parentId: ParentIdType) => {
parentId: null parentId: null
}; };
const pattern = /^[0-9a-fA-F]{24}$/; return {
if (pattern.test(parentId)) parentId
return { };
parentId
};
return {};
}; };
...@@ -17,6 +17,12 @@ export const ChatRoleMap = { ...@@ -17,6 +17,12 @@ export const ChatRoleMap = {
} }
}; };
export enum ChatFeedbackStatusEnum {
Pending = '待审核',
Stored = '已入库',
Ignored = '已忽略'
}
export enum ChatFileTypeEnum { export enum ChatFileTypeEnum {
image = 'image', image = 'image',
audio = 'audio', audio = 'audio',
......
import { ClassifyQuestionAgentItemType } from '../workflow/template/system/classifyQuestion/type';
import type { SearchDataResponseItemType } from '../dataset/type';
import type {
ChatFileTypeEnum,
ChatItemValueTypeEnum,
ChatRoleEnum,
ChatSourceEnum,
ChatStatusEnum
} from './constants';
import type { FlowNodeTypeEnum } from '../workflow/node/constant';
import type { NodeInputKeyEnum, NodeOutputKeyEnum } from '../workflow/constants';
import type { DispatchNodeResponseKeyEnum } from '../workflow/runtime/constants';
import type { AppSchema, VariableItemType } from '../app/type';
import { AppChatConfigType } from '../app/type';
import type { AppSchema as AppType } from '@fastgpt/global/core/app/type.d';
import { DatasetSearchModeEnum } from '../dataset/constants';
import type { DispatchNodeResponseType } from '../workflow/runtime/type.d';
import type { ChatBoxInputType } from '../../../../projects/app/src/components/core/chat/ChatContainer/ChatBox/type';
import type { WorkflowInteractiveResponseType } from '../workflow/template/system/interactive/type';
import type { FlowNodeInputItemType } from '../workflow/type/io';
import type { FlowNodeTemplateType } from '../workflow/type/node.d';
export type ChatSchemaType = {
_id: string;
chatId: string;
userId: string;
teamId: string;
tmbId: string;
appId: string;
createTime: Date;
updateTime: Date;
title: string;
customTitle: string;
top: boolean;
source: `${ChatSourceEnum}`;
sourceName?: string;
shareId?: string;
outLinkUid?: string;
variableList?: VariableItemType[];
welcomeText?: string;
variables: Record<string, any>;
pluginInputs?: FlowNodeInputItemType[];
metadata?: Record<string, any>;
};
export type ChatWithAppSchema = Omit<ChatSchemaType, 'appId'> & {
appId: AppSchema;
};
export type UserChatItemValueItemType = {
type: ChatItemValueTypeEnum.text | ChatItemValueTypeEnum.file;
text?: {
content: string;
};
file?: {
type: `${ChatFileTypeEnum}`;
name?: string;
url: string;
};
};
export type UserChatItemType = {
obj: ChatRoleEnum.Human;
value: UserChatItemValueItemType[];
hideInUI?: boolean;
};
export type SystemChatItemValueItemType = {
type: ChatItemValueTypeEnum.text;
text?: {
content: string;
};
};
export type SystemChatItemType = {
obj: ChatRoleEnum.System;
value: SystemChatItemValueItemType[];
};
export type AIChatItemValueItemType = {
type:
| ChatItemValueTypeEnum.text
| ChatItemValueTypeEnum.reasoning
| ChatItemValueTypeEnum.tool
| ChatItemValueTypeEnum.interactive;
text?: {
content: string;
};
reasoning?: {
content: string;
};
tools?: ToolModuleResponseItemType[];
interactive?: WorkflowInteractiveResponseType;
};
export type AIChatItemType = {
obj: ChatRoleEnum.AI;
value: AIChatItemValueItemType[];
memories?: Record<string, any>;
userGoodFeedback?: string;
userBadFeedback?: string;
customFeedbacks?: string[];
adminFeedback?: AdminFbkType;
[DispatchNodeResponseKeyEnum.nodeResponse]?: ChatHistoryItemResType[];
};
export type ChatItemValueItemType =
| UserChatItemValueItemType
| SystemChatItemValueItemType
| AIChatItemValueItemType;
export type ChatItemSchema = (UserChatItemType | SystemChatItemType | AIChatItemType) & {
dataId: string;
chatId: string;
userId: string;
teamId: string;
tmbId: string;
appId: string;
time: Date;
durationSeconds?: number;
errorMsg?: string;
};
export type ChatFeedbackLogSchema = (UserChatItemType | SystemChatItemType | AIChatItemType) & {
dataId: string;
chatId: string;
userId: string;
teamId: string;
tmbId: string;
appId: string;
time: Date;
qvalue: UserChatItemValueItemType[];
status: string;
responseData?: ChatHistoryItemResType[];
// Additional properties used in the application
appName?: string;
datasetName?: string;
datasetList?: Array<{ datasetId: string; name?: string }>;
q?: string;
a?: string;
};
export type ChatFeedbackLogRelationSchema = {
datasetId: string;
collectionId: string;
};
export type AdminFbkType = {
feedbackDataId: string;
datasetId: string;
collectionId: string;
q: string;
a?: string;
};
/* --------- chat item ---------- */
export type ResponseTagItemType = {
totalQuoteList?: SearchDataResponseItemType[];
llmModuleAccount?: number;
historyPreviewLength?: number;
toolCiteLinks?: ToolCiteLinksType[];
};
export type ChatItemType = (UserChatItemType | SystemChatItemType | AIChatItemType) & {
dataId?: string;
} & ResponseTagItemType;
// Frontend type
export type ChatSiteItemType = (UserChatItemType | SystemChatItemType | AIChatItemType) & {
_id?: string;
dataId: string;
status: `${ChatStatusEnum}`;
moduleName?: string;
ttsBuffer?: Uint8Array;
responseData?: ChatHistoryItemResType[];
time?: Date;
durationSeconds?: number;
errorMsg?: string;
} & ChatBoxInputType &
ResponseTagItemType;
/* --------- team chat --------- */
export type ChatAppListSchema = {
apps: AppType[];
teamInfo: teamInfoSchema;
uid?: string;
};
/* ---------- history ------------- */
export type HistoryItemType = {
chatId: string;
updateTime: Date;
customTitle?: string;
title: string;
};
export type ChatHistoryItemType = HistoryItemType & {
appId: string;
top: boolean;
};
/* ------- response data ------------ */
export type ChatHistoryItemResType = DispatchNodeResponseType & {
nodeId: string;
id: string;
moduleType: FlowNodeTypeEnum;
moduleName: string;
};
/* ---------- node outputs ------------ */
export type NodeOutputItemType = {
nodeId: string;
key: NodeOutputKeyEnum;
value: any;
};
/* One tool run response */
export type ToolRunResponseItemType = any;
/* tool module response */
export type ToolModuleResponseItemType = {
id: string;
toolName: string; // tool name
toolAvatar: string;
params: string; // tool params
response: string;
functionName: string;
};
export type ToolCiteLinksType = {
name: string;
url: string;
};
/* dispatch run time */
export type RuntimeUserPromptType = {
files: UserChatItemValueItemType['file'][];
text: string;
};
...@@ -120,6 +120,7 @@ export enum NodeInputKeyEnum { ...@@ -120,6 +120,7 @@ export enum NodeInputKeyEnum {
// system config // system config
questionGuide = 'questionGuide', questionGuide = 'questionGuide',
showToolCall = 'showToolCall',
tts = 'tts', tts = 'tts',
whisper = 'whisper', whisper = 'whisper',
variables = 'variables', variables = 'variables',
......
import type { HistoryItemType } from '../../core/chat/type.d';
import type { OutLinkSchema } from './type.d';
export type AuthOutLinkInitProps = {
outLinkUid: string;
tokenUrl?: string;
};
export type AuthOutLinkChatProps = { ip?: string | null; outLinkUid: string; question: string };
export type AuthOutLinkLimitProps = AuthOutLinkChatProps & { outLink: OutLinkSchema };
export type AuthOutLinkResponse = {
uid: string;
};
export type PostShareLoginProps = {
shareId: string;
password: string;
};
export enum PublishChannelEnum { export enum PublishChannelEnum {
share = 'share', share = 'share',
authShare = 'authShare',
iframe = 'iframe', iframe = 'iframe',
apikey = 'apikey', apikey = 'apikey',
feishu = 'feishu', feishu = 'feishu',
......
import { AppSchema } from '../../core/app/type';
import type { PublishChannelEnum } from './constant';
// Feishu Config interface
export interface FeishuAppType {
appId: string;
appSecret: string;
// Encrypt config
// refer to: https://open.feishu.cn/document/server-docs/event-subscription-guide/event-subscription-configure-/configure-encrypt-key
encryptKey?: string; // no secret if null
// Token Verification
// refer to: https://open.feishu.cn/document/server-docs/event-subscription-guide/event-subscription-configure-/encrypt-key-encryption-configuration-case
verificationToken?: string;
}
export interface DingtalkAppType {
clientId: string;
clientSecret: string;
}
export interface WecomAppType {
AgentId: string;
CorpId: string;
SuiteSecret: string;
CallbackToken: string;
CallbackEncodingAesKey: string;
}
// TODO: unused
export interface WechatAppType {}
export interface OffiAccountAppType {
appId: string;
isVerified?: boolean; // if isVerified, we could use '客服接口' to reply
secret: string;
CallbackToken: string;
CallbackEncodingAesKey?: string;
timeoutReply?: string; // if timeout (15s), will reply this content.
// timeout reply is optional, but when isVerified is false, the wechat will reply a default message which is `该公众号暂时无法提供服务,请稍后再试`
// because we can not reply anything in 15s. Thus, the wechat server will treat this request as a failed request.
}
export type OutlinkAppType =
| FeishuAppType
| WecomAppType
| OffiAccountAppType
| DingtalkAppType
| undefined;
export type OutLinkSchema<T extends OutlinkAppType = undefined> = {
_id: string;
shareId: string;
teamId: string;
tmbId: string;
appId: string;
name: string;
usagePoints: number;
lastTime: Date;
type: PublishChannelEnum;
// whether the response content is detailed
responseDetail: boolean;
// whether to hide the node status
showNodeStatus?: boolean;
// wheter to show the full text reader
// showFullText?: boolean;
// whether to show the complete quote
showRawSource?: boolean;
// response when request
immediateResponse?: string;
// response when error or other situation
defaultResponse?: string;
limit?: {
expiredTime?: Date;
// Questions per minute
QPM: number;
maxUsagePoints: number;
// Verification message hook url
hookUrl?: string;
password?: string;
};
app: T;
};
// Edit the Outlink
export type OutLinkEditType<T = undefined> = {
_id?: string;
name: string;
responseDetail?: OutLinkSchema<T>['responseDetail'];
showNodeStatus?: OutLinkSchema<T>['showNodeStatus'];
// showFullText?: OutLinkSchema<T>['showFullText'];
showRawSource?: OutLinkSchema<T>['showRawSource'];
// response when request
immediateResponse?: string;
// response when error or other situation
defaultResponse?: string;
limit?: OutLinkSchema<T>['limit'];
// config for specific platform
app?: T;
};
...@@ -45,6 +45,11 @@ export const PermissionTypeMap = { ...@@ -45,6 +45,11 @@ export const PermissionTypeMap = {
} }
}; };
export enum PerCollaboratorTypeEnum {
app = 'app',
dataset = 'dataset'
}
export enum PerResourceTypeEnum { export enum PerResourceTypeEnum {
team = 'team', team = 'team',
app = 'app', app = 'app',
......
import type { UserModelSchema } from '../type';
import type { TeamMemberRoleEnum, TeamMemberStatusEnum } from './constant';
import type { LafAccountType } from './type';
import { PermissionValueType, ResourcePermissionType } from '../../permission/type';
import type { TeamPermission } from '../../permission/user/controller';
export type ThirdPartyAccountType = {
lafAccount?: LafAccountType;
openaiAccount?: OpenaiAccountType;
externalWorkflowVariables?: Record<string, string>;
};
export type TeamSchema = {
_id: string;
name: string;
ownerId: string;
avatar: string;
createTime: Date;
balance: number;
teamDomain: string;
limit: {
lastExportDatasetTime: Date;
lastWebsiteSyncTime: Date;
};
notificationAccount?: string;
} & ThirdPartyAccountType;
export type tagsType = {
label: string;
key: string;
};
export type TeamTagSchema = TeamTagItemType & {
_id: string;
teamId: string;
createTime: Date;
updateTime?: Date;
};
export type TeamMemberSchema = {
_id: string;
teamId: string;
userId: string;
createTime: Date;
updateTime?: Date;
name: string;
role: `${TeamMemberRoleEnum}` | 'admin';
status: `${TeamMemberStatusEnum}`;
avatar: string;
};
export type TeamMemberWithTeamAndUserSchema = TeamMemberSchema & {
team: TeamSchema;
user: UserModelSchema;
};
export type TeamTmbItemType = {
userId: string;
teamId: string;
teamAvatar?: string;
teamName: string;
memberName: string;
avatar: string;
balance?: number;
tmbId: string;
teamDomain: string;
role: `${TeamMemberRoleEnum}` | 'admin';
status: `${TeamMemberStatusEnum}`;
notificationAccount?: string;
permission: TeamPermission;
} & ThirdPartyAccountType;
export type TeamMemberItemType<
Options extends {
withPermission?: boolean;
withOrgs?: boolean;
withGroupRole?: boolean;
} = { withPermission: true; withOrgs: true; withGroupRole: false }
> = {
userId: string;
tmbId: string;
teamId: string;
memberName: string;
avatar: string;
role: `${TeamMemberRoleEnum}` | 'admin';
status: `${TeamMemberStatusEnum}`;
contact?: string;
createTime: Date;
updateTime?: Date;
} & (Options extends { withPermission: true }
? {
permission: TeamPermission;
}
: {}) &
(Options extends { withOrgs: true }
? {
orgs?: string[]; // full path name, pattern: /teamName/orgname1/orgname2
}
: {}) &
(Options extends { withGroupRole: true }
? {
groupRole?: `${GroupMemberRole}`;
}
: {});
export type TeamTagItemType = {
label: string;
key: string;
};
export type LafAccountType = {
appid: string;
token: string;
pat: string;
};
export type OpenaiAccountType = {
key: string;
baseUrl: string;
};
export type TeamInvoiceHeaderType = {
teamName: string;
unifiedCreditCode: string;
companyAddress?: string;
companyPhone?: string;
bankName?: string;
bankAccount?: string;
needSpecialInvoice: boolean;
contactPhone: string;
emailAddress: string;
};
export type TeamInvoiceHeaderInfoSchemaType = TeamInvoiceHeaderType & {
_id: string;
teamId: string;
};
import { i18nT } from '../../../common/i18n/utils'; import { i18nT } from '../../../common/i18n/utils';
export enum UsageSourceEnum { export enum UsageSourceEnum {
fastgpt = 'fastgpt', fastgpt = 'aiagentsflow',
api = 'api', api = 'api',
shareLink = 'shareLink', shareLink = 'shareLink',
training = 'training', training = 'training',
......
import { Client as PgClient } from 'pg'; // PostgreSQL 客户端
import mysql from 'mysql2/promise'; // MySQL 客户端
import mssql from 'mssql'; // SQL Server 客户端
type Props = {
databaseType: string;
host: string;
port: string;
databaseName: string;
user: string;
password: string;
sql: string;
};
type Response = Promise<{
result: any; // 根据你的 SQL 查询结果类型调整
}>;
const main = async ({
databaseType,
host,
port,
databaseName,
user,
password,
sql
}: Props): Response => {
let result;
try {
if (databaseType === 'PostgreSQL') {
const client = new PgClient({
host,
port: parseInt(port, 10),
database: databaseName,
user,
password,
connectionTimeoutMillis: 30000
});
await client.connect();
const res = await client.query(sql);
result = res.rows;
await client.end();
} else if (databaseType === 'MySQL') {
const connection = await mysql.createConnection({
host,
port: parseInt(port, 10),
database: databaseName,
user,
password,
connectTimeout: 30000
});
const [rows] = await connection.execute(sql);
result = rows;
await connection.end();
} else if (databaseType === 'Microsoft SQL Server') {
const pool = await mssql.connect({
server: host,
port: parseInt(port, 10),
database: databaseName,
user,
password,
options: {
trustServerCertificate: true
},
connectionTimeout: 360000,
requestTimeout: 360000
});
result = await pool.query(sql);
await pool.close();
}
return {
result
};
} catch (error: unknown) {
// 使用类型断言来处理错误
if (error instanceof Error) {
console.error('Database query error:', error.message);
return Promise.reject(error.message);
}
console.error('Database query error:', error);
return Promise.reject('An unknown error occurred');
}
};
export default main;
import { redisConnect } from './redisConnection';
import { performance } from 'perf_hooks';
const startPerfTimer = (): number => {
return performance.now();
};
const endPerfTimer = (): number => {
return performance.now();
};
const calculatePerformance = (startTime: number, endTime: number): void => {
console.log(`Response took ${endTime - startTime} milliseconds`);
};
export const fetchCache = async (
key: string,
fetchData: () => Promise<unknown | null | undefined>,
expiresIn: number
) => {
startPerfTimer();
const cachedData = await getKey(key);
if (cachedData) {
console.log('Fetched from cache');
calculatePerformance(startPerfTimer(), endPerfTimer());
return cachedData;
}
console.log('Fetched from API');
calculatePerformance(startPerfTimer(), endPerfTimer());
return setValue(key, fetchData, expiresIn);
};
const getKey = async <T>(key: string): Promise<T | null> => {
const result = await redisConnect.get(key);
if (result) return JSON.parse(result);
endPerfTimer();
return null;
};
const setValue = async <T>(
key: string,
fetchData: () => Promise<T>,
expiresIn: number
): Promise<T> => {
const setValue = await fetchData();
await redisConnect.set(key, JSON.stringify(setValue), 'EX', expiresIn);
endPerfTimer();
return setValue;
};
import Redis from 'ioredis';
const REDIS_URL = process.env.REDIS_URL ?? 'redis://localhost:6379';
export const redisConnect = new Redis(REDIS_URL);
...@@ -17,6 +17,7 @@ import { ...@@ -17,6 +17,7 @@ import {
} from './constants'; } from './constants';
import { MilvusCtrl } from './milvus'; import { MilvusCtrl } from './milvus';
import { retryFn } from '@fastgpt/global/common/system/utils'; import { retryFn } from '@fastgpt/global/common/system/utils';
import { MongoTeam } from '../../support/user/team/teamSchema';
import { getLogger, LogCategories } from '../logger'; import { getLogger, LogCategories } from '../logger';
const getVectorObj = (): VectorControllerType => { const getVectorObj = (): VectorControllerType => {
...@@ -62,6 +63,10 @@ export const insertDatasetDataVector = async ({ ...@@ -62,6 +63,10 @@ export const insertDatasetDataVector = async ({
}; };
} }
// 获取团队的openaiAccount信息
const team = await MongoTeam.findById(props.teamId, 'openaiAccount').lean();
const userKey = team?.openaiAccount;
const embeddingInputs = inputs.map((input) => const embeddingInputs = inputs.map((input) =>
typeof input === 'string' typeof input === 'string'
? { ? {
...@@ -73,7 +78,8 @@ export const insertDatasetDataVector = async ({ ...@@ -73,7 +78,8 @@ export const insertDatasetDataVector = async ({
const { vectors, tokens } = await getVectors({ const { vectors, tokens } = await getVectors({
model, model,
inputs: embeddingInputs, inputs: embeddingInputs,
type: 'db' type: 'db',
userKey
}); });
const { insertIds } = await retryFn(() => const { insertIds } = await retryFn(() =>
Vector.insert({ Vector.insert({
......
...@@ -10,7 +10,8 @@ export async function text2Speech({ ...@@ -10,7 +10,8 @@ export async function text2Speech({
input, input,
model, model,
voice, voice,
speed = 1 speed = 1,
userKey
}: { }: {
res: NodeHttpResponse; res: NodeHttpResponse;
onSuccess: (e: { model: string; buffer: Buffer }) => void; onSuccess: (e: { model: string; buffer: Buffer }) => void;
...@@ -19,9 +20,10 @@ export async function text2Speech({ ...@@ -19,9 +20,10 @@ export async function text2Speech({
model: string; model: string;
voice: string; voice: string;
speed?: number; speed?: number;
userKey?: any;
}) { }) {
const modelData = getTTSModel(model)!; const modelData = getTTSModel(model)!;
const { ai } = getAIApi(); const { ai } = getAIApi({ userKey });
const response = await ai.audio.speech.create( const response = await ai.audio.speech.create(
{ {
model, model,
......
...@@ -13,6 +13,7 @@ type GetVectorsBaseProps = { ...@@ -13,6 +13,7 @@ type GetVectorsBaseProps = {
model: EmbeddingModelItemType; model: EmbeddingModelItemType;
type?: `${EmbeddingTypeEnm}`; type?: `${EmbeddingTypeEnm}`;
headers?: Record<string, string>; headers?: Record<string, string>;
userKey?: any;
}; };
const InputItemSchema = z.object({ const InputItemSchema = z.object({
...@@ -43,7 +44,7 @@ const countInputTokens = async (input: GetVectorInputItem) => { ...@@ -43,7 +44,7 @@ const countInputTokens = async (input: GetVectorInputItem) => {
return countPromptTokens(input.input); return countPromptTokens(input.input);
}; };
export async function getVectors({ model, inputs: rawInputs, type, headers }: GetVectorsProps) { export async function getVectors({ model, inputs: rawInputs, type, headers, userKey }: GetVectorsProps) {
const validatedInputs = z const validatedInputs = z
.array(InputItemSchema) .array(InputItemSchema)
.parse(rawInputs) .parse(rawInputs)
...@@ -88,7 +89,7 @@ export async function getVectors({ model, inputs: rawInputs, type, headers }: Ge ...@@ -88,7 +89,7 @@ export async function getVectors({ model, inputs: rawInputs, type, headers }: Ge
}); });
} }
const { ai } = getAIApi(); const { ai } = getAIApi({ timeout: 480000, userKey });
let chunkSize = Number(model.batchSize || 1); let chunkSize = Number(model.batchSize || 1);
chunkSize = isNaN(chunkSize) ? 1 : chunkSize; chunkSize = isNaN(chunkSize) ? 1 : chunkSize;
......
...@@ -14,12 +14,14 @@ export async function createQuestionGuide({ ...@@ -14,12 +14,14 @@ export async function createQuestionGuide({
messages, messages,
model, model,
customPrompt, customPrompt,
teamId teamId,
userKey
}: { }: {
messages: ChatCompletionMessageParam[]; messages: ChatCompletionMessageParam[];
model: string; model: string;
customPrompt?: string; customPrompt?: string;
teamId: string; teamId: string;
userKey?: any;
}): Promise<{ }): Promise<{
result: string[]; result: string[];
inputTokens: number; inputTokens: number;
...@@ -39,6 +41,7 @@ export async function createQuestionGuide({ ...@@ -39,6 +41,7 @@ export async function createQuestionGuide({
usage: { inputTokens, outputTokens } usage: { inputTokens, outputTokens }
} = await createLLMResponse({ } = await createLLMResponse({
teamId, teamId,
userKey,
saveLLMResponseRecord: false, saveLLMResponseRecord: false,
body: { body: {
model, model,
......
import {
TeamCollectionName,
TeamMemberCollectionName
} from '@fastgpt/global/support/user/team/constant';
import { AppCollectionName } from './schema';
import { DatasetCollectionName } from '../dataset/schema';
import { Schema, getMongoModel } from '../../common/mongo';
import { PerCollaboratorTypeEnum } from '@fastgpt/global/support/permission/constant';
export const CollaboratorPermissionCollectionName = 'collaborator_permissions';
export type CollaboratorPermissionType = {
avatar: string;
name: string;
tmbId: string;
appId: string;
datasetId: string;
permission: number;
};
// schema
const CollaboratorPermissionSchema = new Schema({
collaboratorType: {
type: String,
enum: Object.values(PerCollaboratorTypeEnum),
required: true
},
appId: {
type: Schema.Types.ObjectId,
ref: AppCollectionName,
default: null
},
datasetId: {
type: Schema.Types.ObjectId,
ref: DatasetCollectionName,
default: null
},
tmbId: {
type: Schema.Types.ObjectId,
ref: TeamMemberCollectionName,
required: true
},
permission: {
type: Number,
required: true
},
createTime: {
type: Date,
default: () => new Date()
}
});
CollaboratorPermissionSchema.index({ createTime: -1 });
CollaboratorPermissionSchema.index({ tmbId: 1 });
CollaboratorPermissionSchema.index({ tmbId: 1, appId: 1 });
CollaboratorPermissionSchema.index({ tmbId: 1, datasetId: 1 });
export const MongoCollaboratorPermission = getMongoModel<CollaboratorPermissionType>(
CollaboratorPermissionCollectionName,
CollaboratorPermissionSchema
);
import { RunToolWithStream } from '@fastgpt-sdk/plugin';
import { PluginSourceEnum } from '@fastgpt/global/core/app/plugin/constants';
import { pluginClient, BASE_URL, TOKEN } from '../../../thirdProvider/fastgptPlugin';
export async function APIGetSystemToolList() {
// 检查插件服务是否可用
if (!BASE_URL) {
console.log('Plugin service not configured, returning empty tool list');
return [];
}
try {
const res = await pluginClient.tool.list();
if (res.status === 200) {
return res.body.map((item) => {
return {
...item,
id: `${PluginSourceEnum.systemTool}-${item.id}`,
parentId: item.parentId ? `${PluginSourceEnum.systemTool}-${item.parentId}` : undefined,
avatar:
item.avatar && item.avatar.startsWith('/imgs/tools/')
? `/api/system/pluginImgs/${item.avatar.replace('/imgs/tools/', '')}`
: item.avatar
};
});
}
return Promise.reject(res.body);
} catch (error) {
console.error('Plugin service error:', error);
return [];
}
}
const runToolInstance = BASE_URL
? new RunToolWithStream({
baseUrl: BASE_URL,
token: TOKEN
})
: null;
export const APIRunSystemTool = async (params: {
toolId: string;
inputs: Record<string, any>;
systemVar: {
user: {
id: string;
username: string;
contact: string;
membername: string;
teamName: string;
teamId: string;
name: string;
};
app: {
id: string;
name: string;
};
tool: {
id: string;
version: string;
};
time: string;
};
onMessage: (message: { type: string; content: string }) => void;
}) => {
if (!runToolInstance) {
throw new Error('Plugin service not configured');
}
return runToolInstance.run(params);
};
import { connectionMongo, getMongoModel, type Model } from '../../common/mongo';
const { Schema, model, models } = connectionMongo;
import type { ChatFeedbackLogSchema as ChatFeedbackLogType } from '@fastgpt/global/core/chat/type';
import { ChatRoleMap, ChatFeedbackStatusEnum } from '@fastgpt/global/core/chat/constants';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import {
TeamCollectionName,
TeamMemberCollectionName
} from '@fastgpt/global/support/user/team/constant';
import { AppCollectionName } from '../app/schema';
import { userCollectionName } from '../../support/user/schema';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
export const ChatFeedbackLogCollectionName = 'chat_feedback_log';
const ChatFeedbackLogSchema = new Schema({
teamId: {
type: Schema.Types.ObjectId,
ref: TeamCollectionName,
required: true
},
tmbId: {
type: Schema.Types.ObjectId,
ref: TeamMemberCollectionName,
required: true
},
userId: {
type: Schema.Types.ObjectId,
ref: userCollectionName
},
chatId: {
type: String,
require: true
},
dataId: {
type: String,
require: true,
default: () => getNanoid(22)
},
appId: {
type: Schema.Types.ObjectId,
ref: AppCollectionName,
required: true
},
appName: {
type: String
},
datasetName: {
type: String
},
time: {
type: Date,
default: () => new Date()
},
hideInUI: {
type: Boolean,
default: false
},
obj: {
// chat role
type: String,
required: true,
enum: Object.keys(ChatRoleMap)
},
status: {
type: String,
enum: Object.values(ChatFeedbackStatusEnum),
default: ChatFeedbackStatusEnum.Pending
},
qvalue: {
// chat content
type: Array,
default: []
},
value: {
// chat content
type: Array,
default: []
},
userGoodFeedback: {
type: String
},
userBadFeedback: {
type: String
},
customFeedbacks: {
type: [String]
},
adminFeedback: {
type: {
datasetId: String,
collectionId: String,
dataId: String,
q: String,
a: String
}
},
[DispatchNodeResponseKeyEnum.nodeResponse]: {
type: Array,
default: []
},
q: {
type: String
},
a: {
type: String
},
datasetList: {
type: Array,
default: []
}
});
try {
ChatFeedbackLogSchema.index({ dataId: 1 });
/* delete by app;
delete by chat id;
get chat list;
get chat logs;
close custom feedback;
*/
ChatFeedbackLogSchema.index({ appId: 1, chatId: 1, dataId: 1 });
// admin charts
ChatFeedbackLogSchema.index({ time: -1, obj: 1 });
// timer, clear history
ChatFeedbackLogSchema.index({ teamId: 1, time: -1 });
// Admin charts
ChatFeedbackLogSchema.index({ obj: 1, time: -1 }, { partialFilterExpression: { obj: 'Human' } });
} catch (error) {
console.log(error);
}
export const MongoChatFeedbackLog = getMongoModel<ChatFeedbackLogType>(
ChatFeedbackLogCollectionName,
ChatFeedbackLogSchema
);
import { connectionMongo, getMongoModel, type Model } from '../../common/mongo';
const { Schema, model, models } = connectionMongo;
import type { ChatFeedbackLogRelationSchema as ChatFeedbackLogRelationType } from '@fastgpt/global/core/chat/type';
import { DatasetCollectionName } from '../dataset/schema';
import { DatasetColCollectionName } from '../dataset/collection/schema';
export const ChatFeedbackLogRelationCollectionName = 'chat_feedback_log_relation';
const ChatFeedbackLogRelationSchema = new Schema({
datasetId: {
type: Schema.Types.ObjectId,
ref: DatasetCollectionName,
required: true
},
collectionId: {
type: Schema.Types.ObjectId,
ref: DatasetColCollectionName,
required: true
}
});
try {
ChatFeedbackLogRelationSchema.index({ datasetId: 1, collectionId: 1 });
} catch (error) {
console.log(error);
}
export const MongoChatFeedbackLogRelation = getMongoModel<ChatFeedbackLogRelationType>(
ChatFeedbackLogRelationCollectionName,
ChatFeedbackLogRelationSchema
);
...@@ -15,18 +15,15 @@ import { getS3DatasetSource } from '../../common/s3/sources/dataset'; ...@@ -15,18 +15,15 @@ import { getS3DatasetSource } from '../../common/s3/sources/dataset';
/* ============= dataset ========== */ /* ============= dataset ========== */
/* find all datasetId by top datasetId */ /* find all datasetId by top datasetId */
export async function findDatasetAndAllChildren({ export async function findDatasetAndAllChildren({
teamId,
datasetId, datasetId,
fields fields
}: { }: {
teamId: string;
datasetId: string; datasetId: string;
fields?: string; fields?: string;
}): Promise<DatasetSchemaType[]> { }): Promise<DatasetSchemaType[]> {
const find = async (id: string) => { const find = async (id: string) => {
const children = await MongoDataset.find( const children = await MongoDataset.find(
{ {
teamId,
parentId: id parentId: id
}, },
fields fields
......
...@@ -8,12 +8,12 @@ import { ...@@ -8,12 +8,12 @@ import {
DatasetTypeMap, DatasetTypeMap,
ParagraphChunkAIModeEnum ParagraphChunkAIModeEnum
} from '@fastgpt/global/core/dataset/constants'; } from '@fastgpt/global/core/dataset/constants';
import type { DatasetSchemaType } from '@fastgpt/global/core/dataset/type.d';
import { import {
TeamCollectionName, TeamCollectionName,
TeamMemberCollectionName TeamMemberCollectionName
} from '@fastgpt/global/support/user/team/constant'; } from '@fastgpt/global/support/user/team/constant';
import { userCollectionName } from '../../support/user/schema'; import { userCollectionName } from '../../support/user/schema';
import type { DatasetSchemaType } from '@fastgpt/global/core/dataset/type';
export const DatasetCollectionName = 'datasets'; export const DatasetCollectionName = 'datasets';
...@@ -110,6 +110,10 @@ const DatasetSchema = new Schema({ ...@@ -110,6 +110,10 @@ const DatasetSchema = new Schema({
type: String, type: String,
default: '' default: ''
}, },
permissionType: {
type: String,
default: 'private'
},
websiteConfig: { websiteConfig: {
type: { type: {
url: { url: {
......
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
import { dispatchWorkFlow } from '../index';
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import {
getWorkflowEntryNodeIds,
storeEdges2RuntimeEdges,
rewriteNodeOutputByHistories,
storeNodes2RuntimeNodes,
textAdaptGptResponse
} from '@fastgpt/global/core/workflow/runtime/utils';
import type { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { filterSystemVariables, getHistories } from '../utils';
import { chatValue2RuntimePrompt, runtimePrompt2ChatsValue } from '@fastgpt/global/core/chat/adapt';
import type { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
import { authAppByTmbId } from '../../../../support/permission/app/auth';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { getAppVersionById } from '../../../app/version/controller';
import { parseUrlToFileType } from '@fastgpt/global/common/file/tools';
import type { ChildrenInteractive } from '@fastgpt/global/core/workflow/template/system/interactive/type';
type Props = ModuleDispatchProps<{
[NodeInputKeyEnum.userChatInput]: string;
[NodeInputKeyEnum.history]?: ChatItemType[] | number;
[NodeInputKeyEnum.fileUrlList]?: string[];
[NodeInputKeyEnum.forbidStream]?: boolean;
[NodeInputKeyEnum.fileUrlList]?: string[];
}>;
type Response = DispatchNodeResultType<{
[DispatchNodeResponseKeyEnum.interactive]?: ChildrenInteractive;
[NodeOutputKeyEnum.answerText]: string;
[NodeOutputKeyEnum.history]: ChatItemType[];
}>;
export const dispatchRunAppNode = async (props: Props): Promise<Response> => {
const {
runningAppInfo,
histories,
query,
lastInteractive,
node: { pluginId: appId, version },
workflowStreamResponse,
params,
variables
} = props;
const {
system_forbid_stream = false,
userChatInput,
history,
fileUrlList,
...childrenAppVariables
} = params;
const { files } = chatValue2RuntimePrompt(query);
const userInputFiles = (() => {
if (fileUrlList) {
return fileUrlList.map((url) => parseUrlToFileType(url)).filter(Boolean);
}
// Adapt version 4.8.13 upgrade
return files;
})();
if (!userChatInput && !userInputFiles) {
return Promise.reject('Input is empty');
}
if (!appId) {
return Promise.reject('pluginId is empty');
}
// Auth the app by tmbId(Not the user, but the workflow user)
const { app: appData } = await authAppByTmbId({
appId: appId,
tmbId: runningAppInfo.tmbId,
per: ReadPermissionVal
});
const { nodes, edges, chatConfig } = await getAppVersionById({
appId,
versionId: version,
app: appData
});
const childStreamResponse = system_forbid_stream ? false : props.stream;
// Auto line
if (childStreamResponse) {
workflowStreamResponse?.({
event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({
text: '\n'
})
});
}
const chatHistories = getHistories(history, histories);
// Rewrite children app variables
const systemVariables = filterSystemVariables(variables);
const childrenRunVariables = {
...systemVariables,
...childrenAppVariables,
histories: chatHistories,
appId: String(appData._id)
};
const childrenInteractive =
lastInteractive?.type === 'childrenInteractive'
? lastInteractive.params.childrenResponse
: undefined;
const runtimeNodes = rewriteNodeOutputByHistories(
storeNodes2RuntimeNodes(
nodes,
getWorkflowEntryNodeIds(nodes, childrenInteractive || undefined)
),
childrenInteractive
);
const runtimeEdges = storeEdges2RuntimeEdges(edges, childrenInteractive);
const theQuery = childrenInteractive
? query
: runtimePrompt2ChatsValue({ files: userInputFiles, text: userChatInput });
const { flowResponses, flowUsages, assistantResponses, runTimes, workflowInteractiveResponse } =
await dispatchWorkFlow({
...props,
lastInteractive: childrenInteractive,
// Rewrite stream mode
...(system_forbid_stream
? {
stream: false,
workflowStreamResponse: undefined
}
: {}),
runningAppInfo: {
id: String(appData._id),
teamId: String(appData.teamId),
tmbId: String(appData.tmbId),
isChildApp: true
},
runtimeNodes,
runtimeEdges,
histories: chatHistories,
variables: childrenRunVariables,
query: theQuery,
chatConfig
});
const completeMessages = chatHistories.concat([
{
obj: ChatRoleEnum.Human,
value: query
},
{
obj: ChatRoleEnum.AI,
value: assistantResponses
}
]);
const { text } = chatValue2RuntimePrompt(assistantResponses);
const usagePoints = flowUsages.reduce((sum, item) => sum + (item.totalPoints || 0), 0);
return {
[DispatchNodeResponseKeyEnum.interactive]: workflowInteractiveResponse
? {
type: 'childrenInteractive',
params: {
childrenResponse: workflowInteractiveResponse
}
}
: undefined,
assistantResponses: system_forbid_stream ? [] : assistantResponses,
[DispatchNodeResponseKeyEnum.runTimes]: runTimes,
[DispatchNodeResponseKeyEnum.nodeResponse]: {
moduleLogo: appData.avatar,
totalPoints: usagePoints,
query: userChatInput,
textOutput: text,
pluginDetail: appData.permission.hasWritePer ? flowResponses : undefined,
mergeSignId: props.node.nodeId
},
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [
{
moduleName: appData.name,
totalPoints: usagePoints
}
],
[DispatchNodeResponseKeyEnum.toolResponses]: text,
answerText: text,
history: completeMessages
};
};
...@@ -9,6 +9,7 @@ import { queryExtension } from '../../../../core/ai/functions/queryExtension'; ...@@ -9,6 +9,7 @@ import { queryExtension } from '../../../../core/ai/functions/queryExtension';
import { getHistories } from '../utils'; import { getHistories } from '../utils';
import { hashStr } from '@fastgpt/global/common/string/tools'; import { hashStr } from '@fastgpt/global/common/string/tools';
import type { DispatchNodeResultType, ModuleDispatchProps } from '../../types/runtime'; import type { DispatchNodeResultType, ModuleDispatchProps } from '../../types/runtime';
import { MongoTeam } from '../../../../support/user/team/teamSchema';
type Props = ModuleDispatchProps<{ type Props = ModuleDispatchProps<{
[NodeInputKeyEnum.aiModel]: string; [NodeInputKeyEnum.aiModel]: string;
...@@ -25,12 +26,17 @@ export const dispatchQueryExtension = async ({ ...@@ -25,12 +26,17 @@ export const dispatchQueryExtension = async ({
node, node,
usagePush, usagePush,
runningUserInfo, runningUserInfo,
params: { model, systemPrompt, history, userChatInput } params: { model, systemPrompt, history, userChatInput },
runningAppInfo
}: Props): Promise<Response> => { }: Props): Promise<Response> => {
if (!userChatInput) { if (!userChatInput) {
return Promise.reject('Question is empty'); return Promise.reject('Question is empty');
} }
// 获取团队的openaiAccount信息
const team = await MongoTeam.findById(runningAppInfo.teamId, 'openaiAccount').lean();
const userKey = team?.openaiAccount;
const queryExtensionModel = getLLMModel(model); const queryExtensionModel = getLLMModel(model);
const embeddingModel = getEmbeddingModel(); const embeddingModel = getEmbeddingModel();
const chatHistories = getHistories(history, histories); const chatHistories = getHistories(history, histories);
...@@ -48,7 +54,8 @@ export const dispatchQueryExtension = async ({ ...@@ -48,7 +54,8 @@ export const dispatchQueryExtension = async ({
histories: chatHistories, histories: chatHistories,
llmModel: queryExtensionModel.model, llmModel: queryExtensionModel.model,
embeddingModel: embeddingModel.model, embeddingModel: embeddingModel.model,
teamId: runningUserInfo.teamId teamId: runningUserInfo.teamId,
userKey
}); });
extensionQueries.unshift(userChatInput); extensionQueries.unshift(userChatInput);
......
...@@ -82,6 +82,9 @@ const OutLinkSchema = new Schema({ ...@@ -82,6 +82,9 @@ const OutLinkSchema = new Schema({
}, },
hookUrl: { hookUrl: {
type: String type: String
},
password: {
type: String
} }
}, },
......
...@@ -43,7 +43,6 @@ export const pushResult2Remote = async ({ ...@@ -43,7 +43,6 @@ export const pushResult2Remote = async ({
shareId shareId
}); });
if (!outLink?.limit?.hookUrl) return; if (!outLink?.limit?.hookUrl) return;
axios({ axios({
method: 'post', method: 'post',
baseURL: outLink.limit.hookUrl, baseURL: outLink.limit.hookUrl,
......
...@@ -21,6 +21,7 @@ import { ...@@ -21,6 +21,7 @@ import {
} from '@fastgpt/global/support/permission/app/constant'; } from '@fastgpt/global/support/permission/app/constant';
import { parseHeaderCert } from '../auth/common'; import { parseHeaderCert } from '../auth/common';
import { sumPer } from '@fastgpt/global/support/permission/utils'; import { sumPer } from '@fastgpt/global/support/permission/utils';
import { MongoTeamMember } from '../../user/team/teamMemberSchema';
export const authWorkflowToolByTmbId = async ({ export const authWorkflowToolByTmbId = async ({
tmbId, tmbId,
...@@ -69,16 +70,16 @@ export const authAppByTmbId = async ({ ...@@ -69,16 +70,16 @@ export const authAppByTmbId = async ({
} }
if (String(app.teamId) !== teamId) { if (String(app.teamId) !== teamId) {
return Promise.reject(AppErrEnum.unAuthApp); // return Promise.reject(AppErrEnum.unAuthApp);
} }
if (app.type === AppTypeEnum.hidden) { if (app.type === AppTypeEnum.hidden) {
if (per === AppReadChatLogPerVal) { if (per === AppReadChatLogPerVal) {
if (!tmbPer.hasManagePer) { if (!tmbPer.hasManagePer) {
return Promise.reject(AppErrEnum.unAuthApp); // return Promise.reject(AppErrEnum.unAuthApp);
} }
} else if (per !== ReadPermissionVal) { } else if (per !== ReadPermissionVal) {
return Promise.reject(AppErrEnum.unAuthApp); // return Promise.reject(AppErrEnum.unAuthApp);
} }
return { return {
...@@ -90,7 +91,11 @@ export const authAppByTmbId = async ({ ...@@ -90,7 +91,11 @@ export const authAppByTmbId = async ({
}; };
} }
const isOwner = tmbPer.isOwner || String(app.tmbId) === String(tmbId); // 检查用户是否为admin角色
const teamMember = await MongoTeamMember.findById(tmbId).lean();
const isAdmin = !!(teamMember && teamMember.role === 'admin');
const isOwner = tmbPer.isOwner || String(app.tmbId) === String(tmbId) || isAdmin;
const isGetParentClb = const isGetParentClb =
app.inheritPermission && !AppFolderTypeList.includes(app.type) && !!app.parentId; app.inheritPermission && !AppFolderTypeList.includes(app.type) && !!app.parentId;
...@@ -119,7 +124,7 @@ export const authAppByTmbId = async ({ ...@@ -119,7 +124,7 @@ export const authAppByTmbId = async ({
} }
if (!Per.checkPer(per)) { if (!Per.checkPer(per)) {
return Promise.reject(AppErrEnum.unAuthApp); // return Promise.reject(AppErrEnum.unAuthApp);
} }
return { return {
...@@ -144,12 +149,19 @@ export const authApp = async ({ ...@@ -144,12 +149,19 @@ export const authApp = async ({
} }
> => { > => {
const result = await parseHeaderCert(props); const result = await parseHeaderCert(props);
const { tmbId } = result; let { tmbId } = result;
if (!appId) { if (!appId) {
return Promise.reject(AppErrEnum.unExist); return Promise.reject(AppErrEnum.unExist);
} }
if (!tmbId) {
// 从app中查询tmbId
const app = await MongoApp.findOne({ _id: appId }).lean();
if (!app) {
return Promise.reject(AppErrEnum.unExist);
}
tmbId = app.tmbId;
}
const { app } = await authAppByTmbId({ const { app } = await authAppByTmbId({
tmbId, tmbId,
appId, appId,
......
import { MongoResourcePermission } from '../../permission/schema';
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
import { Types } from 'mongoose';
export type AppPermissionInfo = {
// 当前用户的协作权限
userCollaboratorPermissions: Array<{
appId: string;
permission: number;
}>;
// app的public状态(是否有其他协作者)
publicAppIds: Set<string>;
};
/**
* 获取app权限信息
* @param appIds app ID数组
* @param tmbId 当前登录用户的团队成员ID
* @returns 权限信息
*/
export async function getAppPermissions({
appIds,
tmbId
}: {
appIds: string[];
tmbId: string;
}): Promise<AppPermissionInfo> {
if (appIds.length === 0) {
return {
userCollaboratorPermissions: [],
publicAppIds: new Set()
};
}
// 查询所有app的协作权限
const allCollaborators = await MongoResourcePermission.find({
resourceType: PerResourceTypeEnum.app,
resourceId: { $in: appIds.map((id) => new Types.ObjectId(id)) }
}).lean();
// 过滤出当前登录用户的协作权限
const userCollaboratorPermissions = allCollaborators
.filter((item: any) => String(item.tmbId) === String(tmbId))
.map((item: any) => ({
appId: String(item.resourceId),
permission: item.permission
}));
// 判断哪些app是public的(有除创建者外的其他协作者)
const publicAppIds = new Set<string>();
// 按appId分组
const collaboratorsByApp = new Map<string, string[]>();
allCollaborators.forEach((item: any) => {
const appId = String(item.resourceId);
const tmbId = String(item.tmbId);
if (!collaboratorsByApp.has(appId)) {
collaboratorsByApp.set(appId, []);
}
collaboratorsByApp.get(appId)!.push(tmbId);
});
// 检查每个app是否有多个不同的协作者
collaboratorsByApp.forEach((tmbIds, appId) => {
const uniqueTmbIds = [...new Set(tmbIds)];
if (uniqueTmbIds.length > 1) {
publicAppIds.add(appId);
}
});
return {
userCollaboratorPermissions,
publicAppIds
};
}
/**
* 获取当前用户对特定app的协作权限
* @param appId app ID
* @param tmbId 当前登录用户的团队成员ID
* @returns 权限值,如果没有找到则返回undefined
*/
export async function getUserAppCollaboratorPermission({
appId,
tmbId
}: {
appId: string;
tmbId: string;
}): Promise<number | undefined> {
const collaborator = await MongoResourcePermission.findOne({
resourceType: PerResourceTypeEnum.app,
resourceId: new Types.ObjectId(appId),
tmbId: new Types.ObjectId(tmbId)
}).lean();
return collaborator?.permission;
}
/**
* 检查app是否为public(有多个协作者)
* @param appId app ID
* @returns 是否为public
*/
export async function isAppPublic(appId: string): Promise<boolean> {
const collaborators = await MongoResourcePermission.find({
resourceType: PerResourceTypeEnum.app,
resourceId: new Types.ObjectId(appId)
}).lean();
const uniqueTmbIds = [...new Set(collaborators.map((item: any) => String(item.tmbId)))];
return uniqueTmbIds.length > 1;
}
import { MongoApp } from '../../../core/app/schema';
import { AppPermission } from '@fastgpt/global/support/permission/app/controller';
import { Types } from 'mongoose';
import { AppDefaultRoleVal } from '@fastgpt/global/support/permission/app/constant';
import { getResourcePermission } from '../../permission/controller';
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
/**
* 获取当前登录用户对特定app的操作权限
* @param tmbId 当前登录用户的团队成员ID
* @param appId 要查询权限的app ID
* @returns 用户对该app的权限信息
*/
export async function getUserAppPermission({
tmbId,
appId
}: {
tmbId: string;
appId: string;
}): Promise<{
permission: AppPermission;
isOwner: boolean;
hasReadPer: boolean;
hasWritePer: boolean;
hasManagePer: boolean;
}> {
// 1. 首先查询app信息,判断是否为app的创建者
const app = await MongoApp.findById(appId).lean();
if (!app) {
throw new Error('App not found');
}
const isOwner = String(app.tmbId) === String(tmbId);
// 2. 如果是app的创建者,直接返回owner权限
if (isOwner) {
const permission = new AppPermission({ isOwner: true });
return {
permission,
isOwner: true,
hasReadPer: permission.hasReadPer,
hasWritePer: permission.hasWritePer,
hasManagePer: permission.hasManagePer
};
}
// 3. 使用 getResourcePermission 获取完整权限(包括个人、组、组织权限)
const permissionValue = await getResourcePermission({
teamId: String(app.teamId),
tmbId,
resourceId: appId,
resourceType: PerResourceTypeEnum.app
});
// 4. 如果找到权限记录,使用该权限;否则使用app的默认权限
const finalPermissionValue = permissionValue ?? app.defaultPermission ?? AppDefaultRoleVal;
const permission = new AppPermission({ role: finalPermissionValue });
return {
permission,
isOwner: false,
hasReadPer: permission.hasReadPer,
hasWritePer: permission.hasWritePer,
hasManagePer: permission.hasManagePer
};
}
/**
* 获取当前登录用户对多个app的操作权限
* @param tmbId 当前登录用户的团队成员ID
* @param appIds 要查询权限的app ID数组
* @returns 用户对这些app的权限信息映射
*/
export async function getUserMultipleAppPermissions({
tmbId,
appIds
}: {
tmbId: string;
appIds: string[];
}): Promise<
Record<
string,
{
permission: AppPermission;
isOwner: boolean;
hasReadPer: boolean;
hasWritePer: boolean;
hasManagePer: boolean;
}
>
> {
if (appIds.length === 0) {
return {};
}
// 1. 查询所有app信息
const apps = await MongoApp.find({
_id: { $in: appIds.map((id) => new Types.ObjectId(id)) }
}).lean();
const appMap = new Map(apps.map((app) => [String(app._id), app]));
// 2. 构建结果
const result: Record<string, any> = {};
for (const appId of appIds) {
const app = appMap.get(appId);
if (!app) {
continue;
}
const isOwner = String(app.tmbId) === String(tmbId);
if (isOwner) {
const permission = new AppPermission({ isOwner: true });
result[appId] = {
permission,
isOwner: true,
hasReadPer: permission.hasReadPer,
hasWritePer: permission.hasWritePer,
hasManagePer: permission.hasManagePer
};
} else {
// 使用 getResourcePermission 获取完整权限(包括个人、组、组织权限)
const permissionValue = await getResourcePermission({
teamId: String(app.teamId),
tmbId,
resourceId: appId,
resourceType: PerResourceTypeEnum.app
});
const finalPermissionValue = permissionValue ?? app.defaultPermission ?? AppDefaultRoleVal;
const permission = new AppPermission({ role: finalPermissionValue });
result[appId] = {
permission,
isOwner: false,
hasReadPer: permission.hasReadPer,
hasWritePer: permission.hasWritePer,
hasManagePer: permission.hasManagePer
};
}
}
return result;
}
/**
* 检查当前登录用户是否对特定app有指定权限
* @param tmbId 当前登录用户的团队成员ID
* @param appId 要检查权限的app ID
* @param requiredPermission 需要的权限类型 ('read' | 'write' | 'manage')
* @returns 是否有指定权限
*/
export async function checkUserAppPermission({
tmbId,
appId,
requiredPermission
}: {
tmbId: string;
appId: string;
requiredPermission: 'read' | 'write' | 'manage';
}): Promise<boolean> {
const { permission } = await getUserAppPermission({ tmbId, appId });
switch (requiredPermission) {
case 'read':
return permission.hasReadPer;
case 'write':
return permission.hasWritePer;
case 'manage':
return permission.hasManagePer;
default:
return false;
}
}
...@@ -11,7 +11,7 @@ import { serviceEnv } from '../../../env'; ...@@ -11,7 +11,7 @@ import { serviceEnv } from '../../../env';
export const authCert = async (props: AuthModeType) => { export const authCert = async (props: AuthModeType) => {
const result = await parseHeaderCert(props); const result = await parseHeaderCert(props);
console.log('result333222', result);
return { return {
...result, ...result,
isOwner: true, isOwner: true,
......
import type { AuthModeType } from '../type';
import { parseHeaderCert } from '../controller';
import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset';
import { MongoDataset } from '../../../core/dataset/schema';
import { getCollectionWithDataset } from '../../../core/dataset/controller';
import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant';
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import type { AuthResponseType } from '../type';
import type {
CollectionWithDatasetType,
DatasetFileSchema,
DatasetSchemaType
} from '@fastgpt/global/core/dataset/type';
import { getFileById } from '../../../common/file/gridfs/controller';
import { BucketNameEnum } from '@fastgpt/global/common/file/constants';
import { getTmbInfoByTmbId } from '../../user/team/controller';
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
import { MongoDatasetCollection } from '../../../core/dataset/collection/schema';
export async function authDatasetByTmbId({
teamId,
tmbId,
datasetId,
per
}: {
teamId: string;
tmbId: string;
datasetId: string;
per: AuthModeType['per'];
}) {
const { role } = await getTmbInfoByTmbId({ tmbId });
const { dataset, isOwner, canWrite } = await (async () => {
const dataset = await MongoDataset.findOne({ _id: datasetId, teamId }).lean();
if (!dataset) {
return Promise.reject(DatasetErrEnum.unAuthDataset);
}
const isOwner =
String(dataset.tmbId) === tmbId || role === TeamMemberRoleEnum.owner || role === 'admin';
const canWrite = isOwner || dataset.permissionType === PermissionTypeEnum.public;
return { dataset, isOwner, canWrite };
})();
return {
dataset,
isOwner,
canWrite
};
}
export async function authDataset({
datasetId,
per = 'owner',
...props
}: AuthModeType & {
datasetId: string;
}): Promise<
AuthResponseType & {
dataset: DatasetSchemaType;
}
> {
const result = await parseHeaderCert(props);
const { teamId, tmbId } = result;
const { dataset, isOwner, canWrite } = await authDatasetByTmbId({
teamId,
tmbId,
datasetId,
per
});
return {
...result,
dataset,
isOwner,
canWrite
};
}
/*
Read: in team and dataset permission is public
Write: in team, not visitor and dataset permission is public
*/
export async function authDatasetCollection({
collectionId,
per = 'owner',
...props
}: AuthModeType & {
collectionId: string;
}): Promise<
AuthResponseType & {
collection: CollectionWithDatasetType;
}
> {
const { teamId, tmbId } = await parseHeaderCert(props);
const { role } = await getTmbInfoByTmbId({ tmbId });
const { collection, isOwner, canWrite } = await (async () => {
const collection = await getCollectionWithDataset(collectionId);
if (!collection || String(collection.teamId) !== teamId) {
return Promise.reject(DatasetErrEnum.unAuthDatasetCollection);
}
const isOwner =
String(collection.tmbId) === tmbId || role === TeamMemberRoleEnum.owner || role === 'admin';
const canWrite = isOwner;
return {
collection,
isOwner,
canWrite
};
})();
return {
teamId,
tmbId,
collection,
isOwner,
canWrite
};
}
export async function authDatasetFile({
fileId,
per = 'owner',
...props
}: AuthModeType & {
fileId: string;
}): Promise<
AuthResponseType & {
file: DatasetFileSchema;
}
> {
const { teamId, tmbId } = await parseHeaderCert(props);
const [file, collection] = await Promise.all([
getFileById({ bucketName: BucketNameEnum.dataset, fileId }),
MongoDatasetCollection.findOne({
teamId,
fileId
})
]);
if (!file) {
return Promise.reject(CommonErrEnum.fileNotFound);
}
if (!collection) {
return Promise.reject(DatasetErrEnum.unAuthDatasetFile);
}
// file role = collection role
try {
const { isOwner, canWrite } = await authDatasetCollection({
...props,
collectionId: collection._id,
per
});
return {
teamId,
tmbId,
file,
isOwner,
canWrite
};
} catch (error) {
return Promise.reject(DatasetErrEnum.unAuthDatasetFile);
}
}
import { MongoTeamMember } from '../../user/team/teamMemberSchema';
import { checkTeamAIPoints } from '../teamLimit';
import { type UserModelSchema } from '@fastgpt/global/support/user/type';
import { type TeamSchema } from '@fastgpt/global/support/user/team/type';
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
export async function getUserChatInfoAndAuthTeamPoints(tmbId: string) {
const tmb = await MongoTeamMember.findById(tmbId, 'userId teamId')
.populate<{ user: UserModelSchema; team: TeamSchema }>([
{
path: 'user',
select: 'timezone'
},
{
path: 'team',
select: 'openaiAccount externalWorkflowVariables'
}
])
.lean();
if (!tmb) return Promise.reject(TeamErrEnum.notUser);
console.log('tmb', tmb);
// await checkTeamAIPoints(tmb.team._id);
return {
timezone: tmb.user.timezone,
externalProvider: {
openaiAccount: tmb.team.openaiAccount,
externalWorkflowVariables: tmb.team.externalWorkflowVariables
}
};
}
import type { AuthResponseType } from '@fastgpt/global/support/permission/type';
import type { AuthModeType } from '../type';
import type { TeamItemType } from '@fastgpt/global/support/user/team/type';
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import { parseHeaderCert } from '../controller';
import { getTmbInfoByTmbId } from '../../user/team/controller';
import { UserErrEnum } from '../../../../global/common/error/code/user';
export async function authUserNotVisitor(props: AuthModeType): Promise<
AuthResponseType & {
team: TeamItemType;
role: `${TeamMemberRoleEnum}`;
}
> {
const { userId, teamId, tmbId } = await parseHeaderCert(props);
const team: TeamItemType = {
userId: userId,
teamId: teamId,
teamName: '',
memberName: '',
avatar: '',
balance: 0,
tmbId: tmbId,
teamDomain: '',
defaultTeam: true,
role: 'owner',
status: 'active',
canWrite: true,
defaultPermission: 1
};
return {
teamId,
tmbId,
team,
role: team.role,
isOwner: team.role === TeamMemberRoleEnum.owner, // teamOwner
canWrite: true
};
}
/* auth user role */
export async function authUserRole(props: AuthModeType): Promise<
AuthResponseType & {
role: `${TeamMemberRoleEnum}`;
teamOwner: boolean;
}
> {
const result = await parseHeaderCert(props);
// const { role: userRole, canWrite } = await getTmbInfoByTmbId({ tmbId: result.tmbId });
return {
...result,
isOwner: true,
role: TeamMemberRoleEnum.owner,
teamOwner: true,
canWrite: true
};
}
...@@ -46,13 +46,19 @@ export const getTmbPermission = async ({ ...@@ -46,13 +46,19 @@ export const getTmbPermission = async ({
resourceId: string; resourceId: string;
} }
)): Promise<PermissionValueType | undefined> => { )): Promise<PermissionValueType | undefined> => {
// 首先检查用户是否为admin角色,admin角色拥有所有权限
const teamMember = await MongoTeamMember.findById(tmbId).lean();
if (teamMember && teamMember.role === 'admin') {
return OwnerRoleVal; // admin角色返回最高权限
}
// Personal permission has the highest priority // Personal permission has the highest priority
const tmbPer = ( const tmbPer = (
await MongoResourcePermission.findOne( await MongoResourcePermission.findOne(
{ {
resourceType, resourceType,
teamId, teamId,
resourceId, ...(resourceId && { resourceId }),
tmbId tmbId
}, },
'permission' 'permission'
......
...@@ -87,7 +87,7 @@ export const authDatasetByTmbId = async ({ ...@@ -87,7 +87,7 @@ export const authDatasetByTmbId = async ({
const Per = new DatasetPermission({ role: sumPer(folderPer, myPer), isOwner }); const Per = new DatasetPermission({ role: sumPer(folderPer, myPer), isOwner });
if (!Per.checkPer(per)) { if (!Per.checkPer(per)) {
return Promise.reject(DatasetErrEnum.unAuthDataset); // return Promise.reject(DatasetErrEnum.unAuthDataset);
} }
return { return {
...@@ -181,9 +181,11 @@ export async function authDatasetCollection({ ...@@ -181,9 +181,11 @@ export async function authDatasetCollection({
*/ */
export async function authDatasetData({ export async function authDatasetData({
dataId, dataId,
per = NullPermissionVal,
...props ...props
}: AuthModeType & { }: AuthModeType & {
dataId: string; dataId: string;
per?: PermissionValueType;
}) { }) {
// get mongo dataset.data // get mongo dataset.data
const datasetData = await MongoDatasetData.findById(dataId); const datasetData = await MongoDatasetData.findById(dataId);
...@@ -194,7 +196,8 @@ export async function authDatasetData({ ...@@ -194,7 +196,8 @@ export async function authDatasetData({
const result = await authDatasetCollection({ const result = await authDatasetCollection({
...props, ...props,
collectionId: datasetData.collectionId collectionId: datasetData.collectionId,
per
}); });
const data: DatasetDataItemType = { const data: DatasetDataItemType = {
......
import { MongoResourcePermission } from '../../permission/schema';
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
import { Types } from 'mongoose';
export type DatasetPermissionInfo = {
// 当前用户的协作权限
userCollaboratorPermissions: Array<{
datasetId: string;
permission: number;
}>;
// dataset的public状态(是否有其他协作者)
publicDatasetIds: Set<string>;
};
/**
* 获取dataset权限信息
* @param datasetIds dataset ID数组
* @param tmbId 当前登录用户的团队成员ID
* @returns 权限信息
*/
export async function getDatasetPermissions({
datasetIds,
tmbId
}: {
datasetIds: string[];
tmbId: string;
}): Promise<DatasetPermissionInfo> {
if (datasetIds.length === 0) {
return {
userCollaboratorPermissions: [],
publicDatasetIds: new Set()
};
}
// 查询所有dataset的协作权限
const allCollaborators = await MongoResourcePermission.find({
resourceType: PerResourceTypeEnum.dataset,
resourceId: { $in: datasetIds.map((id) => new Types.ObjectId(id)) }
}).lean();
// 过滤出当前登录用户的协作权限
const userCollaboratorPermissions = allCollaborators
.filter((item: any) => String(item.tmbId) === String(tmbId))
.map((item: any) => ({
datasetId: String(item.resourceId),
permission: item.permission
}));
// 判断哪些dataset是public的(有除创建者外的其他协作者)
const publicDatasetIds = new Set<string>();
// 按datasetId分组
const collaboratorsByDataset = new Map<string, string[]>();
allCollaborators.forEach((item: any) => {
const datasetId = String(item.resourceId);
const tmbId = String(item.tmbId);
if (!collaboratorsByDataset.has(datasetId)) {
collaboratorsByDataset.set(datasetId, []);
}
collaboratorsByDataset.get(datasetId)!.push(tmbId);
});
// 检查每个dataset是否有多个不同的协作者
collaboratorsByDataset.forEach((tmbIds, datasetId) => {
const uniqueTmbIds = [...new Set(tmbIds)];
if (uniqueTmbIds.length > 1) {
publicDatasetIds.add(datasetId);
}
});
return {
userCollaboratorPermissions,
publicDatasetIds
};
}
/**
* 获取当前用户对特定dataset的协作权限
* @param datasetId dataset ID
* @param tmbId 当前登录用户的团队成员ID
* @returns 权限值,如果没有找到则返回undefined
*/
export async function getUserDatasetCollaboratorPermission({
datasetId,
tmbId
}: {
datasetId: string;
tmbId: string;
}): Promise<number | undefined> {
const collaborator = await MongoResourcePermission.findOne({
resourceType: PerResourceTypeEnum.dataset,
resourceId: new Types.ObjectId(datasetId),
tmbId: new Types.ObjectId(tmbId)
}).lean();
return collaborator?.permission;
}
/**
* 检查dataset是否为public(有多个协作者)
* @param datasetId dataset ID
* @returns 是否为public
*/
export async function isDatasetPublic(datasetId: string): Promise<boolean> {
const collaborators = await MongoResourcePermission.find({
resourceType: PerResourceTypeEnum.dataset,
resourceId: new Types.ObjectId(datasetId)
}).lean();
const uniqueTmbIds = [...new Set(collaborators.map((item: any) => String(item.tmbId)))];
return uniqueTmbIds.length > 1;
}
...@@ -109,13 +109,13 @@ export const authGroupMemberRole = async ({ ...@@ -109,13 +109,13 @@ export const authGroupMemberRole = async ({
]); ]);
// Team admin or role check // Team admin or role check
if (tmb.permission.hasManagePer || (groupMember && role.includes(groupMember.role))) { // if (tmb.permission.hasManagePer || (groupMember && role.includes(groupMember.role))) {
return { return {
...result, ...result,
permission: tmb.permission, permission: tmb.permission,
teamId, teamId,
tmbId tmbId
}; };
} // }
return Promise.reject(TeamErrEnum.unAuthTeam); // return Promise.reject(TeamErrEnum.unAuthTeam);
}; };
...@@ -27,9 +27,9 @@ export async function authUserPer(props: AuthModeType): Promise< ...@@ -27,9 +27,9 @@ export async function authUserPer(props: AuthModeType): Promise<
tmb tmb
}; };
} }
if (!tmb.permission.checkPer(props.per ?? NullPermissionVal)) { // if (!tmb.permission.checkPer(props.per ?? NullPermissionVal)) {
return Promise.reject(TeamErrEnum.unAuthTeam); // return Promise.reject(TeamErrEnum.unAuthTeam);
} // }
return { return {
...result, ...result,
...@@ -45,9 +45,10 @@ export const authSystemAdmin = async ({ req }: { req: NodeHttpRequest }) => { ...@@ -45,9 +45,10 @@ export const authSystemAdmin = async ({ req }: { req: NodeHttpRequest }) => {
_id: result.userId _id: result.userId
}); });
if (!user || user.username !== 'root') { console.log('auth admin user', user);
return Promise.reject(ERROR_ENUM.unAuthorization); // if (!user || user.username !== 'root') {
} // return Promise.reject(ERROR_ENUM.unAuthorization);
// }
return result; return result;
} catch (error) { } catch (error) {
throw error; throw error;
......
import { type 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 { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode';
import { TeamPermission } from '@fastgpt/global/support/permission/user/controller';
import type { ClientSession } from '../../common/mongo'; import type { ClientSession } from '../../common/mongo';
import type { UserType } from '@fastgpt/global/support/user/type';
import { MongoUser } from './schema';
import { getTmbInfoByTmbId, getUserDefaultTeam } from './team/controller';
import { Types } from '../../common/mongo';
export async function authUserExist({ userId, username }: { userId?: string; username?: string }) { export async function authUserExist({ userId, username }: { userId?: string; username?: string }) {
if (userId) { if (userId) {
...@@ -31,27 +31,107 @@ export async function getUserDetail({ ...@@ -31,27 +31,107 @@ export async function getUserDetail({
try { try {
const result = await getTmbInfoByTmbId({ tmbId, session }); const result = await getTmbInfoByTmbId({ tmbId, session });
return result; return result;
} catch (error) {} } catch (error) {
console.log('error2222', error);
}
} }
if (userId) { if (userId) {
return getUserDefaultTeam({ userId, session }); return getUserDefaultTeam({ userId, session });
} }
return Promise.reject(ERROR_ENUM.unAuthorization); return Promise.reject(ERROR_ENUM.unAuthorization);
})(); })();
const query = MongoUser.findById(tmb.userId);
if (session) query.session(session);
const user = await query;
if (!user) {
// Validate tmb and userId
if (!tmb || !Types.ObjectId.isValid(tmb.userId)) {
console.log('tmb7777', 'tmb or userId is not valid');
return Promise.reject(ERROR_ENUM.unAuthorization); return Promise.reject(ERROR_ENUM.unAuthorization);
} }
console.log('tmb5555', tmb.userId);
let user = await MongoUser.findOne({ _id: new Types.ObjectId(tmb.userId) });
const permission = isRoot ? new TeamPermission({ isOwner: true }) : tmb.permission; console.log('user6666', user);
const team = { if (!user) {
...tmb, user = await MongoUser.findOne({ _id: tmb.userId });
permission }
if (!user) {
console.error(`User not found with _id: ${tmb.userId}`);
return Promise.reject(ERROR_ENUM.unAuthorization);
}
return {
_id: user._id,
username: user.username,
avatar: tmb.avatar,
timezone: user.timezone,
promotionRate: user.promotionRate,
team: {
userId: tmb.userId,
teamId: tmb.teamId,
teamName: tmb.teamName,
memberName: tmb.memberName,
avatar: tmb.avatar,
balance: tmb.balance,
tmbId: tmb.tmbId,
teamDomain: tmb.teamDomain,
role: tmb.role,
status: tmb.status as 'active' | 'forbidden' | 'leave',
notificationAccount: tmb.notificationAccount,
permission: tmb.permission
},
notificationAccount: tmb.notificationAccount,
permission: tmb.permission,
contact: user.contact
}; };
}
export async function getUserDetailByUsername({
username
}: {
username?: string;
}): Promise<UserType> {
if (!username) {
return Promise.reject(ERROR_ENUM.unAuthorization);
}
const user = await MongoUser.findOne({ username }).lean();
console.log('user3333', user);
if (!user) {
return Promise.reject(ERROR_ENUM.unAuthorization);
}
// 1. Try to get team
let tmb = await getUserDefaultTeam({ userId: user._id }).catch(() => null);
// 2. If no team, create one
// if (!tmb) {
// await mongoSessionRun(async (session) => {
// await createDefaultTeam({
// userId: user._id,
// teamName: 'My Team',
// avatar: (user as any).avatar,
// session
// });
// }).catch((err) => {
// // Log the error during creation
// console.log('Error creating default team:', err);
// return Promise.reject(ERROR_ENUM.unAuthorization);
// });
// // 3. Try to get team again after creation
// tmb = await getUserDefaultTeam({ userId: user._id }).catch(() => null);
// }
if (!tmb) {
console.log('未查询到团队信息', String(user._id));
// return Promise.reject(ERROR_ENUM.unAuthorization);
// 按userId为string查询team - 修复:使用ObjectId类型查询
tmb = await getUserDefaultTeam({ userId: user._id }).catch(() => null);
if (!tmb) {
return Promise.reject(ERROR_ENUM.unAuthorization);
}
}
console.log('tmb4444', tmb);
const permission = tmb.permission;
const team = { ...tmb, permission };
return { return {
_id: user._id, _id: user._id,
username: user.username, username: user.username,
......
...@@ -19,6 +19,7 @@ import { getAIApi } from '../../../core/ai/config'; ...@@ -19,6 +19,7 @@ import { getAIApi } from '../../../core/ai/config';
import { createRootOrg } from '../../permission/org/controllers'; import { createRootOrg } from '../../permission/org/controllers';
import { getS3AvatarSource } from '../../../common/s3/sources/avatar'; import { getS3AvatarSource } from '../../../common/s3/sources/avatar';
import { getLogger, LogCategories } from '../../../common/logger'; import { getLogger, LogCategories } from '../../../common/logger';
import { MongoUser } from '../schema';
const logger = getLogger(LogCategories.MODULE.USER.TEAM); const logger = getLogger(LogCategories.MODULE.USER.TEAM);
...@@ -116,16 +117,20 @@ export async function createDefaultTeam({ ...@@ -116,16 +117,20 @@ export async function createDefaultTeam({
}) { }) {
// auth default team // auth default team
const tmb = await MongoTeamMember.findOne({ const tmb = await MongoTeamMember.findOne({
userId: new Types.ObjectId(userId) userId: userId
}); });
if (!tmb) { if (!tmb) {
// 根据userId查询到用户
const user = await MongoUser.findOne({
_id: userId
});
// create team // create team
const [{ _id: insertedId }] = await MongoTeam.create( const [{ _id: insertedId }] = await MongoTeam.create(
[ [
{ {
ownerId: userId, ownerId: userId,
name: teamName, name: user?.username,
avatar, avatar,
createTime: new Date() createTime: new Date()
} }
...@@ -138,7 +143,7 @@ export async function createDefaultTeam({ ...@@ -138,7 +143,7 @@ export async function createDefaultTeam({
{ {
teamId: insertedId, teamId: insertedId,
userId, userId,
name: 'Owner', name: user?.username,
role: TeamMemberRoleEnum.owner, role: TeamMemberRoleEnum.owner,
status: TeamMemberStatusEnum.active, status: TeamMemberStatusEnum.active,
createTime: new Date() createTime: new Date()
......
import React from 'react';
import { Box } from '@chakra-ui/react';
import type { ImageProps } from '@chakra-ui/react'; import type { ImageProps } from '@chakra-ui/react';
import { Box } from '@chakra-ui/react';
import { LOGO_ICON } from '@fastgpt/global/common/system/constants'; import { LOGO_ICON } from '@fastgpt/global/common/system/constants';
import React from 'react';
import MyIcon from '../Icon'; import MyIcon from '../Icon';
import { iconPaths } from '../Icon/constants'; import { iconPaths } from '../Icon/constants';
import MyImage from '../Image/MyImage'; import MyImage from '../Image/MyImage';
......
...@@ -13,7 +13,7 @@ import MyIcon from '../components/common/Icon'; ...@@ -13,7 +13,7 @@ import MyIcon from '../components/common/Icon';
import type { IconNameType } from '../components/common/Icon/type'; import type { IconNameType } from '../components/common/Icon/type';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useToast } from './useToast'; import { useToast } from './useToast';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { import {
useBoolean, useBoolean,
useCreation, useCreation,
...@@ -144,7 +144,7 @@ export function usePagination<DataT, ResT = {}>( ...@@ -144,7 +144,7 @@ export function usePagination<DataT, ResT = {}>(
setError(error); setError(error);
if (error.code !== 'ERR_CANCELED') { if (error.code !== 'ERR_CANCELED') {
toast({ toast({
title: getErrText(error, t('common:core.chat.error.data_error')), title: error.message || t('common:core.chat.error.data_error'),
status: 'error' status: 'error'
}); });
} }
......
...@@ -164,6 +164,7 @@ ...@@ -164,6 +164,7 @@
"code_error.outlink_error.invalid_link": "Invalid Share Link", "code_error.outlink_error.invalid_link": "Invalid Share Link",
"code_error.outlink_error.link_not_exist": "Share Link Does Not Exist", "code_error.outlink_error.link_not_exist": "Share Link Does Not Exist",
"code_error.outlink_error.un_auth_user": "Identity Verification Failed", "code_error.outlink_error.un_auth_user": "Identity Verification Failed",
"code_error.plugin_error.not_exist": "Plugin Does Not Exist",
"code_error.plugin_error.un_auth": "No permission to operate the tool", "code_error.plugin_error.un_auth": "No permission to operate the tool",
"code_error.sandbox_error.agent_sandbox_initializing": "The virtual machine is initializing. Please try again later.", "code_error.sandbox_error.agent_sandbox_initializing": "The virtual machine is initializing. Please try again later.",
"code_error.sandbox_error.agent_sandbox_permission_denied": "The current app is not authorized to use the sandbox/VM. Please contact an administrator to configure it.", "code_error.sandbox_error.agent_sandbox_permission_denied": "The current app is not authorized to use the sandbox/VM. Please contact an administrator to configure it.",
...@@ -186,7 +187,7 @@ ...@@ -186,7 +187,7 @@
"code_error.skill_error.skill_name_too_long": "Skill name must be 50 characters or fewer", "code_error.skill_error.skill_name_too_long": "Skill name must be 50 characters or fewer",
"code_error.skill_error.un_auth_skill": "Unauthorized to Operate This Skill", "code_error.skill_error.un_auth_skill": "Unauthorized to Operate This Skill",
"code_error.system_error.commercial_feature": "Commercial Edition exclusive feature", "code_error.system_error.commercial_feature": "Commercial Edition exclusive feature",
"code_error.system_error.community_version_num_limit": "Exceeded Open Source Version Limit, Please Upgrade to Commercial Version: https://fastgpt.io", "code_error.system_error.community_version_num_limit": "Exceeded Open Source Version Limit, Please Upgrade to Commercial Version: https://try.ai",
"code_error.system_error.license_app_amount_limit": "Exceed the maximum number of applications in the system", "code_error.system_error.license_app_amount_limit": "Exceed the maximum number of applications in the system",
"code_error.system_error.license_dataset_amount_limit": "Exceed the maximum number of knowledge bases in the system", "code_error.system_error.license_dataset_amount_limit": "Exceed the maximum number of knowledge bases in the system",
"code_error.system_error.license_user_amount_limit": "Exceed the maximum number of users in the system", "code_error.system_error.license_user_amount_limit": "Exceed the maximum number of users in the system",
...@@ -223,9 +224,10 @@ ...@@ -223,9 +224,10 @@
"comfirm_import": "Confirm import", "comfirm_import": "Confirm import",
"comfirm_leave_page": "Confirm to Leave This Page?", "comfirm_leave_page": "Confirm to Leave This Page?",
"comfirn_create": "Confirm Creation", "comfirn_create": "Confirm Creation",
"commercial_function_tip": "Please Upgrade to the Commercial Version to Use This Feature: https://doc.fastgpt.cn/guide/version/commercial", "commercial_function_tip": "Comming soon",
"community_support": "community support", "community_support": "community support",
"compliance.chat": "This content is AI-generated. Please use your own judgment.", "comon.Continue_Adding": "Continue Adding",
"compliance.chat": "The content is generated by third-party AI and cannot be guaranteed to be true and accurate. It is for reference only.",
"compliance.dataset": "Please ensure that your content strictly complies with relevant laws and regulations and avoid containing any illegal or infringing content. \nPlease be careful when uploading materials that may contain sensitive information.", "compliance.dataset": "Please ensure that your content strictly complies with relevant laws and regulations and avoid containing any illegal or infringing content. \nPlease be careful when uploading materials that may contain sensitive information.",
"confirm_choice": "Confirm Choice", "confirm_choice": "Confirm Choice",
"confirm_input_delete_placeholder": "Please enter: {{confirmText}}", "confirm_input_delete_placeholder": "Please enter: {{confirmText}}",
...@@ -278,6 +280,9 @@ ...@@ -278,6 +280,9 @@
"core.app.Share link": "Login-Free Window", "core.app.Share link": "Login-Free Window",
"core.app.Share link desc": "Create shareable links and support login-free use", "core.app.Share link desc": "Create shareable links and support login-free use",
"core.app.Share link desc detail": "You can directly share this model with other users for conversation, they can use it directly without logging in. Note, this feature will consume your account balance, please keep the link safe!", "core.app.Share link desc detail": "You can directly share this model with other users for conversation, they can use it directly without logging in. Note, this feature will consume your account balance, please keep the link safe!",
"core.app.Share auth_link": "Require login Window",
"core.app.Share auth_link desc": "Share the link with other users, they can use it directly after enter the correct password",
"core.app.Share auth_link desc detail": "You can directly share this model with other users for conversation, they can use it after enter the correct password. Note, this feature will consume your account balance, please keep the link safe!",
"core.app.TTS": "Voice Playback", "core.app.TTS": "Voice Playback",
"core.app.TTS Tip": "After enabling, you can use the voice playback function after each conversation. Using this feature may incur additional costs.", "core.app.TTS Tip": "After enabling, you can use the voice playback function after each conversation. Using this feature may incur additional costs.",
"core.app.TTS start": "Read Content", "core.app.TTS start": "Read Content",
...@@ -292,6 +297,9 @@ ...@@ -292,6 +297,9 @@
"core.app.feedback.Custom feedback": "Custom Feedback", "core.app.feedback.Custom feedback": "Custom Feedback",
"core.app.feedback.close custom feedback": "Close Feedback", "core.app.feedback.close custom feedback": "Close Feedback",
"core.app.have_saved": "Saved", "core.app.have_saved": "Saved",
"core.app.logs.ChatId": "Chat ID",
"core.app.logs.Source And Time": "Source & Time",
"core.app.more": "View More",
"core.app.no_app": "No Apps Yet, Create One Now!", "core.app.no_app": "No Apps Yet, Create One Now!",
"core.app.not_saved": "Not Saved", "core.app.not_saved": "Not Saved",
"core.app.outLink.Can Drag": "Icon Can Be Dragged", "core.app.outLink.Can Drag": "Icon Can Be Dragged",
...@@ -1041,6 +1049,7 @@ ...@@ -1041,6 +1049,7 @@
"support.openapi.New api key": "New API Key", "support.openapi.New api key": "New API Key",
"support.openapi.New api key tip": "Keep your key safe and do not share it with others.", "support.openapi.New api key tip": "Keep your key safe and do not share it with others.",
"support.outlink.Delete link tip": "Confirm to Delete This Login-Free Link? The link will become invalid immediately after deletion, but the chat logs will be retained. Please confirm!", "support.outlink.Delete link tip": "Confirm to Delete This Login-Free Link? The link will become invalid immediately after deletion, but the chat logs will be retained. Please confirm!",
"support.outlink.Delete auth_link tip": "Confirm to Delete This Authorization Link? The link will become invalid immediately after deletion, but the chat logs will be retained. Please confirm!",
"support.outlink.Max usage points": "Points Limit", "support.outlink.Max usage points": "Points Limit",
"support.outlink.Max usage points tip": "The maximum number of points allowed for this link. It cannot be used after exceeding the limit. -1 means unlimited.", "support.outlink.Max usage points tip": "The maximum number of points allowed for this link. It cannot be used after exceeding the limit. -1 means unlimited.",
"support.outlink.Usage points": "Points Consumption", "support.outlink.Usage points": "Points Consumption",
...@@ -1138,8 +1147,8 @@ ...@@ -1138,8 +1147,8 @@
"support.wallet.subscription.standardSubLevel.custom_desc": "No longer limited by packages, designed for your unique needs", "support.wallet.subscription.standardSubLevel.custom_desc": "No longer limited by packages, designed for your unique needs",
"support.wallet.subscription.standardSubLevel.enterprise": "Enterprise", "support.wallet.subscription.standardSubLevel.enterprise": "Enterprise",
"support.wallet.subscription.standardSubLevel.enterprise_desc": "Suitable for small and medium-sized enterprises to build Dataset applications in production environments", "support.wallet.subscription.standardSubLevel.enterprise_desc": "Suitable for small and medium-sized enterprises to build Dataset applications in production environments",
"support.wallet.subscription.standardSubLevel.experience": "Experience", "support.wallet.subscription.standardSubLevel.experience": "Experience Version",
"support.wallet.subscription.standardSubLevel.experience_desc": "Unlock the full functionality of FastGPT", "support.wallet.subscription.standardSubLevel.experience_desc": "Unlock the full functionality of ",
"support.wallet.subscription.standardSubLevel.free": "Free", "support.wallet.subscription.standardSubLevel.free": "Free",
"support.wallet.subscription.standardSubLevel.free desc": "Free trial of core features. \nIf you haven't logged in for 30 days, the knowledge base will be cleared.", "support.wallet.subscription.standardSubLevel.free desc": "Free trial of core features. \nIf you haven't logged in for 30 days, the knowledge base will be cleared.",
"support.wallet.subscription.standardSubLevel.team": "Team", "support.wallet.subscription.standardSubLevel.team": "Team",
......
...@@ -18,8 +18,9 @@ ...@@ -18,8 +18,9 @@
"feishu_bot_desc": "Connect to Feishu Bot directly via API", "feishu_bot_desc": "Connect to Feishu Bot directly via API",
"key_alias": "Key alias, for display only", "key_alias": "Key alias, for display only",
"link_name": "Share Link Name", "link_name": "Share Link Name",
"link_password": "Share Link Password",
"native_channels": "Native Channels", "native_channels": "Native Channels",
"new_feishu_bot": "add_new Feishu Bot", "new_feishu_bot": "Add New Feishu Bot",
"official_account.create_modal_title": "Create WeChat Official Account Integration", "official_account.create_modal_title": "Create WeChat Official Account Integration",
"official_account.desc": "Connect to WeChat Official Account directly via API", "official_account.desc": "Connect to WeChat Official Account directly via API",
"official_account.edit_modal_title": "Edit WeChat Official Account Integration", "official_account.edit_modal_title": "Edit WeChat Official Account Integration",
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
"feishu_bot_desc": "通过 API 直接接入飞书机器人", "feishu_bot_desc": "通过 API 直接接入飞书机器人",
"key_alias": "key 的别名,仅用于展示", "key_alias": "key 的别名,仅用于展示",
"link_name": "分享链接的名字", "link_name": "分享链接的名字",
"link_password": "分享链接的密码",
"native_channels": "原生渠道", "native_channels": "原生渠道",
"new_feishu_bot": "新增飞书机器人", "new_feishu_bot": "新增飞书机器人",
"official_account.create_modal_title": "创建微信公众号接入", "official_account.create_modal_title": "创建微信公众号接入",
......
...@@ -289,6 +289,9 @@ ...@@ -289,6 +289,9 @@
"core.app.feedback.Custom feedback": "自訂回饋", "core.app.feedback.Custom feedback": "自訂回饋",
"core.app.feedback.close custom feedback": "關閉回饋", "core.app.feedback.close custom feedback": "關閉回饋",
"core.app.have_saved": "已儲存", "core.app.have_saved": "已儲存",
"core.app.logs.ChatId": "对话ID",
"core.app.logs.Source And Time": "來源與時間",
"core.app.more": "檢視更多",
"core.app.no_app": "還沒有應用程式,快來建立一個吧!", "core.app.no_app": "還沒有應用程式,快來建立一個吧!",
"core.app.not_saved": "未儲存", "core.app.not_saved": "未儲存",
"core.app.outLink.Can Drag": "圖示可拖曳", "core.app.outLink.Can Drag": "圖示可拖曳",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment