Commit 61347d9a by Archer Committed by GitHub

4.8.10 test (#2401)

* perf: i18n

* perf: i18n and img tip

* perf: readme

* perf: hide tool ai response

* fix: copy app

* fix: parse image url regx

* perf: folder collection forbid update
parent 5bf0dd0e

166 KB | W: | H:

259 KB | W: | H:

.github/imgs/intro1.png
.github/imgs/intro1.png
.github/imgs/intro1.png
.github/imgs/intro1.png
  • 2-up
  • Swipe
  • Onion skin

246 KB | W: | H:

371 KB | W: | H:

.github/imgs/intro2.png
.github/imgs/intro2.png
.github/imgs/intro2.png
.github/imgs/intro2.png
  • 2-up
  • Swipe
  • Onion skin

250 KB | W: | H:

259 KB | W: | H:

.github/imgs/intro3.png
.github/imgs/intro3.png
.github/imgs/intro3.png
.github/imgs/intro3.png
  • 2-up
  • Swipe
  • Onion skin

182 KB | W: | H:

228 KB | W: | H:

.github/imgs/intro4.png
.github/imgs/intro4.png
.github/imgs/intro4.png
.github/imgs/intro4.png
  • 2-up
  • Swipe
  • Onion skin
......@@ -24,6 +24,9 @@ weight: 816
3. 新增 - 用户选择节点(Debug 模式暂未支持)
4. 商业版新增 - 飞书机器人接入
5. 商业版新增 - 公众号接入接入
6. 修复 - Prompt 模式调用工具,stream=false 模式下,会携带 0: 开头标记。
7. 修复 - 对话日志鉴权问题:仅为 APP 管理员的用户,无法查看对话日志详情。
8. 修复 - 选择 Milvus 部署时,无法导出知识库。
6. 优化 - 知识库集合禁用,目录禁用会递归修改其下所有 children 的禁用状态。
7. 修复 - Prompt 模式调用工具,stream=false 模式下,会携带 0: 开头标记。
8. 修复 - 对话日志鉴权问题:仅为 APP 管理员的用户,无法查看对话日志详情。
9. 修复 - 选择 Milvus 部署时,无法导出知识库。
10. 修复 - 创建 APP 副本,无法复制系统配置。
11. 修复 - 图片识别模式下,自动解析图片链接正则不够严谨问题。
---
title: "教程 - 接入飞书机器人"
description: "FastGPT 接入飞书机器人"
title: "接入飞书机器人教程"
description: "FastGPT 接入飞书机器人教程"
icon: "chat"
draft: false
toc: true
weight: 507
---
## 1. 申请飞书应用
开一个免费的测试企业更方便进行调试。
......
import { i18nT } from '../../../web/i18n/utils';
export enum FlowNodeTemplateTypeEnum {
systemInput = 'systemInput',
ai = 'ai',
......@@ -181,23 +183,23 @@ export enum VariableInputEnum {
export const variableMap = {
[VariableInputEnum.input]: {
icon: 'core/app/variable/input',
title: 'core.module.variable.input type',
title: i18nT('common:core.module.variable.input type'),
desc: ''
},
[VariableInputEnum.textarea]: {
icon: 'core/app/variable/textarea',
title: 'core.module.variable.textarea type',
desc: '允许用户最多输入4000字的对话框。'
title: i18nT('common:core.module.variable.textarea type'),
desc: i18nT('app:variable.textarea_type_desc')
},
[VariableInputEnum.select]: {
icon: 'core/app/variable/select',
title: 'core.module.variable.select type',
title: i18nT('common:core.module.variable.select type'),
desc: ''
},
[VariableInputEnum.custom]: {
icon: 'core/app/variable/external',
title: 'core.module.variable.Custom type',
desc: '可以定义一个无需用户填写的全局变量。\n该变量的值可以来自于 API 接口,分享链接的 Query 或通过【变量更新】模块进行赋值。'
title: i18nT('common:core.module.variable.Custom type'),
desc: i18nT('app:variable.select type_desc')
}
};
......
......@@ -118,7 +118,8 @@ export const loadRequestMessages = async ({
}
// 正则表达式匹配图片URL
const imageRegex = /(https?:\/\/.*\.(?:png|jpe?g|gif|webp|bmp|tiff?|svg|ico|heic|avif))/i;
const imageRegex =
/(https?:\/\/[^\s/$.?#].[^\s]*\.(?:png|jpe?g|gif|webp|bmp|tiff?|svg|ico|heic|avif))/i;
const result: { type: 'text' | 'image'; value: string }[] = [];
let lastIndex = 0;
......
......@@ -110,10 +110,15 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
const requestBody = await (() => {
if (!httpJsonBody) return {};
try {
// Replace all variables in the string body
httpJsonBody = replaceVariable(httpJsonBody, allVariables);
// Text body, return directly
if (headers['Content-Type']?.includes('text/plain')) {
return httpJsonBody?.replaceAll(UNDEFINED_SIGN, 'null');
}
// Json body, parse and return
const jsonParse = JSON.parse(httpJsonBody);
const removeSignJson = removeUndefinedSign(jsonParse);
return removeSignJson;
......
......@@ -35,7 +35,7 @@ const MyModal = ({
closeOnOverlayClick = true,
...props
}: MyModalProps) => {
const isPc = useSystem();
const { isPc } = useSystem();
return (
<Modal
......
......@@ -125,6 +125,10 @@
},
"upload_file_max_amount": "Max files",
"upload_file_max_amount_tip": "1. The maximum number of files to be uploaded at a time.\n2. The maximum number of files that can be remembered in the dialog window: Files in the history are automatically retrieved for each round of conversation, and files that are out of range are forgotten.",
"variable": {
"select type_desc": "You can define a global variable that does not need to be filled in by the user.\n\nThe value of this variable can come from the API interface, the Query of the shared link, or be assigned through the [Variable Update] module.",
"textarea_type_desc": "A dialog box that allows the user to enter up to 4000 words."
},
"version": {
"Revert success": "Revert success"
},
......
......@@ -600,8 +600,7 @@
"success": "Start syncing"
}
},
"training": {
}
"training": {}
},
"data": {
"Auxiliary Data": "Auxiliary data",
......@@ -1270,7 +1269,9 @@
"Sub plan": "Subscription plan",
"Sub plan tip": "Free use of {{title}} or upgrade to a higher plan",
"Team plan and usage": "Plan and usage",
"web_site_sync": "Website sync",
"Training weight": "Training priority: {{weight}}",
"rerank": "Rerank",
"Update extra ai points": "Extra AI points",
"Update extra dataset size": "Extra storage",
"Upgrade plan": "Upgrade plan",
......
{
"app_key_tips": "These keys have the current application identification, refer to the document for specific use ",
"create_api_key": "Create new Key",
"create_link": "Create link",
"default_response": "Default Response",
"edit_api_key": "Edit Key information",
"edit_link": "Edit",
"feishu_name": "Lark",
"link_name": "Name of the share link",
"qpm_tips": "How many times per minute can each IP ask at most",
"qpm_is_empty": "QPM cannot be empty",
"app_key_tips": "These keys have the current application identification, refer to the document for specific use ",
"key_alias": "key alias, for display only ",
"key_tips": "You can use the API Key to access certain interfaces (you can't access the app, you need to use the API key within the app to access the app)",
"link_name": "Name of the share link",
"official_account": {
"params": "Wechat params"
},
"qpm_is_empty": "QPM cannot be empty",
"qpm_tips": "How many times per minute can each IP ask at most",
"token_auth": "Token authentication",
"token_auth_tips": "Identity verification server address, if this value is filled, a request will be sent to the specified server before each conversation to perform identity verification",
"token_auth_use_cases": "View usage instructions for identity verification"
}
\ No newline at end of file
}
......@@ -127,6 +127,10 @@
},
"upload_file_max_amount": "最大文件数量",
"upload_file_max_amount_tip": "1.单次上传文件的最大数量。\n2.对话窗口记忆的最大文件数量:每轮对话会自动获取历史中的文件,超出范围的文件会被遗忘。",
"variable": {
"select type_desc": "可以定义一个无需用户填写的全局变量。\n该变量的值可以来自于 API 接口,分享链接的 Query 或通过【变量更新】模块进行赋值。",
"textarea_type_desc": "允许用户最多输入4000字的对话框。"
},
"version": {
"Revert success": "回滚成功"
},
......
......@@ -610,8 +610,7 @@
"success": "开始同步"
}
},
"training": {
}
"training": {}
},
"data": {
"Auxiliary Data": "辅助数据",
......@@ -1286,6 +1285,8 @@
"Sub plan tip": "免费使用 {{title}} 或升级更高的套餐",
"Team plan and usage": "套餐与用量",
"Training weight": "训练优先级:{{weight}}",
"web_site_sync": "Web 站点同步",
"rerank": "检索结果重排",
"Update extra ai points": "额外 AI 积分",
"Update extra dataset size": "额外存储量",
"Upgrade plan": "升级套餐",
......
{
"publish_name": "名称",
"app_key_tips": "这些 key 已有当前应用标识,具体使用可参考文档",
"basic_info": "基本信息",
"copy_link_hint": "将下面链接复制到指定位置",
"create_api_key": "创建新 key",
"create_link": "创建链接",
"default_response": "默认回复",
"edit_api_key": "编辑 key 信息",
"edit_feishu_bot": "编辑飞书机器人",
"edit_link": "编辑",
"feishu_api": "飞书接口",
"feishu_bot": "飞书机器人",
"feishu_bot_desc": "通过 API 直接接入飞书机器人",
"feishu_name": "飞书",
"new_feishu_bot": "新增飞书机器人",
"edit_feishu_bot": "编辑飞书机器人",
"link_name": "分享链接的名字",
"qpm_tips": "每个 IP 每分钟最多提问多少次",
"qpm_is_empty": "QPM 不能为空",
"app_key_tips": "这些 key 已有当前应用标识,具体使用可参考文档",
"key_alias": "key 的别名,仅用于展示",
"key_tips": "你可以使用 API 秘钥访问一些特定的接口(无法访问应用,访问应用需使用应用内的 API key)",
"link_name": "分享链接的名字",
"new_feishu_bot": "新增飞书机器人",
"official_account": {
"api": "微信公众号 API",
"create_modal_title": "创建微信公众号接入",
"desc": "通过 API 直接接入微信公众号",
"edit_modal_title": "编辑微信公众号接入",
"name": "微信公众号接入",
"params": "微信公众号参数"
},
"publish_name": "名称",
"qpm_is_empty": "QPM 不能为空",
"qpm_tips": "每个 IP 每分钟最多提问多少次",
"request_address": "请求地址",
"show_share_link_modal_title": "开始使用",
"token_auth": "身份验证",
"token_auth_tips": "身份校验服务器地址,如填写该值,每次对话前都会向指定服务器发送一个请求,进行身份校验",
"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": "发布到企业微信机器人",
"api": "企微 API",
"bot": "企业微信机器人",
"bot_desc": "通过 API 直接接入企业微信机器人",
"edit_modal_title": "编辑企微机器人",
"create_modal_title": "创建企微机器人",
"api": "企微 API"
},
"official_account": {
"name": "微信公众号接入",
"desc": "通过 API 直接接入微信公众号",
"edit_modal_title": "编辑微信公众号接入",
"create_modal_title": "创建微信公众号接入",
"api": "微信公众号 API"
"edit_modal_title": "编辑企微机器人",
"title": "发布到企业微信机器人"
}
}
<svg viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="36" height="36" rx="6" fill="#EBFAF8"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.7766 8.13002C21.1566 8.34961 21.4862 8.65917 22.1455 9.27829L25.0534 12.009C25.7721 12.6839 26.1315 13.0214 26.3873 13.4225C26.6047 13.7635 26.7663 14.1369 26.866 14.5288C26.9833 14.9899 26.9833 15.4829 26.9833 16.4688V22.2622C26.9833 24.4263 26.9833 25.5083 26.5555 26.332C26.195 27.026 25.6291 27.5919 24.935 27.9524C24.1114 28.3802 23.0294 28.3802 20.8653 28.3802H15.1351C12.971 28.3802 11.889 28.3802 11.0654 27.9524C10.3713 27.5919 9.80545 27.026 9.44492 26.332C9.01709 25.5083 9.01709 24.4263 9.01709 22.2622V13.7381C9.01709 11.5741 9.01709 10.492 9.44492 9.66841C9.80545 8.97437 10.3713 8.40847 11.0654 8.04795C11.889 7.62012 12.971 7.62012 15.1351 7.62012H17.9574C18.8619 7.62012 19.3141 7.62012 19.7414 7.72015C20.1047 7.80521 20.4535 7.94331 20.7766 8.13002ZM20.3331 10.6528C19.7398 10.084 19.4432 9.79959 19.1886 9.78928C18.9864 9.78109 18.7913 9.86434 18.6572 10.016C18.4885 10.2069 18.4885 10.6178 18.4885 11.4397V12.378C18.4885 13.5803 18.4885 14.1814 18.7225 14.6405C18.9283 15.0445 19.2566 15.3729 19.6606 15.5787C20.1197 15.8126 20.7209 15.8126 21.9231 15.8126H23.0025C23.8677 15.8126 24.3003 15.8126 24.4927 15.638C24.6453 15.4995 24.7261 15.2986 24.7117 15.093C24.6937 14.8338 24.3814 14.5344 23.7569 13.9356L20.3331 10.6528ZM13.005 19.6764C13.005 19.1241 13.4527 18.6764 14.005 18.6764H21.9998C22.5521 18.6764 22.9998 19.1241 22.9998 19.6764C22.9998 20.2287 22.5521 20.6764 21.9998 20.6764H14.005C13.4527 20.6764 13.005 20.2287 13.005 19.6764ZM12.9933 23.4246C12.9933 22.8723 13.441 22.4246 13.9933 22.4246H19.9949C20.5472 22.4246 20.9949 22.8723 20.9949 23.4246C20.9949 23.9769 20.5472 24.4246 19.9949 24.4246H13.9933C13.441 24.4246 12.9933 23.9769 12.9933 23.4246Z" fill="#00A9A6"/>
</svg>
import React from 'react';
import { Box, Flex, useTheme, Grid, type GridProps, theme, Image, Radio } from '@chakra-ui/react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { Box, Flex, useTheme, Grid, type GridProps, Radio } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { useToast } from '@fastgpt/web/hooks/useToast';
import Avatar from '@fastgpt/web/components/common/Avatar';
// @ts-ignore
interface Props extends GridProps {
......@@ -53,7 +53,8 @@ const MyRadio = ({
{...(value === item.value
? {
borderColor: 'primary.400',
bg: 'primary.50'
bg: 'primary.50',
color: 'primary.600'
}
: {
bg: 'myWhite.300',
......@@ -74,11 +75,7 @@ const MyRadio = ({
>
{!!item.icon && (
<>
{item.icon.startsWith('/') ? (
<Image src={item.icon} mr={'14px'} w={iconSize} alt={''} fill={'primary.600'} />
) : (
<MyIcon mr={'14px'} name={item.icon as any} w={iconSize} fill={'primary.600'} />
)}
<Avatar src={item.icon} w={iconSize} mr={'14px'} />
</>
)}
<Box pr={hiddenCircle ? 0 : 2} flex={'1 0 0'}>
......
......@@ -136,7 +136,7 @@ const AIChatSettingsModal = ({
{t('common:core.ai.Support tool')}
<QuestionTip ml={1} label={t('common:core.module.template.AI support tool tip')} />
</Box>
<Box flex={1} ml={'10px'}>
<Box flex={1}>
{selectedModel?.toolChoice || selectedModel?.functionCall
? t('common:common.support')
: t('common:common.not_support')}
......
......@@ -50,7 +50,7 @@ const ChatFunctionTip = ({ type }: { type: `${FnTypeEnum}` }) => {
imgUrl: '/imgs/app/welcome.svg'
},
[FnTypeEnum.file]: {
icon: '/imgs/app/welcome-icon.svg',
icon: '/imgs/app/fileinput.svg',
title: t('app:file_upload'),
desc: t('app:file_upload_tip'),
imgUrl: '/imgs/app/fileUploadPlaceholder.png'
......
......@@ -112,13 +112,13 @@ const StandardPlanContentList = ({
{!!planContent.permissionReRank && (
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box color={'myGray.600'}>{t('chat:rearrangement')}</Box>
<Box color={'myGray.600'}>{t('support.wallet.subscription.rerank')}</Box>
</Flex>
)}
{!!planContent.permissionWebsiteSync && (
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box color={'myGray.600'}>{t('chat:web_site_sync')}</Box>
<Box color={'myGray.600'}>{t('support.wallet.subscription.web_site_sync')}</Box>
</Flex>
)}
</Grid>
......
......@@ -39,6 +39,7 @@ async function handler(
type: app.type,
modules: app.modules,
edges: app.edges,
chatConfig: app.chatConfig,
teamId: app.teamId,
tmbId,
pluginData: app.pluginData
......
......@@ -5,6 +5,10 @@ import { NextAPI } from '@/service/middleware/entry';
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
import { ApiRequestProps } from '@fastgpt/service/type/next';
import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constants';
import { ClientSession } from '@fastgpt/service/common/mongo';
import { CollectionWithDatasetType } from '@fastgpt/global/core/dataset/type';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
export type UpdateDatasetCollectionParams = {
id: string;
......@@ -14,6 +18,52 @@ export type UpdateDatasetCollectionParams = {
forbid?: boolean;
};
// Set folder collection children forbid status
const updateFolderChildrenForbid = async ({
collection,
forbid,
session
}: {
collection: CollectionWithDatasetType;
forbid: boolean;
session: ClientSession;
}) => {
// 从 collection 作为 parent 进行递归查找,找到它所有 forbid 与它相同的 child
const find = async (parentId: string): Promise<string[]> => {
const children = await MongoDatasetCollection.find(
{
teamId: collection.teamId,
datasetId: collection.datasetId,
parentId
},
'_id',
{ session }
);
const idList = children.map((item) => String(item._id));
const IdChildren = (await Promise.all(idList.map(find))).flat();
return [...idList, ...IdChildren];
};
const allChildrenIdList = await find(collection._id);
await MongoDatasetCollection.updateMany(
{
_id: { $in: allChildrenIdList }
},
{
$set: {
forbid
}
},
{
session
}
);
};
async function handler(req: ApiRequestProps<UpdateDatasetCollectionParams>) {
const { id, parentId, name, tags, forbid } = req.body;
......@@ -22,7 +72,7 @@ async function handler(req: ApiRequestProps<UpdateDatasetCollectionParams>) {
}
// 凭证校验
await authDatasetCollection({
const { collection } = await authDatasetCollection({
req,
authToken: true,
authApiKey: true,
......@@ -30,15 +80,32 @@ async function handler(req: ApiRequestProps<UpdateDatasetCollectionParams>) {
per: WritePermissionVal
});
const updateFields: Record<string, any> = {
...(parentId !== undefined && { parentId: parentId || null }),
...(name && { name, updateTime: getCollectionUpdateTime({ name }) }),
...(tags && { tags }),
...(forbid !== undefined && { forbid })
};
await mongoSessionRun(async (session) => {
await MongoDatasetCollection.updateOne(
{
_id: id
},
{
$set: {
...(parentId !== undefined && { parentId: parentId || null }),
...(name && { name, updateTime: getCollectionUpdateTime({ name }) }),
...(tags && { tags }),
...(forbid !== undefined && { forbid })
}
},
{
session
}
);
await MongoDatasetCollection.findByIdAndUpdate(id, {
$set: updateFields
// Folder update forbid
if (collection.type === DatasetCollectionTypeEnum.folder && forbid !== undefined) {
await updateFolderChildrenForbid({
collection,
forbid,
session
});
}
});
}
......
......@@ -76,7 +76,7 @@ const FeiShuEditModal = ({
<Box color="myGray.600">{t('publish:feishu_api')}</Box>
{feConfigs?.docUrl && (
<Link
href={feConfigs.openAPIDocUrl || getDocPath('/docs/use-cases/feishu-bot')}
href={feConfigs.openAPIDocUrl || getDocPath('/docs/use-cases/feishu')}
target={'_blank'}
ml={2}
color={'primary.500'}
......
......@@ -75,7 +75,7 @@ const OffiAccountEditModal = ({
</Box>
<Flex p={8} minH={['auto', '400px']} flexDirection="column" gap={6}>
<Flex alignItems="center">
<Box color="myGray.600">{t('publish:official_account.api')}</Box>
<Box color="myGray.600">{t('publish:official_account.params')}</Box>
{feConfigs?.docUrl && (
<Link
href={feConfigs.openAPIDocUrl || getDocPath('/docs/use-cases/official_account')}
......
......@@ -36,8 +36,9 @@ const SelectAiModelRender = ({ item, inputs = [], nodeId }: RenderInputProps) =>
inputs.find((input) => input.key === NodeInputKeyEnum.aiChatMaxToken)?.value ?? 2048,
temperature:
inputs.find((input) => input.key === NodeInputKeyEnum.aiChatTemperature)?.value ?? 1,
isResponseAnswerText:
inputs.find((input) => input.key === NodeInputKeyEnum.aiChatIsResponseText)?.value ?? true,
isResponseAnswerText: inputs.find(
(input) => input.key === NodeInputKeyEnum.aiChatIsResponseText
)?.value,
aiChatVision:
inputs.find((input) => input.key === NodeInputKeyEnum.aiChatVision)?.value ?? true
}),
......
......@@ -95,6 +95,10 @@ export async function getServerSideProps(context: any) {
const datasetId = context?.query?.datasetId;
return {
props: { currentTab, datasetId, ...(await serviceSideProps(context, ['dataset', 'file'])) }
props: {
currentTab,
datasetId,
...(await serviceSideProps(context, ['dataset', 'file', 'user']))
}
};
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment