Compare commits

...

7 Commits

Author SHA1 Message Date
gru-agent[bot]
74be2169e4 Add unit test for projects/app/src/web/core/chat/context/useChatStore.ts 2025-05-14 07:44:15 +00:00
Archer
cba8f773fe New license (#4809)
* feat: new-license

* perf: volumn watch

* Set use client
2025-05-14 13:55:09 +08:00
Archer
bd93f28d6f update doc (#4806) 2025-05-13 21:24:35 +08:00
Archer
2063cb6314 i18n (#4805)
* i18n

* version

* copy node
2025-05-13 18:58:57 +08:00
dreamer6680
12acaf491c change password rule (#4804)
* change password rule

* change password.tset.ts
2025-05-13 18:20:11 +08:00
heheer
3688842cc7 filter tool type version & fix unpublished version (#4803) 2025-05-13 17:58:51 +08:00
Archer
398d131bac fix api_dataset.md (#4791) (#4801)
Co-authored-by: dreamer6680 <1468683855@qq.com>
2025-05-13 12:28:50 +08:00
57 changed files with 550 additions and 212 deletions

View File

@@ -132,15 +132,15 @@ services:
# fastgpt
sandbox:
container_name: sandbox
image: ghcr.io/labring/fastgpt-sandbox:v4.9.7-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.7-fix2 # 阿里云
image: ghcr.io/labring/fastgpt-sandbox:v4.9.8 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.8 # 阿里云
networks:
- fastgpt
restart: always
fastgpt-mcp-server:
container_name: fastgpt-mcp-server
image: ghcr.io/labring/fastgpt-mcp_server:v4.9.7-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.9.7-fix2 # 阿里云
image: ghcr.io/labring/fastgpt-mcp_server:v4.9.8 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.9.8 # 阿里云
ports:
- 3005:3000
networks:
@@ -150,8 +150,8 @@ services:
- FASTGPT_ENDPOINT=http://fastgpt:3000
fastgpt:
container_name: fastgpt
image: ghcr.io/labring/fastgpt:v4.9.7-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.7-fix2 # 阿里云
image: ghcr.io/labring/fastgpt:v4.9.8 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.8 # 阿里云
ports:
- 3000:3000
networks:

View File

@@ -109,15 +109,15 @@ services:
# fastgpt
sandbox:
container_name: sandbox
image: ghcr.io/labring/fastgpt-sandbox:v4.9.7-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.7-fix2 # 阿里云
image: ghcr.io/labring/fastgpt-sandbox:v4.9.8 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.8 # 阿里云
networks:
- fastgpt
restart: always
fastgpt-mcp-server:
container_name: fastgpt-mcp-server
image: ghcr.io/labring/fastgpt-mcp_server:v4.9.7-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.9.7-fix2 # 阿里云
image: ghcr.io/labring/fastgpt-mcp_server:v4.9.8 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.9.8 # 阿里云
ports:
- 3005:3000
networks:
@@ -127,8 +127,8 @@ services:
- FASTGPT_ENDPOINT=http://fastgpt:3000
fastgpt:
container_name: fastgpt
image: ghcr.io/labring/fastgpt:v4.9.7-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.7-fix2 # 阿里云
image: ghcr.io/labring/fastgpt:v4.9.8 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.8 # 阿里云
ports:
- 3000:3000
networks:

View File

@@ -96,15 +96,15 @@ services:
# fastgpt
sandbox:
container_name: sandbox
image: ghcr.io/labring/fastgpt-sandbox:v4.9.7-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.7-fix2 # 阿里云
image: ghcr.io/labring/fastgpt-sandbox:v4.9.8 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.8 # 阿里云
networks:
- fastgpt
restart: always
fastgpt-mcp-server:
container_name: fastgpt-mcp-server
image: ghcr.io/labring/fastgpt-mcp_server:v4.9.7-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.9.7-fix2 # 阿里云
image: ghcr.io/labring/fastgpt-mcp_server:v4.9.8 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.9.8 # 阿里云
ports:
- 3005:3000
networks:
@@ -114,8 +114,8 @@ services:
- FASTGPT_ENDPOINT=http://fastgpt:3000
fastgpt:
container_name: fastgpt
image: ghcr.io/labring/fastgpt:v4.9.7-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.7-fix2 # 阿里云
image: ghcr.io/labring/fastgpt:v4.9.8 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.8 # 阿里云
ports:
- 3000:3000
networks:

View File

@@ -72,15 +72,15 @@ services:
sandbox:
container_name: sandbox
image: ghcr.io/labring/fastgpt-sandbox:v4.9.7-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.7-fix2 # 阿里云
image: ghcr.io/labring/fastgpt-sandbox:v4.9.8 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.8 # 阿里云
networks:
- fastgpt
restart: always
fastgpt-mcp-server:
container_name: fastgpt-mcp-server
image: ghcr.io/labring/fastgpt-mcp_server:v4.9.7-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.9.7-fix2 # 阿里云
image: ghcr.io/labring/fastgpt-mcp_server:v4.9.8 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.9.8 # 阿里云
ports:
- 3005:3000
networks:
@@ -90,8 +90,8 @@ services:
- FASTGPT_ENDPOINT=http://fastgpt:3000
fastgpt:
container_name: fastgpt
image: ghcr.io/labring/fastgpt:v4.9.7-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.7-fix2 # 阿里云
image: ghcr.io/labring/fastgpt:v4.9.8 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.8 # 阿里云
ports:
- 3000:3000
networks:

View File

@@ -1,5 +1,5 @@
---
title: 'V4.9.8(进行中)'
title: 'V4.9.8'
description: 'FastGPT V4.9.8 更新说明'
icon: 'upgrade'
draft: false
@@ -7,6 +7,17 @@ toc: true
weight: 792
---
## 升级指南
### 1. 做好数据备份
### 2. 更新镜像 tag
- 更新 FastGPT 镜像 tag: v4.9.8
- 更新 FastGPT 商业版镜像 tag: v4.9.8
- mcp_server 无需更新
- Sandbox 无需更新
- AIProxy 无需更新
## 🚀 新增内容

View File

@@ -43,7 +43,7 @@ type ResponseType = {
// 文件列表中,单项的文件类型
type FileListItem = {
id: string;
parentId: string | null;
parentId: string //也可能为 null 或者 undefined 类型;
name: string;
type: 'file' | 'folder';
updateTime: Date;
@@ -59,7 +59,7 @@ type FileListItem = {
{{< markdownify >}}
{{% alert icon=" " context="success" %}}
- parentId - 父级 id可选或者 null。
- parentId - 父级 id可选或者 null | undefined
- searchKey - 检索词,可选
{{% /alert %}}
@@ -68,7 +68,7 @@ curl --location --request POST '{{baseURL}}/v1/file/list' \
--header 'Authorization: Bearer {{authorization}}' \
--header 'Content-Type: application/json' \
--data-raw '{
"parentId": null,
"parentId": "",
"searchKey": ""
}'
```

View File

@@ -2,13 +2,28 @@ import { type ErrType } from '../errorCode';
import { i18nT } from '../../../../web/i18n/utils';
/* dataset: 509000 */
export enum SystemErrEnum {
communityVersionNumLimit = 'communityVersionNumLimit'
communityVersionNumLimit = 'communityVersionNumLimit',
licenseAppAmountLimit = 'licenseAppAmountLimit',
licenseDatasetAmountLimit = 'licenseDatasetAmountLimit',
licenseUserAmountLimit = 'licenseUserAmountLimit'
}
const systemErr = [
{
statusText: SystemErrEnum.communityVersionNumLimit,
message: i18nT('common:code_error.system_error.community_version_num_limit')
},
{
statusText: SystemErrEnum.licenseAppAmountLimit,
message: i18nT('common:code_error.system_error.license_app_amount_limit')
},
{
statusText: SystemErrEnum.licenseDatasetAmountLimit,
message: i18nT('common:code_error.system_error.license_dataset_amount_limit')
},
{
statusText: SystemErrEnum.licenseUserAmountLimit,
message: i18nT('common:code_error.system_error.license_user_amount_limit')
}
];

View File

@@ -5,7 +5,7 @@ export const checkPasswordRule = (password: string) => {
/[A-Z]/, // Contains uppercase letters
/[!@#$%^&*()_+=-]/ // Contains special characters
];
const validChars = /^[\dA-Za-z!@#$%^&*()_+=-]{6,100}$/;
const validChars = /^[\dA-Za-z!@#$%^&*()_+=-]{8,100}$/;
// Check length and valid characters
if (!validChars.test(password)) return false;

View File

@@ -70,6 +70,9 @@ export type FastGPTFeConfigsType = {
show_publish_dingtalk?: boolean;
show_publish_offiaccount?: boolean;
show_dataset_enhance?: boolean;
show_batch_eval?: boolean;
concatMd?: string;
docUrl?: string;
openAPIDocUrl?: string;
@@ -142,3 +145,21 @@ export type customPdfParseType = {
doc2xKey?: string;
price?: number;
};
export type LicenseDataType = {
startTime: string;
expiredTime: string;
company: string;
description?: string; // 描述
hosts?: string[]; // 管理端有效域名
maxUsers?: number; // 最大用户数,不填默认不上限
maxApps?: number; // 最大应用数,不填默认不上限
maxDatasets?: number; // 最大数据集数,不填默认不上限
functions: {
sso: boolean;
pay: boolean;
customTemplates: boolean;
datasetEnhance: boolean;
batchEval: boolean;
};
};

View File

@@ -2,26 +2,44 @@ import { SystemConfigsTypeEnum } from '@fastgpt/global/common/system/config/cons
import { MongoSystemConfigs } from './schema';
import { type FastGPTConfigFileType } from '@fastgpt/global/common/system/types';
import { FastGPTProUrl } from '../constants';
import { type LicenseDataType } from '@fastgpt/global/common/system/types';
export const getFastGPTConfigFromDB = async () => {
export const getFastGPTConfigFromDB = async (): Promise<{
fastgptConfig: FastGPTConfigFileType;
licenseData?: LicenseDataType;
}> => {
if (!FastGPTProUrl) {
return {
config: {} as FastGPTConfigFileType
fastgptConfig: {} as FastGPTConfigFileType
};
}
const res = await MongoSystemConfigs.findOne({
type: SystemConfigsTypeEnum.fastgpt
}).sort({
createTime: -1
});
const [fastgptConfig, licenseConfig] = await Promise.all([
MongoSystemConfigs.findOne({
type: SystemConfigsTypeEnum.fastgpt
}).sort({
createTime: -1
}),
MongoSystemConfigs.findOne({
type: SystemConfigsTypeEnum.license
}).sort({
createTime: -1
})
]);
const config = res?.value || {};
const config = fastgptConfig?.value || {};
const licenseData = licenseConfig?.value?.data as LicenseDataType | undefined;
const fastgptConfigTime = fastgptConfig?.createTime.getTime().toString();
const licenseConfigTime = licenseConfig?.createTime.getTime().toString();
// 利用配置文件的创建时间(更新时间)来做缓存,如果前端命中缓存,则不需要再返回配置文件
global.systemInitBufferId = res ? res.createTime.getTime().toString() : undefined;
global.systemInitBufferId = fastgptConfigTime
? `${fastgptConfigTime}-${licenseConfigTime}`
: undefined;
return {
config: config as FastGPTConfigFileType
fastgptConfig: config as FastGPTConfigFileType,
licenseData
};
};

View File

@@ -22,8 +22,7 @@ import {
import { type PluginRuntimeType } from '@fastgpt/global/core/plugin/type';
import { MongoSystemPlugin } from './systemPluginSchema';
import { PluginErrEnum } from '@fastgpt/global/common/error/code/plugin';
import { MongoAppVersion } from '../version/schema';
import { i18nT } from '../../../../web/i18n/utils';
import { Types } from 'mongoose';
/*
plugin id rule:
@@ -111,19 +110,13 @@ export async function getChildAppPreviewNode({
const version = await getAppVersionById({ appId, versionId, app: item });
if (!version.versionId) return Promise.reject(i18nT('common:app_not_version'));
const versionData = await MongoAppVersion.findById(
version.versionId,
'_id versionName appId time'
).lean();
const isLatest = versionData
? await checkIsLatestVersion({
appId,
versionId: versionData._id
})
: true;
const isLatest =
version.versionId && Types.ObjectId.isValid(version.versionId)
? await checkIsLatestVersion({
appId,
versionId: version.versionId
})
: true;
return {
id: String(item._id),
@@ -140,7 +133,7 @@ export async function getChildAppPreviewNode({
templateType: FlowNodeTemplateTypeEnum.teamApp,
version: version.versionId,
versionLabel: versionData?.versionName || '',
versionLabel: version?.versionName || '',
isLatestVersion: isLatest,
originCost: 0,

View File

@@ -119,6 +119,7 @@ const AppSchema = new Schema({
defaultPermission: Number
});
AppSchema.index({ type: 1 });
AppSchema.index({ teamId: 1, updateTime: -1 });
AppSchema.index({ teamId: 1, type: 1 });
AppSchema.index(

View File

@@ -15,6 +15,7 @@ export const getAppLatestVersion = async (appId: string, app?: AppSchema) => {
if (version) {
return {
versionId: version._id,
versionName: version.versionName,
nodes: version.nodes,
edges: version.edges,
chatConfig: version.chatConfig || app?.chatConfig || {}
@@ -22,6 +23,7 @@ export const getAppLatestVersion = async (appId: string, app?: AppSchema) => {
}
return {
versionId: app?.pluginData?.nodeVersion,
versionName: app?.name,
nodes: app?.modules || [],
edges: app?.edges || [],
chatConfig: app?.chatConfig || {}
@@ -47,6 +49,7 @@ export const getAppVersionById = async ({
if (version) {
return {
versionId: version._id,
versionName: version.versionName,
nodes: version.nodes,
edges: version.edges,
chatConfig: version.chatConfig || app?.chatConfig || {}

View File

@@ -123,6 +123,7 @@ const DatasetSchema = new Schema({
try {
DatasetSchema.index({ teamId: 1 });
DatasetSchema.index({ type: 1 });
} catch (error) {
console.log(error);
}

View File

@@ -54,23 +54,50 @@ export const checkTeamDatasetLimit = async (teamId: string) => {
})
]);
// User check
if (standardConstants && datasetCount >= standardConstants.maxDatasetAmount) {
return Promise.reject(TeamErrEnum.datasetAmountNotEnough);
}
// System check
if (global?.licenseData?.maxDatasets && typeof global?.licenseData?.maxDatasets === 'number') {
const totalDatasets = await MongoDataset.countDocuments({
type: { $ne: DatasetTypeEnum.folder }
});
if (totalDatasets >= global.licenseData.maxDatasets) {
return Promise.reject(SystemErrEnum.licenseDatasetAmountLimit);
}
}
// Open source check
if (!global.feConfigs.isPlus && datasetCount >= 30) {
return Promise.reject(SystemErrEnum.communityVersionNumLimit);
}
};
export const checkTeamAppLimit = async (teamId: string, amount = 1) => {
const [{ standardConstants }, appCount] = await Promise.all([
getTeamStandPlan({ teamId }),
MongoApp.countDocuments({
teamId,
type: { $in: [AppTypeEnum.simple, AppTypeEnum.workflow, AppTypeEnum.plugin] }
type: {
$in: [AppTypeEnum.simple, AppTypeEnum.workflow, AppTypeEnum.plugin, AppTypeEnum.tool]
}
})
]);
if (standardConstants && appCount + amount >= standardConstants.maxAppAmount) {
return Promise.reject(TeamErrEnum.appAmountNotEnough);
}
// System check
if (global?.licenseData?.maxApps && typeof global?.licenseData?.maxApps === 'number') {
const totalApps = await MongoApp.countDocuments({
type: {
$in: [AppTypeEnum.simple, AppTypeEnum.workflow, AppTypeEnum.plugin, AppTypeEnum.tool]
}
});
if (totalApps >= global.licenseData.maxApps) {
return Promise.reject(SystemErrEnum.licenseAppAmountLimit);
}
}
};

View File

@@ -1,4 +1,8 @@
import type { FastGPTFeConfigsType, SystemEnvType } from '@fastgpt/global/common/system/types';
import type {
FastGPTFeConfigsType,
LicenseDataType,
SystemEnvType
} from '@fastgpt/global/common/system/types';
import {
TTSModelType,
RerankModelItemType,
@@ -17,6 +21,7 @@ declare global {
var feConfigs: FastGPTFeConfigsType;
var systemEnv: SystemEnvType;
var subPlans: SubPlanType | undefined;
var licenseData: LicenseDataType | undefined;
var workerPoll: Record<WorkerNameEnum, WorkerPool>;
}

View File

@@ -24,6 +24,7 @@ import { useRequest2 } from '../../../hooks/useRequest';
import MyDivider from '../MyDivider';
import type { useScrollPagination } from '../../../hooks/useScrollPagination';
import Avatar from '../Avatar';
import EmptyTip from '../EmptyTip';
/** 选择组件 Props 类型
* value: 选中的值
@@ -141,43 +142,49 @@ const MySelect = <T = any,>(
const ListRender = useMemo(() => {
return (
<>
{filterList.map((item, i) => (
<Box key={i}>
<MenuItem
{...menuItemStyles}
{...(value === item.value
? {
ref: SelectedItemRef,
color: 'primary.700',
bg: 'myGray.100',
fontWeight: '600'
{filterList.length > 0 ? (
filterList.map((item, i) => (
<Box key={i}>
<MenuItem
{...menuItemStyles}
{...(value === item.value
? {
ref: SelectedItemRef,
color: 'primary.700',
bg: 'myGray.100',
fontWeight: '600'
}
: {
color: 'myGray.900'
})}
onClick={() => {
if (value !== item.value) {
onClickChange(item.value);
}
: {
color: 'myGray.900'
})}
onClick={() => {
if (value !== item.value) {
onClickChange(item.value);
}
}}
whiteSpace={'pre-wrap'}
fontSize={'sm'}
display={'block'}
mb={0.5}
>
<Flex alignItems={'center'}>
{item.icon && <Avatar mr={2} src={item.icon as any} w={item.iconSize ?? '1rem'} />}
{item.label}
</Flex>
{item.description && (
<Box color={'myGray.500'} fontSize={'xs'}>
{item.description}
</Box>
)}
</MenuItem>
{item.showBorder && <MyDivider my={2} />}
</Box>
))}
}}
whiteSpace={'pre-wrap'}
fontSize={'sm'}
display={'block'}
mb={0.5}
>
<Flex alignItems={'center'}>
{item.icon && (
<Avatar mr={2} src={item.icon as any} w={item.iconSize ?? '1rem'} />
)}
{item.label}
</Flex>
{item.description && (
<Box color={'myGray.500'} fontSize={'xs'}>
{item.description}
</Box>
)}
</MenuItem>
{item.showBorder && <MyDivider my={2} />}
</Box>
))
) : (
<EmptyTip py={0} />
)}
</>
);
}, [filterList, onClickChange, value]);

View File

@@ -47,7 +47,7 @@
"package_usage_rules": "Package usage rules: The system will give priority to using more advanced packages, and the original unused packages will take effect later.",
"password": "Password",
"password_mismatch": "Password Inconsistency: Two passwords are inconsistent",
"password_tip": "Password must be at least 6 characters long and contain at least two combinations: numbers, letters, or special characters",
"password_tip": "Password must be at least 8 characters long and contain at least two combinations: numbers, letters, or special characters",
"password_update_error": "Exception when changing password",
"password_update_success": "Password changed successfully",
"pending_usage": "To be used",

View File

@@ -1,7 +1,6 @@
{
"Action": "Action",
"Add": "Add",
"add_success": "Added Successfully",
"Add_new_input": "Add new input",
"All": "All",
"App": "Application",
@@ -30,7 +29,7 @@
"FAQ.ai_point_q": "What are AI points?",
"FAQ.check_subscription_a": "Go to Account - Personal Information - Package Details - Usage. You can view the effective and expiration dates of your subscribed packages. After the paid package expires, it will automatically switch to the free version.",
"FAQ.check_subscription_q": "Where can I view my subscribed packages?",
"FAQ.dataset_compute_a": "1 Dataset storage equals 1 Dataset index. A piece of Dataset data can contain one or more Dataset indexes. In enhanced training, 1 piece of data generates 5 indexes.",
"FAQ.dataset_compute_a": "1 knowledge base storage is equal to 1 knowledge base index. \nA single chunked data usually corresponds to multiple indexes. You can see \"n group indexes\" in a single knowledge base collection.",
"FAQ.dataset_compute_q": "How is Dataset storage calculated?",
"FAQ.dataset_index_a": "No, but if the Dataset index exceeds the limit, you cannot insert or update Dataset content.",
"FAQ.dataset_index_q": "Will the Dataset index be deleted if it exceeds the limit?",
@@ -64,7 +63,6 @@
"Parse": "Analysis",
"Permission": "Permission",
"Permission_tip": "Individual permissions are greater than group permissions",
"please_input_name": "Please Enter a Name",
"Preview": "Preview",
"Remove": "Remove",
"Rename": "Rename",
@@ -94,6 +92,7 @@
"action_confirm": "Confirm",
"add_new": "add_new",
"add_new_param": "Add new param",
"add_success": "Added Successfully",
"all_quotes": "All quotes",
"all_result": "Full Results",
"app_not_version": "This application has not been published, please publish it first",
@@ -149,6 +148,9 @@
"code_error.plugin_error.not_exist": "Plugin Does Not Exist",
"code_error.plugin_error.un_auth": "Unauthorized to Operate This Plugin",
"code_error.system_error.community_version_num_limit": "Exceeded Open Source Version Limit, Please Upgrade to Commercial Version: https://tryfastgpt.ai",
"code_error.system_error.license_app_amount_limit": "Exceed the maximum number of applications in the system",
"code_error.system_error.license_dataset_amount_limit": "Exceed the maximum number of knowledge bases in the system",
"code_error.system_error.license_user_amount_limit": "Exceed the maximum number of users in the system",
"code_error.team_error.ai_points_not_enough": "Insufficient AI Points",
"code_error.team_error.app_amount_not_enough": "Application Limit Reached",
"code_error.team_error.cannot_delete_default_group": "Cannot delete default group",
@@ -962,6 +964,7 @@
"permission.manager": "administrator",
"permission.read": "Read permission",
"permission.write": "write permission",
"please_input_name": "Please Enter a Name",
"plugin.App": "Select App",
"plugin.Currentapp": "Current App",
"plugin.Description": "Description",
@@ -1260,7 +1263,7 @@
"user.no_usage_records": "No Usage Records",
"user.old_password": "Old Password",
"user.password_message": "Password must be at least 4 characters and at most 60 characters",
"user.password_tip": "Password must be at least 6 characters long and contain at least two combinations: numbers, letters, or special characters",
"user.password_tip": "Password must be at least 8 characters long and contain at least two combinations: numbers, letters, or special characters",
"user.reset_password": "Reset Password",
"user.reset_password_tip": "The initial password is not set/the password has not been modified for a long time, please reset the password",
"user.team.Balance": "Team Balance",

View File

@@ -8,7 +8,7 @@
"login_success": "Login successful",
"no_remind": "Don't remind again",
"password_condition": "Password maximum 60 characters",
"password_tip": "Password must be at least 6 characters long and contain at least two combinations: numbers, letters, or special characters",
"password_tip": "Password must be at least 8 characters long and contain at least two combinations: numbers, letters, or special characters",
"policy_tip": "By using this service, you agree to our",
"privacy": "Privacy Policy",
"privacy_policy": "Privacy Policy",

View File

@@ -45,7 +45,7 @@
"package_usage_rules": "套餐使用规则:系统优先使用更高级的套餐,原未用完的套餐将延后生效",
"password": "密码",
"password_mismatch": "密码不一致: 两次密码不一致",
"password_tip": "密码至少 6 位,且至少包含两种组合:数字、字母或特殊字符",
"password_tip": "密码至少 8 位,且至少包含两种组合:数字、字母或特殊字符",
"password_update_error": "修改密码异常",
"password_update_success": "修改密码成功",
"pending_usage": "待使用",

View File

@@ -1,7 +1,6 @@
{
"Action": "操作",
"Add": "添加",
"add_success": "添加成功",
"Add_new_input": "新增输入",
"All": "全部",
"App": "应用",
@@ -30,7 +29,7 @@
"FAQ.ai_point_q": "什么是AI积分",
"FAQ.check_subscription_a": "账号-个人信息-套餐详情-使用情况。您可以查看所拥有套餐的生效和到期时间。当付费套餐到期后将自动切换免费版。",
"FAQ.check_subscription_q": "在哪里查看已订阅的套餐?",
"FAQ.dataset_compute_a": "1条知识库存储等于1条知识库索引。一条知识库数据可以包含1条或多条知识库索引。增强训练中1条数据会生成5条索引。",
"FAQ.dataset_compute_a": "1条知识库存储等于1条知识库索引。一条分块数据,通常对应多条索引,可以在单个知识库集合中看到\"n组索引\"",
"FAQ.dataset_compute_q": "知识库存储怎么计算?",
"FAQ.dataset_index_a": "不会。但知识库索引超出时,无法插入和更新知识库内容。",
"FAQ.dataset_index_q": "知识库索引超出会删除么?",
@@ -64,7 +63,6 @@
"Parse": "解析",
"Permission": "权限",
"Permission_tip": "个人权限大于群组权限",
"please_input_name": "请输入名称",
"Preview": "预览",
"Remove": "移除",
"Rename": "重命名",
@@ -94,6 +92,7 @@
"action_confirm": "操作确认",
"add_new": "新增",
"add_new_param": "新增参数",
"add_success": "添加成功",
"all_quotes": "全部引用",
"all_result": "完整结果",
"app_not_version": " 该应用未发布过,请先发布应用",
@@ -149,6 +148,9 @@
"code_error.plugin_error.not_exist": "插件不存在",
"code_error.plugin_error.un_auth": "无权操作该插件",
"code_error.system_error.community_version_num_limit": "超出开源版数量限制,请升级商业版: https://fastgpt.in",
"code_error.system_error.license_app_amount_limit": "超出系统最大应用数量",
"code_error.system_error.license_dataset_amount_limit": "超出系统最大知识库数量",
"code_error.system_error.license_user_amount_limit": "超出系统最大用户数量",
"code_error.team_error.ai_points_not_enough": "AI 积分不足",
"code_error.team_error.app_amount_not_enough": "应用数量已达上限~",
"code_error.team_error.cannot_delete_default_group": "不能删除默认群组",
@@ -961,6 +963,7 @@
"permission.manager": "管理员",
"permission.read": "读权限",
"permission.write": "写权限",
"please_input_name": "请输入名称",
"plugin.App": "选择应用",
"plugin.Currentapp": "当前应用",
"plugin.Description": "描述",
@@ -1259,7 +1262,7 @@
"user.no_usage_records": "暂无使用记录",
"user.old_password": "旧密码",
"user.password_message": "密码最少 4 位最多 60 位",
"user.password_tip": "密码至少 6 位,且至少包含两种组合:数字、字母或特殊字符",
"user.password_tip": "密码至少 8 位,且至少包含两种组合:数字、字母或特殊字符",
"user.reset_password": "重置密码",
"user.reset_password_tip": "未设置初始密码/长时间未修改密码,请重置密码",
"user.team.Balance": "团队余额",

View File

@@ -8,7 +8,7 @@
"login_success": "登录成功",
"no_remind": "不再提醒",
"password_condition": "密码最多 60 位",
"password_tip": "密码至少 6 位,且至少包含两种组合:数字、字母或特殊字符",
"password_tip": "密码至少 8 位,且至少包含两种组合:数字、字母或特殊字符",
"policy_tip": "使用即代表你同意我们的",
"privacy": "隐私协议",
"privacy_policy": "隐私政策",

View File

@@ -47,7 +47,7 @@
"package_usage_rules": "套餐使用規則:系統優先使用更進階的套餐,原未用完的套餐將延遲生效",
"password": "密碼",
"password_mismatch": "密碼不一致:兩次密碼不一致",
"password_tip": "密碼至少 6 位,且至少包含兩種組合:數字、字母或特殊字元",
"password_tip": "密碼至少 8 位,且至少包含兩種組合:數字、字母或特殊字元",
"password_update_error": "修改密碼異常",
"password_update_success": "修改密碼成功",
"pending_usage": "待使用",

View File

@@ -1,7 +1,6 @@
{
"Action": "操作",
"Add": "新增",
"add_success": "新增成功",
"Add_new_input": "新增輸入",
"All": "全部",
"App": "應用程式",
@@ -30,7 +29,7 @@
"FAQ.ai_point_q": "什麼是 AI 點數?",
"FAQ.check_subscription_a": "帳號 - 個人資訊 - 方案詳細資訊 - 使用狀況。您可以檢視已訂閱方案的生效和到期時間。當付費方案到期後,將自動切換為免費版。",
"FAQ.check_subscription_q": "在哪裡可以檢視已訂閱的方案?",
"FAQ.dataset_compute_a": "1知識庫儲存等於 1 筆知識庫索引。一筆知識庫資料可以包含 1 筆或多筆知識庫索引。在增強訓練中1 筆資料會產生 5 筆索引。",
"FAQ.dataset_compute_a": "1知識庫存儲等於1條知識庫索引。\n一條分塊數據通常對應多條索引可以在單個知識庫集合中看到\"n組索引\"",
"FAQ.dataset_compute_q": "知識庫儲存如何計算?",
"FAQ.dataset_index_a": "不會,但知識庫索引超出限制時,將無法插入和更新知識庫內容。",
"FAQ.dataset_index_q": "知識庫索引超出是否會被刪除?",
@@ -64,7 +63,6 @@
"Parse": "解析",
"Permission": "權限",
"Permission_tip": "個人權限大於群組權限",
"please_input_name": "請輸入名稱",
"Preview": "預覽",
"Remove": "移除",
"Rename": "重新命名",
@@ -94,6 +92,7 @@
"action_confirm": "確認",
"add_new": "新增",
"add_new_param": "新增參數",
"add_success": "新增成功",
"all_quotes": "全部引用",
"all_result": "完整結果",
"app_not_version": "該應用未發布過,請先發布應用",
@@ -148,6 +147,9 @@
"code_error.plugin_error.not_exist": "外掛程式不存在",
"code_error.plugin_error.un_auth": "無權操作此外掛程式",
"code_error.system_error.community_version_num_limit": "超出開源版數量限制請升級商業版https://tryfastgpt.ai",
"code_error.system_error.license_app_amount_limit": "超出系統最大應用數量",
"code_error.system_error.license_dataset_amount_limit": "超出系統最大知識庫數量",
"code_error.system_error.license_user_amount_limit": "超出系統最大用戶數量",
"code_error.team_error.ai_points_not_enough": "AI 點數不足",
"code_error.team_error.app_amount_not_enough": "已達應用程式數量上限",
"code_error.team_error.cannot_delete_default_group": "無法刪除預設群組",
@@ -961,6 +963,7 @@
"permission.manager": "管理員",
"permission.read": "讀取權限",
"permission.write": "寫入權限",
"please_input_name": "請輸入名稱",
"plugin.App": "選擇應用程式",
"plugin.Currentapp": "目前應用程式",
"plugin.Description": "描述",
@@ -1259,7 +1262,7 @@
"user.no_usage_records": "無使用紀錄",
"user.old_password": "舊密碼",
"user.password_message": "密碼最少 4 位最多 60 位",
"user.password_tip": "密碼至少 6 位,且至少包含兩種組合:數字、字母或特殊字元",
"user.password_tip": "密碼至少 8 位,且至少包含兩種組合:數字、字母或特殊字元",
"user.reset_password": "重置密碼",
"user.reset_password_tip": "未設置初始密碼/長時間未修改密碼,請重置密碼",
"user.team.Balance": "團隊餘額",

View File

@@ -8,7 +8,7 @@
"login_success": "登入成功",
"no_remind": "不再提醒",
"password_condition": "密碼最多 60 個字元",
"password_tip": "密碼至少 6 位,且至少包含兩種組合:數字、字母或特殊字元",
"password_tip": "密碼至少 8 位,且至少包含兩種組合:數字、字母或特殊字元",
"policy_tip": "使用即代表您同意我們的",
"privacy": "隱私權政策",
"privacy_policy": "隱私權政策",

View File

@@ -1,6 +1,6 @@
{
"name": "app",
"version": "4.9.7",
"version": "4.9.8",
"private": false,
"scripts": {
"dev": "next dev",

View File

@@ -17,7 +17,9 @@ export async function register() {
{ getSystemPluginCb },
{ startMongoWatch },
{ startCron },
{ startTrainingQueue }
{ startTrainingQueue },
{ preLoadWorker },
{ loadSystemModels }
] = await Promise.all([
import('@fastgpt/service/common/mongo/init'),
import('@fastgpt/service/common/mongo/index'),
@@ -28,7 +30,9 @@ export async function register() {
import('@/service/core/app/plugin'),
import('@/service/common/system/volumnMongoWatch'),
import('@/service/common/system/cron'),
import('@/service/core/dataset/training/utils')
import('@/service/core/dataset/training/utils'),
import('@fastgpt/service/worker/preload'),
import('@fastgpt/service/core/ai/config/utils')
]);
// 执行初始化流程
@@ -40,8 +44,9 @@ export async function register() {
connectMongo(connectionLogMongo, MONGO_LOG_URL);
//init system configinit vector databaseinit root user
await Promise.all([getInitConfig(), initVectorStore(), initRootUser()]);
await Promise.all([getInitConfig(), initVectorStore(), initRootUser(), loadSystemModels()]);
// 异步加载
initSystemPluginGroups();
initAppTemplateTypes();
getSystemPluginCb();
@@ -49,6 +54,12 @@ export async function register() {
startCron();
startTrainingQueue(true);
try {
await preLoadWorker();
} catch (error) {
console.error('Preload worker error', error);
}
console.log('Init system success');
}
} catch (error) {

View File

@@ -104,8 +104,8 @@ const NodeCard = (props: Props) => {
const isAppNode = node && AppNodeFlowNodeTypeMap[node?.flowNodeType];
const showVersion = useMemo(() => {
if (!isAppNode || !node?.pluginId) return false;
const splitRes = node.pluginId.split('-');
if (splitRes.length > 1) {
if ([FlowNodeTypeEnum.tool, FlowNodeTypeEnum.toolSet].includes(node.flowNodeType)) return false;
if (node.pluginId.split('-').length > 1) {
return false;
}
return true;
@@ -403,7 +403,9 @@ const MenuRender = React.memo(function MenuRender({
outputs: node.data.outputs,
showStatus: node.data.showStatus,
pluginId: node.data.pluginId,
version: node.data.version
version: node.data.version,
versionLabel: node.data.versionLabel,
isLatestVersion: node.data.isLatestVersion
};
return [
@@ -423,7 +425,9 @@ const MenuRender = React.memo(function MenuRender({
pluginId: template.pluginId,
inputs: template.inputs,
outputs: template.outputs,
version: template.version
version: template.version,
versionLabel: template.versionLabel,
isLatestVersion: template.isLatestVersion
},
selected: true,
parentNodeId: undefined,

View File

@@ -218,47 +218,48 @@ const CollectionChunkForm = ({ form }: { form: UseFormReturn<CollectionChunkForm
gridTemplateColumns={'repeat(2, 1fr)'}
/>
</Box>
{trainingType === DatasetCollectionDataProcessModeEnum.chunk && (
<Box mt={6}>
<Box fontSize={'sm'} mb={2} color={'myGray.600'}>
{t('dataset:enhanced_indexes')}
{trainingType === DatasetCollectionDataProcessModeEnum.chunk &&
feConfigs?.show_dataset_enhance !== false && (
<Box mt={6}>
<Box fontSize={'sm'} mb={2} color={'myGray.600'}>
{t('dataset:enhanced_indexes')}
</Box>
<HStack gap={[3, 7]}>
<HStack flex={'1'} spacing={1}>
<MyTooltip label={!feConfigs?.isPlus ? t('common:commercial_function_tip') : ''}>
<Checkbox
isDisabled={!feConfigs?.isPlus}
isChecked={autoIndexes}
{...register('autoIndexes')}
>
<FormLabel>{t('dataset:auto_indexes')}</FormLabel>
</Checkbox>
</MyTooltip>
<QuestionTip label={t('dataset:auto_indexes_tips')} />
</HStack>
<HStack flex={'1'} spacing={1}>
<MyTooltip
label={
!feConfigs?.isPlus
? t('common:commercial_function_tip')
: !datasetDetail?.vlmModel
? t('common:error_vlm_not_config')
: ''
}
>
<Checkbox
isDisabled={!feConfigs?.isPlus || !datasetDetail?.vlmModel}
isChecked={imageIndex}
{...register('imageIndex')}
>
<FormLabel>{t('dataset:image_auto_parse')}</FormLabel>
</Checkbox>
</MyTooltip>
<QuestionTip label={t('dataset:image_auto_parse_tips')} />
</HStack>
</HStack>
</Box>
<HStack gap={[3, 7]}>
<HStack flex={'1'} spacing={1}>
<MyTooltip label={!feConfigs?.isPlus ? t('common:commercial_function_tip') : ''}>
<Checkbox
isDisabled={!feConfigs?.isPlus}
isChecked={autoIndexes}
{...register('autoIndexes')}
>
<FormLabel>{t('dataset:auto_indexes')}</FormLabel>
</Checkbox>
</MyTooltip>
<QuestionTip label={t('dataset:auto_indexes_tips')} />
</HStack>
<HStack flex={'1'} spacing={1}>
<MyTooltip
label={
!feConfigs?.isPlus
? t('common:commercial_function_tip')
: !datasetDetail?.vlmModel
? t('common:error_vlm_not_config')
: ''
}
>
<Checkbox
isDisabled={!feConfigs?.isPlus || !datasetDetail?.vlmModel}
isChecked={imageIndex}
{...register('imageIndex')}
>
<FormLabel>{t('dataset:image_auto_parse')}</FormLabel>
</Checkbox>
</MyTooltip>
<QuestionTip label={t('dataset:image_auto_parse_tips')} />
</HStack>
</HStack>
</Box>
)}
)}
<Box mt={6}>
<Box fontSize={'sm'} mb={2} color={'myGray.600'}>
{t('dataset:params_setting')}

View File

@@ -1,3 +1,4 @@
'use client';
import React from 'react';
import ApiKeyTable from '@/components/support/apikey/Table';
import { useTranslation } from 'next-i18next';

View File

@@ -1,3 +1,4 @@
'use client';
import { Box, Button, Flex } from '@chakra-ui/react';
import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs';
import dynamic from 'next/dynamic';

View File

@@ -1,3 +1,4 @@
'use client';
import React, { useCallback, useEffect, useMemo } from 'react';
import {
Box,

View File

@@ -1,3 +1,4 @@
'use client';
import React, { useState } from 'react';
import { Box, Flex, useTheme } from '@chakra-ui/react';
import { getInforms, readInform } from '@/web/support/user/inform/api';

View File

@@ -1,3 +1,4 @@
'use client';
import { serviceSideProps } from '@/web/common/i18n/utils';
import React, { useMemo, useState } from 'react';
import AccountContainer from '@/pageComponents/account/AccountContainer';

View File

@@ -1,3 +1,4 @@
'use client';
import React from 'react';
import {
Grid,

View File

@@ -1,3 +1,4 @@
'use client';
import { Box, Card, Flex } from '@chakra-ui/react';
import React, { useCallback } from 'react';
import MyIcon from '@fastgpt/web/components/common/Icon';

View File

@@ -1,3 +1,4 @@
'use client';
import { serviceSideProps } from '@/web/common/i18n/utils';
import AccountContainer from '@/pageComponents/account/AccountContainer';
import { Box, Flex } from '@chakra-ui/react';

View File

@@ -1,3 +1,4 @@
'use client';
import React, { useEffect, useMemo, useState } from 'react';
import { Flex, Box, HStack } from '@chakra-ui/react';
import { UsageSourceEnum, UsageSourceMap } from '@fastgpt/global/support/wallet/usage/constants';

View File

@@ -21,7 +21,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
await authCert({ req, authRoot: true });
// load config
const [{ config: dbConfig }, fileConfig] = await Promise.all([
const [{ fastgptConfig: dbConfig }, fileConfig] = await Promise.all([
getFastGPTConfigFromDB(),
readConfigData('config.json')
]);

View File

@@ -11,6 +11,7 @@ import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils';
import { authApp } from '@fastgpt/service/support/permission/app/auth';
import { TeamAppCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant';
import { checkTeamAppLimit } from '@fastgpt/service/support/permission/teamLimit';
export type createHttpPluginQuery = {};
@@ -35,6 +36,8 @@ async function handler(
? await authApp({ req, appId: parentId, per: TeamAppCreatePermissionVal, authToken: true })
: await authUserPer({ req, authToken: true, per: TeamAppCreatePermissionVal });
await checkTeamAppLimit(teamId);
const httpPluginId = await mongoSessionRun(async (session) => {
// create http plugin folder
const httpPluginId = await onCreateApp({

View File

@@ -12,6 +12,7 @@ import {
getMCPToolSetRuntimeNode
} from '@fastgpt/global/core/app/mcpTools/utils';
import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils';
import { checkTeamAppLimit } from '@fastgpt/service/support/permission/teamLimit';
export type createMCPToolsQuery = {};
@@ -35,6 +36,8 @@ async function handler(
? await authApp({ req, appId: parentId, per: TeamAppCreatePermissionVal, authToken: true })
: await authUserPer({ req, authToken: true, per: TeamAppCreatePermissionVal });
await checkTeamAppLimit(teamId);
const mcpToolsId = await mongoSessionRun(async (session) => {
const mcpToolsId = await onCreateApp({
name,

View File

@@ -1,3 +1,4 @@
'use client';
import React, { useEffect } from 'react';
import { Box } from '@chakra-ui/react';
import dynamic from 'next/dynamic';

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react';
import React, { useCallback, useEffect, useMemo } from 'react';
import NextHead from '@/components/common/NextHead';
import { useRouter } from 'next/router';
import { getInitChatInfo } from '@/web/core/chat/api';

View File

@@ -1,3 +1,4 @@
'use client';
import DashboardContainer from '@/pageComponents/dashboard/Container';
import PluginCard from '@/pageComponents/dashboard/SystemPlugin/ToolCard';

View File

@@ -1,4 +1,5 @@
import React, { useMemo, useState, useEffect } from 'react';
'use client';
import React, { useMemo, useState } from 'react';
import { Box, Flex, Button, useDisclosure, Input, InputGroup } from '@chakra-ui/react';
import { AddIcon } from '@chakra-ui/icons';
import { serviceSideProps } from '@/web/common/i18n/utils';

View File

@@ -1,3 +1,4 @@
'use client';
import { serviceSideProps } from '@/web/common/i18n/utils';
import React, { useState } from 'react';
import DashboardContainer from '@/pageComponents/dashboard/Container';

View File

@@ -1,3 +1,4 @@
'use client';
import { serviceSideProps } from '@/web/common/i18n/utils';
import DashboardContainer from '@/pageComponents/dashboard/Container';
import { Box, Button, Flex, Grid, HStack } from '@chakra-ui/react';

View File

@@ -1,3 +1,4 @@
'use client';
import React from 'react';
import { useRouter } from 'next/router';
import { Box, Flex, type FlexProps } from '@chakra-ui/react';

View File

@@ -1,3 +1,4 @@
'use client';
import React, { useCallback, useMemo, useState } from 'react';
import { Box, Flex, Button, InputGroup, InputLeftElement, Input } from '@chakra-ui/react';
import { useRouter } from 'next/router';

View File

@@ -10,7 +10,6 @@ import json5 from 'json5';
import { defaultGroup, defaultTemplateTypes } from '@fastgpt/web/core/workflow/constants';
import { MongoPluginGroups } from '@fastgpt/service/core/app/plugin/pluginGroupSchema';
import { MongoTemplateTypes } from '@fastgpt/service/core/app/templates/templateTypeSchema';
import { loadSystemModels } from '@fastgpt/service/core/ai/config/utils';
import { POST } from '@fastgpt/service/common/api/plusRequest';
import {
type DeepRagSearchProps,
@@ -28,7 +27,6 @@ import {
getProApiDatasetFilePreviewUrlRequest
} from '@/service/core/dataset/apiDataset/controller';
import { isProVersion } from './constants';
import { preLoadWorker } from '@fastgpt/service/worker/preload';
export const readConfigData = async (name: string) => {
const splitName = name.split('.');
@@ -95,12 +93,25 @@ export function initGlobalVariables() {
/* Init system data(Need to connected db). It only needs to run once */
export async function getInitConfig() {
await Promise.all([initSystemConfig(), getSystemVersion(), loadSystemModels()]);
try {
await preLoadWorker();
} catch (error) {
console.error('Preload worker error', error);
}
const getSystemVersion = async () => {
if (global.systemVersion) return;
try {
if (process.env.NODE_ENV === 'development') {
global.systemVersion = process.env.npm_package_version || '0.0.0';
} else {
const packageJson = json5.parse(await fs.promises.readFile('/app/package.json', 'utf-8'));
global.systemVersion = packageJson?.version;
}
console.log(`System Version: ${global.systemVersion}`);
} catch (error) {
console.log(error);
global.systemVersion = '0.0.0';
}
};
await Promise.all([initSystemConfig(), getSystemVersion()]);
}
const defaultFeConfigs: FastGPTFeConfigsType = {
@@ -125,10 +136,12 @@ const defaultFeConfigs: FastGPTFeConfigsType = {
export async function initSystemConfig() {
// load config
const [{ config: dbConfig }, fileConfig] = await Promise.all([
const [{ fastgptConfig, licenseData }, fileConfig] = await Promise.all([
getFastGPTConfigFromDB(),
readConfigData('config.json')
]);
global.licenseData = licenseData;
const fileRes = json5.parse(fileConfig) as FastGPTConfigFileType;
// get config from database
@@ -136,16 +149,18 @@ export async function initSystemConfig() {
feConfigs: {
...fileRes?.feConfigs,
...defaultFeConfigs,
...(dbConfig.feConfigs || {}),
isPlus: !!FastGPTProUrl,
...(fastgptConfig.feConfigs || {}),
isPlus: !!licenseData,
show_aiproxy: !!process.env.AIPROXY_API_ENDPOINT,
show_coupon: process.env.SHOW_COUPON === 'true'
show_coupon: process.env.SHOW_COUPON === 'true',
show_dataset_enhance: licenseData?.functions?.datasetEnhance,
show_batch_eval: licenseData?.functions?.batchEval
},
systemEnv: {
...fileRes.systemEnv,
...(dbConfig.systemEnv || {})
...(fastgptConfig.systemEnv || {})
},
subPlans: dbConfig.subPlans || fileRes.subPlans
subPlans: fastgptConfig.subPlans
};
// set config
@@ -154,28 +169,11 @@ export async function initSystemConfig() {
console.log({
feConfigs: global.feConfigs,
systemEnv: global.systemEnv,
subPlans: global.subPlans
subPlans: global.subPlans,
licenseData: global.licenseData
});
}
async function getSystemVersion() {
if (global.systemVersion) return;
try {
if (process.env.NODE_ENV === 'development') {
global.systemVersion = process.env.npm_package_version || '0.0.0';
} else {
const packageJson = json5.parse(await fs.promises.readFile('/app/package.json', 'utf-8'));
global.systemVersion = packageJson?.version;
}
console.log(`System Version: ${global.systemVersion}`);
} catch (error) {
console.log(error);
global.systemVersion = '0.0.0';
}
}
export async function initSystemPluginGroups() {
try {
const { groupOrder, ...restDefaultGroup } = defaultGroup;

View File

@@ -7,6 +7,7 @@ import { debounce } from 'lodash';
import { MongoAppTemplate } from '@fastgpt/service/core/app/templates/templateSchema';
import { getAppTemplatesAndLoadThem } from '@fastgpt/templates/register';
import { watchSystemModelUpdate } from '@fastgpt/service/core/ai/config/utils';
import { SystemConfigsTypeEnum } from '@fastgpt/global/common/system/config/constants';
export const startMongoWatch = async () => {
reloadConfigWatch();
@@ -21,7 +22,11 @@ const reloadConfigWatch = () => {
changeStream.on('change', async (change) => {
try {
if (change.operationType === 'insert') {
if (
(change.operationType === 'insert' &&
change.fullDocument.type === SystemConfigsTypeEnum.fastgptPro) ||
change.operationType === 'update'
) {
await initSystemConfig();
console.log('refresh system config');
}

View File

@@ -23,13 +23,25 @@ type State = {
const createCustomStorage = () => {
const sessionKeys = ['source', 'chatId', 'appId'];
// 从 URL 中获取 appId 作为存储键的一部分
const getStorageKey = (name: string) => {
let appId = '';
if (typeof window !== 'undefined') {
const urlParams = new URLSearchParams(window.location.search);
appId = urlParams.get('appId') || '';
}
return appId ? `${name}_${appId}` : name;
};
return {
getItem: (name: string) => {
const sessionData = JSON.parse(sessionStorage.getItem(name) || '{}');
const localData = JSON.parse(localStorage.getItem(name) || '{}');
const storageKey = getStorageKey(name);
const sessionData = JSON.parse(sessionStorage.getItem(storageKey) || '{}');
const localData = JSON.parse(localStorage.getItem(storageKey) || '{}');
return JSON.stringify({ ...localData, ...sessionData });
},
setItem: (name: string, value: string) => {
const storageKey = getStorageKey(name);
const data = JSON.parse(value);
// 分离 session 和 local 数据
@@ -42,15 +54,16 @@ const createCustomStorage = () => {
// 分别存储
if (Object.keys(sessionData).length > 0) {
sessionStorage.setItem(name, JSON.stringify({ state: sessionData, version: 0 }));
sessionStorage.setItem(storageKey, JSON.stringify({ state: sessionData, version: 0 }));
}
if (Object.keys(localData).length > 0) {
localStorage.setItem(name, JSON.stringify({ state: localData, version: 0 }));
localStorage.setItem(storageKey, JSON.stringify({ state: localData, version: 0 }));
}
},
removeItem: (name: string) => {
sessionStorage.removeItem(name);
localStorage.removeItem(name);
const storageKey = getStorageKey(name);
sessionStorage.removeItem(storageKey);
localStorage.removeItem(storageKey);
}
};
};
@@ -125,3 +138,5 @@ export const useChatStore = create<State>()(
)
)
);
export { createCustomStorage };

View File

@@ -4,8 +4,8 @@ import { checkPasswordRule } from '@fastgpt/global/common/string/password';
describe('PasswordRule', () => {
it('should be a valid password', () => {
// Small password
expect(checkPasswordRule('123A')).toBe(false);
expect(checkPasswordRule('@ga21')).toBe(false);
expect(checkPasswordRule('123ABC')).toBe(false);
expect(checkPasswordRule('@ga2123')).toBe(false);
// Test single type characters
expect(checkPasswordRule('123456')).toBe(false);
@@ -14,13 +14,13 @@ describe('PasswordRule', () => {
expect(checkPasswordRule('!@#$%^')).toBe(false); // only special chars
// Test two types combination
expect(checkPasswordRule('abc123')).toBe(true); // lowercase + numbers
expect(checkPasswordRule('abcABC')).toBe(true); // lowercase + uppercase
expect(checkPasswordRule('abc!@#')).toBe(true); // lowercase + special chars
expect(checkPasswordRule('ABC!@#')).toBe(true); // uppercase + special chars
expect(checkPasswordRule('ABC123')).toBe(true); // uppercase + numbers
expect(checkPasswordRule('123!@#')).toBe(true); // numbers + special chars
expect(checkPasswordRule('!@123fa')).toBe(true); // numbers + special chars
expect(checkPasswordRule('abcd1234')).toBe(true); // lowercase + numbers
expect(checkPasswordRule('abcdABCD')).toBe(true); // lowercase + uppercase
expect(checkPasswordRule('abcd!@#$')).toBe(true); // lowercase + special chars
expect(checkPasswordRule('ABCD!@#$')).toBe(true); // uppercase + special chars
expect(checkPasswordRule('ABCD1234')).toBe(true); // uppercase + numbers
expect(checkPasswordRule('1234!@#$')).toBe(true); // numbers + special chars
expect(checkPasswordRule('!@123fab')).toBe(true); // numbers + special chars
expect(checkPasswordRule('+2222()222')).toBe(true); // special chars + numbers
expect(checkPasswordRule('_2222()-+=22')).toBe(true); // special chars + numbers

View File

@@ -0,0 +1,170 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import { useChatStore, createCustomStorage } from '@/web/core/chat/context/useChatStore';
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
import { getNanoid } from '@fastgpt/global/common/string/tools';
vi.mock('@fastgpt/global/common/string/tools', () => ({
getNanoid: vi.fn().mockReturnValue('test-nanoid')
}));
const mockStorage = () => {
const store = new Map();
return {
getItem: (key: string) => store.get(key) || null,
setItem: (key: string, value: string) => store.set(key, value),
clear: () => store.clear(),
removeItem: (key: string) => store.delete(key)
};
};
const mockWindow = () => {
const windowMock = {
location: {
search: '?appId=test123'
},
sessionStorage: mockStorage(),
localStorage: mockStorage()
};
vi.stubGlobal('window', windowMock);
global.sessionStorage = windowMock.sessionStorage;
global.localStorage = windowMock.localStorage;
};
beforeEach(() => {
vi.resetModules();
vi.clearAllMocks();
mockWindow();
const store = useChatStore.getState();
store.source = undefined;
store.appId = '';
store.chatId = '';
store.lastChatId = '';
store.lastChatAppId = '';
store.outLinkAuthData = {};
sessionStorage.clear();
localStorage.clear();
});
describe('useChatStore', () => {
it('should set source and restore last chat if available', () => {
const store = useChatStore.getState();
store.lastChatAppId = 'app123';
store.lastChatId = `${ChatSourceEnum.share}-chat123`;
store.setSource(ChatSourceEnum.share);
const updatedStore = useChatStore.getState();
expect(updatedStore.source).toBe(ChatSourceEnum.share);
expect(updatedStore.chatId).toBe('chat123');
expect(updatedStore.lastChatAppId).toBe('app123');
});
it('should generate new chatId when source changes', () => {
const store = useChatStore.getState();
store.source = ChatSourceEnum.share;
store.chatId = 'old-id';
store.setSource(ChatSourceEnum.api);
const updatedStore = useChatStore.getState();
expect(updatedStore.chatId).toBe('test-nanoid');
expect(updatedStore.chatId).not.toBe('old-id');
});
it('should set appId and lastChatAppId', () => {
const store = useChatStore.getState();
store.setAppId('test123');
const updatedStore = useChatStore.getState();
expect(updatedStore.appId).toBe('test123');
expect(updatedStore.lastChatAppId).toBe('test123');
});
it('should not set empty appId', () => {
const store = useChatStore.getState();
store.setAppId('test123');
store.setAppId('');
const updatedStore = useChatStore.getState();
expect(updatedStore.appId).toBe('test123');
expect(updatedStore.lastChatAppId).toBe('test123');
});
it('should set chatId and lastChatId', () => {
const store = useChatStore.getState();
store.source = ChatSourceEnum.share;
store.setChatId('test-id');
const updatedStore = useChatStore.getState();
expect(updatedStore.chatId).toBe('test-id');
expect(updatedStore.lastChatId).toBe(`${ChatSourceEnum.share}-test-id`);
});
it('should generate new chatId if none provided', () => {
const store = useChatStore.getState();
store.source = ChatSourceEnum.share;
store.setChatId();
const updatedStore = useChatStore.getState();
expect(updatedStore.chatId).toBe('test-nanoid');
});
it('should set outLinkAuthData', () => {
const store = useChatStore.getState();
const authData = { apikey: 'test-key' };
store.setOutLinkAuthData(authData);
const updatedStore = useChatStore.getState();
expect(updatedStore.outLinkAuthData).toEqual(authData);
});
});
describe('createCustomStorage', () => {
it('should create storage with appId in key', () => {
const storage = createCustomStorage();
const testData = {
state: {
source: ChatSourceEnum.share,
chatId: '123',
appId: 'app123',
lastChatId: 'last123',
lastChatAppId: 'lastApp123'
},
version: 0
};
storage.setItem('test', JSON.stringify(testData));
const sessionResult = JSON.parse(sessionStorage.getItem('test_test123') || '{}');
const localResult = JSON.parse(localStorage.getItem('test_test123') || '{}');
expect(sessionResult.state).toEqual({
source: ChatSourceEnum.share,
chatId: '123',
appId: 'app123'
});
expect(localResult.state).toEqual({
lastChatId: 'last123',
lastChatAppId: 'lastApp123'
});
});
it('should remove items from both storages', () => {
const storage = createCustomStorage();
const testData = {
state: {
source: ChatSourceEnum.share,
chatId: '123'
},
version: 0
};
storage.setItem('test', JSON.stringify(testData));
storage.removeItem('test');
expect(sessionStorage.getItem('test_test123')).toBeNull();
expect(localStorage.getItem('test_test123')).toBeNull();
});
});

View File

@@ -0,0 +1 @@
{"/root/.cache/node/corepack/v1/pnpm/9.15.5/bin/pnpm.cjs":["f5286335ac1397138eff043e0d78e29501577055",0,1472],"/root/.cache/node/corepack/v1/pnpm/9.15.5/dist/pnpm.cjs":["0b2b22796df6e249cd89f4cdf06786d40a52564e",1472,886352]}