Compare commits
27 Commits
v4.8.8-alp
...
v4.8.8-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d25a1d3ec | ||
|
|
2d1e53c3b5 | ||
|
|
71d0093768 | ||
|
|
cd554f573e | ||
|
|
2d016b7462 | ||
|
|
65515e7952 | ||
|
|
5906daff9f | ||
|
|
55cefccad1 | ||
|
|
87dac54f1e | ||
|
|
45b8d7e8de | ||
|
|
a478621730 | ||
|
|
a233ab9584 | ||
|
|
8d2a192515 | ||
|
|
dcaf972767 | ||
|
|
f9d43ac009 | ||
|
|
abcf48d5ec | ||
|
|
bf5145e632 | ||
|
|
f37cdabb15 | ||
|
|
e99c91aaa6 | ||
|
|
a4787bce5c | ||
|
|
f24e41f5ec | ||
|
|
85de3c1d64 | ||
|
|
c6f682310c | ||
|
|
991398b8d2 | ||
|
|
f452554663 | ||
|
|
57ff38e16f | ||
|
|
f7b55b501f |
2
.github/workflows/preview-image.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-fastgpt-images:
|
||||
preview-fastgpt-images:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
@@ -3,4 +3,6 @@ dist
|
||||
**/.DS_Store
|
||||
node_modules
|
||||
docSite/
|
||||
*.md
|
||||
*.md
|
||||
|
||||
cl100l_base.ts
|
||||
46
.vscode/i18n-ally-custom-framework.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
# .vscode/i18n-ally-custom-framework.yml
|
||||
|
||||
# An array of strings which contain Language Ids defined by VS Code
|
||||
# You can check available language ids here: https://code.visualstudio.com/docs/languages/identifiers
|
||||
languageIds:
|
||||
- javascript
|
||||
- typescript
|
||||
- javascriptreact
|
||||
- typescriptreact
|
||||
|
||||
# An array of RegExes to find the key usage. **The key should be captured in the first match group**.
|
||||
# You should unescape RegEx strings in order to fit in the YAML file
|
||||
# To help with this, you can use https://www.freeformatter.com/json-escape.html
|
||||
usageMatchRegex:
|
||||
# The following example shows how to detect `t("your.i18n.keys")`
|
||||
# the `{key}` will be placed by a proper keypath matching regex,
|
||||
# you can ignore it and use your own matching rules as well
|
||||
- "[^\\w\\d]t\\(['\"`]({key})['\"`]"
|
||||
- "[^\\w\\d]commonT\\(['\"`]({key})['\"`]"
|
||||
# 支持 appT("your.i18n.keys")
|
||||
- "[^\\w\\d]appT\\(['\"`]({key})['\"`]"
|
||||
# 支持 datasetT("your.i18n.keys")
|
||||
- "[^\\w\\d]datasetT\\(['\"`]({key})['\"`]"
|
||||
- "[^\\w\\d]fileT\\(['\"`]({key})['\"`]"
|
||||
- "[^\\w\\d]publishT\\(['\"`]({key})['\"`]"
|
||||
- "[^\\w\\d]workflowT\\(['\"`]({key})['\"`]"
|
||||
- "[^\\w\\d]userT\\(['\"`]({key})['\"`]"
|
||||
- "[^\\w\\d]chatT\\(['\"`]({key})['\"`]"
|
||||
- "[^\\w\\d]i18nT\\(['\"`]({key})['\"`]"
|
||||
|
||||
# A RegEx to set a custom scope range. This scope will be used as a prefix when detecting keys
|
||||
# and works like how the i18next framework identifies the namespace scope from the
|
||||
# useTranslation() hook.
|
||||
# You should unescape RegEx strings in order to fit in the YAML file
|
||||
# To help with this, you can use https://www.freeformatter.com/json-escape.html
|
||||
scopeRangeRegex: "([^:]+):"
|
||||
|
||||
# An array of strings containing refactor templates.
|
||||
# The "$1" will be replaced by the keypath specified.
|
||||
# Optional: uncomment the following two lines to use
|
||||
|
||||
# refactorTemplates:
|
||||
# - i18n.get("$1")
|
||||
|
||||
# If set to true, only enables this custom framework (will disable all built-in frameworks)
|
||||
monopoly: false
|
||||
5
.vscode/settings.json
vendored
@@ -20,5 +20,8 @@
|
||||
"i18n-ally.displayLanguage": "zh", // 显示语言
|
||||
"i18n-ally.namespace": true,
|
||||
"i18n-ally.pathMatcher": "{locale}/{namespaces}.json",
|
||||
"i18n-ally.extract.targetPickingStrategy": "most-similar-by-key"
|
||||
"i18n-ally.extract.targetPickingStrategy": "most-similar-by-key",
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
}
|
||||
72
dev.md
@@ -23,6 +23,77 @@ pnpm dev
|
||||
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
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
@@ -37,4 +108,3 @@ docker build -f ./projects/app/Dockerfile -t registry.cn-hangzhou.aliyuncs.com/f
|
||||
# Make cmd: Build image with proxy
|
||||
make build name=app image=registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.1 proxy=taobao
|
||||
```
|
||||
|
||||
|
||||
@@ -16,10 +16,34 @@ weight: 816
|
||||
- fastgpt 镜像 tag 修改成 v4.8.8-alpha
|
||||
- 商业版镜像 tag 修改成 v4.8.8-alpha
|
||||
|
||||
### 3. 执行初始化
|
||||
|
||||
从任意终端,发起 1 个 HTTP 请求。其中 {{rootkey}} 替换成环境变量里的 `rootkey`;{{host}} 替换成**FastGPT 域名**。
|
||||
|
||||
```bash
|
||||
curl --location --request POST 'https://{{host}}/api/admin/initv488' \
|
||||
--header 'rootkey: {{rootkey}}' \
|
||||
--header 'Content-Type: application/json'
|
||||
```
|
||||
|
||||
会初始化知识库的继承权限
|
||||
|
||||
-------
|
||||
|
||||
## V4.8. 8 更新说明
|
||||
## V4.8.8 更新说明
|
||||
|
||||
1. 新增 - 重构系统插件的结构。允许向开源社区 PR 系统插件,具体可见: [如何向 FastGPT 社区提交系统插件](https://fael3z0zfze.feishu.cn/wiki/ERZnw9R26iRRG0kXZRec6WL9nwh)。欢迎
|
||||
1. 新增 - 重构系统插件的结构。允许向开源社区 PR 系统插件,具体可见: [如何向 FastGPT 社区提交系统插件](https://fael3z0zfze.feishu.cn/wiki/ERZnw9R26iRRG0kXZRec6WL9nwh)。
|
||||
2. 新增 - DuckDuckGo 系统插件。
|
||||
3. 优化 - 节点图标。
|
||||
3. 新增 - 飞书 webhook 系统插件。
|
||||
4. 新增 - 修改变量填写方式。提示词输入框以以及工作流中所有 Textarea 输入框,支持输入 / 唤起变量选择,可直接选择所有上游输出值,无需动态引入。
|
||||
5. 商业版新增 - 知识库权限继承。
|
||||
6. 优化 - 移动端快速切换应用交互。
|
||||
7. 优化 - 节点图标。
|
||||
8. 优化 - 对话框引用增加额外复制案件,便于复制。增加引用内容折叠。
|
||||
9. 优化 - OpenAI sdk 升级,并自定义了 whisper 模型接口(未仔细查看 sdk 实现,但 sdk 中 whisper 接口,似乎无法适配一般 fastapi 接口)
|
||||
10. 修复 - Permission 表声明问题。
|
||||
11. 修复 - 并行执行节点,运行时间未正确记录。
|
||||
12. 修复 - 运行详情未正确展示嵌套节点信息。
|
||||
13. 修复 - 简易模式,首次进入,无法正确获取知识库配置。
|
||||
14. 修复 - Log debug level 配置无效。
|
||||
15. 修复 - 插件独立运行时,会将插件输入的值进行变量替换,可能导致后续节点变量异常。
|
||||
@@ -179,7 +179,7 @@ services:
|
||||
- ./mysql:/var/lib/mysql
|
||||
oneapi:
|
||||
container_name: oneapi
|
||||
image: ghcr.io/songquanpeng/one-api:latest
|
||||
image: ghcr.io/songquanpeng/one-api:0.6.7
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/one-api:v0.6.6 # 阿里云
|
||||
ports:
|
||||
- 3001:3000
|
||||
|
||||
@@ -136,7 +136,7 @@ services:
|
||||
- ./mysql:/var/lib/mysql
|
||||
oneapi:
|
||||
container_name: oneapi
|
||||
image: ghcr.io/songquanpeng/one-api:latest
|
||||
image: ghcr.io/songquanpeng/one-api:0.6.7
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/one-api:v0.6.6 # 阿里云
|
||||
ports:
|
||||
- 3001:3000
|
||||
|
||||
@@ -117,7 +117,7 @@ services:
|
||||
- ./mysql:/var/lib/mysql
|
||||
oneapi:
|
||||
container_name: oneapi
|
||||
image: ghcr.io/songquanpeng/one-api:latest
|
||||
image: ghcr.io/songquanpeng/one-api:0.6.7
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/one-api:v0.6.6 # 阿里云
|
||||
ports:
|
||||
- 3001:3000
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
"gen:theme-typings": "chakra-cli tokens packages/web/styles/theme.ts --out node_modules/.pnpm/node_modules/@chakra-ui/styled-system/dist/theming.types.d.ts",
|
||||
"postinstall": "sh ./scripts/postinstall.sh",
|
||||
"initIcon": "node ./scripts/icon/init.js",
|
||||
"previewIcon": "node ./scripts/icon/index.js"
|
||||
"previewIcon": "node ./scripts/icon/index.js",
|
||||
"checkI18n": "node ./scripts/i18n/delete-unused-keys.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chakra-ui/cli": "^2.4.1",
|
||||
|
||||
@@ -8,8 +8,8 @@ export enum DatasetErrEnum {
|
||||
unAuthDatasetCollection = 'unAuthDatasetCollection',
|
||||
unAuthDatasetData = 'unAuthDatasetData',
|
||||
unAuthDatasetFile = 'unAuthDatasetFile',
|
||||
|
||||
unLinkCollection = 'unLinkCollection'
|
||||
unLinkCollection = 'unLinkCollection',
|
||||
invalidVectorModelOrQAModel = 'invalidVectorModelOrQAModel'
|
||||
}
|
||||
const datasetErr = [
|
||||
{
|
||||
@@ -39,6 +39,10 @@ const datasetErr = [
|
||||
{
|
||||
statusText: DatasetErrEnum.unLinkCollection,
|
||||
message: 'core.dataset.error.unLinkCollection'
|
||||
},
|
||||
{
|
||||
statusText: DatasetErrEnum.invalidVectorModelOrQAModel,
|
||||
message: 'core.dataset.error.invalidVectorModelOrQAModel'
|
||||
}
|
||||
];
|
||||
export default datasetErr.reduce((acc, cur, index) => {
|
||||
|
||||
@@ -3,7 +3,8 @@ import { ErrType } from '../errorCode';
|
||||
/* dataset: 506000 */
|
||||
export enum OpenApiErrEnum {
|
||||
unExist = 'openapiUnExist',
|
||||
unAuth = 'openapiUnAuth'
|
||||
unAuth = 'openapiUnAuth',
|
||||
exceedLimit = 'openapiExceedLimit'
|
||||
}
|
||||
const errList = [
|
||||
{
|
||||
@@ -13,6 +14,10 @@ const errList = [
|
||||
{
|
||||
statusText: OpenApiErrEnum.unAuth,
|
||||
message: '无权操作该 Api Key'
|
||||
},
|
||||
{
|
||||
statusText: OpenApiErrEnum.exceedLimit,
|
||||
message: '最多 10 组 API 密钥'
|
||||
}
|
||||
];
|
||||
export default errList.reduce((acc, cur, index) => {
|
||||
|
||||
@@ -144,7 +144,7 @@ const commonSplit = (props: SplitProps): SplitResponse => {
|
||||
];
|
||||
}
|
||||
|
||||
const isCustomSteep = checkIsCustomStep(step);
|
||||
const isCustomStep = checkIsCustomStep(step);
|
||||
const isMarkdownSplit = checkIsMarkdownSplit(step);
|
||||
const independentChunk = checkIndependentChunk(step);
|
||||
|
||||
@@ -154,7 +154,7 @@ const commonSplit = (props: SplitProps): SplitResponse => {
|
||||
.replace(
|
||||
reg,
|
||||
(() => {
|
||||
if (isCustomSteep) return splitMarker;
|
||||
if (isCustomStep) return splitMarker;
|
||||
if (independentChunk) return `${splitMarker}$1`;
|
||||
return `$1${splitMarker}`;
|
||||
})()
|
||||
|
||||
@@ -6,27 +6,29 @@ import { getAppChatConfig } from '../workflow/utils';
|
||||
import { StoreNodeItemType } from '../workflow/type/node';
|
||||
import { DatasetSearchModeEnum } from '../dataset/constants';
|
||||
|
||||
export const getDefaultAppForm = (): AppSimpleEditFormType => ({
|
||||
aiSettings: {
|
||||
model: 'gpt-4o-mini',
|
||||
systemPrompt: '',
|
||||
temperature: 0,
|
||||
isResponseAnswerText: true,
|
||||
maxHistories: 6,
|
||||
maxToken: 4000
|
||||
},
|
||||
dataset: {
|
||||
datasets: [],
|
||||
similarity: 0.4,
|
||||
limit: 1500,
|
||||
searchMode: DatasetSearchModeEnum.embedding,
|
||||
usingReRank: false,
|
||||
datasetSearchUsingExtensionQuery: false,
|
||||
datasetSearchExtensionBg: ''
|
||||
},
|
||||
selectedTools: [],
|
||||
chatConfig: {}
|
||||
});
|
||||
export const getDefaultAppForm = (): AppSimpleEditFormType => {
|
||||
return {
|
||||
aiSettings: {
|
||||
model: 'gpt-4o-mini',
|
||||
systemPrompt: '',
|
||||
temperature: 0,
|
||||
isResponseAnswerText: true,
|
||||
maxHistories: 6,
|
||||
maxToken: 4000
|
||||
},
|
||||
dataset: {
|
||||
datasets: [],
|
||||
similarity: 0.4,
|
||||
limit: 1500,
|
||||
searchMode: DatasetSearchModeEnum.embedding,
|
||||
usingReRank: false,
|
||||
datasetSearchUsingExtensionQuery: true,
|
||||
datasetSearchExtensionBg: ''
|
||||
},
|
||||
selectedTools: [],
|
||||
chatConfig: {}
|
||||
};
|
||||
};
|
||||
|
||||
/* format app nodes to edit form */
|
||||
export const appWorkflow2Form = ({
|
||||
|
||||
1
packages/global/core/dataset/api.d.ts
vendored
@@ -10,7 +10,6 @@ export type DatasetUpdateBody = {
|
||||
name?: string;
|
||||
avatar?: string;
|
||||
intro?: string;
|
||||
permission?: DatasetSchemaType['permission']; // TODO: Should be deleted.
|
||||
agentModel?: LLMModelItemType;
|
||||
status?: DatasetSchemaType['status'];
|
||||
|
||||
|
||||
@@ -8,22 +8,22 @@ export enum DatasetTypeEnum {
|
||||
export const DatasetTypeMap = {
|
||||
[DatasetTypeEnum.folder]: {
|
||||
icon: 'common/folderFill',
|
||||
label: 'Folder Dataset',
|
||||
label: 'folder_dataset',
|
||||
collectionLabel: 'common.Folder'
|
||||
},
|
||||
[DatasetTypeEnum.dataset]: {
|
||||
icon: 'core/dataset/commonDataset',
|
||||
label: 'Common Dataset',
|
||||
label: 'common_dataset',
|
||||
collectionLabel: 'common.File'
|
||||
},
|
||||
[DatasetTypeEnum.websiteDataset]: {
|
||||
icon: 'core/dataset/websiteDataset',
|
||||
label: 'Website Dataset',
|
||||
label: 'website_dataset',
|
||||
collectionLabel: 'common.Website'
|
||||
},
|
||||
[DatasetTypeEnum.externalFile]: {
|
||||
icon: 'core/dataset/externalDataset',
|
||||
label: 'External File',
|
||||
label: 'external_file',
|
||||
collectionLabel: 'common.File'
|
||||
}
|
||||
};
|
||||
|
||||
21
packages/global/core/dataset/type.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
import { PermissionValueType } from 'support/permission/type';
|
||||
import { PermissionSchemaType } from '../../support/permission/type';
|
||||
import type { LLMModelItemType, VectorModelItemType } from '../../core/ai/model.d';
|
||||
import { PermissionTypeEnum } from '../../support/permission/constant';
|
||||
import { PushDatasetDataChunkProps } from './api';
|
||||
@@ -12,31 +12,28 @@ import {
|
||||
import { DatasetPermission } from '../../support/permission/dataset/controller';
|
||||
import { Permission } from '../../support/permission/controller';
|
||||
|
||||
/* schema */
|
||||
export type DatasetSchemaType = {
|
||||
_id: string;
|
||||
parentId: string;
|
||||
parentId?: string;
|
||||
userId: string;
|
||||
teamId: string;
|
||||
tmbId: string;
|
||||
updateTime: Date;
|
||||
|
||||
avatar: string;
|
||||
name: string;
|
||||
vectorModel: string;
|
||||
agentModel: string;
|
||||
intro: string;
|
||||
type: DatasetTypeEnum;
|
||||
type: `${DatasetTypeEnum}`;
|
||||
status: `${DatasetStatusEnum}`;
|
||||
// permission: DatasetPermission;
|
||||
|
||||
// metadata
|
||||
websiteConfig?: {
|
||||
url: string;
|
||||
selector: string;
|
||||
};
|
||||
externalReadUrl?: string;
|
||||
defaultPermission: PermissionValueType;
|
||||
};
|
||||
} & PermissionSchemaType;
|
||||
// } & PermissionSchemaType;
|
||||
|
||||
export type DatasetCollectionSchemaType = {
|
||||
_id: string;
|
||||
@@ -133,15 +130,13 @@ export type DatasetSimpleItemType = {
|
||||
};
|
||||
export type DatasetListItemType = {
|
||||
_id: string;
|
||||
parentId: string;
|
||||
avatar: string;
|
||||
name: string;
|
||||
intro: string;
|
||||
type: DatasetTypeEnum;
|
||||
type: `${DatasetTypeEnum}`;
|
||||
permission: DatasetPermission;
|
||||
vectorModel: VectorModelItemType;
|
||||
defaultPermission: PermissionValueType;
|
||||
};
|
||||
} & PermissionSchemaType;
|
||||
|
||||
export type DatasetItemType = Omit<DatasetSchemaType, 'vectorModel' | 'agentModel'> & {
|
||||
vectorModel: VectorModelItemType;
|
||||
|
||||
@@ -6,6 +6,7 @@ export enum FlowNodeTemplateTypeEnum {
|
||||
|
||||
search = 'search',
|
||||
multimodal = 'multimodal',
|
||||
communication = 'communication',
|
||||
|
||||
other = 'other',
|
||||
teamApp = 'teamApp'
|
||||
|
||||
@@ -35,6 +35,7 @@ export type WorkflowTemplateType = {
|
||||
avatar: string;
|
||||
intro?: string;
|
||||
author?: string;
|
||||
inputExplanationUrl?: string;
|
||||
version: string;
|
||||
|
||||
showStatus?: boolean;
|
||||
|
||||
1
packages/global/core/workflow/type/node.d.ts
vendored
@@ -31,6 +31,7 @@ export type FlowNodeCommonType = {
|
||||
avatar?: string;
|
||||
name: string;
|
||||
intro?: string; // template list intro
|
||||
inputExplanationUrl?: string;
|
||||
showStatus?: boolean; // chatting response step status
|
||||
version: string;
|
||||
|
||||
|
||||
@@ -3,9 +3,10 @@ import {
|
||||
WorkflowIOValueTypeEnum,
|
||||
NodeInputKeyEnum,
|
||||
VariableInputEnum,
|
||||
variableMap
|
||||
variableMap,
|
||||
VARIABLE_NODE_ID
|
||||
} from './constants';
|
||||
import { FlowNodeInputItemType, FlowNodeOutputItemType } from './type/io.d';
|
||||
import { FlowNodeInputItemType, FlowNodeOutputItemType, ReferenceValueProps } from './type/io.d';
|
||||
import { StoreNodeItemType } from './type/node';
|
||||
import type {
|
||||
VariableItemType,
|
||||
@@ -23,6 +24,7 @@ import {
|
||||
} from '../app/constants';
|
||||
import { IfElseResultEnum } from './template/system/ifElse/constant';
|
||||
import { RuntimeNodeItemType } from './runtime/type';
|
||||
import { getReferenceVariableValue } from './runtime/utils';
|
||||
|
||||
export const getHandleId = (nodeId: string, type: 'source' | 'target', key: string) => {
|
||||
return `${nodeId}-${type}-${key}`;
|
||||
@@ -226,3 +228,90 @@ export const updatePluginInputByVariables = (
|
||||
: node
|
||||
);
|
||||
};
|
||||
|
||||
export const removePluginInputVariables = (
|
||||
variables: Record<string, any>,
|
||||
nodes: RuntimeNodeItemType[]
|
||||
) => {
|
||||
const pluginInputNode = nodes.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput);
|
||||
|
||||
if (!pluginInputNode) return variables;
|
||||
return Object.keys(variables).reduce(
|
||||
(acc, key) => {
|
||||
if (!pluginInputNode.inputs.find((input) => input.key === key)) {
|
||||
acc[key] = variables[key];
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, any>
|
||||
);
|
||||
};
|
||||
|
||||
export function replaceVariableLabel({
|
||||
text,
|
||||
nodes,
|
||||
variables,
|
||||
runningNode
|
||||
}: {
|
||||
text: any;
|
||||
nodes: RuntimeNodeItemType[];
|
||||
variables: Record<string, string | number>;
|
||||
runningNode: RuntimeNodeItemType;
|
||||
}) {
|
||||
if (typeof text !== 'string') return text;
|
||||
|
||||
const globalVariables = Object.keys(variables).map((key) => {
|
||||
return {
|
||||
nodeId: VARIABLE_NODE_ID,
|
||||
id: key,
|
||||
value: variables[key]
|
||||
};
|
||||
});
|
||||
|
||||
// Upstream node outputs
|
||||
const nodeVariables = nodes
|
||||
.map((node) => {
|
||||
return node.outputs.map((output) => {
|
||||
return {
|
||||
nodeId: node.nodeId,
|
||||
id: output.id,
|
||||
value: output.value
|
||||
};
|
||||
});
|
||||
})
|
||||
.flat();
|
||||
|
||||
// Get runningNode inputs(Will be replaced with reference)
|
||||
const customInputs = runningNode.inputs.flatMap((item) => {
|
||||
if (Array.isArray(item.value)) {
|
||||
return [
|
||||
{
|
||||
id: item.key,
|
||||
value: getReferenceVariableValue({
|
||||
value: item.value as ReferenceValueProps,
|
||||
nodes,
|
||||
variables
|
||||
}),
|
||||
nodeId: runningNode.nodeId
|
||||
}
|
||||
];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const allVariables = [...globalVariables, ...nodeVariables, ...customInputs];
|
||||
|
||||
// Replace {{$xxx.xxx$}} to value
|
||||
for (const key in allVariables) {
|
||||
const val = allVariables[key];
|
||||
const regex = new RegExp(`\\{\\{\\$(${val.nodeId}\\.${val.id})\\$\\}\\}`, 'g');
|
||||
if (['string', 'number'].includes(typeof val.value)) {
|
||||
text = text.replace(regex, String(val.value));
|
||||
} else if (['object'].includes(typeof val.value)) {
|
||||
text = text.replace(regex, JSON.stringify(val.value));
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return text || '';
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"jschardet": "3.1.1",
|
||||
"nanoid": "^4.0.1",
|
||||
"next": "14.2.5",
|
||||
"openai": "4.28.0",
|
||||
"openai": "4.53.0",
|
||||
"openapi-types": "^12.1.3",
|
||||
"timezones-list": "^3.0.2"
|
||||
},
|
||||
|
||||
14
packages/global/support/permission/type.d.ts
vendored
@@ -1,11 +1,13 @@
|
||||
import { TeamMemberWithUserSchema } from '../user/team/type';
|
||||
import { AuthUserTypeEnum, PermissionKeyEnum } from './constant';
|
||||
import { AuthUserTypeEnum, PermissionKeyEnum, PerResourceTypeEnum } from './constant';
|
||||
|
||||
// PermissionValueType, the type of permission's value is a number, which is a bit field actually.
|
||||
// It is spired by the permission system in Linux.
|
||||
// The lowest 3 bits present the permission of reading, writing and managing.
|
||||
// The higher bits are advanced permissions or extended permissions, which could be customized.
|
||||
export type PermissionValueType = number;
|
||||
export type ResourceType = `${PerResourceTypeEnum}`;
|
||||
|
||||
export type PermissionListType<T = {}> = Record<
|
||||
T | PermissionKeyEnum,
|
||||
{
|
||||
@@ -16,16 +18,6 @@ export type PermissionListType<T = {}> = Record<
|
||||
}
|
||||
>;
|
||||
|
||||
export type AuthResponseType = {
|
||||
teamId: string;
|
||||
tmbId: string;
|
||||
isOwner: boolean;
|
||||
canWrite: boolean;
|
||||
authType?: `${AuthUserTypeEnum}`;
|
||||
appId?: string;
|
||||
apikey?: string;
|
||||
};
|
||||
|
||||
export type ResourcePermissionType = {
|
||||
teamId: string;
|
||||
tmbId: string;
|
||||
|
||||
@@ -3,22 +3,13 @@ export const TeamMemberCollectionName = 'team_members';
|
||||
export const TeamTagsCollectionName = 'team_tags';
|
||||
|
||||
export enum TeamMemberRoleEnum {
|
||||
owner = 'owner',
|
||||
admin = 'admin',
|
||||
visitor = 'visitor'
|
||||
owner = 'owner'
|
||||
}
|
||||
|
||||
export const TeamMemberRoleMap = {
|
||||
[TeamMemberRoleEnum.owner]: {
|
||||
value: TeamMemberRoleEnum.owner,
|
||||
label: 'user.team.role.Owner'
|
||||
},
|
||||
[TeamMemberRoleEnum.admin]: {
|
||||
value: TeamMemberRoleEnum.admin,
|
||||
label: 'user.team.role.Admin'
|
||||
},
|
||||
[TeamMemberRoleEnum.visitor]: {
|
||||
value: TeamMemberRoleEnum.visitor,
|
||||
label: 'user.team.role.Visitor'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -28,6 +19,7 @@ export enum TeamMemberStatusEnum {
|
||||
reject = 'reject',
|
||||
leave = 'leave'
|
||||
}
|
||||
|
||||
export const TeamMemberStatusMap = {
|
||||
[TeamMemberStatusEnum.waiting]: {
|
||||
label: 'user.team.member.waiting',
|
||||
@@ -46,4 +38,5 @@ export const TeamMemberStatusMap = {
|
||||
color: 'red.600'
|
||||
}
|
||||
};
|
||||
|
||||
export const notLeaveStatus = { $ne: TeamMemberStatusEnum.leave };
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"dependencies": {
|
||||
"duck-duck-scrape": "^2.2.5",
|
||||
"lodash": "^4.17.21",
|
||||
"axios": "^1.5.1",
|
||||
"expr-eval": "^2.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -4,10 +4,19 @@ import { FastGPTProUrl, isProduction } from '../service/common/system/constants'
|
||||
import { GET, POST } from '@fastgpt/service/common/api/plusRequest';
|
||||
import { SystemPluginTemplateItemType } from '@fastgpt/global/core/workflow/type';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { WorkerNameEnum, runWorker } from '@fastgpt/service/worker/utils';
|
||||
|
||||
let list = [
|
||||
// Run in main thread
|
||||
const staticPluginList = [
|
||||
'getTime',
|
||||
'fetchUrl',
|
||||
'Doc2X',
|
||||
'Doc2X/URLPDF2text',
|
||||
'Doc2X/URLImg2text',
|
||||
'feishu'
|
||||
];
|
||||
// Run in worker thread (Have npm packages)
|
||||
const packagePluginList = [
|
||||
'mathExprVal',
|
||||
'duckduckgo',
|
||||
'duckduckgo/search',
|
||||
@@ -16,6 +25,8 @@ let list = [
|
||||
'duckduckgo/searchVideo'
|
||||
];
|
||||
|
||||
const list = [...staticPluginList, ...packagePluginList];
|
||||
|
||||
/* Get plugins */
|
||||
export const getCommunityPlugins = () => {
|
||||
return list.map<SystemPluginTemplateItemType>((name) => {
|
||||
@@ -58,8 +69,7 @@ export const getSystemPluginTemplates = async (refresh = false) => {
|
||||
};
|
||||
|
||||
export const getCommunityCb = async () => {
|
||||
// Do not modify the following code
|
||||
const loadModule = async (name: string) => {
|
||||
const loadCommunityModule = async (name: string) => {
|
||||
const module = await import(`./src/${name}/index`);
|
||||
return module.default;
|
||||
};
|
||||
@@ -70,7 +80,14 @@ export const getCommunityCb = async () => {
|
||||
try {
|
||||
return {
|
||||
name,
|
||||
cb: await loadModule(name)
|
||||
cb: staticPluginList.includes(name)
|
||||
? await loadCommunityModule(name)
|
||||
: (e: any) => {
|
||||
return runWorker(WorkerNameEnum.systemPluginRun, {
|
||||
pluginName: name,
|
||||
data: e
|
||||
});
|
||||
}
|
||||
};
|
||||
} catch (error) {}
|
||||
})
|
||||
|
||||
24
packages/plugins/runtime/worker.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { SystemPluginResponseType } from '../type';
|
||||
import { parentPort } from 'worker_threads';
|
||||
|
||||
const loadModule = async (name: string): Promise<(e: any) => SystemPluginResponseType> => {
|
||||
const module = await import(`../src/${name}/index`);
|
||||
return module.default;
|
||||
};
|
||||
|
||||
parentPort?.on('message', async ({ pluginName, data }: { pluginName: string; data: any }) => {
|
||||
try {
|
||||
const cb = await loadModule(pluginName);
|
||||
parentPort?.postMessage({
|
||||
type: 'success',
|
||||
data: await cb(data)
|
||||
});
|
||||
} catch (error) {
|
||||
parentPort?.postMessage({
|
||||
type: 'error',
|
||||
data: error
|
||||
});
|
||||
}
|
||||
|
||||
process.exit();
|
||||
});
|
||||
158
packages/plugins/src/Doc2X/URLImg2text/index.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import { delay } from '@fastgpt/global/common/system/utils';
|
||||
import { addLog } from '@fastgpt/service/common/system/log';
|
||||
|
||||
type Props = {
|
||||
apikey: string;
|
||||
url: string;
|
||||
img_correction: boolean;
|
||||
formula: boolean;
|
||||
};
|
||||
|
||||
// Response type same as HTTP outputs
|
||||
type Response = Promise<{
|
||||
result: string;
|
||||
success: boolean;
|
||||
}>;
|
||||
|
||||
const main = async ({ apikey, url, img_correction, formula }: Props): Response => {
|
||||
// Check the apikey
|
||||
if (!apikey) {
|
||||
return {
|
||||
result: `API key is required`,
|
||||
success: false
|
||||
};
|
||||
}
|
||||
|
||||
let real_api_key = apikey;
|
||||
if (!apikey.startsWith('sk-')) {
|
||||
const response = await fetch('https://api.doc2x.noedgeai.com/api/token/refresh', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${apikey}`
|
||||
}
|
||||
});
|
||||
if (response.status !== 200) {
|
||||
return {
|
||||
result: `Get token failed: ${await response.text()}`,
|
||||
success: false
|
||||
};
|
||||
}
|
||||
const data = await response.json();
|
||||
real_api_key = data.data.token;
|
||||
}
|
||||
|
||||
//Get the image binary from the URL
|
||||
const extension = url.split('.').pop()?.toLowerCase();
|
||||
const name = url.split('/').pop()?.split('.').shift();
|
||||
let mini = '';
|
||||
switch (extension) {
|
||||
case 'jpg':
|
||||
case 'jpeg':
|
||||
mini = 'image/jpeg';
|
||||
break;
|
||||
case 'png':
|
||||
mini = 'image/png';
|
||||
break;
|
||||
default:
|
||||
return {
|
||||
result: `Not supported image format, only support jpg/jpeg/png`,
|
||||
success: false
|
||||
};
|
||||
}
|
||||
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
return {
|
||||
result: `Failed to fetch image from URL: ${url}`,
|
||||
success: false
|
||||
};
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
const formData = new FormData();
|
||||
formData.append('file', new Blob([blob], { type: mini }), name + '.' + extension);
|
||||
formData.append('img_correction', img_correction ? '1' : '0');
|
||||
formData.append('equation', formula ? '1' : '0');
|
||||
|
||||
let upload_url = 'https://api.doc2x.noedgeai.com/api/platform/async/img';
|
||||
if (real_api_key.startsWith('sk-')) {
|
||||
upload_url = 'https://api.doc2x.noedgeai.com/api/v1/async/img';
|
||||
}
|
||||
|
||||
let uuid;
|
||||
const uploadAttempts = [1, 2, 3];
|
||||
for await (const attempt of uploadAttempts) {
|
||||
const upload_response = await fetch(upload_url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${real_api_key}`
|
||||
},
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (!upload_response.ok) {
|
||||
// Rate limit, wait for 10s and retry at most 3 times
|
||||
if (upload_response.status === 429 && attempt < 3) {
|
||||
await delay(10000);
|
||||
continue;
|
||||
}
|
||||
return {
|
||||
result: `Failed to upload image: ${await upload_response.text()}`,
|
||||
success: false
|
||||
};
|
||||
}
|
||||
|
||||
const upload_data = await upload_response.json();
|
||||
uuid = upload_data.data.uuid;
|
||||
break;
|
||||
}
|
||||
|
||||
// Get the result by uuid
|
||||
let result_url = 'https://api.doc2x.noedgeai.com/api/platform/async/status?uuid=' + uuid;
|
||||
if (real_api_key.startsWith('sk-')) {
|
||||
result_url = 'https://api.doc2x.noedgeai.com/api/v1/async/status?uuid=' + uuid;
|
||||
}
|
||||
const maxAttempts = 100;
|
||||
// Wait for the result, at most 100s
|
||||
for await (const _ of Array(maxAttempts).keys()) {
|
||||
const result_response = await fetch(result_url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${real_api_key}`
|
||||
}
|
||||
});
|
||||
if (!result_response.ok) {
|
||||
return {
|
||||
result: `Failed to get result: ${await result_response.text()}`,
|
||||
success: false
|
||||
};
|
||||
}
|
||||
const result_data = await result_response.json();
|
||||
if (['ready', 'processing'].includes(result_data.data.status)) {
|
||||
await delay(1000);
|
||||
} else if (result_data.data.status === 'pages limit exceeded') {
|
||||
return {
|
||||
result: 'Doc2X Pages limit exceeded',
|
||||
success: false
|
||||
};
|
||||
} else if (result_data.data.status === 'success') {
|
||||
let result = result_data.data.result.pages[0].md;
|
||||
result = result.replace(/\\[\(\)]/g, '$').replace(/\\[\[\]]/g, '$$');
|
||||
return {
|
||||
result: result,
|
||||
success: true
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
result: `Failed to get result: ${await result_data.text()}`,
|
||||
success: false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
result: 'Timeout waiting for result',
|
||||
success: false
|
||||
};
|
||||
};
|
||||
|
||||
export default main;
|
||||
421
packages/plugins/src/Doc2X/URLImg2text/template.json
Normal file
@@ -0,0 +1,421 @@
|
||||
{
|
||||
"author": "Menghuan1918",
|
||||
"version": "488",
|
||||
"name": "Doc2X 图像(URL)识别",
|
||||
"avatar": "plugins/doc2x",
|
||||
"intro": "将传入的图片(URL)发送至Doc2X进行解析,返回带LaTeX公式的markdown格式的文本",
|
||||
"inputExplanationUrl": "https://fael3z0zfze.feishu.cn/wiki/Rkc5witXWiJoi5kORd2cofh6nDg?fromScene=spaceOverview",
|
||||
"showStatus": true,
|
||||
"weight": 10,
|
||||
|
||||
"isTool": true,
|
||||
"templateType": "tools",
|
||||
|
||||
"workflow": {
|
||||
"nodes": [
|
||||
{
|
||||
"nodeId": "pluginInput",
|
||||
"name": "自定义插件输入",
|
||||
"intro": "可以配置插件需要哪些输入,利用这些输入来运行插件",
|
||||
"avatar": "core/workflow/template/workflowStart",
|
||||
"flowNodeType": "pluginInput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 388.243055058894,
|
||||
"y": -75.09744210499466
|
||||
},
|
||||
"version": "481",
|
||||
"inputs": [
|
||||
{
|
||||
"renderTypeList": ["input"],
|
||||
"selectedTypeIndex": 0,
|
||||
"valueType": "string",
|
||||
"canEdit": true,
|
||||
"key": "apikey",
|
||||
"label": "apikey",
|
||||
"description": "Doc2X的验证密匙,对于个人用户可以从Doc2X官网 - 个人信息 - 身份令牌获得",
|
||||
"required": true,
|
||||
"toolDescription": "",
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"renderTypeList": ["reference"],
|
||||
"selectedTypeIndex": 0,
|
||||
"valueType": "string",
|
||||
"canEdit": true,
|
||||
"key": "url",
|
||||
"label": "url",
|
||||
"description": "待处理图片的URL",
|
||||
"required": true,
|
||||
"toolDescription": "待处理图片的URL"
|
||||
},
|
||||
{
|
||||
"renderTypeList": ["switch"],
|
||||
"selectedTypeIndex": 0,
|
||||
"valueType": "boolean",
|
||||
"canEdit": true,
|
||||
"key": "img_correction",
|
||||
"label": "img_correction",
|
||||
"description": "是否启用图形矫正功能",
|
||||
"required": true,
|
||||
"toolDescription": "",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"renderTypeList": ["switch"],
|
||||
"selectedTypeIndex": 0,
|
||||
"valueType": "boolean",
|
||||
"canEdit": true,
|
||||
"key": "formula",
|
||||
"label": "formula",
|
||||
"description": "是否开启纯公式识别(仅适用于图片内容仅有公式时)",
|
||||
"required": true,
|
||||
"toolDescription": "",
|
||||
"defaultValue": false
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"id": "apikey",
|
||||
"valueType": "string",
|
||||
"key": "apikey",
|
||||
"label": "apikey",
|
||||
"type": "hidden"
|
||||
},
|
||||
{
|
||||
"id": "url",
|
||||
"valueType": "string",
|
||||
"key": "url",
|
||||
"label": "url",
|
||||
"type": "hidden"
|
||||
},
|
||||
{
|
||||
"id": "img_correction",
|
||||
"valueType": "boolean",
|
||||
"key": "img_correction",
|
||||
"label": "img_correction",
|
||||
"type": "hidden"
|
||||
},
|
||||
{
|
||||
"id": "formula",
|
||||
"valueType": "boolean",
|
||||
"key": "formula",
|
||||
"label": "formula",
|
||||
"type": "hidden"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nodeId": "pluginOutput",
|
||||
"name": "自定义插件输出",
|
||||
"intro": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出",
|
||||
"avatar": "core/workflow/template/pluginOutput",
|
||||
"flowNodeType": "pluginOutput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 1654.8021754314786,
|
||||
"y": -22.243376051504086
|
||||
},
|
||||
"version": "481",
|
||||
"inputs": [
|
||||
{
|
||||
"renderTypeList": ["reference"],
|
||||
"valueType": "string",
|
||||
"canEdit": true,
|
||||
"key": "result",
|
||||
"label": "result",
|
||||
"description": "处理结果(或者是报错信息)",
|
||||
"value": ["zHG5jJBkXmjB", "xWQuEf50F3mr"]
|
||||
},
|
||||
{
|
||||
"renderTypeList": ["reference"],
|
||||
"valueType": "boolean",
|
||||
"canEdit": true,
|
||||
"key": "success",
|
||||
"label": "success",
|
||||
"description": "是否处理成功",
|
||||
"value": ["zHG5jJBkXmjB", "m6CJJj7GFud5"]
|
||||
}
|
||||
],
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"nodeId": "zHG5jJBkXmjB",
|
||||
"name": "HTTP 请求",
|
||||
"intro": "可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)",
|
||||
"avatar": "core/workflow/template/httpRequest",
|
||||
"flowNodeType": "httpRequest468",
|
||||
"showStatus": true,
|
||||
"position": {
|
||||
"x": 1081.967607938733,
|
||||
"y": -426.08028677656125
|
||||
},
|
||||
"version": "481",
|
||||
"inputs": [
|
||||
{
|
||||
"key": "system_addInputParam",
|
||||
"renderTypeList": ["addInputParam"],
|
||||
"valueType": "dynamic",
|
||||
"label": "",
|
||||
"required": false,
|
||||
"description": "core.module.input.description.HTTP Dynamic Input",
|
||||
"customInputConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"object",
|
||||
"arrayString",
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
"dynamic",
|
||||
"selectApp",
|
||||
"selectDataset"
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "system_httpMethod",
|
||||
"renderTypeList": ["custom"],
|
||||
"valueType": "string",
|
||||
"label": "",
|
||||
"value": "POST",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"key": "system_httpReqUrl",
|
||||
"renderTypeList": ["hidden"],
|
||||
"valueType": "string",
|
||||
"label": "",
|
||||
"description": "core.module.input.description.Http Request Url",
|
||||
"placeholder": "https://api.ai.com/getInventory",
|
||||
"required": false,
|
||||
"value": "Doc2X/URLImg2text"
|
||||
},
|
||||
{
|
||||
"key": "system_httpHeader",
|
||||
"renderTypeList": ["custom"],
|
||||
"valueType": "any",
|
||||
"value": [],
|
||||
"label": "",
|
||||
"description": "core.module.input.description.Http Request Header",
|
||||
"placeholder": "core.module.input.description.Http Request Header",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"key": "system_httpParams",
|
||||
"renderTypeList": ["hidden"],
|
||||
"valueType": "any",
|
||||
"value": [],
|
||||
"label": "",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"key": "system_httpJsonBody",
|
||||
"renderTypeList": ["hidden"],
|
||||
"valueType": "any",
|
||||
"value": "{\n \"apikey\": \"{{apikey}}\",\n \"url\": \"{{url}}\",\n \"img_correction\": \"{{img_correction}}\",\n \"formula\": \"{{img_correction}}\"\n}",
|
||||
"label": "",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"renderTypeList": ["reference"],
|
||||
"valueType": "string",
|
||||
"canEdit": true,
|
||||
"key": "apikey",
|
||||
"label": "apikey",
|
||||
"customInputConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"object",
|
||||
"arrayString",
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
"dynamic",
|
||||
"selectApp",
|
||||
"selectDataset"
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": true
|
||||
},
|
||||
"required": true,
|
||||
"value": ["pluginInput", "apikey"]
|
||||
},
|
||||
{
|
||||
"renderTypeList": ["reference"],
|
||||
"valueType": "string",
|
||||
"canEdit": true,
|
||||
"key": "url",
|
||||
"label": "url",
|
||||
"customInputConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"object",
|
||||
"arrayString",
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
"dynamic",
|
||||
"selectApp",
|
||||
"selectDataset"
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": true
|
||||
},
|
||||
"required": true,
|
||||
"value": ["pluginInput", "url"]
|
||||
},
|
||||
{
|
||||
"renderTypeList": ["reference"],
|
||||
"valueType": "boolean",
|
||||
"canEdit": true,
|
||||
"key": "img_correction",
|
||||
"label": "img_correction",
|
||||
"customInputConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"object",
|
||||
"arrayString",
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
"dynamic",
|
||||
"selectApp",
|
||||
"selectDataset"
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": true
|
||||
},
|
||||
"required": true,
|
||||
"value": ["pluginInput", "img_correction"]
|
||||
},
|
||||
{
|
||||
"renderTypeList": ["reference"],
|
||||
"valueType": "boolean",
|
||||
"canEdit": true,
|
||||
"key": "formula",
|
||||
"label": "formula",
|
||||
"customInputConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"object",
|
||||
"arrayString",
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
"dynamic",
|
||||
"selectApp",
|
||||
"selectDataset"
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": true
|
||||
},
|
||||
"required": true,
|
||||
"value": ["pluginInput", "formula"]
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"id": "error",
|
||||
"key": "error",
|
||||
"label": "请求错误",
|
||||
"description": "HTTP请求错误信息,成功时返回空",
|
||||
"valueType": "object",
|
||||
"type": "static"
|
||||
},
|
||||
{
|
||||
"id": "httpRawResponse",
|
||||
"key": "httpRawResponse",
|
||||
"label": "原始响应",
|
||||
"required": true,
|
||||
"description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。",
|
||||
"valueType": "any",
|
||||
"type": "static"
|
||||
},
|
||||
{
|
||||
"id": "system_addOutputParam",
|
||||
"key": "system_addOutputParam",
|
||||
"type": "dynamic",
|
||||
"valueType": "dynamic",
|
||||
"label": "",
|
||||
"customFieldConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"object",
|
||||
"arrayString",
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
"dynamic",
|
||||
"selectApp",
|
||||
"selectDataset"
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "xWQuEf50F3mr",
|
||||
"valueType": "string",
|
||||
"type": "dynamic",
|
||||
"key": "result",
|
||||
"label": "result"
|
||||
},
|
||||
{
|
||||
"id": "m6CJJj7GFud5",
|
||||
"valueType": "boolean",
|
||||
"type": "dynamic",
|
||||
"key": "success",
|
||||
"label": "success"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"source": "pluginInput",
|
||||
"target": "zHG5jJBkXmjB",
|
||||
"sourceHandle": "pluginInput-source-right",
|
||||
"targetHandle": "zHG5jJBkXmjB-target-left"
|
||||
},
|
||||
{
|
||||
"source": "zHG5jJBkXmjB",
|
||||
"target": "pluginOutput",
|
||||
"sourceHandle": "zHG5jJBkXmjB-source-right",
|
||||
"targetHandle": "pluginOutput-target-left"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
130
packages/plugins/src/Doc2X/URLPDF2text/index.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { delay } from '@fastgpt/global/common/system/utils';
|
||||
import { addLog } from '@fastgpt/service/common/system/log';
|
||||
|
||||
type Props = {
|
||||
apikey: string;
|
||||
url: string;
|
||||
ocr: boolean;
|
||||
};
|
||||
|
||||
// Response type same as HTTP outputs
|
||||
type Response = Promise<{
|
||||
result: string;
|
||||
success: boolean;
|
||||
}>;
|
||||
|
||||
const main = async ({ apikey, url, ocr }: Props): Response => {
|
||||
// Check the apikey
|
||||
if (!apikey) {
|
||||
return {
|
||||
result: `API key is required`,
|
||||
success: false
|
||||
};
|
||||
}
|
||||
|
||||
let real_api_key = apikey;
|
||||
if (!apikey.startsWith('sk-')) {
|
||||
const response = await fetch('https://api.doc2x.noedgeai.com/api/token/refresh', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${apikey}`
|
||||
}
|
||||
});
|
||||
if (response.status !== 200) {
|
||||
return {
|
||||
result: `Get token failed: ${await response.text()}`,
|
||||
success: false
|
||||
};
|
||||
}
|
||||
const data = await response.json();
|
||||
real_api_key = data.data.token;
|
||||
}
|
||||
|
||||
//Get the image binary from the URL
|
||||
const formData = new FormData();
|
||||
formData.append('pdf_url', url);
|
||||
formData.append('ocr', ocr ? '1' : '0');
|
||||
|
||||
let upload_url = 'https://api.doc2x.noedgeai.com/api/platform/async/pdf';
|
||||
if (real_api_key.startsWith('sk-')) {
|
||||
upload_url = 'https://api.doc2x.noedgeai.com/api/v1/async/pdf';
|
||||
}
|
||||
|
||||
let uuid;
|
||||
const uploadAttempts = [1, 2, 3];
|
||||
for await (const attempt of uploadAttempts) {
|
||||
const upload_response = await fetch(upload_url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${real_api_key}`
|
||||
},
|
||||
body: formData
|
||||
});
|
||||
if (!upload_response.ok) {
|
||||
if (upload_response.status === 429 && attempt < 3) {
|
||||
await delay(10000);
|
||||
continue;
|
||||
}
|
||||
return {
|
||||
result: `Failed to upload file: ${await upload_response.text()}`,
|
||||
success: false
|
||||
};
|
||||
}
|
||||
const upload_data = await upload_response.json();
|
||||
uuid = upload_data.data.uuid;
|
||||
break;
|
||||
}
|
||||
|
||||
// Get the result by uuid
|
||||
let result_url = 'https://api.doc2x.noedgeai.com/api/platform/async/status?uuid=' + uuid;
|
||||
if (real_api_key.startsWith('sk-')) {
|
||||
result_url = 'https://api.doc2x.noedgeai.com/api/v1/async/status?uuid=' + uuid;
|
||||
}
|
||||
|
||||
let result = '';
|
||||
// Wait for the result, at most 100s
|
||||
const maxAttempts = 100;
|
||||
for await (const _ of Array(maxAttempts).keys()) {
|
||||
const result_response = await fetch(result_url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${real_api_key}`
|
||||
}
|
||||
});
|
||||
if (!result_response.ok) {
|
||||
return {
|
||||
result: `Failed to get result: ${await result_response.text()}`,
|
||||
success: false
|
||||
};
|
||||
}
|
||||
const result_data = await result_response.json();
|
||||
if (['ready', 'processing'].includes(result_data.data.status)) {
|
||||
await delay(1000);
|
||||
} else if (result_data.data.status === 'pages limit exceeded') {
|
||||
return {
|
||||
result: 'Doc2X Pages limit exceeded',
|
||||
success: false
|
||||
};
|
||||
} else if (result_data.data.status === 'success') {
|
||||
result = await Promise.all(
|
||||
result_data.data.result.pages.map((page: { md: any }) => page.md)
|
||||
).then((pages) => pages.join('\n'));
|
||||
result = result.replace(/\\[\(\)]/g, '$').replace(/\\[\[\]]/g, '$$');
|
||||
return {
|
||||
result: result,
|
||||
success: true
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
result: `Failed to get result: ${await result_data.text()}`,
|
||||
success: false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
result: 'Timeout waiting for result',
|
||||
success: false
|
||||
};
|
||||
};
|
||||
|
||||
export default main;
|
||||
373
packages/plugins/src/Doc2X/URLPDF2text/template.json
Normal file
@@ -0,0 +1,373 @@
|
||||
{
|
||||
"author": "Menghuan1918",
|
||||
"version": "488",
|
||||
"name": "Doc2X PDF文件(URL)识别",
|
||||
"avatar": "plugins/doc2x",
|
||||
"intro": "将传入的PDF文件(URL)发送至Doc2X进行解析,返回带LaTeX公式的markdown格式的文本",
|
||||
"inputExplanationUrl": "https://fael3z0zfze.feishu.cn/wiki/Rkc5witXWiJoi5kORd2cofh6nDg?fromScene=spaceOverview",
|
||||
"showStatus": true,
|
||||
"weight": 10,
|
||||
|
||||
"isTool": true,
|
||||
"templateType": "tools",
|
||||
|
||||
"workflow": {
|
||||
"nodes": [
|
||||
{
|
||||
"nodeId": "pluginInput",
|
||||
"name": "自定义插件输入",
|
||||
"intro": "可以配置插件需要哪些输入,利用这些输入来运行插件",
|
||||
"avatar": "core/workflow/template/workflowStart",
|
||||
"flowNodeType": "pluginInput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 388.243055058894,
|
||||
"y": -75.09744210499466
|
||||
},
|
||||
"version": "481",
|
||||
"inputs": [
|
||||
{
|
||||
"renderTypeList": ["input"],
|
||||
"selectedTypeIndex": 0,
|
||||
"valueType": "string",
|
||||
"canEdit": true,
|
||||
"key": "apikey",
|
||||
"label": "apikey",
|
||||
"description": "Doc2X的验证密匙,对于个人用户可以从Doc2X官网 - 个人信息 - 身份令牌获得",
|
||||
"required": true,
|
||||
"toolDescription": "",
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"renderTypeList": ["reference"],
|
||||
"selectedTypeIndex": 0,
|
||||
"valueType": "string",
|
||||
"canEdit": true,
|
||||
"key": "url",
|
||||
"label": "url",
|
||||
"description": "待处理PDF文件的URL",
|
||||
"required": true,
|
||||
"toolDescription": "待处理PDF文件的URL"
|
||||
},
|
||||
{
|
||||
"renderTypeList": ["switch"],
|
||||
"selectedTypeIndex": 0,
|
||||
"valueType": "boolean",
|
||||
"canEdit": true,
|
||||
"key": "ocr",
|
||||
"label": "ocr",
|
||||
"description": "是否开启对PDF文件内图片的OCR识别,建议开启",
|
||||
"required": true,
|
||||
"toolDescription": "",
|
||||
"defaultValue": true
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"id": "apikey",
|
||||
"valueType": "string",
|
||||
"key": "apikey",
|
||||
"label": "apikey",
|
||||
"type": "hidden"
|
||||
},
|
||||
{
|
||||
"id": "url",
|
||||
"valueType": "string",
|
||||
"key": "url",
|
||||
"label": "url",
|
||||
"type": "hidden"
|
||||
},
|
||||
{
|
||||
"id": "formula",
|
||||
"valueType": "boolean",
|
||||
"key": "ocr",
|
||||
"label": "ocr",
|
||||
"type": "hidden"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nodeId": "pluginOutput",
|
||||
"name": "自定义插件输出",
|
||||
"intro": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出",
|
||||
"avatar": "core/workflow/template/pluginOutput",
|
||||
"flowNodeType": "pluginOutput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 1654.8021754314786,
|
||||
"y": -22.243376051504086
|
||||
},
|
||||
"version": "481",
|
||||
"inputs": [
|
||||
{
|
||||
"renderTypeList": ["reference"],
|
||||
"valueType": "string",
|
||||
"canEdit": true,
|
||||
"key": "result",
|
||||
"label": "result",
|
||||
"description": "处理结果(或者是报错信息)",
|
||||
"value": ["zHG5jJBkXmjB", "xWQuEf50F3mr"]
|
||||
},
|
||||
{
|
||||
"renderTypeList": ["reference"],
|
||||
"valueType": "boolean",
|
||||
"canEdit": true,
|
||||
"key": "success",
|
||||
"label": "success",
|
||||
"description": "是否处理成功",
|
||||
"value": ["zHG5jJBkXmjB", "m6CJJj7GFud5"]
|
||||
}
|
||||
],
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"nodeId": "zHG5jJBkXmjB",
|
||||
"name": "HTTP 请求",
|
||||
"intro": "可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)",
|
||||
"avatar": "core/workflow/template/httpRequest",
|
||||
"flowNodeType": "httpRequest468",
|
||||
"showStatus": true,
|
||||
"position": {
|
||||
"x": 1081.967607938733,
|
||||
"y": -426.08028677656125
|
||||
},
|
||||
"version": "481",
|
||||
"inputs": [
|
||||
{
|
||||
"key": "system_addInputParam",
|
||||
"renderTypeList": ["addInputParam"],
|
||||
"valueType": "dynamic",
|
||||
"label": "",
|
||||
"required": false,
|
||||
"description": "core.module.input.description.HTTP Dynamic Input",
|
||||
"customInputConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"object",
|
||||
"arrayString",
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
"dynamic",
|
||||
"selectApp",
|
||||
"selectDataset"
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "system_httpMethod",
|
||||
"renderTypeList": ["custom"],
|
||||
"valueType": "string",
|
||||
"label": "",
|
||||
"value": "POST",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"key": "system_httpReqUrl",
|
||||
"renderTypeList": ["hidden"],
|
||||
"valueType": "string",
|
||||
"label": "",
|
||||
"description": "core.module.input.description.Http Request Url",
|
||||
"placeholder": "https://api.ai.com/getInventory",
|
||||
"required": false,
|
||||
"value": "Doc2X/URLPDF2text"
|
||||
},
|
||||
{
|
||||
"key": "system_httpHeader",
|
||||
"renderTypeList": ["custom"],
|
||||
"valueType": "any",
|
||||
"value": [],
|
||||
"label": "",
|
||||
"description": "core.module.input.description.Http Request Header",
|
||||
"placeholder": "core.module.input.description.Http Request Header",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"key": "system_httpParams",
|
||||
"renderTypeList": ["hidden"],
|
||||
"valueType": "any",
|
||||
"value": [],
|
||||
"label": "",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"key": "system_httpJsonBody",
|
||||
"renderTypeList": ["hidden"],
|
||||
"valueType": "any",
|
||||
"value": "{\n \"apikey\": \"{{apikey}}\",\n \"url\": \"{{url}}\",\n \"ocr\": \"{{ocr}}\"\n}",
|
||||
"label": "",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"renderTypeList": ["reference"],
|
||||
"valueType": "string",
|
||||
"canEdit": true,
|
||||
"key": "apikey",
|
||||
"label": "apikey",
|
||||
"customInputConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"object",
|
||||
"arrayString",
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
"dynamic",
|
||||
"selectApp",
|
||||
"selectDataset"
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": true
|
||||
},
|
||||
"required": true,
|
||||
"value": ["pluginInput", "apikey"]
|
||||
},
|
||||
{
|
||||
"renderTypeList": ["reference"],
|
||||
"valueType": "string",
|
||||
"canEdit": true,
|
||||
"key": "url",
|
||||
"label": "url",
|
||||
"customInputConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"object",
|
||||
"arrayString",
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
"dynamic",
|
||||
"selectApp",
|
||||
"selectDataset"
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": true
|
||||
},
|
||||
"required": true,
|
||||
"value": ["pluginInput", "url"]
|
||||
},
|
||||
{
|
||||
"renderTypeList": ["reference"],
|
||||
"valueType": "boolean",
|
||||
"canEdit": true,
|
||||
"key": "ocr",
|
||||
"label": "ocr",
|
||||
"customInputConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"object",
|
||||
"arrayString",
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
"dynamic",
|
||||
"selectApp",
|
||||
"selectDataset"
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": true
|
||||
},
|
||||
"required": true,
|
||||
"value": ["pluginInput", "formula"]
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"id": "error",
|
||||
"key": "error",
|
||||
"label": "请求错误",
|
||||
"description": "HTTP请求错误信息,成功时返回空",
|
||||
"valueType": "object",
|
||||
"type": "static"
|
||||
},
|
||||
{
|
||||
"id": "httpRawResponse",
|
||||
"key": "httpRawResponse",
|
||||
"label": "原始响应",
|
||||
"required": true,
|
||||
"description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。",
|
||||
"valueType": "any",
|
||||
"type": "static"
|
||||
},
|
||||
{
|
||||
"id": "system_addOutputParam",
|
||||
"key": "system_addOutputParam",
|
||||
"type": "dynamic",
|
||||
"valueType": "dynamic",
|
||||
"label": "",
|
||||
"customFieldConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"object",
|
||||
"arrayString",
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
"dynamic",
|
||||
"selectApp",
|
||||
"selectDataset"
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "xWQuEf50F3mr",
|
||||
"valueType": "string",
|
||||
"type": "dynamic",
|
||||
"key": "result",
|
||||
"label": "result"
|
||||
},
|
||||
{
|
||||
"id": "m6CJJj7GFud5",
|
||||
"valueType": "boolean",
|
||||
"type": "dynamic",
|
||||
"key": "success",
|
||||
"label": "success"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"source": "pluginInput",
|
||||
"target": "zHG5jJBkXmjB",
|
||||
"sourceHandle": "pluginInput-source-right",
|
||||
"targetHandle": "zHG5jJBkXmjB-target-left"
|
||||
},
|
||||
{
|
||||
"source": "zHG5jJBkXmjB",
|
||||
"target": "pluginOutput",
|
||||
"sourceHandle": "zHG5jJBkXmjB-source-right",
|
||||
"targetHandle": "pluginOutput-target-left"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
17
packages/plugins/src/Doc2X/template.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"author": "Menghuan1918",
|
||||
"version": "488",
|
||||
"name": "Doc2X服务",
|
||||
"avatar": "plugins/doc2x",
|
||||
"intro": "传入的URL形式的图片或PDF文件发送至Doc2X进行解析,返回带LaTeX公式的markdown格式的文本。",
|
||||
"showStatus": true,
|
||||
"weight": 10,
|
||||
|
||||
"isTool": true,
|
||||
"templateType": "tools",
|
||||
|
||||
"workflow": {
|
||||
"nodes": [],
|
||||
"edges": []
|
||||
}
|
||||
}
|
||||
@@ -32,14 +32,13 @@ const main = async (props: Props, retry = 3): Response => {
|
||||
};
|
||||
} catch (error) {
|
||||
if (retry <= 0) {
|
||||
addLog.warn('DuckDuckGo error', { error });
|
||||
return {
|
||||
result: 'Failed to fetch data'
|
||||
};
|
||||
}
|
||||
|
||||
addLog.warn('DuckDuckGo error', { error });
|
||||
|
||||
await delay(Math.random() * 2000);
|
||||
await delay(Math.random() * 5000);
|
||||
return main(props, retry - 1);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -31,14 +31,13 @@ const main = async (props: Props, retry = 3): Response => {
|
||||
};
|
||||
} catch (error) {
|
||||
if (retry <= 0) {
|
||||
addLog.warn('DuckDuckGo error', { error });
|
||||
return {
|
||||
result: 'Failed to fetch data'
|
||||
};
|
||||
}
|
||||
|
||||
addLog.warn('DuckDuckGo error', { error });
|
||||
|
||||
await delay(Math.random() * 2000);
|
||||
await delay(Math.random() * 5000);
|
||||
return main(props, retry - 1);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -32,14 +32,13 @@ const main = async (props: Props, retry = 3): Response => {
|
||||
};
|
||||
} catch (error) {
|
||||
if (retry <= 0) {
|
||||
addLog.warn('DuckDuckGo error', { error });
|
||||
return {
|
||||
result: 'Failed to fetch data'
|
||||
};
|
||||
}
|
||||
|
||||
addLog.warn('DuckDuckGo error', { error });
|
||||
|
||||
await delay(Math.random() * 2000);
|
||||
await delay(Math.random() * 5000);
|
||||
return main(props, retry - 1);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -32,14 +32,13 @@ const main = async (props: Props, retry = 3): Response => {
|
||||
};
|
||||
} catch (error) {
|
||||
if (retry <= 0) {
|
||||
addLog.warn('DuckDuckGo error', { error });
|
||||
return {
|
||||
result: 'Failed to fetch data'
|
||||
};
|
||||
}
|
||||
|
||||
addLog.warn('DuckDuckGo error', { error });
|
||||
|
||||
await delay(Math.random() * 2000);
|
||||
await delay(Math.random() * 5000);
|
||||
return main(props, retry - 1);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"avatar": "core/workflow/template/duckduckgo",
|
||||
"intro": "DuckDuckGo 服务,包含网络搜索、图片搜索、新闻搜索等。",
|
||||
"showStatus": false,
|
||||
"weight": 10,
|
||||
"weight": 100,
|
||||
|
||||
"isTool": true,
|
||||
"templateType": "tools",
|
||||
|
||||
443
packages/plugins/src/feishu/template.json
Normal file
@@ -0,0 +1,443 @@
|
||||
{
|
||||
"author": "",
|
||||
"version": "488",
|
||||
"name": "飞书机器人 webhook",
|
||||
"avatar": "/imgs/app/templates/feishu.svg",
|
||||
"intro": "向飞书机器人发起 webhook 请求。",
|
||||
"inputExplanationUrl": "https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot#f62e72d5",
|
||||
"showStatus": false,
|
||||
"weight": 10,
|
||||
|
||||
"isTool": true,
|
||||
"templateType": "communication",
|
||||
|
||||
"workflow": {
|
||||
"nodes": [
|
||||
{
|
||||
"nodeId": "pluginInput",
|
||||
"name": "自定义插件输入",
|
||||
"intro": "可以配置插件需要哪些输入,利用这些输入来运行插件",
|
||||
"avatar": "core/workflow/template/workflowStart",
|
||||
"flowNodeType": "pluginInput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 156.37657136084977,
|
||||
"y": 90.73380846709256
|
||||
},
|
||||
"version": "481",
|
||||
"inputs": [
|
||||
{
|
||||
"renderTypeList": ["reference"],
|
||||
"selectedTypeIndex": 0,
|
||||
"valueType": "string",
|
||||
"canEdit": true,
|
||||
"key": "content",
|
||||
"label": "content",
|
||||
"description": "需要发送的消息",
|
||||
"required": true,
|
||||
"toolDescription": "需要发送的消息"
|
||||
},
|
||||
{
|
||||
"renderTypeList": ["input"],
|
||||
"selectedTypeIndex": 0,
|
||||
"valueType": "string",
|
||||
"canEdit": true,
|
||||
"key": "hook_url",
|
||||
"label": "hook_url",
|
||||
"description": "飞书机器人地址",
|
||||
"required": true,
|
||||
"defaultValue": ""
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"id": "query",
|
||||
"valueType": "string",
|
||||
"key": "content",
|
||||
"label": "content",
|
||||
"type": "hidden"
|
||||
},
|
||||
{
|
||||
"id": "hook_url",
|
||||
"valueType": "string",
|
||||
"key": "hook_url",
|
||||
"label": "hook_url",
|
||||
"type": "hidden"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nodeId": "pluginOutput",
|
||||
"name": "自定义插件输出",
|
||||
"intro": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出",
|
||||
"avatar": "core/workflow/template/pluginOutput",
|
||||
"flowNodeType": "pluginOutput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 2110.7223589692912,
|
||||
"y": 120.17602722162474
|
||||
},
|
||||
"version": "481",
|
||||
"inputs": [
|
||||
{
|
||||
"renderTypeList": ["reference"],
|
||||
"valueType": "object",
|
||||
"canEdit": true,
|
||||
"key": "result",
|
||||
"label": "result",
|
||||
"description": "",
|
||||
"value": ["vzreK6vHrPvZ", "httpRawResponse"]
|
||||
}
|
||||
],
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"nodeId": "vzreK6vHrPvZ",
|
||||
"name": "HTTP 请求",
|
||||
"intro": "可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)",
|
||||
"avatar": "core/workflow/template/httpRequest",
|
||||
"flowNodeType": "httpRequest468",
|
||||
"showStatus": true,
|
||||
"position": {
|
||||
"x": 1363.4233257919495,
|
||||
"y": -182.3490463845037
|
||||
},
|
||||
"version": "481",
|
||||
"inputs": [
|
||||
{
|
||||
"key": "system_addInputParam",
|
||||
"renderTypeList": ["addInputParam"],
|
||||
"valueType": "dynamic",
|
||||
"label": "",
|
||||
"required": false,
|
||||
"description": "core.module.input.description.HTTP Dynamic Input",
|
||||
"customInputConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"object",
|
||||
"arrayString",
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
"dynamic",
|
||||
"selectApp",
|
||||
"selectDataset"
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "system_httpMethod",
|
||||
"renderTypeList": ["custom"],
|
||||
"valueType": "string",
|
||||
"label": "",
|
||||
"value": "POST",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"key": "system_httpReqUrl",
|
||||
"renderTypeList": ["hidden"],
|
||||
"valueType": "string",
|
||||
"label": "",
|
||||
"description": "core.module.input.description.Http Request Url",
|
||||
"placeholder": "https://api.ai.com/getInventory",
|
||||
"required": false,
|
||||
"value": "{{url}}"
|
||||
},
|
||||
{
|
||||
"key": "system_httpHeader",
|
||||
"renderTypeList": ["custom"],
|
||||
"valueType": "any",
|
||||
"value": [],
|
||||
"label": "",
|
||||
"description": "core.module.input.description.Http Request Header",
|
||||
"placeholder": "core.module.input.description.Http Request Header",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"key": "system_httpParams",
|
||||
"renderTypeList": ["hidden"],
|
||||
"valueType": "any",
|
||||
"value": [],
|
||||
"label": "",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"key": "system_httpJsonBody",
|
||||
"renderTypeList": ["hidden"],
|
||||
"valueType": "any",
|
||||
"value": "{{content}}",
|
||||
"label": "",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"renderTypeList": ["reference"],
|
||||
"valueType": "string",
|
||||
"canEdit": true,
|
||||
"key": "url",
|
||||
"label": "url",
|
||||
"customInputConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"object",
|
||||
"arrayString",
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
"dynamic",
|
||||
"selectApp",
|
||||
"selectDataset"
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": true
|
||||
},
|
||||
"required": true,
|
||||
"value": ["pluginInput", "hook_url"]
|
||||
},
|
||||
{
|
||||
"renderTypeList": ["reference"],
|
||||
"valueType": "object",
|
||||
"canEdit": true,
|
||||
"key": "content",
|
||||
"label": "content",
|
||||
"customInputConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"object",
|
||||
"arrayString",
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
"dynamic",
|
||||
"selectApp",
|
||||
"selectDataset"
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": true
|
||||
},
|
||||
"required": true,
|
||||
"value": ["qcJpBBVtXsGd", "system_rawResponse"]
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"id": "error",
|
||||
"key": "error",
|
||||
"label": "请求错误",
|
||||
"description": "HTTP请求错误信息,成功时返回空",
|
||||
"valueType": "object",
|
||||
"type": "static"
|
||||
},
|
||||
{
|
||||
"id": "httpRawResponse",
|
||||
"key": "httpRawResponse",
|
||||
"label": "原始响应",
|
||||
"required": true,
|
||||
"description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。",
|
||||
"valueType": "any",
|
||||
"type": "static"
|
||||
},
|
||||
{
|
||||
"id": "system_addOutputParam",
|
||||
"key": "system_addOutputParam",
|
||||
"type": "dynamic",
|
||||
"valueType": "dynamic",
|
||||
"label": "",
|
||||
"customFieldConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"object",
|
||||
"arrayString",
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
"dynamic",
|
||||
"selectApp",
|
||||
"selectDataset"
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nodeId": "qcJpBBVtXsGd",
|
||||
"name": "代码运行",
|
||||
"intro": "执行一段简单的脚本代码,通常用于进行复杂的数据处理。",
|
||||
"avatar": "core/workflow/template/codeRun",
|
||||
"flowNodeType": "code",
|
||||
"showStatus": true,
|
||||
"position": {
|
||||
"x": 805.8169457909617,
|
||||
"y": -159.52218926716316
|
||||
},
|
||||
"version": "482",
|
||||
"inputs": [
|
||||
{
|
||||
"key": "system_addInputParam",
|
||||
"renderTypeList": ["addInputParam"],
|
||||
"valueType": "dynamic",
|
||||
"label": "",
|
||||
"required": false,
|
||||
"description": "这些变量会作为代码的运行的输入参数",
|
||||
"customInputConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"object",
|
||||
"arrayString",
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
"dynamic",
|
||||
"selectApp",
|
||||
"selectDataset"
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "codeType",
|
||||
"renderTypeList": ["hidden"],
|
||||
"label": "",
|
||||
"value": "js"
|
||||
},
|
||||
{
|
||||
"key": "code",
|
||||
"renderTypeList": ["custom"],
|
||||
"label": "",
|
||||
"value": "function main({data1}){\n try{\n const parseData = JSON.parse(data1)\n if(typeof parseData === 'object') {\n return parseData\n }\n return {\n \"msg_type\": \"text\",\n content: {\n \"text\": data1\n }\n }\n } catch(err) {\n return {\n \"msg_type\": \"text\",\n content: {\n \"text\": data1\n }\n }\n }\n}"
|
||||
},
|
||||
{
|
||||
"renderTypeList": ["reference"],
|
||||
"valueType": "string",
|
||||
"canEdit": true,
|
||||
"key": "data1",
|
||||
"label": "data1",
|
||||
"customInputConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"object",
|
||||
"arrayString",
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
"dynamic",
|
||||
"selectApp",
|
||||
"selectDataset"
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": true
|
||||
},
|
||||
"required": true,
|
||||
"value": ["pluginInput", "query"]
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"id": "system_rawResponse",
|
||||
"key": "system_rawResponse",
|
||||
"label": "完整响应数据",
|
||||
"valueType": "object",
|
||||
"type": "static"
|
||||
},
|
||||
{
|
||||
"id": "error",
|
||||
"key": "error",
|
||||
"label": "运行错误",
|
||||
"description": "代码运行错误信息,成功时返回空",
|
||||
"valueType": "object",
|
||||
"type": "static"
|
||||
},
|
||||
{
|
||||
"id": "system_addOutputParam",
|
||||
"key": "system_addOutputParam",
|
||||
"type": "dynamic",
|
||||
"valueType": "dynamic",
|
||||
"label": "",
|
||||
"customFieldConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"object",
|
||||
"arrayString",
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
"dynamic",
|
||||
"selectApp",
|
||||
"selectDataset"
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": false
|
||||
},
|
||||
"description": "将代码中 return 的对象作为输出,传递给后续的节点。变量名需要对应 return 的 key"
|
||||
},
|
||||
{
|
||||
"id": "qLUQfhG0ILRX",
|
||||
"type": "dynamic",
|
||||
"key": "content",
|
||||
"valueType": "object",
|
||||
"label": "content"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"source": "vzreK6vHrPvZ",
|
||||
"target": "pluginOutput",
|
||||
"sourceHandle": "vzreK6vHrPvZ-source-right",
|
||||
"targetHandle": "pluginOutput-target-left"
|
||||
},
|
||||
{
|
||||
"source": "pluginInput",
|
||||
"target": "qcJpBBVtXsGd",
|
||||
"sourceHandle": "pluginInput-source-right",
|
||||
"targetHandle": "qcJpBBVtXsGd-target-left"
|
||||
},
|
||||
{
|
||||
"source": "qcJpBBVtXsGd",
|
||||
"target": "vzreK6vHrPvZ",
|
||||
"sourceHandle": "qcJpBBVtXsGd-source-right",
|
||||
"targetHandle": "vzreK6vHrPvZ-target-left"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { Parser } from 'expr-eval';
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -163,6 +163,7 @@ export const readFileContentFromMongo = async ({
|
||||
|
||||
const encoding = file?.metadata?.encoding || detectFileEncoding(fileBuffers);
|
||||
|
||||
// Get raw text
|
||||
const { rawText } = await readRawContentByFileBuffer({
|
||||
extension,
|
||||
isQAImport,
|
||||
|
||||
@@ -2,9 +2,10 @@ import { UploadImgProps } from '@fastgpt/global/common/file/api';
|
||||
import { imageBaseUrl } from '@fastgpt/global/common/file/image/constants';
|
||||
import { MongoImage } from './schema';
|
||||
import { ClientSession } from '../../../common/mongo';
|
||||
import { guessBase64ImageType } from '../utils';
|
||||
|
||||
export function getMongoImgUrl(id: string) {
|
||||
return `${imageBaseUrl}${id}`;
|
||||
export function getMongoImgUrl(id: string, extension: string) {
|
||||
return `${imageBaseUrl}${id}.${extension}`;
|
||||
}
|
||||
|
||||
export const maxImgSize = 1024 * 1024 * 12;
|
||||
@@ -23,9 +24,10 @@ export async function uploadMongoImg({
|
||||
return Promise.reject('Image too large');
|
||||
}
|
||||
|
||||
const [base64Mime, base64Data] = base64Img.split(',')
|
||||
const mime = `image/${base64Mime.match(base64MimeRegex)?.[1] ?? 'jpeg'}`
|
||||
const [base64Mime, base64Data] = base64Img.split(',');
|
||||
const mime = `image/${base64Mime.match(base64MimeRegex)?.[1] ?? 'image/jpeg'}`;
|
||||
const binary = Buffer.from(base64Data, 'base64');
|
||||
const extension = mime.split('/')[1];
|
||||
|
||||
const { _id } = await MongoImage.create({
|
||||
type,
|
||||
@@ -36,15 +38,20 @@ export async function uploadMongoImg({
|
||||
shareId
|
||||
});
|
||||
|
||||
return getMongoImgUrl(String(_id));
|
||||
return getMongoImgUrl(String(_id), extension);
|
||||
}
|
||||
|
||||
export async function readMongoImg({ id }: { id: string }) {
|
||||
const data = await MongoImage.findById(id);
|
||||
const formatId = id.replace(/\.[^/.]+$/, '');
|
||||
|
||||
const data = await MongoImage.findById(formatId);
|
||||
if (!data) {
|
||||
return Promise.reject('Image not found');
|
||||
}
|
||||
return data;
|
||||
return {
|
||||
binary: data.binary,
|
||||
mime: data.metadata?.mime ?? guessBase64ImageType(data.binary.toString('base64'))
|
||||
};
|
||||
}
|
||||
|
||||
export async function delImgByRelatedId({
|
||||
|
||||
@@ -6,9 +6,10 @@ import { addHours } from 'date-fns';
|
||||
import { WorkerNameEnum, runWorker } from '../../../worker/utils';
|
||||
import fs from 'fs';
|
||||
import { detectFileEncoding } from '@fastgpt/global/common/file/tools';
|
||||
import { ReadFileResponse } from '../../../worker/file/type';
|
||||
import type { ReadFileResponse } from '../../../worker/readFile/type';
|
||||
|
||||
export const initMarkdownText = ({
|
||||
// match md img text and upload to db
|
||||
export const matchMdImgTextAndUpload = ({
|
||||
teamId,
|
||||
md,
|
||||
metadata
|
||||
@@ -79,7 +80,7 @@ export const readRawContentByFileBuffer = async ({
|
||||
|
||||
// markdown data format
|
||||
if (['md', 'html', 'docx'].includes(extension)) {
|
||||
rawText = await initMarkdownText({
|
||||
rawText = await matchMdImgTextAndUpload({
|
||||
teamId: teamId,
|
||||
md: rawText,
|
||||
metadata: metadata
|
||||
|
||||
@@ -47,8 +47,6 @@ const addCommonMiddleware = (schema: mongoose.Schema) => {
|
||||
|
||||
if (duration > 1000) {
|
||||
addLog.warn(`Slow operation ${duration}ms`, warnLogData);
|
||||
} else if (duration > 3000) {
|
||||
addLog.error(`Slow operation ${duration}ms`, warnLogData);
|
||||
}
|
||||
}
|
||||
next();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { delay } from '@fastgpt/global/common/system/utils';
|
||||
import { addLog } from '../system/log';
|
||||
import { connectionMongo } from './index';
|
||||
import type { Mongoose } from 'mongoose';
|
||||
@@ -17,9 +18,11 @@ export async function connectMongo(): Promise<Mongoose> {
|
||||
try {
|
||||
connectionMongo.set('strictQuery', true);
|
||||
|
||||
connectionMongo.connection.on('error', (error) => {
|
||||
connectionMongo.connection.on('error', async (error) => {
|
||||
console.log('mongo error', error);
|
||||
connectionMongo.disconnect();
|
||||
await connectionMongo.disconnect();
|
||||
await delay(1000);
|
||||
connectMongo();
|
||||
});
|
||||
connectionMongo.connection.on('disconnected', () => {
|
||||
console.log('mongo disconnected');
|
||||
@@ -43,10 +46,11 @@ export async function connectMongo(): Promise<Mongoose> {
|
||||
});
|
||||
|
||||
console.log('mongo connected');
|
||||
return connectionMongo;
|
||||
} catch (error) {
|
||||
connectionMongo.disconnect();
|
||||
addLog.error('mongo connect error', error);
|
||||
await connectionMongo.disconnect();
|
||||
await delay(1000);
|
||||
return connectMongo();
|
||||
}
|
||||
|
||||
return connectionMongo;
|
||||
}
|
||||
|
||||
@@ -6,95 +6,41 @@ import {
|
||||
} from '@fastgpt/global/core/ai/type';
|
||||
import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
|
||||
import { ChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { WorkerNameEnum, getWorker } from '../../../worker/utils';
|
||||
import { WorkerNameEnum, getWorkerController } from '../../../worker/utils';
|
||||
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import { addLog } from '../../system/log';
|
||||
|
||||
export const getTiktokenWorker = () => {
|
||||
const maxWorkers = global.systemEnv?.tokenWorkers || 20;
|
||||
|
||||
if (!global.tiktokenWorkers) {
|
||||
global.tiktokenWorkers = [];
|
||||
}
|
||||
|
||||
if (global.tiktokenWorkers.length >= maxWorkers) {
|
||||
return global.tiktokenWorkers[Math.floor(Math.random() * global.tiktokenWorkers.length)];
|
||||
}
|
||||
|
||||
const worker = getWorker(WorkerNameEnum.countGptMessagesTokens);
|
||||
|
||||
const i = global.tiktokenWorkers.push({
|
||||
index: global.tiktokenWorkers.length,
|
||||
worker,
|
||||
callbackMap: {}
|
||||
});
|
||||
|
||||
worker.on('message', ({ id, data }: { id: string; data: number }) => {
|
||||
const callback = global.tiktokenWorkers[i - 1]?.callbackMap?.[id];
|
||||
|
||||
if (callback) {
|
||||
callback?.(data);
|
||||
delete global.tiktokenWorkers[i - 1].callbackMap[id];
|
||||
}
|
||||
});
|
||||
|
||||
return global.tiktokenWorkers[i - 1];
|
||||
};
|
||||
|
||||
export const countGptMessagesTokens = (
|
||||
export const countGptMessagesTokens = async (
|
||||
messages: ChatCompletionMessageParam[],
|
||||
tools?: ChatCompletionTool[],
|
||||
functionCall?: ChatCompletionCreateParams.Function[]
|
||||
) => {
|
||||
return new Promise<number>(async (resolve) => {
|
||||
try {
|
||||
const start = Date.now();
|
||||
try {
|
||||
const workerController = getWorkerController<
|
||||
{
|
||||
messages: ChatCompletionMessageParam[];
|
||||
tools?: ChatCompletionTool[];
|
||||
functionCall?: ChatCompletionCreateParams.Function[];
|
||||
},
|
||||
number
|
||||
>({
|
||||
name: WorkerNameEnum.countGptMessagesTokens,
|
||||
maxReservedThreads: global.systemEnv?.tokenWorkers || 20
|
||||
});
|
||||
|
||||
const { worker, callbackMap } = getTiktokenWorker();
|
||||
const total = await workerController.run({ messages, tools, functionCall });
|
||||
|
||||
const id = getNanoid();
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
console.log('Count token Time out');
|
||||
resolve(
|
||||
messages.reduce((sum, item) => {
|
||||
if (item.content) {
|
||||
return sum + item.content.length * 0.5;
|
||||
}
|
||||
return sum;
|
||||
}, 0)
|
||||
);
|
||||
delete callbackMap[id];
|
||||
}, 60000);
|
||||
|
||||
callbackMap[id] = (data) => {
|
||||
// 检测是否有内存泄漏
|
||||
addLog.debug(`Count token time: ${Date.now() - start}, token: ${data}`);
|
||||
// console.log(process.memoryUsage());
|
||||
|
||||
resolve(data);
|
||||
clearTimeout(timer);
|
||||
};
|
||||
|
||||
// 可以进一步优化(传递100w token数据,实际需要300ms,较慢)
|
||||
worker.postMessage({
|
||||
id,
|
||||
messages,
|
||||
tools,
|
||||
functionCall
|
||||
});
|
||||
} catch (error) {
|
||||
addLog.error('Count token error', error);
|
||||
const total = messages.reduce((sum, item) => {
|
||||
if (item.content) {
|
||||
return sum + item.content.length;
|
||||
}
|
||||
return sum;
|
||||
}, 0);
|
||||
resolve(total);
|
||||
}
|
||||
});
|
||||
return total;
|
||||
} catch (error) {
|
||||
addLog.error('Count token error', error);
|
||||
const total = messages.reduce((sum, item) => {
|
||||
if (item.content) {
|
||||
return sum + item.content.length * 0.5;
|
||||
}
|
||||
return sum;
|
||||
}, 0);
|
||||
return total;
|
||||
}
|
||||
};
|
||||
|
||||
export const countMessagesTokens = (messages: ChatItemType[]) => {
|
||||
|
||||
@@ -30,7 +30,7 @@ const { LOG_LEVEL, STORE_LOG_LEVEL } = (() => {
|
||||
const STORE_LOG_LEVEL = (process.env.STORE_LOG_LEVEL || '').toLocaleLowerCase();
|
||||
|
||||
return {
|
||||
LOG_LEVEL: envLogLevelMap[LOG_LEVEL] || LogLevelEnum.info,
|
||||
LOG_LEVEL: envLogLevelMap[LOG_LEVEL] ?? LogLevelEnum.info,
|
||||
STORE_LOG_LEVEL: envLogLevelMap[STORE_LOG_LEVEL] ?? 99
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
InsertVectorControllerProps
|
||||
} from '../controller.d';
|
||||
import dayjs from 'dayjs';
|
||||
import { addLog } from '../../system/log';
|
||||
|
||||
export class PgVectorCtrl {
|
||||
constructor() {}
|
||||
@@ -38,9 +39,9 @@ export class PgVectorCtrl {
|
||||
`CREATE INDEX CONCURRENTLY IF NOT EXISTS create_time_index ON ${DatasetVectorTableName} USING btree(createtime);`
|
||||
);
|
||||
|
||||
console.log('init pg successful');
|
||||
addLog.info('init pg successful');
|
||||
} catch (error) {
|
||||
console.log('init pg error', error);
|
||||
addLog.error('init pg error', error);
|
||||
}
|
||||
};
|
||||
insert = async (props: InsertVectorControllerProps): Promise<{ insertId: string }> => {
|
||||
|
||||
30
packages/service/core/ai/audio/transcriptions.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import fs from 'fs';
|
||||
import { getAxiosConfig } from '../config';
|
||||
import axios from 'axios';
|
||||
import FormData from 'form-data';
|
||||
|
||||
export const aiTranscriptions = async ({
|
||||
model,
|
||||
fileStream
|
||||
}: {
|
||||
model: string;
|
||||
fileStream: fs.ReadStream;
|
||||
}) => {
|
||||
const data = new FormData();
|
||||
data.append('model', model);
|
||||
data.append('file', fileStream);
|
||||
|
||||
const aiAxiosConfig = getAxiosConfig();
|
||||
const { data: result } = await axios<{ text: string }>({
|
||||
method: 'post',
|
||||
baseURL: aiAxiosConfig.baseUrl,
|
||||
url: '/audio/transcriptions',
|
||||
headers: {
|
||||
Authorization: aiAxiosConfig.authorization,
|
||||
...data.getHeaders()
|
||||
},
|
||||
data: data
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
@@ -21,3 +21,16 @@ export const getAIApi = (props?: {
|
||||
maxRetries: 2
|
||||
});
|
||||
};
|
||||
|
||||
export const getAxiosConfig = (props?: { userKey?: UserModelSchema['openaiAccount'] }) => {
|
||||
const { userKey } = props || {};
|
||||
|
||||
const baseUrl =
|
||||
userKey?.baseUrl || global?.systemEnv?.oneapiUrl || process.env.ONEAPI_URL || openaiBaseUrl;
|
||||
const apiKey = userKey?.key || global?.systemEnv?.chatApiKey || process.env.CHAT_API_KEY || '';
|
||||
|
||||
return {
|
||||
baseUrl,
|
||||
authorization: `Bearer ${apiKey}`
|
||||
};
|
||||
};
|
||||
|
||||
@@ -82,6 +82,7 @@ export async function getPluginPreviewNode({ id }: { id: string }): Promise<Flow
|
||||
avatar: plugin.avatar,
|
||||
name: plugin.name,
|
||||
intro: plugin.intro,
|
||||
inputExplanationUrl: plugin.inputExplanationUrl,
|
||||
showStatus: plugin.showStatus,
|
||||
isTool: plugin.isTool,
|
||||
version: plugin.version,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { connectionMongo, getMongoModel, type Model } from '../../common/mongo';
|
||||
const { Schema, model, models } = connectionMongo;
|
||||
import { connectionMongo, getMongoModel } from '../../common/mongo';
|
||||
const { Schema } = connectionMongo;
|
||||
import { ChatSchema as ChatType } from '@fastgpt/global/core/chat/type.d';
|
||||
import { ChatSourceMap } from '@fastgpt/global/core/chat/constants';
|
||||
import {
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { connectionMongo, getMongoModel, type Model } from '../../common/mongo';
|
||||
const { Schema, model, models } = connectionMongo;
|
||||
import { DatasetSchemaType } from '@fastgpt/global/core/dataset/type.d';
|
||||
import { getMongoModel, Schema } from '../../common/mongo';
|
||||
import {
|
||||
DatasetStatusEnum,
|
||||
DatasetStatusMap,
|
||||
@@ -12,6 +10,8 @@ import {
|
||||
TeamMemberCollectionName
|
||||
} from '@fastgpt/global/support/user/team/constant';
|
||||
import { DatasetDefaultPermissionVal } from '@fastgpt/global/support/permission/dataset/constant';
|
||||
import { getPermissionSchema } from '@fastgpt/global/support/permission/utils';
|
||||
import type { DatasetSchemaType } from '@fastgpt/global/core/dataset/type.d';
|
||||
|
||||
export const DatasetCollectionName = 'datasets';
|
||||
|
||||
@@ -85,11 +85,10 @@ const DatasetSchema = new Schema({
|
||||
}
|
||||
}
|
||||
},
|
||||
externalReadUrl: String,
|
||||
defaultPermission: {
|
||||
type: Number,
|
||||
default: DatasetDefaultPermissionVal
|
||||
}
|
||||
externalReadUrl: {
|
||||
type: String
|
||||
},
|
||||
...getPermissionSchema(DatasetDefaultPermissionVal)
|
||||
});
|
||||
|
||||
try {
|
||||
|
||||
@@ -441,11 +441,18 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
|
||||
|
||||
// token filter
|
||||
const filterMaxTokensResult = await (async () => {
|
||||
const tokensScoreFilter = await Promise.all(
|
||||
scoreFilter.map(async (item) => ({
|
||||
...item,
|
||||
tokens: await countPromptTokens(item.q + item.a)
|
||||
}))
|
||||
);
|
||||
|
||||
const results: SearchDataResponseItemType[] = [];
|
||||
let totalTokens = 0;
|
||||
|
||||
for await (const item of scoreFilter) {
|
||||
totalTokens += await countPromptTokens(item.q + item.a);
|
||||
for await (const item of tokensScoreFilter) {
|
||||
totalTokens += item.tokens;
|
||||
|
||||
if (totalTokens > maxTokens + 500) {
|
||||
break;
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
import { replaceVariable } from '@fastgpt/global/common/string/tools';
|
||||
import { responseWriteNodeStatus } from '../../../common/response';
|
||||
import { getSystemTime } from '@fastgpt/global/common/time/timezone';
|
||||
import { replaceVariableLabel } from '@fastgpt/global/core/workflow/utils';
|
||||
|
||||
import { dispatchWorkflowStart } from './init/workflowStart';
|
||||
import { dispatchChatCompletion } from './chat/oneapi';
|
||||
@@ -120,7 +121,6 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
||||
let chatAssistantResponse: AIChatItemValueItemType[] = []; // The value will be returned to the user
|
||||
let chatNodeUsages: ChatNodeUsageType[] = [];
|
||||
let toolRunResponse: ToolRunResponseItemType;
|
||||
let runningTime = Date.now();
|
||||
let debugNextStepRunNodes: RuntimeNodeItemType[] = [];
|
||||
|
||||
/* Store special response field */
|
||||
@@ -140,13 +140,8 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
||||
[DispatchNodeResponseKeyEnum.assistantResponses]?: AIChatItemValueItemType[]; // tool module, save the response value
|
||||
}
|
||||
) {
|
||||
const time = Date.now();
|
||||
|
||||
if (responseData) {
|
||||
chatResponses.push({
|
||||
...responseData,
|
||||
runningTime: +((time - runningTime) / 1000).toFixed(2)
|
||||
});
|
||||
chatResponses.push(responseData);
|
||||
}
|
||||
if (nodeDispatchUsages) {
|
||||
chatNodeUsages = chatNodeUsages.concat(nodeDispatchUsages);
|
||||
@@ -173,8 +168,6 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
runningTime = time;
|
||||
}
|
||||
/* Pass the output of the module to the next stage */
|
||||
function nodeOutput(
|
||||
@@ -269,13 +262,14 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
||||
/* Inject data into module input */
|
||||
function getNodeRunParams(node: RuntimeNodeItemType) {
|
||||
if (node.flowNodeType === FlowNodeTypeEnum.pluginInput) {
|
||||
// Format plugin input to object
|
||||
return node.inputs.reduce<Record<string, any>>((acc, item) => {
|
||||
acc[item.key] = valueTypeFormat(item.value, item.valueType);
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
// common nodes
|
||||
// Dynamic input need to store a key.
|
||||
const dynamicInput = node.inputs.find(
|
||||
(item) => item.renderTypeList[0] === FlowNodeInputTypeEnum.addInputParam
|
||||
);
|
||||
@@ -288,9 +282,17 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
||||
node.inputs.forEach((input) => {
|
||||
if (input.key === dynamicInput?.key) return;
|
||||
|
||||
// replace {{}} variables
|
||||
// replace {{xx}} variables
|
||||
let value = replaceVariable(input.value, variables);
|
||||
|
||||
// replace {{$xx.xx$}} variables
|
||||
value = replaceVariableLabel({
|
||||
text: value,
|
||||
nodes: runtimeNodes,
|
||||
variables,
|
||||
runningNode: node
|
||||
});
|
||||
|
||||
// replace reference variables
|
||||
value = getReferenceVariableValue({
|
||||
value,
|
||||
@@ -298,12 +300,11 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
||||
variables
|
||||
});
|
||||
|
||||
// concat dynamic inputs
|
||||
// Dynamic input is stored in the dynamic key
|
||||
if (input.canEdit && dynamicInput && params[dynamicInput.key]) {
|
||||
params[dynamicInput.key][input.key] = valueTypeFormat(value, input.valueType);
|
||||
}
|
||||
|
||||
// Not dynamic input
|
||||
params[input.key] = valueTypeFormat(value, input.valueType);
|
||||
});
|
||||
|
||||
@@ -318,6 +319,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
||||
status: 'running'
|
||||
});
|
||||
}
|
||||
const startTime = Date.now();
|
||||
|
||||
// get node running params
|
||||
const params = getNodeRunParams(node);
|
||||
@@ -352,6 +354,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
||||
nodeId: node.nodeId,
|
||||
moduleName: node.name,
|
||||
moduleType: node.flowNodeType,
|
||||
runningTime: +((Date.now() - startTime) / 1000).toFixed(2),
|
||||
...dispatchRes[DispatchNodeResponseKeyEnum.nodeResponse]
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -106,6 +106,7 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
|
||||
acc[key] = valueTypeFormat(value, WorkflowIOValueTypeEnum.string);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const requestBody = await (() => {
|
||||
if (!httpJsonBody) return {};
|
||||
try {
|
||||
@@ -292,14 +293,14 @@ async function fetchData({
|
||||
function replaceVariable(text: string, obj: Record<string, any>) {
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
if (value === undefined) {
|
||||
text = text.replace(new RegExp(`{{${key}}}`, 'g'), UNDEFINED_SIGN);
|
||||
text = text.replace(new RegExp(`{{(${key})}}`, 'g'), UNDEFINED_SIGN);
|
||||
} else {
|
||||
const replacement = JSON.stringify(value);
|
||||
const unquotedReplacement =
|
||||
replacement.startsWith('"') && replacement.endsWith('"')
|
||||
? replacement.slice(1, -1)
|
||||
: replacement;
|
||||
text = text.replace(new RegExp(`{{${key}}}`, 'g'), unquotedReplacement);
|
||||
text = text.replace(new RegExp(`{{(${key})}}`, 'g'), unquotedReplacement);
|
||||
}
|
||||
}
|
||||
return text || '';
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
DispatchNodeResponseKeyEnum,
|
||||
SseResponseEventEnum
|
||||
} from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { responseWrite } from '../../../../common/response';
|
||||
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"domino-ext": "^2.1.4",
|
||||
"encoding": "^0.1.13",
|
||||
"file-type": "^19.0.0",
|
||||
"form-data": "^4.0.0",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"joplin-turndown-plugin-gfm": "^1.0.12",
|
||||
"json5": "^2.2.3",
|
||||
@@ -27,9 +28,9 @@
|
||||
"next": "14.2.5",
|
||||
"nextjs-cors": "^2.2.0",
|
||||
"node-cron": "^3.0.3",
|
||||
"node-xlsx": "^0.23.0",
|
||||
"node-xlsx": "^0.24.0",
|
||||
"papaparse": "5.4.1",
|
||||
"pdfjs-dist": "4.0.269",
|
||||
"pdfjs-dist": "4.4.168",
|
||||
"pg": "^8.10.0",
|
||||
"tiktoken": "^1.0.15",
|
||||
"tunnel": "^0.0.6",
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
/* Auth app permission */
|
||||
import { MongoApp } from '../../../core/app/schema';
|
||||
import { AppDetailType } from '@fastgpt/global/core/app/type.d';
|
||||
import { AuthPropsType } from '../type/auth.d';
|
||||
import { parseHeaderCert } from '../controller';
|
||||
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
|
||||
import { getTmbInfoByTmbId } from '../../user/team/controller';
|
||||
import { getResourcePermission } from '../controller';
|
||||
import { AppPermission } from '@fastgpt/global/support/permission/app/controller';
|
||||
import { AuthResponseType } from '../type/auth.d';
|
||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import { AppFolderTypeList } from '@fastgpt/global/core/app/constants';
|
||||
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
||||
import { splitCombinePluginId } from '../../../core/app/plugin/controller';
|
||||
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
|
||||
import { AuthModeType, AuthResponseType } from '../type';
|
||||
|
||||
export const authPluginByTmbId = async ({
|
||||
tmbId,
|
||||
@@ -112,8 +111,9 @@ export const authApp = async ({
|
||||
appId,
|
||||
per,
|
||||
...props
|
||||
}: AuthPropsType & {
|
||||
}: AuthModeType & {
|
||||
appId: ParentIdType;
|
||||
per: PermissionValueType;
|
||||
}): Promise<
|
||||
AuthResponseType & {
|
||||
app: AppDetailType;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { parseHeaderCert } from '../controller';
|
||||
import { AuthModeType } from '../type';
|
||||
import { SERVICE_LOCAL_HOST } from '../../../common/system/tools';
|
||||
import { ApiRequestProps } from '../../../type/next';
|
||||
|
||||
export const authCert = async (props: AuthModeType) => {
|
||||
const result = await parseHeaderCert(props);
|
||||
@@ -13,7 +14,7 @@ export const authCert = async (props: AuthModeType) => {
|
||||
};
|
||||
|
||||
/* auth the request from local service */
|
||||
export const authRequestFromLocal = ({ req }: AuthModeType) => {
|
||||
export const authRequestFromLocal = ({ req }: { req: ApiRequestProps }) => {
|
||||
if (req.headers.host !== SERVICE_LOCAL_HOST) {
|
||||
return Promise.reject('Invalid request');
|
||||
}
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import { AuthModeType } from '../type';
|
||||
import { AuthModeType, AuthResponseType } from '../type';
|
||||
import { DatasetFileSchema } from '@fastgpt/global/core/dataset/type';
|
||||
import { parseHeaderCert } from '../controller';
|
||||
import { getFileById } from '../../../common/file/gridfs/controller';
|
||||
import { BucketNameEnum } from '@fastgpt/global/common/file/constants';
|
||||
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||
import { OwnerPermissionVal, ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { AuthPropsType, AuthResponseType } from '../type/auth';
|
||||
import { Permission } from '@fastgpt/global/support/permission/controller';
|
||||
|
||||
export async function authFile({
|
||||
fileId,
|
||||
per = OwnerPermissionVal,
|
||||
...props
|
||||
}: AuthPropsType & {
|
||||
}: AuthModeType & {
|
||||
fileId: string;
|
||||
}): Promise<
|
||||
AuthResponseType & {
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { AuthResponseType } from '@fastgpt/global/support/permission/type';
|
||||
import { AuthModeType } from '../type';
|
||||
import { AuthModeType, AuthResponseType } from '../type';
|
||||
import { OpenApiSchema } from '@fastgpt/global/support/openapi/type';
|
||||
import { parseHeaderCert } from '../controller';
|
||||
import { getTmbInfoByTmbId } from '../../user/team/controller';
|
||||
import { MongoOpenApi } from '../../openapi/schema';
|
||||
import { OpenApiErrEnum } from '@fastgpt/global/common/error/code/openapi';
|
||||
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
import { OwnerPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { authAppByTmbId } from '../app/auth';
|
||||
import { Permission } from '@fastgpt/global/support/permission/controller';
|
||||
|
||||
export async function authOpenApiKeyCrud({
|
||||
id,
|
||||
per = 'owner',
|
||||
per = OwnerPermissionVal,
|
||||
...props
|
||||
}: AuthModeType & {
|
||||
id: string;
|
||||
@@ -21,40 +22,38 @@ export async function authOpenApiKeyCrud({
|
||||
const result = await parseHeaderCert(props);
|
||||
const { tmbId, teamId } = result;
|
||||
|
||||
const { role } = await getTmbInfoByTmbId({ tmbId });
|
||||
|
||||
const { openapi, isOwner, canWrite } = await (async () => {
|
||||
const { openapi, permission } = await (async () => {
|
||||
const openapi = await MongoOpenApi.findOne({ _id: id, teamId });
|
||||
|
||||
if (!openapi) {
|
||||
throw new Error(OpenApiErrEnum.unExist);
|
||||
return Promise.reject(OpenApiErrEnum.unExist);
|
||||
}
|
||||
|
||||
const isOwner = String(openapi.tmbId) === tmbId || role === TeamMemberRoleEnum.owner;
|
||||
const canWrite =
|
||||
isOwner || (String(openapi.tmbId) === tmbId && role !== TeamMemberRoleEnum.visitor);
|
||||
if (!!openapi.appId) {
|
||||
// if is not global openapi, then auth app
|
||||
const { app } = await authAppByTmbId({ appId: openapi.appId!, tmbId, per });
|
||||
return {
|
||||
permission: app.permission,
|
||||
openapi
|
||||
};
|
||||
}
|
||||
// if is global openapi, then auth openapi
|
||||
const { permission: tmbPer } = await getTmbInfoByTmbId({ tmbId });
|
||||
|
||||
if (per === 'r' && !canWrite) {
|
||||
return Promise.reject(OpenApiErrEnum.unAuth);
|
||||
}
|
||||
if (per === 'w' && !canWrite) {
|
||||
return Promise.reject(OpenApiErrEnum.unAuth);
|
||||
}
|
||||
if (per === 'owner' && !isOwner) {
|
||||
if (!tmbPer.checkPer(per) && tmbId !== String(openapi.tmbId)) {
|
||||
return Promise.reject(OpenApiErrEnum.unAuth);
|
||||
}
|
||||
|
||||
return {
|
||||
openapi,
|
||||
isOwner,
|
||||
canWrite
|
||||
permission: new Permission({
|
||||
per
|
||||
})
|
||||
};
|
||||
})();
|
||||
|
||||
return {
|
||||
...result,
|
||||
openapi,
|
||||
isOwner,
|
||||
canWrite
|
||||
permission
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import { getResourcePermission, parseHeaderCert } from '../controller';
|
||||
import { AuthPropsType, AuthResponseType } from '../type/auth';
|
||||
import {
|
||||
CollectionWithDatasetType,
|
||||
DatasetDataItemType,
|
||||
@@ -9,7 +8,7 @@ import {
|
||||
} from '@fastgpt/global/core/dataset/type';
|
||||
import { getTmbInfoByTmbId } from '../../user/team/controller';
|
||||
import { MongoDataset } from '../../../core/dataset/schema';
|
||||
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
import { NullPermission, PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset';
|
||||
import { DatasetPermission } from '@fastgpt/global/support/permission/dataset/controller';
|
||||
import { getCollectionWithDataset } from '../../../core/dataset/controller';
|
||||
@@ -18,9 +17,11 @@ import { getFileById } from '../../../common/file/gridfs/controller';
|
||||
import { BucketNameEnum } from '@fastgpt/global/common/file/constants';
|
||||
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||
import { MongoDatasetData } from '../../../core/dataset/data/schema';
|
||||
import { DatasetDefaultPermissionVal } from '@fastgpt/global/support/permission/dataset/constant';
|
||||
import { AuthModeType, AuthResponseType } from '../type';
|
||||
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
||||
|
||||
export async function authDatasetByTmbId({
|
||||
export const authDatasetByTmbId = async ({
|
||||
tmbId,
|
||||
datasetId,
|
||||
per
|
||||
@@ -28,30 +29,64 @@ export async function authDatasetByTmbId({
|
||||
tmbId: string;
|
||||
datasetId: string;
|
||||
per: PermissionValueType;
|
||||
}) {
|
||||
const { teamId, permission: tmbPer } = await getTmbInfoByTmbId({ tmbId });
|
||||
|
||||
}): Promise<{
|
||||
dataset: DatasetSchemaType & {
|
||||
permission: DatasetPermission;
|
||||
};
|
||||
}> => {
|
||||
const dataset = await (async () => {
|
||||
// get app and per
|
||||
const [dataset, rp] = await Promise.all([
|
||||
MongoDataset.findOne({ _id: datasetId, teamId }).lean(),
|
||||
getResourcePermission({
|
||||
teamId,
|
||||
tmbId,
|
||||
resourceId: datasetId,
|
||||
resourceType: PerResourceTypeEnum.dataset
|
||||
}) // this could be null
|
||||
const [{ teamId, permission: tmbPer }, dataset] = await Promise.all([
|
||||
getTmbInfoByTmbId({ tmbId }),
|
||||
MongoDataset.findOne({ _id: datasetId }).lean()
|
||||
]);
|
||||
|
||||
if (!dataset) {
|
||||
return Promise.reject(DatasetErrEnum.unExist);
|
||||
}
|
||||
|
||||
const isOwner = tmbPer.isOwner || String(dataset.tmbId) === String(tmbId);
|
||||
const Per = new DatasetPermission({
|
||||
per: rp?.permission ?? dataset.defaultPermission,
|
||||
isOwner
|
||||
});
|
||||
|
||||
// get dataset permission or inherit permission from parent folder.
|
||||
const { Per, defaultPermission } = await (async () => {
|
||||
if (
|
||||
dataset.type === DatasetTypeEnum.folder ||
|
||||
dataset.inheritPermission === false ||
|
||||
!dataset.parentId
|
||||
) {
|
||||
// 1. is a folder. (Folders have compeletely permission)
|
||||
// 2. inheritPermission is false.
|
||||
// 3. is root folder/dataset.
|
||||
const rp = await getResourcePermission({
|
||||
teamId,
|
||||
tmbId,
|
||||
resourceId: datasetId,
|
||||
resourceType: PerResourceTypeEnum.dataset
|
||||
});
|
||||
const Per = new DatasetPermission({
|
||||
per: rp?.permission ?? dataset.defaultPermission,
|
||||
isOwner
|
||||
});
|
||||
return {
|
||||
Per,
|
||||
defaultPermission: dataset.defaultPermission
|
||||
};
|
||||
} else {
|
||||
// is not folder and inheritPermission is true and is not root folder.
|
||||
const { dataset: parent } = await authDatasetByTmbId({
|
||||
tmbId,
|
||||
datasetId: dataset.parentId,
|
||||
per
|
||||
});
|
||||
|
||||
const Per = new DatasetPermission({
|
||||
per: parent.permission.value,
|
||||
isOwner
|
||||
});
|
||||
return {
|
||||
Per,
|
||||
defaultPermission: parent.defaultPermission
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
||||
if (!Per.checkPer(per)) {
|
||||
return Promise.reject(DatasetErrEnum.unAuthDataset);
|
||||
@@ -59,27 +94,34 @@ export async function authDatasetByTmbId({
|
||||
|
||||
return {
|
||||
...dataset,
|
||||
defaultPermission: dataset.defaultPermission ?? DatasetDefaultPermissionVal,
|
||||
defaultPermission,
|
||||
permission: Per
|
||||
};
|
||||
})();
|
||||
|
||||
return { dataset };
|
||||
}
|
||||
};
|
||||
|
||||
// Auth Dataset
|
||||
export async function authDataset({
|
||||
export const authDataset = async ({
|
||||
datasetId,
|
||||
per,
|
||||
...props
|
||||
}: AuthPropsType & {
|
||||
datasetId: string;
|
||||
}: AuthModeType & {
|
||||
datasetId: ParentIdType;
|
||||
per: PermissionValueType;
|
||||
}): Promise<
|
||||
AuthResponseType<DatasetPermission> & {
|
||||
dataset: DatasetSchemaType;
|
||||
AuthResponseType & {
|
||||
dataset: DatasetSchemaType & {
|
||||
permission: DatasetPermission;
|
||||
};
|
||||
}
|
||||
> => {
|
||||
const result = await parseHeaderCert(props);
|
||||
const { tmbId } = result;
|
||||
|
||||
if (!datasetId) {
|
||||
return Promise.reject(DatasetErrEnum.unExist);
|
||||
}
|
||||
> {
|
||||
const { teamId, tmbId } = await parseHeaderCert(props);
|
||||
|
||||
const { dataset } = await authDatasetByTmbId({
|
||||
tmbId,
|
||||
@@ -88,19 +130,17 @@ export async function authDataset({
|
||||
});
|
||||
|
||||
return {
|
||||
teamId,
|
||||
tmbId,
|
||||
dataset,
|
||||
permission: dataset.permission
|
||||
...result,
|
||||
permission: dataset.permission,
|
||||
dataset
|
||||
};
|
||||
}
|
||||
|
||||
};
|
||||
// the temporary solution for authDatasetCollection is getting the
|
||||
export async function authDatasetCollection({
|
||||
collectionId,
|
||||
per,
|
||||
per = NullPermission,
|
||||
...props
|
||||
}: AuthPropsType & {
|
||||
}: AuthModeType & {
|
||||
collectionId: string;
|
||||
}): Promise<
|
||||
AuthResponseType<DatasetPermission> & {
|
||||
@@ -132,7 +172,7 @@ export async function authDatasetFile({
|
||||
fileId,
|
||||
per,
|
||||
...props
|
||||
}: AuthPropsType & {
|
||||
}: AuthModeType & {
|
||||
fileId: string;
|
||||
}): Promise<
|
||||
AuthResponseType<DatasetPermission> & {
|
||||
@@ -178,7 +218,7 @@ export async function authDatasetFile({
|
||||
export async function authDatasetData({
|
||||
dataId,
|
||||
...props
|
||||
}: AuthPropsType & {
|
||||
}: AuthModeType & {
|
||||
dataId: string;
|
||||
}) {
|
||||
// get mongo dataset.data
|
||||
|
||||
@@ -3,17 +3,16 @@ import { OutLinkSchema } from '@fastgpt/global/support/outLink/type';
|
||||
import { parseHeaderCert } from '../controller';
|
||||
import { MongoOutLink } from '../../outLink/schema';
|
||||
import { OutLinkErrEnum } from '@fastgpt/global/common/error/code/outLink';
|
||||
import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { AuthPropsType } from '../type/auth';
|
||||
import { AuthResponseType } from '../type/auth';
|
||||
import { OwnerPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { authAppByTmbId } from '../app/auth';
|
||||
import { AuthModeType, AuthResponseType } from '../type';
|
||||
|
||||
/* crud outlink permission */
|
||||
export async function authOutLinkCrud({
|
||||
outLinkId,
|
||||
per,
|
||||
per = OwnerPermissionVal,
|
||||
...props
|
||||
}: AuthPropsType & {
|
||||
}: AuthModeType & {
|
||||
outLinkId: string;
|
||||
}): Promise<
|
||||
AuthResponseType & {
|
||||
@@ -27,13 +26,13 @@ export async function authOutLinkCrud({
|
||||
const { app, outLink } = await (async () => {
|
||||
const outLink = await MongoOutLink.findOne({ _id: outLinkId, teamId });
|
||||
if (!outLink) {
|
||||
throw new Error(OutLinkErrEnum.unExist);
|
||||
return Promise.reject(OutLinkErrEnum.unExist);
|
||||
}
|
||||
|
||||
const { app } = await authAppByTmbId({
|
||||
tmbId,
|
||||
appId: outLink.appId,
|
||||
per: ManagePermissionVal
|
||||
per: per
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -2,10 +2,10 @@ import {
|
||||
TeamCollectionName,
|
||||
TeamMemberCollectionName
|
||||
} from '@fastgpt/global/support/user/team/constant';
|
||||
import { Model, connectionMongo, getMongoModel } from '../../common/mongo';
|
||||
import { connectionMongo, getMongoModel } from '../../common/mongo';
|
||||
import type { ResourcePermissionType } from '@fastgpt/global/support/permission/type';
|
||||
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
const { Schema, model, models } = connectionMongo;
|
||||
const { Schema } = connectionMongo;
|
||||
|
||||
export const ResourcePermissionCollectionName = 'resource_permission';
|
||||
|
||||
@@ -19,7 +19,8 @@ export const ResourcePermissionSchema = new Schema({
|
||||
ref: TeamMemberCollectionName
|
||||
},
|
||||
resourceType: {
|
||||
type: Object.values(PerResourceTypeEnum),
|
||||
type: String,
|
||||
enum: Object.values(PerResourceTypeEnum),
|
||||
required: true
|
||||
},
|
||||
permission: {
|
||||
|
||||
21
packages/service/support/permission/type.d.ts
vendored
@@ -1,3 +1,4 @@
|
||||
import { Permission } from '@fastgpt/global/support/permission/controller';
|
||||
import { ApiRequestProps } from '../../type/next';
|
||||
import type { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
|
||||
@@ -10,10 +11,26 @@ export type ReqHeaderAuthType = {
|
||||
authorization?: string;
|
||||
};
|
||||
|
||||
export type AuthModeType = {
|
||||
type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Omit<T, Keys> &
|
||||
{
|
||||
[K in Keys]-?: Required<Pick<T, K>> & Partial<Omit<T, K>>;
|
||||
}[Keys];
|
||||
|
||||
type authModeType = {
|
||||
req: ApiRequestProps;
|
||||
authToken?: boolean;
|
||||
authRoot?: boolean;
|
||||
authApiKey?: boolean;
|
||||
per?: PermissionValueType | 'r' | 'w' | 'owner'; // this is for compatibility
|
||||
per?: PermissionValueType;
|
||||
};
|
||||
|
||||
export type AuthModeType = RequireAtLeastOne<authModeType, 'authApiKey' | 'authRoot' | 'authToken'>;
|
||||
|
||||
export type AuthResponseType<T extends Permission = Permission> = {
|
||||
teamId: string;
|
||||
tmbId: string;
|
||||
authType?: `${AuthUserTypeEnum}`;
|
||||
appId?: string;
|
||||
apikey?: string;
|
||||
permission: T;
|
||||
};
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
import { Permission } from '@fastgpt/global/support/permission/controller';
|
||||
import { ApiRequestProps } from '../../../type/next';
|
||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
|
||||
export type AuthPropsType = {
|
||||
req: ApiRequestProps;
|
||||
authToken?: boolean;
|
||||
authRoot?: boolean;
|
||||
authApiKey?: boolean;
|
||||
per: PermissionValueType;
|
||||
};
|
||||
|
||||
export type AuthResponseType<T = Permission> = {
|
||||
teamId: string;
|
||||
tmbId: string;
|
||||
authType?: `${AuthUserTypeEnum}`;
|
||||
appId?: string;
|
||||
apikey?: string;
|
||||
permission: T;
|
||||
};
|
||||
@@ -1,12 +1,12 @@
|
||||
import { AuthResponseType } from '../type/auth.d';
|
||||
import { AuthPropsType } from '../type/auth.d';
|
||||
import { TeamTmbItemType } from '@fastgpt/global/support/user/team/type';
|
||||
import { parseHeaderCert } from '../controller';
|
||||
import { getTmbInfoByTmbId } from '../../user/team/controller';
|
||||
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
|
||||
import { AuthModeType, AuthResponseType } from '../type';
|
||||
import { NullPermission } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
/* auth user role */
|
||||
export async function authUserPer(props: AuthPropsType): Promise<
|
||||
export async function authUserPer(props: AuthModeType): Promise<
|
||||
AuthResponseType & {
|
||||
tmb: TeamTmbItemType;
|
||||
}
|
||||
@@ -14,7 +14,7 @@ export async function authUserPer(props: AuthPropsType): Promise<
|
||||
const result = await parseHeaderCert(props);
|
||||
const tmb = await getTmbInfoByTmbId({ tmbId: result.tmbId });
|
||||
|
||||
if (!tmb.permission.checkPer(props.per)) {
|
||||
if (!tmb.permission.checkPer(props.per ?? NullPermission)) {
|
||||
return Promise.reject(TeamErrEnum.unAuthTeam);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { connectionMongo, getMongoModel, type Model } from '../../../common/mongo';
|
||||
const { Schema, model, models } = connectionMongo;
|
||||
import { connectionMongo, getMongoModel } from '../../../common/mongo';
|
||||
const { Schema } = connectionMongo;
|
||||
import { TeamMemberSchema as TeamMemberType } from '@fastgpt/global/support/user/team/type.d';
|
||||
import { userCollectionName } from '../../user/schema';
|
||||
import {
|
||||
@@ -25,8 +25,8 @@ const TeamMemberSchema = new Schema({
|
||||
default: 'Member'
|
||||
},
|
||||
role: {
|
||||
type: String,
|
||||
enum: Object.keys(TeamMemberRoleMap)
|
||||
type: String
|
||||
// enum: Object.keys(TeamMemberRoleMap) // disable enum validation for old data
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
|
||||
9
packages/service/type.d.ts
vendored
@@ -7,6 +7,7 @@ import {
|
||||
LLMModelItemType
|
||||
} from '@fastgpt/global/core/ai/model.d';
|
||||
import { SubPlanType } from '@fastgpt/global/support/wallet/sub/type';
|
||||
import { WorkerNameEnum, WorkerPool } from './worker/utils';
|
||||
import { Worker } from 'worker_threads';
|
||||
|
||||
declare global {
|
||||
@@ -20,12 +21,8 @@ declare global {
|
||||
var whisperModel: WhisperModelType;
|
||||
var reRankModels: ReRankModelItemType[];
|
||||
|
||||
var tiktokenWorkers: {
|
||||
index: number;
|
||||
worker: Worker;
|
||||
callbackMap: Record<string, (e: number) => void>;
|
||||
}[];
|
||||
|
||||
var systemLoadedGlobalVariables: boolean;
|
||||
var systemLoadedGlobalConfig: boolean;
|
||||
|
||||
var workerPoll: Record<WorkerNameEnum, WorkerPool>;
|
||||
}
|
||||
|
||||
@@ -59,16 +59,16 @@ export const readPdfFile = async ({ buffer }: ReadRawTextByBuffer): Promise<Read
|
||||
const loadingTask = pdfjs.getDocument(buffer.buffer);
|
||||
const doc = await loadingTask.promise;
|
||||
|
||||
const pageTextPromises = [];
|
||||
for (let pageNo = 1; pageNo <= doc.numPages; pageNo++) {
|
||||
pageTextPromises.push(readPDFPage(doc, pageNo));
|
||||
// Avoid OOM.
|
||||
let result = '';
|
||||
const pageArr = Array.from({ length: doc.numPages }, (_, i) => i + 1);
|
||||
for await (const pageNo of pageArr) {
|
||||
result += await readPDFPage(doc, pageNo);
|
||||
}
|
||||
|
||||
const pageTexts = await Promise.all(pageTextPromises);
|
||||
|
||||
loadingTask.destroy();
|
||||
|
||||
return {
|
||||
rawText: pageTexts.join('')
|
||||
rawText: result
|
||||
};
|
||||
};
|
||||
@@ -1,19 +1,32 @@
|
||||
import { Worker } from 'worker_threads';
|
||||
import path from 'path';
|
||||
import { addLog } from '../common/system/log';
|
||||
|
||||
export enum WorkerNameEnum {
|
||||
readFile = 'readFile',
|
||||
htmlStr2Md = 'htmlStr2Md',
|
||||
countGptMessagesTokens = 'countGptMessagesTokens'
|
||||
countGptMessagesTokens = 'countGptMessagesTokens',
|
||||
systemPluginRun = 'systemPluginRun'
|
||||
}
|
||||
|
||||
export const getSafeEnv = () => {
|
||||
return {
|
||||
LOG_LEVEL: process.env.LOG_LEVEL,
|
||||
STORE_LOG_LEVEL: process.env.STORE_LOG_LEVEL,
|
||||
NODE_ENV: process.env.NODE_ENV
|
||||
};
|
||||
};
|
||||
|
||||
export const getWorker = (name: WorkerNameEnum) => {
|
||||
const workerPath = path.join(process.cwd(), '.next', 'server', 'worker', `${name}.js`);
|
||||
return new Worker(workerPath);
|
||||
return new Worker(workerPath, {
|
||||
env: getSafeEnv()
|
||||
});
|
||||
};
|
||||
|
||||
export const runWorker = <T = any>(name: WorkerNameEnum, params?: Record<string, any>) => {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const start = Date.now();
|
||||
const worker = getWorker(name);
|
||||
|
||||
worker.postMessage(params);
|
||||
@@ -22,6 +35,11 @@ export const runWorker = <T = any>(name: WorkerNameEnum, params?: Record<string,
|
||||
if (msg.type === 'error') return reject(msg.data);
|
||||
|
||||
resolve(msg.data);
|
||||
|
||||
const time = Date.now() - start;
|
||||
if (time > 1000) {
|
||||
addLog.info(`Worker ${name} run time: ${time}ms`);
|
||||
}
|
||||
});
|
||||
|
||||
worker.on('error', (err) => {
|
||||
@@ -34,3 +52,169 @@ export const runWorker = <T = any>(name: WorkerNameEnum, params?: Record<string,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
type WorkerRunTaskType<T> = { data: T; resolve: (e: any) => void; reject: (e: any) => void };
|
||||
type WorkerQueueItem = {
|
||||
id: string;
|
||||
worker: Worker;
|
||||
status: 'running' | 'idle';
|
||||
taskTime: number;
|
||||
timeoutId?: NodeJS.Timeout;
|
||||
resolve: (e: any) => void;
|
||||
reject: (e: any) => void;
|
||||
};
|
||||
type WorkerResponse<T = any> = {
|
||||
id: string;
|
||||
type: 'success' | 'error';
|
||||
data: T;
|
||||
};
|
||||
|
||||
/*
|
||||
多线程任务管理
|
||||
* 全局只需要创建一个示例
|
||||
* 可以设置最大常驻线程(不会被销毁),线程满了后,后续任务会等待执行。
|
||||
* 每次执行,会把数据丢到一个空闲线程里运行。主线程需要监听子线程返回的数据,并执行对于的 callback,主要是通过 workerId 进行标记。
|
||||
* 务必保证,每个线程只会同时运行 1 个任务,否则 callback 会对应不上。
|
||||
*/
|
||||
export class WorkerPool<Props = Record<string, any>, Response = any> {
|
||||
name: WorkerNameEnum;
|
||||
maxReservedThreads: number;
|
||||
workerQueue: WorkerQueueItem[] = [];
|
||||
waitQueue: WorkerRunTaskType<Props>[] = [];
|
||||
|
||||
constructor({ name, maxReservedThreads }: { name: WorkerNameEnum; maxReservedThreads: number }) {
|
||||
this.name = name;
|
||||
this.maxReservedThreads = maxReservedThreads;
|
||||
}
|
||||
|
||||
runTask({ data, resolve, reject }: WorkerRunTaskType<Props>) {
|
||||
// Get idle worker or create a new worker
|
||||
const runningWorker = (() => {
|
||||
const worker = this.workerQueue.find((item) => item.status === 'idle');
|
||||
if (worker) return worker;
|
||||
|
||||
if (this.workerQueue.length < this.maxReservedThreads) {
|
||||
return this.createWorker();
|
||||
}
|
||||
})();
|
||||
|
||||
if (runningWorker) {
|
||||
// Update memory data to latest task
|
||||
runningWorker.status = 'running';
|
||||
runningWorker.taskTime = Date.now();
|
||||
runningWorker.resolve = resolve;
|
||||
runningWorker.reject = reject;
|
||||
runningWorker.timeoutId = setTimeout(() => {
|
||||
reject('Worker timeout');
|
||||
}, 30000);
|
||||
|
||||
runningWorker.worker.postMessage({
|
||||
id: runningWorker.id,
|
||||
...data
|
||||
});
|
||||
} else {
|
||||
// Not enough worker, push to wait queue
|
||||
this.waitQueue.push({ data, resolve, reject });
|
||||
}
|
||||
}
|
||||
|
||||
run(data: Props) {
|
||||
// watch memory
|
||||
addLog.debug(`${this.name} worker queueLength: ${this.workerQueue.length}`);
|
||||
|
||||
return new Promise<Response>((resolve, reject) => {
|
||||
/*
|
||||
Whether the task is executed immediately or delayed, the promise callback will dispatch after task complete.
|
||||
*/
|
||||
this.runTask({
|
||||
data,
|
||||
resolve,
|
||||
reject
|
||||
});
|
||||
}).finally(() => {
|
||||
// Run wait queue
|
||||
const waitTask = this.waitQueue.shift();
|
||||
if (waitTask) {
|
||||
this.runTask(waitTask);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
createWorker() {
|
||||
// Create a new worker and push it queue.
|
||||
const workerId = `${Date.now()}${Math.random()}`;
|
||||
const worker = getWorker(this.name);
|
||||
|
||||
const item: WorkerQueueItem = {
|
||||
id: workerId,
|
||||
worker,
|
||||
status: 'running',
|
||||
taskTime: Date.now(),
|
||||
resolve: () => {},
|
||||
reject: () => {}
|
||||
};
|
||||
this.workerQueue.push(item);
|
||||
|
||||
// watch response
|
||||
worker.on('message', ({ id, type, data }: WorkerResponse<Response>) => {
|
||||
// Run callback
|
||||
const workerItem = this.workerQueue.find((item) => item.id === id);
|
||||
|
||||
if (!workerItem) {
|
||||
addLog.warn('Invalid worker', { id, type, data });
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === 'success') {
|
||||
workerItem.resolve(data);
|
||||
} else if (type === 'error') {
|
||||
workerItem.reject(data);
|
||||
}
|
||||
|
||||
// Clear timeout timer and update worker status
|
||||
clearTimeout(workerItem.timeoutId);
|
||||
workerItem.status = 'idle';
|
||||
});
|
||||
|
||||
// Worker error, terminate and delete it.(Un catch error)
|
||||
worker.on('error', (err) => {
|
||||
addLog.warn('Worker error', { err });
|
||||
this.deleteWorker(workerId);
|
||||
});
|
||||
worker.on('messageerror', (err) => {
|
||||
addLog.warn('Worker error', { err });
|
||||
this.deleteWorker(workerId);
|
||||
});
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
deleteWorker(workerId: string) {
|
||||
const item = this.workerQueue.find((item) => item.id === workerId);
|
||||
if (item) {
|
||||
item.reject?.('error');
|
||||
clearTimeout(item.timeoutId);
|
||||
item.worker.terminate();
|
||||
}
|
||||
|
||||
this.workerQueue = this.workerQueue.filter((item) => item.id !== workerId);
|
||||
}
|
||||
}
|
||||
|
||||
export const getWorkerController = <Props, Response>(props: {
|
||||
name: WorkerNameEnum;
|
||||
maxReservedThreads: number;
|
||||
}) => {
|
||||
if (!global.workerPoll) {
|
||||
// @ts-ignore
|
||||
global.workerPoll = {};
|
||||
}
|
||||
|
||||
const name = props.name;
|
||||
|
||||
if (global.workerPoll[name]) return global.workerPoll[name] as WorkerPool<Props, Response>;
|
||||
|
||||
global.workerPoll[name] = new WorkerPool(props);
|
||||
|
||||
return global.workerPoll[name] as WorkerPool<Props, Response>;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Image } from '@chakra-ui/react';
|
||||
import { Box, Flex, Image } from '@chakra-ui/react';
|
||||
import type { ImageProps } from '@chakra-ui/react';
|
||||
import { LOGO_ICON } from '@fastgpt/global/common/system/constants';
|
||||
import MyIcon from '../Icon';
|
||||
@@ -10,12 +10,13 @@ const Avatar = ({ w = '30px', src, ...props }: ImageProps) => {
|
||||
const isIcon = !!iconPaths[src as any];
|
||||
|
||||
return isIcon ? (
|
||||
<MyIcon name={src as any} w={w} borderRadius={props.borderRadius} />
|
||||
<Box {...props}>
|
||||
<MyIcon name={src as any} w={w} borderRadius={props.borderRadius} />
|
||||
</Box>
|
||||
) : (
|
||||
<Image
|
||||
fallbackSrc={LOGO_ICON}
|
||||
fallbackStrategy={'onError'}
|
||||
// borderRadius={'md'}
|
||||
objectFit={'contain'}
|
||||
alt=""
|
||||
w={w}
|
||||
|
||||
@@ -29,12 +29,14 @@ export const iconPaths = {
|
||||
'common/gitLight': () => import('./icons/common/gitLight.svg'),
|
||||
'common/googleFill': () => import('./icons/common/googleFill.svg'),
|
||||
'common/importLight': () => import('./icons/common/importLight.svg'),
|
||||
'common/info': () => import('./icons/common/info.svg'),
|
||||
'common/inviteLight': () => import('./icons/common/inviteLight.svg'),
|
||||
'common/language/en': () => import('./icons/common/language/en.svg'),
|
||||
'common/language/zh': () => import('./icons/common/language/zh.svg'),
|
||||
'common/leftArrowLight': () => import('./icons/common/leftArrowLight.svg'),
|
||||
'common/linkBlue': () => import('./icons/common/linkBlue.svg'),
|
||||
'common/loading': () => import('./icons/common/loading.svg'),
|
||||
'common/logLight': () => import('./icons/common/logLight.svg'),
|
||||
'common/navbar/pluginFill': () => import('./icons/common/navbar/pluginFill.svg'),
|
||||
'common/navbar/pluginLight': () => import('./icons/common/navbar/pluginLight.svg'),
|
||||
'common/openai': () => import('./icons/common/openai.svg'),
|
||||
@@ -100,6 +102,9 @@ export const iconPaths = {
|
||||
'core/chat/chatFill': () => import('./icons/core/chat/chatFill.svg'),
|
||||
'core/chat/chatLight': () => import('./icons/core/chat/chatLight.svg'),
|
||||
'core/chat/chatModelTag': () => import('./icons/core/chat/chatModelTag.svg'),
|
||||
'core/chat/chevronDown': () => import('./icons/core/chat/chevronDown.svg'),
|
||||
'core/chat/chevronSelector': () => import('./icons/core/chat/chevronSelector.svg'),
|
||||
'core/chat/chevronUp': () => import('./icons/core/chat/chevronUp.svg'),
|
||||
'core/chat/export/pdf': () => import('./icons/core/chat/export/pdf.svg'),
|
||||
'core/chat/feedback/badLight': () => import('./icons/core/chat/feedback/badLight.svg'),
|
||||
'core/chat/feedback/goodLight': () => import('./icons/core/chat/feedback/goodLight.svg'),
|
||||
@@ -111,6 +116,7 @@ export const iconPaths = {
|
||||
'core/chat/sendFill': () => import('./icons/core/chat/sendFill.svg'),
|
||||
'core/chat/sendLight': () => import('./icons/core/chat/sendLight.svg'),
|
||||
'core/chat/setTopLight': () => import('./icons/core/chat/setTopLight.svg'),
|
||||
'core/chat/sideLine': () => import('./icons/core/chat/sideLine.svg'),
|
||||
'core/chat/speaking': () => import('./icons/core/chat/speaking.svg'),
|
||||
'core/chat/stopSpeech': () => import('./icons/core/chat/stopSpeech.svg'),
|
||||
'core/dataset/commonDataset': () => import('./icons/core/dataset/commonDataset.svg'),
|
||||
@@ -198,6 +204,7 @@ export const iconPaths = {
|
||||
'core/workflow/template/textConcat': () =>
|
||||
import('./icons/core/workflow/template/textConcat.svg'),
|
||||
'core/workflow/template/toolCall': () => import('./icons/core/workflow/template/toolCall.svg'),
|
||||
'core/workflow/template/variable': () => import('./icons/core/workflow/template/variable.svg'),
|
||||
'core/workflow/template/variableUpdate': () =>
|
||||
import('./icons/core/workflow/template/variableUpdate.svg'),
|
||||
'core/workflow/template/workflowStart': () =>
|
||||
@@ -245,6 +252,7 @@ export const iconPaths = {
|
||||
'phoneTabbar/me': () => import('./icons/phoneTabbar/me.svg'),
|
||||
'phoneTabbar/tool': () => import('./icons/phoneTabbar/tool.svg'),
|
||||
'phoneTabbar/toolFill': () => import('./icons/phoneTabbar/toolFill.svg'),
|
||||
'plugins/doc2x': () => import('./icons/plugins/doc2x.svg'),
|
||||
'plugins/textEditor': () => import('./icons/plugins/textEditor.svg'),
|
||||
'price/bg': () => import('./icons/price/bg.svg'),
|
||||
'price/right': () => import('./icons/price/right.svg'),
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 24" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.8999 3.51941C8.21617 3.51941 4.41928 7.3163 4.41928 12C4.41928 16.6837 8.21617 20.4806 12.8999 20.4806C17.5836 20.4806 21.3805 16.6837 21.3805 12C21.3805 7.3163 17.5836 3.51941 12.8999 3.51941ZM2.41928 12C2.41928 6.21173 7.1116 1.51941 12.8999 1.51941C18.6881 1.51941 23.3805 6.21173 23.3805 12C23.3805 17.7883 18.6881 22.4806 12.8999 22.4806C7.1116 22.4806 2.41928 17.7883 2.41928 12ZM11.8999 8.20776C11.8999 7.65548 12.3476 7.20776 12.8999 7.20776H12.9094C13.4616 7.20776 13.9094 7.65548 13.9094 8.20776C13.9094 8.76005 13.4616 9.20776 12.9094 9.20776H12.8999C12.3476 9.20776 11.8999 8.76005 11.8999 8.20776ZM12.8999 11C13.4522 11 13.8999 11.4477 13.8999 12V15.7922C13.8999 16.3445 13.4522 16.7922 12.8999 16.7922C12.3476 16.7922 11.8999 16.3445 11.8999 15.7922V12C11.8999 11.4477 12.3476 11 12.8999 11Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 944 B |
@@ -0,0 +1 @@
|
||||
<svg t="1721963050324" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4271" width="200" height="200"><path d="M269.844659 81.4308h44.821057v166.626082h-44.821057zM677.140966 491.719232c52.335426 0 102.092273 19.937769 140.105639 56.13883 38.126482 36.31053 60.461599 85.284073 62.891788 137.900467 2.5056 54.276658-16.27424 106.280032-52.881549 146.431672-36.60731 40.15164-86.65972 63.643469-140.936379 66.150285-3.180653 0.147174-6.401444 0.221369-9.576016 0.221369-52.341508 0-102.102004-19.936552-140.114153-56.136398-38.126482-36.309314-60.461599-85.284073-62.891789-137.902899-2.5056-54.276658 16.27424-106.280032 52.88155-146.431672 36.60731-40.15164 86.65972-63.643469 140.936379-66.149069a208.122961 208.122961 0 0 1 9.576016-0.221369h0.008514m-0.00973-44.822274c-3.859355 0-7.746684 0.088791-11.642528 0.268805-136.951744 6.3236-242.847422 122.470346-236.525038 259.422091 6.143586 133.0559 115.942406 236.793842 247.779562 236.793842 3.859355 0 7.747901-0.088791 11.642529-0.268804 136.951744-6.322384 242.847422-122.470346 236.525037-259.422091-6.143586-133.057117-115.942406-236.798708-247.779562-236.793843z" fill="#4E4D4D" p-id="4272"></path><path d="M490.264524 891.110734a272.361206 272.361206 0 0 1-32.682275-37.369937H180.453104c-20.912034 0-37.927007-17.013757-37.927007-37.92579v-590.263526c0-20.912034 17.013757-37.927007 37.927007-37.927007H732.799354c20.912034 0 37.925791 17.013757 37.925791 37.927007V441.15597a268.605238 268.605238 0 0 1 44.821057 21.463023V225.551481c0-45.70045-37.047614-82.746848-82.746848-82.746849H180.453104c-45.70045 0-82.746848 37.047614-82.746848 82.746849v590.263526c0 45.70045 37.047614 82.746848 82.746848 82.746848h317.980164a273.587248 273.587248 0 0 1-8.168744-7.451121z" fill="#4E4D4D" p-id="4273"></path><path d="M770.725145 489.61623a225.243754 225.243754 0 0 1 44.821057 27.231985v-0.21407a225.182938 225.182938 0 0 0-44.821057-27.114003v0.096088zM812.590566 778.530212H646.820768V576.105667h44.821057v157.604704h120.948741zM209.55091 380.121489h498.255687v44.821057H209.55091zM600.682445 81.4308h44.821058v166.626082h-44.821058zM406.842623 712.17437H209.55091v44.821057h203.864657a272.351476 272.351476 0 0 1-6.572944-44.821057zM450.941192 546.147929H209.55091v44.821057h217.435038a268.707408 268.707408 0 0 1 23.955244-44.821057z" p-id="4274"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 19 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.237 6.21967C4.5299 5.92678 5.00477 5.92678 5.29766 6.21967L9.26733 10.1893L13.237 6.21967C13.5299 5.92678 14.0048 5.92678 14.2977 6.21967C14.5906 6.51256 14.5906 6.98744 14.2977 7.28033L9.79766 11.7803C9.50477 12.0732 9.0299 12.0732 8.737 11.7803L4.237 7.28033C3.94411 6.98744 3.94411 6.51256 4.237 6.21967Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 444 B |
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.28082 5.72828C6.95538 6.05372 6.95538 6.58136 7.28082 6.90679C7.60626 7.23223 8.1339 7.23223 8.45933 6.90679L10.5 4.86615L12.5406 6.90679C12.8661 7.23223 13.3937 7.23223 13.7191 6.90679C14.0446 6.58136 14.0446 6.05372 13.7191 5.72828L11.0892 3.09839C10.7638 2.77295 10.2362 2.77295 9.91072 3.09839L7.28082 5.72828ZM7.28082 13.0893C6.95538 13.4147 6.95538 13.9424 7.28082 14.2678L9.91072 16.8977C9.9514 16.9384 9.99524 16.974 10.0414 17.0045C10.3649 17.2181 10.8045 17.1825 11.0892 16.8977L13.7191 14.2678C14.0446 13.9424 14.0446 13.4147 13.7191 13.0893C13.3937 12.7639 12.8661 12.7639 12.5406 13.0893L10.5 15.1299L8.45933 13.0893C8.1339 12.7639 7.60626 12.7639 7.28082 13.0893Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 814 B |
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.96967 11.7803C4.26256 12.0732 4.73744 12.0732 5.03033 11.7803L9 7.81066L12.9697 11.7803C13.2626 12.0732 13.7374 12.0732 14.0303 11.7803C14.3232 11.4874 14.3232 11.0126 14.0303 10.7197L9.53033 6.21967C9.23744 5.92678 8.76256 5.92678 8.46967 6.21967L3.96967 10.7197C3.67678 11.0126 3.67678 11.4874 3.96967 11.7803Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 449 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="4" height="14" viewBox="0 0 4 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<line x1="2" y1="2" x2="2" y2="12" stroke="#3370FF" stroke-width="4" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 195 B |
@@ -0,0 +1,12 @@
|
||||
<svg viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="36" height="36" fill="url(#paint0_linear_7585_26727)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.5796 26.9153C11.7938 26.9153 11.9675 26.7416 11.9675 26.5274V24.7153C11.9675 24.5011 11.7938 24.3274 11.5796 24.3274C11.3077 24.3274 11.1751 24.2403 11.0931 24.1123C10.9905 23.9444 10.9068 23.5904 10.9068 22.9711V20.9846C10.9068 20.17 10.8199 19.4794 10.6306 18.928C10.5033 18.557 10.3257 18.2383 10.0893 17.99C10.6741 17.3773 10.9068 16.338 10.9068 15.035V12.971C10.9068 12.4371 10.9931 12.1027 11.1124 11.913C11.2128 11.7534 11.349 11.6728 11.5796 11.6728C11.7938 11.6728 11.9675 11.4991 11.9675 11.2849V9.47286C11.9675 9.25863 11.7938 9.08496 11.5796 9.08496C10.5396 9.08496 9.7021 9.37682 9.16909 10.044C8.64841 10.6893 8.43401 11.7393 8.43401 13.0872V15.2481C8.43401 15.8426 8.35093 16.2274 8.22932 16.4508C8.17169 16.5566 8.11045 16.617 8.05257 16.6524C7.99561 16.6873 7.92076 16.711 7.81429 16.711C7.60006 16.711 7.42639 16.8847 7.42639 17.0989V18.8819C7.42639 19.0961 7.60006 19.2698 7.81429 19.2698C8.0075 19.2698 8.13178 19.3406 8.2323 19.5185C8.35119 19.7289 8.43401 20.0944 8.43401 20.6649V22.9905C8.43401 24.3313 8.65018 25.3744 9.18213 26.0028C9.72047 26.6387 10.5521 26.9153 11.5796 26.9153Z" fill="white"/>
|
||||
<path d="M24.0325 26.5274C24.0325 26.7416 24.2061 26.9153 24.4203 26.9153C25.4479 26.9153 26.2795 26.6387 26.8178 26.0028C27.3498 25.3744 27.5659 24.3313 27.5659 22.9905V20.6649C27.5659 20.0944 27.6488 19.7289 27.7676 19.5185C27.8682 19.3406 27.9925 19.2698 28.1857 19.2698C28.3999 19.2698 28.5736 19.0961 28.5736 18.8819V17.0989C28.5736 16.8847 28.3999 16.711 28.1857 16.711C28.0792 16.711 28.0043 16.6873 27.9474 16.6524C27.8895 16.617 27.8283 16.5566 27.7706 16.4508C27.649 16.2274 27.5659 15.8426 27.5659 15.2481V13.0872C27.5659 11.7392 27.3515 10.6891 26.8307 10.0438C26.2977 9.37677 25.4603 9.08496 24.4203 9.08496C24.2061 9.08496 24.0325 9.25863 24.0325 9.47286V11.2849C24.0325 11.4991 24.2061 11.6728 24.4203 11.6728C24.651 11.6728 24.7871 11.7534 24.8875 11.913C25.0069 12.1027 25.0931 12.4371 25.0931 12.971V15.035C25.0931 16.338 25.3259 17.3773 25.9107 17.99C25.6743 18.2383 25.4966 18.557 25.3693 18.928C25.1801 19.4794 25.0931 20.17 25.0931 20.9846V22.9711C25.0931 23.5904 25.0094 23.9445 24.9068 24.1123C24.8249 24.2404 24.6922 24.3274 24.4203 24.3274C24.2061 24.3274 24.0325 24.5011 24.0325 24.7153V26.5274Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.8172 15.3258C13.4006 14.9093 13.4006 14.2339 13.8172 13.8173C14.2337 13.4007 14.9091 13.4007 15.3257 13.8173L18 16.4916L20.6742 13.8173C21.0908 13.4007 21.7662 13.4007 22.1828 13.8173C22.5994 14.2339 22.5994 14.9093 22.1828 15.3258L19.5085 18.0001L22.1828 20.6744C22.5994 21.091 22.5994 21.7664 22.1828 22.1829C21.7662 22.5995 21.0908 22.5995 20.6742 22.1829L18 19.5087L15.3257 22.1829C14.9091 22.5995 14.2337 22.5995 13.8172 22.1829C13.4006 21.7664 13.4006 21.091 13.8172 20.6744L16.4914 18.0001L13.8172 15.3258Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_7585_26727" x1="18" y1="0" x2="5.5" y2="33" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#69C1FF"/>
|
||||
<stop offset="1" stop-color="#53A3FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1,6 @@
|
||||
<svg viewBox="0 0 42 42" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="28.6606" y="8.44495" width="6.92163" height="12.0376" rx="3.46082" transform="rotate(45 28.6606 8.44495)" fill="#7748F9"/>
|
||||
<rect x="16.957" y="20.1488" width="6.92163" height="12.0376" rx="3.46082" transform="rotate(45 16.957 20.1488)" fill="#7748F9"/>
|
||||
<rect x="20.1489" y="25.0432" width="6.92163" height="12.0376" rx="3.46082" transform="rotate(-45 20.1489 25.0432)" fill="#BFABFB"/>
|
||||
<rect x="8.44482" y="13.3394" width="6.92163" height="12.0376" rx="3.46082" transform="rotate(-45 8.44482 13.3394)" fill="#BFABFB"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 609 B |
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Box, Flex, useTheme, Grid, type GridProps } from '@chakra-ui/react';
|
||||
import { Box, Flex, useTheme, Grid, type GridProps, HStack } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyTooltip from '../MyTooltip';
|
||||
import QuestionTip from '../MyTooltip/QuestionTip';
|
||||
@@ -90,16 +90,17 @@ const LeftRadio = ({
|
||||
</Box>
|
||||
<Box flex={'1 0 0'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box
|
||||
<HStack
|
||||
spacing={1}
|
||||
color={'myGray.900'}
|
||||
fontWeight={item.desc ? '500' : 'normal'}
|
||||
whiteSpace={'nowrap'}
|
||||
fontSize={'sm'}
|
||||
>
|
||||
{typeof item.title === 'string' ? t(item.title as any) : item.title}
|
||||
</Box>
|
||||
<Box>{typeof item.title === 'string' ? t(item.title as any) : item.title}</Box>
|
||||
{!!item.tooltip && <QuestionTip label={item.tooltip} ml={1} color={'myGray.600'} />}
|
||||
</HStack>
|
||||
</Flex>
|
||||
{!!item.tooltip && <QuestionTip label={item.tooltip} ml={1} color={'myGray.600'} />}
|
||||
{!!item.desc && (
|
||||
<Box fontSize={'xs'} color={'myGray.500'} lineHeight={1.2}>
|
||||
{t(item.desc as any)}
|
||||
|
||||
@@ -52,6 +52,7 @@ const LightRowTabs = <ValueType = string,>({
|
||||
fontSize={sizeMap.fontSize}
|
||||
overflowX={'auto'}
|
||||
userSelect={'none'}
|
||||
display={'inline-grid'}
|
||||
{...props}
|
||||
>
|
||||
{list.map((item) => (
|
||||
|
||||
@@ -17,7 +17,7 @@ const CodeEditor = (props: Props) => {
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
iconSrc="modal/edit"
|
||||
title={t('common:Code editor')}
|
||||
title={t('common:code_editor')}
|
||||
w={'full'}
|
||||
>
|
||||
<ModalBody>
|
||||
|
||||
@@ -80,7 +80,6 @@ const JSONEditor = ({
|
||||
const lineContent = model.getLineContent(position.lineNumber);
|
||||
|
||||
if (context.triggerCharacter) {
|
||||
console.log(context.triggerCharacter);
|
||||
triggerChar.current = context.triggerCharacter;
|
||||
}
|
||||
const word = model.getWordUntilPosition(position);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ContentEditable } from '@lexical/react/LexicalContentEditable';
|
||||
import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin';
|
||||
import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin';
|
||||
import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary';
|
||||
import VariablePickerPlugin from './plugins/VariablePickerPlugin';
|
||||
import VariableLabelPickerPlugin from './plugins/VariableLabelPickerPlugin';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import styles from './index.module.scss';
|
||||
import VariablePlugin from './plugins/VariablePlugin';
|
||||
@@ -13,11 +13,15 @@ import { VariableNode } from './plugins/VariablePlugin/node';
|
||||
import { EditorState, LexicalEditor } from 'lexical';
|
||||
import OnBlurPlugin from './plugins/OnBlurPlugin';
|
||||
import MyIcon from '../../Icon';
|
||||
import { EditorVariablePickerType } from './type.d';
|
||||
import { EditorVariableLabelPickerType, EditorVariablePickerType } from './type.d';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import FocusPlugin from './plugins/FocusPlugin';
|
||||
import { textToEditorState } from './utils';
|
||||
import { MaxLengthPlugin } from './plugins/MaxLengthPlugin';
|
||||
import { VariableLabelNode } from './plugins/VariableLabelPlugin/node';
|
||||
import VariableLabelPlugin from './plugins/VariableLabelPlugin';
|
||||
import { useDeepCompareEffect } from 'ahooks';
|
||||
import VariablePickerPlugin from './plugins/VariablePickerPlugin';
|
||||
|
||||
export default function Editor({
|
||||
h = 200,
|
||||
@@ -26,6 +30,7 @@ export default function Editor({
|
||||
showOpenModal = true,
|
||||
onOpenModal,
|
||||
variables,
|
||||
variableLabels,
|
||||
onChange,
|
||||
onBlur,
|
||||
value,
|
||||
@@ -38,6 +43,7 @@ export default function Editor({
|
||||
showOpenModal?: boolean;
|
||||
onOpenModal?: () => void;
|
||||
variables: EditorVariablePickerType[];
|
||||
variableLabels: EditorVariableLabelPickerType[];
|
||||
onChange?: (editorState: EditorState, editor: LexicalEditor) => void;
|
||||
onBlur?: (editor: LexicalEditor) => void;
|
||||
value?: string;
|
||||
@@ -51,7 +57,7 @@ export default function Editor({
|
||||
|
||||
const initialConfig = {
|
||||
namespace: 'promptEditor',
|
||||
nodes: [VariableNode],
|
||||
nodes: [VariableNode, VariableLabelNode],
|
||||
editorState: textToEditorState(value),
|
||||
onError: (error: Error) => {
|
||||
throw error;
|
||||
@@ -77,13 +83,19 @@ export default function Editor({
|
||||
document.addEventListener('mouseup', handleMouseUp);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
useDeepCompareEffect(() => {
|
||||
if (focus) return;
|
||||
setKey(getNanoid(6));
|
||||
}, [value, variables.length]);
|
||||
}, [value, variables, variableLabels]);
|
||||
|
||||
return (
|
||||
<Box position={'relative'} width={'full'} h={`${height}px`} cursor={'text'}>
|
||||
<Box
|
||||
position={'relative'}
|
||||
width={'full'}
|
||||
h={`${height}px`}
|
||||
cursor={'text'}
|
||||
color={'myGray.700'}
|
||||
>
|
||||
<LexicalComposer initialConfig={initialConfig} key={key}>
|
||||
<PlainTextPlugin
|
||||
contentEditable={
|
||||
@@ -127,8 +139,10 @@ export default function Editor({
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<VariablePickerPlugin variables={variables} />
|
||||
<VariableLabelPlugin variables={variableLabels} />
|
||||
<VariableLabelPickerPlugin variables={variableLabels} isFocus={focus} />
|
||||
<VariablePlugin variables={variables} />
|
||||
<VariablePickerPlugin variables={variableLabels.length > 0 ? [] : variables} />
|
||||
<OnBlurPlugin onBlur={onBlur} />
|
||||
</LexicalComposer>
|
||||
{showResize && (
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: 1px solid var(--chakra-colors-borderColor-base);
|
||||
border: 1px solid rgb(232, 235, 240);
|
||||
border-radius: var(--chakra-radii-md);
|
||||
padding: 8px 12px;
|
||||
background: var(--chakra-colors-gray-50);
|
||||
background: #fff;
|
||||
|
||||
font-size: var(--chakra-fontSizes-sm);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@@ -5,13 +5,14 @@ import Editor from './Editor';
|
||||
import MyModal from '../../MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { EditorState, type LexicalEditor } from 'lexical';
|
||||
import { EditorVariablePickerType } from './type.d';
|
||||
import { EditorVariableLabelPickerType, EditorVariablePickerType } from './type.d';
|
||||
import { useCallback, useTransition } from 'react';
|
||||
|
||||
const PromptEditor = ({
|
||||
showOpenModal = true,
|
||||
showResize = true,
|
||||
variables = [],
|
||||
variableLabels = [],
|
||||
value,
|
||||
onChange,
|
||||
onBlur,
|
||||
@@ -24,6 +25,7 @@ const PromptEditor = ({
|
||||
showOpenModal?: boolean;
|
||||
showResize?: boolean;
|
||||
variables?: EditorVariablePickerType[];
|
||||
variableLabels?: EditorVariableLabelPickerType[];
|
||||
value?: string;
|
||||
onChange?: (text: string) => void;
|
||||
onBlur?: (text: string) => void;
|
||||
@@ -55,6 +57,7 @@ const PromptEditor = ({
|
||||
showOpenModal={showOpenModal}
|
||||
onOpenModal={onOpen}
|
||||
variables={variables}
|
||||
variableLabels={variableLabels}
|
||||
h={h}
|
||||
maxLength={maxLength}
|
||||
value={value}
|
||||
@@ -71,6 +74,7 @@ const PromptEditor = ({
|
||||
showResize
|
||||
showOpenModal={false}
|
||||
variables={variables}
|
||||
variableLabels={variableLabels}
|
||||
value={value}
|
||||
onChange={onChangeInput}
|
||||
onBlur={onBlurInput}
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||
import { LexicalTypeaheadMenuPlugin } from '@lexical/react/LexicalTypeaheadMenuPlugin';
|
||||
import { $createTextNode, $getSelection, $isRangeSelection, TextNode } from 'lexical';
|
||||
import * as React from 'react';
|
||||
import { useCallback, useState, useEffect, useRef } from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { useBasicTypeaheadTriggerMatch } from '../../utils';
|
||||
import { EditorVariableLabelPickerType } from '../../type';
|
||||
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Avatar from '../../../../Avatar';
|
||||
|
||||
interface EditorVariableItemType {
|
||||
key: string;
|
||||
label: string;
|
||||
required?: boolean;
|
||||
icon?: string;
|
||||
valueType?: WorkflowIOValueTypeEnum;
|
||||
index: number;
|
||||
}
|
||||
interface TransformedParent {
|
||||
id: string;
|
||||
label: string;
|
||||
avatar: string;
|
||||
children: EditorVariableItemType[];
|
||||
}
|
||||
|
||||
export default function VariableLabelPickerPlugin({
|
||||
variables,
|
||||
isFocus
|
||||
}: {
|
||||
variables: EditorVariableLabelPickerType[];
|
||||
isFocus: boolean;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [editor] = useLexicalComposerContext();
|
||||
const [queryString, setQueryString] = useState<string | null>(null);
|
||||
const [currentIndex, setCurrentIndex] = useState<number>(0);
|
||||
const highlightedItemRef = useRef<any>(null);
|
||||
|
||||
const checkForTriggerMatch = useBasicTypeaheadTriggerMatch('/', {
|
||||
minLength: 0
|
||||
});
|
||||
|
||||
const onSelectOption = useCallback(
|
||||
(selectedOption: any, nodeToRemove: TextNode | null, closeMenu: () => void) => {
|
||||
editor.update(() => {
|
||||
const selection = $getSelection();
|
||||
if (!$isRangeSelection(selection) || selectedOption == null) {
|
||||
return;
|
||||
}
|
||||
if (nodeToRemove) {
|
||||
nodeToRemove.remove();
|
||||
}
|
||||
selection.insertNodes([
|
||||
$createTextNode(`{{$${selectedOption.parent?.id}.${selectedOption.key}$}}`)
|
||||
]);
|
||||
closeMenu();
|
||||
});
|
||||
},
|
||||
[editor]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (highlightedItemRef.current) {
|
||||
highlightedItemRef.current.scrollIntoView({
|
||||
behavior: 'auto',
|
||||
block: 'end'
|
||||
});
|
||||
}
|
||||
}, [currentIndex]);
|
||||
|
||||
return (
|
||||
<LexicalTypeaheadMenuPlugin
|
||||
onQueryChange={setQueryString}
|
||||
onSelectOption={onSelectOption}
|
||||
triggerFn={checkForTriggerMatch}
|
||||
options={variableFilter(variables, queryString || '')}
|
||||
menuRenderFn={(
|
||||
anchorElementRef,
|
||||
{ selectedIndex, selectOptionAndCleanUp, setHighlightedIndex }
|
||||
) => {
|
||||
if (anchorElementRef.current == null) {
|
||||
return null;
|
||||
}
|
||||
if (currentIndex !== selectedIndex) {
|
||||
setCurrentIndex(selectedIndex || 0);
|
||||
}
|
||||
return anchorElementRef.current && variables.length && isFocus
|
||||
? ReactDOM.createPortal(
|
||||
<Box
|
||||
bg={'white'}
|
||||
boxShadow={'lg'}
|
||||
border={'base'}
|
||||
p={1.5}
|
||||
borderRadius={'md'}
|
||||
position={'absolute'}
|
||||
w={'auto'}
|
||||
maxH={'300px'}
|
||||
minW={'240px'}
|
||||
overflow={'auto'}
|
||||
zIndex={99999}
|
||||
>
|
||||
{variableFilter(variables, queryString || '').length === variables.length && (
|
||||
<Box fontSize={'xs'}>{t('workflow:variable_picker_tips')}</Box>
|
||||
)}
|
||||
{variableFilter(variables, queryString || '').length > 0 ? (
|
||||
transformVariables(variableFilter(variables, queryString || '')).map((item) => {
|
||||
return (
|
||||
<Flex
|
||||
key={item.id}
|
||||
flexDirection={'column'}
|
||||
pt={2}
|
||||
_notLast={{
|
||||
borderBottom: '1px solid',
|
||||
borderColor: 'myGray.200'
|
||||
}}
|
||||
>
|
||||
<Flex alignItems={'center'} mb={1.5}>
|
||||
<Avatar
|
||||
src={item.avatar as any}
|
||||
w={'16px'}
|
||||
borderRadius={'2.8px'}
|
||||
display={'inline-flex'}
|
||||
verticalAlign={'middle'}
|
||||
/>
|
||||
<Box
|
||||
mx={2}
|
||||
fontSize={'sm'}
|
||||
whiteSpace={'nowrap'}
|
||||
color={'myGray.600'}
|
||||
fontWeight={'semibold'}
|
||||
>
|
||||
{item.label}
|
||||
</Box>
|
||||
</Flex>
|
||||
{item.children?.map((child) => (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
as={'li'}
|
||||
key={child.key}
|
||||
px={2}
|
||||
py={1}
|
||||
rounded={'4px'}
|
||||
cursor={'pointer'}
|
||||
overflow={'auto'}
|
||||
_notLast={{
|
||||
mb: 1
|
||||
}}
|
||||
ref={selectedIndex === child.index ? highlightedItemRef : null}
|
||||
{...(selectedIndex === child.index
|
||||
? {
|
||||
bg: '#1118240D',
|
||||
color: 'primary.700'
|
||||
}
|
||||
: {
|
||||
bg: 'white',
|
||||
color: 'myGray.600'
|
||||
})}
|
||||
_hover={{
|
||||
bg: '#1118240D',
|
||||
color: 'primary.700'
|
||||
}}
|
||||
onMouseDown={() => {
|
||||
selectOptionAndCleanUp({ ...child, parent: item });
|
||||
}}
|
||||
>
|
||||
<Box ml={2} fontSize={'sm'} whiteSpace={'nowrap'}>
|
||||
{child.label}
|
||||
</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<Box p={2} color={'myGray.400'} fontSize={'sm'}>
|
||||
{t('common:unusable_variable')}
|
||||
</Box>
|
||||
)}
|
||||
</Box>,
|
||||
anchorElementRef.current
|
||||
)
|
||||
: null;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function transformVariables(variables: EditorVariableLabelPickerType[]): TransformedParent[] {
|
||||
const transformedData: TransformedParent[] = [];
|
||||
const parentMap: { [key: string]: TransformedParent } = {};
|
||||
|
||||
variables.forEach((item, index) => {
|
||||
const parentId = item.parent.id;
|
||||
const parentLabel = item.parent.label;
|
||||
const parentAvatar = item.parent.avatar;
|
||||
|
||||
if (!parentMap[parentId]) {
|
||||
parentMap[parentId] = {
|
||||
id: parentId,
|
||||
label: parentLabel,
|
||||
avatar: parentAvatar || '',
|
||||
children: []
|
||||
};
|
||||
}
|
||||
parentMap[parentId].children.push({
|
||||
label: item.label,
|
||||
key: item.key,
|
||||
icon: item.icon,
|
||||
index
|
||||
});
|
||||
});
|
||||
|
||||
const addedParents = new Set<string>();
|
||||
variables.forEach((item) => {
|
||||
const parentId = item.parent.id;
|
||||
if (!addedParents.has(parentId)) {
|
||||
transformedData.push(parentMap[parentId]);
|
||||
addedParents.add(parentId);
|
||||
}
|
||||
});
|
||||
|
||||
return transformedData;
|
||||
}
|
||||
|
||||
function variableFilter(
|
||||
variables: EditorVariableLabelPickerType[],
|
||||
queryString: string
|
||||
): EditorVariableLabelPickerType[] {
|
||||
const lowerCaseQuery = queryString.toLowerCase();
|
||||
|
||||
return variables.filter((item) => {
|
||||
const labelMatch = item.label.toLowerCase().includes(lowerCaseQuery);
|
||||
const keyMatch = item.key.toLowerCase().includes(lowerCaseQuery);
|
||||
const parentLabelMatch = item.parent.label.toLowerCase().includes(lowerCaseQuery);
|
||||
|
||||
return labelMatch || keyMatch || parentLabelMatch;
|
||||
});
|
||||
}
|
||||