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) => {
parentId: null
};
const pattern = /^[0-9a-fA-F]{24}$/;
if (pattern.test(parentId))
return {
parentId
};
return {};
return {
parentId
};
};
......@@ -17,6 +17,12 @@ export const ChatRoleMap = {
}
};
export enum ChatFeedbackStatusEnum {
Pending = '待审核',
Stored = '已入库',
Ignored = '已忽略'
}
export enum ChatFileTypeEnum {
image = 'image',
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 {
// system config
questionGuide = 'questionGuide',
showToolCall = 'showToolCall',
tts = 'tts',
whisper = 'whisper',
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 {
share = 'share',
authShare = 'authShare',
iframe = 'iframe',
apikey = 'apikey',
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 = {
}
};
export enum PerCollaboratorTypeEnum {
app = 'app',
dataset = 'dataset'
}
export enum PerResourceTypeEnum {
team = 'team',
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';
export enum UsageSourceEnum {
fastgpt = 'fastgpt',
fastgpt = 'aiagentsflow',
api = 'api',
shareLink = 'shareLink',
training = 'training',
......
{
"author": "Menghuan1918",
"version": "488",
"name": "PDF识别",
"avatar": "plugins/doc2x",
"intro": "将PDF文件发送至Doc2X进行解析,返回结构化的LaTeX公式的文本(markdown),支持传入String类型的URL或者流程输出中的文件链接变量",
"courseUrl": "https://fael3z0zfze.feishu.cn/wiki/Rkc5witXWiJoi5kORd2cofh6nDg?fromScene=spaceOverview",
"showStatus": true,
"weight": 10,
"isTool": true,
"templateType": "tools",
"workflow": {
"nodes": [
{
"nodeId": "pluginInput",
"name": "自定义插件输入",
"intro": "可以配置插件需要哪些输入,利用这些输入来运行插件",
"avatar": "core/workflow/template/workflowStart",
"flowNodeType": "pluginInput",
"showStatus": false,
"position": {
"x": -137.96875104510553,
"y": -90.9968973555371
},
"version": "481",
"inputs": [
{
"renderTypeList": ["input"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
"key": "apikey",
"label": "apikey",
"description": "Doc2X的API密匙,可以从Doc2X开放平台获得",
"required": true,
"defaultValue": "",
"list": []
},
{
"renderTypeList": ["fileSelect"],
"selectedTypeIndex": 0,
"valueType": "arrayString",
"canEdit": true,
"key": "files",
"label": "files",
"description": "需要处理的PDF地址",
"required": true,
"list": [],
"canSelectFile": true,
"canSelectImg": false,
"maxFiles": 14,
"defaultValue": ""
},
{
"renderTypeList": ["switch", "reference"],
"selectedTypeIndex": 0,
"valueType": "boolean",
"canEdit": true,
"key": "HTMLtable",
"label": "HTMLtable",
"description": "是否以HTML格式输出表格。如果需要精确地输出表格,请打开此开关以使用HTML格式。关闭后,表格将转换为Markdown形式输出,但这可能会损失一些表格特性,如合并单元格。",
"defaultValue": false,
"list": [
{
"label": "",
"value": ""
}
],
"maxFiles": 5,
"canSelectFile": true,
"canSelectImg": true,
"required": true
}
],
"outputs": [
{
"id": "apikey",
"valueType": "string",
"key": "apikey",
"label": "apikey",
"type": "hidden"
},
{
"id": "url",
"valueType": "arrayString",
"key": "files",
"label": "files",
"type": "hidden"
},
{
"id": "htmltable",
"valueType": "boolean",
"key": "HTMLtable",
"label": "HTMLtable",
"type": "hidden"
}
]
},
{
"nodeId": "pluginOutput",
"name": "自定义插件输出",
"intro": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出",
"avatar": "core/workflow/template/pluginOutput",
"flowNodeType": "pluginOutput",
"showStatus": false,
"position": {
"x": 1505.494975310334,
"y": -4.14668564643415
},
"version": "481",
"inputs": [
{
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "result",
"label": "result",
"description": "处理结果,由文件名以及文档内容组成,多个文件之间由横线分隔开",
"value": ["zHG5jJBkXmjB", "xWQuEf50F3mr"]
},
{
"renderTypeList": ["reference"],
"valueType": "object",
"canEdit": true,
"key": "error",
"label": "error",
"description": "",
"value": ["zHG5jJBkXmjB", "httpRawResponse"],
"isToolOutput": true
},
{
"renderTypeList": ["reference"],
"valueType": "boolean",
"canEdit": true,
"key": "success",
"label": "success",
"description": "是否全部文件都处理成功,如有没有处理成功的文件,失败原因将会输出在failreason中",
"value": ["zHG5jJBkXmjB", "m6CJJj7GFud5"],
"isToolOutput": false
}
],
"outputs": []
},
{
"nodeId": "zHG5jJBkXmjB",
"name": "HTTP 请求",
"intro": "可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)",
"avatar": "core/workflow/template/httpRequest",
"flowNodeType": "httpRequest468",
"showStatus": true,
"position": {
"x": 619.0661933308237,
"y": -472.91377894611503
},
"version": "481",
"inputs": [
{
"key": "system_addInputParam",
"renderTypeList": ["addInputParam"],
"valueType": "dynamic",
"label": "",
"required": false,
"description": "common:core.module.input.description.HTTP Dynamic Input",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpMethod",
"renderTypeList": ["custom"],
"valueType": "string",
"label": "",
"value": "POST",
"required": true,
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpTimeout",
"renderTypeList": ["custom"],
"valueType": "number",
"label": "",
"value": 300,
"min": 5,
"max": 600,
"required": true,
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpReqUrl",
"renderTypeList": ["hidden"],
"valueType": "string",
"label": "",
"description": "common:core.module.input.description.Http Request Url",
"placeholder": "https://api.ai.com/getInventory",
"required": false,
"value": "Doc2X/PDF2text",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpHeader",
"renderTypeList": ["custom"],
"valueType": "any",
"value": [],
"label": "",
"description": "common:core.module.input.description.Http Request Header",
"placeholder": "common:core.module.input.description.Http Request Header",
"required": false,
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpParams",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": [],
"label": "",
"required": false,
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpJsonBody",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": "{\n \"apikey\": \"{{apikey}}\",\n \"HTMLtable\": {{HTMLtable}},\n \"files\": {{files}}\n}",
"label": "",
"required": false,
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpFormBody",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": [],
"label": "",
"required": false,
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpContentType",
"renderTypeList": ["hidden"],
"valueType": "string",
"value": "json",
"label": "",
"required": false,
"debugLabel": "",
"toolDescription": ""
},
{
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "apikey",
"label": "apikey",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"required": true,
"value": ["pluginInput", "apikey"]
},
{
"renderTypeList": ["reference"],
"valueType": "arrayString",
"canEdit": true,
"key": "files",
"label": "files",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"required": true,
"value": [["pluginInput", "url"]]
},
{
"renderTypeList": ["reference"],
"valueType": "boolean",
"canEdit": true,
"key": "HTMLtable",
"label": "HTMLtable",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"required": true,
"value": ["pluginInput", "htmltable"]
}
],
"outputs": [
{
"id": "error",
"key": "error",
"label": "workflow:request_error",
"description": "HTTP请求错误信息,成功时返回空",
"valueType": "object",
"type": "static"
},
{
"id": "httpRawResponse",
"key": "httpRawResponse",
"required": true,
"label": "workflow:raw_response",
"description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。",
"valueType": "any",
"type": "static"
},
{
"id": "system_addOutputParam",
"key": "system_addOutputParam",
"type": "dynamic",
"valueType": "dynamic",
"label": "",
"customFieldConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": false
}
},
{
"id": "xWQuEf50F3mr",
"valueType": "string",
"type": "dynamic",
"key": "result",
"label": "result"
},
{
"id": "m6CJJj7GFud5",
"valueType": "boolean",
"type": "dynamic",
"key": "success",
"label": "success"
}
]
}
],
"edges": [
{
"source": "zHG5jJBkXmjB",
"target": "pluginOutput",
"sourceHandle": "zHG5jJBkXmjB-source-right",
"targetHandle": "pluginOutput-target-left"
},
{
"source": "pluginInput",
"target": "zHG5jJBkXmjB",
"sourceHandle": "pluginInput-source-right",
"targetHandle": "zHG5jJBkXmjB-target-left"
}
],
"chatConfig": {
"questionGuide": {
"open": false
},
"showToolCall": true,
"ttsConfig": {
"type": "web"
},
"whisperConfig": {
"open": false,
"autoSend": false,
"autoTTSResponse": false
},
"chatInputGuide": {
"open": false,
"textList": [],
"customUrl": ""
},
"instruction": "",
"variables": [],
"welcomeText": ""
}
}
}
{
"author": "",
"version": "4811",
"name": "Bing搜索",
"avatar": "core/workflow/template/bing",
"intro": "在Bing中搜索。",
"showStatus": true,
"weight": 10,
"courseUrl": "https://fael3z0zfze.feishu.cn/wiki/LsKAwOmtniA4vkkC259cmfxXnAc?fromScene=spaceOverview",
"isTool": true,
"templateType": "search",
"workflow": {
"nodes": [
{
"nodeId": "pluginInput",
"name": "workflow:template.plugin_start",
"intro": "workflow:intro_plugin_input",
"avatar": "core/workflow/template/workflowStart",
"flowNodeType": "pluginInput",
"showStatus": false,
"position": {
"x": 636.3048409085379,
"y": -238.61714728578016
},
"version": "481",
"inputs": [
{
"renderTypeList": ["input"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
"key": "key",
"label": "key",
"description": "bing搜索key",
"defaultValue": "",
"required": true
},
{
"renderTypeList": ["input", "reference"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
"key": "query",
"label": "query",
"description": "查询字段值",
"defaultValue": "",
"list": [
{
"label": "",
"value": ""
}
],
"required": true,
"toolDescription": "查询字段值"
}
],
"outputs": [
{
"id": "key",
"valueType": "string",
"key": "key",
"label": "key",
"type": "hidden"
},
{
"id": "query",
"valueType": "string",
"key": "query",
"label": "query",
"type": "hidden"
}
]
},
{
"nodeId": "pluginOutput",
"name": "common:core.module.template.self_output",
"intro": "workflow:intro_custom_plugin_output",
"avatar": "core/workflow/template/pluginOutput",
"flowNodeType": "pluginOutput",
"showStatus": false,
"position": {
"x": 2764.1105686698083,
"y": -30.617147285780163
},
"version": "481",
"inputs": [
{
"renderTypeList": ["reference"],
"valueType": "object",
"canEdit": true,
"key": "result",
"label": "result",
"isToolOutput": true,
"description": "",
"value": ["pZTkvleFSZXo", "system_rawResponse"]
}
],
"outputs": []
},
{
"nodeId": "pluginConfig",
"name": "common:core.module.template.system_config",
"intro": "",
"avatar": "core/workflow/template/systemConfig",
"flowNodeType": "pluginConfig",
"position": {
"x": 184.66337662472682,
"y": -216.05298493910115
},
"version": "4811",
"inputs": [],
"outputs": []
},
{
"nodeId": "nyA6oA8mF1iW",
"name": "HTTP 请求",
"intro": "调用谷歌搜索,查询相关内容",
"avatar": "core/workflow/template/httpRequest",
"flowNodeType": "httpRequest468",
"showStatus": true,
"position": {
"x": 1335.0647252518884,
"y": -455.9043948565971
},
"version": "481",
"inputs": [
{
"key": "system_addInputParam",
"renderTypeList": ["addInputParam"],
"valueType": "dynamic",
"label": "",
"required": false,
"description": "common:core.module.input.description.HTTP Dynamic Input",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpMethod",
"renderTypeList": ["custom"],
"valueType": "string",
"label": "",
"value": "GET",
"required": true,
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpTimeout",
"renderTypeList": ["custom"],
"valueType": "number",
"label": "",
"value": 30,
"min": 5,
"max": 600,
"required": true,
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpReqUrl",
"renderTypeList": ["hidden"],
"valueType": "string",
"label": "",
"description": "common:core.module.input.description.Http Request Url",
"placeholder": "https://api.ai.com/getInventory",
"required": false,
"value": "https://api.bing.microsoft.com/v7.0/search",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpHeader",
"renderTypeList": ["custom"],
"valueType": "any",
"value": [
{
"key": "Ocp-Apim-Subscription-Key",
"type": "string",
"value": "{{$pluginInput.key$}}"
}
],
"label": "",
"description": "common:core.module.input.description.Http Request Header",
"placeholder": "common:core.module.input.description.Http Request Header",
"required": false,
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpParams",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": [
{
"key": "q",
"type": "string",
"value": "{{query}}"
}
],
"label": "",
"required": false,
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpJsonBody",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": "",
"label": "",
"required": false,
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpFormBody",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": [],
"label": "",
"required": false,
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpContentType",
"renderTypeList": ["hidden"],
"valueType": "string",
"value": "json",
"label": "",
"required": false,
"debugLabel": "",
"toolDescription": ""
},
{
"valueType": "string",
"renderTypeList": ["reference"],
"key": "query",
"label": "query",
"toolDescription": "谷歌搜索检索词",
"required": true,
"canEdit": true,
"editField": {
"key": true,
"description": true
},
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"value": ["pluginInput", "query"]
}
],
"outputs": [
{
"id": "error",
"key": "error",
"label": "workflow:request_error",
"description": "HTTP请求错误信息,成功时返回空",
"valueType": "object",
"type": "static"
},
{
"id": "httpRawResponse",
"key": "httpRawResponse",
"required": true,
"label": "workflow:raw_response",
"description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。",
"valueType": "any",
"type": "static"
},
{
"id": "system_addOutputParam",
"key": "system_addOutputParam",
"type": "dynamic",
"valueType": "dynamic",
"label": "",
"editField": {
"key": true,
"valueType": true
}
},
{
"id": "M5YmxaYe8em1",
"type": "dynamic",
"key": "prompt",
"valueType": "string",
"label": "prompt"
}
]
},
{
"nodeId": "pZTkvleFSZXo",
"name": "代码运行",
"intro": "执行一段简单的脚本代码,通常用于进行复杂的数据处理。",
"avatar": "core/workflow/template/codeRun",
"flowNodeType": "code",
"showStatus": true,
"position": {
"x": 2153.5325687235554,
"y": -188.04429852303304
},
"version": "482",
"inputs": [
{
"key": "system_addInputParam",
"renderTypeList": ["addInputParam"],
"valueType": "dynamic",
"label": "",
"required": false,
"description": "workflow:these_variables_will_be_input_parameters_for_code_execution",
"editField": {
"key": true,
"valueType": true
},
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"debugLabel": "",
"toolDescription": ""
},
{
"key": "codeType",
"renderTypeList": ["hidden"],
"label": "",
"value": "js",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "code",
"renderTypeList": ["custom"],
"label": "",
"value": "function main({data}){\n const result = data.webPages.value.map((item) => ({\n title: item.name,\n link: item.url,\n snippet: item.snippet\n }))\n return JSON.stringify(result) \n}",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "data",
"valueType": "object",
"label": "data",
"renderTypeList": ["reference"],
"description": "",
"canEdit": true,
"editField": {
"key": true,
"valueType": true
},
"value": ["nyA6oA8mF1iW", "httpRawResponse"],
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
}
}
],
"outputs": [
{
"id": "system_rawResponse",
"key": "system_rawResponse",
"label": "workflow:full_response_data",
"valueType": "object",
"type": "static",
"description": ""
},
{
"id": "error",
"key": "error",
"label": "workflow:execution_error",
"description": "代码运行错误信息,成功时返回空",
"valueType": "object",
"type": "static"
},
{
"id": "system_addOutputParam",
"key": "system_addOutputParam",
"type": "dynamic",
"valueType": "dynamic",
"label": "",
"editField": {
"key": true,
"valueType": true
},
"description": "将代码中 return 的对象作为输出,传递给后续的节点"
},
{
"id": "qLUQfhG0ILRX",
"type": "dynamic",
"key": "prompt",
"valueType": "string",
"label": "prompt"
}
]
}
],
"edges": [
{
"source": "pluginInput",
"target": "nyA6oA8mF1iW",
"sourceHandle": "pluginInput-source-right",
"targetHandle": "nyA6oA8mF1iW-target-left"
},
{
"source": "nyA6oA8mF1iW",
"target": "pZTkvleFSZXo",
"sourceHandle": "nyA6oA8mF1iW-source-right",
"targetHandle": "pZTkvleFSZXo-target-left"
},
{
"source": "pZTkvleFSZXo",
"target": "pluginOutput",
"sourceHandle": "pZTkvleFSZXo-source-right",
"targetHandle": "pluginOutput-target-left"
}
],
"chatConfig": {
"welcomeText": "",
"variables": [],
"questionGuide": {
"open": false
},
"showToolCall": true,
"ttsConfig": {
"type": "web"
},
"whisperConfig": {
"open": false,
"autoSend": false,
"autoTTSResponse": false
},
"chatInputGuide": {
"open": false,
"textList": [],
"customUrl": ""
},
"instruction": "",
"_id": "6709e90cd9873479ee78fe71"
}
}
}
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;
{
"author": "silencezhang",
"version": "4811",
"name": "数据库连接",
"avatar": "core/workflow/template/datasource",
"intro": "可连接常用数据库,并执行sql",
"showStatus": true,
"weight": 10,
"isTool": true,
"templateType": "tools",
"workflow": {
"nodes": [
{
"nodeId": "pluginInput",
"name": "workflow:template.plugin_start",
"intro": "workflow:intro_plugin_input",
"avatar": "core/workflow/template/workflowStart",
"flowNodeType": "pluginInput",
"showStatus": false,
"position": {
"x": 334.24111198705634,
"y": -260.8285440670886
},
"version": "481",
"inputs": [
{
"renderTypeList": ["select", "reference"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
"key": "databaseType",
"label": "databaseType",
"description": "数据库的类型",
"defaultValue": "",
"list": [
{
"label": "MySQL",
"value": "MySQL"
},
{
"label": "PostgreSQL",
"value": "PostgreSQL"
},
{
"label": "Microsoft SQL Server",
"value": "Microsoft SQL Server"
}
],
"required": true
},
{
"renderTypeList": ["input", "reference"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
"key": "host",
"label": "host",
"description": "数据库连接host",
"defaultValue": "",
"required": true,
"list": [
{
"label": "",
"value": ""
}
]
},
{
"renderTypeList": ["numberInput", "reference"],
"selectedTypeIndex": 0,
"valueType": "number",
"canEdit": true,
"key": "port",
"label": "port",
"description": "数据库连接端口号",
"defaultValue": "",
"required": true,
"list": [
{
"label": "",
"value": ""
}
]
},
{
"renderTypeList": ["input", "reference"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
"key": "databaseName",
"label": "databaseName",
"description": "数据库名称",
"defaultValue": "",
"required": true,
"list": [
{
"label": "",
"value": ""
}
]
},
{
"renderTypeList": ["input", "reference"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
"key": "password",
"label": "password",
"description": "数据库密码",
"defaultValue": "",
"list": [
{
"label": "",
"value": ""
}
],
"required": true
},
{
"renderTypeList": ["input", "reference"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
"key": "user",
"label": "user",
"description": "数据库账号",
"defaultValue": "",
"list": [
{
"label": "",
"value": ""
}
],
"required": true
},
{
"renderTypeList": ["input", "reference"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
"key": "sql",
"label": "sql",
"description": "sql语句,可以传入sql语句直接执行",
"defaultValue": "",
"list": [
{
"label": "",
"value": ""
}
],
"required": true,
"toolDescription": "sql语句,可以传入sql语句直接执行"
}
],
"outputs": [
{
"id": "databaseType",
"valueType": "string",
"key": "databaseType",
"label": "databaseType",
"type": "hidden"
},
{
"id": "host",
"valueType": "string",
"key": "host",
"label": "host",
"type": "hidden"
},
{
"id": "port",
"valueType": "number",
"key": "port",
"label": "port",
"type": "hidden"
},
{
"id": "dataBaseName",
"valueType": "string",
"key": "databaseName",
"label": "databaseName",
"type": "hidden"
},
{
"id": "dataBasePwd",
"valueType": "string",
"key": "password",
"label": "password",
"type": "hidden"
},
{
"id": "user",
"valueType": "string",
"key": "user",
"label": "user",
"type": "hidden"
},
{
"id": "sql",
"valueType": "string",
"key": "sql",
"label": "sql",
"type": "hidden"
}
]
},
{
"nodeId": "pluginOutput",
"name": "common:core.module.template.self_output",
"intro": "workflow:intro_custom_plugin_output",
"avatar": "core/workflow/template/pluginOutput",
"flowNodeType": "pluginOutput",
"showStatus": false,
"position": {
"x": 1788.4723692358186,
"y": -153.2313912808486
},
"version": "481",
"inputs": [
{
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "result",
"label": "result",
"isToolOutput": true,
"description": "数据库连接结果",
"value": ["zBeXy7YZEiXe", "httpRawResponse"]
}
],
"outputs": []
},
{
"nodeId": "pluginConfig",
"name": "common:core.module.template.system_config",
"intro": "",
"avatar": "core/workflow/template/systemConfig",
"flowNodeType": "pluginConfig",
"position": {
"x": -133.25818142678844,
"y": -200.98784849888733
},
"version": "4811",
"inputs": [],
"outputs": []
},
{
"nodeId": "zBeXy7YZEiXe",
"name": "数据库连接",
"intro": "可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)",
"avatar": "core/workflow/template/httpRequest",
"flowNodeType": "httpRequest468",
"showStatus": true,
"position": {
"x": 1035.92763304296,
"y": -498.57137296107504
},
"version": "481",
"inputs": [
{
"key": "system_addInputParam",
"renderTypeList": ["addInputParam"],
"valueType": "dynamic",
"label": "",
"required": false,
"description": "common:core.module.input.description.HTTP Dynamic Input",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"valueDesc": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpMethod",
"renderTypeList": ["custom"],
"valueType": "string",
"label": "",
"value": "POST",
"required": true,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpTimeout",
"renderTypeList": ["custom"],
"valueType": "number",
"label": "",
"value": 30,
"min": 5,
"max": 600,
"required": true,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpReqUrl",
"renderTypeList": ["hidden"],
"valueType": "string",
"label": "",
"description": "common:core.module.input.description.Http Request Url",
"placeholder": "https://api.ai.com/getInventory",
"required": false,
"value": "databaseConnection",
"valueDesc": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpHeader",
"renderTypeList": ["custom"],
"valueType": "any",
"value": [],
"label": "",
"description": "common:core.module.input.description.Http Request Header",
"placeholder": "common:core.module.input.description.Http Request Header",
"required": false,
"valueDesc": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpParams",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": [],
"label": "",
"required": false,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpJsonBody",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": "{\r\n \"databaseType\":\"{{databaseType-H}}\",\r\n \"host\":\"{{host-H}}\",\r\n \"port\":\"{{port-H}}\",\r\n \"databaseName\":\"{{databaseName-H}}\",\r\n \"user\":\"{{databaseUser-H}}\",\r\n \"password\":\"{{databasePwd-H}}\",\r\n \"sql\":\"{{sql-H}}\"\r\n}",
"label": "",
"required": false,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpFormBody",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": [],
"label": "",
"required": false,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpContentType",
"renderTypeList": ["hidden"],
"valueType": "string",
"value": "json",
"label": "",
"required": false,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "databaseType-H",
"label": "databaseType-H",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"required": true,
"value": ["pluginInput", "databaseType"]
},
{
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "host-H",
"label": "host-H",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"required": true,
"value": ["pluginInput", "host"]
},
{
"renderTypeList": ["reference"],
"valueType": "number",
"canEdit": true,
"key": "port-H",
"label": "port-H",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"required": true,
"value": ["pluginInput", "port"]
},
{
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "databaseName-H",
"label": "databaseName-H",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"required": true,
"value": ["pluginInput", "dataBaseName"]
},
{
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "databasePwd-H",
"label": "databasePwd-H",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"required": true,
"value": ["pluginInput", "dataBasePwd"]
},
{
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "databaseUser-H",
"label": "databaseUser-H",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"required": true,
"value": ["pluginInput", "user"]
},
{
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "sql-H",
"label": "sql-H",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"required": true,
"value": ["pluginInput", "sql"]
}
],
"outputs": [
{
"id": "error",
"key": "error",
"label": "workflow:request_error",
"description": "HTTP请求错误信息,成功时返回空",
"valueType": "object",
"type": "static"
},
{
"id": "httpRawResponse",
"key": "httpRawResponse",
"required": true,
"label": "workflow:raw_response",
"description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。",
"valueType": "any",
"type": "static"
},
{
"id": "system_addOutputParam",
"key": "system_addOutputParam",
"type": "dynamic",
"valueType": "dynamic",
"label": "",
"customFieldConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": false
},
"valueDesc": "",
"description": ""
}
]
}
],
"edges": [
{
"source": "pluginInput",
"target": "zBeXy7YZEiXe",
"sourceHandle": "pluginInput-source-right",
"targetHandle": "zBeXy7YZEiXe-target-left"
},
{
"source": "zBeXy7YZEiXe",
"target": "pluginOutput",
"sourceHandle": "zBeXy7YZEiXe-source-right",
"targetHandle": "pluginOutput-target-left"
}
],
"chatConfig": {
"welcomeText": "",
"variables": [],
"questionGuide": {
"open": false
},
"showToolCall": true,
"ttsConfig": {
"type": "web"
},
"whisperConfig": {
"open": false,
"autoSend": false,
"autoTTSResponse": false
},
"chatInputGuide": {
"open": false,
"textList": [],
"customUrl": ""
},
"instruction": "数据源配置,支持主流数据库配置",
"_id": "670a23b31957c5b9899b4a4d"
}
}
}
{
"author": "silencezhang",
"version": "4817",
"name": "基础图表",
"avatar": "core/workflow/template/baseChart",
"intro": "根据数据生成图表,可根据chartType生成柱状图,折线图,饼图",
"showStatus": true,
"weight": 10,
"isTool": true,
"templateType": "tools",
"workflow": {
"nodes": [
{
"nodeId": "pluginInput",
"name": "common:core.module.template.self_input",
"intro": "workflow:intro_plugin_input",
"avatar": "core/workflow/template/workflowStart",
"flowNodeType": "pluginInput",
"showStatus": false,
"position": {
"x": 613.7921798611637,
"y": -123.07734867626235
},
"version": "481",
"inputs": [
{
"renderTypeList": ["input", "reference"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
"key": "title",
"label": "title",
"description": "BI图表的标题",
"defaultValue": "",
"list": [
{
"label": "",
"value": ""
}
],
"required": true,
"toolDescription": "BI图表的标题"
},
{
"renderTypeList": ["input", "reference"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
"key": "xAxis",
"label": "xAxis",
"description": "x轴数据,例如:[\"A\", \"B\", \"C\"]",
"defaultValue": "",
"required": true,
"toolDescription": "x轴数据,例如:[\"A\", \"B\", \"C\"]",
"list": [
{
"label": "",
"value": ""
}
]
},
{
"renderTypeList": ["input", "reference"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
"key": "yAxis",
"label": "yAxis",
"description": "y轴数据,例如:[1,2,3]",
"defaultValue": "",
"list": [
{
"label": "",
"value": ""
}
],
"required": true,
"toolDescription": "y轴数据,例如:[1,2,3]"
},
{
"renderTypeList": ["select", "reference"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
"key": "chartType",
"label": "chartType",
"description": "图表类型:柱状图,折线图,饼图",
"defaultValue": "",
"required": true,
"list": [
{
"label": "柱状图",
"value": "柱状图"
},
{
"label": "折线图",
"value": "折线图"
},
{
"label": "饼图",
"value": "饼图"
}
],
"toolDescription": "图表类型:柱状图,折线图,饼图"
}
],
"outputs": [
{
"id": "title",
"valueType": "string",
"key": "title",
"label": "title",
"type": "hidden"
},
{
"id": "xAxis",
"valueType": "string",
"key": "xAxis",
"label": "xAxis",
"type": "hidden"
},
{
"id": "yAxis",
"valueType": "string",
"key": "yAxis",
"label": "yAxis",
"type": "hidden"
},
{
"id": "chartType",
"valueType": "string",
"key": "chartType",
"label": "chartType",
"type": "hidden"
}
]
},
{
"nodeId": "pluginOutput",
"name": "common:core.module.template.self_output",
"intro": "workflow:intro_custom_plugin_output",
"avatar": "core/workflow/template/pluginOutput",
"flowNodeType": "pluginOutput",
"showStatus": false,
"position": {
"x": 2128.8138851197145,
"y": -63.52186746137181
},
"version": "481",
"inputs": [
{
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "图表 url",
"label": "图表 url",
"description": "可用使用markdown格式展示图片,如:![图片](url)",
"value": ["ws0DFKJnCPhk", "bzaYjKyQFOw2"],
"isToolOutput": true,
"required": true
}
],
"outputs": []
},
{
"nodeId": "ws0DFKJnCPhk",
"name": "HTTP 请求",
"intro": "可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)",
"avatar": "core/workflow/template/httpRequest",
"flowNodeType": "httpRequest468",
"showStatus": true,
"position": {
"x": 1264.2009472531117,
"y": -455.0773486762623
},
"version": "481",
"inputs": [
{
"key": "system_addInputParam",
"renderTypeList": ["addInputParam"],
"valueType": "dynamic",
"label": "",
"required": false,
"description": "common:core.module.input.description.HTTP Dynamic Input",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"valueDesc": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpMethod",
"renderTypeList": ["custom"],
"valueType": "string",
"label": "",
"value": "POST",
"required": true,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpTimeout",
"renderTypeList": ["custom"],
"valueType": "number",
"label": "",
"value": 30,
"min": 5,
"max": 600,
"required": true,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpReqUrl",
"renderTypeList": ["hidden"],
"valueType": "string",
"label": "",
"description": "common:core.module.input.description.Http Request Url",
"placeholder": "https://api.ai.com/getInventory",
"required": false,
"valueDesc": "",
"debugLabel": "",
"toolDescription": "",
"value": "drawing/baseChart"
},
{
"key": "system_httpHeader",
"renderTypeList": ["custom"],
"valueType": "any",
"value": [],
"label": "",
"description": "common:core.module.input.description.Http Request Header",
"placeholder": "common:core.module.input.description.Http Request Header",
"required": false,
"valueDesc": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpParams",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": [],
"label": "",
"required": false,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpJsonBody",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": "{\r\n \"title\": \"{{$pluginInput.title$}}\",\r\n \"xAxis\": {{$pluginInput.xAxis$}},\r\n \"yAxis\": {{$pluginInput.yAxis$}},\r\n \"chartType\": \"{{$pluginInput.chartType$}}\"\r\n}",
"label": "",
"required": false,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpFormBody",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": [],
"label": "",
"required": false,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpContentType",
"renderTypeList": ["hidden"],
"valueType": "string",
"value": "json",
"label": "",
"required": false,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
}
],
"outputs": [
{
"id": "error",
"key": "error",
"label": "workflow:request_error",
"description": "HTTP请求错误信息,成功时返回空",
"valueType": "object",
"type": "static"
},
{
"id": "httpRawResponse",
"key": "httpRawResponse",
"required": true,
"label": "workflow:raw_response",
"description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。",
"valueType": "any",
"type": "static"
},
{
"id": "system_addOutputParam",
"key": "system_addOutputParam",
"type": "dynamic",
"valueType": "dynamic",
"label": "",
"customFieldConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": false
},
"valueDesc": "",
"description": ""
},
{
"id": "bzaYjKyQFOw2",
"valueType": "string",
"type": "dynamic",
"key": "result",
"label": "result"
}
]
}
],
"edges": [
{
"source": "pluginInput",
"target": "ws0DFKJnCPhk",
"sourceHandle": "pluginInput-source-right",
"targetHandle": "ws0DFKJnCPhk-target-left"
},
{
"source": "ws0DFKJnCPhk",
"target": "pluginOutput",
"sourceHandle": "ws0DFKJnCPhk-source-right",
"targetHandle": "pluginOutput-target-left"
}
],
"chatConfig": {
"welcomeText": "",
"variables": [],
"showToolCall": true,
"questionGuide": {
"open": false
},
"ttsConfig": {
"type": "web"
},
"whisperConfig": {
"open": false,
"autoSend": false,
"autoTTSResponse": false
},
"chatInputGuide": {
"open": false,
"textList": [],
"customUrl": ""
},
"instruction": "数据源配置,支持主流数据库配置",
"_id": "670a23b31957c5b9899b4a4d"
}
}
}
{
"author": "",
"version": "4811",
"name": "Google搜索",
"avatar": "core/workflow/template/google",
"intro": "在google中搜索。",
"showStatus": true,
"weight": 10,
"courseUrl": "https://fael3z0zfze.feishu.cn/wiki/Vqk1w4ltNiuLifkHTuoc0hSrnVg?fromScene=spaceOverview",
"isTool": true,
"templateType": "search",
"workflow": {
"nodes": [
{
"nodeId": "pluginInput",
"name": "workflow:template.plugin_start",
"intro": "workflow:intro_plugin_input",
"avatar": "core/workflow/template/workflowStart",
"flowNodeType": "pluginInput",
"showStatus": false,
"position": {
"x": 636.3048409085379,
"y": -238.61714728578016
},
"version": "481",
"inputs": [
{
"renderTypeList": ["input", "reference"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
"key": "cx",
"label": "cx",
"description": "Google搜索cxID",
"defaultValue": "",
"list": [
{
"label": "",
"value": ""
}
],
"required": true
},
{
"renderTypeList": ["input", "reference"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
"key": "key",
"label": "key",
"description": "Google搜索key",
"defaultValue": "",
"required": true,
"list": []
},
{
"renderTypeList": ["input", "reference"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
"key": "query",
"label": "query",
"description": "查询字段值",
"defaultValue": "",
"list": [
{
"label": "",
"value": ""
}
],
"required": true,
"toolDescription": "查询字段值"
}
],
"outputs": [
{
"id": "cx",
"valueType": "string",
"key": "cx",
"label": "cx",
"type": "hidden"
},
{
"id": "key",
"valueType": "string",
"key": "key",
"label": "key",
"type": "hidden"
},
{
"id": "query",
"valueType": "string",
"key": "query",
"label": "query",
"type": "hidden"
}
]
},
{
"nodeId": "pluginOutput",
"name": "common:core.module.template.self_output",
"intro": "workflow:intro_custom_plugin_output",
"avatar": "core/workflow/template/pluginOutput",
"flowNodeType": "pluginOutput",
"showStatus": false,
"position": {
"x": 2764.1105686698083,
"y": -30.617147285780163
},
"version": "481",
"inputs": [
{
"renderTypeList": ["reference"],
"valueType": "object",
"canEdit": true,
"key": "result",
"label": "result",
"isToolOutput": true,
"description": "",
"value": ["pZTkvleFSZXo", "system_rawResponse"]
}
],
"outputs": []
},
{
"nodeId": "pluginConfig",
"name": "common:core.module.template.system_config",
"intro": "",
"avatar": "core/workflow/template/systemConfig",
"flowNodeType": "pluginConfig",
"position": {
"x": 184.66337662472682,
"y": -216.05298493910115
},
"version": "4811",
"inputs": [],
"outputs": []
},
{
"nodeId": "nyA6oA8mF1iW",
"name": "HTTP 请求",
"intro": "调用谷歌搜索,查询相关内容",
"avatar": "core/workflow/template/httpRequest",
"flowNodeType": "httpRequest468",
"showStatus": true,
"position": {
"x": 1335.0647252518884,
"y": -455.9043948565971
},
"version": "481",
"inputs": [
{
"key": "system_addInputParam",
"renderTypeList": ["addInputParam"],
"valueType": "dynamic",
"label": "",
"required": false,
"description": "common:core.module.input.description.HTTP Dynamic Input",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpMethod",
"renderTypeList": ["custom"],
"valueType": "string",
"label": "",
"value": "GET",
"required": true,
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpTimeout",
"renderTypeList": ["custom"],
"valueType": "number",
"label": "",
"value": 30,
"min": 5,
"max": 600,
"required": true,
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpReqUrl",
"renderTypeList": ["hidden"],
"valueType": "string",
"label": "",
"description": "common:core.module.input.description.Http Request Url",
"placeholder": "https://api.ai.com/getInventory",
"required": false,
"value": "https://www.googleapis.com/customsearch/v1",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpHeader",
"renderTypeList": ["custom"],
"valueType": "any",
"value": [],
"label": "",
"description": "common:core.module.input.description.Http Request Header",
"placeholder": "common:core.module.input.description.Http Request Header",
"required": false,
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpParams",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": [
{
"key": "q",
"type": "string",
"value": "{{query}}"
},
{
"key": "cx",
"type": "string",
"value": "{{$pluginInput.cx$}}"
},
{
"key": "key",
"type": "string",
"value": "{{$pluginInput.key$}}"
},
{
"key": "c2coff",
"type": "string",
"value": "1"
},
{
"key": "start",
"type": "string",
"value": "1"
},
{
"key": "end",
"type": "string",
"value": "20"
},
{
"key": "dateRestrict",
"type": "string",
"value": "m[1]"
}
],
"label": "",
"required": false,
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpJsonBody",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": "",
"label": "",
"required": false,
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpFormBody",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": [],
"label": "",
"required": false,
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpContentType",
"renderTypeList": ["hidden"],
"valueType": "string",
"value": "json",
"label": "",
"required": false,
"debugLabel": "",
"toolDescription": ""
},
{
"valueType": "string",
"renderTypeList": ["reference"],
"key": "query",
"label": "query",
"toolDescription": "谷歌搜索检索词",
"required": true,
"canEdit": true,
"editField": {
"key": true,
"description": true
},
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"value": ["pluginInput", "query"]
}
],
"outputs": [
{
"id": "error",
"key": "error",
"label": "workflow:request_error",
"description": "HTTP请求错误信息,成功时返回空",
"valueType": "object",
"type": "static"
},
{
"id": "httpRawResponse",
"key": "httpRawResponse",
"required": true,
"label": "workflow:raw_response",
"description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。",
"valueType": "any",
"type": "static"
},
{
"id": "system_addOutputParam",
"key": "system_addOutputParam",
"type": "dynamic",
"valueType": "dynamic",
"label": "",
"editField": {
"key": true,
"valueType": true
}
},
{
"id": "M5YmxaYe8em1",
"type": "dynamic",
"key": "prompt",
"valueType": "string",
"label": "prompt"
}
]
},
{
"nodeId": "pZTkvleFSZXo",
"name": "代码运行",
"intro": "执行一段简单的脚本代码,通常用于进行复杂的数据处理。",
"avatar": "core/workflow/template/codeRun",
"flowNodeType": "code",
"showStatus": true,
"position": {
"x": 2153.5325687235554,
"y": -188.04429852303304
},
"version": "482",
"inputs": [
{
"key": "system_addInputParam",
"renderTypeList": ["addInputParam"],
"valueType": "dynamic",
"label": "",
"required": false,
"description": "workflow:these_variables_will_be_input_parameters_for_code_execution",
"editField": {
"key": true,
"valueType": true
},
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"debugLabel": "",
"toolDescription": ""
},
{
"key": "codeType",
"renderTypeList": ["hidden"],
"label": "",
"value": "js",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "code",
"renderTypeList": ["custom"],
"label": "",
"value": "function main({data}){\n const result = data.items.map((item) => ({\n title: item.title,\n link: item.link,\n snippet: item.snippet\n }))\n return JSON.stringify(result) \n}",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "data",
"valueType": "object",
"label": "data",
"renderTypeList": ["reference"],
"description": "",
"canEdit": true,
"editField": {
"key": true,
"valueType": true
},
"value": ["nyA6oA8mF1iW", "httpRawResponse"],
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
}
}
],
"outputs": [
{
"id": "system_rawResponse",
"key": "system_rawResponse",
"label": "workflow:full_response_data",
"valueType": "object",
"type": "static",
"description": ""
},
{
"id": "error",
"key": "error",
"label": "workflow:execution_error",
"description": "代码运行错误信息,成功时返回空",
"valueType": "object",
"type": "static"
},
{
"id": "system_addOutputParam",
"key": "system_addOutputParam",
"type": "dynamic",
"valueType": "dynamic",
"label": "",
"editField": {
"key": true,
"valueType": true
},
"description": "将代码中 return 的对象作为输出,传递给后续的节点"
},
{
"id": "qLUQfhG0ILRX",
"type": "dynamic",
"key": "prompt",
"valueType": "string",
"label": "prompt"
}
]
}
],
"edges": [
{
"source": "pluginInput",
"target": "nyA6oA8mF1iW",
"sourceHandle": "pluginInput-source-right",
"targetHandle": "nyA6oA8mF1iW-target-left"
},
{
"source": "nyA6oA8mF1iW",
"target": "pZTkvleFSZXo",
"sourceHandle": "nyA6oA8mF1iW-source-right",
"targetHandle": "pZTkvleFSZXo-target-left"
},
{
"source": "pZTkvleFSZXo",
"target": "pluginOutput",
"sourceHandle": "pZTkvleFSZXo-source-right",
"targetHandle": "pluginOutput-target-left"
}
],
"chatConfig": {
"welcomeText": "",
"variables": [],
"questionGuide": {
"open": false
},
"showToolCall": true,
"ttsConfig": {
"type": "web"
},
"whisperConfig": {
"open": false,
"autoSend": false,
"autoTTSResponse": false
},
"chatInputGuide": {
"open": false,
"textList": [],
"customUrl": ""
},
"instruction": "",
"_id": "6709e90cd9873479ee78fe71"
}
}
}
{
"author": "",
"version": "4811",
"name": "Wiki搜索",
"avatar": "core/workflow/template/wiki",
"intro": "在Wiki中查询释义。",
"showStatus": true,
"weight": 10,
"isTool": true,
"templateType": "search",
"workflow": {
"nodes": [
{
"nodeId": "pluginInput",
"name": "插件开始",
"intro": "可以配置插件需要哪些输入,利用这些输入来运行插件",
"avatar": "core/workflow/template/workflowStart",
"flowNodeType": "pluginInput",
"showStatus": false,
"position": {
"x": 484.02074451450517,
"y": -79.06127656499825
},
"version": "481",
"inputs": [
{
"renderTypeList": ["input", "reference"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
"key": "query",
"label": "query",
"description": "检索词",
"required": true,
"toolDescription": "检索词",
"list": []
}
],
"outputs": [
{
"id": "query",
"valueType": "string",
"key": "query",
"label": "query",
"type": "hidden"
}
]
},
{
"nodeId": "pluginOutput",
"name": "插件输出",
"intro": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出",
"avatar": "core/workflow/template/pluginOutput",
"flowNodeType": "pluginOutput",
"showStatus": false,
"position": {
"x": 1759.5180706702588,
"y": -60.56127656499825
},
"version": "481",
"inputs": [
{
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "result",
"label": "result",
"description": " 检索结果",
"value": ["hjnVuJAOwyXV", "lEyy5QqyIBrK"]
}
],
"outputs": []
},
{
"nodeId": "hjnVuJAOwyXV",
"name": "HTTP 请求",
"intro": "可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)",
"avatar": "core/workflow/template/httpRequest",
"flowNodeType": "httpRequest468",
"showStatus": true,
"position": {
"x": 1054.6774638324207,
"y": -403.06127656499825
},
"version": "481",
"inputs": [
{
"key": "system_addInputParam",
"renderTypeList": ["addInputParam"],
"valueType": "dynamic",
"label": "",
"required": false,
"description": "common:core.module.input.description.HTTP Dynamic Input",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpMethod",
"renderTypeList": ["custom"],
"valueType": "string",
"label": "",
"value": "POST",
"required": true,
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpTimeout",
"renderTypeList": ["custom"],
"valueType": "number",
"label": "",
"value": 30,
"min": 5,
"max": 600,
"required": true,
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpReqUrl",
"renderTypeList": ["hidden"],
"valueType": "string",
"label": "",
"description": "common:core.module.input.description.Http Request Url",
"placeholder": "https://api.ai.com/getInventory",
"required": false,
"value": "wiki",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpHeader",
"renderTypeList": ["custom"],
"valueType": "any",
"value": [],
"label": "",
"description": "common:core.module.input.description.Http Request Header",
"placeholder": "common:core.module.input.description.Http Request Header",
"required": false,
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpParams",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": [],
"label": "",
"required": false,
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpJsonBody",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": "{\n \"query\": \"{{query}}\"\n}",
"label": "",
"required": false,
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpFormBody",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": [],
"label": "",
"required": false,
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpContentType",
"renderTypeList": ["hidden"],
"valueType": "string",
"value": "json",
"label": "",
"required": false,
"debugLabel": "",
"toolDescription": ""
},
{
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "query",
"label": "query",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"required": true,
"value": ["pluginInput", "query"]
}
],
"outputs": [
{
"id": "error",
"key": "error",
"label": "workflow:request_error",
"description": "HTTP请求错误信息,成功时返回空",
"valueType": "object",
"type": "static"
},
{
"id": "httpRawResponse",
"key": "httpRawResponse",
"required": true,
"label": "workflow:raw_response",
"description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。",
"valueType": "any",
"type": "static"
},
{
"id": "system_addOutputParam",
"key": "system_addOutputParam",
"type": "dynamic",
"valueType": "dynamic",
"label": "",
"customFieldConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": false
}
},
{
"id": "lEyy5QqyIBrK",
"valueType": "string",
"type": "dynamic",
"key": "result",
"label": "result"
}
]
},
{
"nodeId": "f1mRh1D85H2D",
"name": "系统配置",
"intro": "",
"avatar": "core/workflow/template/systemConfig",
"flowNodeType": "pluginConfig",
"position": {
"x": -28.511358745511643,
"y": -103.56127656499825
},
"version": "4811",
"inputs": [],
"outputs": []
}
],
"edges": [
{
"source": "pluginInput",
"target": "hjnVuJAOwyXV",
"sourceHandle": "pluginInput-source-right",
"targetHandle": "hjnVuJAOwyXV-target-left"
},
{
"source": "hjnVuJAOwyXV",
"target": "pluginOutput",
"sourceHandle": "hjnVuJAOwyXV-source-right",
"targetHandle": "pluginOutput-target-left"
}
],
"chatConfig": {
"welcomeText": "",
"variables": [],
"questionGuide": {
"open": false
},
"showToolCall": true,
"ttsConfig": {
"type": "web"
},
"whisperConfig": {
"open": false,
"autoSend": false,
"autoTTSResponse": false
},
"chatInputGuide": {
"open": false,
"textList": [],
"customUrl": ""
},
"instruction": "",
"_id": "67075cd2702bd7168ef8cb2d"
}
}
}
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 {
} from './constants';
import { MilvusCtrl } from './milvus';
import { retryFn } from '@fastgpt/global/common/system/utils';
import { MongoTeam } from '../../support/user/team/teamSchema';
import { getLogger, LogCategories } from '../logger';
const getVectorObj = (): VectorControllerType => {
......@@ -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) =>
typeof input === 'string'
? {
......@@ -73,7 +78,8 @@ export const insertDatasetDataVector = async ({
const { vectors, tokens } = await getVectors({
model,
inputs: embeddingInputs,
type: 'db'
type: 'db',
userKey
});
const { insertIds } = await retryFn(() =>
Vector.insert({
......
......@@ -10,7 +10,8 @@ export async function text2Speech({
input,
model,
voice,
speed = 1
speed = 1,
userKey
}: {
res: NodeHttpResponse;
onSuccess: (e: { model: string; buffer: Buffer }) => void;
......@@ -19,9 +20,10 @@ export async function text2Speech({
model: string;
voice: string;
speed?: number;
userKey?: any;
}) {
const modelData = getTTSModel(model)!;
const { ai } = getAIApi();
const { ai } = getAIApi({ userKey });
const response = await ai.audio.speech.create(
{
model,
......
......@@ -13,6 +13,7 @@ type GetVectorsBaseProps = {
model: EmbeddingModelItemType;
type?: `${EmbeddingTypeEnm}`;
headers?: Record<string, string>;
userKey?: any;
};
const InputItemSchema = z.object({
......@@ -43,7 +44,7 @@ const countInputTokens = async (input: GetVectorInputItem) => {
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
.array(InputItemSchema)
.parse(rawInputs)
......@@ -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);
chunkSize = isNaN(chunkSize) ? 1 : chunkSize;
......
......@@ -14,12 +14,14 @@ export async function createQuestionGuide({
messages,
model,
customPrompt,
teamId
teamId,
userKey
}: {
messages: ChatCompletionMessageParam[];
model: string;
customPrompt?: string;
teamId: string;
userKey?: any;
}): Promise<{
result: string[];
inputTokens: number;
......@@ -39,6 +41,7 @@ export async function createQuestionGuide({
usage: { inputTokens, outputTokens }
} = await createLLMResponse({
teamId,
userKey,
saveLLMResponseRecord: false,
body: {
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';
/* ============= dataset ========== */
/* find all datasetId by top datasetId */
export async function findDatasetAndAllChildren({
teamId,
datasetId,
fields
}: {
teamId: string;
datasetId: string;
fields?: string;
}): Promise<DatasetSchemaType[]> {
const find = async (id: string) => {
const children = await MongoDataset.find(
{
teamId,
parentId: id
},
fields
......
......@@ -8,12 +8,12 @@ import {
DatasetTypeMap,
ParagraphChunkAIModeEnum
} from '@fastgpt/global/core/dataset/constants';
import type { DatasetSchemaType } from '@fastgpt/global/core/dataset/type.d';
import {
TeamCollectionName,
TeamMemberCollectionName
} from '@fastgpt/global/support/user/team/constant';
import { userCollectionName } from '../../support/user/schema';
import type { DatasetSchemaType } from '@fastgpt/global/core/dataset/type';
export const DatasetCollectionName = 'datasets';
......@@ -110,6 +110,10 @@ const DatasetSchema = new Schema({
type: String,
default: ''
},
permissionType: {
type: String,
default: 'private'
},
websiteConfig: {
type: {
url: {
......
import { createChatCompletion } from '../../../../ai/config';
import { filterGPTMessageByMaxContext, loadRequestMessages } from '../../../../chat/utils';
import {
type ChatCompletion,
type ChatCompletionMessageToolCall,
type StreamChatType,
type ChatCompletionToolMessageParam,
type ChatCompletionMessageParam,
type ChatCompletionTool,
type CompletionFinishReason
} from '@fastgpt/global/core/ai/type';
import { type NextApiResponse } from 'next';
import { responseWriteController } from '../../../../../common/response';
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants';
import { dispatchWorkFlow } from '../../index';
import { type DispatchToolModuleProps, type RunToolResponse, type ToolNodeItemType } from './type';
import json5 from 'json5';
import { type DispatchFlowResponse, type WorkflowResponseType } from '../../type';
import { countGptMessagesTokens } from '../../../../../common/string/tiktoken/index';
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import { type AIChatItemType } from '@fastgpt/global/core/chat/type';
import { formatToolResponse, initToolCallEdges, initToolNodes } from './utils';
import {
computedMaxToken,
llmCompletionsBodyFormat,
removeDatasetCiteText,
parseLLMStreamResponse
} from '../../../../ai/utils';
import { getNanoid, sliceStrStartEnd } from '@fastgpt/global/common/string/tools';
import { toolValueTypeList, valueTypeJsonSchemaMap } from '@fastgpt/global/core/workflow/constants';
import { type WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
import { getErrText } from '@fastgpt/global/common/error/utils';
type ToolRunResponseType = {
toolRunResponse?: DispatchFlowResponse;
toolMsgParams: ChatCompletionToolMessageParam;
}[];
/*
调用思路:
先Check 是否是交互节点触发
交互模式:
1. 从缓存中获取工作流运行数据
2. 运行工作流
3. 检测是否有停止信号或交互响应
- 无:汇总结果,递归运行工具
- 有:缓存结果,结束调用
非交互模式:
1. 组合 tools
2. 过滤 messages
3. Load request llm messages: system prompt, histories, human question, (assistant responses, tool responses, assistant responses....)
4. 请求 LLM 获取结果
- 有工具调用
1. 批量运行工具的工作流,获取结果(工作流原生结果,工具执行结果)
2. 合并递归中,所有工具的原生运行结果
3. 组合 assistants tool 响应
4. 组合本次 request 和 llm response 的 messages,并计算出消耗的 tokens
5. 组合本次 request、llm response 和 tool response 结果
6. 组合本次的 assistant responses: history assistant + tool assistant + tool child assistant
7. 判断是否还有停止信号或交互响应
- 无:递归运行工具
- 有:缓存结果,结束调用
- 无工具调用
1. 汇总结果,递归运行工具
2. 计算 completeMessages 和 tokens 后返回。
交互节点额外缓存结果包括:
1. 入口的节点 id
2. toolCallId: 本次工具调用的 ID,可以找到是调用了哪个工具,入口并不会记录工具的 id
3. messages:本次递归中,assistants responses 和 tool responses
*/
export const runToolWithToolChoice = async (
props: DispatchToolModuleProps & {
maxRunToolTimes: number;
},
response?: RunToolResponse
): Promise<RunToolResponse> => {
const {
messages,
toolNodes,
toolModel,
maxRunToolTimes,
interactiveEntryToolParams,
...workflowProps
} = props;
let {
res,
requestOrigin,
runtimeNodes,
runtimeEdges,
stream,
retainDatasetCite = true,
externalProvider,
workflowStreamResponse,
params: {
temperature,
maxToken,
aiChatVision,
aiChatTopP,
aiChatStopSign,
aiChatResponseFormat,
aiChatJsonSchema,
aiChatReasoning
}
} = workflowProps;
aiChatReasoning = !!aiChatReasoning && !!toolModel.reasoning;
if (maxRunToolTimes <= 0 && response) {
return response;
}
// Interactive
if (interactiveEntryToolParams) {
initToolNodes(runtimeNodes, interactiveEntryToolParams.entryNodeIds);
initToolCallEdges(runtimeEdges, interactiveEntryToolParams.entryNodeIds);
// Run entry tool
const toolRunResponse = await dispatchWorkFlow({
...workflowProps,
isToolCall: true
});
const stringToolResponse = formatToolResponse(toolRunResponse.toolResponses);
// Response to frontend
workflowStreamResponse?.({
event: SseResponseEventEnum.toolResponse,
data: {
tool: {
id: interactiveEntryToolParams.toolCallId,
toolName: '',
toolAvatar: '',
params: '',
response: sliceStrStartEnd(stringToolResponse, 5000, 5000)
}
}
});
// Check stop signal
const hasStopSignal = toolRunResponse.flowResponses?.some((item) => item.toolStop);
// Check interactive response(Only 1 interaction is reserved)
const workflowInteractiveResponse = toolRunResponse.workflowInteractiveResponse;
const requestMessages = [
...messages,
...interactiveEntryToolParams.memoryMessages.map((item) =>
item.role === 'tool' && item.tool_call_id === interactiveEntryToolParams.toolCallId
? {
...item,
content: stringToolResponse
}
: item
)
];
if (hasStopSignal || workflowInteractiveResponse) {
// Get interactive tool data
const toolWorkflowInteractiveResponse: WorkflowInteractiveResponseType | undefined =
workflowInteractiveResponse
? {
...workflowInteractiveResponse,
toolParams: {
entryNodeIds: workflowInteractiveResponse.entryNodeIds,
toolCallId: interactiveEntryToolParams.toolCallId,
memoryMessages: interactiveEntryToolParams.memoryMessages
}
}
: undefined;
return {
dispatchFlowResponse: [toolRunResponse],
toolNodeInputTokens: 0,
toolNodeOutputTokens: 0,
completeMessages: requestMessages,
assistantResponses: toolRunResponse.assistantResponses,
runTimes: toolRunResponse.runTimes,
toolWorkflowInteractiveResponse
};
}
return runToolWithToolChoice(
{
...props,
interactiveEntryToolParams: undefined,
maxRunToolTimes: maxRunToolTimes - 1,
// Rewrite toolCall messages
messages: requestMessages
},
{
dispatchFlowResponse: [toolRunResponse],
toolNodeInputTokens: 0,
toolNodeOutputTokens: 0,
assistantResponses: toolRunResponse.assistantResponses,
runTimes: toolRunResponse.runTimes
}
);
}
// ------------------------------------------------------------
const assistantResponses = response?.assistantResponses || [];
const tools: ChatCompletionTool[] = toolNodes.map((item) => {
if (item.jsonSchema) {
return {
type: 'function',
function: {
name: item.nodeId,
description: item.intro || item.name,
parameters: item.jsonSchema
}
};
}
const properties: Record<
string,
{
type: string;
description: string;
enum?: string[];
required?: boolean;
items?: {
type: string;
};
}
> = {};
item.toolParams.forEach((item) => {
const jsonSchema = item.valueType
? valueTypeJsonSchemaMap[item.valueType] || toolValueTypeList[0].jsonSchema
: toolValueTypeList[0].jsonSchema;
properties[item.key] = {
...jsonSchema,
description: item.toolDescription || '',
enum: item.enum?.split('\n').filter(Boolean) || undefined
};
});
return {
type: 'function',
function: {
name: item.nodeId,
description: item.toolDescription || item.intro || item.name,
parameters: {
type: 'object',
properties,
required: item.toolParams.filter((item) => item.required).map((item) => item.key)
}
}
};
});
const max_tokens = computedMaxToken({
model: toolModel,
maxToken,
min: 100
});
// Filter histories by maxToken
const filterMessages = (
await filterGPTMessageByMaxContext({
messages,
maxContext: toolModel.maxContext - (max_tokens || 0) // filter token. not response maxToken
})
).map((item) => {
if (item.role === 'assistant' && item.tool_calls) {
return {
...item,
tool_calls: item.tool_calls.map((tool) => ({
id: tool.id,
type: tool.type,
function: tool.function
}))
};
}
return item;
});
const [requestMessages] = await Promise.all([
loadRequestMessages({
messages: filterMessages,
useVision: toolModel.vision && aiChatVision,
origin: requestOrigin
})
]);
const requestBody = llmCompletionsBodyFormat(
{
model: toolModel.model,
stream,
messages: requestMessages,
tools,
tool_choice: 'auto',
parallel_tool_calls: true,
temperature,
max_tokens,
top_p: aiChatTopP,
stop: aiChatStopSign,
response_format: {
type: aiChatResponseFormat as any,
json_schema: aiChatJsonSchema
}
},
toolModel
);
// console.log(JSON.stringify(requestBody, null, 2), '==requestMessages');
/* Run llm */
const {
response: aiResponse,
isStreamResponse,
getEmptyResponseTip
} = await createChatCompletion({
body: requestBody,
userKey: externalProvider.openaiAccount,
options: {
headers: {
Accept: 'application/json, text/plain, */*'
}
}
});
let { reasoningContent, answer, toolCalls, finish_reason, inputTokens, outputTokens } =
await (async () => {
if (isStreamResponse) {
if (!res || res.closed) {
return {
reasoningContent: '',
answer: '',
toolCalls: [],
finish_reason: 'close' as const,
inputTokens: 0,
outputTokens: 0
};
}
const result = await streamResponse({
res,
workflowStreamResponse,
toolNodes,
stream: aiResponse,
aiChatReasoning,
retainDatasetCite
});
return {
reasoningContent: result.reasoningContent,
answer: result.answer,
toolCalls: result.toolCalls,
finish_reason: result.finish_reason,
inputTokens: result.usage.prompt_tokens,
outputTokens: result.usage.completion_tokens
};
} else {
const result = aiResponse as ChatCompletion;
const finish_reason = result.choices?.[0]?.finish_reason as CompletionFinishReason;
const calls = result.choices?.[0]?.message?.tool_calls || [];
const answer = result.choices?.[0]?.message?.content || '';
// @ts-ignore
const reasoningContent = result.choices?.[0]?.message?.reasoning_content || '';
const usage = result.usage;
const formatReasoningContent = removeDatasetCiteText(reasoningContent, retainDatasetCite);
const formatAnswer = removeDatasetCiteText(answer, retainDatasetCite);
if (aiChatReasoning && reasoningContent) {
workflowStreamResponse?.({
event: SseResponseEventEnum.fastAnswer,
data: textAdaptGptResponse({
reasoning_content: formatReasoningContent
})
});
}
// 格式化 toolCalls
const toolCalls = calls.map((tool) => {
const toolNode = toolNodes.find((item) => item.nodeId === tool.function?.name);
// 不支持 stream 模式的模型的这里需要补一个响应给客户端
workflowStreamResponse?.({
event: SseResponseEventEnum.toolCall,
data: {
tool: {
id: tool.id,
toolName: toolNode?.name || '',
toolAvatar: toolNode?.avatar || '',
functionName: tool.function.name,
params: tool.function?.arguments ?? '',
response: ''
}
}
});
return {
...tool,
toolName: toolNode?.name || '',
toolAvatar: toolNode?.avatar || ''
};
});
if (answer) {
workflowStreamResponse?.({
event: SseResponseEventEnum.fastAnswer,
data: textAdaptGptResponse({
text: formatAnswer
})
});
}
return {
reasoningContent: formatReasoningContent,
answer: formatAnswer,
toolCalls: toolCalls,
finish_reason,
inputTokens: usage?.prompt_tokens,
outputTokens: usage?.completion_tokens
};
}
})();
if (!answer && !reasoningContent && toolCalls.length === 0) {
return Promise.reject(getEmptyResponseTip());
}
/* Run the selected tool by LLM.
Since only reference parameters are passed, if the same tool is run in parallel, it will get the same run parameters
*/
const toolsRunResponse: ToolRunResponseType = [];
for await (const tool of toolCalls) {
try {
const toolNode = toolNodes.find((item) => item.nodeId === tool.function?.name);
if (!toolNode) continue;
const startParams = (() => {
try {
return json5.parse(tool.function.arguments);
} catch (error) {
return {};
}
})();
initToolNodes(runtimeNodes, [toolNode.nodeId], startParams);
const toolRunResponse = await dispatchWorkFlow({
...workflowProps,
isToolCall: true
});
const stringToolResponse = formatToolResponse(toolRunResponse.toolResponses);
const toolMsgParams: ChatCompletionToolMessageParam = {
tool_call_id: tool.id,
role: ChatCompletionRequestMessageRoleEnum.Tool,
name: tool.function.name,
content: stringToolResponse
};
workflowStreamResponse?.({
event: SseResponseEventEnum.toolResponse,
data: {
tool: {
id: tool.id,
toolName: '',
toolAvatar: '',
params: '',
response: sliceStrStartEnd(stringToolResponse, 5000, 5000)
}
}
});
toolsRunResponse.push({
toolRunResponse,
toolMsgParams
});
} catch (error) {
const err = getErrText(error);
workflowStreamResponse?.({
event: SseResponseEventEnum.toolResponse,
data: {
tool: {
id: tool.id,
toolName: '',
toolAvatar: '',
params: '',
response: sliceStrStartEnd(err, 5000, 5000)
}
}
});
toolsRunResponse.push({
toolRunResponse: undefined,
toolMsgParams: {
tool_call_id: tool.id,
role: ChatCompletionRequestMessageRoleEnum.Tool,
name: tool.function.name,
content: sliceStrStartEnd(err, 5000, 5000)
}
});
}
}
const flatToolsResponseData = toolsRunResponse
.map((item) => item.toolRunResponse)
.flat()
.filter(Boolean) as DispatchFlowResponse[];
// concat tool responses
const dispatchFlowResponse = response
? response.dispatchFlowResponse.concat(flatToolsResponseData)
: flatToolsResponseData;
if (toolCalls.length > 0) {
// Run the tool, combine its results, and perform another round of AI calls
const assistantToolMsgParams: ChatCompletionMessageParam[] = [
...(answer || reasoningContent
? [
{
role: ChatCompletionRequestMessageRoleEnum.Assistant as 'assistant',
content: answer,
reasoning_text: reasoningContent
}
]
: []),
{
role: ChatCompletionRequestMessageRoleEnum.Assistant,
tool_calls: toolCalls
}
];
/*
...
user
assistant: tool data
*/
const concatToolMessages = [
...requestMessages,
...assistantToolMsgParams
] as ChatCompletionMessageParam[];
// Only toolCall tokens are counted here, Tool response tokens count towards the next reply
inputTokens = inputTokens || (await countGptMessagesTokens(requestMessages, tools));
outputTokens = outputTokens || (await countGptMessagesTokens(assistantToolMsgParams));
/*
...
user
assistant: tool data
tool: tool response
*/
const completeMessages = [
...concatToolMessages,
...toolsRunResponse.map((item) => item?.toolMsgParams)
];
/*
Get tool node assistant response
history assistant
current tool assistant
tool child assistant
*/
const toolNodeAssistant = GPTMessages2Chats([
...assistantToolMsgParams,
...toolsRunResponse.map((item) => item?.toolMsgParams)
])[0] as AIChatItemType;
const toolChildAssistants = flatToolsResponseData
.map((item) => item.assistantResponses)
.flat()
.filter((item) => item.type !== ChatItemValueTypeEnum.interactive); // 交互节点留着下次记录
const toolNodeAssistants = [
...assistantResponses,
...toolNodeAssistant.value,
...toolChildAssistants
];
const runTimes =
(response?.runTimes || 0) +
flatToolsResponseData.reduce((sum, item) => sum + item.runTimes, 0);
const toolNodeInputTokens = response ? response.toolNodeInputTokens + inputTokens : inputTokens;
const toolNodeOutputTokens = response
? response.toolNodeOutputTokens + outputTokens
: outputTokens;
// Check stop signal
const hasStopSignal = flatToolsResponseData.some(
(item) => !!item.flowResponses?.find((item) => item.toolStop)
);
// Check interactive response(Only 1 interaction is reserved)
const workflowInteractiveResponseItem = toolsRunResponse.find(
(item) => item.toolRunResponse?.workflowInteractiveResponse
);
// Check finish_reason: if it's 'stop' or 'length', we should stop recursive calls
const shouldStopByFinishReason = finish_reason === 'stop' || finish_reason === 'length';
// Check for duplicate tool calls (same tool name and arguments) to prevent infinite loops
// This prevents MCP tools from being called repeatedly with the same parameters
const checkDuplicateToolCalls = () => {
if (toolCalls.length === 0) return false;
// Get the last assistant message with tool calls from requestMessages
// (checking history to see if model is repeating the same tool call)
let lastToolCalls: Array<{ name: string; arguments: string }> = [];
for (let i = requestMessages.length - 1; i >= 0; i--) {
const msg = requestMessages[i];
if (msg.role === 'assistant' && msg.tool_calls && msg.tool_calls.length > 0) {
lastToolCalls = msg.tool_calls
.map((tc) => ({
name: tc.function?.name || '',
arguments: tc.function?.arguments || ''
}))
.filter((tc) => tc.name);
break;
}
}
if (lastToolCalls.length === 0) return false;
// Check if current tool calls are identical to the last ones
const currentToolCallKeys = toolCalls
.map((tc) => ({
name: tc.function?.name || '',
arguments: tc.function?.arguments || ''
}))
.filter((tc) => tc.name);
// If all current tool calls match the last ones exactly, it's a duplicate
if (
currentToolCallKeys.length === lastToolCalls.length &&
currentToolCallKeys.every((current, index) => {
const last = lastToolCalls[index];
return last && current.name === last.name && current.arguments === last.arguments;
})
) {
return true;
}
return false;
};
const hasDuplicateToolCalls = checkDuplicateToolCalls();
if (
hasStopSignal ||
workflowInteractiveResponseItem ||
shouldStopByFinishReason ||
hasDuplicateToolCalls
) {
// Get interactive tool data
const workflowInteractiveResponse =
workflowInteractiveResponseItem?.toolRunResponse?.workflowInteractiveResponse;
// Flashback traverses completeMessages, intercepting messages that know the first user
const firstUserIndex = completeMessages.findLastIndex((item) => item.role === 'user');
const newMessages = completeMessages.slice(firstUserIndex + 1);
const toolWorkflowInteractiveResponse: WorkflowInteractiveResponseType | undefined =
workflowInteractiveResponse
? {
...workflowInteractiveResponse,
toolParams: {
entryNodeIds: workflowInteractiveResponse.entryNodeIds,
toolCallId: workflowInteractiveResponseItem?.toolMsgParams.tool_call_id,
memoryMessages: newMessages
}
}
: undefined;
return {
dispatchFlowResponse,
toolNodeInputTokens,
toolNodeOutputTokens,
completeMessages,
assistantResponses: toolNodeAssistants,
toolWorkflowInteractiveResponse,
runTimes,
finish_reason
};
}
return runToolWithToolChoice(
{
...props,
maxRunToolTimes: maxRunToolTimes - 1,
messages: completeMessages
},
{
dispatchFlowResponse,
toolNodeInputTokens,
toolNodeOutputTokens,
assistantResponses: toolNodeAssistants,
runTimes,
finish_reason
}
);
} else {
// No tool is invoked, indicating that the process is over
const gptAssistantResponse: ChatCompletionMessageParam = {
role: ChatCompletionRequestMessageRoleEnum.Assistant,
content: answer,
reasoning_text: reasoningContent
};
const completeMessages = filterMessages.concat(gptAssistantResponse);
inputTokens = inputTokens || (await countGptMessagesTokens(requestMessages, tools));
outputTokens = outputTokens || (await countGptMessagesTokens([gptAssistantResponse]));
// concat tool assistant
const toolNodeAssistant = GPTMessages2Chats([gptAssistantResponse])[0] as AIChatItemType;
return {
dispatchFlowResponse: response?.dispatchFlowResponse || [],
toolNodeInputTokens: response ? response.toolNodeInputTokens + inputTokens : inputTokens,
toolNodeOutputTokens: response ? response.toolNodeOutputTokens + outputTokens : outputTokens,
completeMessages,
assistantResponses: [...assistantResponses, ...toolNodeAssistant.value],
runTimes: (response?.runTimes || 0) + 1,
finish_reason
};
}
};
async function streamResponse({
res,
toolNodes,
stream,
workflowStreamResponse,
aiChatReasoning,
retainDatasetCite
}: {
res: NextApiResponse;
toolNodes: ToolNodeItemType[];
stream: StreamChatType;
workflowStreamResponse?: WorkflowResponseType;
aiChatReasoning: boolean;
retainDatasetCite?: boolean;
}) {
const write = responseWriteController({
res,
readStream: stream
});
let callingTool: { name: string; arguments: string } | null = null;
let toolCalls: ChatCompletionMessageToolCall[] = [];
const { parsePart, getResponseData, updateFinishReason } = parseLLMStreamResponse();
for await (const part of stream) {
if (res.closed) {
stream.controller?.abort();
updateFinishReason('close');
break;
}
const { reasoningContent, responseContent } = parsePart({
part,
parseThinkTag: true,
retainDatasetCite
});
const responseChoice = part.choices?.[0]?.delta;
// Reasoning response
if (aiChatReasoning && reasoningContent) {
workflowStreamResponse?.({
write,
event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({
reasoning_content: reasoningContent
})
});
}
if (responseContent) {
workflowStreamResponse?.({
write,
event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({
text: responseContent
})
});
}
// Parse tool calls
if (responseChoice?.tool_calls?.length) {
responseChoice.tool_calls.forEach((toolCall, i) => {
const index = toolCall.index ?? i;
// Call new tool
const hasNewTool = toolCall?.function?.name || callingTool;
if (hasNewTool) {
// 有 function name,代表新 call 工具
if (toolCall?.function?.name) {
callingTool = {
name: toolCall.function?.name || '',
arguments: toolCall.function?.arguments || ''
};
} else if (callingTool) {
// Continue call(Perhaps the name of the previous function was incomplete)
callingTool.name += toolCall.function?.name || '';
callingTool.arguments += toolCall.function?.arguments || '';
}
if (!callingTool) {
return;
}
const toolNode = toolNodes.find((item) => item.nodeId === callingTool!.name);
if (toolNode) {
// New tool, add to list.
const toolId = getNanoid();
toolCalls[index] = {
...toolCall,
id: toolId,
type: 'function',
function: callingTool,
toolName: toolNode.name,
toolAvatar: toolNode.avatar
};
workflowStreamResponse?.({
event: SseResponseEventEnum.toolCall,
data: {
tool: {
id: toolId,
toolName: toolNode.name,
toolAvatar: toolNode.avatar,
functionName: callingTool.name,
params: callingTool?.arguments ?? '',
response: ''
}
}
});
callingTool = null;
}
} else {
/* arg 追加到当前工具的参数里 */
const arg: string = toolCall?.function?.arguments ?? '';
const currentTool = toolCalls[index];
if (currentTool && arg) {
currentTool.function.arguments += arg;
workflowStreamResponse?.({
write,
event: SseResponseEventEnum.toolParams,
data: {
tool: {
id: currentTool.id,
toolName: '',
toolAvatar: '',
params: arg,
response: ''
}
}
});
}
}
});
}
}
const { reasoningContent, content, finish_reason, usage } = getResponseData();
return {
reasoningContent,
answer: content,
toolCalls: toolCalls.filter(Boolean),
finish_reason,
usage
};
}
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';
import { getHistories } from '../utils';
import { hashStr } from '@fastgpt/global/common/string/tools';
import type { DispatchNodeResultType, ModuleDispatchProps } from '../../types/runtime';
import { MongoTeam } from '../../../../support/user/team/teamSchema';
type Props = ModuleDispatchProps<{
[NodeInputKeyEnum.aiModel]: string;
......@@ -25,12 +26,17 @@ export const dispatchQueryExtension = async ({
node,
usagePush,
runningUserInfo,
params: { model, systemPrompt, history, userChatInput }
params: { model, systemPrompt, history, userChatInput },
runningAppInfo
}: Props): Promise<Response> => {
if (!userChatInput) {
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 embeddingModel = getEmbeddingModel();
const chatHistories = getHistories(history, histories);
......@@ -48,7 +54,8 @@ export const dispatchQueryExtension = async ({
histories: chatHistories,
llmModel: queryExtensionModel.model,
embeddingModel: embeddingModel.model,
teamId: runningUserInfo.teamId
teamId: runningUserInfo.teamId,
userKey
});
extensionQueries.unshift(userChatInput);
......
......@@ -82,6 +82,9 @@ const OutLinkSchema = new Schema({
},
hookUrl: {
type: String
},
password: {
type: String
}
},
......
......@@ -43,7 +43,6 @@ export const pushResult2Remote = async ({
shareId
});
if (!outLink?.limit?.hookUrl) return;
axios({
method: 'post',
baseURL: outLink.limit.hookUrl,
......
......@@ -21,6 +21,7 @@ import {
} from '@fastgpt/global/support/permission/app/constant';
import { parseHeaderCert } from '../auth/common';
import { sumPer } from '@fastgpt/global/support/permission/utils';
import { MongoTeamMember } from '../../user/team/teamMemberSchema';
export const authWorkflowToolByTmbId = async ({
tmbId,
......@@ -69,16 +70,16 @@ export const authAppByTmbId = async ({
}
if (String(app.teamId) !== teamId) {
return Promise.reject(AppErrEnum.unAuthApp);
// return Promise.reject(AppErrEnum.unAuthApp);
}
if (app.type === AppTypeEnum.hidden) {
if (per === AppReadChatLogPerVal) {
if (!tmbPer.hasManagePer) {
return Promise.reject(AppErrEnum.unAuthApp);
// return Promise.reject(AppErrEnum.unAuthApp);
}
} else if (per !== ReadPermissionVal) {
return Promise.reject(AppErrEnum.unAuthApp);
// return Promise.reject(AppErrEnum.unAuthApp);
}
return {
......@@ -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 =
app.inheritPermission && !AppFolderTypeList.includes(app.type) && !!app.parentId;
......@@ -119,7 +124,7 @@ export const authAppByTmbId = async ({
}
if (!Per.checkPer(per)) {
return Promise.reject(AppErrEnum.unAuthApp);
// return Promise.reject(AppErrEnum.unAuthApp);
}
return {
......@@ -144,12 +149,19 @@ export const authApp = async ({
}
> => {
const result = await parseHeaderCert(props);
const { tmbId } = result;
let { tmbId } = result;
if (!appId) {
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({
tmbId,
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';
export const authCert = async (props: AuthModeType) => {
const result = await parseHeaderCert(props);
console.log('result333222', result);
return {
...result,
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 ({
resourceId: string;
}
)): 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
const tmbPer = (
await MongoResourcePermission.findOne(
{
resourceType,
teamId,
resourceId,
...(resourceId && { resourceId }),
tmbId
},
'permission'
......
......@@ -87,7 +87,7 @@ export const authDatasetByTmbId = async ({
const Per = new DatasetPermission({ role: sumPer(folderPer, myPer), isOwner });
if (!Per.checkPer(per)) {
return Promise.reject(DatasetErrEnum.unAuthDataset);
// return Promise.reject(DatasetErrEnum.unAuthDataset);
}
return {
......@@ -181,9 +181,11 @@ export async function authDatasetCollection({
*/
export async function authDatasetData({
dataId,
per = NullPermissionVal,
...props
}: AuthModeType & {
dataId: string;
per?: PermissionValueType;
}) {
// get mongo dataset.data
const datasetData = await MongoDatasetData.findById(dataId);
......@@ -194,7 +196,8 @@ export async function authDatasetData({
const result = await authDatasetCollection({
...props,
collectionId: datasetData.collectionId
collectionId: datasetData.collectionId,
per
});
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 ({
]);
// Team admin or role check
if (tmb.permission.hasManagePer || (groupMember && role.includes(groupMember.role))) {
return {
...result,
permission: tmb.permission,
teamId,
tmbId
};
}
return Promise.reject(TeamErrEnum.unAuthTeam);
// if (tmb.permission.hasManagePer || (groupMember && role.includes(groupMember.role))) {
return {
...result,
permission: tmb.permission,
teamId,
tmbId
};
// }
// return Promise.reject(TeamErrEnum.unAuthTeam);
};
......@@ -27,9 +27,9 @@ export async function authUserPer(props: AuthModeType): Promise<
tmb
};
}
if (!tmb.permission.checkPer(props.per ?? NullPermissionVal)) {
return Promise.reject(TeamErrEnum.unAuthTeam);
}
// if (!tmb.permission.checkPer(props.per ?? NullPermissionVal)) {
// return Promise.reject(TeamErrEnum.unAuthTeam);
// }
return {
...result,
......@@ -45,9 +45,10 @@ export const authSystemAdmin = async ({ req }: { req: NodeHttpRequest }) => {
_id: result.userId
});
if (!user || user.username !== 'root') {
return Promise.reject(ERROR_ENUM.unAuthorization);
}
console.log('auth admin user', user);
// if (!user || user.username !== 'root') {
// return Promise.reject(ERROR_ENUM.unAuthorization);
// }
return result;
} catch (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 { TeamPermission } from '@fastgpt/global/support/permission/user/controller';
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 }) {
if (userId) {
......@@ -31,27 +31,107 @@ export async function getUserDetail({
try {
const result = await getTmbInfoByTmbId({ tmbId, session });
return result;
} catch (error) {}
} catch (error) {
console.log('error2222', error);
}
}
if (userId) {
return getUserDefaultTeam({ userId, session });
}
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);
}
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;
const team = {
...tmb,
permission
console.log('user6666', user);
if (!user) {
user = await MongoUser.findOne({ _id: tmb.userId });
}
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 {
_id: user._id,
username: user.username,
......
......@@ -19,6 +19,7 @@ import { getAIApi } from '../../../core/ai/config';
import { createRootOrg } from '../../permission/org/controllers';
import { getS3AvatarSource } from '../../../common/s3/sources/avatar';
import { getLogger, LogCategories } from '../../../common/logger';
import { MongoUser } from '../schema';
const logger = getLogger(LogCategories.MODULE.USER.TEAM);
......@@ -116,16 +117,20 @@ export async function createDefaultTeam({
}) {
// auth default team
const tmb = await MongoTeamMember.findOne({
userId: new Types.ObjectId(userId)
userId: userId
});
if (!tmb) {
// 根据userId查询到用户
const user = await MongoUser.findOne({
_id: userId
});
// create team
const [{ _id: insertedId }] = await MongoTeam.create(
[
{
ownerId: userId,
name: teamName,
name: user?.username,
avatar,
createTime: new Date()
}
......@@ -138,7 +143,7 @@ export async function createDefaultTeam({
{
teamId: insertedId,
userId,
name: 'Owner',
name: user?.username,
role: TeamMemberRoleEnum.owner,
status: TeamMemberStatusEnum.active,
createTime: new Date()
......
import React from 'react';
import { Box } 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 React from 'react';
import MyIcon from '../Icon';
import { iconPaths } from '../Icon/constants';
import MyImage from '../Image/MyImage';
......
......@@ -13,7 +13,7 @@ import MyIcon from '../components/common/Icon';
import type { IconNameType } from '../components/common/Icon/type';
import { useTranslation } from 'next-i18next';
import { useToast } from './useToast';
import { getErrText } from '@fastgpt/global/common/error/utils';
import {
useBoolean,
useCreation,
......@@ -144,7 +144,7 @@ export function usePagination<DataT, ResT = {}>(
setError(error);
if (error.code !== 'ERR_CANCELED') {
toast({
title: getErrText(error, t('common:core.chat.error.data_error')),
title: error.message || t('common:core.chat.error.data_error'),
status: 'error'
});
}
......
......@@ -164,6 +164,7 @@
"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.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.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.",
......@@ -186,7 +187,7 @@
"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.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_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",
......@@ -223,9 +224,10 @@
"comfirm_import": "Confirm import",
"comfirm_leave_page": "Confirm to Leave This Page?",
"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",
"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.",
"confirm_choice": "Confirm Choice",
"confirm_input_delete_placeholder": "Please enter: {{confirmText}}",
......@@ -278,6 +280,9 @@
"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 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 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",
......@@ -292,6 +297,9 @@
"core.app.feedback.Custom feedback": "Custom Feedback",
"core.app.feedback.close custom feedback": "Close Feedback",
"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.not_saved": "Not Saved",
"core.app.outLink.Can Drag": "Icon Can Be Dragged",
......@@ -1041,6 +1049,7 @@
"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.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 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",
......@@ -1138,8 +1147,8 @@
"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_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_desc": "Unlock the full functionality of FastGPT",
"support.wallet.subscription.standardSubLevel.experience": "Experience Version",
"support.wallet.subscription.standardSubLevel.experience_desc": "Unlock the full functionality of ",
"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.team": "Team",
......
......@@ -18,8 +18,9 @@
"feishu_bot_desc": "Connect to Feishu Bot directly via API",
"key_alias": "Key alias, for display only",
"link_name": "Share Link Name",
"link_password": "Share Link Password",
"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.desc": "Connect to WeChat Official Account directly via API",
"official_account.edit_modal_title": "Edit WeChat Official Account Integration",
......
......@@ -223,9 +223,10 @@
"comfirm_import": "确认导入",
"comfirm_leave_page": "确认离开该页面?",
"comfirn_create": "确认创建",
"commercial_function_tip": "请升级商业版后使用该功能:https://doc.fastgpt.cn/guide/version/commercial",
"commercial_function_tip": "暂不支持本功能",
"community_support": "社区支持",
"compliance.chat": "内容由 AI 生成,请注意甄别",
"comon.Continue_Adding": "继续添加",
"compliance.chat": "内容由第三方 AI 生成,无法确保真实准确,仅供参考",
"compliance.dataset": "请确保您的内容严格遵守相关法律法规,避免包含任何违法或侵权的内容。请谨慎上传可能涉及敏感信息的资料。",
"confirm_choice": "确认选择",
"confirm_input_delete_placeholder": "请输入: {{confirmText}}",
......@@ -278,6 +279,9 @@
"core.app.Share link": "免登录窗口",
"core.app.Share link desc": "创建可分享的链接,支持免登录使用",
"core.app.Share link desc detail": "可以直接分享该模型给其他用户去进行对话,对方无需登录即可直接进行对话。注意,这个功能会消耗你账号的余额,请保管好链接!",
"core.app.Share auth_link": "需要登录窗口",
"core.app.Share auth_link desc": "分享链接给其他用户,需要输入密码进行使用",
"core.app.Share auth_link desc detail": "可以直接分享该模型给其他用户去进行对话,对方需输入密码验证后即才能进行对话。注意,这个功能会消耗你账号的余额,请保管好链接!",
"core.app.TTS": "语音播放",
"core.app.TTS Tip": "开启后,每次对话后可使用语音播放功能。使用该功能可能产生额外费用。",
"core.app.TTS start": "朗读内容",
......@@ -292,6 +296,9 @@
"core.app.feedback.Custom feedback": "自定义反馈",
"core.app.feedback.close custom feedback": "关闭反馈",
"core.app.have_saved": "已保存",
"core.app.logs.ChatId": "对话ID",
"core.app.logs.Source And Time": "来源 & 时间",
"core.app.more": "查看更多",
"core.app.no_app": "还没有应用,快去创建一个吧!",
"core.app.not_saved": "未保存",
"core.app.outLink.Can Drag": "图标可拖拽",
......@@ -1041,6 +1048,7 @@
"support.openapi.New api key": "新的 API 密钥",
"support.openapi.New api key tip": "请保管好你的密钥,不要泄露给他人。",
"support.outlink.Delete link tip": "确认删除该免登录链接?删除后,该链接将会立即失效,对话日志仍会保留,请确认!",
"support.outlink.Delete auth_link tip": "确认删除该认证链接?删除后,该链接将会立即失效,对话日志仍会保留,请确认!",
"support.outlink.Max usage points": "积分上限",
"support.outlink.Max usage points tip": "该链接最多允许使用多少积分,超出后将无法使用。-1 代表无限制。",
"support.outlink.Usage points": "积分消耗",
......@@ -1139,7 +1147,7 @@
"support.wallet.subscription.standardSubLevel.enterprise": "企业版",
"support.wallet.subscription.standardSubLevel.enterprise_desc": "适合中小企业在生产环境构建知识库应用",
"support.wallet.subscription.standardSubLevel.experience": "体验版",
"support.wallet.subscription.standardSubLevel.experience_desc": "可解锁 FastGPT 完整功能",
"support.wallet.subscription.standardSubLevel.experience_desc": "可解锁完整功能",
"support.wallet.subscription.standardSubLevel.free": "免费版",
"support.wallet.subscription.standardSubLevel.free desc": "核心功能免费试用。30 天未登录,将会清空知识库。",
"support.wallet.subscription.standardSubLevel.team": "团队版",
......@@ -1216,5 +1224,170 @@
"xx_search_result": "{{key}} 的搜索结果",
"yes": "是",
"yesterday": "昨天",
"yesterday_detail_time": "昨天 {{time}}"
"yesterday_detail_time": "昨天 {{time}}",
"zoomin_tip": "缩小 ctrl -",
"zoomin_tip_mac": "缩小 ⌘ -",
"zoomout_tip": "放大 ctrl +",
"zoomout_tip_mac": "放大 ⌘ +",
"common.Action": "操作",
"common.Add": "添加",
"common.Add New": "新增",
"common.Add Success": "添加成功",
"common.Add_new_input": "新增输入",
"common.All": "全部",
"common.Cancel": "取消",
"common.Choose": "选择",
"common.Close": "关闭",
"common.Code": "源码",
"common.Config": "配置",
"common.Confirm": "确认",
"common.Confirm Create": "确认创建",
"common.Confirm Import": "确认导入",
"common.Confirm Move": "移动到这",
"common.Confirm Update": "确认更新",
"common.Confirm to leave the page": "确认离开该页面?",
"common.Continue_Adding": "继续添加",
"common.Copy": "复制",
"common.Copy Successful": "复制成功",
"common.Create Failed": "创建异常",
"common.Create Success": "创建成功",
"common.Create Time": "创建时间",
"common.Creating": "创建中",
"common.Custom Title": "自定义标题",
"common.Delete": "删除",
"common.Delete Failed": "删除失败",
"common.Delete Success": "删除成功",
"common.Delete Warning": "删除警告",
"common.Delete folder": "删除文件夹",
"common.Detail": "详情",
"common.Documents": "文档",
"common.Done": "完成",
"common.Edit": "编辑",
"common.Error": "错误",
"common.Exit": "退出",
"common.Exit Directly": "直接退出",
"common.Expired Time": "过期时间",
"common.File": "文件",
"common.Finish": "完成",
"common.FullScreen": "全屏",
"common.FullScreenLight": "全屏预览",
"common.Import": "导入",
"common.Import failed": "导入失败",
"common.Import success": "导入成功",
"common.Input": "输入",
"common.Input folder description": "文件夹描述",
"common.Input name": "取个名字",
"common.Intro": "介绍",
"common.Last Step": "上一步",
"common.Last use time": "最后使用时间",
"common.Load Failed": "加载失败",
"common.Loading": "加载中...",
"common.More": "更多",
"common.Move": "移动",
"common.MultipleRowSelect.No data": "没有可选值",
"common.Name": "名称",
"common.Next Step": "下一步",
"common.No more data": "没有更多了~",
"common.Not open": "未开启",
"common.OK": "好的",
"common.Open": "打开",
"common.Other": "其他",
"common.Output": "输出",
"common.Params": "参数",
"common.Parse": "解析",
"common.Password inconsistency": "两次密码不一致",
"common.Permission": "权限",
"common.Permission_tip": "个人权限大于群组权限",
"common.Please Input Name": "请输入名称",
"common.Preview": "预览",
"common.Read document": "查看文档",
"common.Read intro": "查看说明",
"common.Remove": "移除",
"common.Rename": "重命名",
"common.Request Error": "请求异常",
"common.Reset": "恢复默认",
"common.Restart": "重新开始",
"common.Role": "权限",
"common.Root folder": "根目录",
"common.Run": "运行",
"common.Save": "保存",
"common.Save Failed": "保存异常",
"common.Save Success": "保存成功",
"common.Save_and_exit": "保存并退出",
"common.Search": "搜索",
"common.Select File Failed": "选择文件异常",
"common.Select template": "选择模板",
"common.Set Avatar": "点击设置头像",
"common.Set Name": "取个名字",
"common.Setting": "设置",
"common.Status": "状态",
"common.Submit failed": "提交失败",
"common.Success": "成功",
"common.Sync success": "同步成功",
"common.Team": "团队",
"common.Team Tags Set": "标签",
"common.Un used": "未使用",
"common.UnKnow": "未知",
"common.UnKnow Source": "未知来源",
"common.Unlimited": "无限制",
"common.Update": "更新",
"common.Update Failed": "更新异常",
"common.Update Success": "更新成功",
"common.Username": "用户名",
"common.Waiting": "等待中",
"common.Warning": "警告",
"common.Website": "网站",
"common.all_result": "完整结果",
"common.avatar.Select Avatar": "点击选择头像",
"common.avatar.Select Failed": "选择头像异常",
"common.base_config": "基础配置",
"common.choosable": "可选",
"common.confirm.Common Tip": "操作确认",
"common.copy_to_clipboard": "复制到剪贴板",
"common.course.Read Course": "查看教程",
"common.empty.Common Tip": "没有什么数据噢~",
"common.error.Select avatar failed": "头像选择异常",
"common.error.unKnow": "出现了点意外~",
"common.export_to_json": "导出为 JSON",
"common.failed": "失败",
"common.folder.Drag Tip": "点我可拖动",
"common.folder.Move Success": "移动成功",
"common.folder.Move to": "移动到",
"common.folder.No Folder": "没有子目录了,就放这里吧",
"common.folder.Open folder": "打开文件夹",
"common.folder.Root Path": "根目录",
"common.folder.empty": "这个目录已经没东西可选了~",
"common.folder.open_dataset": "打开知识库",
"common.have_done": "已完成",
"common.input.Repeat Value": "有重复的值",
"common.is_requesting": "请求中……",
"common.jsonEditor.Parse error": "JSON 可能有误,请仔细检查",
"common.json_config": "JSON 配置",
"common.link.UnValid": "无效的链接",
"common.month": "月",
"common.name_is_empty": "名称不能为空",
"common.password_is_empty": "密码不能为空",
"common.no_intro": "暂无介绍",
"common.not_support": "不支持",
"common.page_center": "页面居中",
"common.redo_tip": "恢复 ctrl shift z",
"common.redo_tip_mac": "恢复 ⌘ shift z",
"common.request_end": "已加载全部",
"common.request_more": "点击加载更多",
"common.speech.error tip": "语音转文字失败",
"common.speech.not support": "您的浏览器不支持语音输入",
"common.submit_success": "提交成功",
"common.submitted": "已提交",
"common.support": "支持",
"common.system.Commercial version function": "暂不支持本功能",
"common.system.Help Chatbot": "机器人助手",
"common.system.Use Helper": "使用帮助",
"common.ui.textarea.Magnifying": "放大",
"common.undo_tip": "撤销 ctrl z",
"common.undo_tip_mac": "撤销 ⌘ z ",
"common.upload_file": "上传文件",
"common.zoomin_tip": "缩小 ctrl -",
"common.zoomin_tip_mac": "缩小 ⌘ -",
"common.zoomout_tip": "放大 ctrl +",
"common.zoomout_tip_mac": "放大 ⌘ +"
}
......@@ -18,6 +18,7 @@
"feishu_bot_desc": "通过 API 直接接入飞书机器人",
"key_alias": "key 的别名,仅用于展示",
"link_name": "分享链接的名字",
"link_password": "分享链接的密码",
"native_channels": "原生渠道",
"new_feishu_bot": "新增飞书机器人",
"official_account.create_modal_title": "创建微信公众号接入",
......
......@@ -289,6 +289,9 @@
"core.app.feedback.Custom feedback": "自訂回饋",
"core.app.feedback.close custom feedback": "關閉回饋",
"core.app.have_saved": "已儲存",
"core.app.logs.ChatId": "对话ID",
"core.app.logs.Source And Time": "來源與時間",
"core.app.more": "檢視更多",
"core.app.no_app": "還沒有應用程式,快來建立一個吧!",
"core.app.not_saved": "未儲存",
"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