Compare commits

..

10 Commits

Author SHA1 Message Date
archer
361e255af8 fix: tool call 2025-06-03 11:04:17 +08:00
archer
2b888fb0fa perf: tip 2025-06-03 10:41:18 +08:00
archer
2128d306ad perf: per 2025-06-03 10:41:17 +08:00
archer
e59816aba4 fix: ux 2025-06-03 10:41:16 +08:00
archer
ded0383ac4 perf: gate 2025-06-03 10:41:15 +08:00
archer
81202c53a8 feat: gate permission 2025-06-03 10:41:15 +08:00
archer
e74ab643fe fix: ui 2025-06-03 10:41:14 +08:00
Theresa
3b0f0a8108 Gate new (#4928)
* feat: Add portal management related icons

* feat: Add portal configuration pages and related translations

* feat: Add new gateway configuration components and icons

- Introduced `ConfigButtons` component for save and share actions with new SVG icons.
- Added `CopyrightTable` and `HomeTable` components for managing copyright and home settings.
- Implemented `SectionHeader` for consistent section titles in the gateway configuration.
- Updated `FillRowTabs` to support new tabs for home and copyright configurations.
- Modified translations for gateway-related terms in English, Simplified Chinese, and Traditional Chinese.
- Removed unused gateway tab from `AccountContainer`.

* feat(gate): add API and schema for team gate configurations

- Introduced new TypeScript definitions for gate configuration parameters and data structures.
- Created constants for gate status and tools.
- Implemented MongoDB schema for team gate configurations.
- Added API functions for getting, creating, updating, and deleting team gate configurations and logos.
- Developed ShareGateModal component for sharing portal links and custom domains.
- Updated ConfigButtons component to handle saving configurations and opening the share modal.
- Added new icons for gate functionalities.
- Updated English and Chinese translations for gateway-related texts.

* feat(gate): refactor gate configuration API and remove unused logo handling

* feat(gate): enhance team gate configuration with new error handling and chat features

- Added new error codes to CommonErrEnum for method not allowed, system error, and unauthorized access.
- Updated datasetErr to include corresponding error messages for new error codes.
- Refactored API to support updating team gate configurations and copyright information.
- Introduced ChatInputBox component for chat functionalities, including file and image uploads.
- Enhanced HomeTable and CopyrightTable components to manage settings more effectively.
- Updated translations for new terms in English and Chinese.
- Improved layout and user experience in the gateway configuration pages.

* feat: Refactor gateway configuration and chat components

- Replaced direct API calls with Zustand store for gate configuration management.
- Introduced `useGateStore` for managing gate and copyright configurations.
- Updated `GatewayConfig` component to utilize the new store and remove redundant state management.
- Enhanced chat functionality in `application.tsx` and `index.tsx` to support gate model.
- Created new `application.tsx` for handling chat interactions with the gate application.
- Improved error handling and loading states in chat components.
- Added dynamic imports for better performance and code splitting.

* feat(gate): update GateSideBar to conditionally render recent apps based on chat page state

* fix(HomeTable): comment out unused FormControl for better readability

* feat(gate): enhance copyright configuration and file upload functionality

- Updated ConfigButtons to handle team avatar updates and save copyright configurations.
- Refactored CopyrightTable to integrate file selection for team avatars and improve form handling.
- Added animations and hover effects for better user experience during file uploads.
- Improved toast notifications for success and error handling in configuration processes.

* feat(gate): add gate service availability check and update translations

- Implemented gate service availability check in application and index pages, redirecting users if the service is unavailable.
- Added new translation keys for gate service status in English and Chinese.
- Refactored GateSideBar to improve rendering logic for recent apps based on gate status.

* feat(chat): add route check to ToolMenu for app detail visibility

- Implemented a check to prevent displaying app details when the current route starts with '/chat/gate'.
- Updated menu rendering logic to conditionally show app details based on the new route check.

* feat(constants): add 'gate' type to AppTypeEnum

* refactor: rename "Portal" to "Gate" across the application

- Updated schema to remove the slogan field from GateConfigSchema.
- Modified SVG icon dimensions for gateLight.svg.
- Changed localization keys and values from "Portal" to "Gate" in various JSON files.
- Added support for gate applications in the app creation and management logic.
- Enhanced ChatBox component to handle gate-specific routes and configurations.
- Updated ConfigButtons to manage gate configurations and intros.
- Adjusted ShareGateModal to generate correct gate URLs.
- Expanded emptyTemplates to include gate-specific templates and configurations.
- Refactored chatItemContext to include intro for gate applications.
- Updated useGateStore to initialize gate configurations with intros from existing gate applications.

* fix: add isResponseDetail prop to ChatItemContextProvider

* feat: refactor gate-related API and components for improved functionality

* feat: 添加工具选择和工具选择模态框组件

* refactor: Update GateConfig related types, remove unnecessary constants and enums

* feat: Enhance Gate configuration components and API integration

- Updated ConfigButtons and HomeTable to use string arrays for tools instead of GateTool type.
- Implemented batch plugin loading in HomeTable with error handling.
- Added ToolSelect and ToolSelectModal components for improved tool management.
- Introduced AppCard and ChatTest components for app detail editing.
- Enhanced Edit and EditForm components for better app configuration management.
- Added new API endpoint for batch plugin retrieval.
- Improved overall structure and styling for better user experience.

* fix: Update ChatBoxDataType to make intro optional in chatItemContext.tsx

* fix: Add isResponseDetail prop to ChatItemContextProvider in ChatPage component

* feat: Enhance ToolSelectModal and GatewayConfig with new functionalities

- Updated ToolSelectModal to handle tool selection and configuration, integrating new props for selected tools and chat configuration.
- Implemented loading and error handling for Gate applications in GatewayConfig, including a retry mechanism for fetching apps.
- Added selectedTool parameter to chat completions API to enable tool activation during chat.
- Refactored chat component to support app form context and debug mode for testing.
- Enhanced useGateStore to manage gate applications, including loading and updating functionalities.

* feat: Refactor GateSideBar to enhance recent apps display and add resource selection

* refactor: 移除门户删除确认功能

* feat: 更新 Chat 组件以使用 AppContextProvider 并修正 localAppDetail 的类型

* refactor: Remove the tool menu logic in the GateChatInput component to simplify the code structure

* refactor:
Remove the tool menu logic from the GateChatInput component to simplify the code structure

* feat:
Simplify the ShareGateModal component by removing unused states and logic

* fix: Update chatGray.svg to remove fill attributes for paths, improving SVG structure

* feat: Added new chat icons and updated internationalized text to support new chat features

* feat: Refactor chat components and introduce GateChat functionality

- Updated ChatHistorySlider to remove isGateRoute check for PC view.
- Added new GateChatHistorySlider component for handling chat history in gate context.
- Removed obsolete ChatPage component related to gate chat.
- Modified GateSideBar styles for improved UI consistency.
- Implemented new API endpoint for chat gate functionality.
- Refactored chat gate index page to utilize GateChatHistorySlider and streamline chat initialization.
- Cleaned up unused imports and code related to debugging and legacy chat handling.

* feat: Update GateSideBar styles for improved responsiveness and animation

- Adjusted width and padding for collapsed and expanded states.
- Enhanced transition effects for smoother UI interactions.
- Modified alignment and positioning of navigation items and user profile for better layout consistency.
- Improved accessibility by ensuring elements are centered when collapsed.

* feat: 添加新的聊天图标和更新分享门户组件样式以提升用户体验

* Refactor chat gate components and implement sidebar functionality

- Updated ChatGate component to use ChatItemContextProvider and ChatRecordContextProvider for better context management.
- Introduced FoldButton component for sidebar collapsing functionality.
- Created GateNavBar component to replace GateSideBar for improved navigation.
- Refactored GateSideBar to handle folding state and external triggers.
- Updated application and index pages to integrate new components and manage sidebar state.
- Enhanced useChatGate hook to include appDetail.intro.

* feat: Updated team structure, set default banner image and refactored LogoBox component to support diagonal background

* feat: Enhance GateNavBar with user popover functionality and logout feature

- Added user popover for displaying user information and logout option.
- Implemented mouse enter/leave handlers for popover visibility.
- Updated user profile section to include popover and improved layout.
- Modified index page to include 'account' in server-side props for better context management.

* feat: Add a bottom line statement in the ChatBox component to remind users that the content is generated by third-party AI

* feat: Update placeholder text in ChatBox and GateChatInput components for better user guidance

- Added internationalized placeholder text for user input in both English and Chinese.
- Updated ChatBox and GateChatInput components to utilize the new placeholder text from localization files.

* feat: Add upload icon and enhance ChatBox layout for better user experience

- Introduced a new upload icon in the Icon component for improved visual representation.
- Updated ChatBox layout to enhance responsiveness and user interaction, including adjustments to padding and structure.
- Added hover overlay effect for logo upload areas in the CopyrightTable component to improve user guidance.

* feat: Refactor Chat component to integrate GateSideBar and GateChatHistorySlider for improved layout and functionality

* refactor: Update imports to use 'import type' for type-only imports across multiple files

- Changed standard imports to type imports for better clarity and performance in TypeScript.
- Updated files in the global support, service, and app components to reflect this change.

* feat: Update localization strings and improve toast messages for better user feedback

- Added new success and failure messages for create, delete, save, and update actions in English and Chinese localization files.
- Refactored toast message keys in the ConfigButtons, CopyrightTable, HomeTable, ToolSelect, and other components to use updated localization keys for consistency.
- Enhanced user experience by providing clearer feedback on actions performed within the application.

* feat: Implement tag management functionality with CRUD operations

- Added new Tag schema and controller for managing application tags.
- Implemented API endpoints for creating, updating, deleting, and listing tags.
- Enhanced the App schema to include a reference to tags.
- Updated localization files for new tag-related messages.
- Improved user experience by providing clear feedback on tag operations.

* feat: Enhance ChatWelcome and GateNavBar components with conditional rendering for team avatars

- Updated ChatWelcome and GateNavBar components to conditionally render avatars based on availability.
- Improved layout by using Flex components for better alignment and responsiveness.
- Ensured consistent styling and structure for avatar display across both components.

* fix: Update parameter name in getBatchPlugins API for consistency

- Changed parameter name from 'id' to 'appId' in getChildAppPreviewNode function call for better clarity and consistency with the rest of the codebase.

* feat: Enhance ToolSelectModal with gate plugins integration and improved filtering

- Added useEffect to load plugins from gateStore and set them in state.
- Introduced ExtendedNodeTemplateItemType to include cost-related properties.
- Updated filtering logic for plugins based on search input.
- Refactored RenderList to display plugins with cost information and improved layout.

* refactor: Update ToolSelect and ToolSelectModal components for improved UI and state management

- Replaced Button with Flex component in ToolSelect for better styling and hover effects.
- Adjusted layout and styling in ToolSelect for a more responsive design.
- Removed ExtendedNodeTemplateItemType and reverted to NodeTemplateListItemType in ToolSelectModal for simplified state management.
- Updated RenderList to reflect changes in template type and maintain consistency.

* refactor: Replace Flex with Button for add tool action and enhance loading state UI

* feat: Enhance application tag management and localization support

- Added 'tags' property to AppListItemType for better tag management.
- Updated localization files for English and Chinese to include new tag-related strings.
- Implemented new AppTable component in the gateway for managing applications.
- Adjusted routes and components to support the new app management features.

* feat: Update localization and refactor chat components

- Added new localization strings for "enlarge" in English, Simplified Chinese, and Traditional Chinese.
- Refactored chat components to replace `quoteData` with `datasetCiteData` for improved state management.
- Enhanced `ToolSelect` and related components by removing error handling logic for a cleaner UI.
- Updated `AppTable` component to remove unnecessary props for better clarity.

* feat: Initialize copyright configuration in GateNavBar component

* feat: Add appDetail property to ChatGate component and update related logic

* feat: Update GateNavBar routing logic for chat page refresh and enhance avatar display

* feat: Enhance tag management and app detail handling in Chat component

* feat: 更新聊天组件中的国际化文本和输入逻辑,优化用户体验

* feat: Refactor gate configuration management

- Updated API endpoints for fetching and updating gate configurations.
- Changed `avatar` field to `logo` and added `banner` in gate configuration types.
- Implemented new controller methods for creating, retrieving, updating, and deleting gate configurations.
- Enhanced `ConfigButtons` and `CopyrightTable` components to handle new configuration fields.
- Added new SVG icon for sidebar collapse button.
- Improved internationalization support by adding new translation keys.
- Refactored `HomeTable` to manage gate configuration state and handle updates.
- Updated `ShareGateModal` to accept gate configuration as props.
- Cleaned up unused imports and optimized component structures.

* feat: 加载和管理 Gate 配置及版权信息,优化相关组件逻辑

* feat: 更新国际化文本,优化聊天组件中的配置和状态检查逻辑

* feat: Update template configuration and adjust default open state to improve user experience

* feat: Enhance gate management features and update related components

- Added `featuredApps` and `quickApps` fields to `GateSchemaType` for better app management.
- Implemented new methods for updating and managing featured and quick apps in the `controller` and `featureApp` modules.
- Introduced `AddFeatureAppModal` for selecting and adding featured apps.
- Updated `AppTable` and `HomeTable` components to integrate new app management functionalities.
- Enhanced internationalization support by adding new translation keys for app management features.
- Refactored existing components to improve code clarity and maintainability.

* feat: Enhance chat tool selection and quick app management features

- Added `selectedToolIds` and `onSelectedToolIdsChange` props to `ChatBox` and `GateChatInput` components for better tool management.
- Introduced `GateToolSelect` component for selecting tools with improved UI and functionality.
- Implemented `AddQuickAppModal` for managing quick apps, including selection and drag-and-drop functionality.
- Updated `HomeTable` to integrate quick app management and display selected apps.
- Refactored related components to improve code clarity and maintainability.

* refactor: Remove unused AppContext import in useChatGate.tsx to clean up code

* refactor: Update plugin ID handling and clean up unused imports

- Renamed `splitCombinePluginId` to `splitCombineToolId` for consistency in plugin ID processing.
- Removed unused `checkNode` import from `featureApp/detail.ts` and `quickApp/detail.ts` files to streamline the code.
- Added `ownerTmbId` to the parameters in `rewriteAppWorkflowToDetail` for better context management.

* refactor: Rename storeEdgesRenderEdge to storeEdge2RenderEdge for consistency

- Updated the function name from `storeEdgesRenderEdge` to `storeEdge2RenderEdge` in the Header component to maintain naming consistency.
- Adjusted the mapping of edges to use the new function name for improved clarity in the workflow processing.
2025-06-03 10:41:13 +08:00
archer
165b783a95 perf: ux 2025-06-03 10:41:13 +08:00
Finley Ge
d7b9f94270 feat: add app log permission (#4932)
* feat: add app log permission

* fix: org search bug
2025-06-03 10:41:12 +08:00
342 changed files with 17272 additions and 5598 deletions

View File

@@ -27,5 +27,8 @@
},
"markdown.copyFiles.destination": {
"/docSite/content/**/*": "${documentWorkspaceFolder}/docSite/assets/imgs/"
},
"[svg]": {
"editor.defaultFormatter": "jock.svg"
}
}

232
dev.md
View File

@@ -1,118 +1,114 @@
## Premise
Since FastGPT is managed in the same way as monorepo, it is recommended to install make first during development.
monorepo Project Name:
- app: main project
-......
## Dev
```sh
# Give automatic script code execution permission (on non-Linux systems, you can manually execute the postinstall.sh file content)
chmod -R +x ./scripts/
# Executing under the code root directory installs all dependencies within the root package, projects, and packages
pnpm i
# Not make cmd
cd projects/app
pnpm dev
# Make cmd
make dev name=app
```
Note: If the Node version is >= 20, you need to pass the `--no-node-snapshot` parameter to Node when running `pnpm i`
```sh
NODE_OPTIONS=--no-node-snapshot pnpm i
```
### Jest
https://fael3z0zfze.feishu.cn/docx/ZOI1dABpxoGhS7xzhkXcKPxZnDL
## I18N
### Install i18n-ally Plugin
1. Open the Extensions Marketplace in VSCode, search for and install the `i18n Ally` plugin.
### Code Optimization Examples
#### Fetch Specific Namespace Translations in `getServerSideProps`
```typescript
// pages/yourPage.tsx
export async function getServerSideProps(context: any) {
return {
props: {
currentTab: context?.query?.currentTab || TabEnum.info,
...(await serverSideTranslations(context.locale, ['publish', 'user']))
}
};
}
```
#### Use useTranslation Hook in Page
```typescript
// pages/yourPage.tsx
import { useTranslation } from 'next-i18next';
const YourComponent = () => {
const { t } = useTranslation();
return (
<Button
variant="outline"
size="sm"
mr={2}
onClick={() => setShowSelected(false)}
>
{t('common:close')}
</Button>
);
};
export default YourComponent;
```
#### Handle Static File Translations
```typescript
// utils/i18n.ts
import { i18nT } from '@fastgpt/web/i18n/utils';
const staticContent = {
id: 'simpleChat',
avatar: 'core/workflow/template/aiChat',
name: i18nT('app:template.simple_robot'),
};
export default staticContent;
```
### Standardize Translation Format
- Use the t(namespace:key) format to ensure consistent naming.
- Translation keys should use lowercase letters and underscores, e.g., common.close.
## audit
Please fill the OperationLogEventEnum and operationLog/audit function is added to the ts, and on the corresponding position to fill i18n, at the same time to add the location of the log using addOpearationLog function add function
## Build
```sh
# Docker cmd: Build image, not proxy
docker build -f ./projects/app/Dockerfile -t registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.1 . --build-arg name=app
# Make cmd: Build image, not proxy
make build name=app image=registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.1
# Docker cmd: Build image with proxy
docker build -f ./projects/app/Dockerfile -t registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.1 . --build-arg name=app --build-arg proxy=taobao
# Make cmd: Build image with proxy
make build name=app image=registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.1 proxy=taobao
```
## Premise
Since FastGPT is managed in the same way as monorepo, it is recommended to install make first during development.
monorepo Project Name:
- app: main project
-......
## Dev
```sh
# Give automatic script code execution permission (on non-Linux systems, you can manually execute the postinstall.sh file content)
chmod -R +x ./scripts/
# Executing under the code root directory installs all dependencies within the root package, projects, and packages
pnpm i
# Not make cmd
cd projects/app
pnpm dev
# Make cmd
make dev name=app
```
Note: If the Node version is >= 20, you need to pass the `--no-node-snapshot` parameter to Node when running `pnpm i`
```sh
NODE_OPTIONS=--no-node-snapshot pnpm i
```
### Jest
https://fael3z0zfze.feishu.cn/docx/ZOI1dABpxoGhS7xzhkXcKPxZnDL
## I18N
### Install i18n-ally Plugin
1. Open the Extensions Marketplace in VSCode, search for and install the `i18n Ally` plugin.
### Code Optimization Examples
#### Fetch Specific Namespace Translations in `getServerSideProps`
```typescript
// pages/yourPage.tsx
export async function getServerSideProps(context: any) {
return {
props: {
currentTab: context?.query?.currentTab || TabEnum.info,
...(await serverSideTranslations(context.locale, ['publish', 'user']))
}
};
}
```
#### Use useTranslation Hook in Page
```typescript
// pages/yourPage.tsx
import { useTranslation } from 'next-i18next';
const YourComponent = () => {
const { t } = useTranslation();
return (
<Button
variant="outline"
size="sm"
mr={2}
onClick={() => setShowSelected(false)}
>
{t('common:close')}
</Button>
);
};
export default YourComponent;
```
#### Handle Static File Translations
```typescript
// utils/i18n.ts
import { i18nT } from '@fastgpt/web/i18n/utils';
const staticContent = {
id: 'simpleChat',
avatar: 'core/workflow/template/aiChat',
name: i18nT('app:template.simple_robot'),
};
export default staticContent;
```
### Standardize Translation Format
- Use the t(namespace:key) format to ensure consistent naming.
- Translation keys should use lowercase letters and underscores, e.g., common.close.
## Build
```sh
# Docker cmd: Build image, not proxy
docker build -f ./projects/app/Dockerfile -t registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.1 . --build-arg name=app
# Make cmd: Build image, not proxy
make build name=app image=registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.1
# Docker cmd: Build image with proxy
docker build -f ./projects/app/Dockerfile -t registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.1 . --build-arg name=app --build-arg proxy=taobao
# Make cmd: Build image with proxy
make build name=app image=registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.1 proxy=taobao
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

View File

@@ -1,5 +1,5 @@
---
title: 'V4.9.1(包含升级脚本)'
title: 'V4.9.1'
description: 'FastGPT V4.9.1 更新说明'
icon: 'upgrade'
draft: false

View File

@@ -7,28 +7,11 @@ toc: true
weight: 789
---
## 执行升级脚本
该脚本仅需商业版用户执行。
从任意终端,发起 1 个 HTTP 请求。其中 {{rootkey}} 替换成环境变量里的 `rootkey`{{host}} 替换成**FastGPT 域名**。
```bash
curl --location --request POST 'https://{{host}}/api/admin/initv4911' \
--header 'rootkey: {{rootkey}}' \
--header 'Content-Type: application/json'
```
**脚本功能**
1. 移动第三方知识库 API 配置。
## 🚀 新增内容
1. 商业版支持图片知识库
2. 工作流中增加节点搜索功能
3. 工作流中,子流程版本控制,可选择“保持最新版本”,无需手动更新。
4. 增加更多审计操作日志。
1. 工作流中增加节点搜索功能
2. 工作流中,子流程版本控制,可选择“保持最新版本”,无需手动更新
## ⚙️ 优化

View File

@@ -1,5 +1,5 @@
---
title: 'V4.9.4(包含升级脚本)'
title: 'V4.9.4'
description: 'FastGPT V4.9.4 更新说明'
icon: 'upgrade'
draft: false

View File

@@ -1,161 +0,0 @@
---
title: '第三方知识库开发'
description: '本节详细介绍如何在FastGPT上自己接入第三方知识库'
icon: 'language'
draft: false
toc: true
weight: 410
---
目前,互联网上拥有各种各样的文档库,例如飞书,语雀等等。 FastGPT 的不同用户可能使用的文档库不同,目前 FastGPT 内置了飞书、语雀文档库,如果需要接入其他文档库,可以参考本节内容。
## 统一的接口规范
为了实现对不同文档库的统一接入FastGPT 对第三方文档库进行了接口的规范,共包含 4 个接口内容,可以[查看 API 文件库接口](/docs/guide/knowledge_base/api_datase)。
所有内置的文档库,都是基于标准的 API 文件库进行扩展。可以参考`FastGPT/packages/service/core/dataset/apiDataset/yuqueDataset/api.ts`中的代码,进行其他文档库的扩展。一共需要完成 4 个接口开发:
1. 获取文件列表
2. 获取文件内容/文件链接
3. 获取原文预览地址
4. 获取文件详情信息
## 开始一个第三方文件库
为了方便讲解,这里以添加飞书知识库为例。
### 1. 添加第三方文档库参数
首先,要进入 FastGPT 项目路径下的`FastGPT\packages\global\core\dataset\apiDataset.d.ts`文件,添加第三方文档库 Server 类型。例如,语雀文档中,需要提供`userId``token`两个字段作为鉴权信息。
```ts
export type YuqueServer = {
userId: string;
token?: string;
basePath?: string;
};
```
{{% alert icon="🤖 " context="success" %}}
如果文档库有`根目录`选择的功能,需要设置添加一个字段`basePath`
{{% /alert %}}
### 2. 创建 Hook 文件
每个第三方文档库都会采用 Hook 的方式来实现一套 API 接口的维护Hook 里包含 4 个函数需要完成。
-`FastGPT\packages\service\core\dataset\apiDataset\`下创建一个文档库的文件夹,然后在文件夹下创建一个`api.ts`文件
-`api.ts`文件中,需要完成 4 个函数的定义,分别是:
- `listFiles`:获取文件列表
- `getFileContent`:获取文件内容/文件链接
- `getFileDetail`:获取文件详情信息
- `getFilePreviewUrl`:获取原文预览地址
### 3. 数据库添加配置字段
-`packages/service/core/dataset/schema.ts` 中添加第三方文档库的配置字段,类型统一设置成`Object`
-`FastGPT/packages/global/core/dataset/type.d.ts`中添加第三方文档库配置字段的数据类型,类型设置为第一步创建的参数。
![](/imgs/thirddataset-7.png)
{{% alert icon="🤖 " context="success" %}}
`schema.ts`文件修改后,需要重新启动 FastGPT 项目才会生效。
{{% /alert %}}
### 4. 添加知识库类型
`projects/app/src/web/core/dataset/constants.ts`中,添加自己的知识库类型
```TS
export const datasetTypeCourseMap: Record<`${DatasetTypeEnum}`, string> = {
[DatasetTypeEnum.folder]: '',
[DatasetTypeEnum.dataset]: '',
[DatasetTypeEnum.apiDataset]: '/docs/guide/knowledge_base/api_dataset/',
[DatasetTypeEnum.websiteDataset]: '/docs/guide/knowledge_base/websync/',
[DatasetTypeEnum.feishuShare]: '/docs/guide/knowledge_base/lark_share_dataset/',
[DatasetTypeEnum.feishuKnowledge]: '/docs/guide/knowledge_base/lark_knowledge_dataset/',
[DatasetTypeEnum.yuque]: '/docs/guide/knowledge_base/yuque_dataset/',
[DatasetTypeEnum.externalFile]: ''
};
```
{{% alert icon="🤖 " context="success" %}}
在 datasetTypeCourseMap 中添加自己的知识库类型,`' '`内是相应的文档说明,如果有的话,可以添加。
文档添加在`FastGPT\docSite\content\zh-cn\docs\guide\knowledge_base\`
{{% /alert %}}
## 添加前端
`FastGPT\packages\web\i18n\zh-CN\dataset.json`,`FastGPT\packages\web\i18n\en\dataset.json``FastGPT\packages\web\i18n\zh-Hant\dataset.json`中添加自己的 I18n 翻译,以中文翻译为例,大体需要如下几个内容:
![](/imgs/thirddataset-24.png)
`FastGPT\packages\web\components\common\Icon\icons\core\dataset\`添加自己的知识库图标,一共是两个,分为`Outline``Color`,分别是有颜色的和无色的,具体看如下图片。
![](/imgs/thirddataset-10.png)
`FastGPT\packages\web\components\common\Icon\constants.ts`文件中,添加自己的图标。 `import` 是图标的存放路径。
![](/imgs/thirddataset-9.png)
`FastGPT\packages\global\core\dataset\constants.ts`文件中,添加自己的知识库类型。
![](/imgs/thirddataset-8.png)
{{% alert icon="🤖 " context="success" %}}
`label`内容是自己之前通过 i18n 翻译添加的知识库名称的
`icon`是自己之前添加的 Icon , I18n 的添加看最后清单。
{{% /alert %}}
`FastGPT\projects\app\src\pages\dataset\list\index.tsx`文件下,添加如下内容。这个文件负责的是知识库列表页的`新建`按钮点击后的菜单,只有在该文件添加知识库后,才能创建知识库。
![](/imgs/thirddataset-12.png)
`FastGPT\projects\app\src\pageComponents\dataset\detail\Info\index.tsx`文件下,添加如下内容。
![](/imgs/thirddataset-18.png)
`FastGPT\projects\app\src\pageComponents\dataset\list\CreateModal.tsx`文件下,添加如下内容。
| | |
| --- | --- |
| ![](/imgs/thirddataset-19.png) | ![](/imgs/thirddataset-20.png) |
`FastGPT\projects\app\src\pageComponents\dataset\list\SideTag.tsx`文件下,添加如下内容。
![](/imgs/thirddataset-21.png)
`FastGPT\projects\app\src\web\core\dataset\context\datasetPageContext.tsx`文件下,添加如下内容。
![](/imgs/thirddataset-23.png)
## 添加配置表单
`FastGPT\projects\app\src\pageComponents\dataset\ApiDatasetForm.tsx`文件下,添加自己如下内容。这个文件负责的是创建知识库页的字段填写。
| | | |
| --- | --- | --- |
| ![](/imgs/thirddataset-13.png) | ![](/imgs/thirddataset-14.png) | ![](/imgs/thirddataset-15.png) |
代码中添加的两个组件是对根目录选择的渲染,对应设计的 api 的 getfiledetail 方法,如果你的文件不支持,你可以不引用。
```
{renderBaseUrlSelector()} //这是对`Base URL`字段的渲染
{renderDirectoryModal()} //点击`选择`后出现的`选择根目录`窗口,见图
```
| | |
| --- | --- |
| ![](/imgs/thirddataset-16.png) | ![](/imgs/thirddataset-17.png) |
如果知识库需要支持根目录,还需要在`ApiDatasetForm`文件中添加相关内容。
## 添加杂项
最后,需要在很多文件里添加`server`类型,这里由于文件过多,且不大,不一一列举文件的清单。只提供方法:使用自己编程工具的全局搜索功能,搜索`YuqueServer``yuqueServer`。在搜索到的文件中,逐一添加自己的知识库类型。
## 提示
建议知识库创建完成后,完整测试一遍知识库的功能,以确定有无漏洞,如果你的知识库添加有问题,且无法在文档找到对应的文件解决,一定是杂项没有添加完全,建议重复一次全局搜索`YuqueServer``yuqueServer`,检查是否有地方没有加上自己的类型。

View File

@@ -4,6 +4,9 @@ import { type ErrType } from '../errorCode';
/* dataset: 507000 */
const startCode = 507000;
export enum CommonErrEnum {
methodNotAllowed = 'methodNotAllowed',
systemError = 'systemError',
unauthorized = 'unauthorized',
invalidParams = 'invalidParams',
invalidResource = 'invalidResource',
fileNotFound = 'fileNotFound',
@@ -35,6 +38,22 @@ const datasetErr = [
{
statusText: CommonErrEnum.inheritPermissionError,
message: 'error.inheritPermissionError'
},
{
statusText: CommonErrEnum.methodNotAllowed,
message: i18nT('common:code_error.error_message.405')
},
{
statusText: CommonErrEnum.systemError,
message: i18nT('common:code_error.error_message.500')
},
{
statusText: CommonErrEnum.unauthorized,
message: i18nT('common:code_error.error_message.403')
},
{
statusText: CommonErrEnum.invalidParams,
message: i18nT('common:code_error.error_message.422')
}
];
export default datasetErr.reduce((acc, cur, index) => {

View File

@@ -6,8 +6,7 @@ export const fileImgs = [
{ suffix: '(doc|docs)', src: 'file/fill/doc' },
{ suffix: 'txt', src: 'file/fill/txt' },
{ suffix: 'md', src: 'file/fill/markdown' },
{ suffix: 'html', src: 'file/fill/html' },
{ suffix: '(jpg|jpeg|png|gif|bmp|webp|svg|ico|tiff|tif)', src: 'image' }
{ suffix: 'html', src: 'file/fill/html' }
// { suffix: '.', src: '/imgs/files/file.svg' }
];

View File

@@ -2,5 +2,4 @@ export type AuthFrequencyLimitProps = {
eventId: string;
maxAmount: number;
expiredTime: Date;
num?: number;
};

View File

@@ -34,7 +34,7 @@ export const valToStr = (val: any) => {
};
// replace {{variable}} to value
export function replaceVariable(text: any, obj: Record<string, string | number | undefined>) {
export function replaceVariable(text: any, obj: Record<string, string | number>) {
if (typeof text !== 'string') return text;
for (const key in obj) {

View File

@@ -7,6 +7,7 @@ import {
} from './type';
export enum AppTypeEnum {
gate = 'gate',
folder = 'folder',
simple = 'simple',
workflow = 'advanced',

24
packages/global/core/app/tags.d.ts vendored Normal file
View File

@@ -0,0 +1,24 @@
import { TeamMemberStatusEnum } from 'support/user/team/constant';
import type { SourceMemberType } from 'support/user/type';
export type TagSchemaType = {
_id: string;
teamId: string;
name: string;
color: string;
createTime: Date;
};
export type TagWithCountType = TagSchemaType & {
count: number;
};
export type TagListItemType = {
_id: string;
teamId: string;
name: string;
color: string;
createTime: Date;
count?: number;
sourceMember?: SourceMemberType;
};

View File

@@ -65,6 +65,7 @@ export type AppListItemType = {
inheritPermission?: boolean;
private?: boolean;
sourceMember: SourceMemberType;
tags?: string[];
};
export type AppDetailType = AppSchema & {

View File

@@ -46,7 +46,7 @@ export const appWorkflow2Form = ({
chatConfig
}: {
nodes: StoreNodeItemType[];
chatConfig: AppChatConfigType;
chatConfig?: AppChatConfigType;
}) => {
const defaultAppForm = getDefaultAppForm();
const findInputValueByKey = (inputs: FlowNodeInputItemType[], key: string) => {
@@ -172,6 +172,10 @@ export const appWorkflow2Form = ({
}
});
if (chatConfig) {
defaultAppForm.chatConfig = chatConfig;
}
return defaultAppForm;
};

View File

@@ -1,9 +1,4 @@
import type {
ChunkSettingsType,
DatasetDataIndexItemType,
DatasetDataFieldType,
DatasetSchemaType
} from './type';
import type { ChunkSettingsType, DatasetDataIndexItemType, DatasetSchemaType } from './type';
import type {
DatasetCollectionTypeEnum,
DatasetCollectionDataProcessModeEnum,
@@ -12,14 +7,12 @@ import type {
ChunkTriggerConfigTypeEnum,
ParagraphChunkAIModeEnum
} from './constants';
import type { ParentIdType } from '../../common/parentFolder/type';
import type { LLMModelItemType } from '../ai/model.d';
import type { ParentIdType } from 'common/parentFolder/type';
/* ================= dataset ===================== */
export type DatasetUpdateBody = {
id: string;
apiDatasetServer?: DatasetSchemaType['apiDatasetServer'];
parentId?: ParentIdType;
name?: string;
avatar?: string;
@@ -31,6 +24,9 @@ export type DatasetUpdateBody = {
websiteConfig?: DatasetSchemaType['websiteConfig'];
externalReadUrl?: DatasetSchemaType['externalReadUrl'];
defaultPermission?: DatasetSchemaType['defaultPermission'];
apiServer?: DatasetSchemaType['apiServer'];
yuqueServer?: DatasetSchemaType['yuqueServer'];
feishuServer?: DatasetSchemaType['feishuServer'];
chunkSettings?: DatasetSchemaType['chunkSettings'];
// sync schedule
@@ -104,9 +100,6 @@ export type ExternalFileCreateDatasetCollectionParams = ApiCreateDatasetCollecti
externalFileUrl: string;
filename?: string;
};
export type ImageCreateDatasetCollectionParams = ApiCreateDatasetCollectionParams & {
collectionName: string;
};
/* ================= tag ===================== */
export type CreateDatasetCollectionTagParams = {
@@ -132,9 +125,8 @@ export type PgSearchRawType = {
score: number;
};
export type PushDatasetDataChunkProps = {
q?: string;
a?: string;
imageId?: string;
q: string; // embedding content
a?: string; // bonus content
chunkIndex?: number;
indexes?: Omit<DatasetDataIndexItemType, 'dataId'>[];
};

View File

@@ -1,5 +1,5 @@
import { RequireOnlyOne } from '../../../common/type/utils';
import type { ParentIdType } from '../../../common/parentFolder/type';
import { RequireOnlyOne } from '../../common/type/utils';
import type { ParentIdType } from '../../common/parentFolder/type.d';
export type APIFileItem = {
id: string;
@@ -28,12 +28,6 @@ export type YuqueServer = {
basePath?: string;
};
export type ApiDatasetServerType = {
apiServer?: APIFileServer;
feishuServer?: FeishuServer;
yuqueServer?: YuqueServer;
};
// Api dataset api
export type APIFileListResponse = APIFileItem[];

View File

@@ -1,31 +0,0 @@
import type { ApiDatasetServerType } from './type';
export const filterApiDatasetServerPublicData = (apiDatasetServer?: ApiDatasetServerType) => {
if (!apiDatasetServer) return undefined;
const { apiServer, yuqueServer, feishuServer } = apiDatasetServer;
return {
apiServer: apiServer
? {
baseUrl: apiServer.baseUrl,
authorization: '',
basePath: apiServer.basePath
}
: undefined,
yuqueServer: yuqueServer
? {
userId: yuqueServer.userId,
token: '',
basePath: yuqueServer.basePath
}
: undefined,
feishuServer: feishuServer
? {
appId: feishuServer.appId,
appSecret: '',
folderToken: feishuServer.folderToken
}
: undefined
};
};

View File

@@ -6,80 +6,45 @@ export enum DatasetTypeEnum {
dataset = 'dataset',
websiteDataset = 'websiteDataset', // depp link
externalFile = 'externalFile',
apiDataset = 'apiDataset',
feishu = 'feishu',
yuque = 'yuque'
}
// @ts-ignore
export const ApiDatasetTypeMap: Record<
`${DatasetTypeEnum}`,
{
icon: string;
avatar: string;
label: any;
collectionLabel: string;
courseUrl?: string;
}
> = {
[DatasetTypeEnum.apiDataset]: {
icon: 'core/dataset/externalDatasetOutline',
avatar: 'core/dataset/externalDatasetColor',
label: i18nT('dataset:api_file'),
collectionLabel: i18nT('common:File'),
courseUrl: '/docs/guide/knowledge_base/api_dataset/'
},
[DatasetTypeEnum.feishu]: {
icon: 'core/dataset/feishuDatasetOutline',
avatar: 'core/dataset/feishuDatasetColor',
label: i18nT('dataset:feishu_dataset'),
collectionLabel: i18nT('common:File'),
courseUrl: '/docs/guide/knowledge_base/lark_dataset/'
},
[DatasetTypeEnum.yuque]: {
icon: 'core/dataset/yuqueDatasetOutline',
avatar: 'core/dataset/yuqueDatasetColor',
label: i18nT('dataset:yuque_dataset'),
collectionLabel: i18nT('common:File'),
courseUrl: '/docs/guide/knowledge_base/yuque_dataset/'
}
};
export const DatasetTypeMap: Record<
`${DatasetTypeEnum}`,
{
icon: string;
avatar: string;
label: any;
collectionLabel: string;
courseUrl?: string;
}
> = {
...ApiDatasetTypeMap,
export const DatasetTypeMap = {
[DatasetTypeEnum.folder]: {
icon: 'common/folderFill',
avatar: 'common/folderFill',
label: i18nT('dataset:folder_dataset'),
collectionLabel: i18nT('common:Folder')
},
[DatasetTypeEnum.dataset]: {
icon: 'core/dataset/commonDatasetOutline',
avatar: 'core/dataset/commonDatasetColor',
label: i18nT('dataset:common_dataset'),
collectionLabel: i18nT('common:File')
},
[DatasetTypeEnum.websiteDataset]: {
icon: 'core/dataset/websiteDatasetOutline',
avatar: 'core/dataset/websiteDatasetColor',
label: i18nT('dataset:website_dataset'),
collectionLabel: i18nT('common:Website'),
courseUrl: '/docs/guide/knowledge_base/websync/'
collectionLabel: i18nT('common:Website')
},
[DatasetTypeEnum.externalFile]: {
icon: 'core/dataset/externalDatasetOutline',
avatar: 'core/dataset/externalDatasetColor',
label: i18nT('dataset:external_file'),
collectionLabel: i18nT('common:File')
},
[DatasetTypeEnum.apiDataset]: {
icon: 'core/dataset/externalDatasetOutline',
label: i18nT('dataset:api_file'),
collectionLabel: i18nT('common:File')
},
[DatasetTypeEnum.feishu]: {
icon: 'core/dataset/feishuDatasetOutline',
label: i18nT('dataset:feishu_dataset'),
collectionLabel: i18nT('common:File')
},
[DatasetTypeEnum.yuque]: {
icon: 'core/dataset/yuqueDatasetOutline',
label: i18nT('dataset:yuque_dataset'),
collectionLabel: i18nT('common:File')
}
};
@@ -112,8 +77,7 @@ export enum DatasetCollectionTypeEnum {
file = 'file',
link = 'link', // one link
externalFile = 'externalFile',
apiFile = 'apiFile',
images = 'images'
apiFile = 'apiFile'
}
export const DatasetCollectionTypeMap = {
[DatasetCollectionTypeEnum.folder]: {
@@ -133,9 +97,6 @@ export const DatasetCollectionTypeMap = {
},
[DatasetCollectionTypeEnum.apiFile]: {
name: i18nT('common:core.dataset.apiFile')
},
[DatasetCollectionTypeEnum.images]: {
name: i18nT('dataset:core.dataset.Image collection')
}
};
@@ -159,7 +120,6 @@ export const DatasetCollectionSyncResultMap = {
export enum DatasetCollectionDataProcessModeEnum {
chunk = 'chunk',
qa = 'qa',
imageParse = 'imageParse',
backup = 'backup',
auto = 'auto' // abandon
@@ -173,10 +133,6 @@ export const DatasetCollectionDataProcessModeMap = {
label: i18nT('common:core.dataset.training.QA mode'),
tooltip: i18nT('common:core.dataset.import.QA Import Tip')
},
[DatasetCollectionDataProcessModeEnum.imageParse]: {
label: i18nT('dataset:training.Image mode'),
tooltip: i18nT('common:core.dataset.import.Chunk Split Tip')
},
[DatasetCollectionDataProcessModeEnum.backup]: {
label: i18nT('dataset:backup_mode'),
tooltip: i18nT('dataset:backup_mode')
@@ -216,16 +172,14 @@ export enum ImportDataSourceEnum {
fileCustom = 'fileCustom',
externalFile = 'externalFile',
apiDataset = 'apiDataset',
reTraining = 'reTraining',
imageDataset = 'imageDataset'
reTraining = 'reTraining'
}
export enum TrainingModeEnum {
chunk = 'chunk',
qa = 'qa',
auto = 'auto',
image = 'image',
imageParse = 'imageParse'
image = 'image'
}
/* ------------ search -------------- */

View File

@@ -8,19 +8,17 @@ export type CreateDatasetDataProps = {
chunkIndex?: number;
q: string;
a?: string;
imageId?: string;
indexes?: Omit<DatasetDataIndexItemType, 'dataId'>[];
};
export type UpdateDatasetDataProps = {
dataId: string;
q: string;
q?: string;
a?: string;
indexes?: (Omit<DatasetDataIndexItemType, 'dataId'> & {
dataId?: string; // pg data id
})[];
imageId?: string;
};
export type PatchIndexesProps =

View File

@@ -1,13 +0,0 @@
export type DatasetImageSchema = {
_id: string;
teamId: string;
datasetId: string;
collectionId?: string;
name: string;
contentType: string;
size: number;
metadata?: Record<string, any>;
expiredTime?: Date;
createdAt: Date;
updatedAt: Date;
};

View File

@@ -13,15 +13,9 @@ import type {
ChunkTriggerConfigTypeEnum
} from './constants';
import type { DatasetPermission } from '../../support/permission/dataset/controller';
import type {
ApiDatasetServerType,
APIFileServer,
FeishuServer,
YuqueServer
} from './apiDataset/type';
import type { APIFileServer, FeishuServer, YuqueServer } from './apiDataset';
import type { SourceMemberType } from 'support/user/type';
import type { DatasetDataIndexTypeEnum } from './data/constants';
import type { ParentIdType } from 'common/parentFolder/type';
export type ChunkSettingsType = {
trainingType?: DatasetCollectionDataProcessModeEnum;
@@ -55,7 +49,7 @@ export type ChunkSettingsType = {
export type DatasetSchemaType = {
_id: string;
parentId: ParentIdType;
parentId?: string;
userId: string;
teamId: string;
tmbId: string;
@@ -78,16 +72,14 @@ export type DatasetSchemaType = {
chunkSettings?: ChunkSettingsType;
inheritPermission: boolean;
apiDatasetServer?: ApiDatasetServerType;
apiServer?: APIFileServer;
feishuServer?: FeishuServer;
yuqueServer?: YuqueServer;
// abandon
autoSync?: boolean;
externalReadUrl?: string;
defaultPermission?: number;
apiServer?: APIFileServer;
feishuServer?: FeishuServer;
yuqueServer?: YuqueServer;
};
export type DatasetCollectionSchemaType = ChunkSettingsType & {
@@ -140,13 +132,7 @@ export type DatasetDataIndexItemType = {
dataId: string; // pg data id
text: string;
};
export type DatasetDataFieldType = {
q: string; // large chunks or question
a?: string; // answer or custom content
imageId?: string;
};
export type DatasetDataSchemaType = DatasetDataFieldType & {
export type DatasetDataSchemaType = {
_id: string;
userId: string;
teamId: string;
@@ -155,9 +141,13 @@ export type DatasetDataSchemaType = DatasetDataFieldType & {
collectionId: string;
chunkIndex: number;
updateTime: Date;
history?: (DatasetDataFieldType & {
q: string; // large chunks or question
a: string; // answer or custom content
history?: {
q: string;
a: string;
updateTime: Date;
})[];
}[];
forbid?: boolean;
fullTextToken: string;
indexes: DatasetDataIndexItemType[];
@@ -189,7 +179,6 @@ export type DatasetTrainingSchemaType = {
dataId?: string;
q: string;
a: string;
imageId?: string;
chunkIndex: number;
indexSize?: number;
weight: number;
@@ -255,18 +244,20 @@ export type DatasetCollectionItemType = CollectionWithDatasetType & {
};
/* ================= data ===================== */
export type DatasetDataItemType = DatasetDataFieldType & {
export type DatasetDataItemType = {
id: string;
teamId: string;
datasetId: string;
imagePreivewUrl?: string;
updateTime: Date;
collectionId: string;
sourceName: string;
sourceId?: string;
q: string;
a: string;
chunkIndex: number;
indexes: DatasetDataIndexItemType[];
isOwner: boolean;
// permission: DatasetPermission;
};
/* --------------- file ---------------------- */
@@ -293,14 +284,3 @@ export type SearchDataResponseItemType = Omit<
score: { type: `${SearchScoreTypeEnum}`; value: number; index: number }[];
// score: number;
};
export type DatasetCiteItemType = {
_id: string;
q: string;
a?: string;
imagePreivewUrl?: string;
history?: DatasetDataSchemaType['history'];
updateTime: DatasetDataSchemaType['updateTime'];
index: DatasetDataSchemaType['chunkIndex'];
updated?: boolean;
};

View File

@@ -2,15 +2,10 @@ import { TrainingModeEnum, DatasetCollectionTypeEnum } from './constants';
import { getFileIcon } from '../../common/file/icon';
import { strIsLink } from '../../common/string/tools';
export function getCollectionIcon({
type = DatasetCollectionTypeEnum.file,
name = '',
sourceId
}: {
type?: DatasetCollectionTypeEnum;
name?: string;
sourceId?: string;
}) {
export function getCollectionIcon(
type: DatasetCollectionTypeEnum = DatasetCollectionTypeEnum.file,
name = ''
) {
if (type === DatasetCollectionTypeEnum.folder) {
return 'common/folderFill';
}
@@ -20,10 +15,7 @@ export function getCollectionIcon({
if (type === DatasetCollectionTypeEnum.virtual) {
return 'file/fill/manual';
}
if (type === DatasetCollectionTypeEnum.images) {
return 'core/dataset/imageFill';
}
return getSourceNameIcon({ sourceName: name, sourceId });
return getFileIcon(name);
}
export function getSourceNameIcon({
sourceName,

View File

@@ -1,5 +1,4 @@
export enum OperationLogEventEnum {
//Team
LOGIN = 'LOGIN',
CREATE_INVITATION_LINK = 'CREATE_INVITATION_LINK',
JOIN_TEAM = 'JOIN_TEAM',
@@ -12,52 +11,5 @@ export enum OperationLogEventEnum {
RELOCATE_DEPARTMENT = 'RELOCATE_DEPARTMENT',
CREATE_GROUP = 'CREATE_GROUP',
DELETE_GROUP = 'DELETE_GROUP',
ASSIGN_PERMISSION = 'ASSIGN_PERMISSION',
//APP
CREATE_APP = 'CREATE_APP',
UPDATE_APP_INFO = 'UPDATE_APP_INFO',
MOVE_APP = 'MOVE_APP',
DELETE_APP = 'DELETE_APP',
UPDATE_APP_COLLABORATOR = 'UPDATE_APP_COLLABORATOR',
DELETE_APP_COLLABORATOR = 'DELETE_APP_COLLABORATOR',
TRANSFER_APP_OWNERSHIP = 'TRANSFER_APP_OWNERSHIP',
CREATE_APP_COPY = 'CREATE_APP_COPY',
CREATE_APP_FOLDER = 'CREATE_APP_FOLDER',
UPDATE_PUBLISH_APP = 'UPDATE_PUBLISH_APP',
CREATE_APP_PUBLISH_CHANNEL = 'CREATE_APP_PUBLISH_CHANNEL',
UPDATE_APP_PUBLISH_CHANNEL = 'UPDATE_APP_PUBLISH_CHANNEL',
DELETE_APP_PUBLISH_CHANNEL = 'DELETE_APP_PUBLISH_CHANNEL',
EXPORT_APP_CHAT_LOG = 'EXPORT_APP_CHAT_LOG',
//Dataset
CREATE_DATASET = 'CREATE_DATASET',
UPDATE_DATASET = 'UPDATE_DATASET',
DELETE_DATASET = 'DELETE_DATASET',
MOVE_DATASET = 'MOVE_DATASET',
UPDATE_DATASET_COLLABORATOR = 'UPDATE_DATASET_COLLABORATOR',
DELETE_DATASET_COLLABORATOR = 'DELETE_DATASET_COLLABORATOR',
TRANSFER_DATASET_OWNERSHIP = 'TRANSFER_DATASET_OWNERSHIP',
EXPORT_DATASET = 'EXPORT_DATASET',
CREATE_DATASET_FOLDER = 'CREATE_DATASET_FOLDER',
//Collection
CREATE_COLLECTION = 'CREATE_COLLECTION',
UPDATE_COLLECTION = 'UPDATE_COLLECTION',
DELETE_COLLECTION = 'DELETE_COLLECTION',
RETRAIN_COLLECTION = 'RETRAIN_COLLECTION',
//Data
CREATE_DATA = 'CREATE_DATA',
UPDATE_DATA = 'UPDATE_DATA',
DELETE_DATA = 'DELETE_DATA',
//SearchTest
SEARCH_TEST = 'SEARCH_TEST',
//Account
CHANGE_PASSWORD = 'CHANGE_PASSWORD',
CHANGE_NOTIFICATION_SETTINGS = 'CHANGE_NOTIFICATION_SETTINGS',
CHANGE_MEMBER_NAME_ACCOUNT = 'CHANGE_MEMBER_NAME_ACCOUNT',
PURCHASE_PLAN = 'PURCHASE_PLAN',
EXPORT_BILL_RECORDS = 'EXPORT_BILL_RECORDS',
CREATE_INVOICE = 'CREATE_INVOICE',
SET_INVOICE_HEADER = 'SET_INVOICE_HEADER',
CREATE_API_KEY = 'CREATE_API_KEY',
UPDATE_API_KEY = 'UPDATE_API_KEY',
DELETE_API_KEY = 'DELETE_API_KEY'
ASSIGN_PERMISSION = 'ASSIGN_PERMISSION'
}

View File

@@ -2,7 +2,18 @@ import { NullPermission, PermissionKeyEnum, PermissionList } from '../constant';
import { type PermissionListType } from '../type';
import { i18nT } from '../../../../web/i18n/utils';
export enum AppPermissionKeyEnum {}
export const AppPermissionList: PermissionListType = {
export enum AppPermissionKeyEnum {
log = 'log',
quickGate = 'quickGate',
featuredGate = 'featuredGate'
}
export const AppLogPermission = 0b100000;
export const GateQuickAppPermission = 0b001100;
export const GateFeaturedAppPermission = 0b010100;
export const AppPermissionList: PermissionListType<AppPermissionKeyEnum> = {
[PermissionKeyEnum.read]: {
...PermissionList[PermissionKeyEnum.read],
description: i18nT('app:permission.des.read')
@@ -13,8 +24,28 @@ export const AppPermissionList: PermissionListType = {
},
[PermissionKeyEnum.manage]: {
...PermissionList[PermissionKeyEnum.manage],
value: 0b111111,
description: i18nT('app:permission.des.manage')
},
[AppPermissionKeyEnum.log]: {
name: i18nT('app:permission.name.log'),
value: AppLogPermission,
checkBoxType: 'multiple',
description: i18nT('app:permission.des.log')
},
[AppPermissionKeyEnum.quickGate]: {
name: '门户快捷应用权限',
description: '',
value: GateQuickAppPermission,
checkBoxType: 'hiden'
},
[AppPermissionKeyEnum.featuredGate]: {
name: '门户推荐应用权限',
description: '',
value: GateFeaturedAppPermission,
checkBoxType: 'hiden'
}
};
export const AppDefaultPermissionVal = NullPermission;
export const AppLogPermissionVal = AppPermissionList[AppPermissionKeyEnum.log].value;

View File

@@ -1,7 +1,8 @@
import { type PerConstructPros, Permission } from '../controller';
import { AppDefaultPermissionVal } from './constant';
import { AppDefaultPermissionVal, AppPermissionList } from './constant';
export class AppPermission extends Permission {
hasLogPer: boolean = false;
constructor(props?: PerConstructPros) {
if (!props) {
props = {
@@ -10,6 +11,13 @@ export class AppPermission extends Permission {
} else if (!props?.per) {
props.per = AppDefaultPermissionVal;
}
props.permissionList = AppPermissionList;
super(props);
this.setUpdatePermissionCallback(() => {
this.hasReadPer = this.checkPer(AppPermissionList.read.value);
this.hasWritePer = this.checkPer(AppPermissionList.write.value);
this.hasManagePer = this.checkPer(AppPermissionList.manage.value);
this.hasLogPer = this.checkPer(AppPermissionList.log.value);
});
}
}

View File

@@ -1,5 +1,10 @@
import { type PermissionListType, type PermissionValueType } from './type';
import { PermissionList, NullPermission, OwnerPermissionVal } from './constant';
import {
PermissionList,
NullPermission,
OwnerPermissionVal,
ManagePermissionVal
} from './constant';
export type PerConstructPros = {
per?: PermissionValueType;
@@ -63,6 +68,7 @@ export class Permission {
if (perm === OwnerPermissionVal) {
return this.value === OwnerPermissionVal;
}
return (this.value & perm) === perm;
}

View File

@@ -18,7 +18,7 @@ export type PermissionListType<T = {}> = Record<
name: string;
description: string;
value: PermissionValueType;
checkBoxType: 'single' | 'multiple';
checkBoxType: 'single' | 'multiple' | 'hiden';
}
>;

View File

@@ -19,7 +19,7 @@ export const TeamPermissionList: PermissionListType<TeamPermissionKeyEnum> = {
},
[PermissionKeyEnum.manage]: {
...PermissionList[PermissionKeyEnum.manage],
value: 0b000001
value: 0b000101
},
[TeamPermissionKeyEnum.appCreate]: {
checkBoxType: 'multiple',

View File

@@ -0,0 +1,31 @@
export type putUpdateGateConfigData = {
status?: boolean;
tools?: GateTool[];
slogan?: string;
placeholderText?: string;
};
export type putUpdateGateConfigResponse = {
status?: boolean;
tools?: string[];
slogan?: string;
placeholderText?: string;
};
export type putUpdateGateConfigCopyRightData = {
name?: string;
logo?: string;
banner?: string;
};
export type putUpdateGateConfigCopyRightResponse = {
name: string;
logo: string;
banner: string;
};
export type getGateConfigCopyRightResponse = {
name: string;
logo: string;
banner: string;
};

View File

@@ -0,0 +1,12 @@
export type GateSchemaType = {
teamId: string;
status: boolean;
tools: string[];
featuredApps: string[];
quickApps: string[];
slogan: string;
placeholderText: string;
name: string;
logo: string;
banner: string;
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,5 @@
import type {
ApiDatasetDetailResponse,
FeishuServer,
YuqueServer
} from '@fastgpt/global/core/dataset/apiDataset/type';
import type { ApiDatasetDetailResponse } from '@fastgpt/global/core/dataset/apiDataset';
import { FeishuServer, YuqueServer } from '@fastgpt/global/core/dataset/apiDataset';
import type {
DeepRagSearchProps,
SearchDatasetDataResponse

View File

@@ -142,26 +142,23 @@ export const updateRawTextBufferExpiredTime = async ({
};
export const clearExpiredRawTextBufferCron = async () => {
const gridBucket = getGridBucket();
const clearExpiredRawTextBuffer = async () => {
addLog.debug('Clear expired raw text buffer start');
const gridBucket = getGridBucket();
const data = await MongoRawTextBufferSchema.find(
{
'metadata.expiredTime': { $lt: new Date() }
},
'_id'
).lean();
return retryFn(async () => {
const data = await MongoRawTextBufferSchema.find(
{
'metadata.expiredTime': { $lt: new Date() }
},
'_id'
).lean();
for (const item of data) {
try {
for (const item of data) {
await gridBucket.delete(item._id);
} catch (error) {
addLog.error('Delete expired raw text buffer error', error);
}
}
addLog.debug('Clear expired raw text buffer end');
addLog.debug('Clear expired raw text buffer end');
});
};
setCron('*/10 * * * *', async () => {

View File

@@ -7,13 +7,12 @@ import { MongoChatFileSchema, MongoDatasetFileSchema } from './schema';
import { detectFileEncoding, detectFileEncodingByPath } from '@fastgpt/global/common/file/tools';
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
import { readRawContentByFileBuffer } from '../read/utils';
import { computeGridFsChunSize, gridFsStream2Buffer, stream2Encoding } from './utils';
import { gridFsStream2Buffer, stream2Encoding } from './utils';
import { addLog } from '../../system/log';
import { parseFileExtensionFromUrl } from '@fastgpt/global/common/string/tools';
import { Readable } from 'stream';
import { addRawTextBuffer, getRawTextBuffer } from '../../buffer/rawText/controller';
import { addMinutes } from 'date-fns';
import { retryFn } from '@fastgpt/global/common/system/utils';
export function getGFSCollection(bucket: `${BucketNameEnum}`) {
MongoDatasetFileSchema;
@@ -65,7 +64,23 @@ export async function uploadFile({
// create a gridfs bucket
const bucket = getGridBucket(bucketName);
const chunkSizeBytes = computeGridFsChunSize(stats.size);
const fileSize = stats.size;
// 单块大小:尽可能大,但不超过 14MB不小于512KB
const chunkSizeBytes = (() => {
// 计算理想块大小:文件大小 ÷ 目标块数(10)。 并且每个块需要小于 14MB
const idealChunkSize = Math.min(Math.ceil(fileSize / 10), 14 * 1024 * 1024);
// 确保块大小至少为512KB
const minChunkSize = 512 * 1024; // 512KB
// 取理想块大小和最小块大小中的较大值
let chunkSize = Math.max(idealChunkSize, minChunkSize);
// 将块大小向上取整到最接近的64KB的倍数使其更整齐
chunkSize = Math.ceil(chunkSize / (64 * 1024)) * (64 * 1024);
return chunkSize;
})();
const stream = bucket.openUploadStream(filename, {
metadata,
@@ -158,18 +173,24 @@ export async function getFileById({
export async function delFileByFileIdList({
bucketName,
fileIdList
fileIdList,
retry = 3
}: {
bucketName: `${BucketNameEnum}`;
fileIdList: string[];
retry?: number;
}): Promise<any> {
return retryFn(async () => {
try {
const bucket = getGridBucket(bucketName);
for await (const fileId of fileIdList) {
await bucket.delete(new Types.ObjectId(fileId));
}
});
} catch (error) {
if (retry > 0) {
return delFileByFileIdList({ bucketName, fileIdList, retry: retry - 1 });
}
}
}
export async function getDownloadStream({

View File

@@ -105,20 +105,3 @@ export const stream2Encoding = async (stream: NodeJS.ReadableStream) => {
stream: copyStream
};
};
// 单块大小:尽可能大,但不超过 14MB不小于512KB
export const computeGridFsChunSize = (fileSize: number) => {
// 计算理想块大小:文件大小 ÷ 目标块数(10)。 并且每个块需要小于 14MB
const idealChunkSize = Math.min(Math.ceil(fileSize / 10), 14 * 1024 * 1024);
// 确保块大小至少为512KB
const minChunkSize = 512 * 1024; // 512KB
// 取理想块大小和最小块大小中的较大值
let chunkSize = Math.max(idealChunkSize, minChunkSize);
// 将块大小向上取整到最接近的64KB的倍数使其更整齐
chunkSize = Math.ceil(chunkSize / (64 * 1024)) * (64 * 1024);
return chunkSize;
};

View File

@@ -22,7 +22,7 @@ export const getUploadModel = ({ maxSize = 500 }: { maxSize?: number }) => {
maxSize *= 1024 * 1024;
class UploadModel {
uploaderSingle = multer({
uploader = multer({
limits: {
fieldSize: maxSize
},
@@ -41,7 +41,8 @@ export const getUploadModel = ({ maxSize = 500 }: { maxSize?: number }) => {
}
})
}).single('file');
async getUploadFile<T = any>(
async doUpload<T = any>(
req: NextApiRequest,
res: NextApiResponse,
originBucketName?: `${BucketNameEnum}`
@@ -53,7 +54,7 @@ export const getUploadModel = ({ maxSize = 500 }: { maxSize?: number }) => {
bucketName?: `${BucketNameEnum}`;
}>((resolve, reject) => {
// @ts-ignore
this.uploaderSingle(req, res, (error) => {
this.uploader(req, res, (error) => {
if (error) {
return reject(error);
}
@@ -93,58 +94,6 @@ export const getUploadModel = ({ maxSize = 500 }: { maxSize?: number }) => {
});
});
}
uploaderMultiple = multer({
limits: {
fieldSize: maxSize
},
preservePath: true,
storage: multer.diskStorage({
// destination: (_req, _file, cb) => {
// cb(null, tmpFileDirPath);
// },
filename: (req, file, cb) => {
if (!file?.originalname) {
cb(new Error('File not found'), '');
} else {
const { ext } = path.parse(decodeURIComponent(file.originalname));
cb(null, `${getNanoid()}${ext}`);
}
}
})
}).array('file', global.feConfigs?.uploadFileMaxSize);
async getUploadFiles<T = any>(req: NextApiRequest, res: NextApiResponse) {
return new Promise<{
files: FileType[];
data: T;
}>((resolve, reject) => {
// @ts-ignore
this.uploaderMultiple(req, res, (error) => {
if (error) {
console.log(error);
return reject(error);
}
// @ts-ignore
const files = req.files as FileType[];
resolve({
files: files.map((file) => ({
...file,
originalname: decodeURIComponent(file.originalname)
})),
data: (() => {
if (!req.body?.data) return {};
try {
return JSON.parse(req.body.data);
} catch (error) {
return {};
}
})()
});
});
});
}
}
return new UploadModel();

View File

@@ -4,8 +4,7 @@ import { MongoFrequencyLimit } from './schema';
export const authFrequencyLimit = async ({
eventId,
maxAmount,
expiredTime,
num = 1
expiredTime
}: AuthFrequencyLimitProps) => {
try {
// 对应 eventId 的 account+1, 不存在的话,则创建一个
@@ -15,7 +14,7 @@ export const authFrequencyLimit = async ({
expiredTime: { $gte: new Date() }
},
{
$inc: { amount: num },
$inc: { amount: 1 },
// If not exist, set the expiredTime
$setOnInsert: { expiredTime }
},

View File

@@ -6,9 +6,7 @@ export enum TimerIdEnum {
updateStandardPlan = 'updateStandardPlan',
scheduleTriggerApp = 'scheduleTriggerApp',
notification = 'notification',
clearExpiredRawTextBuffer = 'clearExpiredRawTextBuffer',
clearExpiredDatasetImage = 'clearExpiredDatasetImage'
clearExpiredRawTextBuffer = 'clearExpiredRawTextBuffer'
}
export enum LockNotificationEnum {

View File

@@ -20,10 +20,6 @@ export const getVlmModel = (model?: string) => {
?.find((item) => item.model === model || item.name === model);
};
export const getVlmModelList = () => {
return Array.from(global.llmModelMap.values())?.filter((item) => item.vision) || [];
};
export const getDefaultEmbeddingModel = () => global?.systemDefaultModel.embedding!;
export const getEmbeddingModel = (model?: string) => {
if (!model) return getDefaultEmbeddingModel();

View File

@@ -64,7 +64,12 @@ const AppSchema = new Schema({
type: Date,
default: () => new Date()
},
tags: [
{
type: Schema.Types.ObjectId,
ref: 'app_tags'
}
],
// role and auth
teamTags: {
type: [String]

View File

@@ -0,0 +1,242 @@
import { MongoTag } from './schema';
import { MongoApp } from '../schema';
import { Types } from '../../../common/mongo';
/**
* 创建新标签
*/
export const createTag = async ({
teamId,
name,
color
}: {
teamId: string;
name: string;
color?: string;
}) => {
const tag = await MongoTag.create({
teamId,
name,
color
});
return tag.toObject();
};
/**
* 获取团队所有标签
*/
export const getTeamTags = async (teamId: string) => {
const tags = await MongoTag.find({ teamId }).lean();
return tags;
};
/**
* 获取标签使用统计
*/
export const getTagsWithCount = async (teamId: string) => {
return MongoTag.aggregate([
{ $match: { teamId: new Types.ObjectId(teamId) } },
{
$lookup: {
from: 'apps',
localField: '_id',
foreignField: 'tags',
as: 'apps'
}
},
{
$addFields: {
count: { $size: '$apps' }
}
},
{
$project: {
apps: 0
}
}
]);
};
/**
* 更新标签
*/
export const updateTag = async ({
tagId,
teamId,
name,
color
}: {
tagId: string;
teamId: string;
name?: string;
color?: string;
}) => {
const updateData: Record<string, any> = {};
if (name !== undefined) updateData.name = name;
if (color !== undefined) updateData.color = color;
await MongoTag.updateOne({ _id: tagId, teamId }, { $set: updateData });
return MongoTag.findById(tagId).lean();
};
/**
* 删除标签
*/
export const deleteTag = async ({ tagId, teamId }: { tagId: string; teamId: string }) => {
// 先从所有 app 中移除该标签
await MongoApp.updateMany({ teamId, tags: tagId }, { $pull: { tags: tagId } });
// 然后删除标签
await MongoTag.deleteOne({ _id: tagId, teamId });
return true;
};
/**
* 为 app 添加标签
*/
export const addTagToApp = async ({
appId,
tagId,
teamId
}: {
appId: string;
tagId: string;
teamId: string;
}) => {
// 确认标签存在且属于该团队
const tag = await MongoTag.findOne({ _id: tagId, teamId });
if (!tag) {
throw new Error('Tag not found or not authorized');
}
await MongoApp.updateOne({ _id: appId, teamId }, { $addToSet: { tags: tagId } });
return true;
};
/**
* 从 app 移除标签
*/
export const removeTagFromApp = async ({
appId,
tagId,
teamId
}: {
appId: string;
tagId: string;
teamId: string;
}) => {
await MongoApp.updateOne({ _id: appId, teamId }, { $pull: { tags: tagId } });
return true;
};
/**
* 批量删除标签
*/
export const batchDeleteTags = async ({ tagIds, teamId }: { tagIds: string[]; teamId: string }) => {
if (!tagIds || tagIds.length === 0) {
return true;
}
// 先从所有 app 中移除这些标签
await MongoApp.updateMany(
{ teamId, tags: { $in: tagIds } },
{ $pull: { tags: { $in: tagIds } } }
);
// 然后删除标签
const result = await MongoTag.deleteMany({ _id: { $in: tagIds }, teamId });
return { deletedCount: result.deletedCount };
};
/**
* 批量为 app 添加标签
*/
export const batchAddTagsToApp = async ({
appId,
tagIds,
teamId
}: {
appId: string;
tagIds: string[];
teamId: string;
}) => {
if (!tagIds || tagIds.length === 0) {
return true;
}
// 确认标签存在且属于该团队
const tags = await MongoTag.find({ _id: { $in: tagIds }, teamId });
if (tags.length !== tagIds.length) {
throw new Error('Some tags not found or not authorized');
}
await MongoApp.updateOne({ _id: appId, teamId }, { $addToSet: { tags: { $each: tagIds } } });
return true;
};
/**
* 批量从 app 移除标签
*/
export const batchRemoveTagsFromApp = async ({
appId,
tagIds,
teamId
}: {
appId: string;
tagIds: string[];
teamId: string;
}) => {
if (!tagIds || tagIds.length === 0) {
return true;
}
await MongoApp.updateOne({ _id: appId, teamId }, { $pull: { tags: { $in: tagIds } } });
return true;
};
/**
* 批量为某一标签添加 app全量更新
*/
export const batchAddAppsToTag = async ({
tagId,
appIds,
teamId
}: {
tagId: string;
appIds: string[];
teamId: string;
}) => {
// 确认标签存在且属于该团队
const tag = await MongoTag.findOne({ _id: tagId, teamId });
if (!tag) {
throw new Error('Tag not found or not authorized');
}
// 如果 appIds 为空数组,则移除该标签的所有应用
if (!appIds || appIds.length === 0) {
await MongoApp.updateMany({ teamId, tags: tagId }, { $pull: { tags: tagId } });
return true;
}
// 确认所有 app 都存在且属于该团队
const apps = await MongoApp.find({ _id: { $in: appIds }, teamId });
if (apps.length !== appIds.length) {
throw new Error('Some apps not found or not authorized');
}
// 先从所有应用中移除该标签
await MongoApp.updateMany({ teamId, tags: tagId }, { $pull: { tags: tagId } });
// 然后为指定的应用添加该标签
await MongoApp.updateMany({ _id: { $in: appIds }, teamId }, { $addToSet: { tags: tagId } });
return true;
};

View File

@@ -0,0 +1,37 @@
import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant';
import { getMongoModel, Schema } from '../../../common/mongo';
export const TagCollectionName = 'app_tags';
export type TagSchemaType = {
_id: string;
teamId: string;
name: string;
color: string;
createTime: Date;
};
const TagSchema = new Schema({
teamId: {
type: Schema.Types.ObjectId,
ref: TeamCollectionName,
required: true
},
name: {
type: String,
required: true
},
color: {
type: String,
default: '#3370ff'
},
createTime: {
type: Date,
default: () => new Date()
}
});
// 创建复合索引:按团队和名称确保唯一性
TagSchema.index({ teamId: 1, name: 1 }, { unique: true });
export const MongoTag = getMongoModel<TagSchemaType>(TagCollectionName, TagSchema);

View File

@@ -3,11 +3,12 @@ import type {
ApiFileReadContentResponse,
APIFileReadResponse,
ApiDatasetDetailResponse,
APIFileServer
} from '@fastgpt/global/core/dataset/apiDataset/type';
APIFileServer,
APIFileItem
} from '@fastgpt/global/core/dataset/apiDataset';
import axios, { type Method } from 'axios';
import { addLog } from '../../../../common/system/log';
import { readFileRawTextByUrl } from '../../read';
import { addLog } from '../../../common/system/log';
import { readFileRawTextByUrl } from '../read';
import { type ParentIdType } from '@fastgpt/global/common/parentFolder/type';
import { type RequireOnlyOne } from '@fastgpt/global/common/type/utils';

View File

@@ -1,10 +1,18 @@
import { useApiDatasetRequest } from './custom/api';
import { useYuqueDatasetRequest } from './yuqueDataset/api';
import { useFeishuDatasetRequest } from './feishuDataset/api';
import type { ApiDatasetServerType } from '@fastgpt/global/core/dataset/apiDataset/type';
import type {
APIFileServer,
YuqueServer,
FeishuServer
} from '@fastgpt/global/core/dataset/apiDataset';
import { useApiDatasetRequest } from './api';
import { useYuqueDatasetRequest } from '../yuqueDataset/api';
import { useFeishuDatasetRequest } from '../feishuDataset/api';
export const getApiDatasetRequest = async (apiDatasetServer?: ApiDatasetServerType) => {
const { apiServer, yuqueServer, feishuServer } = apiDatasetServer || {};
export const getApiDatasetRequest = async (data: {
apiServer?: APIFileServer;
yuqueServer?: YuqueServer;
feishuServer?: FeishuServer;
}) => {
const { apiServer, yuqueServer, feishuServer } = data;
if (apiServer) {
return useApiDatasetRequest({ apiServer });

View File

@@ -5,10 +5,9 @@ import {
} from '@fastgpt/global/core/dataset/constants';
import type { CreateDatasetCollectionParams } from '@fastgpt/global/core/dataset/api.d';
import { MongoDatasetCollection } from './schema';
import type {
DatasetCollectionSchemaType,
DatasetDataFieldType,
DatasetSchemaType
import {
type DatasetCollectionSchemaType,
type DatasetSchemaType
} from '@fastgpt/global/core/dataset/type';
import { MongoDatasetTraining } from '../training/schema';
import { MongoDatasetData } from '../data/schema';
@@ -16,7 +15,7 @@ import { delImgByRelatedId } from '../../../common/file/image/controller';
import { deleteDatasetDataVector } from '../../../common/vectorDB/controller';
import { delFileByFileIdList } from '../../../common/file/gridfs/controller';
import { BucketNameEnum } from '@fastgpt/global/common/file/constants';
import type { ClientSession } from '../../../common/mongo';
import { type ClientSession } from '../../../common/mongo';
import { createOrGetCollectionTags } from './utils';
import { rawText2Chunks } from '../read';
import { checkDatasetLimit } from '../../../support/permission/teamLimit';
@@ -39,25 +38,20 @@ import {
getLLMMaxChunkSize
} from '@fastgpt/global/core/dataset/training/utils';
import { DatasetDataIndexTypeEnum } from '@fastgpt/global/core/dataset/data/constants';
import { deleteDatasetImage } from '../image/controller';
import { clearCollectionImages, removeDatasetImageExpiredTime } from '../image/utils';
export const createCollectionAndInsertData = async ({
dataset,
rawText,
relatedId,
imageIds,
createCollectionParams,
backupParse = false,
billId,
session
}: {
dataset: DatasetSchemaType;
rawText?: string;
rawText: string;
relatedId?: string;
imageIds?: string[];
createCollectionParams: CreateOneCollectionParams;
backupParse?: boolean;
billId?: string;
@@ -75,13 +69,13 @@ export const createCollectionAndInsertData = async ({
// Set default params
const trainingType =
createCollectionParams.trainingType || DatasetCollectionDataProcessModeEnum.chunk;
const chunkSize = computeChunkSize({
...createCollectionParams,
trainingType,
llmModel: getLLMModel(dataset.agentModel)
});
const chunkSplitter = computeChunkSplitter(createCollectionParams);
const paragraphChunkDeep = computeParagraphChunkDeep(createCollectionParams);
const trainingMode = getTrainingModeByCollection({
trainingType: trainingType,
autoIndexes: createCollectionParams.autoIndexes,
imageIndex: createCollectionParams.imageIndex
});
if (
trainingType === DatasetCollectionDataProcessModeEnum.qa ||
@@ -96,60 +90,35 @@ export const createCollectionAndInsertData = async ({
delete createCollectionParams.qaPrompt;
}
// 1. split chunks or create image chunks
const {
chunks,
chunkSize
}: {
chunks: Array<{
q?: string;
a?: string; // answer or custom content
imageId?: string;
indexes?: string[];
}>;
chunkSize?: number;
} = (() => {
if (rawText) {
const chunkSize = computeChunkSize({
...createCollectionParams,
trainingType,
llmModel: getLLMModel(dataset.agentModel)
});
// Process text chunks
const chunks = rawText2Chunks({
rawText,
chunkTriggerType: createCollectionParams.chunkTriggerType,
chunkTriggerMinSize: createCollectionParams.chunkTriggerMinSize,
chunkSize,
paragraphChunkDeep,
paragraphChunkMinSize: createCollectionParams.paragraphChunkMinSize,
maxSize: getLLMMaxChunkSize(getLLMModel(dataset.agentModel)),
overlapRatio: trainingType === DatasetCollectionDataProcessModeEnum.chunk ? 0.2 : 0,
customReg: chunkSplitter ? [chunkSplitter] : [],
backupParse
});
return { chunks, chunkSize };
}
if (imageIds) {
// Process image chunks
const chunks = imageIds.map((imageId: string) => ({
imageId,
indexes: []
}));
return { chunks };
}
throw new Error('Either rawText or imageIdList must be provided');
})();
// 1. split chunks
const chunks = rawText2Chunks({
rawText,
chunkTriggerType: createCollectionParams.chunkTriggerType,
chunkTriggerMinSize: createCollectionParams.chunkTriggerMinSize,
chunkSize,
paragraphChunkDeep,
paragraphChunkMinSize: createCollectionParams.paragraphChunkMinSize,
maxSize: getLLMMaxChunkSize(getLLMModel(dataset.agentModel)),
overlapRatio: trainingType === DatasetCollectionDataProcessModeEnum.chunk ? 0.2 : 0,
customReg: chunkSplitter ? [chunkSplitter] : [],
backupParse
});
// 2. auth limit
await checkDatasetLimit({
teamId,
insertLen: predictDataLimitLength(trainingMode, chunks)
insertLen: predictDataLimitLength(
getTrainingModeByCollection({
trainingType: trainingType,
autoIndexes: createCollectionParams.autoIndexes,
imageIndex: createCollectionParams.imageIndex
}),
chunks
)
});
const fn = async (session: ClientSession) => {
// 3. Create collection
// 3. create collection
const { _id: collectionId } = await createOneCollection({
...createCollectionParams,
trainingType,
@@ -157,8 +126,8 @@ export const createCollectionAndInsertData = async ({
chunkSize,
chunkSplitter,
hashRawText: rawText ? hashStr(rawText) : undefined,
rawTextLength: rawText?.length,
hashRawText: hashStr(rawText),
rawTextLength: rawText.length,
nextSyncTime: (() => {
// ignore auto collections sync for website datasets
if (!dataset.autoSync && dataset.type === DatasetTypeEnum.websiteDataset) return undefined;
@@ -200,7 +169,11 @@ export const createCollectionAndInsertData = async ({
vectorModel: dataset.vectorModel,
vlmModel: dataset.vlmModel,
indexSize: createCollectionParams.indexSize,
mode: trainingMode,
mode: getTrainingModeByCollection({
trainingType: trainingType,
autoIndexes: createCollectionParams.autoIndexes,
imageIndex: createCollectionParams.imageIndex
}),
prompt: createCollectionParams.qaPrompt,
billId: traingBillId,
data: chunks.map((item, index) => ({
@@ -214,12 +187,7 @@ export const createCollectionAndInsertData = async ({
session
});
// 6. Remove images ttl index
await removeDatasetImageExpiredTime({
ids: imageIds,
collectionId,
session
});
// 6. remove related image ttl
if (relatedId) {
await MongoImage.updateMany(
{
@@ -239,7 +207,7 @@ export const createCollectionAndInsertData = async ({
}
return {
collectionId: String(collectionId),
collectionId,
insertResults
};
};
@@ -320,20 +288,17 @@ export const delCollectionRelatedSource = async ({
.map((item) => item?.metadata?.relatedImgId || '')
.filter(Boolean);
// Delete files and images in parallel
await Promise.all([
// Delete files
delFileByFileIdList({
bucketName: BucketNameEnum.dataset,
fileIdList
}),
// Delete images
delImgByRelatedId({
teamId,
relateIds: relatedImageIds,
session
})
]);
// Delete files
await delFileByFileIdList({
bucketName: BucketNameEnum.dataset,
fileIdList
});
// Delete images
await delImgByRelatedId({
teamId,
relateIds: relatedImageIds,
session
});
};
/**
* delete collection and it related data
@@ -378,16 +343,16 @@ export async function delCollection({
datasetId: { $in: datasetIds },
collectionId: { $in: collectionIds }
}),
// Delete dataset_images
clearCollectionImages(collectionIds),
// Delete images if needed
...(delImg
? collections
.map((item) => item?.metadata?.relatedImgId || '')
.filter(Boolean)
.map((imageId) => deleteDatasetImage(imageId))
? [
delImgByRelatedId({
teamId,
relateIds: collections
.map((item) => item?.metadata?.relatedImgId || '')
.filter(Boolean)
})
]
: []),
// Delete files if needed
...(delFile
? [
delFileByFileIdList({

View File

@@ -1,9 +1,11 @@
import { MongoDatasetCollection } from './schema';
import type { ClientSession } from '../../../common/mongo';
import { type ClientSession } from '../../../common/mongo';
import { MongoDatasetCollectionTags } from '../tag/schema';
import { readFromSecondary } from '../../../common/mongo/utils';
import type { CollectionWithDatasetType } from '@fastgpt/global/core/dataset/type';
import { DatasetCollectionSchemaType } from '@fastgpt/global/core/dataset/type';
import {
type CollectionWithDatasetType,
type DatasetCollectionSchemaType
} from '@fastgpt/global/core/dataset/type';
import {
DatasetCollectionDataProcessModeEnum,
DatasetCollectionSyncResultEnum,
@@ -157,7 +159,9 @@ export const syncCollection = async (collection: CollectionWithDatasetType) => {
return {
type: DatasetSourceReadTypeEnum.apiFile,
sourceId,
apiDatasetServer: dataset.apiDatasetServer
apiServer: dataset.apiServer,
feishuServer: dataset.feishuServer,
yuqueServer: dataset.yuqueServer
};
})();
@@ -229,37 +233,18 @@ export const syncCollection = async (collection: CollectionWithDatasetType) => {
QA: 独立进程
Chunk: Image Index -> Auto index -> chunk index
*/
export const getTrainingModeByCollection = ({
trainingType,
autoIndexes,
imageIndex
}: {
trainingType: DatasetCollectionDataProcessModeEnum;
autoIndexes?: boolean;
imageIndex?: boolean;
export const getTrainingModeByCollection = (collection: {
trainingType: DatasetCollectionSchemaType['trainingType'];
autoIndexes?: DatasetCollectionSchemaType['autoIndexes'];
imageIndex?: DatasetCollectionSchemaType['imageIndex'];
}) => {
if (
trainingType === DatasetCollectionDataProcessModeEnum.imageParse &&
global.feConfigs?.isPlus
) {
return TrainingModeEnum.imageParse;
}
if (trainingType === DatasetCollectionDataProcessModeEnum.qa) {
if (collection.trainingType === DatasetCollectionDataProcessModeEnum.qa) {
return TrainingModeEnum.qa;
}
if (
trainingType === DatasetCollectionDataProcessModeEnum.chunk &&
imageIndex &&
global.feConfigs?.isPlus
) {
if (collection.imageIndex && global.feConfigs?.isPlus) {
return TrainingModeEnum.image;
}
if (
trainingType === DatasetCollectionDataProcessModeEnum.chunk &&
autoIndexes &&
global.feConfigs?.isPlus
) {
if (collection.autoIndexes && global.feConfigs?.isPlus) {
return TrainingModeEnum.auto;
}
return TrainingModeEnum.chunk;

View File

@@ -9,7 +9,6 @@ import { deleteDatasetDataVector } from '../../common/vectorDB/controller';
import { MongoDatasetDataText } from './data/dataTextSchema';
import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset';
import { retryFn } from '@fastgpt/global/common/system/utils';
import { clearDatasetImages } from './image/utils';
/* ============= dataset ========== */
/* find all datasetId by top datasetId */
@@ -103,10 +102,8 @@ export async function delDatasetRelevantData({
}),
//delete dataset_datas
MongoDatasetData.deleteMany({ teamId, datasetId: { $in: datasetIds } }),
// Delete collection image and file
// Delete Image and file
delCollectionRelatedSource({ collections }),
// Delete dataset Image
clearDatasetImages(datasetIds),
// Delete vector data
deleteDatasetDataVector({ teamId, datasetIds })
]);

View File

@@ -1,56 +0,0 @@
import { getDatasetImagePreviewUrl } from '../image/utils';
import type { DatasetCiteItemType, DatasetDataSchemaType } from '@fastgpt/global/core/dataset/type';
export const formatDatasetDataValue = ({
q,
a,
imageId,
teamId,
datasetId
}: {
q: string;
a?: string;
imageId?: string;
teamId: string;
datasetId: string;
}): {
q: string;
a?: string;
imagePreivewUrl?: string;
} => {
if (!imageId) {
return {
q,
a
};
}
const previewUrl = getDatasetImagePreviewUrl({
imageId,
teamId,
datasetId,
expiredMinutes: 60 * 24 * 7 // 7 days
});
return {
q: `![${q.replaceAll('\n', '\\n')}](${previewUrl})`,
a,
imagePreivewUrl: previewUrl
};
};
export const getFormatDatasetCiteList = (list: DatasetDataSchemaType[]) => {
return list.map<DatasetCiteItemType>((item) => ({
_id: item._id,
...formatDatasetDataValue({
teamId: item.teamId,
datasetId: item.datasetId,
q: item.q,
a: item.a,
imageId: item.imageId
}),
history: item.history,
updateTime: item.updateTime,
index: item.chunkIndex
}));
};

View File

@@ -37,7 +37,8 @@ const DatasetDataSchema = new Schema({
required: true
},
a: {
type: String
type: String,
default: ''
},
history: {
type: [
@@ -73,9 +74,6 @@ const DatasetDataSchema = new Schema({
default: []
},
imageId: {
type: String
},
updateTime: {
type: Date,
default: () => new Date()

View File

@@ -3,10 +3,10 @@ import type {
ApiFileReadContentResponse,
ApiDatasetDetailResponse,
FeishuServer
} from '@fastgpt/global/core/dataset/apiDataset/type';
} from '@fastgpt/global/core/dataset/apiDataset';
import { type ParentIdType } from '@fastgpt/global/common/parentFolder/type';
import axios, { type Method } from 'axios';
import { addLog } from '../../../../common/system/log';
import { addLog } from '../../../common/system/log';
type ResponseDataType = {
success: boolean;

View File

@@ -1,166 +0,0 @@
import { addMinutes } from 'date-fns';
import { bucketName, MongoDatasetImageSchema } from './schema';
import { connectionMongo, Types } from '../../../common/mongo';
import fs from 'fs';
import type { FileType } from '../../../common/file/multer';
import fsp from 'fs/promises';
import { computeGridFsChunSize } from '../../../common/file/gridfs/utils';
import { setCron } from '../../../common/system/cron';
import { checkTimerLock } from '../../../common/system/timerLock/utils';
import { TimerIdEnum } from '../../../common/system/timerLock/constants';
import { addLog } from '../../../common/system/log';
const getGridBucket = () => {
return new connectionMongo.mongo.GridFSBucket(connectionMongo.connection.db!, {
bucketName: bucketName
});
};
export const createDatasetImage = async ({
teamId,
datasetId,
file,
expiredTime = addMinutes(new Date(), 30)
}: {
teamId: string;
datasetId: string;
file: FileType;
expiredTime?: Date;
}): Promise<{ imageId: string; previewUrl: string }> => {
const path = file.path;
const gridBucket = getGridBucket();
const metadata = {
teamId: String(teamId),
datasetId: String(datasetId),
expiredTime
};
const stats = await fsp.stat(path);
if (!stats.isFile()) return Promise.reject(`${path} is not a file`);
const readStream = fs.createReadStream(path, {
highWaterMark: 256 * 1024
});
const chunkSizeBytes = computeGridFsChunSize(stats.size);
const stream = gridBucket.openUploadStream(file.originalname, {
metadata,
contentType: file.mimetype,
chunkSizeBytes
});
// save to gridfs
await new Promise((resolve, reject) => {
readStream
.pipe(stream as any)
.on('finish', resolve)
.on('error', reject);
});
return {
imageId: String(stream.id),
previewUrl: ''
};
};
export const getDatasetImageReadData = async (imageId: string) => {
// Get file metadata to get contentType
const fileInfo = await MongoDatasetImageSchema.findOne({
_id: new Types.ObjectId(imageId)
}).lean();
if (!fileInfo) {
return Promise.reject('Image not found');
}
const gridBucket = getGridBucket();
return {
stream: gridBucket.openDownloadStream(new Types.ObjectId(imageId)),
fileInfo
};
};
export const getDatasetImageBase64 = async (imageId: string) => {
// Get file metadata to get contentType
const fileInfo = await MongoDatasetImageSchema.findOne({
_id: new Types.ObjectId(imageId)
}).lean();
if (!fileInfo) {
return Promise.reject('Image not found');
}
// Get image stream from GridFS
const { stream } = await getDatasetImageReadData(imageId);
// Convert stream to buffer
const chunks: Buffer[] = [];
return new Promise<string>((resolve, reject) => {
stream.on('data', (chunk: Buffer) => {
chunks.push(chunk);
});
stream.on('end', () => {
// Combine all chunks into a single buffer
const buffer = Buffer.concat(chunks);
// Convert buffer to base64 string
const base64 = buffer.toString('base64');
const dataUrl = `data:${fileInfo.contentType || 'image/jpeg'};base64,${base64}`;
resolve(dataUrl);
});
stream.on('error', reject);
});
};
export const deleteDatasetImage = async (imageId: string) => {
const gridBucket = getGridBucket();
try {
await gridBucket.delete(new Types.ObjectId(imageId));
} catch (error: any) {
const msg = error?.message;
if (msg.includes('File not found')) {
addLog.warn('Delete dataset image error', error);
return;
} else {
return Promise.reject(error);
}
}
};
export const clearExpiredDatasetImageCron = async () => {
const gridBucket = getGridBucket();
const clearExpiredDatasetImages = async () => {
addLog.debug('Clear expired dataset image start');
const data = await MongoDatasetImageSchema.find(
{
'metadata.expiredTime': { $lt: new Date() }
},
'_id'
).lean();
for (const item of data) {
try {
await gridBucket.delete(item._id);
} catch (error) {
addLog.error('Delete expired dataset image error', error);
}
}
addLog.debug('Clear expired dataset image end');
};
setCron('*/10 * * * *', async () => {
if (
await checkTimerLock({
timerId: TimerIdEnum.clearExpiredDatasetImage,
lockMinuted: 9
})
) {
try {
await clearExpiredDatasetImages();
} catch (error) {
addLog.error('clearExpiredDatasetImageCron error', error);
}
}
});
};

View File

@@ -1,36 +0,0 @@
import type { Types } from '../../../common/mongo';
import { getMongoModel, Schema } from '../../../common/mongo';
export const bucketName = 'dataset_image';
const MongoDatasetImage = new Schema({
length: { type: Number, required: true },
chunkSize: { type: Number, required: true },
uploadDate: { type: Date, required: true },
filename: { type: String, required: true },
contentType: { type: String, required: true },
metadata: {
teamId: { type: String, required: true },
datasetId: { type: String, required: true },
collectionId: { type: String },
expiredTime: { type: Date, required: true }
}
});
MongoDatasetImage.index({ 'metadata.datasetId': 'hashed' });
MongoDatasetImage.index({ 'metadata.collectionId': 'hashed' });
MongoDatasetImage.index({ 'metadata.expiredTime': -1 });
export const MongoDatasetImageSchema = getMongoModel<{
_id: Types.ObjectId;
length: number;
chunkSize: number;
uploadDate: Date;
filename: string;
contentType: string;
metadata: {
teamId: string;
datasetId: string;
collectionId: string;
expiredTime: Date;
};
}>(`${bucketName}.files`, MongoDatasetImage);

View File

@@ -1,101 +0,0 @@
import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode';
import { Types, type ClientSession } from '../../../common/mongo';
import { deleteDatasetImage } from './controller';
import { MongoDatasetImageSchema } from './schema';
import { addMinutes } from 'date-fns';
import jwt from 'jsonwebtoken';
export const removeDatasetImageExpiredTime = async ({
ids = [],
collectionId,
session
}: {
ids?: string[];
collectionId: string;
session?: ClientSession;
}) => {
if (ids.length === 0) return;
return MongoDatasetImageSchema.updateMany(
{
_id: {
$in: ids
.filter((id) => Types.ObjectId.isValid(id))
.map((id) => (typeof id === 'string' ? new Types.ObjectId(id) : id))
}
},
{
$unset: { 'metadata.expiredTime': '' },
$set: {
'metadata.collectionId': String(collectionId)
}
},
{ session }
);
};
export const getDatasetImagePreviewUrl = ({
imageId,
teamId,
datasetId,
expiredMinutes
}: {
imageId: string;
teamId: string;
datasetId: string;
expiredMinutes: number;
}) => {
const expiredTime = Math.floor(addMinutes(new Date(), expiredMinutes).getTime() / 1000);
const key = (process.env.FILE_TOKEN_KEY as string) ?? 'filetoken';
const token = jwt.sign(
{
teamId: String(teamId),
datasetId: String(datasetId),
exp: expiredTime
},
key
);
return `/api/core/dataset/image/${imageId}?token=${token}`;
};
export const authDatasetImagePreviewUrl = (token?: string) =>
new Promise<{
teamId: string;
datasetId: string;
}>((resolve, reject) => {
if (!token) {
return reject(ERROR_ENUM.unAuthFile);
}
const key = (process.env.FILE_TOKEN_KEY as string) ?? 'filetoken';
jwt.verify(token, key, (err, decoded: any) => {
if (err || !decoded?.teamId || !decoded?.datasetId) {
reject(ERROR_ENUM.unAuthFile);
return;
}
resolve({
teamId: decoded.teamId,
datasetId: decoded.datasetId
});
});
});
export const clearDatasetImages = async (datasetIds: string[]) => {
const images = await MongoDatasetImageSchema.find(
{
'metadata.datasetId': { $in: datasetIds.map((item) => String(item)) }
},
'_id'
).lean();
await Promise.all(images.map((image) => deleteDatasetImage(String(image._id))));
};
export const clearCollectionImages = async (collectionIds: string[]) => {
const images = await MongoDatasetImageSchema.find(
{
'metadata.collectionId': { $in: collectionIds.map((item) => String(item)) }
},
'_id'
).lean();
await Promise.all(images.map((image) => deleteDatasetImage(String(image._id))));
};

View File

@@ -9,9 +9,13 @@ import { type TextSplitProps, splitText2Chunks } from '@fastgpt/global/common/st
import axios from 'axios';
import { readRawContentByFileBuffer } from '../../common/file/read/utils';
import { parseFileExtensionFromUrl } from '@fastgpt/global/common/string/tools';
import {
type APIFileServer,
type FeishuServer,
type YuqueServer
} from '@fastgpt/global/core/dataset/apiDataset';
import { getApiDatasetRequest } from './apiDataset';
import Papa from 'papaparse';
import type { ApiDatasetServerType } from '@fastgpt/global/core/dataset/apiDataset/type';
export const readFileRawTextByUrl = async ({
teamId,
@@ -65,7 +69,9 @@ export const readDatasetSourceRawText = async ({
sourceId,
selector,
externalFileId,
apiDatasetServer,
apiServer,
feishuServer,
yuqueServer,
customPdfParse,
getFormatText
}: {
@@ -78,7 +84,9 @@ export const readDatasetSourceRawText = async ({
selector?: string; // link selector
externalFileId?: string; // external file dataset
apiDatasetServer?: ApiDatasetServerType; // api dataset
apiServer?: APIFileServer; // api dataset
feishuServer?: FeishuServer; // feishu dataset
yuqueServer?: YuqueServer; // yuque dataset
}): Promise<{
title?: string;
rawText: string;
@@ -120,7 +128,9 @@ export const readDatasetSourceRawText = async ({
};
} else if (type === DatasetSourceReadTypeEnum.apiFile) {
const { title, rawText } = await readApiServerFileContent({
apiDatasetServer,
apiServer,
feishuServer,
yuqueServer,
apiFileId: sourceId,
teamId,
tmbId
@@ -137,13 +147,17 @@ export const readDatasetSourceRawText = async ({
};
export const readApiServerFileContent = async ({
apiDatasetServer,
apiServer,
feishuServer,
yuqueServer,
apiFileId,
teamId,
tmbId,
customPdfParse
}: {
apiDatasetServer?: ApiDatasetServerType;
apiServer?: APIFileServer;
feishuServer?: FeishuServer;
yuqueServer?: YuqueServer;
apiFileId: string;
teamId: string;
tmbId: string;
@@ -152,7 +166,13 @@ export const readApiServerFileContent = async ({
title?: string;
rawText: string;
}> => {
return (await getApiDatasetRequest(apiDatasetServer)).getFileContent({
return (
await getApiDatasetRequest({
apiServer,
yuqueServer,
feishuServer
})
).getFileContent({
teamId,
tmbId,
apiFileId,
@@ -166,11 +186,9 @@ export const rawText2Chunks = ({
chunkTriggerMinSize = 1000,
backupParse,
chunkSize = 512,
imageIdList,
...splitProps
}: {
rawText: string;
imageIdList?: string[];
chunkTriggerType?: ChunkTriggerConfigTypeEnum;
chunkTriggerMinSize?: number; // maxSize from agent model, not store
@@ -181,7 +199,6 @@ export const rawText2Chunks = ({
q: string;
a: string;
indexes?: string[];
imageIdList?: string[];
}[] => {
const parseDatasetBackup2Chunks = (rawText: string) => {
const csvArr = Papa.parse(rawText).data as string[][];
@@ -192,8 +209,7 @@ export const rawText2Chunks = ({
.map((item) => ({
q: item[0] || '',
a: item[1] || '',
indexes: item.slice(2),
imageIdList
indexes: item.slice(2)
}))
.filter((item) => item.q || item.a);
@@ -215,8 +231,7 @@ export const rawText2Chunks = ({
return [
{
q: rawText,
a: '',
imageIdList
a: ''
}
];
}
@@ -225,7 +240,7 @@ export const rawText2Chunks = ({
if (chunkTriggerType !== ChunkTriggerConfigTypeEnum.forceChunk) {
const textLength = rawText.trim().length;
if (textLength < chunkTriggerMinSize) {
return [{ q: rawText, a: '', imageIdList }];
return [{ q: rawText, a: '' }];
}
}
@@ -238,7 +253,6 @@ export const rawText2Chunks = ({
return chunks.map((item) => ({
q: item,
a: '',
indexes: [],
imageIdList
indexes: []
}));
};

View File

@@ -127,16 +127,14 @@ const DatasetSchema = new Schema({
type: Boolean,
default: true
},
apiDatasetServer: Object,
apiServer: Object,
feishuServer: Object,
yuqueServer: Object,
// abandoned
autoSync: Boolean,
externalReadUrl: String,
defaultPermission: Number,
apiServer: Object,
feishuServer: Object,
yuqueServer: Object
defaultPermission: Number
});
try {

View File

@@ -28,7 +28,6 @@ import type { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { datasetSearchQueryExtension } from './utils';
import type { RerankModelItemType } from '@fastgpt/global/core/ai/model.d';
import { addLog } from '../../../common/system/log';
import { formatDatasetDataValue } from '../data/controller';
export type SearchDatasetDataProps = {
histories: ChatItemType[];
@@ -176,12 +175,6 @@ export async function searchDatasetData(
collectionFilterMatch
} = props;
// Constants data
const datasetDataSelectField =
'_id datasetId collectionId updateTime q a imageId chunkIndex indexes';
const datsaetCollectionSelectField =
'_id name fileId rawLink apiFileId externalFileId externalFileUrl';
/* init params */
searchMode = DatasetSearchModeMap[searchMode] ? searchMode : DatasetSearchModeEnum.embedding;
usingReRank = usingReRank && !!getDefaultRerankModel();
@@ -470,14 +463,14 @@ export async function searchDatasetData(
collectionId: { $in: collectionIdList },
'indexes.dataId': { $in: results.map((item) => item.id?.trim()) }
},
datasetDataSelectField,
'_id datasetId collectionId updateTime q a chunkIndex indexes',
{ ...readFromSecondary }
).lean(),
MongoDatasetCollection.find(
{
_id: { $in: collectionIdList }
},
datsaetCollectionSelectField,
'_id name fileId rawLink apiFileId externalFileId externalFileUrl',
{ ...readFromSecondary }
).lean()
]);
@@ -501,13 +494,8 @@ export async function searchDatasetData(
const result: SearchDataResponseItemType = {
id: String(data._id),
updateTime: data.updateTime,
...formatDatasetDataValue({
teamId,
datasetId: data.datasetId,
q: data.q,
a: data.a,
imageId: data.imageId
}),
q: data.q,
a: data.a,
chunkIndex: data.chunkIndex,
datasetId: String(data.datasetId),
collectionId: String(data.collectionId),
@@ -609,14 +597,14 @@ export async function searchDatasetData(
{
_id: { $in: searchResults.map((item) => item.dataId) }
},
datasetDataSelectField,
'_id datasetId collectionId updateTime q a chunkIndex indexes',
{ ...readFromSecondary }
).lean(),
MongoDatasetCollection.find(
{
_id: { $in: searchResults.map((item) => item.collectionId) }
},
datsaetCollectionSelectField,
'_id name fileId rawLink apiFileId externalFileId externalFileUrl',
{ ...readFromSecondary }
).lean()
]);
@@ -642,13 +630,8 @@ export async function searchDatasetData(
datasetId: String(data.datasetId),
collectionId: String(data.collectionId),
updateTime: data.updateTime,
...formatDatasetDataValue({
teamId,
datasetId: data.datasetId,
q: data.q,
a: data.a,
imageId: data.imageId
}),
q: data.q,
a: data.a,
chunkIndex: data.chunkIndex,
indexes: data.indexes,
...getCollectionSourceData(collection),

View File

@@ -12,7 +12,10 @@ import { getCollectionWithDataset } from '../controller';
import { mongoSessionRun } from '../../../common/mongo/sessionRun';
import { type PushDataToTrainingQueueProps } from '@fastgpt/global/core/dataset/training/type';
import { i18nT } from '../../../../web/i18n/utils';
import { getLLMMaxChunkSize } from '../../../../global/core/dataset/training/utils';
import {
getLLMDefaultChunkSize,
getLLMMaxChunkSize
} from '../../../../global/core/dataset/training/utils';
export const lockTrainingDataByTeamId = async (teamId: string): Promise<any> => {
try {
@@ -62,7 +65,7 @@ export async function pushDataListToTrainingQueue({
const getImageChunkMode = (data: PushDatasetDataChunkProps, mode: TrainingModeEnum) => {
if (mode !== TrainingModeEnum.image) return mode;
// 检查内容中,是否包含 ![](xxx) 的图片格式
const text = (data.q || '') + (data.a || '');
const text = data.q + data.a || '';
const regex = /!\[\]\((.*?)\)/g;
const match = text.match(regex);
if (match) {
@@ -79,6 +82,9 @@ export async function pushDataListToTrainingQueue({
if (!agentModelData) {
return Promise.reject(i18nT('common:error_llm_not_config'));
}
if (mode === TrainingModeEnum.chunk || mode === TrainingModeEnum.auto) {
prompt = undefined;
}
const { model, maxToken, weight } = await (async () => {
if (mode === TrainingModeEnum.chunk) {
@@ -95,7 +101,7 @@ export async function pushDataListToTrainingQueue({
weight: 0
};
}
if (mode === TrainingModeEnum.image || mode === TrainingModeEnum.imageParse) {
if (mode === TrainingModeEnum.image) {
const vllmModelData = getVlmModel(vlmModel);
if (!vllmModelData) {
return Promise.reject(i18nT('common:error_vlm_not_config'));
@@ -111,9 +117,11 @@ export async function pushDataListToTrainingQueue({
})();
// filter repeat or equal content
const set = new Set();
const filterResult: Record<string, PushDatasetDataChunkProps[]> = {
success: [],
overToken: [],
repeat: [],
error: []
};
@@ -132,7 +140,7 @@ export async function pushDataListToTrainingQueue({
.filter(Boolean);
// filter repeat content
if (!item.imageId && !item.q) {
if (!item.q) {
filterResult.error.push(item);
return;
}
@@ -145,26 +153,32 @@ export async function pushDataListToTrainingQueue({
return;
}
filterResult.success.push(item);
if (set.has(text)) {
filterResult.repeat.push(item);
} else {
filterResult.success.push(item);
set.add(text);
}
});
// insert data to db
const insertLen = filterResult.success.length;
const failedDocuments: PushDatasetDataChunkProps[] = [];
// 使用 insertMany 批量插入
const batchSize = 500;
const batchSize = 200;
const insertData = async (startIndex: number, session: ClientSession) => {
const list = filterResult.success.slice(startIndex, startIndex + batchSize);
if (list.length === 0) return;
try {
const result = await MongoDatasetTraining.insertMany(
await MongoDatasetTraining.insertMany(
list.map((item) => ({
teamId,
tmbId,
datasetId: datasetId,
collectionId: collectionId,
datasetId,
collectionId,
billId,
mode: getImageChunkMode(item, mode),
prompt,
@@ -175,25 +189,25 @@ export async function pushDataListToTrainingQueue({
indexSize,
weight: weight ?? 0,
indexes: item.indexes,
retryCount: 5,
...(item.imageId ? { imageId: item.imageId } : {})
retryCount: 5
})),
{
session,
ordered: false,
rawResult: true,
includeResultMetadata: false // 进一步减少返回数据
ordered: true
}
);
if (result.insertedCount !== list.length) {
return Promise.reject(`Insert data error, ${JSON.stringify(result)}`);
}
} catch (error: any) {
addLog.error(`Insert error`, error);
return Promise.reject(error);
// 如果有错误,将失败的文档添加到失败列表中
error.writeErrors?.forEach((writeError: any) => {
failedDocuments.push(data[writeError.index]);
});
console.log('failed', failedDocuments);
}
// 对于失败的文档,尝试单独插入
await MongoDatasetTraining.create(failedDocuments, { session });
return insertData(startIndex + batchSize, session);
};
@@ -208,6 +222,7 @@ export async function pushDataListToTrainingQueue({
delete filterResult.success;
return {
insertLen
insertLen,
...filterResult
};
}

View File

@@ -99,9 +99,6 @@ const TrainingDataSchema = new Schema({
],
default: []
},
imageId: {
type: String
},
errorMsg: String
});

View File

@@ -3,9 +3,9 @@ import type {
ApiFileReadContentResponse,
YuqueServer,
ApiDatasetDetailResponse
} from '@fastgpt/global/core/dataset/apiDataset/type';
} from '@fastgpt/global/core/dataset/apiDataset';
import axios, { type Method } from 'axios';
import { addLog } from '../../../../common/system/log';
import { addLog } from '../../../common/system/log';
import { type ParentIdType } from '@fastgpt/global/common/parentFolder/type';
type ResponseDataType = {
@@ -105,6 +105,7 @@ export const useYuqueDatasetRequest = ({ yuqueServer }: { yuqueServer: YuqueServ
if (!parentId) {
if (yuqueServer.basePath) parentId = yuqueServer.basePath;
}
let files: APIFileItem[] = [];
if (!parentId) {

View File

@@ -358,7 +358,7 @@ async function filterDatasetQuote({
return replaceVariable(quoteTemplate, {
id: item.id,
q: item.q,
a: item.a || '',
a: item.a,
updateTime: formatTime2YMDHM(item.updateTime),
source: item.sourceName,
sourceId: String(item.sourceId || ''),

View File

@@ -2,7 +2,6 @@ import { OperationLogEventEnum } from '@fastgpt/global/support/operationLog/cons
import { i18nT } from '../../../web/i18n/utils';
export const operationLogMap = {
//Team
[OperationLogEventEnum.LOGIN]: {
content: i18nT('account_team:log_login'),
typeLabel: i18nT('account_team:login'),
@@ -67,309 +66,6 @@ export const operationLogMap = {
content: i18nT('account_team:log_assign_permission'),
typeLabel: i18nT('account_team:assign_permission'),
params: {} as { name?: string; objectName: string; permission: string }
},
//APP
[OperationLogEventEnum.CREATE_APP]: {
content: i18nT('account_team:log_create_app'),
typeLabel: i18nT('account_team:create_app'),
params: {} as { name?: string; appName: string; appType: string }
},
[OperationLogEventEnum.UPDATE_APP_INFO]: {
content: i18nT('account_team:log_update_app_info'),
typeLabel: i18nT('account_team:update_app_info'),
params: {} as {
name?: string;
appName: string;
newItemNames: string[];
newItemValues: string[];
appType: string;
}
},
[OperationLogEventEnum.MOVE_APP]: {
content: i18nT('account_team:log_move_app'),
typeLabel: i18nT('account_team:move_app'),
params: {} as { name?: string; appName: string; targetFolderName: string; appType: string }
},
[OperationLogEventEnum.DELETE_APP]: {
content: i18nT('account_team:log_delete_app'),
typeLabel: i18nT('account_team:delete_app'),
params: {} as { name?: string; appName: string; appType: string }
},
[OperationLogEventEnum.UPDATE_APP_COLLABORATOR]: {
content: i18nT('account_team:log_update_app_collaborator'),
typeLabel: i18nT('account_team:update_app_collaborator'),
params: {} as {
name?: string;
appName: string;
appType: string;
tmbList: string[];
groupList: string[];
orgList: string[];
permission: string;
}
},
[OperationLogEventEnum.DELETE_APP_COLLABORATOR]: {
content: i18nT('account_team:log_delete_app_collaborator'),
typeLabel: i18nT('account_team:delete_app_collaborator'),
params: {} as {
name?: string;
appName: string;
appType: string;
itemName: string;
itemValueName: string;
}
},
[OperationLogEventEnum.TRANSFER_APP_OWNERSHIP]: {
content: i18nT('account_team:log_transfer_app_ownership'),
typeLabel: i18nT('account_team:transfer_app_ownership'),
params: {} as {
name?: string;
appName: string;
appType: string;
oldOwnerName: string;
newOwnerName: string;
}
},
[OperationLogEventEnum.CREATE_APP_COPY]: {
content: i18nT('account_team:log_create_app_copy'),
typeLabel: i18nT('account_team:create_app_copy'),
params: {} as { name?: string; appName: string; appType: string }
},
[OperationLogEventEnum.CREATE_APP_FOLDER]: {
content: i18nT('account_team:log_create_app_folder'),
typeLabel: i18nT('account_team:create_app_folder'),
params: {} as { name?: string; folderName: string }
},
[OperationLogEventEnum.UPDATE_PUBLISH_APP]: {
content: i18nT('account_team:log_update_publish_app'),
typeLabel: i18nT('account_team:update_publish_app'),
params: {} as {
name?: string;
operationName: string;
appName: string;
appId: string;
appType: string;
}
},
[OperationLogEventEnum.CREATE_APP_PUBLISH_CHANNEL]: {
content: i18nT('account_team:log_create_app_publish_channel'),
typeLabel: i18nT('account_team:create_app_publish_channel'),
params: {} as { name?: string; appName: string; channelName: string; appType: string }
},
[OperationLogEventEnum.UPDATE_APP_PUBLISH_CHANNEL]: {
content: i18nT('account_team:log_update_app_publish_channel'),
typeLabel: i18nT('account_team:update_app_publish_channel'),
params: {} as { name?: string; appName: string; channelName: string; appType: string }
},
[OperationLogEventEnum.DELETE_APP_PUBLISH_CHANNEL]: {
content: i18nT('account_team:log_delete_app_publish_channel'),
typeLabel: i18nT('account_team:delete_app_publish_channel'),
params: {} as { name?: string; appName: string; channelName: string; appType: string }
},
[OperationLogEventEnum.EXPORT_APP_CHAT_LOG]: {
content: i18nT('account_team:log_export_app_chat_log'),
typeLabel: i18nT('account_team:export_app_chat_log'),
params: {} as { name?: string; appName: string; appType: string }
},
//Dataset
[OperationLogEventEnum.CREATE_DATASET]: {
content: i18nT('account_team:log_create_dataset'),
typeLabel: i18nT('account_team:create_dataset'),
params: {} as { name?: string; datasetName: string; datasetType: string }
},
[OperationLogEventEnum.UPDATE_DATASET]: {
content: i18nT('account_team:log_update_dataset'),
typeLabel: i18nT('account_team:update_dataset'),
params: {} as { name?: string; datasetName: string; datasetType: string }
},
[OperationLogEventEnum.DELETE_DATASET]: {
content: i18nT('account_team:log_delete_dataset'),
typeLabel: i18nT('account_team:delete_dataset'),
params: {} as { name?: string; datasetName: string; datasetType: string }
},
[OperationLogEventEnum.MOVE_DATASET]: {
content: i18nT('account_team:log_move_dataset'),
typeLabel: i18nT('account_team:move_dataset'),
params: {} as {
name?: string;
datasetName: string;
targetFolderName: string;
datasetType: string;
}
},
[OperationLogEventEnum.UPDATE_DATASET_COLLABORATOR]: {
content: i18nT('account_team:log_update_dataset_collaborator'),
typeLabel: i18nT('account_team:update_dataset_collaborator'),
params: {} as {
name?: string;
datasetName: string;
datasetType: string;
tmbList: string[];
groupList: string[];
orgList: string[];
permission: string;
}
},
[OperationLogEventEnum.DELETE_DATASET_COLLABORATOR]: {
content: i18nT('account_team:log_delete_dataset_collaborator'),
typeLabel: i18nT('account_team:delete_dataset_collaborator'),
params: {} as {
name?: string;
datasetName: string;
datasetType: string;
itemName: string;
itemValueName: string;
}
},
[OperationLogEventEnum.TRANSFER_DATASET_OWNERSHIP]: {
content: i18nT('account_team:log_transfer_dataset_ownership'),
typeLabel: i18nT('account_team:transfer_dataset_ownership'),
params: {} as {
name?: string;
datasetName: string;
datasetType: string;
oldOwnerName: string;
newOwnerName: string;
}
},
[OperationLogEventEnum.EXPORT_DATASET]: {
content: i18nT('account_team:log_export_dataset'),
typeLabel: i18nT('account_team:export_dataset'),
params: {} as { name?: string; datasetName: string; datasetType: string }
},
[OperationLogEventEnum.CREATE_DATASET_FOLDER]: {
content: i18nT('account_team:log_create_dataset_folder'),
typeLabel: i18nT('account_team:create_dataset_folder'),
params: {} as { name?: string; folderName: string }
},
//Collection
[OperationLogEventEnum.CREATE_COLLECTION]: {
content: i18nT('account_team:log_create_collection'),
typeLabel: i18nT('account_team:create_collection'),
params: {} as {
name?: string;
collectionName: string;
datasetName: string;
datasetType: string;
}
},
[OperationLogEventEnum.UPDATE_COLLECTION]: {
content: i18nT('account_team:log_update_collection'),
typeLabel: i18nT('account_team:update_collection'),
params: {} as {
name?: string;
collectionName: string;
datasetName: string;
datasetType: string;
}
},
[OperationLogEventEnum.DELETE_COLLECTION]: {
content: i18nT('account_team:log_delete_collection'),
typeLabel: i18nT('account_team:delete_collection'),
params: {} as {
name?: string;
collectionName: string;
datasetName: string;
datasetType: string;
}
},
[OperationLogEventEnum.RETRAIN_COLLECTION]: {
content: i18nT('account_team:log_retrain_collection'),
typeLabel: i18nT('account_team:retrain_collection'),
params: {} as {
name?: string;
collectionName: string;
datasetName: string;
datasetType: string;
}
},
//Data
[OperationLogEventEnum.CREATE_DATA]: {
content: i18nT('account_team:log_create_data'),
typeLabel: i18nT('account_team:create_data'),
params: {} as {
name?: string;
collectionName: string;
datasetName: string;
datasetType: string;
}
},
[OperationLogEventEnum.UPDATE_DATA]: {
content: i18nT('account_team:log_update_data'),
typeLabel: i18nT('account_team:update_data'),
params: {} as {
name?: string;
collectionName: string;
datasetName: string;
datasetType: string;
}
},
[OperationLogEventEnum.DELETE_DATA]: {
content: i18nT('account_team:log_delete_data'),
typeLabel: i18nT('account_team:delete_data'),
params: {} as {
name?: string;
collectionName: string;
datasetName: string;
datasetType: string;
}
},
//SearchTest
[OperationLogEventEnum.SEARCH_TEST]: {
content: i18nT('account_team:log_search_test'),
typeLabel: i18nT('account_team:search_test'),
params: {} as { name?: string; datasetName: string; datasetType: string }
},
//Account
[OperationLogEventEnum.CHANGE_PASSWORD]: {
content: i18nT('account_team:log_change_password'),
typeLabel: i18nT('account_team:change_password'),
params: {} as { name?: string }
},
[OperationLogEventEnum.CHANGE_NOTIFICATION_SETTINGS]: {
content: i18nT('account_team:log_change_notification_settings'),
typeLabel: i18nT('account_team:change_notification_settings'),
params: {} as { name?: string }
},
[OperationLogEventEnum.CHANGE_MEMBER_NAME_ACCOUNT]: {
content: i18nT('account_team:log_change_member_name_self'),
typeLabel: i18nT('account_team:change_member_name_self'),
params: {} as { name?: string; oldName: string; newName: string }
},
[OperationLogEventEnum.PURCHASE_PLAN]: {
content: i18nT('account_team:log_purchase_plan'),
typeLabel: i18nT('account_team:purchase_plan'),
params: {} as { name?: string }
},
[OperationLogEventEnum.EXPORT_BILL_RECORDS]: {
content: i18nT('account_team:log_export_bill_records'),
typeLabel: i18nT('account_team:export_bill_records'),
params: {} as { name?: string }
},
[OperationLogEventEnum.CREATE_INVOICE]: {
content: i18nT('account_team:log_create_invoice'),
typeLabel: i18nT('account_team:create_invoice'),
params: {} as { name?: string }
},
[OperationLogEventEnum.SET_INVOICE_HEADER]: {
content: i18nT('account_team:log_set_invoice_header'),
typeLabel: i18nT('account_team:set_invoice_header'),
params: {} as { name?: string }
},
[OperationLogEventEnum.CREATE_API_KEY]: {
content: i18nT('account_team:log_create_api_key'),
typeLabel: i18nT('account_team:create_api_key'),
params: {} as { name?: string; keyName: string }
},
[OperationLogEventEnum.UPDATE_API_KEY]: {
content: i18nT('account_team:log_update_api_key'),
typeLabel: i18nT('account_team:update_api_key'),
params: {} as { name?: string; keyName: string }
},
[OperationLogEventEnum.DELETE_API_KEY]: {
content: i18nT('account_team:log_delete_api_key'),
typeLabel: i18nT('account_team:delete_api_key'),
params: {} as { name?: string; keyName: string }
}
} as const;

View File

@@ -1,36 +0,0 @@
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
import { i18nT } from '../../../web/i18n/utils';
export function getI18nAppType(type: AppTypeEnum): string {
if (type === AppTypeEnum.folder) return i18nT('account_team:type.Folder');
if (type === AppTypeEnum.simple) return i18nT('account_team:type.Simple bot');
if (type === AppTypeEnum.workflow) return i18nT('account_team:type.Workflow bot');
if (type === AppTypeEnum.plugin) return i18nT('account_team:type.Plugin');
if (type === AppTypeEnum.httpPlugin) return i18nT('account_team:type.Http plugin');
if (type === AppTypeEnum.toolSet) return i18nT('account_team:type.Tool set');
if (type === AppTypeEnum.tool) return i18nT('account_team:type.Tool');
return i18nT('common:UnKnow');
}
export function getI18nCollaboratorItemType(
tmbId: string | undefined,
groupId: string | undefined,
orgId: string | undefined
): string {
if (tmbId) return i18nT('account_team:member');
if (groupId) return i18nT('account_team:group');
if (orgId) return i18nT('account_team:department');
return i18nT('common:UnKnow');
}
export function getI18nDatasetType(type: DatasetTypeEnum | string): string {
if (type === DatasetTypeEnum.folder) return i18nT('account_team:dataset.folder_dataset');
if (type === DatasetTypeEnum.dataset) return i18nT('account_team:dataset.common_dataset');
if (type === DatasetTypeEnum.websiteDataset) return i18nT('account_team:dataset.website_dataset');
if (type === DatasetTypeEnum.externalFile) return i18nT('account_team:dataset.external_file');
if (type === DatasetTypeEnum.apiDataset) return i18nT('account_team:dataset.api_file');
if (type === DatasetTypeEnum.feishu) return i18nT('account_team:dataset.feishu_dataset');
if (type === DatasetTypeEnum.yuque) return i18nT('account_team:dataset.yuque_dataset');
return i18nT('common:UnKnow');
}

View File

@@ -137,7 +137,7 @@ export const authApp = async ({
appId: ParentIdType;
per: PermissionValueType;
}): Promise<
AuthResponseType & {
AuthResponseType<AppPermission> & {
app: AppDetailType;
}
> => {

View File

@@ -16,7 +16,6 @@ import { type AuthModeType, type AuthResponseType } from '../type';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
import { type ParentIdType } from '@fastgpt/global/common/parentFolder/type';
import { DatasetDefaultPermissionVal } from '@fastgpt/global/support/permission/dataset/constant';
import { getDatasetImagePreviewUrl } from '../../../core/dataset/image/utils';
export const authDatasetByTmbId = async ({
tmbId,
@@ -268,15 +267,6 @@ export async function authDatasetData({
updateTime: datasetData.updateTime,
q: datasetData.q,
a: datasetData.a,
imageId: datasetData.imageId,
imagePreivewUrl: datasetData.imageId
? getDatasetImagePreviewUrl({
imageId: datasetData.imageId,
teamId: datasetData.teamId,
datasetId: datasetData.datasetId,
expiredMinutes: 30
})
: undefined,
chunkIndex: datasetData.chunkIndex,
indexes: datasetData.indexes,
datasetId: String(datasetData.datasetId),

View File

@@ -0,0 +1,568 @@
import { MongoTeamGate } from './schema';
import { Types } from '../../../../common/mongo';
import type { ClientSession } from '../../../../common/mongo';
import { mongoSessionRun } from '../../../../common/mongo/sessionRun';
import {
GateFeaturedAppPermission,
GateQuickAppPermission
} from '@fastgpt/global/support/permission/app/constant';
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
import { MongoMemberGroupModel } from '../../../permission/memberGroup/memberGroupSchema';
import { MongoResourcePermission } from '../../../permission/schema';
export const addGatePermission = async ({
teamId,
appId,
per,
session
}: {
teamId: string;
appId: string;
per: number;
session?: ClientSession;
}) => {
// 1. 先找全员组
const teamGroup = await MongoMemberGroupModel.findOne({
teamId,
name: DefaultGroupName
});
if (!teamGroup) {
return Promise.reject('找不到全员组');
}
// 2. 加权限
await MongoResourcePermission.updateOne(
{
teamId,
groupId: teamGroup?._id,
resourceType: PerResourceTypeEnum.app,
resourceId: appId
},
{
permission: per
},
{
session,
upsert: true
}
);
};
export const removeGatePermission = async ({
teamId,
appId,
per,
session
}: {
teamId: string;
appId: string;
per: number;
session?: ClientSession;
}) => {
// 1. 先找全员组
const teamGroup = await MongoMemberGroupModel.findOne({
teamId,
name: DefaultGroupName
});
if (!teamGroup) {
return Promise.reject('找不到全员组');
}
await MongoResourcePermission.deleteOne(
{
teamId,
groupId: teamGroup?._id,
resourceType: PerResourceTypeEnum.app,
resourceId: appId,
permission: per
},
{
session
}
);
};
/**
* 创建团队门户配置
*/
export const createGateConfig = async ({ teamId }: { teamId: string }) => {
const gate = await MongoTeamGate.create({
teamId
});
return gate.toObject();
};
/**
* 获取团队门户配置
*/
export const getGateConfig = async (teamId: string) => {
const gate = await MongoTeamGate.findOne({ teamId }).lean();
return gate;
};
/**
* 更新团队门户配置
*/
export const updateGateConfig = async ({
teamId,
status,
name,
banner,
logo,
tools,
placeholderText
}: {
teamId: string;
status?: boolean;
name?: string;
banner?: string;
logo?: string;
tools?: string[];
placeholderText?: string;
}) => {
const updateData: Record<string, any> = {};
if (status !== undefined) updateData.status = status;
if (name !== undefined) updateData.name = name;
if (banner !== undefined) updateData.banner = banner;
if (logo !== undefined) updateData.logo = logo;
if (tools !== undefined) updateData.tools = tools;
if (placeholderText !== undefined) updateData.placeholderText = placeholderText;
// 使用 upsert 选项,如果不存在则创建
await MongoTeamGate.updateOne({ teamId }, { $set: updateData }, { upsert: true });
return MongoTeamGate.findOne({ teamId }).lean();
};
/**
* 删除团队门户配置
*/
export const deleteGateConfig = async (teamId: string) => {
await MongoTeamGate.deleteOne({ teamId });
return true;
};
/**
* 启用或禁用团队门户
*/
export const toggleGateStatus = async ({ teamId, status }: { teamId: string; status: boolean }) => {
await MongoTeamGate.updateOne({ teamId }, { $set: { status } }, { upsert: true });
return MongoTeamGate.findOne({ teamId }).lean();
};
/**
* 更新门户工具配置
*/
export const updateGateTools = async ({ teamId, tools }: { teamId: string; tools: string[] }) => {
await MongoTeamGate.updateOne({ teamId }, { $set: { tools } }, { upsert: true });
return MongoTeamGate.findOne({ teamId }).lean();
};
/**
* 添加门户工具
*/
export const addGateTool = async ({ teamId, tool }: { teamId: string; tool: string }) => {
await MongoTeamGate.updateOne({ teamId }, { $addToSet: { tools: tool } }, { upsert: true });
return MongoTeamGate.findOne({ teamId }).lean();
};
/**
* 移除门户工具
*/
export const removeGateTool = async ({ teamId, tool }: { teamId: string; tool: string }) => {
await MongoTeamGate.updateOne({ teamId }, { $pull: { tools: tool } });
return MongoTeamGate.findOne({ teamId }).lean();
};
/**
* 更新特色应用列表
*/
export const updateFeaturedApps = async ({
teamId,
featuredApps
}: {
teamId: string;
featuredApps: string[];
}) => {
// 将字符串数组转换为 ObjectId 数组
const objectIdArray = featuredApps.map((id) => new Types.ObjectId(id));
await MongoTeamGate.updateOne(
{ teamId },
{ $set: { featuredApps: objectIdArray } },
{ upsert: true }
);
return MongoTeamGate.findOne({ teamId }).lean();
};
/**
* 添加特色应用
*/
export const addFeaturedApp = async ({ teamId, appId }: { teamId: string; appId: string }) => {
await MongoTeamGate.updateOne(
{ teamId },
{ $addToSet: { featuredApps: new Types.ObjectId(appId) } },
{ upsert: true }
);
return MongoTeamGate.findOne({ teamId }).lean();
};
/**
* 删除特色应用
*/
export const removeFeaturedApp = async ({
teamId,
appId,
session
}: {
teamId: string;
appId: string;
session?: ClientSession;
}) => {
await MongoTeamGate.updateOne(
{ teamId },
{ $pull: { featuredApps: new Types.ObjectId(appId) } },
{ session }
);
return MongoTeamGate.findOne({ teamId }).lean();
};
/**
* 移动特色应用位置(原子操作)
* @param teamId 团队ID
* @param appId 要移动的应用ID
* @param toIndex 目标位置索引
*/
export const moveFeatureAppToPosition = async ({
teamId,
appId,
toIndex
}: {
teamId: string;
appId: string;
toIndex: number;
}) => {
const objectId = new Types.ObjectId(appId);
// 获取当前配置
const config = await MongoTeamGate.findOne({ teamId }).lean();
if (!config || !config.featuredApps) {
throw new Error('团队配置不存在');
}
const apps = [...config.featuredApps];
const currentIndex = apps.findIndex((id) => id.toString() === appId);
if (currentIndex === -1) {
throw new Error('应用不在特色应用列表中');
}
// 移动数组元素
const [movedApp] = apps.splice(currentIndex, 1);
apps.splice(toIndex, 0, movedApp);
// 一次性更新
await MongoTeamGate.updateOne({ teamId }, { $set: { featuredApps: apps } });
return MongoTeamGate.findOne({ teamId }).lean();
};
/**
* 更新工具排序
* @param teamId 团队ID
* @param orderedTools 按新顺序排列的工具数组
*/
export const reorderTools = async ({
teamId,
orderedTools
}: {
teamId: string;
orderedTools: string[];
}) => {
await MongoTeamGate.updateOne({ teamId }, { $set: { tools: orderedTools } });
return MongoTeamGate.findOne({ teamId }).lean();
};
/**
* 批量更新门户配置
*/
export const batchUpdateGateConfigs = async (
configs: {
teamId: string;
status?: boolean;
banner?: string;
logo?: string;
tools?: string[];
placeholderText?: string;
}[]
) => {
const operations = configs.map((config) => {
const { teamId, ...updateData } = config;
return {
updateOne: {
filter: { teamId },
update: { $set: updateData },
upsert: true
}
};
});
if (operations.length === 0) {
return true;
}
await MongoTeamGate.bulkWrite(operations);
return true;
};
/**
* 批量更新特色应用
*/
export const batchUpdateFeaturedApps = async (
updates: {
teamId: string;
featuredApps: string[];
}[]
) => {
const operations = updates.map((update) => {
const { teamId, featuredApps } = update;
// 将字符串数组转换为 ObjectId 数组
const objectIdArray = featuredApps.map((id) => new Types.ObjectId(id));
return {
updateOne: {
filter: { teamId },
update: { $set: { featuredApps: objectIdArray } },
upsert: true
}
};
});
if (operations.length === 0) {
return true;
}
const teamId = updates[0]?.teamId;
const gateConfig = await MongoTeamGate.findOne({ teamId });
if (!gateConfig) return Promise.reject('无 gate 配置');
const updatedAppId = updates[0].featuredApps;
const deleteAppId = gateConfig.featuredApps.filter((id) => !updatedAppId.includes(id));
await mongoSessionRun(async (session) => {
await MongoTeamGate.bulkWrite(operations, { session });
for (const id of deleteAppId) {
await removeGatePermission({
teamId,
appId: id,
per: GateFeaturedAppPermission,
session
});
}
for (const id of updatedAppId) {
await addGatePermission({
teamId,
appId: id,
per: GateFeaturedAppPermission,
session
});
}
});
return true;
};
/**
* 批量更新工具排序
*/
export const batchUpdateToolsOrder = async (
updates: {
teamId: string;
tools: string[];
}[]
) => {
const operations = updates.map((update) => {
const { teamId, tools } = update;
return {
updateOne: {
filter: { teamId },
update: { $set: { tools } },
upsert: true
}
};
});
if (operations.length === 0) {
return true;
}
await MongoTeamGate.bulkWrite(operations);
return true;
};
/**
* 批量删除特色应用
* @param teamId 团队ID
* @param appIds 要删除的应用ID数组
*/
export const batchDeleteFeaturedApps = async ({
teamId,
appIds,
session
}: {
teamId: string;
appIds: string[];
session?: ClientSession;
}) => {
if (!appIds || appIds.length === 0) {
return false;
}
await MongoTeamGate.updateOne(
{ teamId },
{ $pull: { featuredApps: { $in: appIds.map((id) => new Types.ObjectId(id)) } } },
{
session
}
);
return true;
};
/**
* 更新快速应用列表
*/
export const updateQuickApps = async ({
teamId,
quickApps
}: {
teamId: string;
quickApps: string[];
}) => {
await MongoTeamGate.updateOne({ teamId }, { $set: { quickApps } }, { upsert: true });
return MongoTeamGate.findOne({ teamId }).lean();
};
/**
* 添加快速应用
*/
export const addQuickApp = async ({ teamId, appId }: { teamId: string; appId: string }) => {
await MongoTeamGate.updateOne(
{ teamId },
{ $addToSet: { quickApps: new Types.ObjectId(appId) } },
{ upsert: true }
);
return MongoTeamGate.findOne({ teamId }).lean();
};
/**
* 删除快速应用
*/
export const removeQuickApp = async ({ teamId, appId }: { teamId: string; appId: string }) => {
await MongoTeamGate.updateOne({ teamId }, { $pull: { quickApps: new Types.ObjectId(appId) } });
return MongoTeamGate.findOne({ teamId }).lean();
};
/**
* 移动快速应用位置(原子操作)
* @param teamId 团队ID
* @param appId 要移动的应用ID
* @param toIndex 目标位置索引
*/
export const moveQuickAppToPosition = async ({
teamId,
appId,
toIndex
}: {
teamId: string;
appId: string;
toIndex: number;
}) => {
const objectId = new Types.ObjectId(appId);
// 获取当前配置
const config = await MongoTeamGate.findOne({ teamId }).lean();
if (!config || !config.quickApps) {
throw new Error('团队配置不存在');
}
const apps = [...config.quickApps];
const currentIndex = apps.findIndex((id) => id.toString() === appId);
if (currentIndex === -1) {
throw new Error('应用不在快速应用列表中');
}
// 移动数组元素
const [movedApp] = apps.splice(currentIndex, 1);
apps.splice(toIndex, 0, movedApp);
// 一次性更新
await MongoTeamGate.updateOne({ teamId }, { $set: { quickApps: apps } });
return MongoTeamGate.findOne({ teamId }).lean();
};
/**
* 批量更新快速应用
*/
export const batchUpdateQuickApps = async (teamId: string, quickApps: string[]) => {
const gateConfig = await MongoTeamGate.findOne({ teamId });
if (!gateConfig) {
return false;
}
// 计算删除的appId
const deleteAppIds = gateConfig.quickApps.filter((id) => !quickApps.includes(id.toString()));
return mongoSessionRun(async (session) => {
// 1. 删除权限
for (const id of deleteAppIds) {
await removeGatePermission({
teamId,
appId: id,
per: GateQuickAppPermission,
session
});
}
// 加权限
for (const id of quickApps) {
await addGatePermission({
teamId,
appId: id,
per: GateQuickAppPermission,
session
});
}
gateConfig.quickApps = quickApps;
await gateConfig.save({ session });
return true;
});
};
/**
* 批量删除快速应用
* @param teamId 团队ID
* @param appIds 要删除的应用ID数组
*/
export const batchDeleteQuickApps = async ({
teamId,
appIds
}: {
teamId: string;
appIds: string[];
}) => {
if (!appIds || appIds.length === 0) {
return false;
}
await MongoTeamGate.updateOne(
{ teamId },
{ $pull: { quickApps: { $in: appIds.map((id) => new Types.ObjectId(id)) } } }
);
return true;
};

View File

@@ -0,0 +1,45 @@
import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant';
import { Schema, getMongoModel } from '../../../../common/mongo';
import type { GateSchemaType } from '@fastgpt/global/support/user/team/gate/type';
export const gateCollectionName = 'team_gate_config';
const GateConfigSchema = new Schema({
teamId: {
type: Schema.Types.ObjectId,
ref: TeamCollectionName
},
status: {
type: Boolean,
default: true
},
name: {
type: String
},
banner: {
type: String
},
logo: {
type: String
},
tools: {
type: [String]
},
placeholderText: {
type: String
},
featuredApps: [
{
type: Schema.Types.ObjectId,
ref: 'apps'
}
],
quickApps: [
{
type: Schema.Types.ObjectId,
ref: 'apps'
}
]
});
export const MongoTeamGate = getMongoModel<GateSchemaType>(gateCollectionName, GateConfigSchema);

View File

@@ -17,6 +17,11 @@ const TeamSchema = new Schema({
type: String,
default: '/icon/logo.svg'
},
// todo :banner
banner: {
type: String,
default: '/icon/banner.svg'
},
createTime: {
type: Date,
default: () => Date.now()

View File

@@ -1,7 +1,7 @@
import { getWorkerController, WorkerNameEnum } from './utils';
export const preLoadWorker = async () => {
const max = Math.min(Number(global.systemEnv?.tokenWorkers || 30), 100);
const max = Number(global.systemEnv?.tokenWorkers || 30);
const workerController = getWorkerController({
name: WorkerNameEnum.countGptMessagesTokens,
maxReservedThreads: max

View File

@@ -220,11 +220,9 @@ export const iconPaths = {
import('./icons/core/dataset/feishuDatasetOutline.svg'),
'core/dataset/fileCollection': () => import('./icons/core/dataset/fileCollection.svg'),
'core/dataset/fullTextRecall': () => import('./icons/core/dataset/fullTextRecall.svg'),
'core/dataset/imageFill': () => import('./icons/core/dataset/imageFill.svg'),
'core/dataset/manualCollection': () => import('./icons/core/dataset/manualCollection.svg'),
'core/dataset/mixedRecall': () => import('./icons/core/dataset/mixedRecall.svg'),
'core/dataset/modeEmbedding': () => import('./icons/core/dataset/modeEmbedding.svg'),
'core/dataset/otherDataset': () => import('./icons/core/dataset/otherDataset.svg'),
'core/dataset/questionExtension': () => import('./icons/core/dataset/questionExtension.svg'),
'core/dataset/rerank': () => import('./icons/core/dataset/rerank.svg'),
'core/dataset/searchfilter': () => import('./icons/core/dataset/searchfilter.svg'),
@@ -232,6 +230,7 @@ export const iconPaths = {
'core/dataset/tableCollection': () => import('./icons/core/dataset/tableCollection.svg'),
'core/dataset/tag': () => import('./icons/core/dataset/tag.svg'),
'core/dataset/websiteDataset': () => import('./icons/core/dataset/websiteDataset.svg'),
'core/dataset/otherDataset': () => import('./icons/core/dataset/otherDataset.svg'),
'core/dataset/websiteDatasetColor': () => import('./icons/core/dataset/websiteDatasetColor.svg'),
'core/dataset/websiteDatasetOutline': () =>
import('./icons/core/dataset/websiteDatasetOutline.svg'),
@@ -380,12 +379,10 @@ export const iconPaths = {
fullScreen: () => import('./icons/fullScreen.svg'),
help: () => import('./icons/help.svg'),
history: () => import('./icons/history.svg'),
image: () => import('./icons/image.svg'),
infoRounded: () => import('./icons/infoRounded.svg'),
kbTest: () => import('./icons/kbTest.svg'),
key: () => import('./icons/key.svg'),
keyPrimary: () => import('./icons/keyPrimary.svg'),
loading: () => import('./icons/loading.svg'),
menu: () => import('./icons/menu.svg'),
minus: () => import('./icons/minus.svg'),
'modal/AddClb': () => import('./icons/modal/AddClb.svg'),
@@ -477,6 +474,31 @@ export const iconPaths = {
'support/user/userLightSmall': () => import('./icons/support/user/userLightSmall.svg'),
'support/user/usersFill': () => import('./icons/support/user/usersFill.svg'),
'support/user/usersLight': () => import('./icons/support/user/usersLight.svg'),
'support/gate/gateLight': () => import('./icons/support/gate/gateLight.svg'),
'support/gate/chat/sidebar/chatGray': () =>
import('./icons/support/gate/chat/sidebar/chatGray.svg'),
'support/gate/chat/historySlider/new_chat': () =>
import('./icons/support/gate/chat/historySlider/new_chat.svg'),
'support/gate/chat/historySlider/clear-all': () =>
import('./icons/support/gate/chat/historySlider/clear-all.svg'),
'support/gate/chat/historySlider/chevron-right2': () =>
import('./icons/support/gate/chat/historySlider/chevron-right2.svg'),
'support/gate/chat/toolkitLine': () => import('./icons/support/gate/chat/toolkitLine.svg'),
'support/gate/chat/historySlider/chevron-left2': () =>
import('./icons/support/gate/chat/historySlider/chevron-left2.svg'),
'support/gate/chat/sidebar/appGray': () =>
import('./icons/support/gate/chat/sidebar/appGray.svg'),
'support/gate/chat/voiceGray': () => import('./icons/support/gate/chat/voiceGray.svg'),
'support/gate/chat/fileGray': () => import('./icons/support/gate/chat/fileGray.svg'),
'support/gate/chat/paperclip': () => import('./icons/support/gate/chat/paperclip.svg'),
'support/gate/chat/imageGray': () => import('./icons/support/gate/chat/imageGray.svg'),
'support/gate/chat/sidebar/CollapseButton': () =>
import('./icons/support/gate/chat/sidebar/CollapseButton.svg'),
'support/gate/home/savePrimary': () => import('./icons/support/gate/home/savePrimary.svg'),
'support/gate/home/shareLight': () => import('./icons/support/gate/home/shareLight.svg'),
'support/gate/home/sharePrimary': () => import('./icons/support/gate/home/sharePrimary.svg'),
'support/gate/home/upload': () => import('./icons/support/gate/home/upload.svg'),
'support/gate/home/add': () => import('./icons/support/gate/home/add.svg'),
text: () => import('./icons/text.svg'),
union: () => import('./icons/union.svg'),
user: () => import('./icons/user.svg'),

View File

@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 20" >
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.24348 4.15292C1.9165 4.79466 1.9165 5.63474 1.9165 7.31489V12.6852C1.9165 14.3654 1.9165 15.2054 2.24348 15.8472C2.5311 16.4117 2.99005 16.8706 3.55453 17.1582C4.19627 17.4852 5.03635 17.4852 6.7165 17.4852H13.7832C15.4633 17.4852 16.3034 17.4852 16.9451 17.1582C17.5096 16.8706 17.9686 16.4117 18.2562 15.8472C18.5832 15.2054 18.5832 14.3654 18.5832 12.6852V7.31489C18.5832 5.63473 18.5832 4.79466 18.2562 4.15292C17.9686 3.58843 17.5096 3.12949 16.9451 2.84187C16.3034 2.51489 15.4633 2.51489 13.7832 2.51489H6.7165C5.03635 2.51489 4.19627 2.51489 3.55453 2.84187C2.99005 3.12949 2.5311 3.58843 2.24348 4.15292ZM7.88951 6.75656C7.88951 7.67703 7.14331 8.42322 6.22284 8.42322C5.30236 8.42322 4.55617 7.67703 4.55617 6.75656C4.55617 5.83608 5.30236 5.08989 6.22284 5.08989C7.14331 5.08989 7.88951 5.83608 7.88951 6.75656ZM12.8631 8.65525C12.5376 8.32981 12.01 8.32981 11.6845 8.65525L5.92965 14.4101C5.40468 14.9351 5.77648 15.8327 6.5189 15.8327L15.5062 15.8327C16.4267 15.8327 17.1729 15.0865 17.1729 14.1661V13.3103C17.1729 13.0892 17.0851 12.8773 16.9288 12.721L12.8631 8.65525Z" fill="#3370FF"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 17 16" >
<path d="M5.50794 6.8195C6.06022 6.8195 6.50794 6.37178 6.50794 5.8195C6.50794 5.26721 6.06022 4.8195 5.50794 4.8195C4.95565 4.8195 4.50794 5.26721 4.50794 5.8195C4.50794 6.37178 4.95565 6.8195 5.50794 6.8195Z" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.55029 5.85187C1.55029 4.50775 1.55029 3.83568 1.81188 3.32229C2.04197 2.87071 2.40913 2.50355 2.86072 2.27346C3.3741 2.01187 4.04617 2.01187 5.39029 2.01187H11.0436C12.3878 2.01187 13.0598 2.01187 13.5732 2.27346C14.0248 2.50355 14.3919 2.87071 14.622 3.32229C14.8836 3.83568 14.8836 4.50775 14.8836 5.85187V10.1481C14.8836 11.4922 14.8836 12.1643 14.622 12.6777C14.3919 13.1293 14.0248 13.4964 13.5732 13.7265C13.0598 13.9881 12.3878 13.9881 11.0436 13.9881H5.39029C4.04617 13.9881 3.3741 13.9881 2.86072 13.7265C2.40913 13.4964 2.04197 13.1293 1.81188 12.6777C1.55029 12.1643 1.55029 11.4922 1.55029 10.1481V5.85187ZM5.39029 3.3452H11.0436C11.7377 3.3452 12.1781 3.34624 12.5114 3.37347C12.8291 3.39944 12.9305 3.44241 12.9679 3.46146C13.1686 3.56373 13.3318 3.72691 13.434 3.92761C13.4531 3.96502 13.4961 4.06638 13.522 4.38413C13.5493 4.71745 13.5503 5.15781 13.5503 5.85187V10.1481C13.5503 10.1562 13.5503 10.1641 13.5503 10.1721L10.3165 6.93829C10.0561 6.67794 9.634 6.67794 9.37365 6.93829L3.70938 12.6026C3.5547 12.5791 3.49333 12.5524 3.46604 12.5385C3.26533 12.4363 3.10215 12.2731 2.99989 12.0724C2.98083 12.035 2.93786 11.9336 2.9119 11.6159C2.88466 11.2825 2.88363 10.8422 2.88363 10.1481V5.85187C2.88363 5.15781 2.88466 4.71745 2.9119 4.38413C2.93786 4.06638 2.98083 3.96502 2.99989 3.92761C3.10215 3.72691 3.26533 3.56373 3.46604 3.46146C3.50344 3.44241 3.6048 3.39944 3.92255 3.37347C4.25587 3.34624 4.69623 3.3452 5.39029 3.3452ZM9.84506 8.3525L5.54277 12.6548H11.0436C11.7377 12.6548 12.1781 12.6538 12.5114 12.6265C12.8291 12.6006 12.9305 12.5576 12.9679 12.5385C13.1686 12.4363 13.3318 12.2731 13.434 12.0724C13.4422 12.0563 13.4549 12.0283 13.4687 11.9762L9.84506 8.3525Z" />
</svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" >
<path d="M47.3337 24C47.3337 36.8866 36.887 47.3333 24.0003 47.3333C11.1137 47.3333 0.666992 36.8866 0.666992 24C0.666992 11.1133 11.1137 0.666626 24.0003 0.666626C36.887 0.666626 47.3337 11.1133 47.3337 24ZM5.33366 24C5.33366 34.3093 13.691 42.6666 24.0003 42.6666C34.3096 42.6666 42.667 34.3093 42.667 24C42.667 13.6906 34.3096 5.33329 24.0003 5.33329C13.691 5.33329 5.33366 13.6906 5.33366 24Z" />
<path d="M24.0003 2.99996C24.0003 1.71129 25.0476 0.654541 26.3298 0.783194C29.1026 1.06141 31.8097 1.83481 34.3204 3.07293C37.5303 4.6559 40.3331 6.95608 42.5119 9.79553C44.6907 12.635 46.1871 15.9376 46.8853 19.4479C47.4314 22.1934 47.4778 25.0084 47.0289 27.7588C46.8213 29.0306 45.5295 29.7687 44.2848 29.4352C43.04 29.1016 42.3169 27.8222 42.4926 26.5456C42.7752 24.4926 42.7147 22.4014 42.3083 20.3583C41.7497 17.5501 40.5526 14.908 38.8096 12.6364C37.0666 10.3649 34.8243 8.52471 32.2564 7.25833C30.3881 6.33698 28.3838 5.73731 26.3276 5.47894C25.049 5.31827 24.0003 4.28862 24.0003 2.99996Z" />
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.6665 3.0263C10.5079 3.01307 10.2763 3.01095 9.84294 3.01095H7.16646C6.45348 3.01095 5.96581 3.01152 5.58819 3.04048C5.21887 3.06881 5.02616 3.12054 4.89105 3.18516C4.57275 3.3374 4.32387 3.57549 4.17134 3.85649C4.11194 3.96593 4.05991 4.12789 4.03072 4.4633C4.00053 4.81012 3.9998 5.26058 3.9998 5.93236V14.0677C3.9998 14.7394 4.00053 15.1899 4.03072 15.5367C4.05991 15.8721 4.11194 16.0341 4.17134 16.1435C4.32387 16.4245 4.57275 16.6626 4.89105 16.8149C5.02616 16.8795 5.21887 16.9312 5.58819 16.9595C5.96581 16.9885 6.45348 16.9891 7.16646 16.9891H12.4998C13.2128 16.9891 13.7005 16.9885 14.0781 16.9595C14.4474 16.9312 14.6401 16.8795 14.7752 16.8149C15.0935 16.6626 15.3424 16.4245 15.4949 16.1435C15.5543 16.0341 15.6063 15.8721 15.6355 15.5367C15.6657 15.1899 15.6665 14.7394 15.6665 14.0677V8.42632C15.6665 8.04949 15.6642 7.84193 15.654 7.70438L12.8076 7.70439C12.5963 7.70441 12.3934 7.70443 12.2221 7.69129C12.0356 7.67698 11.8167 7.64346 11.5952 7.53756C11.2863 7.38982 11.0253 7.14923 10.8582 6.84149C10.736 6.61626 10.6972 6.39184 10.681 6.2055C10.6664 6.03826 10.6664 5.84201 10.6665 5.64647C10.6665 5.63747 10.6665 5.62847 10.6665 5.61947V3.0263ZM11.8068 1.61342C11.6211 1.53551 11.4284 1.47397 11.2311 1.42951C10.8511 1.34388 10.4571 1.34404 9.92517 1.34426C9.89812 1.34427 9.87072 1.34428 9.84294 1.34428L7.13385 1.34428C6.4615 1.34427 5.90966 1.34427 5.46075 1.37869C4.99631 1.41431 4.57159 1.49047 4.17193 1.68162C3.54942 1.97936 3.03339 2.45928 2.70655 3.0614C2.49347 3.45396 2.40926 3.87156 2.37033 4.31878C2.33311 4.74646 2.33312 5.26987 2.33313 5.89642V14.1036C2.33312 14.7301 2.33311 15.2536 2.37033 15.6812C2.40926 16.1285 2.49347 16.5461 2.70655 16.9386C3.03339 17.5407 3.54942 18.0207 4.17193 18.3184C4.57159 18.5096 4.99631 18.5857 5.46075 18.6213C5.90965 18.6558 6.46147 18.6557 7.13382 18.6557H12.5324C13.2048 18.6557 13.7566 18.6558 14.2055 18.6213C14.67 18.5857 15.0947 18.5096 15.4943 18.3184C16.1168 18.0207 16.6329 17.5407 16.9597 16.9386C17.1728 16.5461 17.257 16.1285 17.2959 15.6812C17.3332 15.2536 17.3331 14.7301 17.3331 14.1036V8.42632C17.3331 8.39763 17.3331 8.3693 17.3332 8.3413C17.3335 7.85013 17.3337 7.46273 17.2381 7.08878C17.1885 6.89509 17.1202 6.70705 17.0344 6.52696C17.0287 6.51433 17.0227 6.50187 17.0163 6.4896C16.9608 6.37678 16.8983 6.26719 16.8292 6.16139C16.6188 5.83909 16.3328 5.57093 15.9611 5.22237C15.9405 5.20307 15.9197 5.18353 15.8986 5.16372L13.2417 2.66977C13.2216 2.65089 13.2018 2.63225 13.1822 2.61385C12.8076 2.26189 12.5241 1.99554 12.186 1.80108C12.0761 1.73786 11.9628 1.68091 11.8466 1.63043C11.8335 1.62442 11.8202 1.61875 11.8068 1.61342ZM12.3331 4.10281V5.61947C12.3331 5.82534 12.3337 5.94445 12.339 6.02864C12.3423 6.02893 12.3458 6.02922 12.3495 6.0295C12.4492 6.03715 12.5869 6.03772 12.8331 6.03772H14.3944L12.3331 4.10281Z" fill="#707070"/>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1,7 @@
<svg width="16" height="20" viewBox="0 0 16 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="icon/solid/chevron-right2">
<path id="Rectangle 3101"
d="M11.0474 14.2501C11.0474 15.735 9.25219 16.4786 8.20225 15.4286L3.95213 11.1785C3.30126 10.5276 3.30126 9.47236 3.95213 8.82149L8.20225 4.57138C9.25219 3.52143 11.0474 4.26505 11.0474 5.74989L11.0474 14.2501Z"
fill="currentColor" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 444 B

View File

@@ -0,0 +1,7 @@
<svg width="16" height="20" viewBox="0 0 16 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="icon/solid/chevron-right2">
<path id="Rectangle 3101"
d="M4.95255 5.74989C4.95255 4.26505 6.74778 3.52143 7.79772 4.57138L12.0478 8.82149C12.6987 9.47236 12.6987 10.5276 12.0478 11.1785L7.79772 15.4286C6.74778 16.4786 4.95255 15.735 4.95255 14.2501V5.74989Z"
fill="currentColor" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 436 B

View File

@@ -0,0 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="icon/line/clear-all">
<path id="Union" fill-rule="evenodd" clip-rule="evenodd"
d="M7.33331 2.63809C7.33331 2.49081 7.4527 2.37142 7.59997 2.37142H8.39997C8.54725 2.37142 8.66664 2.49081 8.66664 2.63809V4.2C8.66664 4.9732 9.29344 5.6 10.0666 5.6L12.861 5.6C13.0083 5.6 13.1277 5.71939 13.1277 5.86666V6.66666C13.1277 6.81394 13.0083 6.93333 12.861 6.93333L3.13891 6.93333C2.99164 6.93333 2.87224 6.81394 2.87224 6.66666V5.86666C2.87224 5.71939 2.99164 5.6 3.13891 5.6L5.93331 5.6C6.70651 5.6 7.33331 4.97319 7.33331 4.2V2.63809ZM7.59997 1.03809C6.71632 1.03809 5.99997 1.75443 5.99997 2.63809V4.2C5.99997 4.23681 5.97012 4.26666 5.93331 4.26666L3.13891 4.26666C2.25526 4.26666 1.53891 4.98301 1.53891 5.86666V6.66666C1.53891 7.41125 2.04753 8.03705 2.73629 8.21558L1.47841 12.628C1.18709 13.6499 1.9545 14.6667 3.01711 14.6667H13.0318C14.0831 14.6667 14.8487 13.6701 14.5778 12.6543L13.384 8.17923C14.0109 7.96253 14.461 7.36717 14.461 6.66666V5.86666C14.461 4.98301 13.7447 4.26666 12.861 4.26666L10.0666 4.26666C10.0298 4.26666 9.99997 4.23681 9.99997 4.2V2.63809C9.99997 1.75443 9.28363 1.03809 8.39997 1.03809H7.59997ZM4.30936 8.26696H11.8226C11.9434 8.26696 12.0491 8.34817 12.0803 8.46489L13.2895 12.9979C13.3347 13.1672 13.207 13.3333 13.0318 13.3333H10.6269C10.627 13.3291 10.6271 13.3249 10.6271 13.3206L10.6409 11.4144C10.6436 11.0462 10.3472 10.7456 9.97907 10.7429C9.61089 10.7403 9.31026 11.0366 9.30759 11.4047L9.2938 13.311C9.29374 13.3184 9.29381 13.3259 9.294 13.3333H6.87303L6.87324 13.3206L6.88704 11.4144C6.88971 11.0462 6.5934 10.7456 6.22522 10.7429C5.85704 10.7403 5.55641 11.0366 5.55374 11.4047L5.53995 13.311C5.53989 13.3184 5.53996 13.3259 5.54015 13.3333H3.01711C2.84 13.3333 2.71211 13.1639 2.76066 12.9936L4.05291 8.46052C4.08557 8.34596 4.19024 8.26696 4.30936 8.26696Z"
fill="currentColor" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,8 @@
<svg width="19" height="18" viewBox="0 0 19 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M11.3834 2.86546C11.3834 3.27967 11.0476 3.61546 10.6334 3.61546H6.05212C4.39495 3.61546 3.05139 4.95892 3.05139 6.61546V10.3215C3.05158 11.5943 3.84602 12.6857 4.9696 13.1201L5.19885 13.1977L5.54646 13.3005C5.76638 13.3656 5.94445 13.5277 6.02982 13.7406L6.16492 14.0774C6.29685 14.4057 6.68973 14.54 6.99503 14.3611L8.59462 13.4243C8.70956 13.357 8.84034 13.3215 8.97353 13.3215H12.8014C14.4065 13.3215 15.7178 12.0607 15.7985 10.4761L15.8021 10.3215V7.97703C15.8021 7.56281 16.1379 7.22703 16.5521 7.22703C16.9663 7.22703 17.3021 7.56281 17.3021 7.97703V10.3215C17.3019 12.8066 15.2865 14.8215 12.8014 14.8215H9.38096C9.24764 14.8215 9.11674 14.857 9.00172 14.9245L6.57434 16.3471C6.10009 16.625 5.48906 16.4168 5.28381 15.907L4.90857 14.9729C4.82314 14.7602 4.64369 14.6019 4.42992 14.5193C2.74626 13.8685 1.55158 12.2347 1.55139 10.3215V6.61546C1.55139 4.13017 3.56684 2.11546 6.05212 2.11546H10.6334C11.0476 2.11546 11.3834 2.45124 11.3834 2.86546Z"
fill="#3370FF" />
<path
d="M14.9027 1.52901C15.2541 1.52912 15.5392 1.81403 15.5392 2.16549V3.43844H16.8121C17.1636 3.43851 17.4486 3.72344 17.4486 4.07491C17.4485 4.42632 17.1635 4.71132 16.8121 4.71139H15.5392V5.98434C15.5391 6.33571 15.2541 6.62071 14.9027 6.62081C14.5513 6.62081 14.2663 6.33577 14.2662 5.98434V4.71139H12.9933C12.6418 4.71139 12.3569 4.42637 12.3568 4.07491C12.3568 3.7234 12.6418 3.43844 12.9933 3.43844H14.2662V2.16549C14.2662 1.81397 14.5512 1.52901 14.9027 1.52901Z"
fill="#3370FF" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

Some files were not shown because too many files have changed in this diff Show More