mirror of
https://github.com/labring/FastGPT.git
synced 2025-10-14 15:11:13 +00:00
feature: 4.10.1 (#5201)
* add dynamic inputRender (#5127) * dynamic input component * fix * fix * fix * perf: dynamic render input * update doc * perf: error catch * num input ui * fix form render (#5177) * perf: i18n check * add log * doc * Sync dataset (#5181) * perf: api dataset create (#5047) * Sync dataset (#5120) * add * wait * restructure dataset sync, update types and APIs, add sync hints, and remove legacy logic * feat: add function to retrieve real file ID from third-party doc library and rename team permission check function for clarity * fix come console * refactor: rename team dataset limit check functions for clarity, update API dataset sync limit usage, and rename root directory to "ROOT_FOLDER" * frat: update sync dataset login * fix delete.ts * feat: update pnpm-lock.yaml to include bullmq, fix comments in api.d.ts and type.d.ts, rename API file ID field, optimize dataset sync logic, and add website sync feature with related APIs * feat: update CollectionCard to support site dataset sync, add API root ID constant and init sync API * feat: add RootCollectionId constant to replace hardcoded root ID --------- Co-authored-by: dreamer6680 <146868355@qq.com> * perf: code * feat: update success message for dataset sync, revise related i18n texts, and optimize file selection logic (#5166) Co-authored-by: dreamer6680 <146868355@qq.com> * perf: select file * Sync dataset (#5180) * feat: update success message for dataset sync, revise related i18n texts, and optimize file selection logic * fix: make listfile function return rawid string --------- Co-authored-by: dreamer6680 <146868355@qq.com> * init sh * fix: ts --------- Co-authored-by: dreamer6680 <1468683855@qq.com> Co-authored-by: dreamer6680 <146868355@qq.com> * update doc * i18n --------- Co-authored-by: heheer <heheer@sealos.io> Co-authored-by: dreamer6680 <1468683855@qq.com> Co-authored-by: dreamer6680 <146868355@qq.com>
This commit is contained in:
@@ -97,8 +97,10 @@ export const getNextTimeByCronStringAndTimezone = ({
|
||||
};
|
||||
const interval = cronParser.parseExpression(cronString, options);
|
||||
const date = interval.next().toString();
|
||||
|
||||
return new Date(date);
|
||||
} catch (error) {
|
||||
return new Date('2099');
|
||||
console.log('getNextTimeByCronStringAndTimezone error', error);
|
||||
return new Date();
|
||||
}
|
||||
};
|
||||
|
@@ -17,7 +17,7 @@ export type JSONSchemaInputType = {
|
||||
required?: string[];
|
||||
};
|
||||
|
||||
const getNodeInputTypeFromSchemaInputType = ({
|
||||
export const getNodeInputTypeFromSchemaInputType = ({
|
||||
type,
|
||||
arrayItems
|
||||
}: {
|
||||
|
8
packages/global/core/dataset/api.d.ts
vendored
8
packages/global/core/dataset/api.d.ts
vendored
@@ -13,6 +13,7 @@ import type {
|
||||
ParagraphChunkAIModeEnum
|
||||
} from './constants';
|
||||
import type { ParentIdType } from '../../common/parentFolder/type';
|
||||
import type { APIFileItemType } from './apiDataset/type';
|
||||
|
||||
/* ================= dataset ===================== */
|
||||
export type DatasetUpdateBody = {
|
||||
@@ -57,6 +58,7 @@ export type CreateDatasetCollectionParams = DatasetCollectionStoreDataType & {
|
||||
externalFileId?: string;
|
||||
externalFileUrl?: string;
|
||||
apiFileId?: string;
|
||||
apiFileParentId?: string; //when file is imported by folder, the parentId is the folderId
|
||||
|
||||
rawTextLength?: number;
|
||||
hashRawText?: string;
|
||||
@@ -65,7 +67,6 @@ export type CreateDatasetCollectionParams = DatasetCollectionStoreDataType & {
|
||||
|
||||
createTime?: Date;
|
||||
updateTime?: Date;
|
||||
nextSyncTime?: Date;
|
||||
};
|
||||
|
||||
export type ApiCreateDatasetCollectionParams = DatasetCollectionStoreDataType & {
|
||||
@@ -83,6 +84,9 @@ export type ApiDatasetCreateDatasetCollectionParams = ApiCreateDatasetCollection
|
||||
name: string;
|
||||
apiFileId: string;
|
||||
};
|
||||
export type ApiDatasetCreateDatasetCollectionV2Params = ApiCreateDatasetCollectionParams & {
|
||||
apiFiles: APIFileItemType[];
|
||||
};
|
||||
export type FileIdCreateDatasetCollectionParams = ApiCreateDatasetCollectionParams & {
|
||||
fileId: string;
|
||||
};
|
||||
@@ -139,7 +143,7 @@ export type PushDatasetDataChunkProps = {
|
||||
indexes?: Omit<DatasetDataIndexItemType, 'dataId'>[];
|
||||
};
|
||||
|
||||
export type PostWebsiteSyncParams = {
|
||||
export type PostDatasetSyncParams = {
|
||||
datasetId: string;
|
||||
};
|
||||
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import { RequireOnlyOne } from '../../../common/type/utils';
|
||||
import type { ParentIdType } from '../../../common/parentFolder/type';
|
||||
|
||||
export type APIFileItem = {
|
||||
export type APIFileItemType = {
|
||||
id: string;
|
||||
rawId: string;
|
||||
parentId: ParentIdType;
|
||||
name: string;
|
||||
type: 'file' | 'folder';
|
||||
@@ -36,8 +37,6 @@ export type ApiDatasetServerType = {
|
||||
|
||||
// Api dataset api
|
||||
|
||||
export type APIFileListResponse = APIFileItem[];
|
||||
|
||||
export type ApiFileReadContentResponse = {
|
||||
title?: string;
|
||||
rawText: string;
|
||||
@@ -47,8 +46,4 @@ export type APIFileReadResponse = {
|
||||
url: string;
|
||||
};
|
||||
|
||||
export type ApiDatasetDetailResponse = {
|
||||
id: string;
|
||||
name: string;
|
||||
parentId: ParentIdType;
|
||||
};
|
||||
export type ApiDatasetDetailResponse = APIFileItemType;
|
||||
|
@@ -4,3 +4,5 @@ export enum CollectionSourcePrefixEnum {
|
||||
link = 'link',
|
||||
external = 'external'
|
||||
}
|
||||
|
||||
export const RootCollectionId = 'SYSTEM_ROOT';
|
||||
|
2
packages/global/core/dataset/type.d.ts
vendored
2
packages/global/core/dataset/type.d.ts
vendored
@@ -106,13 +106,13 @@ export type DatasetCollectionSchemaType = ChunkSettingsType & {
|
||||
|
||||
// Status
|
||||
forbid?: boolean;
|
||||
nextSyncTime?: Date;
|
||||
|
||||
// Collection metadata
|
||||
fileId?: string; // local file id
|
||||
rawLink?: string; // link url
|
||||
externalFileId?: string; //external file id
|
||||
apiFileId?: string; // api file id
|
||||
apiFileParentId?: string;
|
||||
externalFileUrl?: string; // external import url
|
||||
|
||||
rawTextLength?: number;
|
||||
|
@@ -19,6 +19,8 @@ const defaultWorkerOpts: Omit<ConnectionOptions, 'connection'> = {
|
||||
};
|
||||
|
||||
export enum QueueNames {
|
||||
datasetSync = 'datasetSync',
|
||||
// abondoned
|
||||
websiteSync = 'websiteSync'
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import type {
|
||||
APIFileListResponse,
|
||||
ApiFileReadContentResponse,
|
||||
APIFileReadResponse,
|
||||
ApiDatasetDetailResponse,
|
||||
@@ -19,6 +18,16 @@ type ResponseDataType = {
|
||||
data: any;
|
||||
};
|
||||
|
||||
type APIFileListResponse = {
|
||||
id: string;
|
||||
parentId: ParentIdType;
|
||||
name: string;
|
||||
type: 'file' | 'folder';
|
||||
updateTime: Date;
|
||||
createTime: Date;
|
||||
hasChild?: boolean;
|
||||
};
|
||||
|
||||
export const useApiDatasetRequest = ({ apiServer }: { apiServer: APIFileServer }) => {
|
||||
const instance = axios.create({
|
||||
baseURL: apiServer.baseUrl,
|
||||
@@ -106,6 +115,7 @@ export const useApiDatasetRequest = ({ apiServer }: { apiServer: APIFileServer }
|
||||
|
||||
const formattedFiles = files.map((file) => ({
|
||||
...file,
|
||||
rawId: file.id,
|
||||
hasChild: file.hasChild ?? file.type === 'folder'
|
||||
}));
|
||||
|
||||
@@ -201,18 +211,27 @@ export const useApiDatasetRequest = ({ apiServer }: { apiServer: APIFileServer }
|
||||
if (fileData) {
|
||||
return {
|
||||
id: fileData.id,
|
||||
rawId: apiFileId,
|
||||
name: fileData.name,
|
||||
parentId: fileData.parentId === null ? '' : fileData.parentId
|
||||
parentId: fileData.parentId === null ? '' : fileData.parentId,
|
||||
type: fileData.type,
|
||||
updateTime: fileData.updateTime,
|
||||
createTime: fileData.createTime
|
||||
};
|
||||
}
|
||||
|
||||
return Promise.reject('File not found');
|
||||
};
|
||||
|
||||
const getFileRawId = (fileId: string) => {
|
||||
return fileId;
|
||||
};
|
||||
|
||||
return {
|
||||
getFileContent,
|
||||
listFiles,
|
||||
getFilePreviewUrl,
|
||||
getFileDetail
|
||||
getFileDetail,
|
||||
getFileRawId
|
||||
};
|
||||
};
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import type {
|
||||
APIFileItem,
|
||||
APIFileItemType,
|
||||
ApiFileReadContentResponse,
|
||||
ApiDatasetDetailResponse,
|
||||
FeishuServer
|
||||
@@ -104,7 +104,11 @@ export const useFeishuDatasetRequest = ({ feishuServer }: { feishuServer: Feishu
|
||||
.catch((err) => responseError(err));
|
||||
};
|
||||
|
||||
const listFiles = async ({ parentId }: { parentId?: ParentIdType }): Promise<APIFileItem[]> => {
|
||||
const listFiles = async ({
|
||||
parentId
|
||||
}: {
|
||||
parentId?: ParentIdType;
|
||||
}): Promise<APIFileItemType[]> => {
|
||||
const fetchFiles = async (pageToken?: string): Promise<FeishuFileListResponse['files']> => {
|
||||
const data = await request<FeishuFileListResponse>(
|
||||
`/open-apis/drive/v1/files`,
|
||||
@@ -130,6 +134,7 @@ export const useFeishuDatasetRequest = ({ feishuServer }: { feishuServer: Feishu
|
||||
.filter((file) => ['folder', 'docx'].includes(file.type))
|
||||
.map((file) => ({
|
||||
id: file.token,
|
||||
rawId: file.token,
|
||||
parentId: file.parent_token,
|
||||
name: file.name,
|
||||
type: file.type === 'folder' ? ('folder' as const) : ('file' as const),
|
||||
@@ -186,23 +191,33 @@ export const useFeishuDatasetRequest = ({ feishuServer }: { feishuServer: Feishu
|
||||
}: {
|
||||
apiFileId: string;
|
||||
}): Promise<ApiDatasetDetailResponse> => {
|
||||
const { document } = await request<{ document: { title: string } }>(
|
||||
const { document } = await request<{ document: { title: string; type: string } }>(
|
||||
`/open-apis/docx/v1/documents/${apiFileId}`,
|
||||
{},
|
||||
'GET'
|
||||
);
|
||||
|
||||
return {
|
||||
rawId: apiFileId,
|
||||
name: document?.title,
|
||||
parentId: null,
|
||||
id: apiFileId
|
||||
id: apiFileId,
|
||||
type: document.type === 'folder' ? ('folder' as const) : ('file' as const),
|
||||
hasChild: document.type === 'folder',
|
||||
updateTime: new Date(),
|
||||
createTime: new Date()
|
||||
};
|
||||
};
|
||||
|
||||
const getFileRawId = (fileId: string) => {
|
||||
return fileId;
|
||||
};
|
||||
|
||||
return {
|
||||
getFileContent,
|
||||
listFiles,
|
||||
getFilePreviewUrl,
|
||||
getFileDetail
|
||||
getFileDetail,
|
||||
getFileRawId
|
||||
};
|
||||
};
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import type {
|
||||
APIFileItem,
|
||||
APIFileItemType,
|
||||
ApiFileReadContentResponse,
|
||||
YuqueServer,
|
||||
ApiDatasetDetailResponse
|
||||
@@ -106,7 +106,7 @@ export const useYuqueDatasetRequest = ({ yuqueServer }: { yuqueServer: YuqueServ
|
||||
if (yuqueServer.basePath) parentId = yuqueServer.basePath;
|
||||
}
|
||||
|
||||
let files: APIFileItem[] = [];
|
||||
let files: APIFileItemType[] = [];
|
||||
|
||||
if (!parentId) {
|
||||
const limit = 100;
|
||||
@@ -133,7 +133,8 @@ export const useYuqueDatasetRequest = ({ yuqueServer }: { yuqueServer: YuqueServ
|
||||
|
||||
files = allData.map((item) => {
|
||||
return {
|
||||
id: item.id,
|
||||
id: String(item.id),
|
||||
rawId: String(item.id),
|
||||
name: item.name,
|
||||
parentId: null,
|
||||
type: 'folder',
|
||||
@@ -144,7 +145,8 @@ export const useYuqueDatasetRequest = ({ yuqueServer }: { yuqueServer: YuqueServ
|
||||
};
|
||||
});
|
||||
} else {
|
||||
if (typeof parentId === 'number') {
|
||||
const numParentId = Number(parentId);
|
||||
if (!isNaN(numParentId)) {
|
||||
const data = await request<YuqueTocListResponse>(
|
||||
`/api/v2/repos/${parentId}/toc`,
|
||||
{},
|
||||
@@ -155,6 +157,7 @@ export const useYuqueDatasetRequest = ({ yuqueServer }: { yuqueServer: YuqueServ
|
||||
.filter((item) => !item.parent_uuid && item.type !== 'LINK')
|
||||
.map((item) => ({
|
||||
id: `${parentId}-${item.id}-${item.uuid}`,
|
||||
rawId: String(item.uuid),
|
||||
name: item.title,
|
||||
parentId: item.parent_uuid,
|
||||
type: item.type === 'TITLE' ? ('folder' as const) : ('file' as const),
|
||||
@@ -167,11 +170,11 @@ export const useYuqueDatasetRequest = ({ yuqueServer }: { yuqueServer: YuqueServ
|
||||
} else {
|
||||
const [repoId, uuid, parentUuid] = parentId.split(/-(.*?)-(.*)/);
|
||||
const data = await request<YuqueTocListResponse>(`/api/v2/repos/${repoId}/toc`, {}, 'GET');
|
||||
|
||||
return data
|
||||
.filter((item) => item.parent_uuid === parentUuid)
|
||||
.map((item) => ({
|
||||
id: `${repoId}-${item.id}-${item.uuid}`,
|
||||
rawId: String(item.uuid),
|
||||
name: item.title,
|
||||
parentId: item.parent_uuid,
|
||||
type: item.type === 'TITLE' ? ('folder' as const) : ('file' as const),
|
||||
@@ -207,6 +210,10 @@ export const useYuqueDatasetRequest = ({ yuqueServer }: { yuqueServer: YuqueServ
|
||||
'GET'
|
||||
);
|
||||
|
||||
if (!data.title) {
|
||||
return Promise.reject('Cannot find the file');
|
||||
}
|
||||
|
||||
return {
|
||||
title: data.title,
|
||||
rawText: data.body
|
||||
@@ -266,8 +273,13 @@ export const useYuqueDatasetRequest = ({ yuqueServer }: { yuqueServer: YuqueServ
|
||||
}
|
||||
return {
|
||||
id: file.id,
|
||||
rawId: file.id,
|
||||
name: file.name,
|
||||
parentId: null
|
||||
parentId: null,
|
||||
type: file.type === 'TITLE' ? ('folder' as const) : ('file' as const),
|
||||
updateTime: file.updated_at,
|
||||
createTime: file.created_at,
|
||||
hasChild: true
|
||||
};
|
||||
} else {
|
||||
const [repoId, parentUuid, fileId] = apiFileId.split(/-(.*?)-(.*)/);
|
||||
@@ -283,23 +295,43 @@ export const useYuqueDatasetRequest = ({ yuqueServer }: { yuqueServer: YuqueServ
|
||||
if (file.parent_uuid) {
|
||||
return {
|
||||
id: file.id,
|
||||
rawId: file.id,
|
||||
name: file.title,
|
||||
parentId: parentId
|
||||
parentId: parentId,
|
||||
type: file.type === 'TITLE' ? ('folder' as const) : ('file' as const),
|
||||
updateTime: new Date(),
|
||||
createTime: new Date(),
|
||||
hasChild: !!file.child_uuid
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
id: file.id,
|
||||
rawId: file.id,
|
||||
name: file.title,
|
||||
parentId: repoId
|
||||
parentId: repoId,
|
||||
type: file.type === 'TITLE' ? ('folder' as const) : ('file' as const),
|
||||
updateTime: new Date(),
|
||||
createTime: new Date(),
|
||||
hasChild: !!file.child_uuid
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getFileRawId = (fileId: string) => {
|
||||
const [repoId, parentUuid, fileUuid] = fileId.split(/-(.*?)-(.*)/);
|
||||
if (fileUuid) {
|
||||
return `${fileUuid}`;
|
||||
} else {
|
||||
return `${repoId}`;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
getFileContent,
|
||||
listFiles,
|
||||
getFilePreviewUrl,
|
||||
getFileDetail
|
||||
getFileDetail,
|
||||
getFileRawId
|
||||
};
|
||||
};
|
||||
|
@@ -180,18 +180,6 @@ export const createCollectionAndInsertData = async ({
|
||||
|
||||
hashRawText: rawText ? hashStr(rawText) : undefined,
|
||||
rawTextLength: rawText?.length,
|
||||
nextSyncTime: (() => {
|
||||
// ignore auto collections sync for website datasets
|
||||
if (!dataset.autoSync && dataset.type === DatasetTypeEnum.websiteDataset) return undefined;
|
||||
if (
|
||||
[DatasetCollectionTypeEnum.link, DatasetCollectionTypeEnum.apiFile].includes(
|
||||
formatCreateCollectionParams.type
|
||||
)
|
||||
) {
|
||||
return addDays(new Date(), 1);
|
||||
}
|
||||
return undefined;
|
||||
})(),
|
||||
session
|
||||
});
|
||||
|
||||
@@ -285,7 +273,8 @@ export async function createOneCollection({ session, ...props }: CreateOneCollec
|
||||
rawLink,
|
||||
externalFileId,
|
||||
externalFileUrl,
|
||||
apiFileId
|
||||
apiFileId,
|
||||
apiFileParentId
|
||||
} = props;
|
||||
|
||||
const collectionTags = await createOrGetCollectionTags({
|
||||
@@ -310,7 +299,8 @@ export async function createOneCollection({ session, ...props }: CreateOneCollec
|
||||
...(rawLink ? { rawLink } : {}),
|
||||
...(externalFileId ? { externalFileId } : {}),
|
||||
...(externalFileUrl ? { externalFileUrl } : {}),
|
||||
...(apiFileId ? { apiFileId } : {})
|
||||
...(apiFileId ? { apiFileId } : {}),
|
||||
...(apiFileParentId ? { apiFileParentId } : {})
|
||||
}
|
||||
],
|
||||
{ session, ordered: true }
|
||||
|
@@ -78,11 +78,10 @@ const DatasetCollectionSchema = new Schema({
|
||||
},
|
||||
|
||||
forbid: Boolean,
|
||||
// next sync time
|
||||
nextSyncTime: Date,
|
||||
|
||||
// Parse settings
|
||||
customPdfParse: Boolean,
|
||||
apiFileParentId: String,
|
||||
|
||||
// Chunk settings
|
||||
...ChunkSettings
|
||||
@@ -112,16 +111,6 @@ try {
|
||||
// create time filter
|
||||
DatasetCollectionSchema.index({ teamId: 1, datasetId: 1, createTime: 1 });
|
||||
|
||||
// next sync time filter
|
||||
DatasetCollectionSchema.index(
|
||||
{ type: 1, nextSyncTime: -1 },
|
||||
{
|
||||
partialFilterExpression: {
|
||||
nextSyncTime: { $exists: true }
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Get collection by external file id
|
||||
DatasetCollectionSchema.index(
|
||||
{ datasetId: 1, externalFileId: 1 },
|
||||
|
@@ -173,37 +173,39 @@ export const syncCollection = async (collection: CollectionWithDatasetType) => {
|
||||
|
||||
// Check if the original text is the same: skip if same
|
||||
const hashRawText = hashStr(rawText);
|
||||
if (collection.hashRawText && hashRawText === collection.hashRawText) {
|
||||
return DatasetCollectionSyncResultEnum.sameRaw;
|
||||
if (collection.hashRawText && hashRawText !== collection.hashRawText) {
|
||||
await mongoSessionRun(async (session) => {
|
||||
// Delete old collection
|
||||
await delCollection({
|
||||
collections: [collection],
|
||||
delImg: false,
|
||||
delFile: false,
|
||||
session
|
||||
});
|
||||
|
||||
// Create new collection
|
||||
await createCollectionAndInsertData({
|
||||
session,
|
||||
dataset,
|
||||
rawText: rawText,
|
||||
createCollectionParams: {
|
||||
...collection,
|
||||
name: title || collection.name,
|
||||
updateTime: new Date(),
|
||||
tags: await collectionTagsToTagLabel({
|
||||
datasetId: collection.datasetId,
|
||||
tags: collection.tags
|
||||
})
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return DatasetCollectionSyncResultEnum.success;
|
||||
} else if (collection.name !== title) {
|
||||
await MongoDatasetCollection.updateOne({ _id: collection._id }, { $set: { name: title } });
|
||||
return DatasetCollectionSyncResultEnum.success;
|
||||
}
|
||||
|
||||
await mongoSessionRun(async (session) => {
|
||||
// Delete old collection
|
||||
await delCollection({
|
||||
collections: [collection],
|
||||
delImg: false,
|
||||
delFile: false,
|
||||
session
|
||||
});
|
||||
|
||||
// Create new collection
|
||||
await createCollectionAndInsertData({
|
||||
session,
|
||||
dataset,
|
||||
rawText: rawText,
|
||||
createCollectionParams: {
|
||||
...collection,
|
||||
name: title || collection.name,
|
||||
updateTime: new Date(),
|
||||
tags: await collectionTagsToTagLabel({
|
||||
datasetId: collection.datasetId,
|
||||
tags: collection.tags
|
||||
})
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return DatasetCollectionSyncResultEnum.success;
|
||||
return DatasetCollectionSyncResultEnum.sameRaw;
|
||||
};
|
||||
|
||||
/*
|
||||
|
@@ -2,11 +2,11 @@ import { type Processor } from 'bullmq';
|
||||
import { getQueue, getWorker, QueueNames } from '../../../common/bullmq';
|
||||
import { DatasetStatusEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
|
||||
export type WebsiteSyncJobData = {
|
||||
export type DatasetSyncJobData = {
|
||||
datasetId: string;
|
||||
};
|
||||
|
||||
export const websiteSyncQueue = getQueue<WebsiteSyncJobData>(QueueNames.websiteSync, {
|
||||
export const datasetSyncQueue = getQueue<DatasetSyncJobData>(QueueNames.datasetSync, {
|
||||
defaultJobOptions: {
|
||||
attempts: 3, // retry 3 times
|
||||
backoff: {
|
||||
@@ -15,8 +15,8 @@ export const websiteSyncQueue = getQueue<WebsiteSyncJobData>(QueueNames.websiteS
|
||||
}
|
||||
}
|
||||
});
|
||||
export const getWebsiteSyncWorker = (processor: Processor<WebsiteSyncJobData>) => {
|
||||
return getWorker<WebsiteSyncJobData>(QueueNames.websiteSync, processor, {
|
||||
export const getDatasetSyncWorker = (processor: Processor<DatasetSyncJobData>) => {
|
||||
return getWorker<DatasetSyncJobData>(QueueNames.datasetSync, processor, {
|
||||
removeOnFail: {
|
||||
age: 15 * 24 * 60 * 60, // Keep up to 15 days
|
||||
count: 1000 // Keep up to 1000 jobs
|
||||
@@ -25,21 +25,21 @@ export const getWebsiteSyncWorker = (processor: Processor<WebsiteSyncJobData>) =
|
||||
});
|
||||
};
|
||||
|
||||
export const addWebsiteSyncJob = (data: WebsiteSyncJobData) => {
|
||||
export const addDatasetSyncJob = (data: DatasetSyncJobData) => {
|
||||
const datasetId = String(data.datasetId);
|
||||
// deduplication: make sure only 1 job
|
||||
return websiteSyncQueue.add(datasetId, data, { deduplication: { id: datasetId } });
|
||||
return datasetSyncQueue.add(datasetId, data, { deduplication: { id: datasetId } });
|
||||
};
|
||||
|
||||
export const getWebsiteSyncDatasetStatus = async (datasetId: string) => {
|
||||
const jobId = await websiteSyncQueue.getDeduplicationJobId(datasetId);
|
||||
export const getDatasetSyncDatasetStatus = async (datasetId: string) => {
|
||||
const jobId = await datasetSyncQueue.getDeduplicationJobId(datasetId);
|
||||
if (!jobId) {
|
||||
return {
|
||||
status: DatasetStatusEnum.active,
|
||||
errorMsg: undefined
|
||||
};
|
||||
}
|
||||
const job = await websiteSyncQueue.getJob(jobId);
|
||||
const job = await datasetSyncQueue.getJob(jobId);
|
||||
if (!job) {
|
||||
return {
|
||||
status: DatasetStatusEnum.active,
|
||||
@@ -76,10 +76,10 @@ export const getWebsiteSyncDatasetStatus = async (datasetId: string) => {
|
||||
|
||||
// Scheduler setting
|
||||
const repeatDuration = 24 * 60 * 60 * 1000; // every day
|
||||
export const upsertWebsiteSyncJobScheduler = (data: WebsiteSyncJobData, startDate?: number) => {
|
||||
export const upsertDatasetSyncJobScheduler = (data: DatasetSyncJobData, startDate?: number) => {
|
||||
const datasetId = String(data.datasetId);
|
||||
|
||||
return websiteSyncQueue.upsertJobScheduler(
|
||||
return datasetSyncQueue.upsertJobScheduler(
|
||||
datasetId,
|
||||
{
|
||||
every: repeatDuration,
|
||||
@@ -92,10 +92,10 @@ export const upsertWebsiteSyncJobScheduler = (data: WebsiteSyncJobData, startDat
|
||||
);
|
||||
};
|
||||
|
||||
export const getWebsiteSyncJobScheduler = (datasetId: string) => {
|
||||
return websiteSyncQueue.getJobScheduler(String(datasetId));
|
||||
export const getDatasetSyncJobScheduler = (datasetId: string) => {
|
||||
return datasetSyncQueue.getJobScheduler(String(datasetId));
|
||||
};
|
||||
|
||||
export const removeWebsiteSyncJobScheduler = (datasetId: string) => {
|
||||
return websiteSyncQueue.removeJobScheduler(String(datasetId));
|
||||
export const removeDatasetSyncJobScheduler = (datasetId: string) => {
|
||||
return datasetSyncQueue.removeJobScheduler(String(datasetId));
|
||||
};
|
@@ -119,7 +119,7 @@ export const checkTeamDatasetLimit = async (teamId: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const checkTeamWebSyncPermission = async (teamId: string) => {
|
||||
export const checkTeamDatasetSyncPermission = async (teamId: string) => {
|
||||
const { standardConstants } = await getTeamStandPlan({
|
||||
teamId
|
||||
});
|
||||
|
@@ -41,7 +41,7 @@ export const useSelectFile = (props?: {
|
||||
/>
|
||||
</Box>
|
||||
),
|
||||
[fileType, maxCount, multiple, toast]
|
||||
[fileType, maxCount, multiple, t, toast]
|
||||
);
|
||||
|
||||
const onOpen = useCallback((sign?: any) => {
|
||||
|
@@ -192,7 +192,7 @@ const MultipleSelect = <T = any,>({
|
||||
bg={'primary.100'}
|
||||
color={'primary.700'}
|
||||
type={'fill'}
|
||||
borderRadius={'full'}
|
||||
borderRadius={'lg'}
|
||||
px={2}
|
||||
py={0.5}
|
||||
flexShrink={0}
|
||||
|
@@ -54,6 +54,9 @@ export type SelectProps<T = any> = Omit<ButtonProps, 'onChange'> & {
|
||||
ScrollData?: ReturnType<typeof useScrollPagination>['ScrollData'];
|
||||
customOnOpen?: () => void;
|
||||
customOnClose?: () => void;
|
||||
|
||||
isInvalid?: boolean;
|
||||
isDisabled?: boolean;
|
||||
};
|
||||
|
||||
export const menuItemStyles: MenuItemProps = {
|
||||
@@ -82,6 +85,8 @@ const MySelect = <T = any,>(
|
||||
ScrollData,
|
||||
customOnOpen,
|
||||
customOnClose,
|
||||
isInvalid,
|
||||
isDisabled,
|
||||
...props
|
||||
}: SelectProps<T>,
|
||||
ref: ForwardedRef<{
|
||||
@@ -213,16 +218,31 @@ const MySelect = <T = any,>(
|
||||
h={'auto'}
|
||||
whiteSpace={'pre-wrap'}
|
||||
wordBreak={'break-word'}
|
||||
transition={'border-color 0.1s ease-in-out, box-shadow 0.1s ease-in-out'}
|
||||
isDisabled={isDisabled}
|
||||
_active={{
|
||||
transform: 'none'
|
||||
}}
|
||||
{...(isOpen
|
||||
? {
|
||||
boxShadow: '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)',
|
||||
borderColor: 'primary.600',
|
||||
color: 'primary.700'
|
||||
}
|
||||
: {})}
|
||||
color={isOpen ? 'primary.700' : 'myGray.700'}
|
||||
borderColor={isInvalid ? 'red.500' : isOpen ? 'primary.300' : 'myGray.200'}
|
||||
boxShadow={
|
||||
isOpen
|
||||
? isInvalid
|
||||
? '0px 0px 0px 2.4px rgba(255, 0, 0, 0.15)'
|
||||
: '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)'
|
||||
: 'none'
|
||||
}
|
||||
_hover={
|
||||
isInvalid
|
||||
? {
|
||||
borderColor: 'red.400',
|
||||
boxShadow: '0px 0px 0px 2.4px rgba(255, 0, 0, 0.15)'
|
||||
}
|
||||
: {
|
||||
borderColor: 'primary.300',
|
||||
boxShadow: '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)'
|
||||
}
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
<Flex alignItems={'center'} justifyContent="space-between" w="100%">
|
||||
|
@@ -230,12 +230,24 @@ const JSONEditor = ({
|
||||
|
||||
return (
|
||||
<Box
|
||||
borderWidth={isInvalid ? '2px' : '1px'}
|
||||
borderRadius={'md'}
|
||||
borderWidth={'1px'}
|
||||
borderRadius={'sm'}
|
||||
borderColor={isInvalid ? 'red.500' : 'myGray.200'}
|
||||
py={2}
|
||||
height={height}
|
||||
position={'relative'}
|
||||
transition={'border-color 0.1s ease-in-out, box-shadow 0.1s ease-in-out'}
|
||||
_focusWithin={
|
||||
isInvalid
|
||||
? {
|
||||
borderColor: 'red.500',
|
||||
boxShadow: '0px 0px 0px 2.4px rgba(244, 69, 46, 0.15)'
|
||||
}
|
||||
: {
|
||||
borderColor: 'primary.600',
|
||||
boxShadow: '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)'
|
||||
}
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
{resize && (
|
||||
@@ -291,6 +303,19 @@ const JSONEditor = ({
|
||||
>
|
||||
{placeholder}
|
||||
</Box>
|
||||
{isDisabled && (
|
||||
<Box
|
||||
position="absolute"
|
||||
top={0}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
bg="rgba(255, 255, 255, 0.4)"
|
||||
borderRadius="sm"
|
||||
zIndex={1}
|
||||
cursor="not-allowed"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@@ -21,6 +21,7 @@ import { VariableNode } from './plugins/VariablePlugin/node';
|
||||
import type { EditorState, LexicalEditor } from 'lexical';
|
||||
import OnBlurPlugin from './plugins/OnBlurPlugin';
|
||||
import MyIcon from '../../Icon';
|
||||
import type { FormPropsType } from './type.d';
|
||||
import { type EditorVariableLabelPickerType, type EditorVariablePickerType } from './type.d';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import FocusPlugin from './plugins/FocusPlugin';
|
||||
@@ -43,7 +44,9 @@ export default function Editor({
|
||||
onBlur,
|
||||
value,
|
||||
placeholder = '',
|
||||
bg = 'white'
|
||||
bg = 'white',
|
||||
|
||||
isInvalid
|
||||
}: {
|
||||
minH?: number;
|
||||
maxH?: number;
|
||||
@@ -56,8 +59,9 @@ export default function Editor({
|
||||
onBlur?: (editor: LexicalEditor) => void;
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
bg?: string;
|
||||
}) {
|
||||
|
||||
isInvalid?: boolean;
|
||||
} & FormPropsType) {
|
||||
const [key, setKey] = useState(getNanoid(6));
|
||||
const [_, startSts] = useTransition();
|
||||
const [focus, setFocus] = useState(false);
|
||||
@@ -91,7 +95,7 @@ export default function Editor({
|
||||
<PlainTextPlugin
|
||||
contentEditable={
|
||||
<ContentEditable
|
||||
className={styles.contentEditable}
|
||||
className={isInvalid ? styles.contentEditable_invalid : styles.contentEditable}
|
||||
style={{
|
||||
minHeight: `${minH}px`,
|
||||
maxHeight: `${maxH}px`
|
||||
|
@@ -3,13 +3,17 @@
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: 1px solid rgb(232, 235, 240);
|
||||
border-radius: var(--chakra-radii-md);
|
||||
border-radius: var(--chakra-radii-sm);
|
||||
padding: 8px 12px;
|
||||
// background: #fff;
|
||||
|
||||
font-size: var(--chakra-fontSizes-sm);
|
||||
overflow-y: auto;
|
||||
|
||||
transition:
|
||||
border-color 0.1s ease-in-out,
|
||||
box-shadow 0.1s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--chakra-colors-primary-300);
|
||||
}
|
||||
@@ -32,6 +36,42 @@
|
||||
box-shadow: 0px 0px 0px 2.4px rgba(51, 112, 255, 0.15);
|
||||
}
|
||||
|
||||
.contentEditable_invalid {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: 1px solid rgb(232, 235, 240);
|
||||
border-radius: var(--chakra-radii-sm);
|
||||
padding: 8px 12px;
|
||||
|
||||
font-size: var(--chakra-fontSizes-sm);
|
||||
overflow-y: auto;
|
||||
|
||||
transition:
|
||||
border-color 0.1s ease-in-out,
|
||||
box-shadow 0.1s ease-in-out;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
color: var(--chakra-colors-myGray-100);
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: var(--chakra-colors-myGray-200) !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background-color: var(--chakra-colors-myGray-250) !important;
|
||||
}
|
||||
|
||||
border-color: var(--chakra-colors-red-500);
|
||||
}
|
||||
|
||||
.contentEditable_invalid:focus {
|
||||
outline: none;
|
||||
border: 1px solid;
|
||||
border-color: var(--chakra-colors-red-600);
|
||||
box-shadow: 0px 0px 0px 2.4px rgba(244, 69, 46, 0.15);
|
||||
}
|
||||
|
||||
.variable {
|
||||
color: var(--chakra-colors-primary-600);
|
||||
padding: 0 2px;
|
||||
|
@@ -1,10 +1,12 @@
|
||||
import { Button, ModalBody, ModalFooter, useDisclosure } from '@chakra-ui/react';
|
||||
import type { BoxProps } from '@chakra-ui/react';
|
||||
import { Box, Button, ModalBody, ModalFooter, useDisclosure } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import { editorStateToText } from './utils';
|
||||
import Editor from './Editor';
|
||||
import MyModal from '../../MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import type { EditorState, LexicalEditor } from 'lexical';
|
||||
import type { FormPropsType } from './type.d';
|
||||
import { type EditorVariableLabelPickerType, type EditorVariablePickerType } from './type.d';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
@@ -20,7 +22,9 @@ const PromptEditor = ({
|
||||
maxLength,
|
||||
placeholder,
|
||||
title,
|
||||
bg = 'white'
|
||||
isInvalid,
|
||||
isDisabled,
|
||||
...props
|
||||
}: {
|
||||
showOpenModal?: boolean;
|
||||
variables?: EditorVariablePickerType[];
|
||||
@@ -33,8 +37,10 @@ const PromptEditor = ({
|
||||
maxLength?: number;
|
||||
placeholder?: string;
|
||||
title?: string;
|
||||
bg?: string;
|
||||
}) => {
|
||||
|
||||
isInvalid?: boolean;
|
||||
isDisabled?: boolean;
|
||||
} & FormPropsType) => {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -55,20 +61,36 @@ const PromptEditor = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Editor
|
||||
showOpenModal={showOpenModal}
|
||||
onOpenModal={onOpen}
|
||||
variables={variables}
|
||||
variableLabels={variableLabels}
|
||||
minH={minH}
|
||||
maxH={maxH}
|
||||
maxLength={maxLength}
|
||||
value={value}
|
||||
onChange={onChangeInput}
|
||||
onBlur={onBlurInput}
|
||||
placeholder={placeholder}
|
||||
bg={bg}
|
||||
/>
|
||||
<Box position="relative">
|
||||
<Editor
|
||||
showOpenModal={showOpenModal}
|
||||
onOpenModal={onOpen}
|
||||
variables={variables}
|
||||
variableLabels={variableLabels}
|
||||
minH={minH}
|
||||
maxH={maxH}
|
||||
maxLength={maxLength}
|
||||
value={value}
|
||||
onChange={onChangeInput}
|
||||
onBlur={onBlurInput}
|
||||
placeholder={placeholder}
|
||||
isInvalid={isInvalid}
|
||||
{...props}
|
||||
/>
|
||||
{isDisabled && (
|
||||
<Box
|
||||
position="absolute"
|
||||
top={0}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
bg="rgba(255, 255, 255, 0.4)"
|
||||
borderRadius="md"
|
||||
zIndex={1}
|
||||
cursor="not-allowed"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
<MyModal isOpen={isOpen} onClose={onClose} iconSrc="modal/edit" title={title} w={'full'}>
|
||||
<ModalBody>
|
||||
<Editor
|
||||
|
@@ -21,3 +21,5 @@ export type EditorVariableLabelPickerType = {
|
||||
avatar?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type FormPropsType = Omit<BoxProps, 'onChange' | 'onBlur'>;
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import { useBoolean } from 'ahooks';
|
||||
|
||||
export const useRefresh = () => {
|
||||
const [_, { toggle }] = useBoolean();
|
||||
const [refreshSign, { toggle }] = useBoolean();
|
||||
|
||||
return {
|
||||
refresh: toggle
|
||||
refresh: toggle,
|
||||
refreshSign
|
||||
};
|
||||
};
|
||||
|
23
packages/web/hooks/useSafeTranslation.ts
Normal file
23
packages/web/hooks/useSafeTranslation.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useTranslation as useNextTranslation } from 'next-i18next';
|
||||
import type { I18nNsType } from '../i18n/i18next';
|
||||
import { I18N_NAMESPACES_MAP } from '../i18n/constants';
|
||||
|
||||
export function useTranslation(ns?: I18nNsType[0] | I18nNsType) {
|
||||
const { t: originalT, ...rest } = useNextTranslation(ns);
|
||||
|
||||
const t = (key: string | undefined, ...args: any[]): string => {
|
||||
if (!key) return '';
|
||||
|
||||
if (!I18N_NAMESPACES_MAP[key as any]) {
|
||||
return key;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
return originalT(key, ...args);
|
||||
};
|
||||
|
||||
return {
|
||||
t,
|
||||
...rest
|
||||
};
|
||||
}
|
31
packages/web/i18n/constants.ts
Normal file
31
packages/web/i18n/constants.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
export const I18N_NAMESPACES = [
|
||||
'common',
|
||||
'dataset',
|
||||
'app',
|
||||
'file',
|
||||
'publish',
|
||||
'workflow',
|
||||
'user',
|
||||
'chat',
|
||||
'login',
|
||||
'account_info',
|
||||
'account_usage',
|
||||
'account_bill',
|
||||
'account_apikey',
|
||||
'account_setting',
|
||||
'account_inform',
|
||||
'account_promotion',
|
||||
'account_thirdParty',
|
||||
'account',
|
||||
'account_team',
|
||||
'account_model',
|
||||
'dashboard_mcp'
|
||||
];
|
||||
|
||||
export const I18N_NAMESPACES_MAP = I18N_NAMESPACES.reduce(
|
||||
(acc, namespace) => {
|
||||
acc[namespace] = true;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, boolean>
|
||||
);
|
@@ -77,6 +77,7 @@
|
||||
"select_file_img": "Upload file / image",
|
||||
"select_img": "Upload Image",
|
||||
"source_cronJob": "Scheduled execution",
|
||||
"start_chat": "Start",
|
||||
"stream_output": "Stream Output",
|
||||
"unsupported_file_type": "Unsupported file types",
|
||||
"upload": "Upload",
|
||||
|
@@ -77,7 +77,6 @@
|
||||
"Save": "Save",
|
||||
"Save_and_exit": "Save and Exit",
|
||||
"Search": "Search",
|
||||
"Select_all": "Select all",
|
||||
"Setting": "Setting",
|
||||
"Status": "Status",
|
||||
"Submit": "Submit",
|
||||
@@ -355,7 +354,6 @@
|
||||
"core.chat.Select dataset Desc": "Select a Dataset to store the expected answer",
|
||||
"core.chat.Send Message": "Send",
|
||||
"core.chat.Speaking": "I'm Listening, Please Speak...",
|
||||
"core.chat.Start Chat": "Start Chat",
|
||||
"core.chat.Type a message": "Enter a Question, Press [Enter] to Send / Press [Ctrl(Alt/Shift) + Enter] for New Line",
|
||||
"core.chat.Unpin": "Unpin",
|
||||
"core.chat.You need to a chat app": "You Do Not Have an Available App",
|
||||
|
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"Enable": "Enable",
|
||||
"Select_all": "Select all files",
|
||||
"add_file": "Import",
|
||||
"api_file": "API Dataset",
|
||||
"api_url": "API Url",
|
||||
@@ -24,6 +25,7 @@
|
||||
"close_auto_sync": "Are you sure you want to turn off automatic sync?",
|
||||
"collection.Create update time": "Creation/Update Time",
|
||||
"collection.Training type": "Training",
|
||||
"collection.sync.submit": "The synchronization task has been submitted",
|
||||
"collection.training_type": "Chunk type",
|
||||
"collection_data_count": "Data amount",
|
||||
"collection_metadata_custom_pdf_parse": "PDF enhancement analysis",
|
||||
@@ -147,6 +149,7 @@
|
||||
"pleaseFillUserIdAndToken": "Please fill in User ID and Token",
|
||||
"preview_chunk": "Preview chunks",
|
||||
"preview_chunk_empty": "File content is empty",
|
||||
"preview_chunk_folder_warning": "Directory does not support preview",
|
||||
"preview_chunk_intro": "A total of {{total}} blocks, up to 10",
|
||||
"preview_chunk_not_selected": "Click on the file on the left to preview",
|
||||
"process.Auto_Index": "Automatic index generation",
|
||||
@@ -178,7 +181,7 @@
|
||||
"split_sign_period": "period",
|
||||
"split_sign_question": "question mark",
|
||||
"split_sign_semicolon": "semicolon",
|
||||
"start_sync_website_tip": "Confirm to start synchronizing data? \nThe old data will be deleted and retrieved again, please confirm!",
|
||||
"start_sync_dataset_tip": "Do you really start synchronizing the entire knowledge base?",
|
||||
"status_error": "Running exception",
|
||||
"sync_collection_failed": "Synchronization collection error, please check whether the source file can be accessed normally",
|
||||
"sync_schedule": "Timing synchronization",
|
||||
|
65
packages/web/i18n/i18next.d.ts
vendored
Normal file
65
packages/web/i18n/i18next.d.ts
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
import 'i18next';
|
||||
import type account_team from './zh-CN/account_team.json';
|
||||
import type account from './zh-CN/account.json';
|
||||
import type account_thirdParty from './zh-CN/account_thirdParty.json';
|
||||
import type account_promotion from './zh-CN/account_promotion.json';
|
||||
import type account_inform from './zh-CN/account_inform.json';
|
||||
import type account_setting from './zh-CN/account_setting.json';
|
||||
import type account_apikey from './zh-CN/account_apikey.json';
|
||||
import type account_bill from './zh-CN/account_bill.json';
|
||||
import type account_usage from './zh-CN/account_usage.json';
|
||||
import type account_info from './zh-CN/account_info.json';
|
||||
import type common from './zh-CN/common.json';
|
||||
import type dataset from './zh-CN/dataset.json';
|
||||
import type app from './zh-CN/app.json';
|
||||
import type file from './zh-CN/file.json';
|
||||
import type publish from './zh-CN/publish.json';
|
||||
import type workflow from './zh-CN/workflow.json';
|
||||
import type user from './zh-CN/user.json';
|
||||
import type chat from './zh-CN/chat.json';
|
||||
import type login from './zh-CN/login.json';
|
||||
import type account_model from './zh-CN/account_model.json';
|
||||
import type dashboard_mcp from './zh-CN/dashboard_mcp.json';
|
||||
import type { I18N_NAMESPACES } from './constants';
|
||||
|
||||
export interface I18nNamespaces {
|
||||
common: typeof common;
|
||||
dataset: typeof dataset;
|
||||
app: typeof app;
|
||||
file: typeof file;
|
||||
publish: typeof publish;
|
||||
workflow: typeof workflow;
|
||||
user: typeof user;
|
||||
chat: typeof chat;
|
||||
login: typeof login;
|
||||
account_info: typeof account_info;
|
||||
account_usage: typeof account_usage;
|
||||
account_bill: typeof account_bill;
|
||||
account_apikey: typeof account_apikey;
|
||||
account_setting: typeof account_setting;
|
||||
account_inform: typeof account_inform;
|
||||
account_promotion: typeof account_promotion;
|
||||
account: typeof account;
|
||||
account_team: typeof account_team;
|
||||
account_thirdParty: typeof account_thirdParty;
|
||||
account_model: typeof account_model;
|
||||
dashboard_mcp: typeof dashboard_mcp;
|
||||
}
|
||||
|
||||
export type I18nNsType = (keyof I18nNamespaces)[];
|
||||
|
||||
export type ParseKeys<Ns extends keyof I18nNamespaces = keyof I18nNamespaces> = {
|
||||
[K in Ns]: `${K}:${keyof I18nNamespaces[K] & string}`;
|
||||
}[Ns];
|
||||
|
||||
export type I18nKeyFunction = {
|
||||
<Key extends ParseKeys>(key: Key): Key;
|
||||
};
|
||||
|
||||
declare module 'i18next' {
|
||||
interface CustomTypeOptions {
|
||||
returnNull: false;
|
||||
defaultNS: I18N_NAMESPACES;
|
||||
resources: I18nNamespaces;
|
||||
}
|
||||
}
|
@@ -1,3 +1,3 @@
|
||||
import { type I18nKeyFunction } from '../types/i18next';
|
||||
import { type I18nKeyFunction } from './i18next';
|
||||
|
||||
export const i18nT: I18nKeyFunction = (key) => key;
|
||||
|
@@ -109,7 +109,7 @@
|
||||
"join_update_time": "加入/更新时间",
|
||||
"kick_out_team": "移除成员",
|
||||
"label_sync": "标签同步",
|
||||
"leave": "已离职",
|
||||
"leave": "离开",
|
||||
"leave_team_failed": "离开团队异常",
|
||||
"log_admin_add_plan": "【{{name}}】将给团队id为【{{teamId}}】的团队添加了套餐",
|
||||
"log_admin_add_user": "【{{name}}】创建了一个名为【{{userName}}】的用户",
|
||||
@@ -218,7 +218,7 @@
|
||||
"recover_team_member": "成员恢复",
|
||||
"relocate_department": "部门移动",
|
||||
"remark": "备注",
|
||||
"remove_tip": "确认将 {{username}} 移出团队?成员将被标记为“已离职”,不删除操作数据,账号下资源自动转让给团队所有者。",
|
||||
"remove_tip": "确认将 {{username}} 移出团队?成员将被标记为“离开”,不删除操作数据,账号下资源自动转让给团队所有者。",
|
||||
"restore_tip": "确认将 {{username}} 加入团队吗?仅恢复该成员账号可用性及相关权限,无法恢复账号下资源。",
|
||||
"restore_tip_title": "恢复确认",
|
||||
"retain_admin_permissions": "保留管理员权限",
|
||||
|
@@ -77,6 +77,7 @@
|
||||
"select_file_img": "上传文件/图片",
|
||||
"select_img": "上传图片",
|
||||
"source_cronJob": "定时执行",
|
||||
"start_chat": "开始对话",
|
||||
"stream_output": "流输出",
|
||||
"unsupported_file_type": "不支持的文件类型",
|
||||
"upload": "上传",
|
||||
|
@@ -77,7 +77,6 @@
|
||||
"Save": "保存",
|
||||
"Save_and_exit": "保存并退出",
|
||||
"Search": "搜索",
|
||||
"Select_all": "全选",
|
||||
"Setting": "设置",
|
||||
"Status": "状态",
|
||||
"Submit": "提交",
|
||||
@@ -355,7 +354,6 @@
|
||||
"core.chat.Select dataset Desc": "选择一个知识库存储预期答案",
|
||||
"core.chat.Send Message": "发送",
|
||||
"core.chat.Speaking": "我在听,请说...",
|
||||
"core.chat.Start Chat": "开始对话",
|
||||
"core.chat.Type a message": "输入问题,发送 [Enter]/换行 [Ctrl(Alt/Shift) + Enter]",
|
||||
"core.chat.Unpin": "取消置顶",
|
||||
"core.chat.You need to a chat app": "你没有可用的应用",
|
||||
@@ -1301,7 +1299,7 @@
|
||||
"user.team.role.Visitor": "访客",
|
||||
"user.team.role.writer": "可写成员",
|
||||
"user.type": "类型",
|
||||
"user_leaved": "已离开",
|
||||
"user_leaved": "离开",
|
||||
"value": "值",
|
||||
"verification": "验证",
|
||||
"xx_search_result": "{{key}} 的搜索结果",
|
||||
|
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"Enable": "启用",
|
||||
"Select_all": "选择所有文件",
|
||||
"add_file": "添加文件",
|
||||
"api_file": "API 文件库",
|
||||
"api_url": "接口地址",
|
||||
@@ -24,6 +25,7 @@
|
||||
"close_auto_sync": "确认关闭自动同步功能?",
|
||||
"collection.Create update time": "创建/更新时间",
|
||||
"collection.Training type": "训练模式",
|
||||
"collection.sync.submit": "已提交同步任务",
|
||||
"collection.training_type": "处理模式",
|
||||
"collection_data_count": "数据量",
|
||||
"collection_metadata_custom_pdf_parse": "PDF增强解析",
|
||||
@@ -147,6 +149,7 @@
|
||||
"pleaseFillUserIdAndToken": "请填写 User ID 和 Token",
|
||||
"preview_chunk": "分块预览",
|
||||
"preview_chunk_empty": "文件内容为空",
|
||||
"preview_chunk_folder_warning": "目录不支持预览",
|
||||
"preview_chunk_intro": "共 {{total}} 个分块,最多展示 10 个",
|
||||
"preview_chunk_not_selected": "点击左侧文件后进行预览",
|
||||
"process.Auto_Index": "自动索引生成",
|
||||
@@ -178,7 +181,7 @@
|
||||
"split_sign_period": "句号",
|
||||
"split_sign_question": "问号",
|
||||
"split_sign_semicolon": "分号",
|
||||
"start_sync_website_tip": "确认开始同步数据?将会删除旧数据后重新获取,请确认!",
|
||||
"start_sync_dataset_tip": "确实开始同步整个知识库?",
|
||||
"status_error": "运行异常",
|
||||
"sync_collection_failed": "同步集合错误,请检查是否能正常访问源文件",
|
||||
"sync_schedule": "定时同步",
|
||||
|
@@ -77,6 +77,7 @@
|
||||
"select_file_img": "上傳檔案 / 圖片",
|
||||
"select_img": "上傳圖片",
|
||||
"source_cronJob": "定時執行",
|
||||
"start_chat": "開始對話",
|
||||
"stream_output": "串流輸出",
|
||||
"unsupported_file_type": "不支援的檔案類型",
|
||||
"upload": "上傳",
|
||||
|
@@ -77,7 +77,6 @@
|
||||
"Save": "儲存",
|
||||
"Save_and_exit": "儲存並離開",
|
||||
"Search": "搜尋",
|
||||
"Select_all": "全選",
|
||||
"Setting": "設定",
|
||||
"Status": "狀態",
|
||||
"Submit": "送出",
|
||||
@@ -355,7 +354,6 @@
|
||||
"core.chat.Select dataset Desc": "選擇一個知識庫來儲存預期回答",
|
||||
"core.chat.Send Message": "傳送",
|
||||
"core.chat.Speaking": "我在聽,請說...",
|
||||
"core.chat.Start Chat": "開始對話",
|
||||
"core.chat.Type a message": "輸入問題,按 [Enter] 傳送 / 按 [Ctrl(Alt/Shift) + Enter] 換行",
|
||||
"core.chat.Unpin": "取消釘選",
|
||||
"core.chat.You need to a chat app": "您沒有可用的應用程式",
|
||||
|
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"Enable": "啟用",
|
||||
"Select_all": "選中所有檔案",
|
||||
"add_file": "新增文件",
|
||||
"api_file": "API 檔案庫",
|
||||
"api_url": "介面位址",
|
||||
@@ -24,6 +25,7 @@
|
||||
"close_auto_sync": "確認關閉自動同步功能?",
|
||||
"collection.Create update time": "建立/更新時間",
|
||||
"collection.Training type": "分段模式",
|
||||
"collection.sync.submit": "已提交同步任務",
|
||||
"collection.training_type": "處理模式",
|
||||
"collection_data_count": "資料量",
|
||||
"collection_metadata_custom_pdf_parse": "PDF 增強解析",
|
||||
@@ -147,6 +149,7 @@
|
||||
"pleaseFillUserIdAndToken": "請填寫 User ID 和 Token",
|
||||
"preview_chunk": "分塊預覽",
|
||||
"preview_chunk_empty": "文件內容為空",
|
||||
"preview_chunk_folder_warning": "目錄不支持預覽",
|
||||
"preview_chunk_intro": "共 {{total}} 個分塊,最多展示 10 個",
|
||||
"preview_chunk_not_selected": "點選左側文件後進行預覽",
|
||||
"process.Auto_Index": "自動索引生成",
|
||||
@@ -178,7 +181,7 @@
|
||||
"split_sign_period": "句號",
|
||||
"split_sign_question": "問號",
|
||||
"split_sign_semicolon": "分號",
|
||||
"start_sync_website_tip": "確認開始同步資料?\n將會刪除舊資料後重新取得,請確認!",
|
||||
"start_sync_dataset_tip": "確實開始同步整個知識庫?",
|
||||
"status_error": "執行異常",
|
||||
"sync_collection_failed": "同步集合錯誤,請檢查是否能正常存取來原始檔",
|
||||
"sync_schedule": "定時同步",
|
||||
|
@@ -382,14 +382,31 @@ const NumberInput = numInputMultiStyle({
|
||||
bg: 'myGray.50',
|
||||
border: '1px solid',
|
||||
borderColor: 'myGray.200',
|
||||
borderRadius: 'sm',
|
||||
transition: 'border-color 0.1s ease-in-out, box-shadow 0.1s ease-in-out',
|
||||
_hover: {
|
||||
borderColor: 'primary.300'
|
||||
},
|
||||
_focus: {
|
||||
borderColor: 'primary.500 !important',
|
||||
borderColor: 'primary.600 !important',
|
||||
boxShadow: `${shadowLight} !important`,
|
||||
bg: 'white'
|
||||
},
|
||||
_disabled: {
|
||||
color: 'myGray.400 !important',
|
||||
bg: 'myWhite.300 !important'
|
||||
},
|
||||
_invalid: {
|
||||
borderColor: 'red.500 !important',
|
||||
borderWidth: '1px !important',
|
||||
boxShadow: 'none !important',
|
||||
_hover: {
|
||||
borderColor: 'red.400 !important'
|
||||
},
|
||||
_focus: {
|
||||
borderColor: 'red.600 !important',
|
||||
boxShadow: '0px 0px 0px 2.4px rgba(244, 69, 46, 0.15) !important'
|
||||
}
|
||||
}
|
||||
},
|
||||
stepper: {
|
||||
|
86
packages/web/types/i18next.d.ts
vendored
86
packages/web/types/i18next.d.ts
vendored
@@ -1,86 +0,0 @@
|
||||
import 'i18next';
|
||||
import type account_team from '../i18n/zh-CN/account_team.json';
|
||||
import type account from '../i18n/zh-CN/account.json';
|
||||
import type account_thirdParty from '../i18n/zh-CN/account_thirdParty.json';
|
||||
import type account_promotion from '../i18n/zh-CN/account_promotion.json';
|
||||
import type account_inform from '../i18n/zh-CN/account_inform.json';
|
||||
import type account_setting from '../i18n/zh-CN/account_setting.json';
|
||||
import type account_apikey from '../i18n/zh-CN/account_apikey.json';
|
||||
import type account_bill from '../i18n/zh-CN/account_bill.json';
|
||||
import type account_usage from '../i18n/zh-CN/account_usage.json';
|
||||
import type account_info from '../i18n/zh-CN/account_info.json';
|
||||
import type common from '../i18n/zh-CN/common.json';
|
||||
import type dataset from '../i18n/zh-CN/dataset.json';
|
||||
import type app from '../i18n/zh-CN/app.json';
|
||||
import type file from '../i18n/zh-CN/file.json';
|
||||
import type publish from '../i18n/zh-CN/publish.json';
|
||||
import type workflow from '../i18n/zh-CN/workflow.json';
|
||||
import type user from '../i18n/zh-CN/user.json';
|
||||
import type chat from '../i18n/zh-CN/chat.json';
|
||||
import type login from '../i18n/zh-CN/login.json';
|
||||
import type account_model from '../i18n/zh-CN/account_model.json';
|
||||
import type dashboard_mcp from '../i18n/zh-CN/dashboard_mcp.json';
|
||||
|
||||
export interface I18nNamespaces {
|
||||
common: typeof common;
|
||||
dataset: typeof dataset;
|
||||
app: typeof app;
|
||||
file: typeof file;
|
||||
publish: typeof publish;
|
||||
workflow: typeof workflow;
|
||||
user: typeof user;
|
||||
chat: typeof chat;
|
||||
login: typeof login;
|
||||
account_info: typeof account_info;
|
||||
account_usage: typeof account_usage;
|
||||
account_bill: typeof account_bill;
|
||||
account_apikey: typeof account_apikey;
|
||||
account_setting: typeof account_setting;
|
||||
account_inform: typeof account_inform;
|
||||
account_promotion: typeof account_promotion;
|
||||
account: typeof account;
|
||||
account_team: typeof account_team;
|
||||
account_thirdParty: typeof account_thirdParty;
|
||||
account_model: typeof account_model;
|
||||
dashboard_mcp: typeof dashboard_mcp;
|
||||
}
|
||||
|
||||
export type I18nNsType = (keyof I18nNamespaces)[];
|
||||
|
||||
export type ParseKeys<Ns extends keyof I18nNamespaces = keyof I18nNamespaces> = {
|
||||
[K in Ns]: `${K}:${keyof I18nNamespaces[K] & string}`;
|
||||
}[Ns];
|
||||
|
||||
export type I18nKeyFunction = {
|
||||
<Key extends ParseKeys>(key: Key): Key;
|
||||
};
|
||||
|
||||
declare module 'i18next' {
|
||||
interface CustomTypeOptions {
|
||||
returnNull: false;
|
||||
defaultNS: [
|
||||
'common',
|
||||
'dataset',
|
||||
'app',
|
||||
'file',
|
||||
'publish',
|
||||
'workflow',
|
||||
'user',
|
||||
'chat',
|
||||
'login',
|
||||
'account_info',
|
||||
'account_usage',
|
||||
'account_bill',
|
||||
'account_apikey',
|
||||
'account_setting',
|
||||
'account_inform',
|
||||
'account_promotion',
|
||||
'account_thirdParty',
|
||||
'account',
|
||||
'account_team',
|
||||
'account_model',
|
||||
'dashboard_mcp'
|
||||
];
|
||||
resources: I18nNamespaces;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user