Publish app - feishu and wecom (#2375)

* feat(app publish): feishu bot (#2290)

* feat: feishu publish channel fe

* feat: enable feishu fe,
feat: feishu token api

* feat: feishu bot

* chore: extract saveChat from projects/app

* chore: remove debug log output

* feat: Basic Info

* chore: feishu bot fe adjusting

* feat: feishu bot docs

* feat: new tmpData collection for all tmpdata

* chore: compress the image

* perf: feishu config

* feat: source name

* perf: text desc

* perf: load system plugins

* perf: chat source

* feat(publish): Wecom bot (#2343)

* chore: Wecom Config

* feat(fe): wecom config fe

* feat: wecom fe

* chore: uses the newest editmodal

* feat: update png; adjust the fe

* chore: adjust fe

* perf: publish app ui

---------

Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
This commit is contained in:
Archer
2024-08-13 21:52:18 +08:00
committed by GitHub
parent 7417de74da
commit 0f3418daf5
71 changed files with 1301 additions and 498 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

View File

@@ -1,12 +1,20 @@
--- ---
title: 'V4.8. 10进行中' title: 'V4.8.10(进行中)'
description: 'FastGPT V4.8. 10 更新说明' description: 'FastGPT V4.8.10 更新说明'
icon: 'upgrade' icon: 'upgrade'
draft: false draft: false
toc: true toc: true
weight: 816 weight: 816
--- ---
## 更新指南
### 1. 做好数据备份
### 2. 更新商业版环境变量
商业版用户,需要给`fastgpt-pro`镜像,增加沙盒的环境变量:`SANDBOX_URL=http://fastgpt-sandbox.ns-hti44k5d.svc.cluster.local:3000`
------- -------
## V4.8.10 更新说明 ## V4.8.10 更新说明

View File

@@ -1,70 +1,85 @@
--- ---
title: " 接入飞书(社区文章)" title: "教程 - 接入飞书机器人"
description: "FastGPT 接入飞书机器人" description: "FastGPT 接入飞书机器人"
icon: "chat" icon: "chat"
draft: false draft: false
toc: true toc: true
weight: 503 weight: 507
--- ---
## 1. 申请飞书应用
# FastGPT 一分钟接入飞书 开一个免费的测试企业更方便进行调试。
[Feishu OpenAI GitHub 地址](https://github.com/ConnectAI-E/Feishu-OpenAI) 1. 在[飞书开放平台](https://open.feishu.cn/app)的开发者后台申请企业自建应用。
[查看视频教程](https://www.bilibili.com/video/BV1Su4y1r7R3/?spm_id_from=333.999.list.card_archive.click) ![图片](/imgs/feishu-bot-1.png)
由于 FastGPT 的 API 接口和 OpenAI 的规范一致,可以无需变更第三方应用即可使用 FastGPT 上编排好的应用。API 使用可参考 [这篇文章](/docs/use-cases/openapi/)。编排示例,可参考 [高级编排介绍](/docs/workflow/intro) 添加一个**机器人**应用。
## 1. 获取 FastGPT 的 OpenAPI 秘钥 ## 2. FastGPT 新建发布渠道
依次选择应用 -> 「API 访问」然后点击「API 密钥」来创建密钥。 [参考这篇文章](/docs/use-cases/openapi/) 在fastgpt中选择想要接入的应用在 发布渠道 页面,新建一个接入飞书机器人的发布渠道,填写好基础信息。
![图片](/imgs/feishu-bot-2.png)
![](/imgs/fastgpt-api.png) ## 3. 获取应用的 App ID, App Secret 两个凭证
## 2. 部署飞书服务 在飞书开放平台开发者后台,刚刚创建的企业自建应用中,找到 App ID 和 App Secret填入 FastGPT 新建发布渠道的对话框里面。
![图片](/imgs/feishu-bot-3.png)
推荐使用 Railway 一键部署 填入两个参数到 FastGPT 配置弹窗中。
[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/10D-TF?referralCode=oMcVS2) ![图片](/imgs/feishu-bot-4.png)
参考环境变量配置: (可选)在飞书开放平台开发者后台,点击事件与回调 -> 加密策略 获取 Encrypt Key并填入飞书机器人接入的对话框里面
![](/imgs/feishu-env.png) ![图片](/imgs/feishu-bot-5.png)
FastGPT 集成**重点参数:** Encrypt Key 用于加密飞书服务器与 FastGPT 之间通信。
建议如果使用 Https 协议,则不需要 Encrypt Key。如果使用 Http 协议通信,则建议使用 Encrypt Key
Verification Token 默认生成的这个 Token 用于校验来源。但我们使用飞书官方推荐的另一种更为安全的校验方式,因此可以忽略这个配置项。
## 4. 配置回调地址
```bash 新建好发布渠道后,点击**请求地址**,复制对应的请求地址。
#上一步FastGPT的OpenAPI 秘钥
OPENAI_KEY=fastgpt-z51pkjqm9nrk03a1rx2funoy
#调用OpenAI的BaseUrl要换成FastGPT的
API_URL=https://api.fastgpt.in/api/openapi
```
## 3. 创建飞书机器人 在飞书控制台,点击左侧的 `事件与回调` ,点击`配置订阅方式`旁边的编辑 icon粘贴刚刚复制的请求地址到输入框中。
| | | |
| --- | --- | --- |
| ![图片](/imgs/feishu-bot-10.jpg) | ![图片](/imgs/feishu-bot-11.jpg) | ![图片](/imgs/feishu-bot-6.png) |
1. 前往 [开发者平台](https://open.feishu.cn/app?lang=zh-CN) 创建应用 , 并获取到 APPID 和 Secret ## 5. 配置机器人回调事件和权限
2. 前往`应用功能-机器人`, 创建机器人
3. 从 cpolar、serverless 或 Railway 获得公网地址,在飞书机器人后台的 `事件订阅` 板块填写。例如,
- `http://xxxx.r6.cpolar.top` 为 cpolar 暴露的公网地址
- `/webhook/event` 为统一的应用路由
- 最终的回调地址为 `http://xxxx.r6.cpolar.top/webhook/event`
4. 在飞书机器人后台的 `机器人` 板块,填写消息卡片请求网址。例如,
- `http://xxxx.r6.cpolar.top` 为 cpolar 暴露的公网地址
- `/webhook/card` 为统一的应用路由
- 最终的消息卡片请求网址为 `http://xxxx.r6.cpolar.top/webhook/card`
5. 在事件订阅板块,搜索三个词`机器人进群`、 `接收消息`、 `消息已读`, 把他们后面所有的权限全部勾选。 进入权限管理界面,搜索`图片`, 勾选`获取与上传图片或文件资源`。 最终会添加下列回调事件
- im:resource(获取与上传图片或文件资源)
- im:message
- im:message.group_at_msg(获取群组中所有消息)
- im:message.group_at_msg:readonly(接收群聊中 @ 机器人消息事件)
- im:message.p2p_msg(获取用户发给机器人的单聊消息)
- im:message.p2p_msg:readonly(读取用户发给机器人的单聊消息)
- im:message:send_as_bot(获取用户在群组中 @ 机器人的消息)
- im:chat:readonly(获取群组信息)
- im:chat(获取与更新群组信息)
## 4. 测试飞书机器人 * 添加 `接收消息` 事件
`事件与回调`页面,点击`添加事件`
私聊机器人,或者群里艾特它,就可以基于 FastGPT 的应用进行回答啦 搜索`接收消息`,或者直接搜索 `im.message.receive_v1` ,找到`接收消息 v2.0`的时间,勾选上并点击`确认添加`
![](/imgs/feishu-res.png) 添加事件后,增加两个权限:点击对应权限,会有弹窗提示添加权限,添加上图两个权限。
| | |
| --- | --- |
| ![图片](/imgs/feishu-bot-7.png) | ![图片](/imgs/feishu-bot-8.png) |
不推荐启用上图中的两个“历史版本”,而是使用新版本的权限。
- 若开启 “读取用户发给机器人的单聊消息”, 则单聊发送给机器人的消息将被送到 FastGPT
- 若开启 “接收群聊中@机器人消息事件”, 则群聊中@机器人的消息将被送到 FastGPT
- 若开启(不推荐开启)“获取群组中所有消息”,则群聊中所有消息都将被送到 FastGPT
## 6. 配置回复消息权限
在飞书控制台,点击左侧的 `权限管理` ,搜索框中输入`发消息`,找到`以应用的身份发消息`的权限,点击开通权限。
![](/imgs/feishu-bot-13.jpg)
## 6. 发布机器人
点击飞书控制台左侧的`版本管理与发布`,即可发布机器人。
![](/imgs/feishu-bot-12.jpg)
然后就可以在工作台里找到你的机器人啦。接下来就是把机器人拉进群组,或者单独与它对话。
![图片](/imgs/feishu-bot-9.png)

View File

@@ -0,0 +1,10 @@
export const retryRun = <T>(fn: () => T, retry = 2): T => {
try {
return fn();
} catch (error) {
if (retry > 0) {
return retryRun(fn, retry - 1);
}
throw error;
}
};

View File

@@ -26,7 +26,7 @@ export const formatTimeToChatTime = (time: Date) => {
// 如果时间是今天,展示几时:几分 // 如果时间是今天,展示几时:几分
if (now.isSame(target, 'day')) { if (now.isSame(target, 'day')) {
return target.format('HH:mm'); return target.format('HH : mm');
} }
// 如果是昨天,展示昨天 // 如果是昨天,展示昨天

View File

@@ -1,3 +1,5 @@
import { i18nT } from '../../../web/i18n/utils';
export enum ChatRoleEnum { export enum ChatRoleEnum {
System = 'System', System = 'System',
Human = 'Human', Human = 'Human',
@@ -30,23 +32,27 @@ export enum ChatSourceEnum {
online = 'online', online = 'online',
share = 'share', share = 'share',
api = 'api', api = 'api',
team = 'team' team = 'team',
feishu = 'feishu'
} }
export const ChatSourceMap = { export const ChatSourceMap = {
[ChatSourceEnum.test]: { [ChatSourceEnum.test]: {
name: 'core.chat.logs.test' name: i18nT('common:core.chat.logs.test')
}, },
[ChatSourceEnum.online]: { [ChatSourceEnum.online]: {
name: 'core.chat.logs.online' name: i18nT('common:core.chat.logs.online')
}, },
[ChatSourceEnum.share]: { [ChatSourceEnum.share]: {
name: 'core.chat.logs.share' name: i18nT('common:core.chat.logs.share')
}, },
[ChatSourceEnum.api]: { [ChatSourceEnum.api]: {
name: 'core.chat.logs.api' name: i18nT('common:core.chat.logs.api')
}, },
[ChatSourceEnum.team]: { [ChatSourceEnum.team]: {
name: 'core.chat.logs.team' name: i18nT('common:core.chat.logs.team')
},
[ChatSourceEnum.feishu]: {
name: i18nT('common:core.chat.logs.feishu')
} }
}; };

View File

@@ -26,7 +26,7 @@ export const TextEditorNode: FlowNodeTemplateType = {
inputs: [ inputs: [
{ {
...Input_Template_DynamicInput, ...Input_Template_DynamicInput,
description: '可以引用其他节点的输出,作为文本拼接的变量,通过 {{字段名}} 来引用变量', description: '可以引用其他节点的输出,作为文本拼接的变量,输入 / 唤起变量列表',
customInputConfig: { customInputConfig: {
selectValueTypeList: Object.values(WorkflowIOValueTypeEnum), selectValueTypeList: Object.values(WorkflowIOValueTypeEnum),
showDescription: false, showDescription: false,
@@ -39,7 +39,7 @@ export const TextEditorNode: FlowNodeTemplateType = {
valueType: WorkflowIOValueTypeEnum.string, valueType: WorkflowIOValueTypeEnum.string,
required: true, required: true,
label: '拼接文本', label: '拼接文本',
placeholder: '可通过 {{字段名}} 来引用变量' placeholder: '可输入 / 唤起变量列表'
} }
], ],
outputs: [ outputs: [

View File

@@ -2,5 +2,6 @@ export enum PublishChannelEnum {
share = 'share', share = 'share',
iframe = 'iframe', iframe = 'iframe',
apikey = 'apikey', apikey = 'apikey',
feishu = 'feishu' feishu = 'feishu',
wecom = 'wecom'
} }

View File

@@ -1,8 +1,9 @@
import { AppSchema } from 'core/app/type'; import { AppSchema } from '../../core/app/type';
import { PublishChannelEnum } from './constant'; import { PublishChannelEnum } from './constant';
import { RequireOnlyOne } from '../../common/type/utils';
// Feishu Config interface // Feishu Config interface
export interface FeishuType { export interface FeishuAppType {
appId: string; appId: string;
appSecret: string; appSecret: string;
// Encrypt config // Encrypt config
@@ -10,29 +11,28 @@ export interface FeishuType {
encryptKey?: string; // no secret if null encryptKey?: string; // no secret if null
// Token Verification // Token Verification
// refer to: https://open.feishu.cn/document/server-docs/event-subscription-guide/event-subscription-configure-/encrypt-key-encryption-configuration-case // refer to: https://open.feishu.cn/document/server-docs/event-subscription-guide/event-subscription-configure-/encrypt-key-encryption-configuration-case
verificationToken: string; verificationToken?: string;
} }
// TODO: Unused export interface WecomAppType {
export interface WecomType { AgentId: string;
ReplyLimit: Boolean; CorpId: string;
defaultResponse: string; SuiteSecret: string;
immediateResponse: boolean; CallbackToken: string;
WXWORK_TOKEN: string; CallbackEncodingAesKey: string;
WXWORK_AESKEY: string;
WXWORK_SECRET: string;
WXWORD_ID: string;
} }
export type OutLinkSchema<T = void> = { // TODO: unused
export interface WechatAppType {}
export type OutlinkAppType = FeishuAppType | WecomAppType | undefined;
export type OutLinkSchema<T extends OutlinkAppType = undefined> = {
_id: string; _id: string;
shareId: string; shareId: string;
teamId: string; teamId: string;
tmbId: string; tmbId: string;
appId: string; appId: string;
// teamId: Schema.Types.ObjectId;
// tmbId: Schema.Types.ObjectId;
// appId: Schema.Types.ObjectId;
name: string; name: string;
usagePoints: number; usagePoints: number;
lastTime: Date; lastTime: Date;
@@ -55,7 +55,7 @@ export type OutLinkSchema<T = void> = {
hookUrl?: string; hookUrl?: string;
}; };
app?: T; app: T;
}; };
// to handle MongoDB querying // to handle MongoDB querying
@@ -64,10 +64,10 @@ export type OutLinkWithAppType = Omit<OutLinkSchema, 'appId'> & {
}; };
// Edit the Outlink // Edit the Outlink
export type OutLinkEditType<T = void> = { export type OutLinkEditType<T = undefined> = {
_id?: string; _id?: string;
name: string; name: string;
responseDetail: OutLinkSchema<T>['responseDetail']; responseDetail?: OutLinkSchema<T>['responseDetail'];
// response when request // response when request
immediateResponse?: string; immediateResponse?: string;
// response when error or other situation // response when error or other situation

View File

@@ -0,0 +1,31 @@
export enum TmpDataEnum {
FeishuAccessToken = 'feishu_access_token',
WecomAccessToken = 'wecom_access_token'
}
type _TmpDataMetadata = {
[TmpDataEnum.FeishuAccessToken]: {
FeishuAppId: string;
};
[TmpDataEnum.WecomAccessToken]: {
CorpId: string;
AgentId: string;
};
};
type _TmpDataType = {
[TmpDataEnum.FeishuAccessToken]: {
accessToken: string;
};
[TmpDataEnum.WecomAccessToken]: {
accessToken: string;
};
};
export const TmpDataExpireTime = {
[TmpDataEnum.FeishuAccessToken]: 1000 * 60 * 60 * 1.5, // 1.5 hours
[TmpDataEnum.WecomAccessToken]: 1000 * 60 * 60 * 2 // 2 hours
};
export type TmpDataMetadata<T extends TmpDataEnum> = _TmpDataMetadata[T];
export type TmpDataType<T extends TmpDataEnum> = _TmpDataType[T];

View File

@@ -0,0 +1,5 @@
export type TmpDataSchema<T> = {
dataId: string;
data: T;
expireAt: Date;
};

View File

@@ -21,6 +21,6 @@ export type CreateUsageProps = {
appId?: string; appId?: string;
pluginId?: string; pluginId?: string;
totalPoints: number; totalPoints: number;
source: UsageSourceEnum; source: `${UsageSourceEnum}`;
list: UsageListItemType[]; list: UsageListItemType[];
}; };

View File

@@ -5,7 +5,8 @@ export enum UsageSourceEnum {
api = 'api', api = 'api',
shareLink = 'shareLink', shareLink = 'shareLink',
training = 'training', training = 'training',
cronJob = 'cronJob' cronJob = 'cronJob',
feishu = 'feishu'
} }
export const UsageSourceMap = { export const UsageSourceMap = {
@@ -23,5 +24,8 @@ export const UsageSourceMap = {
}, },
[UsageSourceEnum.cronJob]: { [UsageSourceEnum.cronJob]: {
label: i18nT('common:cron_job_run_app') label: i18nT('common:cron_job_run_app')
},
[UsageSourceEnum.feishu]: {
label: i18nT('user:usage.feishu')
} }
}; };

View File

@@ -1,7 +1,5 @@
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants'; import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
import { SystemPluginResponseType } from './type'; import { SystemPluginResponseType } from './type';
import { FastGPTProUrl, isProduction } from '../service/common/system/constants';
import { GET, POST } from '@fastgpt/service/common/api/plusRequest';
import { SystemPluginTemplateItemType } from '@fastgpt/global/core/workflow/type'; import { SystemPluginTemplateItemType } from '@fastgpt/global/core/workflow/type';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { WorkerNameEnum, runWorker } from '@fastgpt/service/worker/utils'; import { WorkerNameEnum, runWorker } from '@fastgpt/service/worker/utils';
@@ -27,7 +25,7 @@ const packagePluginList = [
'duckduckgo/searchVideo' 'duckduckgo/searchVideo'
]; ];
const list = [...staticPluginList, ...packagePluginList]; export const list = [...staticPluginList, ...packagePluginList];
/* Get plugins */ /* Get plugins */
export const getCommunityPlugins = () => { export const getCommunityPlugins = () => {
@@ -49,25 +47,10 @@ export const getCommunityPlugins = () => {
}; };
}); });
}; };
const getCommercialPlugins = () => {
return GET<SystemPluginTemplateItemType[]>('/core/app/plugin/getSystemPlugins');
};
export const getSystemPluginTemplates = async (refresh = false) => {
if (isProduction && global.systemPlugins && !refresh) return cloneDeep(global.systemPlugins);
try { export const getSystemPluginTemplates = () => {
if (!global.systemPlugins) { const oldPlugins = global.communityPlugins ?? [];
global.systemPlugins = []; return [...oldPlugins, ...cloneDeep(global.systemPlugins)];
}
global.systemPlugins = FastGPTProUrl ? await getCommercialPlugins() : getCommunityPlugins();
return cloneDeep(global.systemPlugins);
} catch (error) {
//@ts-ignore
global.systemPlugins = undefined;
return Promise.reject(error);
}
}; };
export const getCommunityCb = async () => { export const getCommunityCb = async () => {
@@ -107,39 +90,7 @@ export const getCommunityCb = async () => {
{} {}
); );
}; };
const getCommercialCb = async () => {
const plugins = await getSystemPluginTemplates();
const result = plugins.map((plugin) => {
const name = plugin.id.split('-')[1];
return {
name,
cb: (e: any) =>
POST<Record<string, any>>('/core/app/plugin/run', {
pluginName: name,
data: e
})
};
});
return result.reduce<Record<string, (e: any) => SystemPluginResponseType>>(
(acc, { name, cb }) => {
acc[name] = cb;
return acc;
},
{}
);
};
export const getSystemPluginCb = async () => { export const getSystemPluginCb = async () => {
if (isProduction && global.systemPluginCb) return global.systemPluginCb; return global.systemPluginCb;
try {
global.systemPluginCb = {};
global.systemPluginCb = FastGPTProUrl ? await getCommercialCb() : await getCommunityCb();
return global.systemPluginCb;
} catch (error) {
//@ts-ignore
global.systemPluginCb = undefined;
return Promise.reject(error);
}
}; };

View File

@@ -2,7 +2,7 @@
"author": "", "author": "",
"version": "488", "version": "488",
"name": "飞书机器人 webhook", "name": "飞书机器人 webhook",
"avatar": "/imgs/app/templates/feishu.svg", "avatar": "/appMarketTemplates/plugin-feishu/avatar.svg",
"intro": "向飞书机器人发起 webhook 请求。", "intro": "向飞书机器人发起 webhook 请求。",
"inputExplanationUrl": "https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot#f62e72d5", "inputExplanationUrl": "https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot#f62e72d5",
"showStatus": false, "showStatus": false,

View File

@@ -1,4 +1,9 @@
import axios, { Method, InternalAxiosRequestConfig, AxiosResponse } from 'axios'; import axios, {
Method,
InternalAxiosRequestConfig,
AxiosResponse,
AxiosRequestConfig
} from 'axios';
import { FastGPTProUrl } from '../system/constants'; import { FastGPTProUrl } from '../system/constants';
interface ConfigType { interface ConfigType {
@@ -118,3 +123,9 @@ export function PUT<T = undefined>(url: string, data = {}, config: ConfigType =
export function DELETE<T = undefined>(url: string, data = {}, config: ConfigType = {}): Promise<T> { export function DELETE<T = undefined>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
return request(url, data, config, 'DELETE'); return request(url, data, config, 'DELETE');
} }
export const plusRequest = (config: AxiosRequestConfig) =>
instance.request({
...config,
baseURL: FastGPTProUrl
});

View File

@@ -61,9 +61,7 @@ const getPluginTemplateById = async (
currentCost: 0 currentCost: 0
}; };
} else { } else {
const item = [...global.communityPlugins, ...(await getSystemPluginTemplates())].find( const item = getSystemPluginTemplates().find((plugin) => plugin.id === pluginId);
(plugin) => plugin.id === pluginId
);
if (!item) return Promise.reject('plugin not found'); if (!item) return Promise.reject('plugin not found');
return cloneDeep(item); return cloneDeep(item);

View File

@@ -1,10 +1,10 @@
import type { AIChatItemType, UserChatItemType } from '@fastgpt/global/core/chat/type.d'; import type { AIChatItemType, UserChatItemType } from '@fastgpt/global/core/chat/type.d';
import { MongoApp } from '@fastgpt/service/core/app/schema'; import { MongoApp } from '../app/schema';
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants'; import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema'; import { MongoChatItem } from './chatItemSchema';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema'; import { MongoChat } from './chatSchema';
import { addLog } from '@fastgpt/service/common/system/log'; import { addLog } from '../../common/system/log';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun'; import { mongoSessionRun } from '../../common/mongo/sessionRun';
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node'; import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
import { getAppChatConfig, getGuideModule } from '@fastgpt/global/core/workflow/utils'; import { getAppChatConfig, getGuideModule } from '@fastgpt/global/core/workflow/utils';
import { AppChatConfigType } from '@fastgpt/global/core/app/type'; import { AppChatConfigType } from '@fastgpt/global/core/app/type';

View File

@@ -21,6 +21,12 @@ export const dispatchRunCode = async (props: RunCodeType): Promise<RunCodeRespon
params: { codeType, code, [NodeInputKeyEnum.addInputParam]: customVariables } params: { codeType, code, [NodeInputKeyEnum.addInputParam]: customVariables }
} = props; } = props;
if (!process.env.SANDBOX_URL) {
return {
[NodeOutputKeyEnum.error]: 'Can not find SANDBOX_URL in env'
};
}
const sandBoxRequestUrl = `${process.env.SANDBOX_URL}/sandbox/js`; const sandBoxRequestUrl = `${process.env.SANDBOX_URL}/sandbox/js`;
try { try {
const { data: runResult } = await axios.post<{ const { data: runResult } = await axios.post<{

View File

@@ -1,5 +1,5 @@
import { connectionMongo, getMongoModel, type Model } from '../../common/mongo'; import { connectionMongo, getMongoModel } from '../../common/mongo';
const { Schema, model, models } = connectionMongo; const { Schema } = connectionMongo;
import { OutLinkSchema as SchemaType } from '@fastgpt/global/support/outLink/type'; import { OutLinkSchema as SchemaType } from '@fastgpt/global/support/outLink/type';
import { import {
TeamCollectionName, TeamCollectionName,
@@ -63,18 +63,7 @@ const OutLinkSchema = new Schema({
} }
}, },
app: { app: {
appId: { type: Object // could be FeishuAppType | WecomAppType | ...
type: String
},
appSecret: {
type: String
},
encryptKey: {
type: String
},
verificationToken: {
type: String
}
}, },
immediateResponse: { immediateResponse: {
type: String type: String

View File

@@ -0,0 +1,18 @@
import { TeamMemberWithUserSchema } from '@fastgpt/global/support/user/team/type';
import { MongoTeamMember } from '../../user/team/teamMemberSchema';
import { checkTeamAIPoints } from '../teamLimit';
import { UserErrEnum } from '@fastgpt/global/common/error/code/user';
export async function getUserChatInfoAndAuthTeamPoints(tmbId: string) {
const tmb = (await MongoTeamMember.findById(tmbId, 'teamId userId').populate(
'userId',
'timezone openaiAccount'
)) as TeamMemberWithUserSchema;
if (!tmb) return Promise.reject(UserErrEnum.unAuthUser);
await checkTeamAIPoints(tmb.teamId);
return {
user: tmb.userId
};
}

View File

@@ -1,5 +1,5 @@
import { AppDetailType } from '@fastgpt/global/core/app/type'; import { AppDetailType } from '@fastgpt/global/core/app/type';
import { OutLinkSchema } from '@fastgpt/global/support/outLink/type'; import { OutlinkAppType, OutLinkSchema } from '@fastgpt/global/support/outLink/type';
import { parseHeaderCert } from '../controller'; import { parseHeaderCert } from '../controller';
import { MongoOutLink } from '../../outLink/schema'; import { MongoOutLink } from '../../outLink/schema';
import { OutLinkErrEnum } from '@fastgpt/global/common/error/code/outLink'; import { OutLinkErrEnum } from '@fastgpt/global/common/error/code/outLink';
@@ -50,11 +50,15 @@ export async function authOutLinkCrud({
} }
/* outLink exist and it app exist */ /* outLink exist and it app exist */
export async function authOutLinkValid({ shareId }: { shareId?: string }) { export async function authOutLinkValid<T extends OutlinkAppType = undefined>({
shareId
}: {
shareId?: string;
}) {
if (!shareId) { if (!shareId) {
return Promise.reject(OutLinkErrEnum.linkUnInvalid); return Promise.reject(OutLinkErrEnum.linkUnInvalid);
} }
const shareChat = await MongoOutLink.findOne({ shareId }); const shareChat = (await MongoOutLink.findOne({ shareId }).lean()) as OutLinkSchema<T>;
if (!shareChat) { if (!shareChat) {
return Promise.reject(OutLinkErrEnum.linkUnInvalid); return Promise.reject(OutLinkErrEnum.linkUnInvalid);

View File

@@ -0,0 +1,49 @@
import {
TmpDataEnum,
TmpDataExpireTime,
TmpDataMetadata,
TmpDataType
} from '@fastgpt/global/support/tmpData/constant';
import { MongoTmpData } from './schema';
import { TmpDataSchema } from '@fastgpt/global/support/tmpData/type';
import { addMilliseconds } from 'date-fns';
function getDataId<T extends TmpDataEnum>(type: T, metadata: TmpDataMetadata<T>) {
return `${type}--${Object.values(metadata).join('--')}`;
}
export async function getTmpData<T extends TmpDataEnum>({
type,
metadata
}: {
type: T;
metadata: TmpDataMetadata<T>;
}) {
return (await MongoTmpData.findOne({
dataId: getDataId(type, metadata)
}).lean()) as TmpDataSchema<TmpDataType<T>> | null;
}
export async function setTmpData<T extends TmpDataEnum>({
type,
metadata,
data
}: {
type: T;
metadata: TmpDataMetadata<T>;
data: TmpDataType<T>;
}) {
return await MongoTmpData.updateOne(
{
dataId: getDataId(type, metadata)
},
{
dataId: getDataId(type, metadata),
data,
expireAt: addMilliseconds(Date.now(), TmpDataExpireTime[type])
},
{
upsert: true
}
);
}

View File

@@ -0,0 +1,28 @@
import { getMongoModel, Schema } from '../../common/mongo';
import type { TmpDataSchema as SchemaType } from '@fastgpt/global/support/tmpData/type';
const collectionName = 'tmp_datas';
const TmpDataSchema = new Schema({
dataId: {
type: String,
required: true,
unique: true
},
data: {
type: Object
},
expireAt: {
type: Date,
required: true
}
});
try {
TmpDataSchema.index({ dataId: -1 });
TmpDataSchema.index({ expireAt: -1 }, { expireAfterSeconds: 5 });
} catch (error) {
console.log(error);
}
export const MongoTmpData = getMongoModel<SchemaType<Object>>(collectionName, TmpDataSchema);

View File

@@ -76,6 +76,7 @@ export const iconPaths = {
'core/app/logsLight': () => import('./icons/core/app/logsLight.svg'), 'core/app/logsLight': () => import('./icons/core/app/logsLight.svg'),
'core/app/markLight': () => import('./icons/core/app/markLight.svg'), 'core/app/markLight': () => import('./icons/core/app/markLight.svg'),
'core/app/publish/lark': () => import('./icons/core/app/publish/lark.svg'), 'core/app/publish/lark': () => import('./icons/core/app/publish/lark.svg'),
'core/app/publish/wecom': () => import('./icons/core/app/publish/wecom.svg'),
'core/app/questionGuide': () => import('./icons/core/app/questionGuide.svg'), 'core/app/questionGuide': () => import('./icons/core/app/questionGuide.svg'),
'core/app/schedulePlan': () => import('./icons/core/app/schedulePlan.svg'), 'core/app/schedulePlan': () => import('./icons/core/app/schedulePlan.svg'),
'core/app/simpleBot': () => import('./icons/core/app/simpleBot.svg'), 'core/app/simpleBot': () => import('./icons/core/app/simpleBot.svg'),

View File

@@ -0,0 +1,7 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.9278 14.6745C12.9329 14.6814 12.9381 14.6882 12.9449 14.6934C12.9552 14.7037 12.9673 14.714 12.9793 14.7209C13.0033 14.7432 13.0256 14.7655 13.0497 14.7878C13.5031 15.2412 13.7882 15.7994 13.9049 16.385C13.9067 16.4176 13.9101 16.4503 13.9152 16.4829C13.9204 16.519 13.929 16.555 13.9376 16.5894C13.9822 16.7542 14.0698 16.9088 14.1986 17.0376C14.5936 17.4326 15.2359 17.4326 15.6326 17.0376C16.0276 16.6426 16.0276 16.0003 15.6326 15.6036C15.4935 15.4645 15.3235 15.3735 15.1432 15.3322C15.1226 15.3271 15.1037 15.3237 15.0831 15.3202C15.0624 15.3168 15.0418 15.3134 15.0212 15.3116C14.4219 15.2 13.8483 14.9115 13.3846 14.4478C13.3519 14.4152 13.3193 14.3808 13.2867 14.3465C13.1922 14.252 13.0394 14.252 12.9432 14.3465C12.8556 14.4409 12.8505 14.58 12.9278 14.6745Z" fill="#FB6500"/>
<path d="M16.1323 15.4541C16.1391 15.449 16.146 15.4438 16.1511 15.4369C16.1615 15.4266 16.1718 15.4146 16.1786 15.4026C16.201 15.3786 16.2233 15.3562 16.2456 15.3322C16.699 14.8788 17.2571 14.5937 17.8428 14.4769C17.8754 14.4752 17.908 14.4718 17.9407 14.4666C17.9767 14.4615 18.0128 14.4529 18.0471 14.4443C18.212 14.3996 18.3666 14.3121 18.4954 14.1833C18.8904 13.7883 18.8904 13.146 18.4954 12.7493C18.1004 12.3543 17.4581 12.3543 17.0614 12.7493C16.9223 12.8884 16.8312 13.0584 16.79 13.2387C16.7849 13.2593 16.7814 13.2782 16.778 13.2988C16.7746 13.3194 16.7711 13.34 16.7694 13.3606C16.6578 13.96 16.3693 14.5336 15.9056 14.9973C15.8729 15.0299 15.8386 15.0626 15.8042 15.0952C15.7098 15.1896 15.7098 15.3425 15.8042 15.4387C15.897 15.5245 16.0361 15.5314 16.1323 15.4541Z" fill="#0082EF"/>
<path d="M16.9103 12.2496C16.9052 12.2427 16.9 12.2358 16.8932 12.2307C16.8828 12.2204 16.8708 12.2101 16.8588 12.2032C16.8348 12.1809 16.8124 12.1585 16.7884 12.1362C16.335 11.6828 16.0499 11.1247 15.9331 10.5391C15.9314 10.5064 15.928 10.4738 15.9228 10.4412C15.9177 10.4051 15.9091 10.369 15.9005 10.3347C15.8559 10.1698 15.7683 10.0153 15.6395 9.88646C15.2445 9.49146 14.6022 9.49146 14.2055 9.88646C13.8087 10.2815 13.8105 10.9238 14.2055 11.3205C14.3446 11.4596 14.5146 11.5506 14.6949 11.5918C14.7155 11.597 14.7344 11.6004 14.755 11.6038C14.7756 11.6073 14.7962 11.6107 14.8168 11.6124C15.4162 11.724 15.9898 12.0126 16.4535 12.4763C16.4861 12.5089 16.5188 12.5432 16.5514 12.5776C16.6459 12.672 16.7987 12.672 16.8949 12.5776C16.9825 12.4848 16.9876 12.3457 16.9103 12.2496Z" fill="#2DBC00"/>
<path d="M13.7056 11.4716C13.6988 11.4767 13.6919 11.4819 13.6867 11.4887C13.6764 11.4991 13.6661 11.5111 13.6593 11.5231C13.6369 11.5471 13.6146 11.5695 13.5923 11.5935C13.1389 12.0469 12.5807 12.332 11.9951 12.4488C11.9625 12.4505 11.9299 12.4539 11.8972 12.4591C11.8612 12.4642 11.8251 12.4728 11.7907 12.4814C11.6259 12.526 11.4713 12.6136 11.3425 12.7424C10.9475 13.1374 10.9475 13.7797 11.3425 14.1764C11.7375 14.5732 12.3798 14.5714 12.7765 14.1764C12.9156 14.0373 13.0066 13.8673 13.0479 13.687C13.053 13.6664 13.0565 13.6475 13.0599 13.6269C13.0633 13.6063 13.0668 13.5857 13.0685 13.5651C13.1801 12.9657 13.4686 12.3921 13.9323 11.9284C13.9649 11.8958 13.9993 11.8631 14.0336 11.8305C14.1281 11.7361 14.1281 11.5832 14.0336 11.487C13.9409 11.3994 13.8018 11.3943 13.7056 11.4716Z" fill="#FFCC00"/>
<path d="M15.2136 6.51859C14.9319 5.93984 14.5524 5.4023 14.0887 4.92143C12.9123 3.70553 11.267 2.92241 9.45521 2.71804C9.13063 2.68026 8.80776 2.66309 8.49691 2.66309C8.20153 2.66309 7.8924 2.68026 7.57984 2.71461C5.75942 2.91211 4.10559 3.69179 2.92231 4.90769C2.45519 5.38856 2.07393 5.92438 1.79056 6.50142C1.40415 7.28282 1.20837 8.11575 1.20837 8.97272C1.20837 10.077 1.54498 11.1641 2.18041 12.119C2.53934 12.6599 3.15416 13.3572 3.67968 13.7848L3.38772 14.9887L3.30357 15.3287C3.28812 15.3614 3.27781 15.3974 3.26922 15.4335C3.26407 15.4558 3.26235 15.4781 3.26064 15.5022C3.25892 15.5194 3.25549 15.5365 3.25549 15.5554C3.25549 15.8645 3.50622 16.117 3.81707 16.117C3.91839 16.117 4.01285 16.0878 4.09356 16.0414C4.097 16.0397 4.09872 16.038 4.10215 16.038L4.13822 16.0174L6.21796 14.9732C6.66619 15.102 7.10927 15.1845 7.57812 15.236C7.88209 15.2703 8.19122 15.2858 8.49691 15.2858C8.80776 15.2858 9.13063 15.2669 9.45521 15.2308C10.0941 15.1587 10.7089 15.011 11.2911 14.8015C11.2275 14.7809 11.1657 14.7534 11.1056 14.7191C10.7484 14.5147 10.5646 14.1266 10.6007 13.7419C10.1834 13.8741 9.74888 13.9686 9.29893 14.0201C9.02587 14.051 8.75624 14.0665 8.4952 14.0665C8.23931 14.0665 7.9817 14.0527 7.72581 14.0235C7.67257 14.0184 7.61934 14.0098 7.5661 14.0029C7.21575 13.9565 6.87056 13.8844 6.53567 13.7865C6.46698 13.7642 6.39485 13.7539 6.321 13.7539C6.20594 13.7539 6.09431 13.7848 5.98096 13.8432C5.9655 13.8501 5.95176 13.8569 5.93631 13.8655L4.60191 14.6521H4.60019C4.57271 14.6675 4.55726 14.6744 4.5418 14.6744C4.49543 14.6744 4.45765 14.6349 4.45765 14.5868L4.50745 14.3876C4.52119 14.3344 4.54008 14.2605 4.56412 14.1712C4.62252 13.9497 4.70151 13.6457 4.76162 13.419C4.77536 13.3726 4.78738 13.3177 4.78738 13.2559C4.78738 13.0841 4.70495 12.921 4.56756 12.8214C4.49715 12.7698 4.42845 12.7166 4.35461 12.6565C4.24298 12.5655 4.13478 12.471 4.03174 12.3731C3.7415 12.0983 3.48733 11.7978 3.27438 11.4767C2.77119 10.721 2.50499 9.86232 2.50499 8.99505C2.50499 8.32184 2.65956 7.66752 2.96181 7.04926C3.18851 6.58729 3.4942 6.15794 3.87031 5.77153C4.84406 4.77202 6.21109 4.13144 7.72409 3.96657C7.98685 3.93738 8.24618 3.92364 8.49348 3.92364C8.7528 3.92364 9.02243 3.9391 9.29721 3.97001C10.8016 4.14175 12.1618 4.78576 13.1287 5.78356C13.5031 6.16996 13.807 6.60103 14.032 7.06472C14.3308 7.67954 14.482 8.3287 14.482 8.99505C14.482 9.06374 14.4768 9.13415 14.4734 9.20285C14.8632 8.96413 15.3802 9.0105 15.7168 9.34882C15.7339 9.366 15.7477 9.38489 15.7631 9.40206C15.7734 9.25952 15.7803 9.11698 15.7803 8.97444C15.7837 8.12434 15.5914 7.29828 15.2136 6.51859Z" fill="#0079DE"/>
</svg>

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@@ -4,5 +4,5 @@
xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"> xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64">
<path <path
d="M975.657143 96.914286l-48.457143-48.457143c-1.828571-1.828571-4.114286-2.628571-6.514286-2.628572s-4.685714 0.914286-6.514285 2.628572l-86.971429 86.971428a227.737143 227.737143 0 0 0-128.114286-39.2c-58.514286 0-117.028571 22.285714-161.714285 66.971429L420.914286 279.657143a9.177143 9.177143 0 0 0 0 12.914286L731.428571 603.085714c1.828571 1.828571 4.114286 2.628571 6.514286 2.628572 2.285714 0 4.685714-0.914286 6.514286-2.628572l116.457143-116.457143c78.742857-78.857143 88-200.8 27.771428-289.714285l86.971429-86.971429c3.542857-3.657143 3.542857-9.485714 0-13.028571zM588.457143 551.657143a9.177143 9.177143 0 0 0-12.914286 0L499.428571 627.771429 396.228571 524.571429l76.228572-76.228572c3.542857-3.542857 3.542857-9.371429 0-12.914286L430.857143 393.828571a9.177143 9.177143 0 0 0-12.914286 0L341.714286 470.057143l-49.142857-49.142857a8.971429 8.971429 0 0 0-6.514286-2.628572c-2.285714 0-4.685714 0.914286-6.514286 2.628572L163.2 537.371429c-78.742857 78.742857-88 200.8-27.771429 289.714285l-86.971428 86.971429a9.177143 9.177143 0 0 0 0 12.914286l48.457143 48.457142c1.828571 1.828571 4.114286 2.628571 6.514285 2.628572s4.685714-0.914286 6.514286-2.628572l86.971429-86.971428c38.514286 26.171429 83.314286 39.2 128.114285 39.2 58.514286 0 117.028571-22.285714 161.714286-66.971429l116.457143-116.457143c3.542857-3.542857 3.542857-9.371429 0-12.914285l-49.142857-49.142857 76.228571-76.228572c3.542857-3.542857 3.542857-9.371429 0-12.914286l-41.828571-41.371428z" d="M975.657143 96.914286l-48.457143-48.457143c-1.828571-1.828571-4.114286-2.628571-6.514286-2.628572s-4.685714 0.914286-6.514285 2.628572l-86.971429 86.971428a227.737143 227.737143 0 0 0-128.114286-39.2c-58.514286 0-117.028571 22.285714-161.714285 66.971429L420.914286 279.657143a9.177143 9.177143 0 0 0 0 12.914286L731.428571 603.085714c1.828571 1.828571 4.114286 2.628571 6.514286 2.628572 2.285714 0 4.685714-0.914286 6.514286-2.628572l116.457143-116.457143c78.742857-78.857143 88-200.8 27.771428-289.714285l86.971429-86.971429c3.542857-3.657143 3.542857-9.485714 0-13.028571zM588.457143 551.657143a9.177143 9.177143 0 0 0-12.914286 0L499.428571 627.771429 396.228571 524.571429l76.228572-76.228572c3.542857-3.542857 3.542857-9.371429 0-12.914286L430.857143 393.828571a9.177143 9.177143 0 0 0-12.914286 0L341.714286 470.057143l-49.142857-49.142857a8.971429 8.971429 0 0 0-6.514286-2.628572c-2.285714 0-4.685714 0.914286-6.514286 2.628572L163.2 537.371429c-78.742857 78.742857-88 200.8-27.771429 289.714285l-86.971428 86.971429a9.177143 9.177143 0 0 0 0 12.914286l48.457143 48.457142c1.828571 1.828571 4.114286 2.628571 6.514285 2.628572s4.685714-0.914286 6.514286-2.628572l86.971429-86.971428c38.514286 26.171429 83.314286 39.2 128.114285 39.2 58.514286 0 117.028571-22.285714 161.714286-66.971429l116.457143-116.457143c3.542857-3.542857 3.542857-9.371429 0-12.914285l-49.142857-49.142857 76.228571-76.228572c3.542857-3.542857 3.542857-9.371429 0-12.914286l-41.828571-41.371428z"
p-id="4983" fill="#515151"></path> p-id="4983"></path>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -6,8 +6,7 @@ import {
ModalHeader, ModalHeader,
ModalCloseButton, ModalCloseButton,
ModalContentProps, ModalContentProps,
Box, Box
Image
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import MyBox from '../MyBox'; import MyBox from '../MyBox';
import { useSystem } from '../../../hooks/useSystem'; import { useSystem } from '../../../hooks/useSystem';
@@ -18,13 +17,13 @@ export interface MyModalProps extends ModalContentProps {
title?: any; title?: any;
isCentered?: boolean; isCentered?: boolean;
isLoading?: boolean; isLoading?: boolean;
isOpen: boolean; isOpen?: boolean;
onClose?: () => void; onClose?: () => void;
closeOnOverlayClick?: boolean; closeOnOverlayClick?: boolean;
} }
const MyModal = ({ const MyModal = ({
isOpen, isOpen = true,
onClose, onClose,
iconSrc, iconSrc,
title, title,

View File

@@ -484,8 +484,10 @@
}, },
"logs": { "logs": {
"api": "API call", "api": "API call",
"feishu": "Lark",
"online": "Online use", "online": "Online use",
"share": "External link call", "share": "External link call",
"team": "Team chat",
"test": "Test" "test": "Test"
}, },
"markdown": { "markdown": {

View File

@@ -72,5 +72,8 @@
"add_collaborator": "Add collaborators", "add_collaborator": "Add collaborators",
"manage_collaborators": "Manage collaborators", "manage_collaborators": "Manage collaborators",
"no_collaborators": "No collaborators yet" "no_collaborators": "No collaborators yet"
},
"usage": {
"feishu": "Lark"
} }
} }

View File

@@ -494,8 +494,10 @@
}, },
"logs": { "logs": {
"api": "API 调用", "api": "API 调用",
"feishu": "飞书",
"online": "在线使用", "online": "在线使用",
"share": "外部链接调用", "share": "外部链接调用",
"team": "团队空间对话",
"test": "测试" "test": "测试"
}, },
"markdown": { "markdown": {

View File

@@ -1,10 +1,13 @@
{ {
"publish_name": "名称",
"create_api_key": "创建新 key", "create_api_key": "创建新 key",
"create_link": "创建链接", "create_link": "创建链接",
"default_response": "默认回复", "default_response": "默认回复",
"edit_api_key": "编辑 key 信息", "edit_api_key": "编辑 key 信息",
"edit_link": "编辑", "edit_link": "编辑",
"feishu_name": "飞书", "feishu_name": "飞书",
"new_feishu_bot": "新增飞书机器人",
"edit_feishu_bot": "编辑飞书机器人",
"link_name": "分享链接的名字", "link_name": "分享链接的名字",
"qpm_tips": "每个 IP 每分钟最多提问多少次", "qpm_tips": "每个 IP 每分钟最多提问多少次",
"qpm_is_empty": "QPM 不能为空", "qpm_is_empty": "QPM 不能为空",
@@ -13,5 +16,20 @@
"key_tips": "你可以使用 API 秘钥访问一些特定的接口(无法访问应用,访问应用需使用应用内的 API key", "key_tips": "你可以使用 API 秘钥访问一些特定的接口(无法访问应用,访问应用需使用应用内的 API key",
"token_auth": "身份验证", "token_auth": "身份验证",
"token_auth_tips": "身份校验服务器地址,如填写该值,每次对话前都会向指定服务器发送一个请求,进行身份校验", "token_auth_tips": "身份校验服务器地址,如填写该值,每次对话前都会向指定服务器发送一个请求,进行身份校验",
"token_auth_use_cases": "查看身份验证使用说明" "token_auth_use_cases": "查看身份验证使用说明",
} "show_share_link_modal_title": "开始使用",
"request_address": "请求地址",
"basic_info": "基本信息",
"feishu_api": "飞书接口",
"feishu_bot": "飞书机器人",
"feishu_bot_desc": "通过 API 直接接入飞书机器人",
"copy_link_hint": "将下面链接复制到指定位置",
"wecom": {
"title": "发布到企业微信机器人",
"bot": "企业微信机器人",
"bot_desc": "通过 API 直接接入企业微信机器人",
"edit_modal_title": "编辑企微机器人",
"create_modal_title": "创建企微机器人",
"api": "企微 API"
}
}

View File

@@ -1,10 +1,47 @@
{ {
"bind_inform_account_error": "绑定通知账号异常", "bind_inform_account_error": "绑定通知账号异常",
"bind_inform_account_success": "绑定通知账号成功", "bind_inform_account_success": "绑定通知账号成功",
"delete": {
"admin_failed": "删除管理员失败",
"admin_success": "删除管理员成功"
},
"has_chosen": "已选择",
"login": {
"error": "登录异常",
"failed": "登录失败",
"login_account": "登录 {{account}} 账号",
"login_error": "用户名或密码错误",
"password_condition": "密码最多 60 位",
"success": "登录成功",
"to_register": "没有账号,去注册"
},
"name": "名称",
"notification": { "notification": {
"Bind Notification Pipe Hint": "请绑定通知接收账号,以确保您能正常接收套餐过期提醒等通知,保障您的服务正常运行。", "Bind Notification Pipe Hint": "请绑定通知接收账号,以确保您能正常接收套餐过期提醒等通知,保障您的服务正常运行。",
"remind_owner_bind": "请提醒创建者绑定通知账号" "remind_owner_bind": "请提醒创建者绑定通知账号"
}, },
"operations": "操作",
"password": {
"change_error": "修改密码异常",
"code_required": "验证码不能为空",
"code_send_error": "验证码发送异常",
"code_sended": "验证码已发送",
"confirm": "确认密码",
"email_phone": "邮箱/手机号",
"email_phone_error": "邮箱/手机号格式错误",
"email_phone_void": "邮箱/手机号不能为空",
"get_code": "获取验证码",
"get_code_again": "s后重新获取",
"new_password": "新密码(4~20位)",
"not_match": "两次密码不一致",
"password_condition": "密码最少 4 位最多 20 位",
"password_required": "密码不能为空",
"retrieve": "找回密码",
"retrieved": "密码已找回",
"retrieved_account": "找回 {{account}} 账号",
"to_login": "去登录",
"verification_code": "验证码"
},
"permission": { "permission": {
"Manage": "管理员", "Manage": "管理员",
"Manage tip": "团队管理员,拥有全部权限", "Manage tip": "团队管理员,拥有全部权限",
@@ -16,62 +53,28 @@
"team_read": "团队可访问", "team_read": "团队可访问",
"team_write": "团队可编辑" "team_write": "团队可编辑"
}, },
"permissions": "权限",
"register": {
"confirm": "确认注册",
"error": "注册异常",
"failed": "注册失败",
"register_account": "注册 {{account}} 账号",
"success": "注册成功",
"to_login": "已有账号,去登录"
},
"search_user": "搜索用户名",
"synchronization": {
"button": "立即同步",
"placeholder": "请输入同步标签",
"title": "填写标签同步链接,点击同步按钮即可同步"
},
"team": { "team": {
"Add manager": "添加管理员", "Add manager": "添加管理员",
"add_collaborator": "添加协作者", "add_collaborator": "添加协作者",
"manage_collaborators": "管理协作者", "manage_collaborators": "管理协作者",
"no_collaborators": "暂无协作者" "no_collaborators": "暂无协作者"
}, },
"search_user": "搜索用户名", "usage": {
"has_chosen": "已选择", "feishu": "飞书"
"name": "名称",
"permissions": "权限",
"operations": "操作",
"delete": {
"admin_success": "删除管理员成功",
"admin_failed": "删除管理员失败"
},
"synchronization": {
"title": "填写标签同步链接,点击同步按钮即可同步",
"placeholder": "请输入同步标签",
"button": "立即同步"
},
"password": {
"retrieve": "找回密码",
"retrieved": "密码已找回",
"change_error": "修改密码异常",
"retrieved_account": "找回 {{account}} 账号",
"email_phone": "邮箱/手机号",
"email_phone_void": "邮箱/手机号不能为空",
"email_phone_error": "邮箱/手机号格式错误",
"code_required": "验证码不能为空",
"new_password": "新密码(4~20位)",
"password_required": "密码不能为空",
"password_condition": "密码最少 4 位最多 20 位",
"verification_code": "验证码",
"confirm": "确认密码",
"not_match": "两次密码不一致",
"to_login": "去登录",
"get_code": "获取验证码",
"get_code_again": "s后重新获取",
"code_sended": "验证码已发送",
"code_send_error": "验证码发送异常"
},
"register": {
"success": "注册成功",
"failed": "注册失败",
"error": "注册异常",
"register_account": "注册 {{account}} 账号",
"confirm": "确认注册",
"to_login": "已有账号,去登录"
},
"login": {
"success": "登录成功",
"failed": "登录失败",
"error": "登录异常",
"login_account": "登录 {{account}} 账号",
"login_error": "用户名或密码错误",
"to_register": "没有账号,去注册",
"password_condition": "密码最多 60 位"
} }
} }

View File

@@ -20,7 +20,7 @@
"input_description": "字段描述", "input_description": "字段描述",
"only_the_reference_type_is_supported": "仅支持引用类型", "only_the_reference_type_is_supported": "仅支持引用类型",
"optional_value_type": "可选的数据类型", "optional_value_type": "可选的数据类型",
"optional_value_type_tip": "可以指定 1 个或多个数据类型,用户在冬天添加字段时,仅可选择配置的类型", "optional_value_type_tip": "可以指定 1 个或多个数据类型,用户在动态添加字段时,仅可选择配置的类型",
"response": { "response": {
"Code log": "Log 日志", "Code log": "Log 日志",
"Custom inputs": "自定义输入", "Custom inputs": "自定义输入",

View File

@@ -7,6 +7,7 @@ import publish from '../i18n/zh/publish.json';
import workflow from '../i18n/zh/workflow.json'; import workflow from '../i18n/zh/workflow.json';
import user from '../i18n/zh/user.json'; import user from '../i18n/zh/user.json';
import chat from '../i18n/zh/chat.json'; import chat from '../i18n/zh/chat.json';
export interface I18nNamespaces { export interface I18nNamespaces {
common: typeof common; common: typeof common;
dataset: typeof dataset; dataset: typeof dataset;

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

View File

@@ -75,9 +75,9 @@ const MyRadio = ({
{!!item.icon && ( {!!item.icon && (
<> <>
{item.icon.startsWith('/') ? ( {item.icon.startsWith('/') ? (
<Image src={item.icon} mr={'14px'} w={iconSize} alt={''} /> <Image src={item.icon} mr={'14px'} w={iconSize} alt={''} fill={'primary.600'} />
) : ( ) : (
<MyIcon mr={'14px'} name={item.icon as any} w={iconSize} /> <MyIcon mr={'14px'} name={item.icon as any} w={iconSize} fill={'primary.600'} />
)} )}
</> </>
)} )}

View File

@@ -25,7 +25,6 @@ import {
} from '@/web/support/openapi/api'; } from '@/web/support/openapi/api';
import type { EditApiKeyProps } from '@/global/support/openapi/api.d'; import type { EditApiKeyProps } from '@/global/support/openapi/api.d';
import { useQuery, useMutation } from '@tanstack/react-query'; import { useQuery, useMutation } from '@tanstack/react-query';
import { useLoading } from '@fastgpt/web/hooks/useLoading';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { AddIcon } from '@chakra-ui/icons'; import { AddIcon } from '@chakra-ui/icons';
import { useCopyData } from '@/web/common/hooks/useCopyData'; import { useCopyData } from '@/web/common/hooks/useCopyData';
@@ -53,7 +52,6 @@ const defaultEditData: EditProps = {
const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => { const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { Loading } = useLoading();
const theme = useTheme(); const theme = useTheme();
const { copyData } = useCopyData(); const { copyData } = useCopyData();
const { feConfigs } = useSystemStore(); const { feConfigs } = useSystemStore();
@@ -82,7 +80,7 @@ const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => {
useEffect(() => { useEffect(() => {
setBaseUrl(feConfigs?.customApiDomain || `${location.origin}/api`); setBaseUrl(feConfigs?.customApiDomain || `${location.origin}/api`);
}, []); }, [feConfigs?.customApiDomain]);
return ( return (
<MyBox <MyBox

View File

@@ -2,7 +2,7 @@ import type { NextApiResponse } from 'next';
import { authCert } from '@fastgpt/service/support/permission/auth/common'; import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { NodeTemplateListItemType } from '@fastgpt/global/core/workflow/type/node.d'; import { NodeTemplateListItemType } from '@fastgpt/global/core/workflow/type/node.d';
import { NextAPI } from '@/service/middleware/entry'; import { NextAPI } from '@/service/middleware/entry';
import { getSystemPluginTemplates } from '@fastgpt/plugins/register'; import { getSystemPlugins } from '@/service/core/app/plugin';
import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type'; import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
@@ -24,7 +24,7 @@ async function handler(
const formatParentId = parentId || null; const formatParentId = parentId || null;
return getSystemPluginTemplates().then((res) => return getSystemPlugins().then((res) =>
res res
// Just show the active plugins // Just show the active plugins
.filter((item) => item.isActive) .filter((item) => item.isActive)

View File

@@ -1,7 +1,7 @@
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next'; import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry'; import { NextAPI } from '@/service/middleware/entry';
import { ParentIdType, ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type'; import { ParentIdType, ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
import { getSystemPluginTemplates } from '@fastgpt/plugins/register'; import { getSystemPlugins } from '@/service/core/app/plugin';
export type pathQuery = { export type pathQuery = {
parentId: ParentIdType; parentId: ParentIdType;
@@ -19,7 +19,7 @@ async function handler(
if (!parentId) return []; if (!parentId) return [];
const plugins = await getSystemPluginTemplates(); const plugins = await getSystemPlugins();
const plugin = plugins.find((item) => item.id === parentId); const plugin = plugins.find((item) => item.id === parentId);
if (!plugin) return []; if (!plugin) return [];

View File

@@ -0,0 +1,20 @@
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import { POST } from '@fastgpt/service/common/api/plusRequest';
export type OutLinkFeishuQuery = any;
export type OutLinkFeishuBody = any;
export type OutLinkFeishuResponse = {};
async function handler(
req: ApiRequestProps<OutLinkFeishuBody, OutLinkFeishuQuery>,
res: ApiResponseType<any>
): Promise<void> {
// send to pro
const { token } = req.query;
const result = await POST<any>(`support/outLink/feishu/${token}`, req.body, {
headers: req.headers as any
});
res.json(result);
}
export default handler;

View File

@@ -13,7 +13,7 @@ export type OutLinkUpdateResponse = {};
async function handler( async function handler(
req: ApiRequestProps<OutLinkUpdateBody, OutLinkUpdateQuery> req: ApiRequestProps<OutLinkUpdateBody, OutLinkUpdateQuery>
): Promise<OutLinkUpdateResponse> { ): Promise<OutLinkUpdateResponse> {
const { _id, name, responseDetail, limit } = req.body; const { _id, name, responseDetail, limit, app } = req.body;
if (!_id) { if (!_id) {
return Promise.reject(CommonErrEnum.missingParams); return Promise.reject(CommonErrEnum.missingParams);
@@ -24,7 +24,8 @@ async function handler(
await MongoOutLink.findByIdAndUpdate(_id, { await MongoOutLink.findByIdAndUpdate(_id, {
name, name,
responseDetail, responseDetail,
limit limit,
app
}); });
return {}; return {};
} }

View File

@@ -0,0 +1,31 @@
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import { plusRequest } from '@fastgpt/service/common/api/plusRequest';
export type OutLinkWecomQuery = any;
export type OutLinkWecomBody = any;
export type OutLinkWecomResponse = {};
async function handler(
req: ApiRequestProps<OutLinkWecomBody, OutLinkWecomQuery>,
res: ApiResponseType<any>
): Promise<any> {
const { token, type } = req.query;
const result = await plusRequest({
url: `support/outLink/wecom/${token}`,
params: {
...req.query,
type
},
data: req.body
});
if (result.data?.data?.message) {
// chanllege
res.send(result.data.data.message);
res.end();
}
res.send('success');
res.end();
}
export default handler;

View File

@@ -21,7 +21,7 @@ import {
} from '@fastgpt/global/core/workflow/runtime/utils'; } from '@fastgpt/global/core/workflow/runtime/utils';
import { GPTMessages2Chats, chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt'; import { GPTMessages2Chats, chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
import { getChatItems } from '@fastgpt/service/core/chat/controller'; import { getChatItems } from '@fastgpt/service/core/chat/controller';
import { saveChat } from '@/service/utils/chat/saveChat'; import { saveChat } from '@fastgpt/service/core/chat/saveChat';
import { responseWrite } from '@fastgpt/service/common/response'; import { responseWrite } from '@fastgpt/service/common/response';
import { pushChatUsage } from '@/service/support/wallet/usage/push'; import { pushChatUsage } from '@/service/support/wallet/usage/push';
import { authOutLinkChatStart } from '@/service/support/permission/auth/outLink'; import { authOutLinkChatStart } from '@/service/support/permission/auth/outLink';

View File

@@ -71,35 +71,33 @@ const Logs = () => {
const [detailLogsId, setDetailLogsId] = useState<string>(); const [detailLogsId, setDetailLogsId] = useState<string>();
return ( return (
<> <Flex flexDirection={'column'} h={'100%'}>
<Box {...cardStyles} boxShadow={2} px={[4, 8]} py={[4, 6]}> {isPc && (
{isPc && ( <Box {...cardStyles} boxShadow={2} px={[4, 8]} py={[4, 6]}>
<> <Box fontWeight={'bold'} fontSize={['md', 'lg']} mb={2}>
<Box fontWeight={'bold'} fontSize={['md', 'lg']} mb={2}> {appT('chat_logs')}
{appT('chat_logs')} </Box>
<Box color={'myGray.500'} fontSize={'sm'}>
{appT('chat_logs_tips')},{' '}
<Box
as={'span'}
mr={2}
textDecoration={'underline'}
cursor={'pointer'}
onClick={onOpenMarkDesc}
>
{t('common:core.chat.Read Mark Description')}
</Box> </Box>
<Box color={'myGray.500'} fontSize={'sm'}> </Box>
{appT('chat_logs_tips')},{' '} </Box>
<Box )}
as={'span'}
mr={2}
textDecoration={'underline'}
cursor={'pointer'}
onClick={onOpenMarkDesc}
>
{t('common:core.chat.Read Mark Description')}
</Box>
</Box>
</>
)}
</Box>
{/* table */} {/* table */}
<Flex <Flex
flexDirection={'column'} flexDirection={'column'}
{...cardStyles} {...cardStyles}
boxShadow={3.5} boxShadow={3.5}
mt={4} mt={[0, 4]}
px={[4, 8]} px={[4, 8]}
py={[4, 6]} py={[4, 6]}
flex={'1 0 0'} flex={'1 0 0'}
@@ -214,7 +212,7 @@ const Logs = () => {
> >
<ModalBody whiteSpace={'pre-wrap'}>{t('common:core.chat.Mark Description')}</ModalBody> <ModalBody whiteSpace={'pre-wrap'}>{t('common:core.chat.Mark Description')}</ModalBody>
</MyModal> </MyModal>
</> </Flex>
); );
}; };

View File

@@ -1,32 +1,35 @@
import React, { useMemo } from 'react'; import React from 'react';
import { Flex, Box, Button, ModalFooter, ModalBody, Input } from '@chakra-ui/react'; import { Flex, Box, Button, ModalFooter, ModalBody, Input, Link, Grid } from '@chakra-ui/react';
import MyModal from '@fastgpt/web/components/common/MyModal'; import MyModal from '@fastgpt/web/components/common/MyModal';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant'; import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
import type { FeishuType, OutLinkEditType } from '@fastgpt/global/support/outLink/type'; import type { FeishuAppType, OutLinkEditType } from '@fastgpt/global/support/outLink/type';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { useRequest } from '@/web/common/hooks/useRequest';
import dayjs from 'dayjs';
import { createShareChat, updateShareChat } from '@/web/support/outLink/api'; import { createShareChat, updateShareChat } from '@/web/support/outLink/api';
import { useI18n } from '@/web/context/I18n'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import BasicInfo from '../components/BasicInfo';
import { getDocPath } from '@/web/common/system/doc';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
const FeiShuEditModal = ({ const FeiShuEditModal = ({
appId, appId,
defaultData, defaultData,
onClose, onClose,
onCreate, onCreate,
onEdit onEdit,
isEdit = false
}: { }: {
appId: string; appId: string;
defaultData: OutLinkEditType<FeishuType>; defaultData: OutLinkEditType<FeishuAppType>;
onClose: () => void; onClose: () => void;
onCreate: (id: string) => void; onCreate: (id: string) => void;
onEdit: () => void; onEdit: () => void;
isEdit?: boolean;
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { publishT } = useI18n();
const { const {
register, register,
setValue, setValue,
@@ -35,174 +38,101 @@ const FeiShuEditModal = ({
defaultValues: defaultData defaultValues: defaultData
}); });
const isEdit = useMemo(() => !!defaultData?._id, [defaultData]); const { runAsync: onclickCreate, loading: creating } = useRequest2(
(e) =>
const { mutate: onclickCreate, isLoading: creating } = useRequest({
mutationFn: async (e: OutLinkEditType<FeishuType>) => {
createShareChat({ createShareChat({
...e, ...e,
appId, appId,
type: PublishChannelEnum.feishu type: PublishChannelEnum.feishu
}); }),
}, {
errorToast: t('common:common.Create Failed'), errorToast: t('common:common.Create Failed'),
onSuccess: onCreate successToast: t('common:common.Create Success'),
}); onSuccess: onCreate
const { mutate: onclickUpdate, isLoading: updating } = useRequest({ }
mutationFn: (e: OutLinkEditType<FeishuType>) => { );
return updateShareChat(e);
}, const { runAsync: onclickUpdate, loading: updating } = useRequest2((e) => updateShareChat(e), {
errorToast: t('common:common.Update Failed'), errorToast: t('common:common.Update Failed'),
successToast: t('common:common.Update Success'),
onSuccess: onEdit onSuccess: onEdit
}); });
const { feConfigs } = useSystemStore();
const { isPc } = useSystem();
return ( return (
<MyModal <MyModal
isOpen={true} iconSrc="core/app/publish/lark"
iconSrc="/imgs/modal/shareFill.svg" title={isEdit ? t('publish:edit_feishu_bot') : t('publish:new_feishu_bot')}
title={isEdit ? publishT('edit_link') : publishT('create_link')} minW={['auto', '60rem']}
> >
<ModalBody> <ModalBody display={'grid'} gridTemplateColumns={['1fr', '1fr 1fr']} fontSize={'14px'} p={0}>
<Flex alignItems={'center'}> <Box p={8} h={['auto', '400px']} borderRight={'base'}>
<Box flex={'0 0 90px'}>{t('common:Name')}</Box> <BasicInfo register={register} setValue={setValue} defaultData={defaultData} />
<Input </Box>
placeholder={publishT('feishu_name') || 'link_name'} // TODO: i18n <Flex p={8} h={['auto', '400px']} flexDirection="column" gap={6}>
maxLength={20} <Flex alignItems="center">
{...register('name', { <Box color="myGray.600">{t('publish:feishu_api')}</Box>
required: t('common:common.name_is_empty') || 'name_is_empty' {feConfigs?.docUrl && (
})} <Link
/> href={feConfigs.openAPIDocUrl || getDocPath('/docs/use-cases/feishu-bot')}
</Flex> target={'_blank'}
<Flex alignItems={'center'} mt={4}> ml={2}
<Flex flex={'0 0 90px'} alignItems={'center'}> color={'primary.500'}
QPM fontSize={'sm'}
<QuestionTip ml={1} label={publishT('qpm_tips' || '')}></QuestionTip> >
<Flex alignItems={'center'}>
<MyIcon name="book" mr="1" />
{t('common:common.Read document')}
</Flex>
</Link>
)}
</Flex> </Flex>
<Input <Flex alignItems={'center'}>
max={1000} <FormLabel flex={'0 0 6.25rem'} required>
{...register('limit.QPM', { App ID
min: 0, </FormLabel>
max: 1000, <Input
valueAsNumber: true, placeholder={t('common:core.module.http.AppId')}
required: publishT('qpm_is_empty') || '' {...register('app.appId', {
})} required: true
/> })}
</Flex> />
<Flex alignItems={'center'} mt={4}>
<Flex flex={'0 0 90px'} alignItems={'center'}>
{t('common:support.outlink.Max usage points')}
<QuestionTip
ml={1}
label={t('common:support.outlink.Max usage points tip')}
></QuestionTip>
</Flex> </Flex>
<Input <Flex alignItems={'center'}>
{...register('limit.maxUsagePoints', { <FormLabel flex={'0 0 6.25rem'} required>
min: -1, App Secret
max: 10000000, </FormLabel>
valueAsNumber: true, <Input
required: true placeholder={'App Secret'}
})} {...register('app.appSecret', {
/> required: true
</Flex> })}
<Flex alignItems={'center'} mt={4}> />
<Flex flex={'0 0 90px'} alignItems={'center'}>
{t('common:common.Expired Time')}
</Flex> </Flex>
<Input <Flex alignItems={'center'}>
type="datetime-local" <FormLabel flex={'0 0 6.25rem'}>Encrypt Key</FormLabel>
defaultValue={ <Input placeholder="Encrypt Key" {...register('app.encryptKey')} />
defaultData.limit?.expiredTime
? dayjs(defaultData.limit?.expiredTime).format('YYYY-MM-DDTHH:mm')
: ''
}
onChange={(e) => {
setValue('limit.expiredTime', new Date(e.target.value));
}}
/>
</Flex>
<Flex alignItems={'center'} mt={4}>
<Flex flex={'0 0 90px'} alignItems={'center'}>
{t('common:default_reply')}
</Flex> </Flex>
<Input
placeholder={publishT('default_response') || 'link_name'} <Box flex={1}></Box>
maxLength={20}
{...register('defaultResponse', { <Flex justifyContent={'end'}>
required: true <Button variant={'whiteBase'} mr={3} onClick={onClose}>
})} {t('common:common.Close')}
/> </Button>
</Flex> <Button
<Flex alignItems={'center'} mt={4}> isLoading={creating || updating}
<Flex flex={'0 0 90px'} alignItems={'center'}> onClick={submitShareChat((data) =>
{t('common:reply_now')} isEdit ? onclickUpdate(data) : onclickCreate(data)
)}
>
{t('common:common.Confirm')}
</Button>
</Flex> </Flex>
<Input
placeholder={publishT('default_response') || 'link_name'}
maxLength={20}
{...register('immediateResponse', {
required: true
})}
/>
</Flex> </Flex>
<Flex alignItems={'center'} mt={4}>
<Box flex={'0 0 90px'}>{t('common:core.module.http.AppId')}</Box>
<Input
placeholder={t('common:core.module.http.AppId') || 'link_name'}
// maxLength={20}
{...register('app.appId', {
required: true
})}
/>
</Flex>
<Flex alignItems={'center'} mt={4}>
<Box flex={'0 0 90px'}>{t('common:core.module.http.AppSecret' as any)}</Box>
<Input
placeholder={'App Secret'}
// maxLength={20}
{...register('app.appSecret', {
required: t('common:common.name_is_empty') || 'name_is_empty'
})}
/>
</Flex>
<Flex alignItems={'center'} mt={4}>
<Box flex={'0 0 90px'}>Encrypt Key</Box>
<Input
placeholder="Encrypt Key"
// maxLength={20}
{...register('app.encryptKey', {
required: t('common:common.name_is_empty') || 'name_is_empty'
})}
/>
</Flex>
<Flex alignItems={'center'} mt={4}>
<Box flex={'0 0 90px'}>Verification Token</Box>
<Input
placeholder="Verification Token"
// maxLength={20}
{...register('app.verificationToken', {
required: t('common:common.name_is_empty') || 'name_is_empty'
})}
/>
</Flex>
{/* <Flex alignItems={'center'} mt={4}> */}
{/* <Flex flex={'0 0 90px'} alignItems={'center'}> */}
{/* 限制回复 */}
{/* </Flex> */}
{/* <Switch {...register('wecomConfig.ReplyLimit')} size={'lg'} /> */}
{/* </Flex> */}
</ModalBody> </ModalBody>
<ModalFooter>
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
{t('common:common.Close')}
</Button>
<Button
isLoading={creating || updating}
onClick={submitShareChat((data) => (isEdit ? onclickUpdate(data) : onclickCreate(data)))}
>
{t('common:common.Confirm')}
</Button>
</ModalFooter>
</MyModal> </MyModal>
); );
}; };

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react'; import React, { useMemo, useState } from 'react';
import { import {
Flex, Flex,
Box, Box,
@@ -9,61 +9,82 @@ import {
Tr, Tr,
Th, Th,
Td, Td,
Tbody Tbody,
useDisclosure
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import { useLoading } from '@fastgpt/web/hooks/useLoading'; import { useLoading } from '@fastgpt/web/hooks/useLoading';
import { useQuery } from '@tanstack/react-query';
import { getShareChatList, delShareChatById } from '@/web/support/outLink/api'; import { getShareChatList, delShareChatById } from '@/web/support/outLink/api';
import { formatTimeToChatTime } from '@fastgpt/global/common/string/time'; import { formatTimeToChatTime } from '@fastgpt/global/common/string/time';
import { useCopyData } from '@/web/common/hooks/useCopyData';
import { defaultFeishuOutLinkForm } from '@/web/core/app/constants'; import { defaultFeishuOutLinkForm } from '@/web/core/app/constants';
import type { FeishuType, OutLinkEditType } from '@fastgpt/global/support/outLink/type.d'; import type { FeishuAppType, OutLinkEditType } from '@fastgpt/global/support/outLink/type.d';
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant'; import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import MyMenu from '@fastgpt/web/components/common/MyMenu'; import MyMenu from '@fastgpt/web/components/common/MyMenu';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip'; import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
const FeiShuEditModal = dynamic(() => import('./FeiShuEditModal')); const FeiShuEditModal = dynamic(() => import('./FeiShuEditModal'));
const ShowShareLinkModal = dynamic(() => import('../components/showShareLinkModal'));
const FeiShu = ({ appId }: { appId: string }) => { const FeiShu = ({ appId }: { appId: string }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { Loading, setIsLoading } = useLoading(); const { Loading, setIsLoading } = useLoading();
const { feConfigs } = useSystemStore(); const { feConfigs } = useSystemStore();
const { copyData } = useCopyData(); const [editFeiShuLinkData, setEditFeiShuLinkData] = useState<OutLinkEditType<FeishuAppType>>();
const [editFeiShuLinkData, setEditFeiShuLinkData] = useState<OutLinkEditType<FeishuType>>(); const [isEdit, setIsEdit] = useState<boolean>(false);
const { toast } = useToast();
const { const baseUrl = useMemo(
isFetching, () => feConfigs?.customApiDomain || `${location.origin}/api`,
data: shareChatList = [], [feConfigs?.customApiDomain]
refetch: refetchShareChatList
} = useQuery(['initShareChatList', appId], () =>
getShareChatList<FeishuType>({ appId, type: PublishChannelEnum.feishu })
); );
const {
data: shareChatList = [],
loading: isFetching,
runAsync: refetchShareChatList
} = useRequest2(
() => getShareChatList<FeishuAppType>({ appId, type: PublishChannelEnum.feishu }),
{
manual: false
}
);
const {
onOpen: openShowShareLinkModal,
isOpen: showShareLinkModalOpen,
onClose: closeShowShareLinkModal
} = useDisclosure();
const [showShareLink, setShowShareLink] = useState<string | null>(null);
return ( return (
<Box position={'relative'} pt={3} px={5} minH={'50vh'}> <Box position={'relative'} pt={3} px={5} minH={'50vh'}>
<Flex justifyContent={'space-between'}> <Flex justifyContent={'space-between'} flexDirection="row">
<Box fontWeight={'bold'} fontSize={['md', 'lg']}> <Box fontWeight={'bold'} fontSize={['md', 'lg']}>
{t('common:core.app.publish.Fei shu bot publish')} {t('common:core.app.publish.Fei shu bot publish')}
</Box> </Box>
<Button <Button
variant={'whitePrimary'} variant={'primary'}
colorScheme={'blue'} colorScheme={'blue'}
size={['sm', 'md']} size={['sm', 'md']}
leftIcon={<MyIcon name={'common/addLight'} w="1.25rem" color="white" />}
ml={3}
{...(shareChatList.length >= 10 {...(shareChatList.length >= 10
? { ? {
isDisabled: true, isDisabled: true,
title: t('common:core.app.share.Amount limit tip') title: t('common:core.app.share.Amount limit tip')
} }
: {})} : {})}
onClick={() => setEditFeiShuLinkData(defaultFeishuOutLinkForm)} onClick={() => {
setEditFeiShuLinkData(defaultFeishuOutLinkForm);
setIsEdit(false);
}}
> >
{t('common:core.app.share.Create link')} {t('common:add_new')}
</Button> </Button>
</Flex> </Flex>
<TableContainer mt={3}> <TableContainer mt={3}>
@@ -112,11 +133,22 @@ const FeiShu = ({ appId }: { appId: string }) => {
: t('common:common.Un used')} : t('common:common.Un used')}
</Td> </Td>
<Td display={'flex'} alignItems={'center'}> <Td display={'flex'} alignItems={'center'}>
<Button
onClick={() => {
setShowShareLink(`${baseUrl}/support/outLink/feishu/${item.shareId}`);
openShowShareLinkModal();
}}
size={'sm'}
mr={3}
variant={'whitePrimary'}
>
{t('publish:request_address')}
</Button>
<MyMenu <MyMenu
Button={ Button={
<MyIcon <MyIcon
name={'more'} name={'more'}
_hover={{ bg: 'myGray.100 ' }} _hover={{ bg: 'myGray.100' }}
cursor={'pointer'} cursor={'pointer'}
borderRadius={'md'} borderRadius={'md'}
w={'14px'} w={'14px'}
@@ -129,7 +161,7 @@ const FeiShu = ({ appId }: { appId: string }) => {
{ {
label: t('common:common.Edit'), label: t('common:common.Edit'),
icon: 'edit', icon: 'edit',
onClick: () => onClick: () => {
setEditFeiShuLinkData({ setEditFeiShuLinkData({
_id: item._id, _id: item._id,
name: item.name, name: item.name,
@@ -138,7 +170,9 @@ const FeiShu = ({ appId }: { appId: string }) => {
responseDetail: item.responseDetail, responseDetail: item.responseDetail,
defaultResponse: item.defaultResponse, defaultResponse: item.defaultResponse,
immediateResponse: item.immediateResponse immediateResponse: item.immediateResponse
}) });
setIsEdit(true);
}
}, },
{ {
label: t('common:common.Delete'), label: t('common:common.Delete'),
@@ -167,27 +201,24 @@ const FeiShu = ({ appId }: { appId: string }) => {
{editFeiShuLinkData && ( {editFeiShuLinkData && (
<FeiShuEditModal <FeiShuEditModal
appId={appId} appId={appId}
// type={'feishu' as PublishChannelEnum}
defaultData={editFeiShuLinkData} defaultData={editFeiShuLinkData}
onCreate={(id) => { onCreate={() => Promise.all([refetchShareChatList(), setEditFeiShuLinkData(undefined)])}
refetchShareChatList(); onEdit={() => Promise.all([refetchShareChatList(), setEditFeiShuLinkData(undefined)])}
setEditFeiShuLinkData(undefined);
}}
onEdit={() => {
toast({
status: 'success',
title: t('common:common.Update Successful')
});
refetchShareChatList();
setEditFeiShuLinkData(undefined);
}}
onClose={() => setEditFeiShuLinkData(undefined)} onClose={() => setEditFeiShuLinkData(undefined)}
isEdit={isEdit}
/> />
)} )}
{shareChatList.length === 0 && !isFetching && ( {shareChatList.length === 0 && !isFetching && (
<EmptyTip text={t('common:core.app.share.Not share link')}></EmptyTip> <EmptyTip text={t('common:core.app.share.Not share link')}></EmptyTip>
)} )}
<Loading loading={isFetching} fixed={false} /> <Loading loading={isFetching} fixed={false} />
{showShareLinkModalOpen && (
<ShowShareLinkModal
shareLink={showShareLink ?? ''}
onClose={closeShowShareLinkModal}
img="/imgs/outlink/feishu-copylink-instruction.png"
/>
)}
</Box> </Box>
); );
}; };

View File

@@ -0,0 +1,168 @@
import React from 'react';
import { Flex, Box, Button, ModalBody, Input, Link } from '@chakra-ui/react';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
import type { WecomAppType, OutLinkEditType } from '@fastgpt/global/support/outLink/type';
import { useTranslation } from 'next-i18next';
import { useForm } from 'react-hook-form';
import { createShareChat, updateShareChat } from '@/web/support/outLink/api';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import BasicInfo from '../components/BasicInfo';
import { getDocPath } from '@/web/common/system/doc';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
const WecomEditModal = ({
appId,
defaultData,
onClose,
onCreate,
onEdit,
isEdit = false
}: {
appId: string;
defaultData: OutLinkEditType<WecomAppType>;
onClose: () => void;
onCreate: (id: string) => void;
onEdit: () => void;
isEdit?: boolean;
}) => {
const { t } = useTranslation();
const {
register,
setValue,
handleSubmit: submitShareChat
} = useForm({
defaultValues: defaultData
});
const { runAsync: onclickCreate, loading: creating } = useRequest2(
(e) =>
createShareChat({
...e,
appId,
type: PublishChannelEnum.wecom
}),
{
errorToast: t('common:common.Create Failed'),
successToast: t('common:common.Create Success'),
onSuccess: onCreate
}
);
const { runAsync: onclickUpdate, loading: updating } = useRequest2((e) => updateShareChat(e), {
errorToast: t('common:common.Update Failed'),
successToast: t('common:common.Update Success'),
onSuccess: onEdit
});
const { feConfigs } = useSystemStore();
return (
<MyModal
iconSrc="core/app/publish/wecom"
title={isEdit ? t('publish:wecom.edit_modal_title') : t('publish:wecom.create_modal_title')}
minW={['auto', '60rem']}
>
<ModalBody display={'grid'} gridTemplateColumns={['1fr', '1fr 1fr']} fontSize={'14px'} p={0}>
<Box p={8} minH={['auto', '400px']} borderRight={'base'}>
<BasicInfo register={register} setValue={setValue} defaultData={defaultData} />
</Box>
<Flex p={8} minH={['auto', '400px']} flexDirection="column" gap={6}>
<Flex alignItems="center">
<Box color="myGray.600">{t('publish:wecom.api')}</Box>
{feConfigs?.docUrl && (
<Link
href={feConfigs.openAPIDocUrl || getDocPath('/docs/use-cases/wecom-bot')}
target={'_blank'}
ml={2}
color={'primary.500'}
fontSize={'sm'}
>
<Flex alignItems={'center'}>
<MyIcon name="book" mr="1" />
{t('common:common.Read document')}
</Flex>
</Link>
)}
</Flex>
<Flex alignItems={'center'}>
<FormLabel flex={'0 0 6.25rem'} required>
Corp ID
</FormLabel>
<Input
placeholder="Corp ID"
{...register('app.CorpId', {
required: true
})}
/>
</Flex>
<Flex alignItems={'center'}>
<FormLabel flex={'0 0 6.25rem'} required>
Agent ID
</FormLabel>
<Input
placeholder="Agent ID"
{...register('app.AgentId', {
required: true
})}
/>
</Flex>
<Flex alignItems={'center'}>
<FormLabel flex={'0 0 6.25rem'} required>
Secret
</FormLabel>
<Input
placeholder="Secret"
{...register('app.SuiteSecret', {
required: true
})}
/>
</Flex>
<Flex alignItems={'center'}>
<FormLabel flex={'0 0 6.25rem'} required>
Token
</FormLabel>
<Input
placeholder="Token"
{...register('app.CallbackToken', {
required: true
})}
/>
</Flex>
<Flex alignItems={'center'}>
<FormLabel flex={'0 0 6.25rem'} required>
AES Key
</FormLabel>
<Input
placeholder="AES Key"
{...register('app.CallbackEncodingAesKey', {
required: true
})}
/>
</Flex>
<Box flex={1}></Box>
<Flex justifyContent={'end'}>
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
{t('common:common.Close')}
</Button>
<Button
isLoading={creating || updating}
onClick={submitShareChat((data) =>
isEdit ? onclickUpdate(data) : onclickCreate(data)
)}
>
{t('common:common.Confirm')}
</Button>
</Flex>
</Flex>
</ModalBody>
</MyModal>
);
};
export default WecomEditModal;

View File

@@ -0,0 +1,223 @@
import React, { useMemo, useState } from 'react';
import {
Flex,
Box,
Button,
TableContainer,
Table,
Thead,
Tr,
Th,
Td,
Tbody,
useDisclosure
} from '@chakra-ui/react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useLoading } from '@fastgpt/web/hooks/useLoading';
import { getShareChatList, delShareChatById } from '@/web/support/outLink/api';
import { formatTimeToChatTime } from '@fastgpt/global/common/string/time';
import { defaultOutLinkForm } from '@/web/core/app/constants';
import type { WecomAppType, OutLinkEditType } from '@fastgpt/global/support/outLink/type.d';
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
import { useTranslation } from 'next-i18next';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import dayjs from 'dayjs';
import dynamic from 'next/dynamic';
import MyMenu from '@fastgpt/web/components/common/MyMenu';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
const WecomEditModal = dynamic(() => import('./WecomEditModal'));
const ShowShareLinkModal = dynamic(() => import('../components/showShareLinkModal'));
const Wecom = ({ appId }: { appId: string }) => {
const { t } = useTranslation();
const { Loading, setIsLoading } = useLoading();
const { feConfigs } = useSystemStore();
const [editWecomData, setEditWecomData] = useState<OutLinkEditType<WecomAppType>>();
const [isEdit, setIsEdit] = useState<boolean>(false);
const baseUrl = useMemo(
() => feConfigs?.customApiDomain || `${location.origin}/api`,
[feConfigs?.customApiDomain]
);
const {
data: shareChatList = [],
loading: isFetching,
runAsync: refetchShareChatList
} = useRequest2(() => getShareChatList<WecomAppType>({ appId, type: PublishChannelEnum.wecom }), {
manual: false
});
const {
onOpen: openShowShareLinkModal,
isOpen: showShareLinkModalOpen,
onClose: closeShowShareLinkModal
} = useDisclosure();
const [showShareLink, setShowShareLink] = useState<string | null>(null);
return (
<Box position={'relative'} pt={3} px={5} minH={'50vh'}>
<Flex justifyContent={'space-between'} flexDirection="row">
<Box fontWeight={'bold'} fontSize={['md', 'lg']}>
{t('publish:wecom.title')}
</Box>
<Button
variant={'primary'}
colorScheme={'blue'}
size={['sm', 'md']}
leftIcon={<MyIcon name={'common/addLight'} w="1.25rem" color="white" />}
ml={3}
{...(shareChatList.length >= 10
? {
isDisabled: true,
title: t('common:core.app.share.Amount limit tip')
}
: {})}
onClick={() => {
setEditWecomData(defaultOutLinkForm as any); // HACK
setIsEdit(false);
}}
>
{t('common:add_new')}
</Button>
</Flex>
<TableContainer mt={3}>
<Table variant={'simple'} w={'100%'} overflowX={'auto'} fontSize={'sm'}>
<Thead>
<Tr>
<Th>{t('common:common.Name')} </Th>
<Th> {t('common:support.outlink.Usage points')} </Th>
{feConfigs?.isPlus && (
<>
<Th>{t('common:core.app.share.Ip limit title')} </Th>
<Th> {t('common:common.Expired Time')} </Th>
</>
)}
<Th>{t('common:common.Last use time')} </Th>
<Th> </Th>
</Tr>
</Thead>
<Tbody>
{shareChatList.map((item) => (
<Tr key={item._id}>
<Td>{item.name} </Td>
<Td>
{Math.round(item.usagePoints)}
{feConfigs?.isPlus
? `${
item.limit?.maxUsagePoints && item.limit.maxUsagePoints > -1
? ` / ${item.limit.maxUsagePoints}`
: ` / ${t('common:common.Unlimited')}`
}`
: ''}
</Td>
{feConfigs?.isPlus && (
<>
<Td>{item?.limit?.QPM || '-'} </Td>
<Td>
{item?.limit?.expiredTime
? dayjs(item.limit?.expiredTime).format('YYYY/MM/DD\nHH:mm')
: '-'}
</Td>
</>
)}
<Td>
{item.lastTime
? t(formatTimeToChatTime(item.lastTime) as any)
: t('common:common.Un used')}
</Td>
<Td display={'flex'} alignItems={'center'}>
<Button
onClick={() => {
setShowShareLink(`${baseUrl}/support/outLink/wecom/${item.shareId}`);
openShowShareLinkModal();
}}
size={'sm'}
mr={3}
variant={'whitePrimary'}
>
{t('publish:request_address')}
</Button>
<MyMenu
Button={
<MyIcon
name={'more'}
_hover={{ bg: 'myGray.100' }}
cursor={'pointer'}
borderRadius={'md'}
w={'14px'}
p={2}
/>
}
menuList={[
{
children: [
{
label: t('common:common.Edit'),
icon: 'edit',
onClick: () => {
setEditWecomData({
_id: item._id,
name: item.name,
limit: item.limit,
app: item.app,
responseDetail: item.responseDetail,
defaultResponse: item.defaultResponse,
immediateResponse: item.immediateResponse
});
setIsEdit(true);
}
},
{
label: t('common:common.Delete'),
icon: 'delete',
onClick: async () => {
setIsLoading(true);
try {
await delShareChatById(item._id);
refetchShareChatList();
} catch (error) {
console.log(error);
}
setIsLoading(false);
}
}
]
}
]}
/>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
{editWecomData && (
<WecomEditModal
appId={appId}
defaultData={editWecomData}
onCreate={() => Promise.all([refetchShareChatList(), setEditWecomData(undefined)])}
onEdit={() => Promise.all([refetchShareChatList(), setEditWecomData(undefined)])}
onClose={() => setEditWecomData(undefined)}
isEdit={isEdit}
/>
)}
{shareChatList.length === 0 && !isFetching && (
<EmptyTip text={t('common:core.app.share.Not share link')}> </EmptyTip>
)}
<Loading loading={isFetching} fixed={false} />
{showShareLinkModalOpen && (
<ShowShareLinkModal
shareLink={showShareLink ?? ''}
onClose={closeShowShareLinkModal}
img="/imgs/outlink/wecom-copylink-instruction.png"
/>
)}
</Box>
);
};
export default React.memo(Wecom);

View File

@@ -0,0 +1,87 @@
import React from 'react';
import { Box, Flex, Input } from '@chakra-ui/react';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import dayjs from 'dayjs';
import { useTranslation } from 'react-i18next';
import { UseFormRegister, UseFormSetValue } from 'react-hook-form';
import { OutLinkEditType } from '@fastgpt/global/support/outLink/type';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
function BasicInfo({
register,
setValue,
defaultData
}: {
register: UseFormRegister<OutLinkEditType<any>>;
setValue: UseFormSetValue<OutLinkEditType<any>>;
defaultData: OutLinkEditType<any>;
}) {
const { t } = useTranslation();
return (
<Flex flexDirection="column" gap={6}>
<Box color="myGray.600">{t('publish:basic_info')}</Box>
<Flex alignItems={'center'}>
<FormLabel required flex={'0 0 6.25rem'}>
{t('common:Name')}
</FormLabel>
<Input
placeholder={t('publish:publish_name')}
maxLength={20}
{...register('name', {
required: t('common:common.name_is_empty')
})}
/>
</Flex>
<Flex alignItems={'center'}>
<FormLabel flex={'0 0 6.25rem'} alignItems={'center'}>
QPM
<QuestionTip ml={1} label={t('publish:qpm_tips')}></QuestionTip>
</FormLabel>
<Input
max={1000}
{...register('limit.QPM', {
min: 0,
max: 1000,
valueAsNumber: true,
required: t('publish:qpm_is_empty')
})}
/>
</Flex>
<Flex alignItems={'center'}>
<FormLabel flex={'0 0 6.25rem'} alignItems={'center'}>
{t('common:support.outlink.Max usage points')}
<QuestionTip
ml={1}
label={t('common:support.outlink.Max usage points tip')}
></QuestionTip>
</FormLabel>
<Input
{...register('limit.maxUsagePoints', {
min: -1,
max: 10000000,
valueAsNumber: true,
required: true
})}
/>
</Flex>
<Flex alignItems={'center'}>
<FormLabel flex={'0 0 6.25rem'} alignItems={'center'}>
{t('common:common.Expired Time')}
</FormLabel>
<Input
type="datetime-local"
defaultValue={
defaultData.limit?.expiredTime
? dayjs(defaultData.limit?.expiredTime).format('YYYY-MM-DDTHH:mm')
: ''
}
onChange={(e) => {
setValue('limit.expiredTime', new Date(e.target.value));
}}
/>
</Flex>
</Flex>
);
}
export default BasicInfo;

View File

@@ -0,0 +1,50 @@
import { useCopyData } from '@/web/common/hooks/useCopyData';
import { Box, Image, Flex, ModalBody } from '@chakra-ui/react';
import MyModal from '@fastgpt/web/components/common/MyModal';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useTranslation } from 'react-i18next';
export type ShowShareLinkModalProps = {
shareLink: string;
onClose: () => void;
img: string;
};
function ShowShareLinkModal({ shareLink, onClose, img }: ShowShareLinkModalProps) {
const { copyData } = useCopyData();
const { t } = useTranslation();
return (
<MyModal onClose={onClose} title={t('publish:show_share_link_modal_title')}>
<ModalBody>
<Box borderRadius={'md'} bg={'myGray.100'} overflow={'hidden'} fontSize={'sm'}>
<Flex
p={3}
bg={'myWhite.500'}
border="base"
borderTopLeftRadius={'md'}
borderTopRightRadius={'md'}
>
<Box flex={1}>{t('publish:copy_link_hint')}</Box>
<MyIcon
name={'copy'}
w={'16px'}
color={'myGray.600'}
cursor={'pointer'}
_hover={{ color: 'primary.500' }}
onClick={() => copyData(shareLink)}
/>
</Flex>
<Box whiteSpace={'pre'} p={3} overflowX={'auto'}>
{shareLink}
</Box>
</Box>
<Box mt="4" borderRadius="0.5rem" border="1px" borderStyle="solid" borderColor="myGray.200">
<Image src={img} borderRadius="0.5rem" alt="" />
</Box>
</ModalBody>
</MyModal>
);
}
export default ShowShareLinkModal;

View File

@@ -1,5 +1,5 @@
import React, { useRef, useState } from 'react'; import React, { useRef, useState } from 'react';
import { Box, Flex, useTheme } from '@chakra-ui/react'; import { Box, Flex } from '@chakra-ui/react';
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant'; import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
@@ -10,14 +10,18 @@ import { useTranslation } from 'next-i18next';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
import { AppContext } from '../context'; import { AppContext } from '../context';
import { cardStyles } from '../constants'; import { cardStyles } from '../constants';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useToast } from '@fastgpt/web/hooks/useToast';
import Link from './Link'; const Link = dynamic(() => import('./Link'));
const API = dynamic(() => import('./API')); const API = dynamic(() => import('./API'));
const FeiShu = dynamic(() => import('./FeiShu')); const FeiShu = dynamic(() => import('./FeiShu'));
const Wecom = dynamic(() => import('./Wecom'));
const OutLink = () => { const OutLink = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const theme = useTheme(); const { feConfigs } = useSystemStore();
const { toast } = useToast();
const appId = useContextSelector(AppContext, (v) => v.appId); const appId = useContextSelector(AppContext, (v) => v.appId);
@@ -26,33 +30,65 @@ const OutLink = () => {
icon: '/imgs/modal/shareFill.svg', icon: '/imgs/modal/shareFill.svg',
title: t('common:core.app.Share link'), title: t('common:core.app.Share link'),
desc: t('common:core.app.Share link desc'), desc: t('common:core.app.Share link desc'),
value: PublishChannelEnum.share value: PublishChannelEnum.share,
isProFn: false
}, },
{ {
icon: 'support/outlink/apikeyFill', icon: 'support/outlink/apikeyFill',
title: t('common:core.app.Api request'), title: t('common:core.app.Api request'),
desc: t('common:core.app.Api request desc'), desc: t('common:core.app.Api request desc'),
value: PublishChannelEnum.apikey value: PublishChannelEnum.apikey,
isProFn: false
},
{
icon: 'core/app/publish/lark',
title: t('publish:feishu_bot'),
desc: t('publish:feishu_bot_desc'),
value: PublishChannelEnum.feishu,
isProFn: true
},
{
icon: 'core/app/publish/wecom',
title: t('publish:wecom.bot'),
desc: t('publish:wecom.bot_desc'),
value: PublishChannelEnum.wecom,
isProFn: true
} }
// {
// icon: 'core/app/publish/lark',
// title: t('common:core.app.publish.Fei shu bot'),
// desc: t('common:core.app.publish.Fei Shu Bot Desc'),
// value: PublishChannelEnum.feishu
// }
]); ]);
const [linkType, setLinkType] = useState<PublishChannelEnum>(PublishChannelEnum.share); const [linkType, setLinkType] = useState<PublishChannelEnum>(PublishChannelEnum.share);
return ( return (
<> <Box
display={['block', 'flex']}
overflowY={'auto'}
overflowX={'hidden'}
h={'100%'}
flexDirection={'column'}
>
<Box {...cardStyles} boxShadow={2} px={[4, 8]} py={[4, 6]}> <Box {...cardStyles} boxShadow={2} px={[4, 8]} py={[4, 6]}>
<MyRadio <MyRadio
gridTemplateColumns={['repeat(1,1fr)', 'repeat(auto-fill, minmax(0, 400px))']} gridTemplateColumns={[
'repeat(1,1fr)',
'repeat(2, 1fr)',
'repeat(3, 1fr)',
'repeat(3, 1fr)',
'repeat(4, 1fr)'
]}
iconSize={'20px'} iconSize={'20px'}
list={publishList.current} list={publishList.current}
value={linkType} value={linkType}
onChange={(e) => setLinkType(e as PublishChannelEnum)} onChange={(e) => {
const config = publishList.current.find((v) => v.value === e)!;
if (!feConfigs.isPlus && config.isProFn) {
toast({
status: 'warning',
title: t('common:common.system.Commercial version function')
});
} else {
setLinkType(e as PublishChannelEnum);
}
}}
/> />
</Box> </Box>
@@ -63,15 +99,16 @@ const OutLink = () => {
mt={4} mt={4}
px={[4, 8]} px={[4, 8]}
py={[4, 6]} py={[4, 6]}
flex={'1 0 0'} flex={1}
> >
{linkType === PublishChannelEnum.share && ( {linkType === PublishChannelEnum.share && (
<Link appId={appId} type={PublishChannelEnum.share} /> <Link appId={appId} type={PublishChannelEnum.share} />
)} )}
{linkType === PublishChannelEnum.apikey && <API appId={appId} />} {linkType === PublishChannelEnum.apikey && <API appId={appId} />}
{linkType === PublishChannelEnum.feishu && <FeiShu appId={appId} />} {linkType === PublishChannelEnum.feishu && <FeiShu appId={appId} />}
{linkType === PublishChannelEnum.wecom && <Wecom appId={appId} />}
</Flex> </Flex>
</> </Box>
); );
}; };

View File

@@ -6,7 +6,7 @@ import Edit from './Edit';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
import { AppContext, TabEnum } from '../context'; import { AppContext, TabEnum } from '../context';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { Flex } from '@chakra-ui/react'; import { Box, Flex } from '@chakra-ui/react';
import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload'; import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
@@ -29,10 +29,10 @@ const SimpleEdit = () => {
{currentTab === TabEnum.appEdit ? ( {currentTab === TabEnum.appEdit ? (
<Edit appForm={appForm} setAppForm={setAppForm} /> <Edit appForm={appForm} setAppForm={setAppForm} />
) : ( ) : (
<Flex h={'100%'} flexDirection={'column'} mt={4}> <Box flex={'1 0 0'} h={0} mt={4}>
{currentTab === TabEnum.publish && <PublishChannel />} {currentTab === TabEnum.publish && <PublishChannel />}
{currentTab === TabEnum.logs && <Logs />} {currentTab === TabEnum.logs && <Logs />}
</Flex> </Box>
)} )}
</Flex> </Flex>
); );

View File

@@ -1,12 +1,12 @@
import { getSystemPlugins } from '@/service/core/app/plugin';
import { initSystemConfig } from '.'; import { initSystemConfig } from '.';
import { createDatasetTrainingMongoWatch } from '@/service/core/dataset/training/utils'; import { createDatasetTrainingMongoWatch } from '@/service/core/dataset/training/utils';
import { getSystemPluginTemplates } from '@fastgpt/plugins/register';
import { MongoSystemConfigs } from '@fastgpt/service/common/system/config/schema'; import { MongoSystemConfigs } from '@fastgpt/service/common/system/config/schema';
import { MongoSystemPluginSchema } from '@fastgpt/service/core/app/plugin/systemPluginSchema'; import { MongoSystemPluginSchema } from '@fastgpt/service/core/app/plugin/systemPluginSchema';
export const startMongoWatch = async () => { export const startMongoWatch = async () => {
reloadConfigWatch(); reloadConfigWatch();
refetchSystemPlugin(); refetchSystemPlugins();
createDatasetTrainingMongoWatch(); createDatasetTrainingMongoWatch();
}; };
@@ -23,12 +23,12 @@ const reloadConfigWatch = () => {
}); });
}; };
const refetchSystemPlugin = () => { const refetchSystemPlugins = () => {
const changeStream = MongoSystemPluginSchema.watch(); const changeStream = MongoSystemPluginSchema.watch();
changeStream.on('change', async (change) => { changeStream.on('change', async (change) => {
try { try {
getSystemPluginTemplates(true); getSystemPlugins(true);
} catch (error) {} } catch (error) {}
}); });
}; };

View File

@@ -0,0 +1,69 @@
import { FastGPTProUrl, isProduction } from '@fastgpt/service/common/system/constants';
import { cloneDeep } from 'lodash';
import { getCommunityCb, getCommunityPlugins } from '@fastgpt/plugins/register';
import { GET, POST } from '@fastgpt/service/common/api/plusRequest';
import { SystemPluginTemplateItemType } from '@fastgpt/global/core/workflow/type';
import { addLog } from '@fastgpt/service/common/system/log';
import { SystemPluginResponseType } from '@fastgpt/plugins/type';
/* Get plugins */
const getCommercialPlugins = () => {
return GET<SystemPluginTemplateItemType[]>('/core/app/plugin/getSystemPlugins');
};
export const getSystemPlugins = async (refresh = false) => {
if (isProduction && global.systemPlugins && !refresh) return cloneDeep(global.systemPlugins);
try {
if (!global.systemPlugins) {
global.systemPlugins = [];
}
global.systemPlugins = FastGPTProUrl ? await getCommercialPlugins() : getCommunityPlugins();
addLog.info(`Load system plugin successfully: ${global.systemPlugins.length}`);
return cloneDeep(global.systemPlugins);
} catch (error) {
//@ts-ignore
global.systemPlugins = undefined;
return Promise.reject(error);
}
};
/* Get plugin callback */
const getCommercialCb = async () => {
const plugins = await getSystemPlugins();
const result = plugins.map((plugin) => {
const name = plugin.id.split('-')[1];
return {
name,
cb: (e: any) =>
POST<Record<string, any>>('/core/app/plugin/run', {
pluginName: name,
data: e
})
};
});
return result.reduce<Record<string, (e: any) => SystemPluginResponseType>>(
(acc, { name, cb }) => {
acc[name] = cb;
return acc;
},
{}
);
};
export const getSystemPluginCb = async () => {
if (isProduction && global.systemPluginCb) return global.systemPluginCb;
try {
global.systemPluginCb = {};
global.systemPluginCb = FastGPTProUrl ? await getCommercialCb() : await getCommunityCb();
return global.systemPluginCb;
} catch (error) {
//@ts-ignore
global.systemPluginCb = undefined;
return Promise.reject(error);
}
};

View File

@@ -12,6 +12,7 @@ import { startMongoWatch } from './common/system/volumnMongoWatch';
import { startTrainingQueue } from './core/dataset/training/utils'; import { startTrainingQueue } from './core/dataset/training/utils';
import { systemStartCb } from '@fastgpt/service/common/system/tools'; import { systemStartCb } from '@fastgpt/service/common/system/tools';
import { addLog } from '@fastgpt/service/common/system/log'; import { addLog } from '@fastgpt/service/common/system/log';
import { getSystemPluginCb } from './core/app/plugin';
/** /**
* This function is equivalent to the entry to the service * This function is equivalent to the entry to the service
@@ -31,7 +32,7 @@ export function connectToDatabase() {
systemStartCb(); systemStartCb();
//init system configinit vector databaseinit root user //init system configinit vector databaseinit root user
await Promise.all([getInitConfig(), initVectorStore(), initRootUser()]); await Promise.all([getInitConfig(), getSystemPluginCb(), initVectorStore(), initRootUser()]);
startMongoWatch(); startMongoWatch();
// cron // cron

View File

@@ -1,6 +1,6 @@
import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { AppDetailType } from '@fastgpt/global/core/app/type.d'; import { AppDetailType } from '@fastgpt/global/core/app/type.d';
import type { FeishuType, OutLinkEditType } from '@fastgpt/global/support/outLink/type.d'; import type { FeishuAppType, OutLinkEditType } from '@fastgpt/global/support/outLink/type.d';
import { AppPermission } from '@fastgpt/global/support/permission/app/controller'; import { AppPermission } from '@fastgpt/global/support/permission/app/controller';
import { NullPermission } from '@fastgpt/global/support/permission/constant'; import { NullPermission } from '@fastgpt/global/support/permission/constant';
import { i18nT } from '@fastgpt/web/i18n/utils'; import { i18nT } from '@fastgpt/web/i18n/utils';
@@ -32,30 +32,12 @@ export const defaultOutLinkForm: OutLinkEditType = {
} }
}; };
// export const defaultWecomOutLinkForm: OutLinkConfigEditType = { export const defaultFeishuOutLinkForm: OutLinkEditType<FeishuAppType> = {
// name: '',
// wecomConfig: {
// ReplyLimit: false,
// defaultResponse: '',
// immediateResponse: false,
// WXWORK_TOKEN: '',
// WXWORK_AESKEY: '',
// WXWORK_SECRET: '',
// WXWORD_ID: ''
// },
// limit: {
// QPM: 100,
// maxUsagePoints: -1
// }
// };
export const defaultFeishuOutLinkForm: OutLinkEditType<FeishuType> = {
name: '', name: '',
limit: { limit: {
QPM: 100, QPM: 100,
maxUsagePoints: -1 maxUsagePoints: -1
}, }
responseDetail: false
}; };
export enum TTSTypeEnum { export enum TTSTypeEnum {

View File

@@ -1,5 +1,9 @@
import { GET, POST, DELETE } from '@/web/common/api/request'; import { GET, POST, DELETE } from '@/web/common/api/request';
import type { OutLinkEditType, OutLinkSchema } from '@fastgpt/global/support/outLink/type.d'; import type {
OutlinkAppType,
OutLinkEditType,
OutLinkSchema
} from '@fastgpt/global/support/outLink/type.d';
// create a shareChat // create a shareChat
export function createShareChat<T>( export function createShareChat<T>(
@@ -15,7 +19,10 @@ export const putShareChat = (data: OutLinkEditType) =>
POST<string>(`/support/outLink/update`, data); POST<string>(`/support/outLink/update`, data);
// get shareChat // get shareChat
export function getShareChatList<T>(data: { appId: string; type: OutLinkSchema<T>['type'] }) { export function getShareChatList<T extends OutlinkAppType>(data: {
appId: string;
type: OutLinkSchema<T>['type'];
}) {
return GET<OutLinkSchema<T>[]>(`/support/outLink/list`, data); return GET<OutLinkSchema<T>[]>(`/support/outLink/list`, data);
} }
@@ -25,7 +32,7 @@ export function delShareChatById(id: string) {
} }
// update a shareChat // update a shareChat
export function updateShareChat<T>(data: OutLinkEditType<T>) { export function updateShareChat<T extends OutlinkAppType>(data: OutLinkEditType<T>) {
return POST<string>(`/support/outLink/update`, data); return POST<string>(`/support/outLink/update`, data);
} }