diff --git a/.gitignore b/.gitignore index 0275e3f353b4..5e8c01633fb6 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,5 @@ files/helm/fastgpt/charts/*.tgz tmp/ coverage -document/.source \ No newline at end of file +document/.source +AGENTS.md \ No newline at end of file diff --git a/deploy/docker/docker-compose-milvus.yml b/deploy/docker/docker-compose-milvus.yml index 46a539adb080..5d25763de970 100644 --- a/deploy/docker/docker-compose-milvus.yml +++ b/deploy/docker/docker-compose-milvus.yml @@ -185,6 +185,8 @@ services: - AIPROXY_API_ENDPOINT=http://aiproxy:3000 # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY - AIPROXY_API_TOKEN=aiproxy + # diting 地址 + - DITING_BASE_URL=http://diting:3000 # 数据库最大连接数 - DB_MAX_LINK=30 @@ -298,5 +300,23 @@ services: interval: 5s timeout: 5s retries: 10 + + # diting + diting: + image: ghcr.io/labring/diting:v0.1.0 + container_name: diting + restart: unless-stopped + networks: + - fastgpt + environment: + # 对应fastgpt中的aiproxy的AIPROXY_API_ENDPOINT + - AIPROXY_API_ENDPOINT=http://aiproxy:3000 + # 对应fastgpt中的aiproxy的AIPROXY_API_TOKEN + - AIPROXY_API_TOKEN=aiproxy + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/api/v1/healthz'] + interval: 5s + timeout: 5s + retries: 10 networks: fastgpt: diff --git a/deploy/docker/docker-compose-oceanbase/docker-compose.yml b/deploy/docker/docker-compose-oceanbase/docker-compose.yml index 8ed69f5c058c..e5c6322a2d98 100644 --- a/deploy/docker/docker-compose-oceanbase/docker-compose.yml +++ b/deploy/docker/docker-compose-oceanbase/docker-compose.yml @@ -160,6 +160,8 @@ services: - AIPROXY_API_ENDPOINT=http://aiproxy:3000 # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY - AIPROXY_API_TOKEN=aiproxy + # diting 地址 + - DITING_BASE_URL=http://diting:3000 # 数据库最大连接数 - DB_MAX_LINK=30 @@ -272,5 +274,23 @@ services: interval: 5s timeout: 5s retries: 10 + + # diting + diting: + image: ghcr.io/labring/diting:v0.1.0 + container_name: diting + restart: unless-stopped + networks: + - fastgpt + environment: + # 对应fastgpt中的aiproxy的AIPROXY_API_ENDPOINT + - AIPROXY_API_ENDPOINT=http://aiproxy:3000 + # 对应fastgpt中的aiproxy的AIPROXY_API_TOKEN + - AIPROXY_API_TOKEN=aiproxy + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/api/v1/healthz'] + interval: 5s + timeout: 5s + retries: 10 networks: fastgpt: diff --git a/deploy/docker/docker-compose-pgvector.yml b/deploy/docker/docker-compose-pgvector.yml index 61bf40cf2b2b..8a2aa045680d 100644 --- a/deploy/docker/docker-compose-pgvector.yml +++ b/deploy/docker/docker-compose-pgvector.yml @@ -146,6 +146,8 @@ services: - AIPROXY_API_ENDPOINT=http://aiproxy:3000 # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY - AIPROXY_API_TOKEN=aiproxy + # diting 地址 + - DITING_BASE_URL=http://diting:3000 # 数据库最大连接数 - DB_MAX_LINK=30 @@ -258,5 +260,23 @@ services: interval: 5s timeout: 5s retries: 10 + + # diting + diting: + image: ghcr.io/labring/diting:v0.1.0 + container_name: diting + restart: unless-stopped + networks: + - fastgpt + environment: + # 对应fastgpt中的aiproxy的AIPROXY_API_ENDPOINT + - AIPROXY_API_ENDPOINT=http://aiproxy:3000 + # 对应fastgpt中的aiproxy的AIPROXY_API_TOKEN + - AIPROXY_API_TOKEN=aiproxy + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/api/v1/healthz'] + interval: 5s + timeout: 5s + retries: 10 networks: fastgpt: diff --git a/deploy/docker/docker-compose-zilliz.yml b/deploy/docker/docker-compose-zilliz.yml index 72419de7484b..e3511b65c871 100644 --- a/deploy/docker/docker-compose-zilliz.yml +++ b/deploy/docker/docker-compose-zilliz.yml @@ -126,6 +126,8 @@ services: - AIPROXY_API_ENDPOINT=http://aiproxy:3000 # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY - AIPROXY_API_TOKEN=aiproxy + # diting 地址 + - DITING_BASE_URL=http://diting:3000 # 数据库最大连接数 - DB_MAX_LINK=30 @@ -240,5 +242,23 @@ services: interval: 5s timeout: 5s retries: 10 + + # diting + diting: + image: ghcr.io/labring/diting:v0.1.0 + container_name: diting + restart: unless-stopped + networks: + - fastgpt + environment: + # 对应fastgpt中的aiproxy的AIPROXY_API_ENDPOINT + - AIPROXY_API_ENDPOINT=http://aiproxy:3000 + # 对应fastgpt中的aiproxy的AIPROXY_API_TOKEN + - AIPROXY_API_TOKEN=aiproxy + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/api/v1/healthz'] + interval: 5s + timeout: 5s + retries: 10 networks: fastgpt: diff --git a/packages/global/common/error/code/database.ts b/packages/global/common/error/code/database.ts new file mode 100644 index 000000000000..b6cc41b38492 --- /dev/null +++ b/packages/global/common/error/code/database.ts @@ -0,0 +1,139 @@ +import { i18nT } from '../../../../web/i18n/utils'; +import { type ErrType } from '../errorCode'; + +/* database: 509000 */ +export enum DatabaseErrEnum { + // 知识库相关 + datasetParamsError = 'datasetParamsError', + // 客户端创建和管理错误 + clientCreateError = 'databaseClientCreateError', + clientUpdateError = 'databaseClientUpdateError', + clientDestroyError = 'databaseClientDestroyError', + clientAlreadyExists = 'databaseClientAlreadyExists', + clientNotFound = 'databaseClientNotFound', + + // 连接相关错误 + authError = 'databaseAuthError', + databaseNameError = 'databaseNameError', + databasePortError = 'databasePortError', + hostError = 'databaseHostError', + checkError = 'databaseCheckError', + econnRefused = 'connectionRefused', + connectionFailed = 'databaseConnectionFailed', + connectionTimeout = 'databaseConnectionTimeout', + connectionLost = 'databaseConnectionLost', + + // 数据库类型和支持错误 + notSupportType = 'databaseNotSupportType', + notImplemented = 'databaseNotImplemented', + + // API 请求和验证错误 + requestValidationError = 'databaseRequestValidationError', + invalidTableName = 'databaseInvalidTableName', + fetchInfoError = 'databaseFetchInfoError', + dbConfigNotFound = 'databaseConfigNotFound', + opUnknownDatabaseError = 'opUnknownDatabaseError', + dativeServiceError = 'dativeServiceError' +} + +const databaseErr = [ + // 客户端管理错误 + { + statusText: DatabaseErrEnum.clientCreateError, + message: 'core.database.error.client_create_failed' + }, + { + statusText: DatabaseErrEnum.clientUpdateError, + message: 'core.database.error.client_update_failed' + }, + { + statusText: DatabaseErrEnum.clientDestroyError, + message: i18nT('database_client:client_destory_error') + }, + { + statusText: DatabaseErrEnum.clientNotFound, + message: i18nT('database_client:client_not_found') + }, + + // 连接错误 + { + statusText: DatabaseErrEnum.authError, + message: i18nT('database_client:authentication_failed') + }, + { + statusText: DatabaseErrEnum.databaseNameError, + message: i18nT('database_client:database_not_exist') + }, + { + statusText: DatabaseErrEnum.databasePortError, + message: i18nT('database_client:database_port_error') + }, + { + statusText: DatabaseErrEnum.hostError, + message: i18nT('database_client:host_error') + }, + { + statusText: DatabaseErrEnum.econnRefused, + message: i18nT('database_client:connection_refused') + }, + { + statusText: DatabaseErrEnum.checkError, + message: i18nT('database_client:connection_check_error') + }, + { + statusText: DatabaseErrEnum.connectionLost, + message: i18nT('database_client:connection_lost') + }, + { + statusText: DatabaseErrEnum.connectionFailed, + message: i18nT('database_client:connection_failed') + }, + { + statusText: DatabaseErrEnum.connectionTimeout, + message: i18nT('database_client:connection_timeout') + }, + + // 类型支持错误 + { + statusText: DatabaseErrEnum.notSupportType, + message: i18nT('database_client:not_support_databaseType') + }, + { + statusText: DatabaseErrEnum.notImplemented, + message: i18nT('database_client:not_implemented_databaseType') + }, + + // 请求验证错误 + { + statusText: DatabaseErrEnum.invalidTableName, + message: i18nT('database_client:invalid_table_name') + }, + { + statusText: DatabaseErrEnum.fetchInfoError, + message: i18nT('database_client:fetch_info_error') + }, + { + statusText: DatabaseErrEnum.dbConfigNotFound, + message: i18nT('database_client:database_config_not_found') + }, + { + statusText: DatabaseErrEnum.opUnknownDatabaseError, + message: i18nT('database_client:op_unknown_database_error') + }, + { + statusText: DatabaseErrEnum.dativeServiceError, + message: i18nT('database_client:dative_service_error') + } +]; + +export default databaseErr.reduce((acc, cur, index) => { + return { + ...acc, + [cur.statusText]: { + code: 509000 + index, + statusText: cur.statusText, + message: cur.message, + data: null + } + }; +}, {} as ErrType<`${DatabaseErrEnum}`>); diff --git a/packages/global/common/error/code/evaluation.ts b/packages/global/common/error/code/evaluation.ts new file mode 100644 index 000000000000..9f6d5683aaca --- /dev/null +++ b/packages/global/common/error/code/evaluation.ts @@ -0,0 +1,663 @@ +import { type ErrType } from '../errorCode'; +import { i18nT } from '../../../../web/i18n/utils'; + +/* evaluation: 510000 */ +export enum EvaluationErrEnum { + // Validation errors + evalNameRequired = 'evaluationNameRequired', + evalNameTooLong = 'evaluationNameTooLong', + evalNameDuplicate = 'evaluationNameDuplicate', + evalDescriptionTooLong = 'evaluationDescriptionTooLong', + evalDescriptionInvalidType = 'evaluationDescriptionInvalidType', + evalTargetRequired = 'evaluationTargetRequired', + evalTargetInvalidConfig = 'evaluationTargetInvalidConfig', + evalTargetAppIdMissing = 'evaluationTargetAppIdMissing', + evalTargetVersionIdMissing = 'evaluationTargetVersionIdMissing', + evalEvaluatorsRequired = 'evaluationEvaluatorsRequired', + evalEvaluatorInvalidConfig = 'evaluationEvaluatorInvalidConfig', + evalEvaluatorInvalidScoreScaling = 'evaluationEvaluatorInvalidScoreScaling', + evalInvalidFormat = 'evaluationInvalidFormat', + evalIdRequired = 'evaluationIdRequired', + evalItemIdRequired = 'evaluationItemIdRequired', + evalDataItemIdRequired = 'evaluationDataItemIdRequired', + + // Authentication errors + evalInsufficientPermission = 'evaluationInsufficientPermission', + evalAppNotFound = 'evaluationAppNotFound', + evalTaskNotFound = 'evaluationTaskNotFound', + evalItemNotFound = 'evaluationItemNotFound', + + // Task related errors + evalInvalidStatus = 'evaluationInvalidStatus', + evalInvalidStateTransition = 'evaluationInvalidStateTransition', + evalOnlyRunningCanStop = 'evaluationOnlyRunningCanStop', + evalItemNoErrorToRetry = 'evaluationItemNoErrorToRetry', + evalDatasetLoadFailed = 'evaluationDatasetLoadFailed', + evalTargetConfigInvalid = 'evaluationTargetConfigInvalid', + evalEvaluatorsConfigInvalid = 'evaluationEvaluatorsConfigInvalid', + evalUnsupportedTargetType = 'evaluationUnsupportedTargetType', + evalAppVersionNotFound = 'evaluationAppVersionNotFound', + evalDuplicateDatasetName = 'evaluationDuplicateDatasetName', + evalNoDataInCollections = 'evaluationNoDataInCollections', + evalUpdateFailed = 'evaluationUpdateFailed', + // Task execution errors + evalTaskSystemError = 'evaluationTaskSystemError', + evalManuallyStopped = 'evaluationManuallyStopped', + evalEvaluatorExecutionErrors = 'evaluationEvaluatorExecutionErrors', + evalTargetExecutionError = 'evaluationTargetExecutionError', + + // Metric related errors + evalMetricNotFound = 'evaluationMetricNotFound', + evalMetricUnAuth = 'evaluationMetricUnAuth', + evalMetricNameRequired = 'evaluationMetricNameRequired', + evalMetricNameTooLong = 'evaluationMetricNameTooLong', + evalMetricDescriptionRequired = 'evaluationMetricDescriptionRequired', + evalMetricDescriptionTooLong = 'evaluationMetricDescriptionTooLong', + evalMetricPromptRequired = 'evaluationMetricPromptRequired', + evalMetricPromptTooLong = 'evaluationMetricPromptTooLong', + evalMetricTypeRequired = 'evaluationMetricTypeRequired', + evalMetricTypeInvalid = 'evaluationMetricTypeInvalid', + evalMetricNameInvalid = 'evaluationMetricNameInvalid', + evalMetricBuiltinCannotModify = 'evaluationMetricBuiltinCannotModify', + evalMetricBuiltinCannotDelete = 'evaluationMetricBuiltinCannotDelete', + evalMetricIdRequired = 'evaluationMetricIdRequired', + + // Evaluation case related errors + evalCaseRequired = 'evaluationCaseRequired', + evalCaseUserInputRequired = 'evaluationCaseUserInputRequired', + evalCaseUserInputTooLong = 'evaluationCaseUserInputTooLong', + evalCaseActualOutputRequired = 'evaluationCaseActualOutputRequired', + evalCaseActualOutputTooLong = 'evaluationCaseActualOutputTooLong', + evalCaseExpectedOutputRequired = 'evaluationCaseExpectedOutputRequired', + evalCaseExpectedOutputTooLong = 'evaluationCaseExpectedOutputTooLong', + + // LLM config related errors + evalLLmConfigRequired = 'evaluationLLmConfigRequired', + evalLLmModelNameRequired = 'evaluationLLmModelNameRequired', + + // Debug related errors + debugEvaluationFailed = 'evaluationDebugFailed', + + // Evaluator related errors + evaluatorConfigRequired = 'evaluationEvaluatorConfigRequired', + evaluatorLLmConfigMissing = 'evaluationEvaluatorLLmConfigMissing', + evaluatorEmbeddingConfigMissing = 'evaluationEvaluatorEmbeddingConfigMissing', + evaluatorLLmModelNotFound = 'evaluationEvaluatorLLmModelNotFound', + evaluatorEmbeddingModelNotFound = 'evaluationEvaluatorEmbeddingModelNotFound', + evaluatorRequestTimeout = 'evaluationEvaluatorRequestTimeout', + evaluatorServiceUnavailable = 'evaluationEvaluatorServiceUnavailable', + evaluatorInvalidResponse = 'evaluationEvaluatorInvalidResponse', + evaluatorNetworkError = 'evaluationEvaluatorNetworkError', + + // Dataset collection validation errors + datasetCollectionNotFound = 'evaluationDatasetCollectionNotFound', + datasetCollectionIdRequired = 'evaluationDatasetCollectionIdRequired', + datasetCollectionUpdateFailed = 'evaluationDatasetCollectionUpdateFailed', + + // Dataset data validation errors + datasetDataNotFound = 'evaluationDatasetDataNotFound', + datasetModelNotFound = 'evaluationDatasetModelNotFound', + datasetNoData = 'evaluationDatasetNoData', + datasetDataIdRequired = 'evaluationDatasetDataIdRequired', + evalDataQualityStatusInvalid = 'evaluationDataQualityStatusInvalid', + evalDatasetDataListError = 'evaluationDatasetDataListError', + datasetDataUserInputRequired = 'evaluationDatasetDataUserInputRequired', + datasetDataExpectedOutputRequired = 'evaluationDatasetDataExpectedOutputRequired', + datasetDataActualOutputMustBeString = 'evaluationDatasetDataActualOutputMustBeString', + datasetDataContextMustBeArrayOfStrings = 'evaluationDatasetDataContextMustBeArrayOfStrings', + datasetDataRetrievalContextMustBeArrayOfStrings = 'evaluationDatasetDataRetrievalContextMustBeArrayOfStrings', + datasetDataEnableQualityEvalRequired = 'evaluationDatasetDataEnableQualityEvalRequired', + datasetDataEvaluationModelRequiredForQuality = 'evaluationDatasetDataEvaluationModelRequiredForQuality', + datasetDataMetadataMustBeObject = 'evaluationDatasetDataMetadataMustBeObject', + qualityAssessmentFailed = 'evaluationQualityAssessmentFailed', + evalDataQualityJobActiveCannotSetHighQuality = 'evaluationDataQualityJobActiveCannotSetHighQuality', + + // Task/Job related errors + datasetTaskNotRetryable = 'evaluationDatasetTaskNotRetryable', + datasetTaskJobNotFound = 'evaluationDatasetTaskJobNotFound', + datasetTaskJobMismatch = 'evaluationDatasetTaskJobMismatch', + datasetTaskOnlyFailedCanDelete = 'evaluationDatasetTaskOnlyFailedCanDelete', + datasetTaskOperationFailed = 'evaluationDatasetTaskOperationFailed', + datasetTaskDeleteFailed = 'evaluationDatasetTaskDeleteFailed', + fetchFailedTasksError = 'evaluationFetchFailedTasksError', + + // File/Import related errors + fileRequired = 'evaluationFileRequired', + fileMustBeCSV = 'evaluationFileMustBeCSV', + csvInvalidStructure = 'evaluationCSVInvalidStructure', + csvParsingError = 'evaluationCSVParsingError', + csvNoDataRows = 'evaluationCSVNoDataRows', + + // Count/Resource validation errors + countMustBeGreaterThanZero = 'evaluationCountMustBeGreaterThanZero', + countExceedsAvailableData = 'evaluationCountExceedsAvailableData', + selectedDatasetsContainNoData = 'evaluationSelectedDatasetsContainNoData', + + // Model validation errors + evalModelNameInvalid = 'evaluationModelNameInvalid', + evalModelNameTooLong = 'evaluationModelNameTooLong', + + // Summary related errors + summaryMetricsConfigError = 'evaluationSummaryMetricsConfigError', + summaryThresholdValueRequired = 'evaluationSummaryThresholdValueRequired', + summaryWeightRequired = 'evaluationSummaryWeightRequired', + summaryWeightMustBeNumber = 'evaluationSummaryWeightMustBeNumber', + summaryThresholdMustBeNumber = 'evaluationSummaryThresholdMustBeNumber', + summaryCalculateTypeRequired = 'evaluationSummaryCalculateTypeRequired', + summaryCalculateTypeInvalid = 'evaluationSummaryCalculateTypeInvalid', + summaryNoValidMetricsFound = 'evaluationSummaryNoValidMetricsFound', + summaryStreamResponseNotSupported = 'evaluationSummaryStreamResponseNotSupported', + summaryWeightSumMustBe100 = 'evaluationSummaryWeightSumMustBe100', + summaryModelInvalid = 'evaluationSummaryModelInvalid' +} + +const evaluationErrList = [ + // Dataset related errors + { + statusText: EvaluationErrEnum.datasetCollectionNotFound, + message: i18nT('evaluation:dataset_collection_not_found') + }, + { + statusText: EvaluationErrEnum.datasetDataNotFound, + message: i18nT('evaluation:dataset_data_not_found') + }, + + // Validation errors + { + statusText: EvaluationErrEnum.evalNameRequired, + message: i18nT('evaluation:name_required') + }, + { + statusText: EvaluationErrEnum.evalNameTooLong, + message: i18nT('evaluation:name_too_long') + }, + { + statusText: EvaluationErrEnum.evalNameDuplicate, + message: i18nT('evaluation:name_duplicate') + }, + { + statusText: EvaluationErrEnum.evalDescriptionTooLong, + message: i18nT('evaluation:description_too_long') + }, + { + statusText: EvaluationErrEnum.evalTargetRequired, + message: i18nT('evaluation:target_required') + }, + { + statusText: EvaluationErrEnum.evalTargetInvalidConfig, + message: i18nT('evaluation:target_invalid_config') + }, + { + statusText: EvaluationErrEnum.evalTargetAppIdMissing, + message: i18nT('evaluation:target_app_id_missing') + }, + { + statusText: EvaluationErrEnum.evalTargetVersionIdMissing, + message: i18nT('evaluation:target_version_id_missing') + }, + { + statusText: EvaluationErrEnum.evalEvaluatorsRequired, + message: i18nT('evaluation:evaluators_required') + }, + { + statusText: EvaluationErrEnum.evalEvaluatorInvalidConfig, + message: i18nT('evaluation:evaluator_invalid_config') + }, + { + statusText: EvaluationErrEnum.evalEvaluatorInvalidScoreScaling, + message: i18nT('evaluation:evaluator_invalid_score_scaling') + }, + { + statusText: EvaluationErrEnum.evalInvalidFormat, + message: i18nT('evaluation:invalid_format') + }, + { + statusText: EvaluationErrEnum.evalIdRequired, + message: i18nT('evaluation:id_required') + }, + { + statusText: EvaluationErrEnum.evalItemIdRequired, + message: i18nT('evaluation:item_id_required') + }, + { + statusText: EvaluationErrEnum.evalDataItemIdRequired, + message: i18nT('evaluation:data_item_id_required') + }, + + // Authentication errors + { + statusText: EvaluationErrEnum.evalInsufficientPermission, + message: i18nT('evaluation:insufficient_permission') + }, + { + statusText: EvaluationErrEnum.evalAppNotFound, + message: i18nT('evaluation:app_not_found') + }, + { + statusText: EvaluationErrEnum.evalTaskNotFound, + message: i18nT('evaluation:task_not_found') + }, + { + statusText: EvaluationErrEnum.evalItemNotFound, + message: i18nT('evaluation:item_not_found') + }, + + // Business logic errors + { + statusText: EvaluationErrEnum.evalInvalidStatus, + message: i18nT('evaluation:invalid_status') + }, + { + statusText: EvaluationErrEnum.evalInvalidStateTransition, + message: i18nT('evaluation:invalid_state_transition') + }, + { + statusText: EvaluationErrEnum.evalOnlyRunningCanStop, + message: i18nT('evaluation:only_running_can_stop') + }, + { + statusText: EvaluationErrEnum.evalItemNoErrorToRetry, + message: i18nT('evaluation:item_no_error_to_retry') + }, + { + statusText: EvaluationErrEnum.evalTargetExecutionError, + message: i18nT('evaluation:target_execution_error') + }, + { + statusText: EvaluationErrEnum.evalDatasetLoadFailed, + message: i18nT('evaluation:dataset_load_failed') + }, + { + statusText: EvaluationErrEnum.evalTargetConfigInvalid, + message: i18nT('evaluation:target_config_invalid') + }, + { + statusText: EvaluationErrEnum.evalEvaluatorsConfigInvalid, + message: i18nT('evaluation:evaluators_config_invalid') + }, + { + statusText: EvaluationErrEnum.evalUnsupportedTargetType, + message: i18nT('evaluation:unsupported_target_type') + }, + { + statusText: EvaluationErrEnum.evalAppVersionNotFound, + message: i18nT('evaluation:app_version_not_found') + }, + { + statusText: EvaluationErrEnum.evalDuplicateDatasetName, + message: i18nT('evaluation:duplicate_dataset_name') + }, + { + statusText: EvaluationErrEnum.evalNoDataInCollections, + message: i18nT('evaluation:no_data_in_collections') + }, + { + statusText: EvaluationErrEnum.evalUpdateFailed, + message: i18nT('evaluation:update_failed') + }, + // Metric related errors + { + statusText: EvaluationErrEnum.evalMetricNotFound, + message: i18nT('evaluation:metric_not_found') + }, + { + statusText: EvaluationErrEnum.evalMetricUnAuth, + message: i18nT('evaluation:metric_un_auth') + }, + { + statusText: EvaluationErrEnum.evalMetricNameRequired, + message: i18nT('evaluation:metric_name_required') + }, + { + statusText: EvaluationErrEnum.evalMetricNameTooLong, + message: i18nT('evaluation:metric_name_too_long') + }, + { + statusText: EvaluationErrEnum.evalMetricDescriptionRequired, + message: i18nT('evaluation:metric_description_required') + }, + { + statusText: EvaluationErrEnum.evalMetricDescriptionTooLong, + message: i18nT('evaluation:metric_description_too_long') + }, + { + statusText: EvaluationErrEnum.evalMetricPromptRequired, + message: i18nT('evaluation:metric_prompt_required') + }, + { + statusText: EvaluationErrEnum.evalMetricPromptTooLong, + message: i18nT('evaluation:metric_prompt_too_long') + }, + { + statusText: EvaluationErrEnum.evalMetricTypeRequired, + message: i18nT('evaluation:metric_type_required') + }, + { + statusText: EvaluationErrEnum.evalMetricTypeInvalid, + message: i18nT('evaluation:metric_type_invalid') + }, + { + statusText: EvaluationErrEnum.evalMetricNameInvalid, + message: i18nT('evaluation:metric_name_invalid') + }, + { + statusText: EvaluationErrEnum.evalMetricBuiltinCannotModify, + message: i18nT('evaluation:metric_builtin_cannot_modify') + }, + { + statusText: EvaluationErrEnum.evalMetricBuiltinCannotDelete, + message: i18nT('evaluation:metric_builtin_cannot_delete') + }, + { + statusText: EvaluationErrEnum.evalMetricIdRequired, + message: i18nT('evaluation:metric_id_required') + }, + + // Evaluation case related errors + { + statusText: EvaluationErrEnum.evalCaseRequired, + message: i18nT('evaluation:eval_case_required') + }, + { + statusText: EvaluationErrEnum.evalCaseUserInputRequired, + message: i18nT('evaluation:eval_case_user_input_required') + }, + { + statusText: EvaluationErrEnum.evalCaseUserInputTooLong, + message: i18nT('evaluation:eval_case_user_input_too_long') + }, + { + statusText: EvaluationErrEnum.evalCaseActualOutputRequired, + message: i18nT('evaluation:eval_case_actual_output_required') + }, + { + statusText: EvaluationErrEnum.evalCaseActualOutputTooLong, + message: i18nT('evaluation:eval_case_actual_output_too_long') + }, + { + statusText: EvaluationErrEnum.evalCaseExpectedOutputRequired, + message: i18nT('evaluation:eval_case_expected_output_required') + }, + { + statusText: EvaluationErrEnum.evalCaseExpectedOutputTooLong, + message: i18nT('evaluation:eval_case_expected_output_too_long') + }, + + // LLM config related errors + { + statusText: EvaluationErrEnum.evalLLmConfigRequired, + message: i18nT('evaluation:llm_config_required') + }, + { + statusText: EvaluationErrEnum.evalLLmModelNameRequired, + message: i18nT('evaluation:llm_model_name_required') + }, + + // Debug related errors + { + statusText: EvaluationErrEnum.debugEvaluationFailed, + message: i18nT('evaluation:debug_evaluation_failed') + }, + + // Evaluator related errors + { + statusText: EvaluationErrEnum.evaluatorConfigRequired, + message: i18nT('evaluation:evaluator_config_required') + }, + { + statusText: EvaluationErrEnum.evaluatorLLmConfigMissing, + message: i18nT('evaluation:evaluator_llm_config_missing') + }, + { + statusText: EvaluationErrEnum.evaluatorEmbeddingConfigMissing, + message: i18nT('evaluation:evaluator_embedding_config_missing') + }, + { + statusText: EvaluationErrEnum.evaluatorLLmModelNotFound, + message: i18nT('evaluation:evaluator_llm_model_not_found') + }, + { + statusText: EvaluationErrEnum.evaluatorEmbeddingModelNotFound, + message: i18nT('evaluation:evaluator_embedding_model_not_found') + }, + { + statusText: EvaluationErrEnum.evaluatorRequestTimeout, + message: i18nT('evaluation:evaluator_request_timeout') + }, + { + statusText: EvaluationErrEnum.evaluatorServiceUnavailable, + message: i18nT('evaluation:evaluator_service_unavailable') + }, + { + statusText: EvaluationErrEnum.evaluatorInvalidResponse, + message: i18nT('evaluation:evaluator_invalid_response') + }, + { + statusText: EvaluationErrEnum.evaluatorNetworkError, + message: i18nT('evaluation:evaluator_network_error') + }, + + // Dataset collection validation errors + { + statusText: EvaluationErrEnum.datasetCollectionIdRequired, + message: i18nT('evaluation:dataset_collection_id_required') + }, + { + statusText: EvaluationErrEnum.datasetCollectionUpdateFailed, + message: i18nT('evaluation:dataset_collection_update_failed') + }, + { + statusText: EvaluationErrEnum.datasetModelNotFound, + message: i18nT('evaluation:dataset_model_not_found') + }, + { + statusText: EvaluationErrEnum.datasetNoData, + message: i18nT('evaluation:dataset_no_data') + }, + + // Dataset data validation errors + { + statusText: EvaluationErrEnum.datasetDataIdRequired, + message: i18nT('evaluation:dataset_data_id_required') + }, + { + statusText: EvaluationErrEnum.evalDataQualityStatusInvalid, + message: i18nT('evaluation:data_quality_status_invalid') + }, + { + statusText: EvaluationErrEnum.datasetDataUserInputRequired, + message: i18nT('evaluation:dataset_data_user_input_required') + }, + { + statusText: EvaluationErrEnum.datasetDataExpectedOutputRequired, + message: i18nT('evaluation:dataset_data_expected_output_required') + }, + { + statusText: EvaluationErrEnum.datasetDataActualOutputMustBeString, + message: i18nT('evaluation:dataset_data_actual_output_must_be_string') + }, + { + statusText: EvaluationErrEnum.datasetDataContextMustBeArrayOfStrings, + message: i18nT('evaluation:dataset_data_context_must_be_array_of_strings') + }, + { + statusText: EvaluationErrEnum.datasetDataRetrievalContextMustBeArrayOfStrings, + message: i18nT('evaluation:dataset_data_retrieval_context_must_be_array_of_strings') + }, + { + statusText: EvaluationErrEnum.datasetDataEnableQualityEvalRequired, + message: i18nT('evaluation:dataset_data_enable_quality_eval_required') + }, + { + statusText: EvaluationErrEnum.datasetDataEvaluationModelRequiredForQuality, + message: i18nT('evaluation:dataset_data_evaluation_model_required_for_quality') + }, + { + statusText: EvaluationErrEnum.datasetDataMetadataMustBeObject, + message: i18nT('evaluation:dataset_data_metadata_must_be_object') + }, + { + statusText: EvaluationErrEnum.evalDatasetDataListError, + message: i18nT('evaluation:dataset_data_list_error') + }, + { + statusText: EvaluationErrEnum.qualityAssessmentFailed, + message: i18nT('evaluation:quality_assessment_failed') + }, + { + statusText: EvaluationErrEnum.evalDataQualityJobActiveCannotSetHighQuality, + message: i18nT('evaluation:data_quality_job_active_cannot_set_high_quality') + }, + + // Task/Job related errors + { + statusText: EvaluationErrEnum.datasetTaskNotRetryable, + message: i18nT('evaluation:dataset_task_not_retryable') + }, + { + statusText: EvaluationErrEnum.datasetTaskJobNotFound, + message: i18nT('evaluation:dataset_task_job_not_found') + }, + { + statusText: EvaluationErrEnum.datasetTaskJobMismatch, + message: i18nT('evaluation:dataset_task_job_mismatch') + }, + { + statusText: EvaluationErrEnum.datasetTaskOnlyFailedCanDelete, + message: i18nT('evaluation:dataset_task_only_failed_can_delete') + }, + { + statusText: EvaluationErrEnum.datasetTaskOperationFailed, + message: i18nT('evaluation:dataset_task_operation_failed') + }, + { + statusText: EvaluationErrEnum.datasetTaskDeleteFailed, + message: i18nT('evaluation:dataset_task_delete_failed') + }, + { + statusText: EvaluationErrEnum.fetchFailedTasksError, + message: i18nT('evaluation:fetch_failed_tasks_error') + }, + + // File/Import related errors + { + statusText: EvaluationErrEnum.fileRequired, + message: i18nT('evaluation:file_required') + }, + { + statusText: EvaluationErrEnum.fileMustBeCSV, + message: i18nT('evaluation:file_must_be_csv') + }, + { + statusText: EvaluationErrEnum.csvInvalidStructure, + message: i18nT('evaluation:csv_invalid_structure') + }, + { + statusText: EvaluationErrEnum.csvParsingError, + message: i18nT('evaluation:csv_parsing_error') + }, + { + statusText: EvaluationErrEnum.csvNoDataRows, + message: i18nT('evaluation:csv_no_data_rows') + }, + + // Count/Resource validation errors + { + statusText: EvaluationErrEnum.countMustBeGreaterThanZero, + message: i18nT('evaluation:count_must_be_greater_than_zero') + }, + { + statusText: EvaluationErrEnum.countExceedsAvailableData, + message: i18nT('evaluation:count_exceeds_available_data') + }, + { + statusText: EvaluationErrEnum.selectedDatasetsContainNoData, + message: i18nT('evaluation:selected_datasets_contain_no_data') + }, + + // Summary related errors + { + statusText: EvaluationErrEnum.summaryMetricsConfigError, + message: i18nT('evaluation:summary_metrics_config_error') + }, + { + statusText: EvaluationErrEnum.summaryThresholdValueRequired, + message: i18nT('evaluation:summary_threshold_value_required') + }, + { + statusText: EvaluationErrEnum.summaryWeightRequired, + message: i18nT('evaluation:summary_weight_required') + }, + { + statusText: EvaluationErrEnum.summaryWeightMustBeNumber, + message: i18nT('evaluation:summary_weight_must_be_number') + }, + { + statusText: EvaluationErrEnum.summaryThresholdMustBeNumber, + message: i18nT('evaluation:summary_threshold_must_be_number') + }, + { + statusText: EvaluationErrEnum.summaryCalculateTypeRequired, + message: i18nT('evaluation:summary_calculate_type_required') + }, + { + statusText: EvaluationErrEnum.summaryCalculateTypeInvalid, + message: i18nT('evaluation:summary_calculate_type_invalid') + }, + { + statusText: EvaluationErrEnum.summaryNoValidMetricsFound, + message: i18nT('evaluation:summary_no_valid_metrics_found') + }, + { + statusText: EvaluationErrEnum.summaryStreamResponseNotSupported, + message: i18nT('evaluation:summary_stream_response_not_supported') + }, + { + statusText: EvaluationErrEnum.summaryWeightSumMustBe100, + message: i18nT('evaluation:summary_weight_sum_must_be_100') + }, + { + statusText: EvaluationErrEnum.summaryModelInvalid, + message: i18nT('evaluation:summary_model_invalid') + }, + + // Model validation errors + { + statusText: EvaluationErrEnum.evalModelNameInvalid, + message: i18nT('evaluation:model_name_invalid') + }, + { + statusText: EvaluationErrEnum.evalModelNameTooLong, + message: i18nT('evaluation:model_name_too_long') + }, + { + statusText: EvaluationErrEnum.evalDescriptionInvalidType, + message: i18nT('evaluation:description_invalid_type') + }, + + // Task execution errors + { + statusText: EvaluationErrEnum.evalTaskSystemError, + message: i18nT('evaluation:task_system_error') + }, + { + statusText: EvaluationErrEnum.evalManuallyStopped, + message: i18nT('evaluation:manually_stopped') + }, + { + statusText: EvaluationErrEnum.evalEvaluatorExecutionErrors, + message: i18nT('evaluation:evaluator_execution_errors') + } +]; + +export default evaluationErrList.reduce((acc, cur, index) => { + return { + ...acc, + [cur.statusText]: { + code: 510000 + index, + statusText: cur.statusText, + message: cur.message, + data: null + } + }; +}, {} as ErrType<`${EvaluationErrEnum}`>); diff --git a/packages/global/common/error/code/system.ts b/packages/global/common/error/code/system.ts index efe177c60f4e..1615872d639c 100644 --- a/packages/global/common/error/code/system.ts +++ b/packages/global/common/error/code/system.ts @@ -5,7 +5,11 @@ export enum SystemErrEnum { communityVersionNumLimit = 'communityVersionNumLimit', licenseAppAmountLimit = 'licenseAppAmountLimit', licenseDatasetAmountLimit = 'licenseDatasetAmountLimit', - licenseUserAmountLimit = 'licenseUserAmountLimit' + licenseUserAmountLimit = 'licenseUserAmountLimit', + licenseEvaluationTaskAmountLimit = 'licenseEvaluationTaskAmountLimit', + licenseEvalDatasetAmountLimit = 'licenseEvalDatasetAmountLimit', + licenseEvalDatasetDataAmountLimit = 'licenseEvalDatasetDataAmountLimit', + licenseEvalMetricAmountLimit = 'licenseEvalMetricAmountLimit' } const systemErr = [ @@ -24,6 +28,22 @@ const systemErr = [ { statusText: SystemErrEnum.licenseUserAmountLimit, message: i18nT('common:code_error.system_error.license_user_amount_limit') + }, + { + statusText: SystemErrEnum.licenseEvaluationTaskAmountLimit, + message: i18nT('common:code_error.system_error.license_evaluation_task_amount_limit') + }, + { + statusText: SystemErrEnum.licenseEvalDatasetAmountLimit, + message: i18nT('common:code_error.system_error.license_eval_dataset_amount_limit') + }, + { + statusText: SystemErrEnum.licenseEvalDatasetDataAmountLimit, + message: i18nT('common:code_error.system_error.license_eval_dataset_data_amount_limit') + }, + { + statusText: SystemErrEnum.licenseEvalMetricAmountLimit, + message: i18nT('common:code_error.system_error.license_eval_metric_amount_limit') } ]; diff --git a/packages/global/common/error/code/team.ts b/packages/global/common/error/code/team.ts index 0d42ee79ac56..44c3846ab702 100644 --- a/packages/global/common/error/code/team.ts +++ b/packages/global/common/error/code/team.ts @@ -13,6 +13,10 @@ export enum TeamErrEnum { pluginAmountNotEnough = 'pluginAmountNotEnough', websiteSyncNotEnough = 'websiteSyncNotEnough', reRankNotEnough = 'reRankNotEnough', + evaluationTaskAmountNotEnough = 'evaluationTaskAmountNotEnough', + evaluationDatasetAmountNotEnough = 'evaluationDatasetAmountNotEnough', + evaluationDatasetDataAmountNotEnough = 'evaluationDatasetDataAmountNotEnough', + evaluationMetricAmountNotEnough = 'evaluationMetricAmountNotEnough', groupNameEmpty = 'groupNameEmpty', groupNameDuplicate = 'groupNameDuplicate', groupNotExist = 'groupNotExist', @@ -73,6 +77,22 @@ const teamErr = [ statusText: TeamErrEnum.reRankNotEnough, message: i18nT('common:code_error.team_error.re_rank_not_enough') }, + { + statusText: TeamErrEnum.evaluationTaskAmountNotEnough, + message: i18nT('common:code_error.team_error.evaluation_task_amount_not_enough') + }, + { + statusText: TeamErrEnum.evaluationDatasetAmountNotEnough, + message: i18nT('common:code_error.team_error.evaluation_dataset_amount_not_enough') + }, + { + statusText: TeamErrEnum.evaluationDatasetDataAmountNotEnough, + message: i18nT('common:code_error.team_error.evaluation_dataset_data_amount_not_enough') + }, + { + statusText: TeamErrEnum.evaluationMetricAmountNotEnough, + message: i18nT('common:code_error.team_error.evaluation_metric_amount_not_enough') + }, { statusText: TeamErrEnum.groupNameEmpty, message: i18nT('common:code_error.team_error.group_name_empty') diff --git a/packages/global/common/error/errorCode.ts b/packages/global/common/error/errorCode.ts index 478694742dfb..8b29f0efb0e6 100644 --- a/packages/global/common/error/errorCode.ts +++ b/packages/global/common/error/errorCode.ts @@ -8,6 +8,8 @@ import teamErr from './code/team'; import userErr from './code/user'; import commonErr from './code/common'; import SystemErrEnum from './code/system'; +import databaseErr from './code/database'; +import evaluationErr from './code/evaluation'; import { i18nT } from '../../../web/i18n/utils'; export const ERROR_CODE: { [key: number]: string } = { @@ -106,7 +108,9 @@ export const ERROR_RESPONSE: Record< ...outLinkErr, ...teamErr, ...userErr, + ...databaseErr, ...pluginErr, ...commonErr, - ...SystemErrEnum + ...SystemErrEnum, + ...evaluationErr }; diff --git a/packages/global/common/error/utils.ts b/packages/global/common/error/utils.ts index c13c8e2666cd..f21cc46d87d9 100644 --- a/packages/global/common/error/utils.ts +++ b/packages/global/common/error/utils.ts @@ -28,3 +28,15 @@ export class UserError extends Error { this.name = 'UserError'; } } + +export class FileUploadError extends UserError { + code: string; + details?: Record; + + constructor(code: string, message: string, details?: Record) { + super(message); + this.name = 'FileUploadError'; + this.code = code; + this.details = details; + } +} diff --git a/packages/global/common/file/constants.ts b/packages/global/common/file/constants.ts index ac48e3a3e3ff..3d7832675d54 100644 --- a/packages/global/common/file/constants.ts +++ b/packages/global/common/file/constants.ts @@ -3,7 +3,8 @@ import { i18nT } from '../../../web/i18n/utils'; /* mongo fs bucket */ export enum BucketNameEnum { dataset = 'dataset', - chat = 'chat' + chat = 'chat', + evaluation = 'evaluation' } export const bucketNameMap = { [BucketNameEnum.dataset]: { @@ -13,6 +14,10 @@ export const bucketNameMap = { [BucketNameEnum.chat]: { label: i18nT('file:bucket_chat'), previewExpireMinutes: 7 * 24 * 60 // 7 days + }, + [BucketNameEnum.evaluation]: { + label: i18nT('file:eval_file'), + previewExpireMinutes: 30 // 30 minutes } }; @@ -22,3 +27,18 @@ export const ReadFileBaseUrl = `${EndpointUrl}/api/common/file/read`; export const documentFileType = '.txt, .docx, .csv, .xlsx, .pdf, .md, .html, .pptx'; export const imageFileType = '.jpg, .jpeg, .png, .gif, .bmp, .webp, .svg, .tiff, .tif, .ico, .heic, .heif, .avif, .raw, .cr2, .nef, .arw, .dng, .psd, .ai, .eps, .emf, .wmf, .jfif, .exif, .pgm, .ppm, .pbm, .jp2, .j2k, .jpf, .jpx, .jpm, .mj2, .xbm, .pcx'; + +/* File Upload Limits */ +export const DEFAULT_FILE_UPLOAD_LIMITS = { + // Default maximum number of files that can be uploaded at once + MAX_FILE_COUNT: 20, + // Default maximum size per file in MB + MAX_FILE_SIZE_MB: 500 +} as const; + +/* File Upload Error Codes */ +export enum FileUploadErrorEnum { + FILE_TOO_LARGE = 'FILE_TOO_LARGE', + TOO_MANY_FILES = 'TOO_MANY_FILES', + INVALID_FILE_TYPE = 'INVALID_FILE_TYPE' +} diff --git a/packages/global/common/i18n/utils.ts b/packages/global/common/i18n/utils.ts index 8e3f5082b3e8..9d9e9c4887c8 100644 --- a/packages/global/common/i18n/utils.ts +++ b/packages/global/common/i18n/utils.ts @@ -4,3 +4,28 @@ export const parseI18nString = (str: I18nStringType | string = '', lang: localeT if (!str || typeof str === 'string') return str; return str[lang] ?? str['en']; }; + +/** + * Parse i18n array data, supporting both old array format and new i18n object format + * @param data - The data to parse (array or i18n object) + * @param lang - The target language + * @returns The parsed array data + */ +export const parseI18nArray = ( + data: T[] | Record | undefined, + lang: localeType = 'en' +): T[] | undefined => { + if (!data) return []; + + // If it's an array, it's the old format + if (Array.isArray(data)) { + return data; + } + + // If it's an object, it's the new i18n format + if (typeof data === 'object' && data !== null) { + return data[lang] ?? data['en'] ?? []; + } + + return undefined; +}; diff --git a/packages/global/common/system/types/index.d.ts b/packages/global/common/system/types/index.d.ts index c52feedb443e..0a6188c11844 100644 --- a/packages/global/common/system/types/index.d.ts +++ b/packages/global/common/system/types/index.d.ts @@ -129,6 +129,7 @@ export type FastGPTFeConfigsType = { export type SystemEnvType = { openapiPrefix?: string; + parseMaxProcess?: number; vectorMaxProcess: number; qaMaxProcess: number; vlmMaxProcess: number; @@ -141,15 +142,31 @@ export type SystemEnvType = { chatApiKey?: string; customPdfParse?: customPdfParseType; + + // Evaluation configurations + evalConfig?: EvaluationConfigType; }; export type customPdfParseType = { url?: string; key?: string; + timeout?: number; doc2xKey?: string; price?: number; }; +export type EvaluationConfigType = { + taskConcurrency?: number; + caseConcurrency?: number; + caseMaxRetry?: number; + caseResultThreshold?: number; + summaryConcurrency?: number; + dataQualityConcurrency?: number; + datasetDataSynthesizeConcurrency?: number; + datasetSmartGenerateConcurrency?: number; + maxStalledCount?: number; +}; + export type LicenseDataType = { startTime: string; expiredTime: string; @@ -159,6 +176,10 @@ export type LicenseDataType = { maxUsers?: number; // 最大用户数,不填默认不上限 maxApps?: number; // 最大应用数,不填默认不上限 maxDatasets?: number; // 最大数据集数,不填默认不上限 + maxEvaluationTaskAmount?: number; // 最大评估任务数,不填默认不上限 + maxEvalDatasetAmount?: number; // 最大评估数据集数,不填默认不上限 + maxEvalDatasetDataAmount?: number; // 最大评估数据集数据量,不填默认不上限 + maxEvalMetricAmount?: number; // 最大评估指标数,不填默认不上限 functions: { sso: boolean; pay: boolean; diff --git a/packages/global/core/ai/model.d.ts b/packages/global/core/ai/model.d.ts index 694c63b4882c..19cd0c59eef4 100644 --- a/packages/global/core/ai/model.d.ts +++ b/packages/global/core/ai/model.d.ts @@ -19,6 +19,7 @@ type BaseModelItemType = { isDefault?: boolean; isDefaultDatasetTextModel?: boolean; isDefaultDatasetImageModel?: boolean; + isDefaultEvaluationModel?: boolean; // If has requestUrl, it will request the model directly requestUrl?: string; diff --git a/packages/global/core/ai/prompt/eval.ts b/packages/global/core/ai/prompt/eval.ts new file mode 100644 index 000000000000..a2c8b4c58f57 --- /dev/null +++ b/packages/global/core/ai/prompt/eval.ts @@ -0,0 +1,144 @@ +// Simplified Chinese +const problemAnalysisTemplateZhCN = `你是一名问题诊断专家,专注于分析AI系统的缺陷和错误模式,假设具备足够的领域知识。请使用{language}回复用户的问题 +##任务 +基于评估原因,对AI表现中暴露的核心问题进行诊断,识别关键错误类型和频发模式。 + +##输出要求 +1. 控制在150字以内,突出最重要的缺陷发现,直接返回正文,不能携带标题 +2. 明确指出错误类型、表现模式和影响 +3. 结合评估原因进行具体诊断,避免笼统描述 +4. 输出应具备洞察力和价值,而非简单复述,但无需给优化建议 +5. 返回总结用户能够了解评估数据的概况 + +##参考示例 +{example} + +##评估数据 +{evaluation_result_for_single_metric} + +开始返回问题分析总结:`; + +const strengthAnalysisTemplateZhCN = `你是一名优势分析专家,专注于识别AI系统的优秀表现和成功模式,假设具备足够的领域知识。请使用{language}回复用户的问题 +##任务 +基于评估原因,总结AI在本次表现中的优势,提炼可复制的成功因子。 + +##输出要求 +1. 控制在150字以内,突出最关键的优势,直接返回正文,不用携带标题 +2. 明确描述优势的具体表现和成功模式 +3. 强调可推广的优点和最佳实践 +4. 基于评估原因提炼洞察,避免空洞或泛泛而谈 +5. 返回总结用户能够了解评估数据的概况 + +##参考示例 +{example} + +##评估数据 +{evaluation_result_for_single_metric} + +开始返回优势分析总结:`; + +const goodExampleZhCN = `回答准确性高,逻辑条理清晰,能正确理解用户意图并给出相关解答。在复杂问题处理上展现出良好的上下文理解与知识整合能力,优势主要体现在逻辑推理严谨、信息提取完整、回答结构化程度高。`; + +const badExampleZhCN = `存在理解偏差、信息遗漏和逻辑错误。如xx表现体现复杂指令理解不足,常忽略关键要求;知识整合不连贯,导致答案不全面;多步骤推理中出现逻辑跳跃。这些问题暴露出指令解析和推理能力的缺陷。`; + +// Traditional Chinese +const problemAnalysisTemplateZhTW = `你是一名問題診斷專家,專注於分析AI系統的缺陷和錯誤模式,假設具備足夠的領域知識。請使用 {language} 回覆使用者的問題 +##任務 +基於評估原因,對AI表現中暴露的核心問題進行診斷,識別關鍵錯誤類型和頻發模式。 + +##輸出要求 +1. 控制在150字以內,突出最重要的缺陷發現,直接返回正文,不能攜帶標題 +2. 明確指出錯誤類型、表現模式和影響 +3. 結合評估原因進行具體診斷,避免籠統描述 +4. 輸出應具備洞察力和價值,而非簡單複述,但無需給優化建議 +5. 返回總結用戶能夠了解評估數據的概況 + +##參考示例 +{example} + +##評估數據 +{evaluation_result_for_single_metric} + +開始返回問題分析總結:`; + +const strengthAnalysisTemplateZhTW = `你是一名優勢分析專家,專注於識別AI系統的優秀表現和成功模式,假設具備足夠的領域知識。請使用 {language} 回覆使用者的問題 +##任務 +基於評估原因,總結AI在本次表現中的優勢,提煉可複製的成功因子。 + +##輸出要求 +1. 控制在150字以內,突出最關鍵的優勢,直接返回正文,不用攜帶標題 +2. 明確描述優勢的具體表現和成功模式 +3. 強調可推廣的優點和最佳實踐 +4. 基於評估原因提煉洞察,避免空洞或泛泛而談 +5. 返回總結用戶能夠了解評估數據的概況 + +##參考示例 +{example} + +##評估數據 +{evaluation_result_for_single_metric} + +開始返回優勢分析總結:`; + +const goodExampleZhTW = `回答準確性高,邏輯條理清晰,能正確理解用戶意圖並給出相關解答。在複雜問題處理上展現出良好的上下文理解與知識整合能力,優勢主要體現在邏輯推理嚴謹、資訊提取完整、回答結構化程度高。`; + +const badExampleZhTW = `存在理解偏差、資訊遺漏和邏輯錯誤。如xx表現體現複雜指令理解不足,常忽略關鍵要求;知識整合不連貫,導致答案不全面;多步驟推理中出現邏輯跳躍。這些問題暴露出指令解析和推理能力的缺陷。`; + +// English +const problemAnalysisTemplateEn = `You are a problem diagnosis expert focused on analyzing AI system defects and error patterns, assuming sufficient domain knowledge.Please use {language} to reply to the user's question +##Task +Based on evaluation reasons, diagnose core problems exposed in AI performance, identifying key error types and frequent patterns. + +##Output Requirements +1. Keep within 150 words, highlight the most critical defect findings, return the main text directly without titles +2. Clearly identify error types, behavior patterns, and impacts +3. Provide specific diagnosis based on evaluation reasons, avoiding vague descriptions +4. Output should be insightful and valuable, not merely repetitive, but no optimization suggestions needed +5. Return a summary that helps users understand the evaluation data overview + +##Reference Example +{example} + +##Evaluation Data +{evaluation_result_for_single_metric} + +Begin returning problem analysis summary:`; + +const strengthAnalysisTemplateEn = `You are a strength analysis expert focused on identifying excellent AI system performance and success patterns, assuming sufficient domain knowledge.Please use {language} to reply to the user's question +##Task +Based on evaluation reasons, summarize AI strengths in this performance, extract replicable success factors. + +##Output Requirements +1. Keep within 150 words, highlight the most critical strengths, return the main text directly without titles +2. Clearly describe specific manifestations of strengths and success patterns +3. Emphasize generalizable advantages and best practices +4. Extract insights based on evaluation reasons, avoiding empty or vague statements +5. Return a summary that helps users understand the evaluation data overview + +##Reference Example +{example} + +##Evaluation Data +{evaluation_result_for_single_metric} + +Begin returning strength analysis summary:`; + +const goodExampleEn = `High answer accuracy, clear logical structure, correctly understands user intent and provides relevant answers. Demonstrates good contextual understanding and knowledge integration in complex problem handling. Strengths mainly reflected in rigorous logical reasoning, complete information extraction, and high degree of answer structuring.`; + +const badExampleEn = `Shows understanding bias, information omission and logical errors. Performance in xx indicates insufficient understanding of complex instructions, often ignoring key requirements; inconsistent knowledge integration leads to incomplete answers; logical jumps occur in multi-step reasoning. These problems expose deficiencies in instruction parsing and reasoning capabilities.`; + +// Exports +export { + problemAnalysisTemplateZhCN, + strengthAnalysisTemplateZhCN, + goodExampleZhCN, + badExampleZhCN, + problemAnalysisTemplateZhTW, + strengthAnalysisTemplateZhTW, + goodExampleZhTW, + badExampleZhTW, + problemAnalysisTemplateEn, + strengthAnalysisTemplateEn, + goodExampleEn, + badExampleEn +}; diff --git a/packages/global/core/app/evaluation/api.d.ts b/packages/global/core/app/evaluation/api.d.ts deleted file mode 100644 index 8c1d8e87335c..000000000000 --- a/packages/global/core/app/evaluation/api.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { PaginationProps } from '@fastgpt/web/common/fetch/type'; - -export type listEvaluationsBody = PaginationProps<{ - searchKey?: string; -}>; - -export type listEvalItemsBody = PaginationProps<{ - evalId: string; -}>; - -export type retryEvalItemBody = { - evalItemId: string; -}; - -export type updateEvalItemBody = { - evalItemId: string; - question: string; - expectedResponse: string; - variables: Record; -}; diff --git a/packages/global/core/app/evaluation/constants.ts b/packages/global/core/app/evaluation/constants.ts deleted file mode 100644 index d6b02985820c..000000000000 --- a/packages/global/core/app/evaluation/constants.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { i18nT } from '../../../../web/i18n/utils'; - -export const evaluationFileErrors = i18nT('dashboard_evaluation:eval_file_check_error'); - -export enum EvaluationStatusEnum { - queuing = 0, - evaluating = 1, - completed = 2 -} - -export const EvaluationStatusMap = { - [EvaluationStatusEnum.queuing]: { - name: i18nT('dashboard_evaluation:queuing') - }, - [EvaluationStatusEnum.evaluating]: { - name: i18nT('dashboard_evaluation:evaluating') - }, - [EvaluationStatusEnum.completed]: { - name: i18nT('dashboard_evaluation:completed') - } -}; -export const EvaluationStatusValues = Object.keys(EvaluationStatusMap).map(Number); diff --git a/packages/global/core/app/evaluation/type.d.ts b/packages/global/core/app/evaluation/type.d.ts deleted file mode 100644 index 2a497a50979a..000000000000 --- a/packages/global/core/app/evaluation/type.d.ts +++ /dev/null @@ -1,51 +0,0 @@ -import type { EvaluationStatusEnum } from './constants'; - -export type EvaluationSchemaType = { - _id: string; - teamId: string; - tmbId: string; - evalModel: string; - appId: string; - usageId: string; - name: string; - createTime: Date; - finishTime?: Date; - score?: number; - errorMessage?: string; -}; - -export type EvalItemSchemaType = { - evalId: string; - question: string; - expectedResponse: string; - globalVariables?: Record; - history?: string; - response?: string; - responseTime?: Date; - finishTime?: Date; - status: EvaluationStatusEnum; - retry: number; - errorMessage?: string; - accuracy?: number; - relevance?: number; - semanticAccuracy?: number; - score?: number; -}; - -export type evaluationType = Pick< - EvaluationSchemaType, - 'name' | 'appId' | 'createTime' | 'finishTime' | 'evalModel' | 'errorMessage' | 'score' -> & { - _id: string; - executorAvatar: string; - executorName: string; - appAvatar: string; - appName: string; - completedCount: number; - errorCount: number; - totalCount: number; -}; - -export type listEvalItemsItem = EvalItemSchemaType & { - evalItemId: string; -}; diff --git a/packages/global/core/app/evaluation/utils.ts b/packages/global/core/app/evaluation/utils.ts deleted file mode 100644 index adad61c67817..000000000000 --- a/packages/global/core/app/evaluation/utils.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { VariableItemType } from '../type'; - -export const getEvaluationFileHeader = (appVariables?: VariableItemType[]) => { - if (!appVariables || appVariables.length === 0) return '*q,*a,history'; - - const variablesStr = appVariables - .map((item) => (item.required ? `*${item.key}` : item.key)) - .join(','); - return `${variablesStr},*q,*a,history`; -}; diff --git a/packages/global/core/app/plugin/type.d.ts b/packages/global/core/app/plugin/type.d.ts index 807e15a4d22e..cf58d01b6f32 100644 --- a/packages/global/core/app/plugin/type.d.ts +++ b/packages/global/core/app/plugin/type.d.ts @@ -23,6 +23,14 @@ export type PluginRuntimeType = { hasTokenFee?: boolean; }; +// system plugin version list item type (support i18n) +export type SystemPluginVersionListItemType = { + value: string; + description?: string; + inputs: FlowNodeInputItemType[]; + outputs: FlowNodeOutputItemType[]; +}; + // system plugin export type SystemPluginTemplateItemType = WorkflowTemplateType & { templateType: string; diff --git a/packages/global/core/app/type.d.ts b/packages/global/core/app/type.d.ts index a3514d88302f..c8405cfc3f68 100644 --- a/packages/global/core/app/type.d.ts +++ b/packages/global/core/app/type.d.ts @@ -86,6 +86,9 @@ export type AppDatasetSearchParamsType = { datasetSearchUsingExtensionQuery?: boolean; datasetSearchExtensionModel?: string; datasetSearchExtensionBg?: string; + + // database + generateSqlModel?: string; }; export type AppSimpleEditFormType = { // templateId: string; diff --git a/packages/global/core/app/utils.ts b/packages/global/core/app/utils.ts index e967f758454f..81e0cb993ba2 100644 --- a/packages/global/core/app/utils.ts +++ b/packages/global/core/app/utils.ts @@ -138,6 +138,10 @@ export const appWorkflow2Form = ({ node.inputs, NodeInputKeyEnum.datasetSearchExtensionBg ); + defaultAppForm.dataset.generateSqlModel = findInputValueByKey( + node.inputs, + NodeInputKeyEnum.generateSqlModel + ); } else if ( node.flowNodeType === FlowNodeTypeEnum.pluginModule || node.flowNodeType === FlowNodeTypeEnum.appModule || diff --git a/packages/global/core/chat/constants.ts b/packages/global/core/chat/constants.ts index 424678f685ec..ce30defcdfee 100644 --- a/packages/global/core/chat/constants.ts +++ b/packages/global/core/chat/constants.ts @@ -39,7 +39,8 @@ export enum ChatSourceEnum { feishu = 'feishu', official_account = 'official_account', wecom = 'wecom', - mcp = 'mcp' + mcp = 'mcp', + evaluation = 'evaluation' } export const ChatSourceMap = { @@ -82,6 +83,10 @@ export const ChatSourceMap = { [ChatSourceEnum.mcp]: { name: i18nT('common:core.chat.logs.mcp'), color: '#F97066' + }, + [ChatSourceEnum.evaluation]: { + name: i18nT('common:core.chat.logs.evaluation'), + color: '#8B5CF6' } }; diff --git a/packages/global/core/dataset/api.d.ts b/packages/global/core/dataset/api.d.ts index 1a3935127018..18ed0f51724c 100644 --- a/packages/global/core/dataset/api.d.ts +++ b/packages/global/core/dataset/api.d.ts @@ -2,7 +2,8 @@ import type { ChunkSettingsType, DatasetDataIndexItemType, DatasetDataFieldType, - DatasetSchemaType + DatasetSchemaType, + TableSchemaType } from './type'; import type { DatasetCollectionTypeEnum, @@ -30,6 +31,7 @@ export type DatasetUpdateBody = { vlmModel?: string; websiteConfig?: DatasetSchemaType['websiteConfig']; + databaseConfig?: DatasetSchemaType['databaseConfig']; externalReadUrl?: DatasetSchemaType['externalReadUrl']; defaultPermission?: DatasetSchemaType['defaultPermission']; chunkSettings?: DatasetSchemaType['chunkSettings']; @@ -67,6 +69,8 @@ export type CreateDatasetCollectionParams = DatasetCollectionStoreDataType & { createTime?: Date; updateTime?: Date; + tableSchema?: TableSchemaType; + forbid?: boolean; }; export type ApiCreateDatasetCollectionParams = DatasetCollectionStoreDataType & { diff --git a/packages/global/core/dataset/constants.ts b/packages/global/core/dataset/constants.ts index 75d36e896b0a..5b8e2b6b8d30 100644 --- a/packages/global/core/dataset/constants.ts +++ b/packages/global/core/dataset/constants.ts @@ -9,20 +9,40 @@ export enum DatasetTypeEnum { apiDataset = 'apiDataset', feishu = 'feishu', - yuque = 'yuque' + yuque = 'yuque', + database = 'database' +} +interface DatasetTypeConfig { + icon: string; + avatar: string; + label: any; + collectionLabel: string; + courseUrl?: string; + formConfig?: { + vectorModel?: { + isHidden?: boolean; + tip?: string; + }; + agentModel?: { + isHidden?: boolean; + tip?: string; + }; + vlmModel?: { + isHidden?: boolean; + tip?: string; + }; + }; +} +/* ------------ database_dataset -------------- */ +export enum DatabaseTypeEnum { + mysql = 'mysql', + postgresql = 'postgresql', + mssql = 'mssql', + sqlite = 'sqlite' } // @ts-ignore -export const ApiDatasetTypeMap: Record< - `${DatasetTypeEnum}`, - { - icon: string; - avatar: string; - label: any; - collectionLabel: string; - courseUrl?: string; - } -> = { +export const ApiDatasetTypeMap: Record<`${DatasetTypeEnum}`, DatasetTypeConfig> = { [DatasetTypeEnum.apiDataset]: { icon: 'core/dataset/externalDatasetOutline', avatar: 'core/dataset/externalDatasetColor', @@ -45,16 +65,7 @@ export const ApiDatasetTypeMap: Record< courseUrl: '/docs/introduction/guide/knowledge_base/yuque_dataset/' } }; -export const DatasetTypeMap: Record< - `${DatasetTypeEnum}`, - { - icon: string; - avatar: string; - label: any; - collectionLabel: string; - courseUrl?: string; - } -> = { +export const DatasetTypeMap: Record<`${DatasetTypeEnum}`, DatasetTypeConfig> = { ...ApiDatasetTypeMap, [DatasetTypeEnum.folder]: { icon: 'common/folderFill', @@ -75,6 +86,24 @@ export const DatasetTypeMap: Record< collectionLabel: i18nT('common:Website'), courseUrl: '/docs/introduction/guide/knowledge_base/websync/' }, + [DatasetTypeEnum.database]: { + icon: 'core/dataset/databaseOutline', + avatar: 'core/dataset/databaseColor', + label: i18nT('dataset:enterprise_database'), + collectionLabel: i18nT('dataset:enterprise_database'), + formConfig: { + agentModel: { + isHidden: true + }, + vlmModel: { + isHidden: true + }, + vectorModel: { + isHidden: false, + tip: i18nT('dataset:enterprise_database_embedding_model_tip') + } + } + }, [DatasetTypeEnum.externalFile]: { icon: 'core/dataset/externalDatasetOutline', avatar: 'core/dataset/externalDatasetColor', @@ -113,7 +142,8 @@ export enum DatasetCollectionTypeEnum { link = 'link', // one link externalFile = 'externalFile', apiFile = 'apiFile', - images = 'images' + images = 'images', + table = 'table' // database table } export const DatasetCollectionTypeMap = { [DatasetCollectionTypeEnum.folder]: { @@ -136,6 +166,9 @@ export const DatasetCollectionTypeMap = { }, [DatasetCollectionTypeEnum.images]: { name: i18nT('dataset:core.dataset.Image collection') + }, + [DatasetCollectionTypeEnum.table]: { + name: i18nT('common:core.dataset.table') } }; @@ -163,7 +196,7 @@ export enum DatasetCollectionDataProcessModeEnum { backup = 'backup', template = 'template', - + databaseSchema = 'databaseSchema', auto = 'auto' // abandon } export const DatasetCollectionDataProcessModeMap = { @@ -191,6 +224,10 @@ export const DatasetCollectionDataProcessModeMap = { [DatasetCollectionDataProcessModeEnum.template]: { label: i18nT('dataset:template_mode'), tooltip: i18nT('dataset:template_mode') + }, + [DatasetCollectionDataProcessModeEnum.databaseSchema]: { + label: i18nT('common:core.dataset.training.databaseSchema mode'), + tooltip: i18nT('common:core.dataset.import.databaseSchema Tip') } }; @@ -225,7 +262,8 @@ export enum ImportDataSourceEnum { externalFile = 'externalFile', apiDataset = 'apiDataset', reTraining = 'reTraining', - imageDataset = 'imageDataset' + imageDataset = 'imageDataset', + database = 'database' } export enum TrainingModeEnum { @@ -234,14 +272,16 @@ export enum TrainingModeEnum { qa = 'qa', auto = 'auto', image = 'image', - imageParse = 'imageParse' + imageParse = 'imageParse', + databaseSchema = 'databaseSchema' } /* ------------ search -------------- */ export enum DatasetSearchModeEnum { embedding = 'embedding', fullTextRecall = 'fullTextRecall', - mixedRecall = 'mixedRecall' + mixedRecall = 'mixedRecall', + database = 'database' } export const DatasetSearchModeMap = { @@ -262,6 +302,12 @@ export const DatasetSearchModeMap = { title: i18nT('common:core.dataset.search.mode.mixedRecall'), desc: i18nT('common:core.dataset.search.mode.mixedRecall desc'), value: DatasetSearchModeEnum.mixedRecall + }, + [DatasetSearchModeEnum.database]: { + icon: 'core/dataset/database', + title: i18nT('common:core.dataset.search.mode.database'), + desc: i18nT('common:core.dataset.search.mode.database desc'), + value: DatasetSearchModeEnum.database } }; diff --git a/packages/global/core/dataset/data/constants.ts b/packages/global/core/dataset/data/constants.ts index 2cc17562ad5a..bb2143460fea 100644 --- a/packages/global/core/dataset/data/constants.ts +++ b/packages/global/core/dataset/data/constants.ts @@ -5,7 +5,9 @@ export enum DatasetDataIndexTypeEnum { custom = 'custom', summary = 'summary', question = 'question', - image = 'image' + image = 'image', + column_des_index = 'column_des_index', + column_val_index = 'column_val_index' } export const DatasetDataIndexMap: Record< @@ -34,6 +36,14 @@ export const DatasetDataIndexMap: Record< [DatasetDataIndexTypeEnum.image]: { label: i18nT('dataset:data_index_image'), color: 'purple' + }, + [DatasetDataIndexTypeEnum.column_des_index]: { + label: i18nT('dataset:data_index_column_description'), + color: 'orange' + }, + [DatasetDataIndexTypeEnum.column_val_index]: { + label: i18nT('dataset:data_index_column_value'), + color: 'cyan' } }; export const defaultDatasetIndexData = DatasetDataIndexMap[DatasetDataIndexTypeEnum.custom]; diff --git a/packages/global/core/dataset/database/api.d.ts b/packages/global/core/dataset/database/api.d.ts new file mode 100644 index 000000000000..f1470c5b43ce --- /dev/null +++ b/packages/global/core/dataset/database/api.d.ts @@ -0,0 +1,158 @@ +import type { ColumnSchemaType, TableSchemaType, DatabaseConfig } from '../type'; +import { ConstraintSchemaType, ForeignKeySchemaType } from '../type'; +/*-------API Request & Response Types-------*/ + +export type CheckConnectionBody = { + datasetId: string; + databaseConfig: DatabaseConfig; +}; + +/*-------Create Database Collections Type-------*/ +export type DatabaseCollectionsTable = Omit & { forbid: boolean }; + +export type DatabaseCollectionsBody = { + tables: DatabaseCollectionsTable[]; +}; + +export type CreateDatabaseCollectionsBody = DatabaseCollectionsBody & { datasetId: string }; + +export type CreateDatabaseCollectionsResponse = { + collectionIds: string[]; +}; + +/*-------Detect Changes Type-------*/ +export enum StatusEnum { + add = 'add', + delete = 'delete', + available = 'available' +} + +export type DBTableColumn = ColumnSchemaType & { status: StatusEnum }; + +export type DBTableChange = Omit & { + forbid: boolean; + status: StatusEnum; + columns: Record; +}; + +export type DetectChangesQuery = { + datasetId: string; +}; + +export type DetectChangesResponse = { + tables: DBTableChange[]; + hasChanges: boolean; + summary: { + addedTables: number; + deletedTables: number; + modifiedTables: number; + addedColumns: number; + deletedColumns: number; + }; +}; +/*-------Apply Changes Type-------*/ +export type ApplyChangesBody = { + datasetId: string; + tables: Array; +}; + +export type ApplyChangesResponse = { + success: boolean; + processedItems: { + deletedTables: number; + updatedTables: number; + addedTables: number; + affectedDataRecords: number; + }; + errors: Array<{ + type: 'table' | 'column' | 'data'; + target: string; + error: string; + }>; + taskId?: string; +}; + +/*-------Database Search Test Type-------*/ +export type DatabaseSearchTestBody = { + datasetId: string; + query: string; + model?: string; +}; + +/*-------Dativate Retrieval Type-------*/ + +export type DativeCostraintKey = { + name: string; + column: string; +}; +export type DativeForeignKey = DativeCostraintKey & { + referenced_schema: string; + referenced_table: string; + referenced_column: string; +}; +export type DativeTableColumns = { + name: string; + type: string; + comment: string; + auto_increment: boolean; + nullable: boolean; + default: any; + examples: Array; + enabled: boolean; + value_index: boolean; +}; +export type DativeTable = { + name: string; + ns_name?: string; + comment: string; + columns: Record; + primary_keys: Array; + foreign_keys: Array; + enable: boolean; + score: number; +}; + +export type DativeSchema = { + name: string; // databaseName + comments?: string; + tables: Array; +}; + +// SQL Generation types +export type SqlGenerationRequest = { + source_config: { + type: string; + host: string; + port: number; + username: string; + password: string; + db_name: string; + }; + generate_sql_llm: { + model: string; + api_key?: string; + base_url?: string; + }; + evaluate_sql_llm: { + model: string; + api_key?: string; + base_url?: string; + }; + query: string; + result_num_limit: number; + retrieved_metadata?: DativeSchema; + evidence?: string; +}; + +export type SqlGenerationResponse = { + answer: string; + sql: string; + sql_res: { + data: any[]; + columns: string[]; + }; + input_tokens: number; + output_tokens: number; +}; + +export type SqlResultWithDatasetId = SqlGenerationResponse & { datasetId: string }; diff --git a/packages/global/core/dataset/type.d.ts b/packages/global/core/dataset/type.d.ts index 26df8a5305ae..04633344de08 100644 --- a/packages/global/core/dataset/type.d.ts +++ b/packages/global/core/dataset/type.d.ts @@ -10,7 +10,8 @@ import type { SearchScoreTypeEnum, TrainingModeEnum, ChunkSettingModeEnum, - ChunkTriggerConfigTypeEnum + ChunkTriggerConfigTypeEnum, + DatabaseTypeEnum } from './constants'; import type { DatasetPermission } from '../../support/permission/dataset/controller'; import type { @@ -22,7 +23,7 @@ import type { import type { SourceMemberType } from 'support/user/type'; import type { DatasetDataIndexTypeEnum } from './data/constants'; import type { ParentIdType } from 'common/parentFolder/type'; - +import type { CollectionStatusEnum } from 'core/dataset/collection/schema'; export type ChunkSettingsType = { trainingType?: DatasetCollectionDataProcessModeEnum; @@ -54,6 +55,60 @@ export type ChunkSettingsType = { qaPrompt?: string; }; +export type DatabaseConfig = { + clientType: DatabaseTypeEnum; + version?: string; + host: string; + port?: number; + database: string; + user: string; + password: string; + encrypt?: boolean; + poolSize?: number; +}; + +// Database table schema types +export type ColumnSchemaType = { + columnName: string; + columnType: string; + description: string; + examples: string[]; + forbid: boolean; + valueIndex: boolean; + + // Database attributes + isNullable?: boolean; + defaultValue?: string | null; + isAutoIncrement?: boolean; + isPrimaryKey?: boolean; + isForeignKey?: boolean; + relatedColumns?: string[]; + + // Extended metadata + metadata?: Record; +}; + +export type ConstraintSchemaType = { + name: string; + column: string; +}; + +export type ForeignKeySchemaType = ConstraintSchemaType & { + referredSchema: string; + referredTable: string; + referredColumns: string; +}; +export type TableSchemaType = { + tableName: string; + description: string; + exist: boolean; + columns: Record; + foreignKeys: ForeignKeySchemaType[]; + primaryKeys: string[]; + constraints: ConstraintSchemaType[]; + lastUpdated: Date; +}; + export type DatasetSchemaType = { _id: string; parentId: ParentIdType; @@ -82,6 +137,7 @@ export type DatasetSchemaType = { apiDatasetServer?: ApiDatasetServerType; + databaseConfig?: DatabaseConfig; // abandon autoSync?: boolean; externalReadUrl?: string; @@ -127,6 +183,9 @@ export type DatasetCollectionSchemaType = ChunkSettingsType & { // Parse settings customPdfParse?: boolean; trainingType: DatasetCollectionDataProcessModeEnum; + + // Database table schema (for database type collections) + tableSchema?: TableSchemaType; }; export type DatasetCollectionTagsSchemaType = { @@ -223,6 +282,7 @@ export type DatasetListItemType = { inheritPermission: boolean; private?: boolean; sourceMember?: SourceMemberType; + dataCount?: number; }; export type DatasetItemType = Omit & { diff --git a/packages/global/core/dataset/utils.ts b/packages/global/core/dataset/utils.ts index 42dbc2315dbf..ec084742ed5a 100644 --- a/packages/global/core/dataset/utils.ts +++ b/packages/global/core/dataset/utils.ts @@ -33,6 +33,9 @@ export function getSourceNameIcon({ sourceId?: string; }) { try { + if (sourceId?.startsWith('sql')) { + return 'core/dataset/database' + } const fileIcon = getFileIcon(decodeURIComponent(sourceName.replace(/%/g, '%25')), ''); if (fileIcon) { return fileIcon; @@ -40,7 +43,7 @@ export function getSourceNameIcon({ if (strIsLink(sourceId)) { return 'common/linkBlue'; } - } catch (error) {} + } catch (error) { } return 'file/fill/file'; } diff --git a/packages/global/core/evaluation/api.d.ts b/packages/global/core/evaluation/api.d.ts new file mode 100644 index 000000000000..1e93fad79e30 --- /dev/null +++ b/packages/global/core/evaluation/api.d.ts @@ -0,0 +1,119 @@ +import type { PaginationProps, PaginationResponse } from '../../../web/common/fetch/type'; +import type { + CreateEvaluationParams, + EvaluationSchemaType, + EvaluationItemSchemaType, + EvaluationDisplayType, + EvaluationItemDisplayType, + EvaluationDataItemType, + EvaluationStatistics +} from './type'; +import type { EvaluationStatusEnum } from './constants'; +import type { EvalDatasetDataKeyEnum } from './dataset/constants'; + +// ===== Common Types ===== +export type MessageResponse = { message: string }; + +// ===== Evaluation Task API ===== +export type EvalIdQuery = { evalId: string }; +// Create Evaluation +export type CreateEvaluationRequest = CreateEvaluationParams; +export type CreateEvaluationResponse = EvaluationSchemaType; + +// Update Evaluation +export type UpdateEvaluationRequest = EvalIdQuery & Partial; +export type UpdateEvaluationResponse = MessageResponse; + +// Get Evaluation Detail +export type EvaluationDetailRequest = EvalIdQuery; +export type EvaluationDetailResponse = EvaluationDisplayType; + +// Delete Evaluation +export type DeleteEvaluationRequest = EvalIdQuery; +export type DeleteEvaluationResponse = MessageResponse; + +// List Evaluations +export type ListEvaluationsRequest = PaginationProps<{ + searchKey?: string; + appName?: string; + appId?: string; +}>; +export type ListEvaluationsResponse = PaginationResponse; + +// Start Evaluation +export type StartEvaluationRequest = EvalIdQuery; +export type StartEvaluationResponse = MessageResponse; + +// Stop Evaluation +export type StopEvaluationRequest = EvalIdQuery; +export type StopEvaluationResponse = MessageResponse; + +// Get Evaluation Stats +export type StatsEvaluationRequest = EvalIdQuery; +export type EvaluationStatsResponse = EvaluationStatistics & { + belowThreshold: number; +}; + +// Export Evaluation Items +export type ExportEvaluationItemsRequest = { + evalId: string; + filters?: { + status?: EvaluationStatusEnum; + belowThreshold?: boolean; + userInput?: string; + expectedOutput?: string; + actualOutput?: string; + }; + headers?: { + itemId?: string; + userInput?: string; + expectedOutput?: string; + actualOutput?: string; + status?: string; + errorMessage?: string; + }; + metricColumns?: Array<{ + key: string; + label: string; + }>; + statusLabelMap?: Record; +}; + +// Retry Failed Evaluation Items +export type RetryFailedEvaluationItemsRequest = EvalIdQuery; +export type RetryFailedItemsResponse = { + message: string; + retryCount: number; +}; + +// ===== Evaluation Item API ===== + +export type EvalItemIdQuery = { evalItemId: string }; + +// List Evaluation Items +export type ListEvaluationItemsRequest = PaginationProps< + EvalIdQuery & { + status?: EvaluationStatusEnum; + belowThreshold?: boolean; + [EvalDatasetDataKeyEnum.UserInput]?: string; + [EvalDatasetDataKeyEnum.ExpectedOutput]?: string; + [EvalDatasetDataKeyEnum.ActualOutput]?: string; + } +>; +export type ListEvaluationItemsResponse = PaginationResponse; + +// Get Evaluation Item Detail +export type EvaluationItemDetailRequest = EvalItemIdQuery; +export type EvaluationItemDetailResponse = EvaluationItemSchemaType; + +// Update Evaluation Item +export type UpdateEvaluationItemRequest = EvalItemIdQuery & Partial; +export type UpdateEvaluationItemResponse = MessageResponse; + +// Retry Evaluation Item +export type RetryEvaluationItemRequest = EvalItemIdQuery; +export type RetryEvaluationItemResponse = MessageResponse; + +// Delete Evaluation Item +export type DeleteEvaluationItemRequest = EvalItemIdQuery; +export type DeleteEvaluationItemResponse = MessageResponse; diff --git a/packages/global/core/evaluation/constants.ts b/packages/global/core/evaluation/constants.ts new file mode 100644 index 000000000000..393e393e7b24 --- /dev/null +++ b/packages/global/core/evaluation/constants.ts @@ -0,0 +1,81 @@ +import { i18nT } from '../../../web/i18n/utils'; + +export const evaluationFileErrors = i18nT('dashboard_evaluation:eval_file_check_error'); + +export enum EvaluationStatusEnum { + queuing = 'queuing', + evaluating = 'evaluating', + completed = 'completed', + error = 'error' +} + +export const EvaluationStatusMap = { + [EvaluationStatusEnum.queuing]: { + name: i18nT('dashboard_evaluation:queuing') + }, + [EvaluationStatusEnum.evaluating]: { + name: i18nT('dashboard_evaluation:evaluating') + }, + [EvaluationStatusEnum.completed]: { + name: i18nT('dashboard_evaluation:completed') + }, + [EvaluationStatusEnum.error]: { + name: i18nT('dashboard_evaluation:error') + } +}; +export const EvaluationStatusValues = Object.values(EvaluationStatusEnum); + +export enum SummaryStatusEnum { + pending = 'pending', + generating = 'generating', + completed = 'completed', + failed = 'failed' +} + +export const SummaryStatusMap = { + [SummaryStatusEnum.pending]: { + name: i18nT('dashboard_evaluation:summary_pending') + }, + [SummaryStatusEnum.generating]: { + name: i18nT('dashboard_evaluation:summary_generating') + }, + [SummaryStatusEnum.completed]: { + name: i18nT('dashboard_evaluation:summary_done') + }, + [SummaryStatusEnum.failed]: { + name: i18nT('dashboard_evaluation:summary_failed') + } +}; + +export const SummaryStatusValues = Object.values(SummaryStatusEnum); + +// Calculation method enumeration +export enum CalculateMethodEnum { + mean = 'mean', + median = 'median' +} + +export const CaculateMethodMap = { + [CalculateMethodEnum.mean]: { + name: i18nT('dashboard_evaluation:method_mean') + }, + [CalculateMethodEnum.median]: { + name: i18nT('dashboard_evaluation:method_median') + } +}; + +export const CaculateMethodValues = Object.values(CalculateMethodEnum); + +// Score constants +export const PERFECT_SCORE = 1; + +// Validation length constants +export const MAX_NAME_LENGTH = 100; +export const MAX_DESCRIPTION_LENGTH = 100; +export const MAX_MODEL_NAME_LENGTH = 100; +export const MAX_USER_INPUT_LENGTH = 1000; +export const MAX_OUTPUT_LENGTH = 4000; +export const MAX_PROMPT_LENGTH = 4000; + +export const MAX_TOKEN_FOR_EVALUATION_SUMMARY = 1024; +export const TEMPERATURE_FOR_EVALUATION_SUMMARY = 1e-7; diff --git a/packages/global/core/evaluation/dataset/api.d.ts b/packages/global/core/evaluation/dataset/api.d.ts new file mode 100644 index 000000000000..955cdc597beb --- /dev/null +++ b/packages/global/core/evaluation/dataset/api.d.ts @@ -0,0 +1,229 @@ +import type { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type'; +import type { + EvalDatasetCollectionSchemaType, + EvalDatasetDataSchemaType, + EvalDatasetCollectionStatus, + EvalDatasetDataQualityStatus, + EvalDatasetDataQualityMetadata, + EvalDatasetDataSynthesisMetadata +} from './type'; +import type { EvalDatasetDataQualityResultEnum } from './constants'; +import type { EvalDatasetDataKeyEnum } from './constants'; + +type EvalDatasetCollectionBase = { + name: string; + description?: string; + evaluationModel?: string; +}; + +export type createEvalDatasetCollectionBody = EvalDatasetCollectionBase; + +export type updateEvalDatasetCollectionBody = Omit & { + name?: string; + collectionId: string; +}; + +export type deleteEvalDatasetCollectionQuery = { + collectionId: string; +}; + +export type listEvalDatasetCollectionBody = PaginationProps<{ + searchKey?: string; +}>; + +export type listEvalDatasetCollectionResponse = PaginationResponse< + Pick< + EvalDatasetCollectionSchemaType, + '_id' | 'name' | 'description' | 'createTime' | 'updateTime' + > & { + creatorAvatar?: string; + creatorName?: string; + status: EvalDatasetCollectionStatus; + dataItemsCount: number; + } +>; + +export type listEvalDatasetCollectionV2Body = { + searchKey?: string; + pageSize?: number; + pageNum?: number; + offset?: number; +}; + +export type listEvalDatasetCollectionV2Response = PaginationResponse< + Pick +>; +type QualityEvaluationBase = { + enableQualityEvaluation: boolean; + evaluationModel?: string; +}; + +export type importEvalDatasetFromFileBody = { + collectionId?: string; // Optional - use existing collection mode + // Optional fields for creating new collection mode + name?: string; + description?: string; +} & QualityEvaluationBase; +type EvalDatasetDataBase = { + [EvalDatasetDataKeyEnum.UserInput]: string; + [EvalDatasetDataKeyEnum.ActualOutput]?: string; + [EvalDatasetDataKeyEnum.ExpectedOutput]: string; + [EvalDatasetDataKeyEnum.Context]?: string[]; + [EvalDatasetDataKeyEnum.RetrievalContext]?: string[]; + qualityMetadata?: Partial; + synthesisMetadata?: Partial; + qualityResult?: EvalDatasetDataQualityResultEnum; +}; + +export type createEvalDatasetDataBody = EvalDatasetDataBase & + QualityEvaluationBase & { + collectionId: string; + }; + +export type listEvalDatasetDataBody = PaginationProps<{ + collectionId: string; + searchKey?: string; + status?: EvalDatasetDataQualityStatus; + qualityResult?: EvalDatasetDataQualityResultEnum; +}>; + +export type listEvalDatasetDataResponse = PaginationResponse< + Pick< + EvalDatasetDataSchemaType, + | '_id' + | EvalDatasetDataKeyEnum.UserInput + | EvalDatasetDataKeyEnum.ActualOutput + | EvalDatasetDataKeyEnum.ExpectedOutput + | EvalDatasetDataKeyEnum.Context + | EvalDatasetDataKeyEnum.RetrievalContext + | 'qualityMetadata' + | 'synthesisMetadata' + | 'qualityResult' + | 'createFrom' + | 'createTime' + | 'updateTime' + > +>; + +export type updateEvalDatasetDataBody = EvalDatasetDataBase & { + dataId: string; +}; + +export type qualityAssessmentBody = { + dataId: string; + evaluationModel?: string; +}; + +export type qualityAssessmentBatchBody = { + collectionId: string; + evaluationModel?: string; +}; + +export type qualityAssessmentBatchResponse = { + success: boolean; + message: string; + processedCount: number; + skippedCount: number; + errorCount: number; +}; + +export type deleteEvalDatasetDataQuery = { + dataId: string; +}; + +export type getEvalDatasetDataDetailQuery = { + dataId: string; +}; + +export type getEvalDatasetDataDetailResponse = Pick< + EvalDatasetDataSchemaType, + | '_id' + | 'teamId' + | 'tmbId' + | 'evalDatasetCollectionId' + | EvalDatasetDataKeyEnum.UserInput + | EvalDatasetDataKeyEnum.ActualOutput + | EvalDatasetDataKeyEnum.ExpectedOutput + | EvalDatasetDataKeyEnum.Context + | EvalDatasetDataKeyEnum.RetrievalContext + | 'qualityMetadata' + | 'synthesisMetadata' + | 'qualityResult' + | 'createFrom' + | 'createTime' + | 'updateTime' +>; + +export type smartGenerateEvalDatasetBody = { + collectionId?: string; + kbDatasetIds: string[]; + count?: number; + intelligentGenerationModel: string; + // Optional fields for creating new collection + name?: string; + description?: string; +}; + +export type listFailedTasksBody = { + collectionId: string; +}; + +export type listFailedTasksResponse = { + tasks: Array<{ + jobId: string; + // all about dataset + dataId: string; + datasetId: string; + datasetName: string; + collectionId: string; + collectionName: string; + errorMessage: string; + failedAt: Date; + attemptsMade: number; + maxAttempts: number; + }>; +}; + +export type retryTaskBody = { + collectionId: string; + jobId: string; +}; + +export type deleteTaskBody = { + collectionId: string; + jobId: string; +}; + +export type retryAllTaskBody = { + collectionId: string; +}; + +export type retryAllTaskResponse = { + success: boolean; + message: string; + totalFailedTasks: number; + retriedTasks: number; + failedRetries: number; +}; + +export type getEvalDatasetCollectionDetailQuery = { + collectionId: string; +}; + +export type getEvalDatasetCollectionDetailResponse = Pick< + EvalDatasetCollectionSchemaType, + | '_id' + | 'teamId' + | 'tmbId' + | 'name' + | 'description' + | 'createTime' + | 'updateTime' + | 'evaluationModel' + | 'metadata' +> & { + creatorAvatar?: string; + creatorName?: string; + status: EvalDatasetCollectionStatus; + dataItemsCount: number; +}; diff --git a/packages/global/core/evaluation/dataset/constants.ts b/packages/global/core/evaluation/dataset/constants.ts new file mode 100644 index 000000000000..8418c21df8f9 --- /dev/null +++ b/packages/global/core/evaluation/dataset/constants.ts @@ -0,0 +1,39 @@ +export enum EvalDatasetDataCreateFromEnum { + manual = 'manual', + fileImport = 'file_import', + intelligentGeneration = 'intelligent_generation' +} + +export const EvalDatasetDataCreateFromValues = Object.values(EvalDatasetDataCreateFromEnum); + +export enum EvalDatasetCollectionStatusEnum { + queuing = 'queuing', + processing = 'processing', + error = 'error', + ready = 'ready' +} + +export enum EvalDatasetDataQualityStatusEnum { + unevaluated = 'unevaluated', + queuing = 'queuing', + evaluating = 'evaluating', + error = 'error', + completed = 'completed' +} + +export enum EvalDatasetDataQualityResultEnum { + highQuality = 'highQuality', + needsOptimization = 'needsOptimization' +} + +export enum EvalDatasetDataKeyEnum { + UserInput = 'userInput', + ActualOutput = 'actualOutput', + ExpectedOutput = 'expectedOutput', + Context = 'context', + RetrievalContext = 'retrievalContext', + Metadata = 'metadata' +} + +export const EvalDatasetDataQualityStatusValues = Object.values(EvalDatasetDataQualityStatusEnum); +export const EvalDatasetDataQualityResultValues = Object.values(EvalDatasetDataQualityResultEnum); diff --git a/packages/global/core/evaluation/dataset/type.d.ts b/packages/global/core/evaluation/dataset/type.d.ts new file mode 100644 index 000000000000..5fc7189502e9 --- /dev/null +++ b/packages/global/core/evaluation/dataset/type.d.ts @@ -0,0 +1,65 @@ +import type { + EvaluationStatusEnum, + EvalDatasetDataCreateFromEnum, + EvalDatasetCollectionStatusEnum, + EvalDatasetDataQualityStatusEnum, + EvalDatasetDataQualityResultEnum, + EvalDatasetDataKeyEnum +} from './constants'; +import type { Usage } from '@fastgpt/global/support/wallet/usage/type'; + +export type EvalDatasetCollectionStatus = EvalDatasetCollectionStatusEnum; +export type EvalDatasetDataQualityStatus = EvalDatasetDataQualityStatusEnum; +export type EvalDatasetDataQualityResult = EvalDatasetDataQualityResultEnum; + +export type EvalDatasetDataQualityMetadata = { + status: EvalDatasetDataQualityStatusEnum; + score?: number; + reason?: string; + model?: string; + usages?: Usage[]; + runLogs?: any[]; + startTime?: Date; + finishTime?: Date; + queueTime?: Date; + error?: string; +}; + +export type EvalDatasetDataSynthesisMetadata = { + sourceDataId?: string; + sourceDatasetId?: string; + sourceCollectionId?: string; + intelligentGenerationModel?: string; + synthesizedAt?: Date; + generatedAt?: Date; +}; + +export type EvalDatasetCollectionSchemaType = { + _id: string; + teamId: string; + tmbId: string; + name: string; + description: string; + createTime: Date; + updateTime: Date; + metadata: Record; + evaluationModel?: string; +}; + +export type EvalDatasetDataSchemaType = { + _id: string; + teamId: string; + tmbId: string; + evalDatasetCollectionId: string; + [EvalDatasetDataKeyEnum.UserInput]: string; + [EvalDatasetDataKeyEnum.ActualOutput]: string; + [EvalDatasetDataKeyEnum.ExpectedOutput]: string; + [EvalDatasetDataKeyEnum.Context]: string[]; + [EvalDatasetDataKeyEnum.RetrievalContext]: string[]; + qualityMetadata: EvalDatasetDataQualityMetadata; + synthesisMetadata?: EvalDatasetDataSynthesisMetadata; + qualityResult?: EvalDatasetDataQualityResultEnum; + createFrom: EvalDatasetDataCreateFromEnum; + createTime: Date; + updateTime: Date; +}; diff --git a/packages/global/core/evaluation/metric/api.d.ts b/packages/global/core/evaluation/metric/api.d.ts new file mode 100644 index 000000000000..b8bfdf1e5d27 --- /dev/null +++ b/packages/global/core/evaluation/metric/api.d.ts @@ -0,0 +1,34 @@ +import type { EvalCase, EvalModelConfigType, MetricConfig } from './type'; + +export type CreateMetricBody = { + name: string; + description?: string; + prompt: string; +}; + +export type DebugMetricBody = { + evalCase: EvalCase; + llmConfig: EvalModelConfigType; + metricConfig: MetricConfig; +}; + +export type UpdateMetricBody = { + metricId: string; + name?: string; + description?: string; + prompt?: string; +}; + +export type DeleteMetricQuery = { + metricId: string; +}; + +export type DetailMetricQuery = { + metricId: string; +}; + +export type ListMetricsBody = { + pageNum?: number; + pageSize?: number; + searchKey?: string; +}; diff --git a/packages/global/core/evaluation/metric/constants.ts b/packages/global/core/evaluation/metric/constants.ts new file mode 100644 index 000000000000..665fa8ad546e --- /dev/null +++ b/packages/global/core/evaluation/metric/constants.ts @@ -0,0 +1,17 @@ +export enum EvalMetricTypeEnum { + Custom = 'custom_metric', + Builtin = 'builtin_metric' +} +export const EvalMetricTypeValues = Object.values(EvalMetricTypeEnum); + +export enum ModelTypeEnum { + LLM = 'llm', + EMBED = 'embed' +} +export const ModelTypeValues = Object.values(ModelTypeEnum); + +export enum MetricResultStatusEnum { + Success = 'success', + Failed = 'failed' +} +export const MetricResultStatusValues = Object.values(MetricResultStatusEnum); diff --git a/packages/global/core/evaluation/metric/type.d.ts b/packages/global/core/evaluation/metric/type.d.ts new file mode 100644 index 000000000000..5c54a8f1f209 --- /dev/null +++ b/packages/global/core/evaluation/metric/type.d.ts @@ -0,0 +1,179 @@ +import type { EvalDatasetDataKeyEnum } from '../dataset/constants'; +import type { ModelTypeEnum, EvalMetricTypeEnum, MetricResultStatusEnum } from './constants'; +import type { SourceMemberType } from '@fastgpt/global/support/user/type'; + +export type EvalModelConfigType = { + name: string; + baseUrl?: string; + apiKey?: string; + timeout?: number; + parameters?: Record; +}; + +export type EvalCase = { + [EvalDatasetDataKeyEnum.UserInput]?: string; + [EvalDatasetDataKeyEnum.ExpectedOutput]?: string; + [EvalDatasetDataKeyEnum.ActualOutput]?: string; + [EvalDatasetDataKeyEnum.Context]?: string[]; + [EvalDatasetDataKeyEnum.RetrievalContext]?: string[]; +}; + +export type MetricResult = { + metricName: string; + status?: MetricResultStatusEnum; + data?: EvaluationResult; + usages?: Usage[]; + error?: string; + totalPoints?: number; +}; + +export type EvalMetricSchemaType = { + _id: string; + teamId: string; + tmbId: string; + name: string; + description?: string; + type: EvalMetricTypeEnum; + prompt?: string; + + userInputRequired?: boolean; + actualOutputRequired?: boolean; + expectedOutputRequired?: boolean; + contextRequired?: boolean; + retrievalContextRequired?: boolean; + + embeddingRequired?: boolean; + llmRequired?: boolean; + + createTime: Date; + updateTime: Date; +}; + +export interface EvalMetricDisplayType extends EvalMetricSchemaType { + sourceMember: SourceMemberType; +} + +export type HttpConfig = { + url: string; + timeout?: number; +}; + +export type MetricConfig = { + metricName: string; + metricType: EvalMetricTypeEnum; + prompt?: string; +}; + +export type EvaluationRequest = { + evalCase: EvalCase; + metricConfig: MetricConfig; + embeddingConfig?: EvalModelConfigType | null; + llmConfig?: EvalModelConfigType | null; +}; + +export type EvaluationResult = { + metricName: string; + score: number; + reason?: string; + runLogs?: Record; +}; + +export type Usage = { + modelType: ModelTypeEnum; + promptTokens?: number; + completionTokens?: number; + totalTokens?: number; +}; + +export type EvaluationResponse = { + status: MetricResultStatusEnum; + data?: EvaluationResult; + usages?: Usage[]; + error?: string; +}; + +export type MetricDefinition = { + name: string; + description: string; + requireQuestion: boolean; + requireActualResponse: boolean; + requireExpectedResponse: boolean; + requireContext: boolean; + requireRetrievalContext: boolean; +}; + +export type SynthesisCase = { + context?: string[]; + themes?: string[]; +}; + +export type SynthesisOutput = { + qaPair: { + question: string; + answer: string; + }; + metadata?: Record; +}; + +export type SynthesisResult = { + synthesisName: string; + status: string; + data?: SynthesisOutput; + usages?: Usage[]; + error?: string; + totalPoints?: number; +}; + +export type SynthesizerConfig = { + synthesizerName: string; + config?: Record; +}; + +export type SynthesisRequest = { + synthesisCase: SynthesisCase; + synthesizerConfig: SynthesizerConfig; + llmConfig?: EvalModelConfigType; + embeddingConfig?: EvalModelConfigType; +}; + +export type SynthesisResponse = { + requestId: string; + status: string; + data?: SynthesisOutput; + usages?: Usage[]; + error?: string; + metadata?: Record; +}; + +export type SynthesisMetadata = { + chunkId?: string; + totalChunks?: number; + projectName?: string; + createdAt?: string; +}; + +export type DatasetSynthesisRequest = { + metadata?: SynthesisMetadata; + llmConfig: EvalModelConfigType; + embeddingConfig?: EvalModelConfigType; + synthesizerConfig: SynthesizerConfig; + inputData: { + context?: string[]; + themes?: string[]; + }; +}; + +export type DatasetSynthesisResponse = { + requestId: string; + status: string; + data?: { + qaPair: { + question: string; + answer: string; + }; + metadata?: Record; + }; + usages?: Usage[]; + error?: string; + metadata?: Record; +}; diff --git a/packages/global/core/evaluation/summary/api.d.ts b/packages/global/core/evaluation/summary/api.d.ts new file mode 100644 index 000000000000..c6f6b5a180c2 --- /dev/null +++ b/packages/global/core/evaluation/summary/api.d.ts @@ -0,0 +1,73 @@ +import type { CalculateMethodEnum } from '../constants'; + +// ===== Common Types ===== + +export type MetricConfigItem = { + metricId: string; + thresholdValue?: number; + weight?: number; + calculateType?: CalculateMethodEnum; +}; + +export type MetricConfigItemWithName = Omit & { + weight: number; // Required in config responses + metricName: string; // Metric name for display + metricDescription: string; // Metric description for display +}; + +export type UpdateMetricConfigItem = Omit & { + metricId: string; + thresholdValue: number; + weight?: number; +}; + +// ===== Config API Types ===== + +export type UpdateSummaryConfigBody = { + evalId: string; + calculateType: CalculateMethodEnum; + metricsConfig: UpdateMetricConfigItem[]; +}; + +export type UpdateSummaryConfigResponse = { + message: string; +}; + +// ===== Config Detail API Types ===== + +export type GetConfigDetailQuery = { + evalId: string; +}; + +export type GetConfigDetailResponse = { + calculateType: CalculateMethodEnum; + calculateTypeName: string; + metricsConfig: MetricConfigItemWithName[]; +}; + +// ===== Summary Detail API Types ===== + +export type GetEvaluationSummaryQuery = { + evalId: string; +}; + +export type EvaluationSummaryResponse = { + data: Array<{ + metricId: string; + metricName: string; + metricScore: number; + summary: string; + summaryStatus: string; + errorReason?: string; + completedItemCount: number; + overThresholdItemCount: number; + underThresholdRate: number; + threshold: number; + weight: number; + customSummary: string; + }>; + aggregateScore: number; +}; + +// ===== Generate Summary API Types ===== +// Note: GenerateSummaryParams and GenerateSummaryResponse are already defined in ../type.d.ts diff --git a/packages/global/core/evaluation/type.d.ts b/packages/global/core/evaluation/type.d.ts new file mode 100644 index 000000000000..04196eafe9cb --- /dev/null +++ b/packages/global/core/evaluation/type.d.ts @@ -0,0 +1,230 @@ +import type { EvaluationStatusEnum, CalculateMethodEnum, SummaryStatusEnum } from './constants'; +import type { EvalDatasetDataKeyEnum } from './dataset/constants'; +import type { EvalDatasetDataSchemaType } from './dataset/type'; +import type { MetricResult, EvalMetricSchemaType } from './metric/type'; +import type { EvaluationPermission } from '../../support/permission/evaluation/controller'; +import type { SourceMemberType } from '../../support/user/type'; + +// Evaluation target related types +export interface WorkflowConfig { + appId: string; + versionId: string; // Required app version ID + chatConfig?: any; + // Extended fields populated by aggregation queries + appName?: string; // App name from apps collection + avatar?: string; // App avatar from apps collection + versionName?: string; // Version name from app_versions collection +} + +export interface EvalTarget { + type: 'workflow'; + config: WorkflowConfig; +} + +export interface RuntimeConfig { + llm?: string; // LLM model selection + embedding?: string; // Embedding model selection +} + +// Summary configuration type +export type SummaryConfig = { + metricId: string; + metricName: string; + weight: number; + summary: string; + summaryStatus: SummaryStatusEnum; + errorReason: string; +}; + +export type UpdateStatusParams = { + evalId: string; + metricId: string; + status: SummaryStatusEnum; + errorReason?: string; +}; + +// SummaryData type containing calculation method and configs +export type SummaryData = { + calculateType: CalculateMethodEnum; // Calculation method for all metrics + summaryConfigs: SummaryConfig[]; // Array of summary configs, one for each metric +}; + +// Evaluator configuration type +export interface EvaluatorSchema { + metric: EvalMetricSchemaType; // Contains complete metric configuration + runtimeConfig: RuntimeConfig; // Runtime configuration including LLM model + thresholdValue?: number; + scoreScaling?: number; // Score scaling factor, default is 1 +} + +// Statistics information for evaluation task +export interface EvaluationStatistics { + total: number; + completed: number; + evaluating: number; + queuing: number; + error: number; +} + +// Improved evaluation task types +export type EvaluationSchemaType = { + _id: string; + teamId: string; + tmbId: string; + name: string; + description?: string; + evalDatasetCollectionId: string; // Associated evaluation dataset collection + target: EvalTarget; // Embedded evaluation target + evaluators: EvaluatorSchema[]; // Array of evaluator configurations + summaryData: SummaryData; // Summary configuration containing calculateType and summaryConfigs + usageId: string; + status: EvaluationStatusEnum; + createTime: Date; + finishTime?: Date; + errorMessage?: string; + statistics?: EvaluationStatistics; +}; + +/** + * Target call parameters for evaluation execution extensibility. + * Provides structured way to pass variables and additional configuration + * to target instances during evaluation. + * + * @example + * ```typescript + * const params: TargetCallParams = { + * variables: { userId: "123", theme: "dark" }, // Named variables for target execution + * timeout: 5000, // Custom timeout setting + * retryCount: 3, // Retry configuration + * customHeaders: { "X-Custom": "value" } // Additional parameters + * } + * ``` + */ +export interface TargetCallParams { + /** Named variables to be passed to target execution (replaces globalVariables) */ + variables?: Record; + /** Index signature allowing additional extensible parameters */ + [key: string]: any; +} + +/** + * Evaluation data item that contains only the necessary fields for evaluation execution. + * Used in evaluation context where both dataset content and execution parameters are needed. + */ +export type EvaluationDataItemType = Pick< + EvalDatasetDataSchemaType, + | '_id' + | EvalDatasetDataKeyEnum.UserInput + | EvalDatasetDataKeyEnum.ExpectedOutput + | EvalDatasetDataKeyEnum.Context +> & { + targetCallParams?: TargetCallParams; +}; + +// Evaluation item type (batch: one dataItem + one target + multiple evaluators) +export type EvaluationItemSchemaType = { + _id: string; + evalId: string; + // Dependent component configurations + dataItem: EvaluationDataItemType; + // Execution results + targetOutput?: TargetOutput; // Actual output from target + evaluatorOutputs?: MetricResult[]; // Results from multiple evaluators + status: EvaluationStatusEnum; + finishTime?: Date; + errorMessage?: string; +}; + +// Evaluation target input/output types +export interface TargetInput { + [EvalDatasetDataKeyEnum.UserInput]: string; + [EvalDatasetDataKeyEnum.Context]?: string[]; + targetCallParams?: TargetCallParams; +} + +export interface TargetOutput { + [EvalDatasetDataKeyEnum.ActualOutput]: string; + [EvalDatasetDataKeyEnum.RetrievalContext]?: string[]; + usage?: any; + responseTime: number; + chatId: string; + aiChatItemDataId: string; +} + +export type EvaluationWithPerType = EvaluationSchemaType & { + permission: EvaluationPermission; +}; + +// ===== Display Types ===== + +// Extended SummaryConfig for display purposes (includes runtime calculated fields) +export type SummaryConfigDisplay = SummaryConfig & { + score: number; // Real-time calculated metric score + completedItemCount: number; // Real-time calculated completed items count + overThresholdItemCount: number; // Real-time calculated over threshold items count +}; + +export type EvaluationDisplayType = Omit & { + evalDatasetCollectionName?: string; + metricNames: string[]; + private: boolean; + sourceMember: SourceMemberType; + summaryData: Omit & { + summaryConfigs: SummaryConfigDisplay[]; // Use extended version for display + }; + aggregateScore?: number; // Real-time calculated aggregate score +}; + +export type EvaluationItemDisplayType = EvaluationItemSchemaType & { + evaluators: Array<{ + metric: EvalMetricSchemaType; // Contains complete metric configuration + thresholdValue?: number; // Threshold value for this evaluator + weight?: number; + }>; // Array of evaluator configurations +}; + +export interface CreateEvaluationParams { + name: string; + description?: string; + evalDatasetCollectionId: string; + target: EvalTarget; // Only supports workflow type target configuration + evaluators: EvaluatorSchema[]; // Replace metricIds with evaluators + autoStart?: boolean; // Whether to automatically start the evaluation task after creation (default: true) +} + +type EvaluationParamsType = Omit; + +type EvaluationParamsWithDeafultConfigType = Omit & { + summaryData: { + calculateType: CalculateMethodEnum; + summaryConfigs: SummaryConfig[]; + }; +}; + +// Queue job data types +export interface EvaluationTaskJobData { + evalId: string; +} + +export interface EvaluationItemJobData { + evalId: string; + evalItemId: string; +} + +// ===== Summary Generate API Types ===== + +export type GenerateSummaryParams = { + evalId: string; + metricIds: string[]; +}; + +export type GenerateSummaryResponse = { + success: boolean; + message: string; +}; + +export type SummaryGenerationTaskData = { + evalId: string; + metricId: string; + evaluatorIndex: number; +}; diff --git a/packages/global/core/evaluation/validate.ts b/packages/global/core/evaluation/validate.ts new file mode 100644 index 000000000000..fe0942662c01 --- /dev/null +++ b/packages/global/core/evaluation/validate.ts @@ -0,0 +1,106 @@ +// Validation error structure +export interface ValidationError { + code: string; // Error code for programmatic handling + message: string; // Human-readable error message + field?: string; // Field name that caused the error (optional) + debugInfo?: Record; // Additional debug information +} + +export interface ValidationResult { + isValid: boolean; + errors: ValidationError[]; + warnings?: ValidationError[]; // Optional warnings that don't prevent usage +} + +// Base abstract class for all validatable objects +export abstract class Validatable { + /** + * Validate this object and return detailed validation result + */ + abstract validate(): Promise; + + /** + * Convenience method to check if this object is valid + */ + async isValid(): Promise { + const result = await this.validate(); + return result.isValid; + } + + /** + * Convenience method to get validation error messages + */ + async getValidationErrors(): Promise { + const result = await this.validate(); + return result.errors; + } +} + +// Utility functions for ValidationResult +export const ValidationResultUtils = { + /** + * Format validation errors into a readable string + */ + formatErrors(result: ValidationResult, separator = '; '): string { + return (result.errors || []).map((err) => `${err.code}: ${err.message}`).join(separator); + }, + + /** + * Format validation warnings into a readable string + */ + formatWarnings(result: ValidationResult, separator = '; '): string { + return result.warnings?.map((err) => `${err.code}: ${err.message}`).join(separator) || ''; + }, + + /** + * Get a summary message for validation result + */ + getSummaryMessage(result: ValidationResult): string { + if (result.isValid) { + const warningMsg = result.warnings?.length ? ` (${result.warnings.length} warnings)` : ''; + return `Validation passed${warningMsg}`; + } else { + return `Validation failed: ${ValidationResultUtils.formatErrors(result)}`; + } + }, + + /** + * Create an Error object from ValidationResult + */ + toError(result: ValidationResult): Error { + if (result.isValid) { + throw new Error('Cannot create error from valid validation result'); + } + return new Error(ValidationResultUtils.formatErrors(result)); + }, + + /** + * Create an Error object with only the first error code for i18n translation + * Logs detailed validation errors for debugging + */ + toTranslatableError(result: ValidationResult): Error { + if (result.isValid) { + throw new Error('Cannot create error from valid validation result'); + } + + // Log detailed validation errors for debugging + // Note: addLog is not available in global package, will be logged by caller + const debugInfo = { + errors: result.errors, + formattedMessage: ValidationResultUtils.formatErrors(result), + summary: ValidationResultUtils.getSummaryMessage(result) + }; + + // Return error with only the first error code for i18n translation + const firstError = result.errors[0]; + if (!firstError) { + return new Error('Validation failed with unknown error'); + } + + // Attach debug info to the error for caller to log + const error = new Error(firstError.code); + (error as any).validationDebugInfo = debugInfo; + + return error; + } +}; diff --git a/packages/global/core/workflow/constants.ts b/packages/global/core/workflow/constants.ts index 1cb29ffb6382..1ecd01b8b1f9 100644 --- a/packages/global/core/workflow/constants.ts +++ b/packages/global/core/workflow/constants.ts @@ -197,6 +197,8 @@ export enum NodeInputKeyEnum { datasetSearchExtensionBg = 'datasetSearchExtensionBg', collectionFilterMatch = 'collectionFilterMatch', authTmbId = 'authTmbId', + searchColumnslLimitRatio = 'searchRatio', + generateSqlModel = 'generateSqlModel', datasetDeepSearch = 'datasetDeepSearch', datasetDeepSearchModel = 'datasetDeepSearchModel', datasetDeepSearchMaxTimes = 'datasetDeepSearchMaxTimes', diff --git a/packages/global/core/workflow/runtime/type.d.ts b/packages/global/core/workflow/runtime/type.d.ts index e5925790c925..317516fec3b2 100644 --- a/packages/global/core/workflow/runtime/type.d.ts +++ b/packages/global/core/workflow/runtime/type.d.ts @@ -30,6 +30,7 @@ import type { } from '../template/system/interactive/type'; import type { SearchDataResponseItemType } from '../../dataset/type'; import type { localeType } from '../../../common/i18n/type'; +import type { SqlResultWithDatasetId } from '../../../core/dataset/database/api.d'; export type ExternalProviderType = { openaiAccount?: OpenaiAccountType; externalWorkflowVariables?: Record; @@ -183,6 +184,8 @@ export type DispatchNodeResponseType = { inputTokens: number; outputTokens: number; }; + // text2sql result + sqlResult?: SqlResultWithDatasetId[]; // dataset concat concatLength?: number; diff --git a/packages/global/core/workflow/template/system/datasetSearch.ts b/packages/global/core/workflow/template/system/datasetSearch.ts index 85c8e85a1c97..8b0c87936399 100644 --- a/packages/global/core/workflow/template/system/datasetSearch.ts +++ b/packages/global/core/workflow/template/system/datasetSearch.ts @@ -134,6 +134,15 @@ export const DatasetSearchModule: FlowNodeTemplateType = { valueType: WorkflowIOValueTypeEnum.string, isPro: true, description: i18nT('workflow:filter_description') + }, + + // database + { + key: NodeInputKeyEnum.generateSqlModel, + renderTypeList: [FlowNodeInputTypeEnum.hidden], + label: i18nT('common:search_model'), + value: '', + valueType: WorkflowIOValueTypeEnum.string } ], outputs: [ diff --git a/packages/global/core/workflow/type/io.d.ts b/packages/global/core/workflow/type/io.d.ts index 382b212da267..95fa6e9a7acc 100644 --- a/packages/global/core/workflow/type/io.d.ts +++ b/packages/global/core/workflow/type/io.d.ts @@ -3,6 +3,7 @@ import type { LLMModelTypeEnum } from '../../ai/constants'; import type { WorkflowIOValueTypeEnum, NodeInputKeyEnum, NodeOutputKeyEnum } from '../constants'; import type { FlowNodeInputTypeEnum, FlowNodeOutputTypeEnum } from '../node/constant'; import type { SecretValueType } from '../../../common/secret/type'; +import type { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants'; // Dynamic input field configuration export type CustomFieldConfigType = { @@ -112,6 +113,8 @@ export type SelectedDatasetType = { avatar: string; name: string; vectorModel: EmbeddingModelItemType; + dataCount?: number; + datasetType?: `${DatasetTypeEnum}`; }[]; /* http node */ diff --git a/packages/global/support/permission/constant.ts b/packages/global/support/permission/constant.ts index de254f89b487..1c59c565f0b3 100644 --- a/packages/global/support/permission/constant.ts +++ b/packages/global/support/permission/constant.ts @@ -49,7 +49,8 @@ export const PermissionTypeMap = { export enum PerResourceTypeEnum { team = 'team', app = 'app', - dataset = 'dataset' + dataset = 'dataset', + evaluation = 'evaluation' } /* new permission */ diff --git a/packages/global/support/permission/evaluation/constant.ts b/packages/global/support/permission/evaluation/constant.ts new file mode 100644 index 000000000000..ddcd06a24868 --- /dev/null +++ b/packages/global/support/permission/evaluation/constant.ts @@ -0,0 +1,21 @@ +import { type PermissionListType, type RoleListType, type RolePerMapType } from '../type'; +import { CommonPerList, CommonRoleList, CommonRolePerMap } from '../constant'; + +// 评估模块权限列表 (沿用通用权限,无特殊权限) +export const EvaluationPerList: PermissionListType = CommonPerList; + +// 评估模块角色列表 (沿用通用角色) +export const EvaluationRoleList: RoleListType = { + ...CommonRoleList +} as const; + +// 评估模块角色权限映射 (沿用通用映射) +export const EvaluationRolePerMap: RolePerMapType = CommonRolePerMap; + +// 评估模块默认权限值 +export const EvaluationDefaultRoleVal = 0; + +// 常用权限值导出 +export const EvaluationReadPermissionVal = EvaluationPerList.read; +export const EvaluationWritePermissionVal = EvaluationPerList.write; +export const EvaluationManagePermissionVal = EvaluationPerList.manage; diff --git a/packages/global/support/permission/evaluation/controller.ts b/packages/global/support/permission/evaluation/controller.ts new file mode 100644 index 000000000000..5e8ed700702e --- /dev/null +++ b/packages/global/support/permission/evaluation/controller.ts @@ -0,0 +1,23 @@ +import { type PerConstructPros, Permission } from '../controller'; +import { + EvaluationDefaultRoleVal, + EvaluationPerList, + EvaluationRoleList, + EvaluationRolePerMap +} from './constant'; + +export class EvaluationPermission extends Permission { + constructor(props?: PerConstructPros) { + if (!props) { + props = { + role: EvaluationDefaultRoleVal + }; + } else if (!props?.role) { + props.role = EvaluationDefaultRoleVal; + } + props.roleList = EvaluationRoleList; + props.rolePerMap = EvaluationRolePerMap; + props.perList = EvaluationPerList; + super(props); + } +} diff --git a/packages/global/support/permission/user/constant.ts b/packages/global/support/permission/user/constant.ts index 9a99d873bc66..b22cd0351428 100644 --- a/packages/global/support/permission/user/constant.ts +++ b/packages/global/support/permission/user/constant.ts @@ -12,20 +12,23 @@ import { sumPer } from '../utils'; export enum TeamPerKeyEnum { appCreate = 'appCreate', datasetCreate = 'datasetCreate', - apikeyCreate = 'apikeyCreate' + apikeyCreate = 'apikeyCreate', + evaluationCreate = 'evaluationCreate' } export enum TeamRoleKeyEnum { appCreate = 'appCreate', datasetCreate = 'datasetCreate', - apikeyCreate = 'apikeyCreate' + apikeyCreate = 'apikeyCreate', + evaluationCreate = 'evaluationCreate' } export const TeamPerList: PermissionListType = { ...CommonPerList, apikeyCreate: 0b100000, appCreate: 0b001000, - datasetCreate: 0b010000 + datasetCreate: 0b010000, + evaluationCreate: 0b1000000 }; export const TeamRoleList: RoleListType = { @@ -58,6 +61,12 @@ export const TeamRoleList: RoleListType = { description: '', name: i18nT('account_team:permission_apikeyCreate'), value: 0b100000 + }, + [TeamRoleKeyEnum.evaluationCreate]: { + checkBoxType: 'multiple', + description: '', + name: i18nT('account_team:permission_evaluationCreate'), + value: 0b1000000 } }; @@ -78,6 +87,14 @@ export const TeamRolePerMap: RolePerMapType = new Map([ [ TeamRoleList['apikeyCreate'].value, sumPer(TeamPerList.apikeyCreate, CommonPerList.read, CommonPerList.write) as PermissionValueType + ], + [ + TeamRoleList['evaluationCreate'].value, + sumPer( + TeamPerList.evaluationCreate, + CommonPerList.read, + CommonPerList.write + ) as PermissionValueType ] ]); @@ -85,6 +102,7 @@ export const TeamReadRoleVal = TeamRoleList['read'].value; export const TeamWriteRoleVal = TeamRoleList['write'].value; export const TeamManageRoleVal = TeamRoleList['manage'].value; export const TeamAppCreateRoleVal = TeamRoleList['appCreate'].value; +export const TeamEvaluationCreateRoleVal = TeamRoleList['evaluationCreate'].value; export const TeamDatasetCreateRoleVal = TeamRoleList['datasetCreate'].value; export const TeamApikeyCreateRoleVal = TeamRoleList['apikeyCreate'].value; export const TeamDefaultRoleVal = TeamReadRoleVal; @@ -95,4 +113,5 @@ export const TeamManagePermissionVal = TeamPerList.manage; export const TeamAppCreatePermissionVal = TeamPerList.appCreate; export const TeamDatasetCreatePermissionVal = TeamPerList.datasetCreate; export const TeamApikeyCreatePermissionVal = TeamPerList.apikeyCreate; +export const TeamEvaluationCreatePermissionVal = TeamPerList.evaluationCreate; export const TeamDefaultPermissionVal = TeamReadPermissionVal; diff --git a/packages/global/support/permission/user/controller.ts b/packages/global/support/permission/user/controller.ts index b92f98072da8..2abdfc2e10d1 100644 --- a/packages/global/support/permission/user/controller.ts +++ b/packages/global/support/permission/user/controller.ts @@ -3,6 +3,7 @@ import { TeamApikeyCreateRoleVal, TeamAppCreateRoleVal, TeamDatasetCreateRoleVal, + TeamEvaluationCreateRoleVal, TeamDefaultRoleVal, TeamPerList, TeamRoleList, @@ -13,9 +14,11 @@ export class TeamPermission extends Permission { hasAppCreateRole: boolean = false; hasDatasetCreateRole: boolean = false; hasApikeyCreateRole: boolean = false; + hasEvaluationCreateRole: boolean = false; hasAppCreatePer: boolean = false; hasDatasetCreatePer: boolean = false; hasApikeyCreatePer: boolean = false; + hasEvaluationCreatePer: boolean = false; constructor(props?: PerConstructPros) { if (!props) { @@ -34,9 +37,11 @@ export class TeamPermission extends Permission { this.hasAppCreateRole = this.checkRole(TeamAppCreateRoleVal); this.hasDatasetCreateRole = this.checkRole(TeamDatasetCreateRoleVal); this.hasApikeyCreateRole = this.checkRole(TeamApikeyCreateRoleVal); + this.hasEvaluationCreateRole = this.checkRole(TeamEvaluationCreateRoleVal); this.hasAppCreatePer = this.checkPer(TeamAppCreateRoleVal); this.hasDatasetCreatePer = this.checkPer(TeamDatasetCreateRoleVal); this.hasApikeyCreatePer = this.checkPer(TeamApikeyCreateRoleVal); + this.hasEvaluationCreatePer = this.checkPer(TeamEvaluationCreateRoleVal); }); } } diff --git a/packages/global/support/user/audit/constants.ts b/packages/global/support/user/audit/constants.ts index 8c60385acbc2..6fb3c19ec373 100644 --- a/packages/global/support/user/audit/constants.ts +++ b/packages/global/support/user/audit/constants.ts @@ -76,6 +76,40 @@ export enum AuditEventEnum { CREATE_DATA = 'CREATE_DATA', UPDATE_DATA = 'UPDATE_DATA', DELETE_DATA = 'DELETE_DATA', + //Evaluation Dataset + CREATE_EVALUATION_DATASET_COLLECTION = 'CREATE_EVALUATION_DATASET_COLLECTION', + UPDATE_EVALUATION_DATASET_COLLECTION = 'UPDATE_EVALUATION_DATASET_COLLECTION', + DELETE_EVALUATION_DATASET_COLLECTION = 'DELETE_EVALUATION_DATASET_COLLECTION', + CREATE_EVALUATION_DATASET_DATA = 'CREATE_EVALUATION_DATASET_DATA', + UPDATE_EVALUATION_DATASET_DATA = 'UPDATE_EVALUATION_DATASET_DATA', + DELETE_EVALUATION_DATASET_DATA = 'DELETE_EVALUATION_DATASET_DATA', + QUALITY_ASSESSMENT_EVALUATION_DATA = 'QUALITY_ASSESSMENT_EVALUATION_DATA', + SMART_GENERATE_EVALUATION_DATA = 'SMART_GENERATE_EVALUATION_DATA', + DELETE_EVALUATION_DATASET_TASK = 'DELETE_EVALUATION_DATASET_TASK', + RETRY_EVALUATION_DATASET_TASK = 'RETRY_EVALUATION_DATASET_TASK', + IMPORT_EVALUATION_DATASET_DATA = 'IMPORT_EVALUATION_DATASET_DATA', + //Evaluation Task + CREATE_EVALUATION_TASK = 'CREATE_EVALUATION_TASK', + UPDATE_EVALUATION_TASK = 'UPDATE_EVALUATION_TASK', + DELETE_EVALUATION_TASK = 'DELETE_EVALUATION_TASK', + START_EVALUATION_TASK = 'START_EVALUATION_TASK', + STOP_EVALUATION_TASK = 'STOP_EVALUATION_TASK', + RETRY_EVALUATION_TASK = 'RETRY_EVALUATION_TASK', + //Evaluation Task Item + DELETE_EVALUATION_TASK_ITEM = 'DELETE_EVALUATION_TASK_ITEM', + UPDATE_EVALUATION_TASK_ITEM = 'UPDATE_EVALUATION_TASK_ITEM', + RETRY_EVALUATION_TASK_ITEM = 'RETRY_EVALUATION_TASK_ITEM', + EXPORT_EVALUATION_TASK_ITEMS = 'EXPORT_EVALUATION_TASK_ITEMS', + // Evaluation Metric + CREATE_EVALUATION_METRIC = 'CREATE_EVALUATION_METRIC', + UPDATE_EVALUATION_METRIC = 'UPDATE_EVALUATION_METRIC', + DELETE_EVALUATION_METRIC = 'DELETE_EVALUATION_METRIC', + DEBUG_EVALUATION_METRIC = 'DEBUG_EVALUATION_METRIC', + //Evaluation Task DataItem Aggregation + DELETE_EVALUATION_TASK_DATA_ITEM = 'DELETE_EVALUATION_TASK_DATA_ITEM', + UPDATE_EVALUATION_TASK_DATA_ITEM = 'UPDATE_EVALUATION_TASK_DATA_ITEM', + RETRY_EVALUATION_TASK_DATA_ITEM = 'RETRY_EVALUATION_TASK_DATA_ITEM', + EXPORT_EVALUATION_TASK_DATA_ITEMS = 'EXPORT_EVALUATION_TASK_DATA_ITEMS', //SearchTest SEARCH_TEST = 'SEARCH_TEST', //Account @@ -88,7 +122,9 @@ export enum AuditEventEnum { SET_INVOICE_HEADER = 'SET_INVOICE_HEADER', CREATE_API_KEY = 'CREATE_API_KEY', UPDATE_API_KEY = 'UPDATE_API_KEY', - DELETE_API_KEY = 'DELETE_API_KEY' + DELETE_API_KEY = 'DELETE_API_KEY', + GENERATE_EVALUATION_SUMMARY = 'GENERATE_EVALUATION_SUMMARY', + UPDATE_EVALUATION_SUMMARY_CONFIG = 'UPDATE_EVALUATION_SUMMARY_CONFIG' } export type AuditEventParamsType = { diff --git a/packages/global/support/user/inform/constants.ts b/packages/global/support/user/inform/constants.ts index dfd19e6a4258..3ba6b74a3c34 100644 --- a/packages/global/support/user/inform/constants.ts +++ b/packages/global/support/user/inform/constants.ts @@ -1,3 +1,5 @@ +import { i18nT } from '../../../../web/i18n/utils'; + export enum InformLevelEnum { 'common' = 'common', 'important' = 'important', @@ -6,13 +8,13 @@ export enum InformLevelEnum { export const InformLevelMap = { [InformLevelEnum.common]: { - label: '普通' + label: i18nT('admin:level_normal_text') }, [InformLevelEnum.important]: { - label: '重要' + label: i18nT('admin:level_important_text') }, [InformLevelEnum.emergency]: { - label: '紧急' + label: i18nT('admin:level_urgent_text') } }; diff --git a/packages/global/support/user/inform/type.d.ts b/packages/global/support/user/inform/type.d.ts index 4a87dc6a35dc..79b3343a1ebd 100644 --- a/packages/global/support/user/inform/type.d.ts +++ b/packages/global/support/user/inform/type.d.ts @@ -1,7 +1,7 @@ import type { InformLevelEnum, SendInformTemplateCodeEnum } from './constants'; export type SendInformProps = { - level: `${InformLevelEnum}`; + level: string; templateCode: `${SendInformTemplateCodeEnum}`; templateParam: Record; customLockMinutes?: number; // custom lock minutes diff --git a/packages/global/support/wallet/sub/type.d.ts b/packages/global/support/wallet/sub/type.d.ts index 2f89b7c95852..c215043660ce 100644 --- a/packages/global/support/wallet/sub/type.d.ts +++ b/packages/global/support/wallet/sub/type.d.ts @@ -11,6 +11,10 @@ export type TeamStandardSubPlanItemType = { maxDatasetAmount: number; chatHistoryStoreDuration: number; // n day maxDatasetSize: number; + maxEvaluationTaskAmount: number; // max evaluation task amount + maxEvalDatasetAmount: number; // max evaluation dataset amount + maxEvalDatasetDataAmount: number; // max evaluation data amount per dataset + maxEvalMetricAmount: number; // max evaluation metric amount trainingWeight: number; // 1~4 permissionCustomApiKey: boolean; permissionCustomCopyright: boolean; // feature @@ -48,6 +52,10 @@ export type TeamSubSchema = { maxTeamMember?: number; maxApp?: number; maxDataset?: number; + maxEvaluationTaskAmount?: number; + maxEvalDatasetAmount?: number; + maxEvalDatasetDataAmount?: number; + maxEvalMetricAmount?: number; totalPoints: number; surplusPoints: number; @@ -71,4 +79,8 @@ export type ClientTeamPlanStatusType = TeamPlanStatusType & { usedAppAmount: number; usedDatasetSize: number; usedDatasetIndexSize: number; + usedEvaluationTaskAmount: number; + usedEvalDatasetAmount: number; + usedEvalDatasetDataAmount: number; + usedEvalMetricAmount: number; }; diff --git a/packages/service/common/bullmq/index.ts b/packages/service/common/bullmq/index.ts index ddd6f2b8f224..fc96c4ea4dd7 100644 --- a/packages/service/common/bullmq/index.ts +++ b/packages/service/common/bullmq/index.ts @@ -20,7 +20,11 @@ const defaultWorkerOpts: Omit = { export enum QueueNames { datasetSync = 'datasetSync', - evaluation = 'evaluation', + evalDatasetDataQuality = 'evalDatasetDataQuality', + evalDatasetDataSynthesize = 'evalDatasetDataSynthesize', + evalTask = 'evalTask', + evalTaskItem = 'evalTaskitem', + evaluationSummary = 'evaluationSummary', // abondoned websiteSync = 'websiteSync' } @@ -79,7 +83,7 @@ export function getWorker( newWorker.on('error', (error) => { addLog.error(`MQ Worker [${name}]: ${error.message}`, error); }); - newWorker.on('failed', (jobId, error) => { + newWorker.on('failed', (_, error) => { addLog.error(`MQ Worker [${name}]: ${error.message}`, error); }); workers.set(name, newWorker); diff --git a/packages/service/common/file/gridfs/controller.ts b/packages/service/common/file/gridfs/controller.ts index afa99571e7a1..2153e8252f58 100644 --- a/packages/service/common/file/gridfs/controller.ts +++ b/packages/service/common/file/gridfs/controller.ts @@ -3,7 +3,7 @@ import type { BucketNameEnum } from '@fastgpt/global/common/file/constants'; import fsp from 'fs/promises'; import fs from 'fs'; import { type DatasetFileSchema } from '@fastgpt/global/core/dataset/type'; -import { MongoChatFileSchema, MongoDatasetFileSchema } from './schema'; +import { MongoChatFileSchema, MongoDatasetFileSchema, MongoEvaluationFileSchema } from './schema'; import { detectFileEncoding, detectFileEncodingByPath } from '@fastgpt/global/common/file/tools'; import { CommonErrEnum } from '@fastgpt/global/common/error/code/common'; import { readRawContentByFileBuffer } from '../read/utils'; @@ -18,6 +18,7 @@ import { retryFn } from '@fastgpt/global/common/system/utils'; export function getGFSCollection(bucket: `${BucketNameEnum}`) { MongoDatasetFileSchema; MongoChatFileSchema; + MongoEvaluationFileSchema; return connectionMongo.connection.db!.collection(`${bucket}.files`); } diff --git a/packages/service/common/file/gridfs/schema.ts b/packages/service/common/file/gridfs/schema.ts index 61b98ea96caa..51e9b36dd4f0 100644 --- a/packages/service/common/file/gridfs/schema.ts +++ b/packages/service/common/file/gridfs/schema.ts @@ -6,11 +6,17 @@ const DatasetFileSchema = new Schema({ const ChatFileSchema = new Schema({ metadata: Object }); +const EvaluationFileSchema = new Schema({ + metadata: Object +}); DatasetFileSchema.index({ uploadDate: -1 }); ChatFileSchema.index({ uploadDate: -1 }); ChatFileSchema.index({ 'metadata.chatId': 1 }); +EvaluationFileSchema.index({ uploadDate: -1 }); + export const MongoDatasetFileSchema = getMongoModel('dataset.files', DatasetFileSchema); export const MongoChatFileSchema = getMongoModel('chat.files', ChatFileSchema); +export const MongoEvaluationFileSchema = getMongoModel('evaluation.files', EvaluationFileSchema); diff --git a/packages/service/common/file/multer.ts b/packages/service/common/file/multer.ts index 237407656654..a2f77f2592cf 100644 --- a/packages/service/common/file/multer.ts +++ b/packages/service/common/file/multer.ts @@ -2,9 +2,19 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import multer from 'multer'; import path from 'path'; import type { BucketNameEnum } from '@fastgpt/global/common/file/constants'; -import { bucketNameMap } from '@fastgpt/global/common/file/constants'; +import { + bucketNameMap, + DEFAULT_FILE_UPLOAD_LIMITS, + FileUploadErrorEnum +} from '@fastgpt/global/common/file/constants'; import { getNanoid } from '@fastgpt/global/common/string/tools'; -import { UserError } from '@fastgpt/global/common/error/utils'; +import { UserError, FileUploadError } from '@fastgpt/global/common/error/utils'; +import { + validateFileUpload, + getEffectiveUploadLimits, + formatUploadLimitsMessage, + type FileUploadLimits +} from './uploadValidation'; export type FileType = { fieldname: string; @@ -18,14 +28,24 @@ export type FileType = { /* maxSize: File max size (MB) + customLimits: Custom upload limits configuration */ -export const getUploadModel = ({ maxSize = 500 }: { maxSize?: number }) => { - maxSize *= 1024 * 1024; +export const getUploadModel = ({ + maxSize = DEFAULT_FILE_UPLOAD_LIMITS.MAX_FILE_SIZE_MB, + customLimits +}: { + maxSize?: number; + customLimits?: FileUploadLimits; +} = {}) => { + const limits = getEffectiveUploadLimits(customLimits); + const effectiveMaxSize = Math.min(maxSize, limits.maxFileSizeMB); + const maxSizeBytes = effectiveMaxSize * 1024 * 1024; class UploadModel { uploaderSingle = multer({ limits: { - fieldSize: maxSize + fileSize: maxSizeBytes, + fieldSize: maxSizeBytes }, preservePath: true, storage: multer.diskStorage({ @@ -56,6 +76,15 @@ export const getUploadModel = ({ maxSize = 500 }: { maxSize?: number }) => { // @ts-ignore this.uploaderSingle(req, res, (error) => { if (error) { + if (error.code === 'LIMIT_FILE_SIZE') { + return reject( + new FileUploadError( + FileUploadErrorEnum.FILE_TOO_LARGE, + `File exceeds the maximum file size limit of ${effectiveMaxSize}MB`, + { maxSizeMB: effectiveMaxSize } + ) + ); + } return reject(error); } @@ -68,11 +97,23 @@ export const getUploadModel = ({ maxSize = 500 }: { maxSize?: number }) => { // @ts-ignore const file = req.file as FileType; + if (!file) { + return reject(new UserError('No file uploaded')); + } + + // Validate single file + const decodedFile = { + ...file, + originalname: decodeURIComponent(file.originalname) + }; + + const validation = validateFileUpload([decodedFile], customLimits); + if (!validation.isValid) { + return reject(validation.errors[0]); + } + resolve({ - file: { - ...file, - originalname: decodeURIComponent(file.originalname) - }, + file: decodedFile, bucketName, metadata: (() => { if (!req.body?.metadata) return {}; @@ -97,7 +138,9 @@ export const getUploadModel = ({ maxSize = 500 }: { maxSize?: number }) => { uploaderMultiple = multer({ limits: { - fieldSize: maxSize + fileSize: maxSizeBytes, + files: limits.maxFileCount, + fieldSize: maxSizeBytes }, preservePath: true, storage: multer.diskStorage({ @@ -113,7 +156,7 @@ export const getUploadModel = ({ maxSize = 500 }: { maxSize?: number }) => { } } }) - }).array('file', global.feConfigs?.uploadFileMaxSize); + }).array('file', limits.maxFileCount); async getUploadFiles(req: NextApiRequest, res: NextApiResponse) { return new Promise<{ files: FileType[]; @@ -122,18 +165,67 @@ export const getUploadModel = ({ maxSize = 500 }: { maxSize?: number }) => { // @ts-ignore this.uploaderMultiple(req, res, (error) => { if (error) { - console.log(error); + console.log('File upload error:', error); + + if (error.code === 'LIMIT_FILE_SIZE') { + return reject( + new FileUploadError( + FileUploadErrorEnum.FILE_TOO_LARGE, + `File exceeds the maximum file size limit of ${effectiveMaxSize}MB`, + { maxSizeMB: effectiveMaxSize } + ) + ); + } + if (error.code === 'LIMIT_FILE_COUNT') { + return reject( + new FileUploadError( + FileUploadErrorEnum.TOO_MANY_FILES, + `File count exceeds the limit, maximum ${limits.maxFileCount} files supported`, + { maxFileCount: limits.maxFileCount } + ) + ); + } + if (error.code === 'LIMIT_UNEXPECTED_FILE') { + return reject( + new FileUploadError( + FileUploadErrorEnum.TOO_MANY_FILES, + `File count exceeds the limit, maximum ${limits.maxFileCount} files supported`, + { maxFileCount: limits.maxFileCount } + ) + ); + } + return reject(error); } // @ts-ignore const files = req.files as FileType[]; + if (!files || files.length === 0) { + return reject(new UserError('No files uploaded')); + } + + // Decode filenames and validate all files + const decodedFiles = files.map((file) => ({ + ...file, + originalname: decodeURIComponent(file.originalname) + })); + + const validation = validateFileUpload(decodedFiles, customLimits); + if (!validation.isValid) { + // Return the first error, but include information about all errors + const errorMessage = validation.errors.map((err) => err.message).join('; '); + return reject( + new FileUploadError(validation.errors[0].code, errorMessage, { + errors: validation.errors, + totalErrors: validation.errors.length, + limits: formatUploadLimitsMessage(limits) + }) + ); + } + resolve({ - files: files.map((file) => ({ - ...file, - originalname: decodeURIComponent(file.originalname) - })), + files: validation.validFiles, data: (() => { if (!req.body?.data) return {}; try { diff --git a/packages/service/common/file/read/utils.ts b/packages/service/common/file/read/utils.ts index 461da09dc0c4..437123f8cd5f 100644 --- a/packages/service/common/file/read/utils.ts +++ b/packages/service/common/file/read/utils.ts @@ -73,6 +73,8 @@ export const readRawContentByFileBuffer = async ({ const token = global.systemEnv.customPdfParse?.key; if (!url) return systemParse(); + const timeout = global.systemEnv.customPdfParse?.timeout || 10; + const start = Date.now(); addLog.info('Parsing files from an external service'); @@ -85,7 +87,7 @@ export const readRawContentByFileBuffer = async ({ markdown: string; error?: Object | string; }>(url, data, { - timeout: 600000, + timeout: timeout * 1000 * 60, headers: { ...data.getHeaders(), Authorization: token ? `Bearer ${token}` : undefined diff --git a/packages/service/common/file/uploadValidation.ts b/packages/service/common/file/uploadValidation.ts new file mode 100644 index 000000000000..a6b8a263d824 --- /dev/null +++ b/packages/service/common/file/uploadValidation.ts @@ -0,0 +1,183 @@ +import { FileUploadError } from '@fastgpt/global/common/error/utils'; +import { + DEFAULT_FILE_UPLOAD_LIMITS, + FileUploadErrorEnum +} from '@fastgpt/global/common/file/constants'; +import type { FileType } from './multer'; + +export interface FileUploadLimits { + maxFileCount?: number; + maxFileSizeMB?: number; + allowedTypes?: string[]; +} + +export interface FileValidationResult { + isValid: boolean; + errors: FileUploadError[]; + validFiles: FileType[]; + invalidFiles: FileType[]; +} + +/** + * Get effective upload limits by merging global config with custom limits + */ +export function getEffectiveUploadLimits( + customLimits?: FileUploadLimits +): Required { + const globalLimits = { + maxFileCount: + global.feConfigs?.uploadFileMaxAmount ?? DEFAULT_FILE_UPLOAD_LIMITS.MAX_FILE_COUNT, + maxFileSizeMB: + global.feConfigs?.uploadFileMaxSize ?? DEFAULT_FILE_UPLOAD_LIMITS.MAX_FILE_SIZE_MB + }; + + return { + maxFileCount: Math.min( + customLimits?.maxFileCount ?? globalLimits.maxFileCount, + globalLimits.maxFileCount + ), + maxFileSizeMB: Math.min( + customLimits?.maxFileSizeMB ?? globalLimits.maxFileSizeMB, + globalLimits.maxFileSizeMB + ), + allowedTypes: customLimits?.allowedTypes ?? [] + }; +} + +/** + * Validate file size constraints + */ +export function validateFileSize( + file: FileType, + limits: Required +): FileUploadError | null { + const maxSizeBytes = limits.maxFileSizeMB * 1024 * 1024; + + if (file.size > maxSizeBytes) { + return new FileUploadError( + FileUploadErrorEnum.FILE_TOO_LARGE, + `File "${file.originalname}" exceeds the maximum file size limit of ${limits.maxFileSizeMB}MB`, + { + fileName: file.originalname, + fileSize: file.size, + maxSize: maxSizeBytes, + maxSizeMB: limits.maxFileSizeMB + } + ); + } + + return null; +} + +/** + * Validate file count constraints + */ +export function validateFileCount( + files: FileType[], + limits: Required +): FileUploadError | null { + if (files.length > limits.maxFileCount) { + return new FileUploadError( + FileUploadErrorEnum.TOO_MANY_FILES, + `File count exceeds the limit, maximum ${limits.maxFileCount} files supported`, + { + fileCount: files.length, + maxFileCount: limits.maxFileCount + } + ); + } + + return null; +} + +/** + * Validate file type constraints + */ +export function validateFileType( + file: FileType, + limits: Required +): FileUploadError | null { + if (limits.allowedTypes.length === 0) { + return null; // No type restrictions + } + + const fileExtension = file.originalname + .toLowerCase() + .substring(file.originalname.lastIndexOf('.')); + const isAllowed = limits.allowedTypes.some( + (type) => type.toLowerCase() === fileExtension || file.mimetype.includes(type.toLowerCase()) + ); + + if (!isAllowed) { + return new FileUploadError( + FileUploadErrorEnum.INVALID_FILE_TYPE, + `File type not supported, file "${file.originalname}" has type ${fileExtension}`, + { + fileName: file.originalname, + fileType: fileExtension, + mimetype: file.mimetype, + allowedTypes: limits.allowedTypes + } + ); + } + + return null; +} + +export function validateFileUpload( + files: FileType[], + customLimits?: FileUploadLimits +): FileValidationResult { + const limits = getEffectiveUploadLimits(customLimits); + const errors: FileUploadError[] = []; + const validFiles: FileType[] = []; + const invalidFiles: FileType[] = []; + + const countError = validateFileCount(files, limits); + if (countError) { + errors.push(countError); + return { + isValid: false, + errors, + validFiles: [], + invalidFiles: files + }; + } + + for (const file of files) { + const fileErrors: FileUploadError[] = []; + + const sizeError = validateFileSize(file, limits); + if (sizeError) fileErrors.push(sizeError); + + const typeError = validateFileType(file, limits); + if (typeError) fileErrors.push(typeError); + + if (fileErrors.length > 0) { + errors.push(...fileErrors); + invalidFiles.push(file); + } else { + validFiles.push(file); + } + } + + return { + isValid: errors.length === 0, + errors, + validFiles, + invalidFiles + }; +} + +export function formatUploadLimitsMessage(limits: Required): string { + const messages = [ + `Maximum ${limits.maxFileCount} files supported`, + `Maximum file size ${limits.maxFileSizeMB}MB` + ]; + + if (limits.allowedTypes.length > 0) { + messages.push(`Supported file types: ${limits.allowedTypes.join(', ')}`); + } + + return messages.join(', '); +} diff --git a/packages/service/common/middle/i18n.ts b/packages/service/common/middle/i18n.ts index 90f465e3a6e8..e49f6d469d88 100644 --- a/packages/service/common/middle/i18n.ts +++ b/packages/service/common/middle/i18n.ts @@ -13,3 +13,15 @@ export const getLocale = (req: ApiRequestProps): localeType => { } return 'en'; }; + +export let templateLocale = 'zh-CN'; + +export const isRefreshTemplateData = (req: ApiRequestProps): boolean => { + const isUpdate = getLocale(req) !== templateLocale; + templateLocale = getLocale(req); + return isUpdate; +}; + +export const isEnLocale = (req: ApiRequestProps): boolean => { + return getLocale(req) === 'en'; +}; diff --git a/packages/service/common/redis/index.ts b/packages/service/common/redis/index.ts index 55f8126bafb7..ab634bec101c 100644 --- a/packages/service/common/redis/index.ts +++ b/packages/service/common/redis/index.ts @@ -4,7 +4,9 @@ import Redis from 'ioredis'; const REDIS_URL = process.env.REDIS_URL ?? 'redis://localhost:6379'; export const newQueueRedisConnection = () => { - const redis = new Redis(REDIS_URL); + const redis = new Redis(REDIS_URL, { + maxRetriesPerRequest: null + }); redis.on('connect', () => { console.log('Redis connected'); }); diff --git a/packages/service/common/response/index.ts b/packages/service/common/response/index.ts index 5d2f58db6ea8..09bb194c1237 100644 --- a/packages/service/common/response/index.ts +++ b/packages/service/common/response/index.ts @@ -49,11 +49,11 @@ export const jsonRes = ( // another error let msg = ''; if ((code < 200 || code >= 400) && !message) { - msg = error?.response?.statusText || error?.message || '请求错误'; + msg = error?.response?.statusText || error?.message || 'Request error'; if (typeof error === 'string') { msg = error; } else if (proxyError[error?.code]) { - msg = '网络连接异常'; + msg = 'Network error'; } else if (error?.response?.data?.error?.message) { msg = error?.response?.data?.error?.message; } else if (error?.error?.message) { @@ -92,11 +92,11 @@ export const sseErrRes = (res: NextApiResponse, error: any) => { }); } - let msg = error?.response?.statusText || error?.message || '请求错误'; + let msg = error?.response?.statusText || error?.message || 'Request error'; if (typeof error === 'string') { msg = error; } else if (proxyError[error?.code]) { - msg = '网络连接异常'; + msg = 'Network error'; } else if (error?.response?.data?.error?.message) { msg = error?.response?.data?.error?.message; } else if (error?.error?.message) { diff --git a/packages/service/common/vectorDB/constants.ts b/packages/service/common/vectorDB/constants.ts index 8476cf6c1f66..23f65d3c0bf5 100644 --- a/packages/service/common/vectorDB/constants.ts +++ b/packages/service/common/vectorDB/constants.ts @@ -1,6 +1,7 @@ export const DatasetVectorDbName = 'fastgpt'; export const DatasetVectorTableName = 'modeldata'; - +export const DBDatasetVectorTableName = 'coulumndescriptionindex'; +export const DBDatasetValueVectorTableName = 'coulumnvalueindex'; export const PG_ADDRESS = process.env.PG_URL; export const OCEANBASE_ADDRESS = process.env.OCEANBASE_URL; export const MILVUS_ADDRESS = process.env.MILVUS_ADDRESS; diff --git a/packages/service/common/vectorDB/controller.d.ts b/packages/service/common/vectorDB/controller.d.ts index 5f0328ce22d8..647d07cca988 100644 --- a/packages/service/common/vectorDB/controller.d.ts +++ b/packages/service/common/vectorDB/controller.d.ts @@ -9,6 +9,7 @@ export type DeleteDatasetVectorProps = ( }; export type DelDatasetVectorCtrlProps = DeleteDatasetVectorProps & { retry?: number; + tableName?: string; }; export type InsertVectorProps = { @@ -18,6 +19,11 @@ export type InsertVectorProps = { }; export type InsertVectorControllerProps = InsertVectorProps & { vectors: number[][]; + tableName?: string; + table_des_index?: string; + column_des_index?: string; + column_val_index?: string; + retry?: number; }; export type EmbeddingRecallProps = { @@ -35,3 +41,21 @@ export type EmbeddingRecallCtrlProps = EmbeddingRecallProps & { export type EmbeddingRecallResponse = { results: EmbeddingRecallItemType[]; }; + +export type DatabaseEmbeddingRecallCtrlProps = EmbeddingRecallProps & { + vector: number[]; + limit: number; + tableName: string; // DBDatasetVectorTableName or DBDatasetValueVectorTableName + retry?: number; +}; + +export type DatabaseEmbeddingRecallItemType = { + id: string; + collectionId: string; + score: number; + columnDesIndex?: string; + columnValIndex?: string; +}; +export type DatabaseEmbeddingRecallResponse = { + results: Array; +}; diff --git a/packages/service/common/vectorDB/controller.ts b/packages/service/common/vectorDB/controller.ts index a2658b97e139..681f785faca7 100644 --- a/packages/service/common/vectorDB/controller.ts +++ b/packages/service/common/vectorDB/controller.ts @@ -2,12 +2,8 @@ import { PgVectorCtrl } from './pg'; import { ObVectorCtrl } from './oceanbase'; import { getVectorsByText } from '../../core/ai/embedding'; -import type { - EmbeddingRecallCtrlProps} from './controller.d'; -import { - type DelDatasetVectorCtrlProps, - type InsertVectorProps -} from './controller.d'; +import type { EmbeddingRecallCtrlProps } from './controller.d'; +import { type DelDatasetVectorCtrlProps, type InsertVectorProps } from './controller.d'; import { type EmbeddingModelItemType } from '@fastgpt/global/core/ai/model.d'; import { MILVUS_ADDRESS, PG_ADDRESS, OCEANBASE_ADDRESS } from './constants'; import { MilvusCtrl } from './milvus'; @@ -21,7 +17,7 @@ import { } from '../redis/cache'; import { throttle } from 'lodash'; import { retryFn } from '@fastgpt/global/common/system/utils'; - +import { DBDatasetVectorTableName, DBDatasetValueVectorTableName } from './constants'; const getVectorObj = () => { if (PG_ADDRESS) return new PgVectorCtrl(); if (OCEANBASE_ADDRESS) return new ObVectorCtrl(); @@ -42,6 +38,7 @@ const Vector = getVectorObj(); export const initVectorStore = Vector.init; export const recallFromVectorStore = (props: EmbeddingRecallCtrlProps) => retryFn(() => Vector.embRecall(props)); +export const databaseEmbeddingRecall = Vector.databaseEmbRecall; export const getVectorDataByTime = Vector.getVectorDataByTime; export const getVectorCountByTeamId = async (teamId: string) => { @@ -95,3 +92,82 @@ export const deleteDatasetDataVector = async (props: DelDatasetVectorCtrlProps) onDelCache(props.teamId); return result; }; + +/*Database Dataset specific operations*/ +export const insertCoulmnDescriptionVector = async ({ + model, + query, + teamId, + datasetId, + collectionId, + column_des_index +}: { + model: EmbeddingModelItemType; + query: string; + teamId: string; + datasetId: string; + collectionId: string; + column_des_index: string; +}) => { + return retryFn(async () => { + const { vectors, tokens } = await getVectorsByText({ + model, + input: query, + type: 'db' + }); + const { insertIds } = await Vector.insert({ + teamId, + datasetId, + collectionId, + vectors: vectors, + column_des_index, + tableName: DBDatasetVectorTableName + }); + + console.debug('insertCoulmnDescriptionVector', { insertIds }); + onIncrCache(teamId); + + return { + tokens, + insertIds + }; + }); +}; + +export const insertColumnValueVector = async ({ + model, + query, + teamId, + datasetId, + collectionId, + column_val_index +}: { + model: EmbeddingModelItemType; + query: string; + teamId: string; + datasetId: string; + collectionId: string; + column_val_index: string; +}) => { + return retryFn(async () => { + const { vectors, tokens } = await getVectorsByText({ + model, + input: query, + type: 'db' + }); + + const { insertIds } = await Vector.insert({ + teamId, + datasetId, + collectionId, + vectors: vectors, + column_val_index, + tableName: DBDatasetValueVectorTableName + }); + + return { + tokens, + insertIds + }; + }); +}; diff --git a/packages/service/common/vectorDB/milvus/config.ts b/packages/service/common/vectorDB/milvus/config.ts new file mode 100644 index 000000000000..bcff93827ae3 --- /dev/null +++ b/packages/service/common/vectorDB/milvus/config.ts @@ -0,0 +1,104 @@ +import { DataType } from '@zilliz/milvus2-sdk-node'; +import type { FieldType } from '@zilliz/milvus2-sdk-node/dist/milvus/types/Collection'; +import type { CreateIndexSimpleReq } from '@zilliz/milvus2-sdk-node/dist/milvus/types/MilvusIndex'; +import { + DatasetVectorTableName, + DBDatasetVectorTableName, + DBDatasetValueVectorTableName +} from '../constants'; + +export const MILVUS_VECTOR_DIMENSION = 1536; + +export type MilvusIndexParam = Omit; + +export type MilvusCollectionConfig = { + name: string; + description: string; + fields: FieldType[]; + indexParams: MilvusIndexParam[]; +}; + +const createBaseFields = (): FieldType[] => [ + { + name: 'id', + data_type: DataType.Int64, + is_primary_key: true, + autoID: false + }, + { + name: 'vector', + data_type: DataType.FloatVector, + dim: MILVUS_VECTOR_DIMENSION + }, + { name: 'teamId', data_type: DataType.VarChar, max_length: 64 }, + { name: 'datasetId', data_type: DataType.VarChar, max_length: 64 }, + { name: 'collectionId', data_type: DataType.VarChar, max_length: 64 }, + { + name: 'createTime', + data_type: DataType.Int64 + } +]; + +const createBaseIndexParams = (): MilvusIndexParam[] => [ + { + field_name: 'vector', + index_name: 'vector_HNSW', + index_type: 'HNSW', + metric_type: 'IP', + params: { efConstruction: 32, M: 64 } + }, + { + field_name: 'teamId', + index_type: 'Trie' + }, + { + field_name: 'datasetId', + index_type: 'Trie' + }, + { + field_name: 'collectionId', + index_type: 'Trie' + }, + { + field_name: 'createTime', + index_type: 'STL_SORT' + } +]; + +export const milvusCollectionDefinitions: MilvusCollectionConfig[] = [ + { + name: DatasetVectorTableName, + description: 'Store dataset vector', + fields: createBaseFields(), + indexParams: createBaseIndexParams() + }, + { + name: DBDatasetVectorTableName, + description: 'Store database table column description embeddings', + fields: [ + ...createBaseFields(), + { name: 'columnDesIndex', data_type: DataType.VarChar, max_length: 1024 } + ], + indexParams: createBaseIndexParams() + }, + { + name: DBDatasetValueVectorTableName, + description: 'Store database table column value example embeddings', + fields: [ + ...createBaseFields(), + { name: 'columnValIndex', data_type: DataType.VarChar, max_length: 1024 } + ], + indexParams: createBaseIndexParams() + } +]; + +export type MilvusInsertRow = { + id: number; + vector: number[]; + teamId: string; + datasetId: string; + collectionId: string; + createTime: number; + columnDesIndex?: string; + columnValIndex?: string; +}; diff --git a/packages/service/common/vectorDB/milvus/index.ts b/packages/service/common/vectorDB/milvus/index.ts index a6db2b173ead..1bbee4c81a7c 100644 --- a/packages/service/common/vectorDB/milvus/index.ts +++ b/packages/service/common/vectorDB/milvus/index.ts @@ -1,19 +1,28 @@ -import { DataType, LoadState, MilvusClient } from '@zilliz/milvus2-sdk-node'; +import { LoadState, MilvusClient } from '@zilliz/milvus2-sdk-node'; import { DatasetVectorDbName, DatasetVectorTableName, + DBDatasetVectorTableName, + DBDatasetValueVectorTableName, MILVUS_ADDRESS, MILVUS_TOKEN } from '../constants'; import type { + DatabaseEmbeddingRecallCtrlProps, + DatabaseEmbeddingRecallResponse, DelDatasetVectorCtrlProps, EmbeddingRecallCtrlProps, EmbeddingRecallResponse, InsertVectorControllerProps } from '../controller.d'; -import { delay, retryFn } from '@fastgpt/global/common/system/utils'; +import { retryFn } from '@fastgpt/global/common/system/utils'; import { addLog } from '../../system/log'; import { customNanoid } from '@fastgpt/global/common/string/tools'; +import { + milvusCollectionDefinitions, + type MilvusCollectionConfig, + type MilvusInsertRow +} from './config'; export class MilvusCtrl { constructor() {} @@ -33,6 +42,38 @@ export class MilvusCtrl { return global.milvusClient; }; + private createMilvusCollection = async (client: MilvusClient, config: MilvusCollectionConfig) => { + const { name, description, fields, indexParams } = config; + + const { value: hasCollection } = await client.hasCollection({ + collection_name: name + }); + + if (!hasCollection) { + const result = await client.createCollection({ + collection_name: name, + description, + enableDynamicField: true, + fields, + index_params: indexParams + }); + addLog.info(`Create milvus collection ${name}`, result); + return; + } + }; + + private loadCollection = async (client: MilvusClient, collectionName: string) => { + const { state } = await client.getLoadState({ + collection_name: collectionName + }); + + if (state === LoadState.LoadStateNotExist || state === LoadState.LoadStateNotLoad) { + await client.loadCollectionSync({ + collection_name: collectionName + }); + addLog.info(`Milvus collection ${collectionName} load success`); + } + }; init = async () => { const client = await this.getClient(); @@ -51,83 +92,23 @@ export class MilvusCtrl { }); } catch (error) {} - // init collection and index - const { value: hasCollection } = await client.hasCollection({ - collection_name: DatasetVectorTableName - }); - if (!hasCollection) { - const result = await client.createCollection({ - collection_name: DatasetVectorTableName, - description: 'Store dataset vector', - enableDynamicField: true, - fields: [ - { - name: 'id', - data_type: DataType.Int64, - is_primary_key: true, - autoID: false // disable auto id, and we need to set id in insert - }, - { - name: 'vector', - data_type: DataType.FloatVector, - dim: 1536 - }, - { name: 'teamId', data_type: DataType.VarChar, max_length: 64 }, - { name: 'datasetId', data_type: DataType.VarChar, max_length: 64 }, - { name: 'collectionId', data_type: DataType.VarChar, max_length: 64 }, - { - name: 'createTime', - data_type: DataType.Int64 - } - ], - index_params: [ - { - field_name: 'vector', - index_name: 'vector_HNSW', - index_type: 'HNSW', - metric_type: 'IP', - params: { efConstruction: 32, M: 64 } - }, - { - field_name: 'teamId', - index_type: 'Trie' - }, - { - field_name: 'datasetId', - index_type: 'Trie' - }, - { - field_name: 'collectionId', - index_type: 'Trie' - }, - { - field_name: 'createTime', - index_type: 'STL_SORT' - } - ] - }); - - addLog.info(`Create milvus collection: `, result); - } - - const { state: colLoadState } = await client.getLoadState({ - collection_name: DatasetVectorTableName - }); - - if ( - colLoadState === LoadState.LoadStateNotExist || - colLoadState === LoadState.LoadStateNotLoad - ) { - await client.loadCollectionSync({ - collection_name: DatasetVectorTableName - }); - addLog.info(`Milvus collection load success`); + for (const config of milvusCollectionDefinitions) { + await this.createMilvusCollection(client, config); + await this.loadCollection(client, config.name); } }; insert = async (props: InsertVectorControllerProps): Promise<{ insertIds: string[] }> => { const client = await this.getClient(); - const { teamId, datasetId, collectionId, vectors } = props; + const { + teamId, + datasetId, + collectionId, + vectors, + tableName = DatasetVectorTableName, + column_des_index, + column_val_index + } = props; const generateId = () => { // in js, the max safe integer is 2^53 - 1: 9007199254740991 @@ -138,16 +119,28 @@ export class MilvusCtrl { return Number(`${firstDigit}${restDigits}`); }; - const result = await client.insert({ - collection_name: DatasetVectorTableName, - data: vectors.map((vector) => ({ + const now = Date.now(); + const data: MilvusInsertRow[] = vectors.map((vector) => { + const row: MilvusInsertRow = { id: generateId(), vector, teamId: String(teamId), datasetId: String(datasetId), collectionId: String(collectionId), - createTime: Date.now() - })) + createTime: now + }; + if (column_des_index) { + row.columnDesIndex = column_des_index; + } + if (column_val_index) { + row.columnValIndex = column_val_index; + } + return row; + }); + + const result = await client.insert({ + collection_name: tableName, + data }); const insertIds = (() => { @@ -162,7 +155,7 @@ export class MilvusCtrl { }; }; delete = async (props: DelDatasetVectorCtrlProps): Promise => { - const { teamId } = props; + const { teamId, tableName = DatasetVectorTableName } = props; const client = await this.getClient(); const teamIdWhere = `(teamId=="${String(teamId)}")`; @@ -195,7 +188,7 @@ export class MilvusCtrl { const concatWhere = `${teamIdWhere} and ${where}`; await client.delete({ - collection_name: DatasetVectorTableName, + collection_name: tableName, filter: concatWhere }); }; @@ -257,6 +250,70 @@ export class MilvusCtrl { }; }; + databaseEmbRecall = async ( + props: DatabaseEmbeddingRecallCtrlProps + ): Promise => { + const client = await this.getClient(); + const { + teamId, + datasetIds, + vector, + limit, + tableName, + retry = 2, + forbidCollectionIdList + } = props; + + const forbidFilter = + forbidCollectionIdList.length > 0 + ? `and (collectionId not in [${forbidCollectionIdList.map((id) => `"${String(id)}"`).join(',')}])` + : ''; + + const outputFields = ['collectionId']; + if (tableName === DBDatasetVectorTableName) { + outputFields.push('columnDesIndex'); + } + if (tableName === DBDatasetValueVectorTableName) { + outputFields.push('columnValIndex'); + } + + try { + const { results } = await retryFn( + () => + client.search({ + collection_name: tableName, + data: vector, + limit, + filter: `(teamId == "${teamId}") and (datasetId in [${datasetIds + .map((id) => `"${String(id)}"`) + .join(',')}]) ${forbidFilter}`, + output_fields: outputFields + }), + retry + ); + + const rows = results as Array<{ + score: number; + id: string; + collectionId: string; + columnDesIndex?: string; + columnValIndex?: string; + }>; + + return { + results: rows.map((item) => ({ + id: String(item.id), + collectionId: item.collectionId, + score: item.score, + ...(item.columnDesIndex ? { columnDesIndex: item.columnDesIndex } : {}), + ...(item.columnValIndex ? { columnValIndex: item.columnValIndex } : {}) + })) + }; + } catch (error) { + return Promise.reject(error); + } + }; + getVectorCountByTeamId = async (teamId: string) => { const client = await this.getClient(); diff --git a/packages/service/common/vectorDB/oceanbase/controller.ts b/packages/service/common/vectorDB/oceanbase/controller.ts index 322bbb9ee44a..cb2d98753bbe 100644 --- a/packages/service/common/vectorDB/oceanbase/controller.ts +++ b/packages/service/common/vectorDB/oceanbase/controller.ts @@ -6,6 +6,7 @@ import mysql, { } from 'mysql2/promise'; import { addLog } from '../../system/log'; import { OCEANBASE_ADDRESS } from '../constants'; +import { delay } from '@fastgpt/global/common/system/utils'; export const getClient = async (): Promise => { if (!OCEANBASE_ADDRESS) { @@ -27,9 +28,22 @@ export const getClient = async (): Promise => { keepAliveInitialDelay: 0 }); - addLog.info(`oceanbase connected`); + try { + // Test the connection with a simple query instead of calling connect() + await global.obClient.query('SELECT 1'); + addLog.info(`oceanbase connected`); + return global.obClient; + } catch (error) { + addLog.error(`oceanbase connect error`, error); + + global.obClient?.end(); + global.obClient = null; + + await delay(1000); + addLog.info(`Retry connect oceanbase`); - return global.obClient; + return getClient(); + } }; type WhereProps = (string | [string, string | number])[]; @@ -40,7 +54,6 @@ type GetProps = { limit?: number; offset?: number; }; - type DeleteProps = { where: WhereProps; }; @@ -53,7 +66,6 @@ type UpdateProps = { type InsertProps = { values: ValuesProps[]; }; - class ObClass { private getWhereStr(where?: WhereProps) { return where @@ -118,9 +130,9 @@ class ObClass { `; const client = await getClient(); - return client - .query<({ count: number } & RowDataPacket)[]>(sql) - .then(([rows]) => Number(rows[0]?.count || 0)); + return client.query<({ count: number } & RowDataPacket)[]>(sql).then(([res]) => { + return res[0]?.['COUNT(*)'] || 0; + }); } async delete(table: string, props: DeleteProps) { const sql = `DELETE FROM ${table} ${this.getWhereStr(props.where)}`; @@ -140,24 +152,73 @@ class ObClass { const client = await getClient(); return client.query(sql); } + /** + * 批量插入数据并获取自增 ID + * 在 OceanBase 多副本环境下使用 LAST_INSERT_ID() 获取准确的自增 ID + * + * 原理说明: + * 1. OceanBase 的 LAST_INSERT_ID() 返回当前会话最后一次插入操作的第一个自增 ID + * 2. 批量插入时,ID 是连续的:first_id, first_id+1, first_id+2, ... + * 3. 这种方法在多副本环境下是可靠的,因为每个连接会话是独立的 + */ async insert(table: string, props: InsertProps) { if (props.values.length === 0) { return { rowCount: 0, - rows: [] + insertIds: [] }; } const fields = props.values[0].map((item) => item.key).join(','); const sql = `INSERT INTO ${table} (${fields}) VALUES ${this.getInsertValStr(props.values)}`; + console.debug('sql insert', sql); + // 获取专用连接而不是从连接池获取 + const connection = await (await getClient()).getConnection(); + + try { + const result = await connection.query(sql); + + if (result[0].affectedRows > 0) { + // 在同一个连接上获取LAST_INSERT_ID,确保会话一致性 + const [lastIdResult] = await connection.query( + 'SELECT LAST_INSERT_ID() as firstId' + ); + const firstId = lastIdResult[0]?.firstId; + + if (firstId && typeof firstId === 'number') { + const count = result[0].affectedRows; + // Generate consecutive IDs: firstId, firstId+1, firstId+2, ... + const ids = Array.from({ length: count }, (_, i) => String(firstId + i)); + + return { + rowCount: result[0].affectedRows, + insertIds: ids + }; + } + + // Fallback: try to use insertId from ResultSetHeader if LAST_INSERT_ID() fails + if (result[0].insertId) { + const startId = result[0].insertId; + const count = result[0].affectedRows; + const ids = Array.from({ length: count }, (_, i) => String(startId + i)); + + return { + rowCount: result[0].affectedRows, + insertIds: ids + }; + } + } - const client = await getClient(); - return client.query(sql).then(([result]) => { return { - rowCount: result.affectedRows, - rows: [{ id: String(result.insertId) }] + rowCount: result[0].affectedRows || 0, + insertIds: [] }; - }); + } catch (error) { + addLog.error(`OceanBase batch insert error: ${error}`); + throw error; + } finally { + connection.release(); // 释放连接回连接池 + } } async query(sql: string) { const client = await getClient(); diff --git a/packages/service/common/vectorDB/oceanbase/index.ts b/packages/service/common/vectorDB/oceanbase/index.ts index 79592a413d04..04a820da7fac 100644 --- a/packages/service/common/vectorDB/oceanbase/index.ts +++ b/packages/service/common/vectorDB/oceanbase/index.ts @@ -1,9 +1,14 @@ /* oceanbase vector crud */ -import { DatasetVectorTableName } from '../constants'; -import { delay, retryFn } from '@fastgpt/global/common/system/utils'; +import { + DatasetVectorTableName, + DBDatasetVectorTableName, + DBDatasetValueVectorTableName +} from '../constants'; import { ObClient } from './controller'; import { type RowDataPacket } from 'mysql2/promise'; import { + type DatabaseEmbeddingRecallCtrlProps, + type DatabaseEmbeddingRecallResponse, type DelDatasetVectorCtrlProps, type EmbeddingRecallCtrlProps, type EmbeddingRecallResponse, @@ -26,7 +31,31 @@ export class ObVectorCtrl { createtime TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); `); + /********************** Database Vector Store **********************/ + await ObClient.query(` + CREATE TABLE IF NOT EXISTS ${DBDatasetVectorTableName} ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + vector VECTOR(1536) NOT NULL, + team_id VARCHAR(50) NOT NULL, + dataset_id VARCHAR(50) NOT NULL, + collection_id VARCHAR(50) NOT NULL, + column_des_index VARCHAR(1024) NOT NULL, + createtime TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + `); + await ObClient.query(` + CREATE TABLE IF NOT EXISTS ${DBDatasetValueVectorTableName} ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + vector VECTOR(1536) NOT NULL, + team_id VARCHAR(50) NOT NULL, + dataset_id VARCHAR(50) NOT NULL, + collection_id VARCHAR(50) NOT NULL, + column_val_index VARCHAR(1024) NOT NULL, + createtime TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + `); + // modeldata await ObClient.query( `CREATE VECTOR INDEX IF NOT EXISTS vector_index ON ${DatasetVectorTableName}(vector) WITH (distance=inner_product, type=hnsw, m=32, ef_construction=128);` ); @@ -37,14 +66,46 @@ export class ObVectorCtrl { `CREATE INDEX IF NOT EXISTS create_time_index ON ${DatasetVectorTableName}(createtime);` ); + // coulumndescriptionindex + await ObClient.query( + `CREATE VECTOR INDEX IF NOT EXISTS table_des_vector_index ON ${DBDatasetVectorTableName}(vector) WITH (distance=cosine, type=hnsw, m=32, ef_construction=128);` + ); + await ObClient.query( + `CREATE INDEX IF NOT EXISTS table_des_team_dataset_collection_index ON ${DBDatasetVectorTableName}(team_id, dataset_id, collection_id);` + ); + await ObClient.query( + `CREATE INDEX IF NOT EXISTS table_des_create_time_index ON ${DBDatasetVectorTableName}(createtime);` + ); + + // coulumnvalueindex + await ObClient.query( + `CREATE VECTOR INDEX IF NOT EXISTS table_val_vector_index ON ${DBDatasetValueVectorTableName}(vector) WITH (distance=cosine, type=hnsw, m=32, ef_construction=128);` + ); + await ObClient.query( + `CREATE INDEX IF NOT EXISTS table_val_team_dataset_collection_index ON ${DBDatasetValueVectorTableName}(team_id, dataset_id, collection_id);` + ); + await ObClient.query( + `CREATE INDEX IF NOT EXISTS table_val_create_time_index ON ${DBDatasetValueVectorTableName}(createtime);` + ); addLog.info('init oceanbase successful'); } catch (error) { addLog.error('init oceanbase error', error); } }; - insert = async (props: InsertVectorControllerProps): Promise<{ insertIds: string[] }> => { - const { teamId, datasetId, collectionId, vectors } = props; + insert = async (props: InsertVectorControllerProps): Promise<{ insertIds: string[] }> => { + const { + teamId, + datasetId, + collectionId, + vectors, + tableName = DatasetVectorTableName, + column_des_index, + column_val_index + } = props; + console.info( + `[ob insert] tableName:${tableName},value:${column_des_index || column_val_index}` + ); const values = vectors.map((vector) => [ { key: 'vector', value: `[${vector}]` }, { key: 'team_id', value: String(teamId) }, @@ -52,7 +113,16 @@ export class ObVectorCtrl { { key: 'collection_id', value: String(collectionId) } ]); - const { rowCount, rows } = await ObClient.insert(DatasetVectorTableName, { + // Add db schema special fields + if (column_des_index) { + values.map((item) => item.push({ key: 'column_des_index', value: column_des_index })); + } + + if (column_val_index) { + values.map((item) => item.push({ key: 'column_val_index', value: column_val_index })); + } + + const { rowCount, insertIds } = await ObClient.insert(tableName || DatasetVectorTableName, { values }); @@ -61,11 +131,11 @@ export class ObVectorCtrl { } return { - insertIds: rows.map((row) => row.id) + insertIds }; }; delete = async (props: DelDatasetVectorCtrlProps): Promise => { - const { teamId } = props; + const { teamId, tableName = DatasetVectorTableName } = props; const teamIdWhere = `team_id='${String(teamId)}' AND`; @@ -95,7 +165,7 @@ export class ObVectorCtrl { if (!where) return; - await ObClient.delete(DatasetVectorTableName, { + await ObClient.delete(tableName, { where: [where] }); }; @@ -159,6 +229,76 @@ export class ObVectorCtrl { })) }; }; + + databaseEmbRecall = async ( + props: DatabaseEmbeddingRecallCtrlProps + ): Promise => { + const { + teamId, + datasetIds, + vector, + limit, + tableName, + retry = 2, + forbidCollectionIdList + } = props; + + let index: string = ''; + if (tableName == DBDatasetVectorTableName) index = 'column_des_index'; + if (tableName == DBDatasetValueVectorTableName) index = 'column_val_index'; + + + try { + const forbidCollectionSql = + forbidCollectionIdList.length > 0 + ? `AND collection_id NOT IN (${forbidCollectionIdList + .map((id) => `'${String(id)}'`) + .join(',')})` + : ''; + // cosine_distance(a,b)= 1 − cosine_similarity(a,b) , range [0,2] + const rows = await ObClient.query< + ({ + id: string; + collection_id: string; + index_field?: string; + distance: number; + } & RowDataPacket)[][] + >( + `BEGIN; + SET ob_hnsw_ef_search = ${global.systemEnv?.hnswEfSearch || 100}; + SELECT id, + collection_id, + ${index ? `${index} AS index_field,` : ''} + cosine_distance(vector, [${vector}]) AS distance + FROM ${tableName} + WHERE team_id='${teamId}' + AND dataset_id IN (${datasetIds.map((id) => `'${String(id)}'`).join(',')}) + ${forbidCollectionSql} + ORDER BY distance ASC APPROXIMATE LIMIT ${limit}; + COMMIT;` + ).then(([rows]) => rows[2] || []); + + return { + results: rows.map((item) => ({ + id: String(item.id), + collectionId: item.collection_id, + score: 1 - item.distance, + ...(tableName === DBDatasetVectorTableName + ? { columnDesIndex: item.index_field } + : { columnValIndex: item.index_field }) + })) + }; + } catch (error) { + if (retry <= 0) { + return Promise.reject(error); + } + return this.databaseEmbRecall({ + ...props, + retry: retry - 1 + }); + } + }; + getVectorDataByTime = async (start: Date, end: Date) => { const rows = await ObClient.query< ({ diff --git a/packages/service/common/vectorDB/pg/index.ts b/packages/service/common/vectorDB/pg/index.ts index abe51f4ca26a..e3e0b6719c1a 100644 --- a/packages/service/common/vectorDB/pg/index.ts +++ b/packages/service/common/vectorDB/pg/index.ts @@ -1,13 +1,18 @@ /* pg vector crud */ -import { DatasetVectorTableName } from '../constants'; -import { delay, retryFn } from '@fastgpt/global/common/system/utils'; +import { + DatasetVectorTableName, + DBDatasetVectorTableName, + DBDatasetValueVectorTableName +} from '../constants'; import { PgClient, connectPg } from './controller'; import { type PgSearchRawType } from '@fastgpt/global/core/dataset/api'; import type { DelDatasetVectorCtrlProps, EmbeddingRecallCtrlProps, EmbeddingRecallResponse, - InsertVectorControllerProps + InsertVectorControllerProps, + DatabaseEmbeddingRecallCtrlProps, + DatabaseEmbeddingRecallResponse } from '../controller.d'; import dayjs from 'dayjs'; import { addLog } from '../../system/log'; @@ -28,6 +33,32 @@ export class PgVectorCtrl { createtime TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); `); + // Create Table `ColumnDescriptionIndex` to Record the description of the table (Text2sql) + await PgClient.query(` + CREATE TABLE IF NOT EXISTS ${DBDatasetVectorTableName} ( + id BIGSERIAL PRIMARY KEY, + vector VECTOR(1536) NOT NULL, + dataset_id VARCHAR(50) NOT NULL, + collection_id VARCHAR(50) NOT NULL, + column_des_index VARCHAR(1024) NOT NULL, + team_id VARCHAR(50) NOT NULL, + createtime TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + `); + + // Create Table `ColumnValueIndex` to Record the example value of the table (Text2sql) + // foreign key: (table_val_index, LENGTH(table_des_index)) = table_des_index + await PgClient.query(` + CREATE TABLE IF NOT EXISTS ${DBDatasetValueVectorTableName} ( + id BIGSERIAL PRIMARY KEY, + vector VECTOR(1536) NOT NULL, + team_id VARCHAR(50) NOT NULL, + dataset_id VARCHAR(50) NOT NULL, + collection_id VARCHAR(50) NOT NULL, + column_val_index VARCHAR(1024) NOT NULL, + createtime TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + `); await PgClient.query( `CREATE INDEX CONCURRENTLY IF NOT EXISTS vector_index ON ${DatasetVectorTableName} USING hnsw (vector vector_ip_ops) WITH (m = 32, ef_construction = 128);` @@ -38,6 +69,28 @@ export class PgVectorCtrl { await PgClient.query( `CREATE INDEX CONCURRENTLY IF NOT EXISTS create_time_index ON ${DatasetVectorTableName} USING btree(createtime);` ); + + // ColumnDescriptionIndex + await PgClient.query( + `CREATE INDEX CONCURRENTLY IF NOT EXISTS table_des_vector_index ON ${DBDatasetVectorTableName} USING hnsw (vector vector_ip_ops) WITH (m = 32, ef_construction = 128);` + ); + await PgClient.query( + `CREATE INDEX CONCURRENTLY IF NOT EXISTS table_team_dataset_collection_index ON ${DBDatasetVectorTableName} USING btree(team_id, dataset_id, collection_id);` + ); + await PgClient.query( + `CREATE INDEX CONCURRENTLY IF NOT EXISTS table_des_create_time_index ON ${DBDatasetVectorTableName} USING btree(createtime);` + ); + + // ColumnValueIndex + await PgClient.query( + `CREATE INDEX CONCURRENTLY IF NOT EXISTS table_val_vector_index ON ${DBDatasetValueVectorTableName} USING hnsw (vector vector_ip_ops) WITH (m = 32, ef_construction = 128);` + ); + await PgClient.query( + `CREATE INDEX CONCURRENTLY IF NOT EXISTS table_val_team_dataset_collection_index ON ${DBDatasetValueVectorTableName} USING btree(team_id, dataset_id, collection_id);` + ); + await PgClient.query( + `CREATE INDEX CONCURRENTLY IF NOT EXISTS table_val_create_time_index ON ${DBDatasetValueVectorTableName} USING btree(createtime);` + ); // 10w rows // await PgClient.query(` // ALTER TABLE modeldata SET ( @@ -66,7 +119,15 @@ export class PgVectorCtrl { } }; insert = async (props: InsertVectorControllerProps): Promise<{ insertIds: string[] }> => { - const { teamId, datasetId, collectionId, vectors } = props; + const { + teamId, + datasetId, + collectionId, + vectors, + tableName = DatasetVectorTableName, + column_des_index, + column_val_index + } = props; const values = vectors.map((vector) => [ { key: 'vector', value: `[${vector}]` }, @@ -75,7 +136,14 @@ export class PgVectorCtrl { { key: 'collection_id', value: String(collectionId) } ]); - const { rowCount, rows } = await PgClient.insert(DatasetVectorTableName, { + // Add db schema special fields + if (column_des_index) { + values.map((item) => item.push({ key: 'column_des_index', value: column_des_index })); + } + if (column_val_index) { + values.map((item) => item.push({ key: 'column_val_index', value: column_val_index })); + } + const { rowCount, rows } = await PgClient.insert(tableName || DatasetVectorTableName, { values }); @@ -84,11 +152,11 @@ export class PgVectorCtrl { } return { - insertIds: rows.map((row) => row.id) + insertIds: rows.map((row: any) => row.id) }; }; delete = async (props: DelDatasetVectorCtrlProps): Promise => { - const { teamId } = props; + const { teamId, tableName = DatasetVectorTableName } = props; const teamIdWhere = `team_id='${String(teamId)}' AND`; @@ -118,7 +186,7 @@ export class PgVectorCtrl { if (!where) return; - await PgClient.delete(DatasetVectorTableName, { + await PgClient.delete(tableName || DatasetVectorTableName, { where: [where] }); }; @@ -186,6 +254,67 @@ export class PgVectorCtrl { })) }; }; + databaseEmbRecall = async ( + props: DatabaseEmbeddingRecallCtrlProps + ): Promise => { + const { + teamId, + datasetIds, + vector, + limit, + tableName, + retry = 2, + forbidCollectionIdList + } = props; + let index: string = ''; + if (tableName == DBDatasetVectorTableName) index = 'column_des_index'; + if (tableName == DBDatasetValueVectorTableName) index = 'column_val_index'; + + try { + // Build forbid collection filter + const forbidCollectionFilter = + forbidCollectionIdList.length > 0 + ? `AND collection_id NOT IN (${forbidCollectionIdList.map((id: string) => `'${String(id)}'`).join(',')})` + : ''; + const results: any = await PgClient.query(` + BEGIN; + SET LOCAL hnsw.ef_search = ${global.systemEnv?.hnswEfSearch || 100}; + SET LOCAL hnsw.max_scan_tuples = ${global.systemEnv?.hnswMaxScanTuples || 100000}; + SET LOCAL hnsw.iterative_scan = relaxed_order; + WITH relaxed_results AS MATERIALIZED ( + SELECT id, collection_id, + ${index} as index_field, + vector <=> '[${vector}]' AS score + FROM ${tableName} + WHERE team_id = '${teamId}' + AND dataset_id IN (${datasetIds.map((id: string) => `'${String(id)}'`).join(',')}) + ${forbidCollectionFilter} + ORDER BY score LIMIT ${limit} + ) SELECT id, collection_id, index_field, score FROM relaxed_results ORDER BY score; + COMMIT; + `); + + const rows = results?.[results.length - 2]?.rows || []; + return { + results: rows.map((row: any) => ({ + id: String(row.id), + collectionId: row.collection_id, + score: row.score * -1, // Convert cosine distance to similarity score + ...(tableName === DBDatasetVectorTableName + ? { columnDesIndex: row.index_field } + : { columnValIndex: row.index_field }) + })) + }; + } catch (error) { + if (retry <= 0) { + return Promise.reject(error); + } + return this.databaseEmbRecall({ + ...props, + retry: retry - 1 + }); + } + }; getVectorDataByTime = async (start: Date, end: Date) => { const { rows } = await PgClient.query<{ id: string; diff --git a/packages/service/core/ai/config/utils.ts b/packages/service/core/ai/config/utils.ts index 5636727ea797..66b2d3b0c21a 100644 --- a/packages/service/core/ai/config/utils.ts +++ b/packages/service/core/ai/config/utils.ts @@ -47,6 +47,9 @@ export const loadSystemModels = async (init = false) => { if (model.isDefaultDatasetImageModel) { global.systemDefaultModel.datasetImageLLM = model; } + if (model.isDefaultEvaluationModel) { + global.systemDefaultModel.evaluation = model; + } } else if (model.type === ModelTypeEnum.embedding) { global.embeddingModelMap.set(model.model, model); global.embeddingModelMap.set(model.name, model); @@ -154,6 +157,11 @@ export const loadSystemModels = async (init = false) => { (item) => item.vision ); } + if (!global.systemDefaultModel.evaluation) { + global.systemDefaultModel.evaluation = Array.from(global.llmModelMap.values()).find( + (item) => item.useInEvaluation + ); + } if (!global.systemDefaultModel.embedding) { global.systemDefaultModel.embedding = Array.from(global.embeddingModelMap.values())[0]; } diff --git a/packages/service/core/ai/model.ts b/packages/service/core/ai/model.ts index 7821b9e73e36..790c319834aa 100644 --- a/packages/service/core/ai/model.ts +++ b/packages/service/core/ai/model.ts @@ -48,6 +48,12 @@ export function getRerankModel(model?: string) { return global.reRankModelMap.get(model) || getDefaultRerankModel(); } +export const getDefaultEvaluationModel = () => global?.systemDefaultModel.evaluation; +export function getEvaluationModel(model?: string) { + if (!model) return getDefaultEvaluationModel(); + return global.llmModelMap.get(model) || getDefaultEvaluationModel(); +} + export const findAIModel = (model: string): SystemModelItemType | undefined => { return ( global.llmModelMap.get(model) || diff --git a/packages/service/core/ai/type.d.ts b/packages/service/core/ai/type.d.ts index 395bddc0469e..a4c42294d225 100644 --- a/packages/service/core/ai/type.d.ts +++ b/packages/service/core/ai/type.d.ts @@ -24,6 +24,7 @@ export type SystemDefaultModelType = { [ModelTypeEnum.llm]?: LLMModelItemType; datasetTextLLM?: LLMModelItemType; datasetImageLLM?: LLMModelItemType; + evaluation?: LLMModelItemType; [ModelTypeEnum.embedding]?: EmbeddingModelItemType; [ModelTypeEnum.tts]?: TTSModelType; diff --git a/packages/service/core/app/controller.ts b/packages/service/core/app/controller.ts index 55aab10f7f5d..03b5b1e56d77 100644 --- a/packages/service/core/app/controller.ts +++ b/packages/service/core/app/controller.ts @@ -1,14 +1,13 @@ import { type AppSchema } from '@fastgpt/global/core/app/type'; import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; +import type { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants'; import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; import { MongoApp } from './schema'; import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node'; import { encryptSecretValue, storeSecretValue } from '../../common/secret/utils'; import { SystemToolInputTypeEnum } from '@fastgpt/global/core/app/systemTool/constants'; import { type ClientSession } from '../../common/mongo'; -import { MongoEvaluation } from './evaluation/evalSchema'; -import { removeEvaluationJob } from './evaluation/mq'; import { deleteChatFiles } from '../chat/controller'; import { MongoChatItem } from '../chat/chatItemSchema'; import { MongoChat } from '../chat/chatSchema'; @@ -57,13 +56,15 @@ export const beforeUpdateAppFormat = ({ nodes }: { nodes?: StoreNodeItemType[] } return; } input.value = val - .map((dataset: { datasetId: string }) => ({ + .map((dataset: any & { datasetType?: DatasetTypeEnum }) => ({ + ...dataset, datasetId: dataset.datasetId })) .filter((item) => !!item.datasetId); } else if (typeof val === 'object' && val !== null) { input.value = [ { + ...(val as any & { datasetType?: DatasetTypeEnum }), datasetId: val.datasetId } ]; @@ -146,15 +147,6 @@ export const onDelOneApp = async ({ .filter((app) => app.type !== AppTypeEnum.folder) .map((app) => String(app._id)); - // Remove eval job - const evalJobs = await MongoEvaluation.find( - { - appId: { $in: apps.map((app) => app._id) } - }, - '_id' - ).lean(); - await Promise.all(evalJobs.map((evalJob) => removeEvaluationJob(evalJob._id))); - const del = async (session: ClientSession) => { for await (const app of apps) { const appId = app._id; diff --git a/packages/service/core/app/evaluation/evalItemSchema.ts b/packages/service/core/app/evaluation/evalItemSchema.ts deleted file mode 100644 index 45e8633da062..000000000000 --- a/packages/service/core/app/evaluation/evalItemSchema.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { connectionMongo, getMongoModel } from '../../../common/mongo'; -import { EvaluationCollectionName } from './evalSchema'; -import { - EvaluationStatusEnum, - EvaluationStatusValues -} from '@fastgpt/global/core/app/evaluation/constants'; -import type { EvalItemSchemaType } from '@fastgpt/global/core/app/evaluation/type'; - -const { Schema } = connectionMongo; - -export const EvalItemCollectionName = 'eval_items'; - -const EvalItemSchema = new Schema({ - evalId: { - type: Schema.Types.ObjectId, - ref: EvaluationCollectionName, - required: true - }, - question: { - type: String, - required: true - }, - expectedResponse: { - type: String, - required: true - }, - history: String, - globalVariables: Object, - response: String, - responseTime: Date, - - status: { - type: Number, - default: EvaluationStatusEnum.queuing, - enum: EvaluationStatusValues - }, - retry: { - type: Number, - default: 3 - }, - finishTime: Date, - - accuracy: Number, - relevance: Number, - semanticAccuracy: Number, - score: Number, // average score - - errorMessage: String -}); - -EvalItemSchema.index({ evalId: 1, status: 1 }); - -export const MongoEvalItem = getMongoModel( - EvalItemCollectionName, - EvalItemSchema -); diff --git a/packages/service/core/app/evaluation/evalSchema.ts b/packages/service/core/app/evaluation/evalSchema.ts deleted file mode 100644 index a8678ebda04b..000000000000 --- a/packages/service/core/app/evaluation/evalSchema.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { - TeamCollectionName, - TeamMemberCollectionName -} from '@fastgpt/global/support/user/team/constant'; -import { connectionMongo, getMongoModel } from '../../../common/mongo'; -import { AppCollectionName } from '../schema'; -import type { EvaluationSchemaType } from '@fastgpt/global/core/app/evaluation/type'; -import { UsageCollectionName } from '../../../support/wallet/usage/schema'; -const { Schema } = connectionMongo; - -export const EvaluationCollectionName = 'eval'; - -const EvaluationSchema = new Schema({ - teamId: { - type: Schema.Types.ObjectId, - ref: TeamCollectionName, - required: true - }, - tmbId: { - type: Schema.Types.ObjectId, - ref: TeamMemberCollectionName, - required: true - }, - appId: { - type: Schema.Types.ObjectId, - ref: AppCollectionName, - required: true - }, - usageId: { - type: Schema.Types.ObjectId, - ref: UsageCollectionName, - required: true - }, - evalModel: { - type: String, - required: true - }, - name: { - type: String, - required: true - }, - createTime: { - type: Date, - required: true, - default: () => new Date() - }, - finishTime: Date, - score: Number, - errorMessage: String -}); - -EvaluationSchema.index({ teamId: 1 }); - -export const MongoEvaluation = getMongoModel( - EvaluationCollectionName, - EvaluationSchema -); diff --git a/packages/service/core/app/evaluation/mq.ts b/packages/service/core/app/evaluation/mq.ts deleted file mode 100644 index c0192d4763d8..000000000000 --- a/packages/service/core/app/evaluation/mq.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { getQueue, getWorker, QueueNames } from '../../../common/bullmq'; -import { type Processor } from 'bullmq'; -import { addLog } from '../../../common/system/log'; - -export type EvaluationJobData = { - evalId: string; -}; - -export const evaluationQueue = getQueue(QueueNames.evaluation, { - defaultJobOptions: { - attempts: 3, - backoff: { - type: 'exponential', - delay: 1000 - } - } -}); - -const concurrency = process.env.EVAL_CONCURRENCY ? Number(process.env.EVAL_CONCURRENCY) : 3; -export const getEvaluationWorker = (processor: Processor) => { - return getWorker(QueueNames.evaluation, processor, { - removeOnFail: { - count: 1000 // Keep last 1000 failed jobs - }, - concurrency: concurrency - }); -}; - -export const addEvaluationJob = (data: EvaluationJobData) => { - const evalId = String(data.evalId); - - return evaluationQueue.add(evalId, data, { deduplication: { id: evalId } }); -}; - -export const checkEvaluationJobActive = async (evalId: string): Promise => { - try { - const jobId = await evaluationQueue.getDeduplicationJobId(String(evalId)); - if (!jobId) return false; - - const job = await evaluationQueue.getJob(jobId); - if (!job) return false; - - const jobState = await job.getState(); - return ['waiting', 'delayed', 'prioritized', 'active'].includes(jobState); - } catch (error) { - addLog.error('Failed to check evaluation job status', { evalId, error }); - return false; - } -}; - -export const removeEvaluationJob = async (evalId: string): Promise => { - const formatEvalId = String(evalId); - try { - const jobId = await evaluationQueue.getDeduplicationJobId(formatEvalId); - if (!jobId) { - addLog.warn('No job found to remove', { evalId }); - return false; - } - - const job = await evaluationQueue.getJob(jobId); - if (!job) { - addLog.warn('Job not found in queue', { evalId, jobId }); - return false; - } - - const jobState = await job.getState(); - - if (['waiting', 'delayed', 'prioritized'].includes(jobState)) { - await job.remove(); - addLog.info('Evaluation job removed successfully', { evalId, jobId, jobState }); - return true; - } else { - addLog.warn('Cannot remove active or completed job', { evalId, jobId, jobState }); - return false; - } - } catch (error) { - addLog.error('Failed to remove evaluation job', { evalId, error }); - return false; - } -}; diff --git a/packages/service/core/app/plugin/controller.ts b/packages/service/core/app/plugin/controller.ts index 621ac4e34aa2..67232d5f9fc1 100644 --- a/packages/service/core/app/plugin/controller.ts +++ b/packages/service/core/app/plugin/controller.ts @@ -15,7 +15,7 @@ import { } from '@fastgpt/global/core/workflow/utils'; import { MongoApp } from '../schema'; import type { localeType } from '@fastgpt/global/common/i18n/type'; -import { parseI18nString } from '@fastgpt/global/common/i18n/utils'; +import { parseI18nString, parseI18nArray } from '@fastgpt/global/common/i18n/utils'; import type { WorkflowTemplateBasicType } from '@fastgpt/global/core/workflow/type'; import { type SystemPluginTemplateItemType } from '@fastgpt/global/core/app/plugin/type'; import { @@ -37,7 +37,8 @@ import { Types } from '../../../common/mongo'; import type { SystemPluginConfigSchemaType } from './type'; import type { FlowNodeInputItemType, - FlowNodeOutputItemType + FlowNodeOutputItemType, + InputConfigType } from '@fastgpt/global/core/workflow/type/io'; import { isProduction } from '@fastgpt/global/common/system/constants'; import { Output_Template_Error_Message } from '@fastgpt/global/core/workflow/template/output'; @@ -58,9 +59,10 @@ type ChildAppType = SystemPluginTemplateItemType & { export const getSystemPluginByIdAndVersionId = async ( pluginId: string, - versionId?: string + versionId?: string, + lang: localeType = 'en' ): Promise => { - const plugin = await getSystemToolById(pluginId); + const plugin = await getSystemToolById(pluginId, lang); // Admin selected system tool if (plugin.associatedPluginId) { @@ -129,14 +131,21 @@ export const getSystemPluginByIdAndVersionId = async ( const lastVersion = versionList[0]; // concat parent (if exists) input config - const parent = plugin.parentId ? await getSystemToolById(plugin.parentId) : undefined; - if (parent?.inputList) { - version?.inputs?.unshift({ - key: NodeInputKeyEnum.systemInputConfig, - label: '', - renderTypeList: [FlowNodeInputTypeEnum.hidden], - inputList: parent.inputList - }); + const parent = plugin.parentId ? await getSystemToolById(plugin.parentId, lang) : undefined; + if (parent?.inputList && parent.inputList.length > 0) { + // Check if system_input_config already exists in version.inputs + const hasSystemInputConfig = version?.inputs?.some( + (input) => input.key === NodeInputKeyEnum.systemInputConfig + ); + + if (!hasSystemInputConfig) { + version?.inputs?.unshift({ + key: NodeInputKeyEnum.systemInputConfig, + label: '', + renderTypeList: [FlowNodeInputTypeEnum.hidden], + inputList: parent.inputList + }); + } } return { @@ -263,7 +272,7 @@ export async function getChildAppPreviewNode({ // 1. System Tools // 2. System Plugins configured in Pro (has associatedPluginId) else { - return getSystemPluginByIdAndVersionId(pluginId, versionId); + return getSystemPluginByIdAndVersionId(pluginId, versionId, lang); } })(); @@ -280,14 +289,20 @@ export async function getChildAppPreviewNode({ if (source === PluginSourceEnum.systemTool) { // system Tool or Toolsets const children = app.isFolder - ? (await getSystemTools()).filter((item) => item.parentId === pluginId) + ? (await getSystemTools(lang)).filter((item) => item.parentId === pluginId) : []; + // Check if system_input_config already exists in app.inputs + const hasSystemInputConfig = app.inputs?.some( + (input) => input.key === NodeInputKeyEnum.systemInputConfig + ); + return { flowNodeType: app.isFolder ? FlowNodeTypeEnum.toolSet : FlowNodeTypeEnum.tool, nodeIOConfig: { inputs: [ - ...(app.inputList + // Only add system_input_config if it doesn't exist and app.inputList is available + ...(!hasSystemInputConfig && app.inputList ? [ { key: NodeInputKeyEnum.systemInputConfig, @@ -443,7 +458,7 @@ export async function getChildAppRuntimeById({ pluginOrder: 0 }; } else { - return getSystemPluginByIdAndVersionId(pluginId, versionId); + return getSystemPluginByIdAndVersionId(pluginId, versionId, lang); } })(); @@ -506,6 +521,7 @@ const dbPluginFormat = (item: SystemPluginConfigSchemaType): SystemPluginTemplat function getCachedSystemPlugins() { if (!global.systemPlugins_cache) { global.systemPlugins_cache = { + lang: 'en', expires: 0, data: [] as SystemPluginTemplateItemType[] }; @@ -527,74 +543,99 @@ export const refetchSystemPlugins = () => { }); }; -export const getSystemTools = async (): Promise => { - if (getCachedSystemPlugins().expires > Date.now() && isProduction) { - return getCachedSystemPlugins().data; - } else { - const tools = await APIGetSystemToolList(); +export const getSystemTools = async ( + lang: localeType = 'en' +): Promise => { + const cache = getCachedSystemPlugins(); + const isCacheValid = cache.expires > Date.now() && isProduction; + const isLangMatched = cache.lang === lang; - // 从数据库里加载插件配置进行替换 - const systemToolsArray = await MongoSystemPlugin.find({}).lean(); - const systemTools = new Map(systemToolsArray.map((plugin) => [plugin.pluginId, plugin])); + // 检查缓存是否有效且语言匹配 + if (isCacheValid && isLangMatched) { + return cache.data; + } - const formatTools = tools.map((item) => { - const dbPluginConfig = systemTools.get(item.id); - const isFolder = tools.some((tool) => tool.parentId === item.id); + // 语言不匹配时清空缓存 + if (!isLangMatched) { + cleanSystemPluginCache(); + } - const versionList = (item.versionList as SystemPluginTemplateItemType['versionList']) || []; + const tools = await APIGetSystemToolList(); - return { - id: item.id, - parentId: item.parentId, - isFolder, - name: item.name, - avatar: item.avatar, - intro: item.description, - toolDescription: item.toolDescription, - author: item.author, - courseUrl: item.courseUrl, - instructions: dbPluginConfig?.customConfig?.userGuide, - weight: item.weight, - workflow: { - nodes: [], - edges: [] - }, - versionList, - templateType: item.templateType, - showStatus: true, - isActive: dbPluginConfig?.isActive ?? item.isActive ?? true, - inputList: item?.secretInputConfig, - hasSystemSecret: !!dbPluginConfig?.inputListVal, - - originCost: dbPluginConfig?.originCost ?? 0, - currentCost: dbPluginConfig?.currentCost ?? 0, - systemKeyCost: dbPluginConfig?.systemKeyCost ?? 0, - hasTokenFee: dbPluginConfig?.hasTokenFee ?? false, - pluginOrder: dbPluginConfig?.pluginOrder - }; - }); + // 从数据库里加载插件配置进行替换 + const systemToolsArray = await MongoSystemPlugin.find({}).lean(); + const systemTools = new Map(systemToolsArray.map((plugin) => [plugin.pluginId, plugin])); - // TODO: Check the app exists - const dbPlugins = systemToolsArray - .filter((item) => item.customConfig?.associatedPluginId) - .map((item) => dbPluginFormat(item)); + const formatTools = tools.map((item) => { + const dbPluginConfig = systemTools.get(item.id); + const isFolder = tools.some((tool) => tool.parentId === item.id); - const plugins = [...formatTools, ...dbPlugins]; - plugins.sort((a, b) => (a.pluginOrder ?? 0) - (b.pluginOrder ?? 0)); + // 解析并确保 versionList 中的 outputs 有有效的 id + const versionList = (parseI18nArray(item.versionList, lang) || []).map((version) => ({ + ...version, + outputs: version.outputs.map((output) => ({ + ...output, + type: output.type ?? FlowNodeOutputTypeEnum.static, + id: output.id ?? output.key + })) + })); - global.systemPlugins_cache = { - expires: Date.now() + 30 * 60 * 1000, // 30 minutes - data: plugins + return { + id: item.id, + parentId: item.parentId, + isFolder, + name: parseI18nString(item.name, lang), + avatar: item.avatar, + intro: parseI18nString(item.description, lang), + toolDescription: parseI18nString(item.toolDescription, lang), + author: item.author, + courseUrl: item.courseUrl, + instructions: dbPluginConfig?.customConfig?.userGuide, + weight: item.weight, + workflow: { + nodes: [], + edges: [] + }, + versionList, + templateType: item.templateType, + showStatus: true, + isActive: dbPluginConfig?.isActive ?? item.isActive ?? true, + inputList: parseI18nArray(item?.secretInputConfig, lang), + hasSystemSecret: !!dbPluginConfig?.inputListVal, + + originCost: dbPluginConfig?.originCost ?? 0, + currentCost: dbPluginConfig?.currentCost ?? 0, + systemKeyCost: dbPluginConfig?.systemKeyCost ?? 0, + hasTokenFee: dbPluginConfig?.hasTokenFee ?? false, + pluginOrder: dbPluginConfig?.pluginOrder }; + }); - return plugins; - } + // TODO: Check the app exists + const dbPlugins = systemToolsArray + .filter((item) => item.customConfig?.associatedPluginId) + .map((item) => dbPluginFormat(item)); + + const plugins = [...formatTools, ...dbPlugins]; + plugins.sort((a, b) => (a.pluginOrder ?? 0) - (b.pluginOrder ?? 0)); + + // 更新缓存,保存当前语言 + global.systemPlugins_cache = { + lang, + expires: Date.now() + 30 * 60 * 1000, // 30 minutes + data: plugins + }; + + return plugins; }; -export const getSystemToolById = async (id: string): Promise => { +export const getSystemToolById = async ( + id: string, + lang: localeType = 'en' +): Promise => { const { source, pluginId } = splitCombinePluginId(id); if (source === PluginSourceEnum.systemTool) { - const tools = await getSystemTools(); + const tools = await getSystemTools(lang); const tool = tools.find((item) => item.id === pluginId); if (tool) { return cloneDeep(tool); @@ -610,6 +651,7 @@ export const getSystemToolById = async (id: string): Promise { if (item.key !== NodeInputKeyEnum.datasetSelectList) return; - const val = item.value as undefined | { datasetId: string }[] | { datasetId: string }; + const val = item.value as + | undefined + | (any & { datasetType?: DatasetTypeEnum })[] + | (any & { datasetType?: DatasetTypeEnum }); if (Array.isArray(val)) { item.value = val @@ -172,7 +176,8 @@ export async function rewriteAppWorkflowToDetail({ datasetId: data.datasetId, avatar: data.avatar, name: data.name, - vectorModel: data.vectorModel + vectorModel: data.vectorModel, + datasetType: v.datasetType }; }) .filter(Boolean); @@ -193,7 +198,8 @@ export async function rewriteAppWorkflowToDetail({ datasetId: data.datasetId, avatar: data.avatar, name: data.name, - vectorModel: data.vectorModel + vectorModel: data.vectorModel, + datasetType: val.datasetType } ]; } diff --git a/packages/service/core/dataset/collection/controller.ts b/packages/service/core/dataset/collection/controller.ts index 3e0231005d08..5b4fe06f1f58 100644 --- a/packages/service/core/dataset/collection/controller.ts +++ b/packages/service/core/dataset/collection/controller.ts @@ -37,7 +37,11 @@ import { } from '@fastgpt/global/core/dataset/training/utils'; import { DatasetDataIndexTypeEnum } from '@fastgpt/global/core/dataset/data/constants'; import { clearCollectionImages, removeDatasetImageExpiredTime } from '../image/utils'; - +import { + DBDatasetVectorTableName, + DBDatasetValueVectorTableName, + DatasetVectorTableName +} from '../../../common/vectorDB/constants'; export const createCollectionAndInsertData = async ({ dataset, rawText, @@ -274,7 +278,9 @@ export async function createOneCollection({ session, ...props }: CreateOneCollec externalFileId, externalFileUrl, apiFileId, - apiFileParentId + apiFileParentId, + tableSchema, + forbid } = props; const collectionTags = await createOrGetCollectionTags({ @@ -300,7 +306,9 @@ export async function createOneCollection({ session, ...props }: CreateOneCollec ...(externalFileId ? { externalFileId } : {}), ...(externalFileUrl ? { externalFileUrl } : {}), ...(apiFileId ? { apiFileId } : {}), - ...(apiFileParentId ? { apiFileParentId } : {}) + ...(apiFileParentId ? { apiFileParentId } : {}), + ...(tableSchema ? { tableSchema } : {}), + forbid: forbid ?? false } ], { session, ordered: true } @@ -415,7 +423,24 @@ export async function delCollection({ ] : []), // Delete vector data - deleteDatasetDataVector({ teamId, datasetIds, collectionIds }) + deleteDatasetDataVector({ + teamId, + datasetIds, + collectionIds, + tableName: DatasetVectorTableName + }), + deleteDatasetDataVector({ + teamId, + datasetIds, + collectionIds, + tableName: DBDatasetVectorTableName + }), + deleteDatasetDataVector({ + teamId, + datasetIds, + collectionIds, + tableName: DBDatasetValueVectorTableName + }) ]); // delete collections diff --git a/packages/service/core/dataset/collection/schema.ts b/packages/service/core/dataset/collection/schema.ts index 61ddc48a099b..1415ddedc096 100644 --- a/packages/service/core/dataset/collection/schema.ts +++ b/packages/service/core/dataset/collection/schema.ts @@ -10,6 +10,49 @@ import { export const DatasetColCollectionName = 'dataset_collections'; +// Column Schema for database tables +const ColumnSchema = new Schema( + { + columnName: { type: String, required: true }, + columnType: { type: String, default: 'TEXT' }, + description: { type: String, default: '' }, + examples: { type: [String], default: [] }, + forbid: { type: Boolean, default: false }, + valueIndex: { type: Boolean, default: true }, + + // Database attributes + isNullable: { type: Boolean, default: true }, + defaultValue: { type: String, default: null }, + isAutoIncrement: { type: Boolean, default: false }, + isPrimaryKey: { type: Boolean, default: false }, + isForeignKey: { type: Boolean, default: false }, + relatedColumns: { type: [String], default: [] }, + + // Extended metadata + metadata: { type: Object, default: {} } + }, + { _id: false } +); + +// Constraint Schema +const ConstraintSchema = new Schema( + { + name: { type: String, required: true }, + column: { type: String, default: '' } + }, + { _id: false } +); + +// Foreign Key Schema +const ForeignKeySchema = new Schema( + { + referredSchema: { type: String }, + referredTable: { type: String, required: true }, + referredColumns: { type: String, required: true } + }, + { _id: false } +).add(ConstraintSchema); + const DatasetCollectionSchema = new Schema({ parentId: { type: Schema.Types.ObjectId, @@ -55,7 +98,22 @@ const DatasetCollectionSchema = new Schema({ type: Date, default: () => new Date() }, - + tableSchema: { + type: { + tableName: { type: String, required: true }, + description: { type: String, default: '' }, + exist: { type: Boolean, default: true }, + columns: { + type: Map, + of: ColumnSchema, + default: {} + }, + foreignKeys: { type: [ForeignKeySchema], default: [] }, + primaryKeys: { type: [String], default: [] }, + constraints: { type: [ConstraintSchema], default: [] }, + lastUpdated: { type: Date, default: Date.now } + } + }, // Metadata // local file collection fileId: { diff --git a/packages/service/core/dataset/collection/utils.ts b/packages/service/core/dataset/collection/utils.ts index e2334ef0b3a9..8405e4f79dee 100644 --- a/packages/service/core/dataset/collection/utils.ts +++ b/packages/service/core/dataset/collection/utils.ts @@ -231,6 +231,9 @@ export const getTrainingModeByCollection = ({ if (trainingType === DatasetCollectionDataProcessModeEnum.qa) { return TrainingModeEnum.qa; } + if (trainingType === DatasetCollectionDataProcessModeEnum.databaseSchema) { + return TrainingModeEnum.databaseSchema; + } if ( trainingType === DatasetCollectionDataProcessModeEnum.chunk && imageIndex && diff --git a/packages/service/core/dataset/controller.ts b/packages/service/core/dataset/controller.ts index dade64db9214..963437640357 100644 --- a/packages/service/core/dataset/controller.ts +++ b/packages/service/core/dataset/controller.ts @@ -15,7 +15,7 @@ import { removeDatasetSyncJobScheduler } from './datasetSync'; import { mongoSessionRun } from '../../common/mongo/sessionRun'; import { removeImageByPath } from '../../common/file/image/controller'; import { UserError } from '@fastgpt/global/common/error/utils'; - +import { DBDatasetVectorTableName,DBDatasetValueVectorTableName,DatasetVectorTableName } from '../../common/vectorDB/constants'; /* ============= dataset ========== */ /* find all datasetId by top datasetId */ export async function findDatasetAndAllChildren({ @@ -113,7 +113,9 @@ export async function delDatasetRelevantData({ // Delete dataset Image clearDatasetImages(datasetIds), // Delete vector data - deleteDatasetDataVector({ teamId, datasetIds }) + deleteDatasetDataVector({ teamId, datasetIds,tableName:DBDatasetVectorTableName}), + deleteDatasetDataVector({ teamId, datasetIds,tableName:DBDatasetValueVectorTableName}), + deleteDatasetDataVector({ teamId, datasetIds,tableName:DatasetVectorTableName}) ]); }); diff --git a/packages/service/core/dataset/database/clientManager.ts b/packages/service/core/dataset/database/clientManager.ts new file mode 100644 index 000000000000..b5183f50b126 --- /dev/null +++ b/packages/service/core/dataset/database/clientManager.ts @@ -0,0 +1,96 @@ +import type { DatabaseConfig } from '@fastgpt/global/core/dataset/type'; +import { DatabaseErrEnum } from '@fastgpt/global/common/error/code/database'; +import { addLog } from '../../../common/system/log'; +import { MysqlClient } from './model/mysql'; +import type { AsyncDB } from './model/asyncDB'; +import { MongoDataset } from '../schema'; +import { i18nT } from '../../../../web/i18n/utils'; +import { DatabaseTypeEnum } from '@fastgpt/global/core/dataset/constants'; + +export async function createDatabaseClient(config: DatabaseConfig): Promise { + switch (config.clientType) { + case DatabaseTypeEnum.mysql: + return MysqlClient.fromConfig(config); + default: + return Promise.reject(DatabaseErrEnum.notSupportType); + } +} + +export async function checkDatabaseConnection(config: DatabaseConfig): Promise { + let dbClient: AsyncDB | undefined; + try { + dbClient = await createDatabaseClient(config); + const result = await dbClient.checkConnection(); + return result; + } catch (err: any) { + return Promise.reject(err); + } finally { + if (dbClient) { + try { + await dbClient.destroy(); + } catch (destroyErr: any) { + addLog.warn(`Failed to destroy client after connection test`, destroyErr.message); + } + } + } +} + +export async function withDatabaseClient( + datasetId: string, + operation: (client: AsyncDB) => Promise +): Promise { + let dbClient: AsyncDB | undefined; + try { + const dataset = await MongoDataset.findById(datasetId); + if (!dataset?.databaseConfig) { + return Promise.reject(DatabaseErrEnum.dbConfigNotFound); + } + + dbClient = await createDatabaseClient(dataset.databaseConfig); + await dbClient.checkConnection(); + const result = await operation(dbClient); + + return result; + } catch (err) { + addLog.error(`Database operation failed`, err); + return Promise.reject(i18nT('database_client:op_unknown_database_error')); + } finally { + if (dbClient) { + try { + await dbClient.destroy(); + } catch (destroyErr: any) { + addLog.warn(`Failed to destroy database client for dataset ${datasetId}`, { + error: destroyErr.message + }); + } + } + } +} +export async function withDatabaseClientByConfig( + config: DatabaseConfig, + operation: (client: AsyncDB) => Promise +): Promise { + let dbClient: AsyncDB | undefined; + + try { + dbClient = await createDatabaseClient(config); + + await dbClient.checkConnection(); + const result = await operation(dbClient); + + return result; + } catch (err) { + addLog.error(`Database operation failed`, err); + return Promise.reject(i18nT('database_client:op_unknown_database_error')); + } finally { + if (dbClient) { + try { + await dbClient.destroy(); + } catch (destroyErr: any) { + addLog.warn(`Failed to destroy database client`, { + error: destroyErr.message + }); + } + } + } +} diff --git a/packages/service/core/dataset/database/model/asyncDB.ts b/packages/service/core/dataset/database/model/asyncDB.ts new file mode 100644 index 000000000000..2323fc70c7d7 --- /dev/null +++ b/packages/service/core/dataset/database/model/asyncDB.ts @@ -0,0 +1,225 @@ +import { DatabaseTypeEnum } from '@fastgpt/global/core/dataset/constants'; +import { DatabaseErrEnum } from '@fastgpt/global/common/error/code/database'; +import type { DatabaseConfig } from '@fastgpt/global/core/dataset/type'; +import { DBTable, TableColumn, TableForeignKey } from './dataModel'; +import { truncateText, isStringType, convertValueToString } from './utils'; +import type { TableColumn as ORMColumn, ColumnType, DataSourceOptions, Driver } from 'typeorm'; +import { DataSource } from 'typeorm'; + +export abstract class AsyncDB { + protected db: DataSource; + protected config: DatabaseConfig; + protected sample_value_num: number; + protected sql_result_limit: number; + protected max_string_length: number; + protected db_server_info: string; + protected table_names: Array; + + constructor( + db: DataSource, + config: DatabaseConfig, + sample_value_num: number = 3, + sql_result_limit: number = 100, + max_string_length: number = 1024 + ) { + this.db = db; + this.config = config; + this.sample_value_num = sample_value_num; + this.sql_result_limit = sql_result_limit; + this.max_string_length = max_string_length; + this.db_server_info = ''; + this.table_names = new Array(); + } + + static fromConfig(config: DatabaseConfig): AsyncDB { + throw new Error(DatabaseErrEnum.notImplemented); + } + + static from_uri(config: DatabaseConfig): DataSource { + const options: DataSourceOptions = { + type: config.clientType as any, + host: config.host, + port: config.port, + username: config.user, + password: config.password, + database: config.database, + synchronize: false, + logging: false + }; + console.debug(`[AsyncDB.from_uri]:${Object.values(options)}`); + return new DataSource(options); + } + + async checkConnection(): Promise { + try { + if (!this.db.isInitialized) { + await this.db.initialize(); + } + await this.db.query('SELECT 1'); + return Promise.resolve(true); + } catch (err: any) { + return Promise.reject(err); + } + } + + async destroy(): Promise { + try { + if (this.db.isInitialized) { + await this.db.destroy(); + } + // @ts-ignore + this.db = null; + } catch (err: any) { + return Promise.reject(DatabaseErrEnum.clientDestroyError); + } + } + + async dialect(): Promise { + return new Promise((resolve, reject) => { + if (!this.db.isInitialized) { + reject(DatabaseErrEnum.clientNotFound); + return; + } + resolve(this.db.options.type); + }); + } + + public driver(): Promise { + return new Promise((resolve, reject) => { + if (!this.db.isInitialized) { + reject(DatabaseErrEnum.clientNotFound); + return; + } + resolve(this.db.driver); + }); + } + + async get_db_server_info(): Promise { + if (!this.db.isInitialized) { + await this.db.initialize(); + } + + const dbDriver = await this.driver(); + return `${dbDriver.options.type}-${dbDriver.version || 'unknown'}`; + } + + /*-----------------------Dynamic Introspection Methods-----------------------*/ + abstract get_all_table_names(): Promise>; + + async init_db_schema(): Promise { + this.db_server_info = await this.get_db_server_info(); + this.table_names = await this.get_all_table_names(); + } + + async get_table_columns(table_name: string): Promise> { + const queryRunner = this.db.createQueryRunner(); + try { + const table = await queryRunner.getTable(table_name); + if (!table) return Promise.reject(DatabaseErrEnum.fetchInfoError); + + return table.columns.map((col: ORMColumn) => { + return new TableColumn(col.name, col.type as ColumnType, col.comment ?? ''); + }); + } finally { + await queryRunner.release(); + } + } + // get protected name for sql query + protected getProtectedIdentifier(identifier: string): string { + switch (this.config.clientType) { + case DatabaseTypeEnum.mysql: + case DatabaseTypeEnum.sqlite: + return `\`${identifier}\``; + case DatabaseTypeEnum.postgresql: + return `"${identifier}"`; + default: + return identifier; + } + } + + async get_table_info(tableName: string, getExamples: boolean = false): Promise { + if (!this.db.isInitialized) { + await this.db.initialize(); + } + + const queryRunner = this.db.createQueryRunner(); + // try-finally to ensure the queryRunner is released + try { + const table = await queryRunner.getTable(tableName); + if (!table) return Promise.reject(DatabaseErrEnum.fetchInfoError); + + const tableComment = table.comment ?? ''; + + const columns = new Map(); + + for (const col of table.columns) { + let examples: string[] = []; + let valueIndex = false; + + if (getExamples) { + const sql = ` + SELECT DISTINCT ${this.getProtectedIdentifier(col.name)} + FROM ${this.getProtectedIdentifier(tableName)} + WHERE ${this.getProtectedIdentifier(col.name)} IS NOT NULL + LIMIT ${this.sample_value_num} + `; + + try { + const result = await queryRunner.query(sql); + + for (const row of result) { + const value = row[col.name]; + if (value !== null && value !== undefined) { + const strValue = truncateText(convertValueToString(value), this.max_string_length); + examples.push(strValue); + } + } + + if (isStringType(col.type as ColumnType) && examples.length > 0) valueIndex = true; + } catch (error) { + console.warn(`获取列 ${col.name} 的示例数据失败:`, error); + examples = []; + } + } + + const tableColumn = new TableColumn( + col.name, + col.type as ColumnType, + col.comment || '', + false, + valueIndex, + examples, + col.isNullable, + col.default ?? null, + col.isGenerated, + col.isPrimary, + table.foreignKeys.some((fk) => fk.columnNames.includes(col.name)), // isForeignKey + table.foreignKeys + ?.filter((fk) => fk.columnNames.includes(col.name)) + .map((fk) => fk.referencedColumnNames) + .flat() // relatedColumns + ); + columns.set(col.name, tableColumn); + } + + const primaryKeys = table.primaryColumns.map((col) => col.name); + const foreignKeys: TableForeignKey[] = table.foreignKeys.flatMap((fk) => { + return fk.columnNames.map((col, idx) =>{ + if (fk.referencedColumnNames[idx] && fk.referencedTableName) + return new TableForeignKey( + fk.name || '', + col, + fk.referencedSchema || fk.referencedDatabase || this.config.database, + fk.referencedTableName, + fk.referencedColumnNames[idx] + ) + } + ).filter(Boolean) as TableForeignKey[]; + }); + + return new DBTable(tableName, tableComment, false, columns, foreignKeys, primaryKeys); + } finally { + await queryRunner.release(); + } + } +} diff --git a/packages/service/core/dataset/database/model/dataModel.ts b/packages/service/core/dataset/database/model/dataModel.ts new file mode 100644 index 000000000000..694a7836f2bd --- /dev/null +++ b/packages/service/core/dataset/database/model/dataModel.ts @@ -0,0 +1,193 @@ +import type { ColumnType } from 'typeorm'; +import type { DatabaseCollectionsTable } from '@fastgpt/global/core/dataset/database/api'; +import type { ColumnSchemaType } from '@fastgpt/global/core/dataset/type'; + +export class RequestValidationDiagnosisError extends Error {} + +export class TableColumn { + columnName: string; + columnType: ColumnType; + description: string; + examples: Array; + forbid: boolean; + valueIndex: boolean; + + // Database attributes + isNullable?: boolean; + defaultValue?: string | null; + isAutoIncrement?: boolean; + isPrimaryKey?: boolean; + isForeignKey?: boolean; + relatedColumns?: string[]; + + constructor( + columnName: string, + columnType: ColumnType, + description: string = '', + forbid: boolean = true, + value_index: boolean = true, + examples: Array = [], + isNullable: boolean = true, + defaultValue?: string | null, + isAutoIncrement: boolean = false, + isPrimaryKey: boolean = false, + isForeignKey: boolean = false, + relatedColumns?: string[] + ) { + this.columnName = columnName; + this.columnType = columnType; + this.description = description; // 会触发 setter 校验 + this.examples = examples; + this.forbid = forbid; + this.valueIndex = value_index; + // Database constraints + this.isNullable = isNullable; + this.defaultValue = defaultValue; + this.isAutoIncrement = isAutoIncrement; + this.isPrimaryKey = isPrimaryKey; + this.isForeignKey = isForeignKey; + this.relatedColumns = relatedColumns; + } +} + +export class TableConstraint { + name: string; // constraint name + column: string; // constrained column + + constructor(name: string, column: string) { + this.name = name; + this.column = column; + } +} + +export class TableForeignKey extends TableConstraint { + referredSchema: string; + referredTable: string; + referredColumns: string; + constructor( + name: string, // constraint name + column: string, // constrained column + referredSchema: string, + referredTable: string, + referredColumns: string + ) { + super(name, column); + this.referredSchema = referredSchema; + this.referredTable = referredTable; + this.referredColumns = referredColumns; + } +} + +export class TableKeyInfo { + columns: Map; + foreignKeys: Array; + primaryKeys: Array; + + constructor( + columns: Map, + foreignKeys: Array, + primaryKeys: Array + ) { + this.columns = columns; + this.foreignKeys = foreignKeys; + this.primaryKeys = primaryKeys; + } +} + +export class DBTable extends TableKeyInfo { + tableName: string; + description: string; + forbid: boolean; + constraints: Array; + estimatedSize?: string; + + constructor( + tableName: string, + description: string, + forbid: boolean = true, + columns: Map, + foreignKeys: Array, + primaryKeys: Array, + constraints: Array = [] + ) { + super(columns, foreignKeys, primaryKeys); + this.tableName = tableName; + this.description = description; + this.forbid = forbid; + this.constraints = constraints; + } +} + +export class TableColumnTransformer { + static toPlainObject(tableColumn: TableColumn): any { + if (!tableColumn) return null; + return { + columnName: tableColumn.columnName, + columnType: String(tableColumn.columnType), + description: tableColumn.description, + examples: tableColumn.examples, + forbid: tableColumn.forbid, + valueIndex: tableColumn.valueIndex, + // Database attributes + isNullable: tableColumn.isNullable, + defaultValue: tableColumn.defaultValue, + isAutoIncrement: tableColumn.isAutoIncrement, + isPrimaryKey: tableColumn.isPrimaryKey, + isForeignKey: tableColumn.isForeignKey, + relatedColumns: tableColumn.relatedColumns + }; + } + + static fromPlainObject(col: ColumnSchemaType): TableColumn { + return new TableColumn( + col.columnName, + col.columnType as ColumnType, + col.description, + col.forbid, + col.valueIndex, + col.examples, + col.isNullable, + col.defaultValue, + col.isAutoIncrement, + col.isPrimaryKey, + col.isForeignKey, + col.relatedColumns + ); + } +} + +export class TableTransformer { + static toPlainObject(table: DBTable, extra: Record = {}): any { + const columnObj: Record = {}; + table.columns.forEach((value, key) => { + columnObj[key] = TableColumnTransformer.toPlainObject(value); + }); + + return { + tableName: table.tableName, + description: table.description, + columns: columnObj, + foreignKeys: table.foreignKeys, + primaryKeys: table.primaryKeys, + constraints: table.constraints, + ...extra + }; + } + + static fromPlainObject(table: DatabaseCollectionsTable): DBTable { + return new DBTable( + table.tableName, + table.description, + table.forbid, + new Map( + Object.entries(table.columns).map(([key, value]) => [ + key, + TableColumnTransformer.fromPlainObject(value) + ]) + ), + table.foreignKeys, + table.primaryKeys, + table.constraints + ); + } +} diff --git a/packages/service/core/dataset/database/model/mysql.ts b/packages/service/core/dataset/database/model/mysql.ts new file mode 100644 index 000000000000..d7af344dda8c --- /dev/null +++ b/packages/service/core/dataset/database/model/mysql.ts @@ -0,0 +1,72 @@ +import type { DatabaseConfig } from '@fastgpt/global/core/dataset/type'; +import { AsyncDB } from './asyncDB'; +import { DatabaseErrEnum } from '@fastgpt/global/common/error/code/database'; +import { addLog } from '../../../../common/system/log'; + +export class MysqlClient extends AsyncDB { + override async get_all_table_names(): Promise> { + if (!this.db.isInitialized) { + await this.db.initialize(); + } + const queryRunner = this.db.createQueryRunner(); + // try-finally to ensure the queryRunner is released + try { + // 获取当前数据库名 + const dbName = this.db.options.database; + + // 查询所有表名 + const tables: { TABLE_NAME: string }[] = await queryRunner.query( + `SELECT TABLE_NAME + FROM information_schema.tables + WHERE table_schema = ? + AND TABLE_TYPE = 'BASE TABLE'`, + [dbName] + ); + + return tables.map((t) => t.TABLE_NAME); + } finally { + await queryRunner.release(); + } + } + static fromConfig(config: DatabaseConfig): MysqlClient { + const db = AsyncDB.from_uri(config); + return new MysqlClient(db, config); + } + + override async checkConnection(): Promise { + try { + await super.checkConnection(); + return true; + } catch (err: any) { + addLog.warn('[checkConnection]:', err); + if (err?.code === 'ER_ACCESS_DENIED_ERROR') { + // username or password error + return Promise.reject(DatabaseErrEnum.authError); + } else if (err?.code === 'ER_BAD_DB_ERROR') { + // database not found + return Promise.reject(DatabaseErrEnum.databaseNameError); + } else if (err?.code === 'PROTOCOL_CONNECTION_LOST') { + // connection lost + return Promise.reject(DatabaseErrEnum.connectionLost); + } else if (err?.code === 'ECONNREFUSED') { + // Error: Connection Refused + return Promise.reject(DatabaseErrEnum.econnRefused); + } else if (err?.code === 'ETIMEDOUT') { + // timeout error + return Promise.reject(DatabaseErrEnum.connectionTimeout); + } else if (err?.code === 'ENOTFOUND') { + // url error + return Promise.reject(DatabaseErrEnum.connectionFailed); + } else if (err?.code === 'EHOSTUNREACH') { + // address error + return Promise.reject(DatabaseErrEnum.hostError); + } else if (err?.code === 'ERR_SOCKET_BAD_PORT') { + // port error + return Promise.reject(DatabaseErrEnum.databasePortError); + } else { + // other + return Promise.reject(DatabaseErrEnum.checkError); + } + } + } +} diff --git a/packages/service/core/dataset/database/model/utils.ts b/packages/service/core/dataset/database/model/utils.ts new file mode 100644 index 000000000000..132b2d530dcc --- /dev/null +++ b/packages/service/core/dataset/database/model/utils.ts @@ -0,0 +1,29 @@ +import type {ColumnType} from "typeorm"; + +export function isStringType(columnType: ColumnType): boolean { + const stringTypes = ['varchar', 'char', 'text', 'string', 'nvarchar', 'nchar', 'ntext']; + const typeStr = String(columnType).toLowerCase(); + return stringTypes.some(type => typeStr.includes(type)); +} + +export function convertValueToString(value: any): string { + if (value === null || value === undefined) { + return ''; + } + if (value instanceof Date) { + return value.toISOString(); + } + if (typeof value === 'object') { + try { + return JSON.stringify(value); + } catch { + return String(value); + } + } + return String(value); +} + + +export function truncateText(text: string, maxLength: number=1024): string { + return text.length <= maxLength ? text : text.substring(0, maxLength - 3) + '...'; +} diff --git a/packages/service/core/dataset/schema.ts b/packages/service/core/dataset/schema.ts index 3749601db9e9..0c039c7f3634 100644 --- a/packages/service/core/dataset/schema.ts +++ b/packages/service/core/dataset/schema.ts @@ -2,6 +2,7 @@ import { getMongoModel, Schema } from '../../common/mongo'; import { ChunkSettingModeEnum, ChunkTriggerConfigTypeEnum, + DatabaseTypeEnum, DataChunkSplitModeEnum, DatasetCollectionDataProcessModeEnum, DatasetTypeEnum, @@ -101,7 +102,9 @@ const DatasetSchema = new Schema({ }, agentModel: { type: String, - required: true, + required: function (this: any) { + return this.type !== DatasetTypeEnum.database; + }, default: 'gpt-4o-mini' }, vlmModel: String, @@ -121,6 +124,47 @@ const DatasetSchema = new Schema({ } } }, + databaseConfig: { + type: { + clientType: { + type: String, + required: true, + enum: Object.values(DatabaseTypeEnum) + }, + version: { + type: String, + default: '5.7.44' + }, + host: { + type: String, + required: true + }, + port: { + type: Number, + default: 3306 + }, + database: { + type: String, + required: true + }, + user: { + type: String, + required: true + }, + password: { + type: String, + required: true + }, + encrypt: { + type: Boolean, + default: false + }, + poolSize: { + type: Number, + default: 20 + } + } + }, chunkSettings: { type: ChunkSettings }, diff --git a/packages/service/core/dataset/search/controller.ts b/packages/service/core/dataset/search/controller.ts index 080e6fe2e838..52fbe7778f04 100644 --- a/packages/service/core/dataset/search/controller.ts +++ b/packages/service/core/dataset/search/controller.ts @@ -3,11 +3,15 @@ import { DatasetSearchModeMap, SearchScoreTypeEnum } from '@fastgpt/global/core/dataset/constants'; -import { recallFromVectorStore } from '../../../common/vectorDB/controller'; +import { + recallFromVectorStore, + databaseEmbeddingRecall +} from '../../../common/vectorDB/controller'; import { getVectorsByText } from '../../ai/embedding'; import { getEmbeddingModel, getDefaultRerankModel, getLLMModel } from '../../ai/model'; import { MongoDatasetData } from '../data/schema'; import type { + DatabaseConfig, DatasetCollectionSchemaType, DatasetDataSchemaType } from '@fastgpt/global/core/dataset/type'; @@ -32,6 +36,24 @@ import type { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { datasetSearchQueryExtension } from './utils'; import type { RerankModelItemType } from '@fastgpt/global/core/ai/model.d'; import { formatDatasetDataValue } from '../data/controller'; +import { + DBDatasetValueVectorTableName, + DBDatasetVectorTableName +} from '../../../common/vectorDB/constants'; +import { MongoDataset } from '../schema'; +import { addLog } from '../../../common/system/log'; +import { i18nT } from '../../../../web/i18n/utils'; +import { DatabaseErrEnum } from '@fastgpt/global/common/error/code/database'; +import type { + DativeForeignKey, + DativeTable, + DativeTableColumns, + SqlGenerationRequest, + SqlGenerationResponse +} from '@fastgpt/global/core/dataset/database/api'; +import type { DatabaseEmbeddingRecallItemType } from '../../../common/vectorDB/controller.d'; + +export const dativeUrl = process.env.DATIVE_BASE_URL; export type SearchDatasetDataProps = { histories: ChatItemType[]; @@ -65,6 +87,16 @@ export type SearchDatasetDataProps = { collectionFilterMatch?: string; }; +export type SearchDatabaseDataProps = { + histories?: ChatItemType[]; + teamId: string; + model: string; + datasetIds: string[]; + queries: string[]; + [NodeInputKeyEnum.datasetMaxTokens]: number; // max Token limit + [NodeInputKeyEnum.searchColumnslLimitRatio]?: number; // default 0.3 +}; + export type SearchDatasetDataResponse = { searchRes: SearchDataResponseItemType[]; embeddingTokens: number; @@ -84,6 +116,19 @@ export type SearchDatasetDataResponse = { deepSearchResult?: { model: string; inputTokens: number; outputTokens: number }; }; +export type SearchDatabaseDataResponse = { + schema: Record< + string, + { + collectionId: string; + datasetId: string; + score: number; + retrieval_columns: string[]; + } + >; + tokens: number; +}; + export const datasetDataReRank = async ({ rerankModel, data, @@ -971,3 +1016,387 @@ export type DeepRagSearchProps = SearchDatasetDataProps & { [NodeInputKeyEnum.datasetDeepSearchBg]?: string; }; export const deepRagSearch = (data: DeepRagSearchProps) => global.deepRagHandler(data); + +export const SearchDatabaseData = async ( + props: SearchDatabaseDataProps +): Promise => { + let { histories, teamId, model, datasetIds, queries, limit = 50, searchRatio = 0.3 } = props; + const desLimit = Math.floor(limit * (1 - searchRatio)); + try { + const forbidCollections = await MongoDatasetCollection.find( + { + teamId, + datasetId: { $in: datasetIds }, + forbid: true + }, + '_id' + ); + const vectorModel = getEmbeddingModel(model); + let totalTokens = 0; + const columnDescriptionRecallResList: DatabaseEmbeddingRecallItemType[] = []; + const columnValueRecallResultList: DatabaseEmbeddingRecallItemType[] = []; + + const forbidCollectionIdList = forbidCollections.map((item: any) => String(item._id)); + await Promise.all( + queries.map(async (query: string) => { + const { tokens, vectors } = await getVectorsByText({ + model: vectorModel, + input: query, + type: 'query' + }); + + totalTokens += tokens; + const q_vector = vectors[0]; + + const columnDescriptionResults = await columnDescriptionRecall({ + teamId, + datasetIds, + vector: q_vector, + limit: desLimit, + forbidCollectionIdList + }); + + const columnValueResults = await columnValueRecall({ + teamId, + datasetIds, + vector: q_vector, + limit: limit - desLimit, + forbidCollectionIdList + }); + + columnDescriptionRecallResList.push(...columnDescriptionResults); + columnValueRecallResultList.push(...columnValueResults); + }) + ); + + const schema = await mergeAndGetSchema({ + columnDescriptionRecallResList, + columnValueRecallResultList, + teamId + }); + + addLog.debug(`Database embed recall completed. Found ${Object.keys(schema).length} tables.`); + + return { + schema, + tokens: totalTokens + }; + } catch (error) { + return Promise.reject(i18nT('chat:embedding_model_error')); + } +}; + +const columnDescriptionRecall = async ({ + teamId, + datasetIds, + vector, + limit, + forbidCollectionIdList +}: { + teamId: string; + datasetIds: string[]; + vector: number[]; + limit: number; + forbidCollectionIdList: string[]; +}): Promise => { + try { + // Use universal database embedding recall interface + const { results } = await databaseEmbeddingRecall({ + teamId, + datasetIds, + vector, + limit, + tableName: DBDatasetVectorTableName, + forbidCollectionIdList + }); + addLog.debug( + 'Column description recall results:', + results.map((r) => r.columnDesIndex) + ); + return results; + } catch (error) { + addLog.error('Column description recall error', error); + return []; + } +}; + +const columnValueRecall = async ({ + teamId, + datasetIds, + vector, + limit, + forbidCollectionIdList +}: { + teamId: string; + datasetIds: string[]; + vector: number[]; + limit: number; + forbidCollectionIdList: string[]; +}): Promise => { + try { + // Use universal database embedding recall interface + const { results } = await databaseEmbeddingRecall({ + teamId, + datasetIds, + vector, + limit, + tableName: DBDatasetValueVectorTableName, + forbidCollectionIdList + }); + + addLog.debug( + 'Column value recall results:', + results.map((r) => r.columnValIndex) + ); + return results; + } catch (error) { + addLog.error('Column value recall error', error); + return []; + } +}; + +// merge results and get schema +const mergeAndGetSchema = async ({ + columnDescriptionRecallResList, + columnValueRecallResultList, + teamId +}: { + columnDescriptionRecallResList: DatabaseEmbeddingRecallItemType[]; + columnValueRecallResultList: DatabaseEmbeddingRecallItemType[]; + teamId: string; +}) => { + const schema: Record< + string, + { collectionId: string; datasetId: string; score: number; retrieval_columns: string[] } + > = {}; + // Group results by collectionId directly (avoid creating intermediate array) + const resultsByCollection = new Map< + string, + Array + >(); + + // Process column description results + for (const result of columnDescriptionRecallResList) { + if (result.collectionId) { + if (!resultsByCollection.has(result.collectionId)) + resultsByCollection.set(result.collectionId, []); + resultsByCollection.get(result.collectionId)!.push({ ...result, type: 'description' }); + } + } + + // Process column value results + for (const result of columnValueRecallResultList) { + if (result.collectionId) { + if (!resultsByCollection.has(result.collectionId)) + resultsByCollection.set(result.collectionId, []); + resultsByCollection.get(result.collectionId)!.push({ ...result, type: 'value' }); + } + } + + // Batch fetch all collections with table schema + const collections = await MongoDatasetCollection.find({ + _id: { $in: Array.from(resultsByCollection.keys()) }, + teamId + }) + .select('_id datasetId name tableSchema') + .lean(); + const collectionMap = new Map(collections.map((coll) => [String(coll._id), coll])); + // Process each collection once with all its results + for (const [collectionId, results] of resultsByCollection) { + const collection = collectionMap.get(collectionId); + if (!collection || !collection.name || !results) continue; + + const tableName = collection.name; + const primaryKeys = collection.tableSchema?.primaryKeys || []; + const foreignKyes = collection.tableSchema?.foreignKeys.map((fk) => fk.column) || []; + + // Collect all unique columns from all results for this collection + const allRetrievedColumns = new Set([...primaryKeys, ...foreignKyes]); + + // Find the best score among all results for this collection (optimized) + let bestResult = results[0]; + for (let i = 1; i < results.length; i++) { + if (results[i].score > bestResult.score) { + bestResult = results[i]; + } + } + + schema[tableName] = { + collectionId: String(collectionId), + datasetId: String(collection.datasetId), + score: bestResult.score, + retrieval_columns: [] + }; + + // Add retrieved columns from all results in one pass + for (const result of results) { + const retrievedColumn = + result.type === 'description' ? result.columnDesIndex : result.columnValIndex; + if (retrievedColumn) { + allRetrievedColumns.add(retrievedColumn.split('')[1]); + } + } + + // Update retrieval_columns with all unique columns + schema[tableName].retrieval_columns = Array.from(allRetrievedColumns); + } + return schema; +}; + +// Generate SQL and execute query with Dative Plugin +export const generateAndExecuteSQL = async ({ + datasetId, + query, + schema, + teamId, + limit = 50, + generate_sql_llm, + evaluate_sql_llm +}: { + datasetId: string; + query: string; + schema: Record< + string, + { collectionId: string; datasetId: string; score: number; retrieval_columns: string[] } + >; + teamId: string; + limit?: number; + generate_sql_llm: { model: string; api_key?: string; base_url?: string }; + evaluate_sql_llm: { model: string; api_key?: string; base_url?: string }; + externalProvider?: { + openaiAccount?: { + key: string; + baseUrl: string; + }; + }; +}): Promise => { + // Get dataset and database config + const dataset = await MongoDataset.findById(datasetId).lean(); + if (!dataset?.databaseConfig) { + addLog.warn('No database config found for dataset', { datasetId }); + return Promise.reject(DatabaseErrEnum.dbConfigNotFound); + } + + const dbConfig: DatabaseConfig = dataset.databaseConfig; + + // Get table schema from collections + const tableNames = Object.keys(schema); + if (tableNames.length === 0) { + addLog.warn('No tables found in schema'); + return null; + } + + // Get all table schemas from MongoDB collections + const collections = await MongoDatasetCollection.find({ + datasetId, + name: { $in: tableNames }, + teamId + }).lean(); + + // Collections Changes during Sql Generation + if (!collections || collections.length === 0) { + addLog.warn('No collections found for tables', { tableNames }); + return Promise.reject(`${tableNames} not found or has been deleted`); + } + + // Build table schemas for Dative + const retrievedMetadata = collections.map((collection) => { + const columns: Record = {}; + if (collection.tableSchema?.columns) { + Object.values(collection.tableSchema.columns).forEach((col) => { + if (schema[collection.name]?.retrieval_columns.includes(col.columnName)) { + columns[col.columnName] = { + name: col.columnName, + type: col.columnType, + comment: col.description, + auto_increment: col.isAutoIncrement || false, + nullable: col.isNullable || false, + default: col.defaultValue || null, + examples: col.examples || [], + enabled: !col.forbid, + value_index: col.valueIndex || false + }; + } + }); + } + + const foreign_keys: DativeForeignKey[] = + collection.tableSchema?.foreignKeys?.map((fk) => ({ + name: fk.name, + column: fk.column, + referenced_schema: fk.referredSchema, + referenced_table: fk.referredTable, + referenced_column: fk.referredColumns + })) || []; + const table: DativeTable = { + name: collection.name, + ns_name: '', + comment: collection.tableSchema?.description || '', + columns, + primary_keys: collection.tableSchema?.primaryKeys || [], + foreign_keys: foreign_keys, + enable: !collection.forbid, + score: schema[collection.name]?.score || 0 + }; + return table; + }); + addLog.debug( + `[generateAndExecuteSQL] retrieved_metadata: ${JSON.stringify(retrievedMetadata).length}` + ); + if (retrievedMetadata.length === 0) { + addLog.warn('No valid table schemas found'); + return null; + } + + // Sort by score (highest first) for better SQL generation + retrievedMetadata.sort((a, b) => b.score - a.score); + + // Update request payload to include all table schemas + const requestPayload: SqlGenerationRequest = { + source_config: { + type: dbConfig.clientType, + host: dbConfig.host, + port: dbConfig.port || 3306, + username: dbConfig.user, + password: dbConfig.password, + db_name: dbConfig.database + }, + generate_sql_llm, + evaluate_sql_llm, + query, + result_num_limit: limit, + retrieved_metadata: { + name: dbConfig.database, // DatabaseName + comments: '', + tables: retrievedMetadata + } + }; + let response: Response; + + try { + response = await fetch(`${dativeUrl}/api/v1/data_source/query_by_nl`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(requestPayload) + }); + } catch (error: any) { + addLog.error('Error connecting to Dative service', error); + return Promise.reject(DatabaseErrEnum.dativeServiceError); + } + if (!response.ok) { + const errorText = await response.text(); + addLog.error('[generateAndExecuteSQL]:', { + status: response.status, + statusText: response.statusText, + error: errorText + }); + const detail = JSON.parse(errorText).detail?.error; + + return Promise.reject(`dative error: ${detail ?? 'Request failed'}`); + } + + const result: SqlGenerationResponse = await response.json(); + return result; +}; diff --git a/packages/service/core/dataset/search/utils.ts b/packages/service/core/dataset/search/utils.ts index 410409f3deb8..4d03fbdc8bd4 100644 --- a/packages/service/core/dataset/search/utils.ts +++ b/packages/service/core/dataset/search/utils.ts @@ -3,6 +3,7 @@ import { queryExtension } from '../../ai/functions/queryExtension'; import { type ChatItemType } from '@fastgpt/global/core/chat/type'; import { hashStr } from '@fastgpt/global/common/string/tools'; import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt'; +import { getLLMModel } from '../../ai/model'; export const datasetSearchQueryExtension = async ({ query, @@ -86,3 +87,26 @@ Human: ${query} aiExtensionResult }; }; + +// Calculate dynamic limit based on LLM model's max context and safety factor +export const calculateDynamicLimit = ({ + generateSqlModel, + safetyFactor = 0.6, + estimatedTokensPerItem = 512 +}: { + generateSqlModel?: string; + safetyFactor?: number; + estimatedTokensPerItem?: number; +}): number => { + // Get the LLM model configuration + const llmModel = getLLMModel(generateSqlModel); + + // Calculate safe limit based on model's maxContext + const modelMaxToken = llmModel.maxContext; + const safeLimit = Math.floor((modelMaxToken * safetyFactor) / estimatedTokensPerItem); + + // Ensure minimum limit of 5 and maximum of 50 for reasonable performance + const finalLimit = Math.max(5, Math.min(safeLimit, 50)); + + return finalLimit; +}; diff --git a/packages/service/core/evaluation/common.ts b/packages/service/core/evaluation/common.ts new file mode 100644 index 000000000000..c1993c8ab886 --- /dev/null +++ b/packages/service/core/evaluation/common.ts @@ -0,0 +1,482 @@ +import { Types } from 'mongoose'; +import type { AuthModeType } from '../../support/permission/type'; +import { authEvaluation } from '../../support/permission/evaluation/auth'; +import { authEvalDataset, authEvalMetric } from '../../support/permission/evaluation/auth'; +import { authUserPer } from '../../support/permission/user/auth'; +import { TeamEvaluationCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant'; +import { + ReadPermissionVal, + WritePermissionVal, + PerResourceTypeEnum +} from '@fastgpt/global/support/permission/constant'; +import type { + EvalTarget, + EvaluationWithPerType, + EvaluationItemSchemaType +} from '@fastgpt/global/core/evaluation/type'; +import { authApp } from '../../support/permission/app/auth'; +import { authDataset } from '../../support/permission/dataset/auth'; +import { MongoEvalItem } from './task/schema'; +import { MongoEvalDatasetData } from './dataset/evalDatasetDataSchema'; +import { MongoResourcePermission } from '../../support/permission/schema'; +import { getGroupsByTmbId } from '../../support/permission/memberGroup/controllers'; +import { getOrgIdSetWithParentByTmbId } from '../../support/permission/org/controllers'; +import { EvaluationErrEnum } from '@fastgpt/global/common/error/code/evaluation'; + +export const buildListQuery = ( + teamId: string, + searchKey?: string, + searchFields: string[] = ['name', 'description'] +): any => { + const filter: any = { teamId: new Types.ObjectId(teamId) }; + + if (searchKey) { + filter.$or = searchFields.map((field) => ({ + [field]: { $regex: searchKey, $options: 'i' } + })); + } + + return filter; +}; + +export const buildPaginationOptions = (page: number = 1, pageSize: number = 20) => ({ + skip: (page - 1) * pageSize, + limit: pageSize, + sort: { createTime: -1 as const } +}); + +export const getEvaluationPermissionAggregation = async ( + auth: AuthModeType +): Promise<{ + teamId: string; + tmbId: string; + isOwner: boolean; + roleList: any[]; + myGroupMap: Map; + myOrgSet: Set; +}> => { + // Auth user permission - supports API Key and Token authentication + const { + tmbId, + teamId, + permission: teamPer + } = await authUserPer({ + ...auth, + per: ReadPermissionVal + }); + + // Get team all evaluation permissions + const [roleList, myGroupMap, myOrgSet] = await Promise.all([ + MongoResourcePermission.find({ + resourceType: PerResourceTypeEnum.evaluation, + teamId, + resourceId: { + $exists: true + } + }).lean(), + getGroupsByTmbId({ + tmbId, + teamId + }).then((item) => { + const map = new Map(); + item.forEach((item) => { + map.set(String(item._id), 1); + }); + return map; + }), + getOrgIdSetWithParentByTmbId({ + teamId, + tmbId + }) + ]); + + return { + teamId, + tmbId, + isOwner: teamPer.isOwner, + roleList, + myGroupMap, + myOrgSet + }; +}; + +// ================ Evaluation module authorization functions ================ + +export const authEvaluationTaskCreate = async ( + target: EvalTarget, + auth: AuthModeType +): Promise<{ + teamId: string; + tmbId: string; +}> => { + const { teamId, tmbId } = await authUserPer({ + ...auth, + per: TeamEvaluationCreatePermissionVal + }); + + if (target.type == 'workflow') { + if (!target.config?.appId) { + return Promise.reject(EvaluationErrEnum.evalTargetAppIdMissing); + } + await authApp({ + ...auth, + appId: target.config.appId, + per: ReadPermissionVal + }); + } + + return { + teamId, + tmbId + }; +}; + +export const authEvaluationTaskRead = async ( + evaluationId: string, + auth: AuthModeType +): Promise<{ + evaluation: EvaluationWithPerType; + teamId: string; + tmbId: string; +}> => { + const { evaluation, teamId, tmbId } = await authEvaluation({ + ...auth, + evaluationId, + per: ReadPermissionVal + }); + + return { evaluation, teamId, tmbId }; +}; + +export const authEvaluationTaskWrite = async ( + evaluationId: string, + auth: AuthModeType +): Promise<{ + evaluation: EvaluationWithPerType; + teamId: string; + tmbId: string; +}> => { + const { evaluation, teamId, tmbId } = await authEvaluation({ + ...auth, + evaluationId, + per: WritePermissionVal + }); + + return { evaluation, teamId, tmbId }; +}; + +export const authEvaluationTaskExecution = async ( + evaluationId: string, + auth: AuthModeType +): Promise<{ + evaluation: EvaluationWithPerType; + teamId: string; + tmbId: string; +}> => { + const { evaluation, teamId, tmbId } = await authEvaluation({ + ...auth, + evaluationId, + per: WritePermissionVal + }); + + if (evaluation.target.type == 'workflow') { + if (!evaluation.target.config?.appId) { + return Promise.reject(EvaluationErrEnum.evalTargetAppIdMissing); + } + await authApp({ + ...auth, + appId: evaluation.target.config.appId, + per: ReadPermissionVal + }); + } + + return { + evaluation, + teamId, + tmbId + }; +}; + +// ================ Evaluation item authorization functions ================ + +export const authEvaluationItemRead = async ( + evalItemId: string, + auth: AuthModeType +): Promise<{ + evaluation: EvaluationWithPerType; + evaluationItem: EvaluationItemSchemaType; + teamId: string; + tmbId: string; +}> => { + const evaluationItem = await MongoEvalItem.findById(evalItemId).lean(); + if (!evaluationItem) { + throw new Error(EvaluationErrEnum.evalItemNotFound); + } + + const { teamId, tmbId, evaluation } = await authEvaluationTaskRead(evaluationItem.evalId, auth); + + return { + evaluation, + evaluationItem, + teamId, + tmbId + }; +}; + +export const authEvaluationItemWrite = async ( + evalItemId: string, + auth: AuthModeType +): Promise<{ + evaluation: EvaluationWithPerType; + evaluationItem: EvaluationItemSchemaType; + teamId: string; + tmbId: string; +}> => { + const evaluationItem = await MongoEvalItem.findById(evalItemId).lean(); + if (!evaluationItem) { + throw new Error(EvaluationErrEnum.evalItemNotFound); + } + + const { teamId, tmbId, evaluation } = await authEvaluationTaskWrite(evaluationItem.evalId, auth); + + return { + evaluation, + evaluationItem, + teamId, + tmbId + }; +}; + +export const authEvaluationItemRetry = async ( + evalItemId: string, + auth: AuthModeType +): Promise<{ + evaluation: EvaluationWithPerType; + evaluationItem: EvaluationItemSchemaType; + teamId: string; + tmbId: string; +}> => { + return await authEvaluationItemWrite(evalItemId, auth); +}; + +// ================ Evaluation dataset authorization functions ================ + +export const authEvaluationDatasetCreate = async ( + auth: AuthModeType +): Promise<{ + teamId: string; + tmbId: string; +}> => { + const { teamId, tmbId } = await authUserPer({ + ...auth, + per: TeamEvaluationCreatePermissionVal + }); + + return { teamId, tmbId }; +}; +export const authEvaluationDatasetRead = async ( + datasetId: string, + auth: AuthModeType +): Promise<{ + teamId: string; + tmbId: string; + datasetId: string; +}> => { + const { teamId, tmbId } = await authEvalDataset({ + ...auth, + datasetId, + per: ReadPermissionVal + }); + + return { teamId, tmbId, datasetId }; +}; + +export const authEvaluationDatasetWrite = async ( + datasetId: string, + auth: AuthModeType +): Promise<{ + teamId: string; + tmbId: string; + datasetId: string; +}> => { + const { teamId, tmbId } = await authEvalDataset({ + ...auth, + datasetId, + per: WritePermissionVal + }); + + return { teamId, tmbId, datasetId }; +}; + +export const authEvaluationDatasetGenFromKnowledgeBase = async ( + collectionId: string, + kbDatasetIds: string[], + auth: AuthModeType +): Promise<{ + teamId: string; + tmbId: string; +}> => { + const { teamId, tmbId } = await authEvaluationDatasetRead(collectionId, auth); + + await Promise.all( + kbDatasetIds.map((datasetId) => + authDataset({ + ...auth, + datasetId, + per: ReadPermissionVal + }) + ) + ); + + return { + teamId, + tmbId + }; +}; + +// ================ Evaluation metric authorization functions ================ + +export const authEvaluationMetricCreate = async ( + auth: AuthModeType +): Promise<{ + teamId: string; + tmbId: string; +}> => { + const { teamId, tmbId } = await authUserPer({ + ...auth, + per: TeamEvaluationCreatePermissionVal + }); + + return { teamId, tmbId }; +}; + +export const authEvaluationMetricRead = async ( + metricId: string, + auth: AuthModeType +): Promise<{ + teamId: string; + tmbId: string; + metricId: string; +}> => { + const { teamId, tmbId } = await authEvalMetric({ + ...auth, + metricId, + per: ReadPermissionVal + }); + + return { teamId, tmbId, metricId }; +}; +export const authEvaluationMetricWrite = async ( + metricId: string, + auth: AuthModeType +): Promise<{ + teamId: string; + tmbId: string; + metricId: string; +}> => { + const { teamId, tmbId } = await authEvalMetric({ + ...auth, + metricId, + per: WritePermissionVal + }); + + return { teamId, tmbId, metricId }; +}; + +// ================ Evaluation dataset data authorization functions ================ +export const authEvaluationDatasetDataRead = async ( + collectionId: string, + auth: AuthModeType +): Promise<{ + teamId: string; + tmbId: string; + collectionId: string; +}> => { + const { teamId, tmbId } = await authEvaluationDatasetRead(collectionId, auth); + + return { teamId, tmbId, collectionId }; +}; + +export const authEvaluationDatasetDataWrite = async ( + collectionId: string, + auth: AuthModeType +): Promise<{ + teamId: string; + tmbId: string; + collectionId: string; +}> => { + const { teamId, tmbId } = await authEvaluationDatasetWrite(collectionId, auth); + + return { teamId, tmbId, collectionId }; +}; + +export const authEvaluationDatasetDataCreate = async ( + collectionId: string, + auth: AuthModeType +): Promise<{ + teamId: string; + tmbId: string; + collectionId: string; +}> => { + return await authEvaluationDatasetDataWrite(collectionId, auth); +}; + +export const authEvaluationDatasetDataDelete = async ( + collectionId: string, + auth: AuthModeType +): Promise<{ + teamId: string; + tmbId: string; + collectionId: string; +}> => { + return await authEvaluationDatasetDataWrite(collectionId, auth); +}; + +export const authEvaluationDatasetDataUpdate = async ( + collectionId: string, + auth: AuthModeType +): Promise<{ + teamId: string; + tmbId: string; + collectionId: string; +}> => { + return await authEvaluationDatasetDataWrite(collectionId, auth); +}; + +export const authEvaluationDatasetDataUpdateById = async ( + dataId: string, + auth: AuthModeType +): Promise<{ + teamId: string; + tmbId: string; + collectionId: string; +}> => { + const dataItem = await MongoEvalDatasetData.findById(dataId) + .select('evalDatasetCollectionId') + .lean(); + if (!dataItem) { + throw new Error(EvaluationErrEnum.datasetDataNotFound); + } + + const collectionId = String(dataItem.evalDatasetCollectionId); + return await authEvaluationDatasetDataUpdate(collectionId, auth); +}; + +export const authEvaluationDatasetDataReadById = async ( + dataId: string, + auth: AuthModeType +): Promise<{ + teamId: string; + tmbId: string; + collectionId: string; +}> => { + const dataItem = await MongoEvalDatasetData.findById(dataId) + .select('evalDatasetCollectionId') + .lean(); + if (!dataItem) { + throw new Error(EvaluationErrEnum.datasetDataNotFound); + } + + const collectionId = String(dataItem.evalDatasetCollectionId); + return await authEvaluationDatasetDataRead(collectionId, auth); +}; diff --git a/packages/service/core/evaluation/dataset/csvUtils.ts b/packages/service/core/evaluation/dataset/csvUtils.ts new file mode 100644 index 000000000000..0bd75179f806 --- /dev/null +++ b/packages/service/core/evaluation/dataset/csvUtils.ts @@ -0,0 +1,116 @@ +import Papa from 'papaparse'; + +// Interface for CSV row data structure +export interface CSVRow { + user_input: string; + expected_output: string; + actual_output?: string; + context?: string; + retrieval_context?: string; + metadata?: string; +} + +// Required CSV columns that must be present +export const REQUIRED_CSV_COLUMNS = ['user_input', 'expected_output'] as const; + +// Optional CSV columns that can be present +export const OPTIONAL_CSV_COLUMNS = [ + 'actual_output', + 'context', + 'retrieval_context', + 'metadata' +] as const; + +// All valid CSV columns +export const CSV_COLUMNS = [...REQUIRED_CSV_COLUMNS, ...OPTIONAL_CSV_COLUMNS] as const; + +// Enum to CSV mapping for header normalization +export const ENUM_TO_CSV_MAPPING = { + userInput: 'user_input', + expectedOutput: 'expected_output', + actualOutput: 'actual_output', + context: 'context', + retrievalContext: 'retrieval_context' +} as const; + +/** + * Normalize header names by mapping enum values to CSV column names + * @param header - The header string to normalize + * @returns The normalized header name + */ +function normalizeHeaderName(header: string): string { + // For most CSV files, headers should be used as-is + // Only apply mapping if the header matches an enum value exactly + const mappedValue = ENUM_TO_CSV_MAPPING[header as keyof typeof ENUM_TO_CSV_MAPPING]; + return mappedValue || header; +} + +/** + * Parse CSV content using Papa Parse with optimized performance settings + * @param csvContent - The raw CSV content string + * @returns Array of parsed CSV rows + * @throws Error if CSV parsing fails or required columns are missing + */ +export function parseCSVContent(csvContent: string): CSVRow[] { + if (!csvContent.trim()) { + return []; + } + + // Parse CSV with Papa Parse for optimal performance + const parseResult = Papa.parse(csvContent, { + header: true, + skipEmptyLines: true, + fastMode: false, // Disable fastMode to handle complex quoted fields + transformHeader: (header: string) => { + // Remove quotes and normalize header names + const cleanHeader = header.replace(/^"|"$/g, '').trim(); + return normalizeHeaderName(cleanHeader); + } + }); + + if (parseResult.errors.length > 0) { + const error = parseResult.errors[0]; + throw new Error(`CSV parsing error at row ${error.row + 1}: ${error.message}`); + } + + const data = parseResult.data as Record[]; + + if (data.length === 0) { + return []; + } + + // Get normalized headers from the first data row + const headers = Object.keys(data[0]); + + // Validate CSV structure + const missingColumns = REQUIRED_CSV_COLUMNS.filter((col) => !headers.includes(col)); + if (missingColumns.length > 0) { + throw new Error(`CSV file is missing required columns: ${missingColumns.join(', ')}`); + } + + // Convert to CSVRow format + const rows: CSVRow[] = data.map((rowData) => { + const row: CSVRow = { + user_input: (rowData.user_input || '').trim(), + expected_output: (rowData.expected_output || '').trim() + }; + + // Add optional fields if they exist + if ('actual_output' in rowData) { + row.actual_output = (rowData.actual_output || '').trim(); + } + if ('context' in rowData) { + row.context = (rowData.context || '').trim(); + } + if ('retrieval_context' in rowData) { + row.retrieval_context = (rowData.retrieval_context || '').trim(); + } + if ('metadata' in rowData) { + row.metadata = (rowData.metadata || '{}').trim(); + } + + return row; + }); + + return rows; +} diff --git a/packages/service/core/evaluation/dataset/dataQualityMq.ts b/packages/service/core/evaluation/dataset/dataQualityMq.ts new file mode 100644 index 000000000000..1eff918f10d9 --- /dev/null +++ b/packages/service/core/evaluation/dataset/dataQualityMq.ts @@ -0,0 +1,191 @@ +import { getQueue, getWorker, QueueNames } from '../../../common/bullmq'; +import { type Processor } from 'bullmq'; +import { addLog } from '../../../common/system/log'; +import { MongoEvalDatasetData } from './evalDatasetDataSchema'; +import { EvalDatasetDataQualityStatusEnum } from '@fastgpt/global/core/evaluation/dataset/constants'; +import { + createJobCleaner, + type JobCleanupResult, + type JobCleanupOptions, + checkBullMQHealth +} from '../utils/mq'; + +export type EvalDatasetDataQualityData = { + dataId: string; + evaluationModel: string; +}; + +export const evalDatasetDataQualityQueue = getQueue( + QueueNames.evalDatasetDataQuality +); + +const concurrency = global.systemEnv?.evalConfig?.dataQualityConcurrency || 2; + +export const getEvalDatasetDataQualityWorker = ( + processor: Processor +) => { + const worker = getWorker( + QueueNames.evalDatasetDataQuality, + processor, + { + maxStalledCount: global.systemEnv?.evalConfig?.maxStalledCount || 3, + removeOnFail: {}, + concurrency: concurrency + } + ); + + // When a job stalls (moves from active to waiting state) + worker.on('stalled', async (jobId: string) => { + try { + const job = await evalDatasetDataQualityQueue.getJob(jobId); + if (!job) return; + + addLog.warn('Data quality job stalled', { + jobId: job.id, + dataId: job.data.dataId + }); + + await MongoEvalDatasetData.findByIdAndUpdate(job.data.dataId, { + $set: { + 'qualityMetadata.status': EvalDatasetDataQualityStatusEnum.queuing, + 'qualityMetadata.queueTime': new Date() + } + }); + + addLog.info('Updated stalled job status to queuing', { + jobId: job.id, + dataId: job.data.dataId + }); + } catch (error) { + addLog.error('Failed to handle stalled job', { + jobId, + error + }); + } + }); + + worker.on('failed', async (job, err) => { + try { + if (!job) { + addLog.error('Data quality job failed but job is undefined', { error: err }); + return; + } + + addLog.error('Data quality job failed', { + jobId: job.id, + dataId: job.data.dataId, + error: err + }); + + // Check if failure is due to permanent stall limit exceeded + const isStallLimitExceeded = + err && + (err.message?.includes('stalled more than allowable limit') || + err.message?.includes('job stalled more than')); + + if (isStallLimitExceeded) { + await MongoEvalDatasetData.findByIdAndUpdate(job.data.dataId, { + $set: { + 'qualityMetadata.status': EvalDatasetDataQualityStatusEnum.error, + 'qualityMetadata.error': 'Job stalled more than allowable limit and failed permanently', + 'qualityMetadata.finishTime': new Date() + } + }); + + addLog.error('Updated permanently failed job status to error', { + jobId: job.id, + dataId: job.data.dataId, + reason: 'stall limit exceeded' + }); + } + // For other failure types, let the processor handle the status update + } catch (updateError) { + addLog.error('Failed to handle failed job', { + jobId: job?.id, + dataId: job?.data?.dataId, + error: updateError + }); + } + }); + + return worker; +}; + +export const addEvalDatasetDataQualityJob = (data: EvalDatasetDataQualityData) => { + const dataId = String(data.dataId); + + return evalDatasetDataQualityQueue.add(dataId, data, { deduplication: { id: dataId } }); +}; + +export const addEvalDatasetDataQualityBulk = (dataArray: EvalDatasetDataQualityData[]) => { + const jobs = dataArray.map((data) => ({ + name: String(data.dataId), + data, + opts: { deduplication: { id: String(data.dataId) } } + })); + + return evalDatasetDataQualityQueue.addBulk(jobs); +}; + +export const checkEvalDatasetDataQualityJobActive = async (dataId: string): Promise => { + try { + const jobId = await evalDatasetDataQualityQueue.getDeduplicationJobId(String(dataId)); + if (!jobId) return false; + + const job = await evalDatasetDataQualityQueue.getJob(jobId); + if (!job) return false; + + const jobState = await job.getState(); + return ['waiting', 'delayed', 'prioritized', 'active'].includes(jobState); + } catch (error) { + addLog.error('Failed to check eval dataset data quality job status', { dataId, error }); + return false; + } +}; + +export const checkEvalDatasetDataQualityJobInactive = async (dataId: string): Promise => { + try { + const jobId = await evalDatasetDataQualityQueue.getDeduplicationJobId(String(dataId)); + if (!jobId) return false; + + const job = await evalDatasetDataQualityQueue.getJob(jobId); + if (!job) return false; + + const jobState = await job.getState(); + return ['completed', 'failed'].includes(jobState); + } catch (error) { + addLog.error('Failed to check eval dataset data quality job inactive status', { + dataId, + error + }); + return false; + } +}; + +export const removeEvalDatasetDataQualityJobsRobust = async ( + dataIds: string[], + options?: JobCleanupOptions +): Promise => { + const cleaner = createJobCleaner(options); + + const filterFn = (job: any) => { + return dataIds.includes(String(job.data?.dataId)); + }; + + const result = await cleaner.cleanAllJobsByFilter( + evalDatasetDataQualityQueue, + filterFn, + QueueNames.evalDatasetDataQuality + ); + + addLog.info('Evaluation DatasetData quality jobs cleanup completed', { + dataIds: dataIds.length, + result + }); + + return result; +}; + +export const checkEvalDatasetDataQualityQueueHealth = (): Promise => { + return checkBullMQHealth(evalDatasetDataQualityQueue, 'quality'); +}; diff --git a/packages/service/core/evaluation/dataset/dataQualityProcessor.ts b/packages/service/core/evaluation/dataset/dataQualityProcessor.ts new file mode 100644 index 000000000000..1004aefb9162 --- /dev/null +++ b/packages/service/core/evaluation/dataset/dataQualityProcessor.ts @@ -0,0 +1,149 @@ +import type { Job } from 'bullmq'; +import { addLog } from '../../../common/system/log'; +import { MongoEvalDatasetData } from './evalDatasetDataSchema'; +import { getEvalDatasetDataQualityWorker, type EvalDatasetDataQualityData } from './dataQualityMq'; +import { + EvalDatasetDataKeyEnum, + EvalDatasetDataQualityStatusEnum, + EvalDatasetDataQualityResultEnum +} from '@fastgpt/global/core/evaluation/dataset/constants'; +import { EvalMetricTypeEnum } from '@fastgpt/global/core/evaluation/metric/constants'; +import type { EvalMetricSchemaType } from '@fastgpt/global/core/evaluation/metric/type'; +import { createEvaluatorInstance } from '../evaluator'; +import { checkTeamAIPoints } from '../../../support/permission/teamLimit'; +import { createEvalDatasetDataQualityUsage } from '../../../support/wallet/usage/controller'; + +// Queue processor function +export const processEvalDatasetDataQuality = async (job: Job) => { + const { dataId, evaluationModel } = job.data; + + addLog.info('Processing eval dataset data quality job', { dataId, evaluationModel }); + + if (!global.llmModelMap.has(evaluationModel)) { + const errorMsg = `Invalid evaluation model: ${evaluationModel}`; + addLog.error('Eval dataset data quality job failed - invalid model', { + dataId, + evaluationModel + }); + + await MongoEvalDatasetData.findByIdAndUpdate(dataId, { + $set: { + 'qualityMetadata.status': EvalDatasetDataQualityStatusEnum.error, + 'qualityMetadata.error': errorMsg, + 'qualityMetadata.finishTime': new Date() + } + }); + + throw new Error(errorMsg); + } + + try { + await MongoEvalDatasetData.findByIdAndUpdate(dataId, { + $set: { + 'qualityMetadata.status': EvalDatasetDataQualityStatusEnum.evaluating, + 'qualityMetadata.startTime': new Date() + } + }); + + const datasetData = await MongoEvalDatasetData.findById(dataId); + if (!datasetData) { + throw new Error(`Dataset data not found: ${dataId}`); + } + + // Check AI points limit + await checkTeamAIPoints(datasetData.teamId); + + // Create evaluator instance and run evaluation + const metricSchema: EvalMetricSchemaType = { + _id: '', + teamId: '', + tmbId: '', + name: 'q_a_quality', + type: EvalMetricTypeEnum.Builtin, + userInputRequired: true, + actualOutputRequired: false, + expectedOutputRequired: true, + contextRequired: false, + retrievalContextRequired: false, + embeddingRequired: false, + llmRequired: true, + createTime: new Date(), + updateTime: new Date() + }; + + const evaluatorConfig = { + metric: metricSchema, + runtimeConfig: { + llm: evaluationModel + } + }; + + const evalCase = { + [EvalDatasetDataKeyEnum.UserInput]: datasetData.userInput, + [EvalDatasetDataKeyEnum.ActualOutput]: datasetData.actualOutput, + [EvalDatasetDataKeyEnum.ExpectedOutput]: datasetData.expectedOutput, + [EvalDatasetDataKeyEnum.Context]: datasetData.context, + [EvalDatasetDataKeyEnum.RetrievalContext]: datasetData.retrievalContext + }; + + const evaluator = await createEvaluatorInstance(evaluatorConfig, { validate: false }); + const metricResult = await evaluator.evaluate(evalCase); + + if (metricResult.status === 'success' && metricResult.data) { + // Save usage + let totalPoints = 0; + if (metricResult.usages?.length) { + const { totalPoints: calculatedPoints } = await createEvalDatasetDataQualityUsage({ + teamId: datasetData.teamId, + tmbId: datasetData.tmbId, + model: evaluationModel, + usages: metricResult.usages + }); + totalPoints = calculatedPoints; + } + + const qualityResult = + metricResult.data.score >= 0.7 + ? EvalDatasetDataQualityResultEnum.highQuality + : EvalDatasetDataQualityResultEnum.needsOptimization; + + await MongoEvalDatasetData.findByIdAndUpdate(dataId, { + $set: { + 'qualityMetadata.status': EvalDatasetDataQualityStatusEnum.completed, + 'qualityMetadata.score': metricResult.data.score, + 'qualityMetadata.reason': metricResult.data?.reason, + 'qualityMetadata.runLogs': metricResult.data?.runLogs, + 'qualityMetadata.usages': metricResult?.usages, + 'qualityMetadata.finishTime': new Date(), + 'qualityMetadata.model': evaluationModel, + qualityResult: qualityResult + } + }); + + addLog.info('Eval dataset data quality job completed successfully', { + dataId, + score: metricResult.data.score, + totalPoints + }); + } else { + throw new Error(metricResult.error || 'Evaluation failed'); + } + } catch (error) { + addLog.error('Eval dataset data quality job failed', { dataId, error }); + + await MongoEvalDatasetData.findByIdAndUpdate(dataId, { + $set: { + 'qualityMetadata.status': EvalDatasetDataQualityStatusEnum.error, + 'qualityMetadata.error': error instanceof Error ? error.message : 'Unknown error', + 'qualityMetadata.finishTime': new Date() + } + }); + + throw error; + } +}; + +// Initialize worker +export const initEvalDatasetDataQualityWorker = () => { + return getEvalDatasetDataQualityWorker(processEvalDatasetDataQuality); +}; diff --git a/packages/service/core/evaluation/dataset/dataSynthesizeMq.ts b/packages/service/core/evaluation/dataset/dataSynthesizeMq.ts new file mode 100644 index 000000000000..1891e7a9b32f --- /dev/null +++ b/packages/service/core/evaluation/dataset/dataSynthesizeMq.ts @@ -0,0 +1,86 @@ +import { getQueue, getWorker, QueueNames } from '../../../common/bullmq'; +import { type Processor } from 'bullmq'; +import { addLog } from '../../../common/system/log'; +import { + createJobCleaner, + type JobCleanupResult, + type JobCleanupOptions, + checkBullMQHealth +} from '../utils/mq'; + +export type EvalDatasetDataSynthesizeData = { + dataId: string; + intelligentGenerationModel: string; + evalDatasetCollectionId: string; +}; + +export const evalDatasetDataSynthesizeQueue = getQueue( + QueueNames.evalDatasetDataSynthesize +); + +const concurrency = global.systemEnv?.evalConfig?.datasetDataSynthesizeConcurrency || 5; + +export const getEvalDatasetDataSynthesizeWorker = ( + processor: Processor +) => { + return getWorker(QueueNames.evalDatasetDataSynthesize, processor, { + maxStalledCount: global.systemEnv?.evalConfig?.maxStalledCount || 3, + removeOnFail: {}, + concurrency: concurrency + }); +}; + +export const addEvalDatasetDataSynthesizeBulk = (dataArray: EvalDatasetDataSynthesizeData[]) => { + const jobs = dataArray.map((data, index) => ({ + name: `synthesize-${data.dataId}-${Date.now()}-${index}`, + data, + opts: { + deduplication: { id: `synthesize-${data.dataId}-${Date.now()}-${index}` } + } + })); + + return evalDatasetDataSynthesizeQueue.addBulk(jobs); +}; + +export const checkEvalDatasetDataSynthesizeJobActive = async ( + evalDatasetCollectionId: string +): Promise => { + try { + const jobs = await evalDatasetDataSynthesizeQueue.getJobs(['waiting', 'active', 'delayed']); + return jobs.some((job) => job.data.evalDatasetCollectionId === evalDatasetCollectionId); + } catch (error) { + addLog.error('Failed to check eval dataset data synthesize job status', { + evalDatasetCollectionId: evalDatasetCollectionId, + error + }); + return false; + } +}; + +export const removeEvalDatasetDataSynthesizeJobsRobust = async ( + evalDatasetCollectionIds: string[], + options?: JobCleanupOptions +): Promise => { + const cleaner = createJobCleaner(options); + + const filterFn = (job: any) => { + return evalDatasetCollectionIds.includes(String(job.data?.evalDatasetCollectionId)); + }; + + const result = await cleaner.cleanAllJobsByFilter( + evalDatasetDataSynthesizeQueue, + filterFn, + QueueNames.evalDatasetDataSynthesize + ); + + addLog.info('Evaluation DatasetData synthesize jobs cleanup completed', { + evalDatasetCollectionIds: evalDatasetCollectionIds.length, + result + }); + + return result; +}; + +export const checkEvalDatasetDataSynthesizeQueueHealth = (): Promise => { + return checkBullMQHealth(evalDatasetDataSynthesizeQueue, 'synthesis'); +}; diff --git a/packages/service/core/evaluation/dataset/dataSynthesizeProcessor.ts b/packages/service/core/evaluation/dataset/dataSynthesizeProcessor.ts new file mode 100644 index 000000000000..cb7bba31318e --- /dev/null +++ b/packages/service/core/evaluation/dataset/dataSynthesizeProcessor.ts @@ -0,0 +1,141 @@ +import type { Job } from 'bullmq'; +import { addLog } from '../../../common/system/log'; +import { MongoEvalDatasetCollection } from './evalDatasetCollectionSchema'; +import { MongoEvalDatasetData } from './evalDatasetDataSchema'; +import { MongoDatasetData } from '../../dataset/data/schema'; +import { + EvalDatasetDataCreateFromEnum, + EvalDatasetDataKeyEnum, + EvalDatasetDataQualityStatusEnum, + EvalDatasetDataQualityResultEnum +} from '@fastgpt/global/core/evaluation/dataset/constants'; +import type { EvalDatasetDataSchemaType } from '@fastgpt/global/core/evaluation/dataset/type'; +import { + type EvalDatasetDataSynthesizeData, + getEvalDatasetDataSynthesizeWorker +} from './dataSynthesizeMq'; +import { createSynthesizerInstance } from '../synthesizer'; +import { checkTeamAIPoints } from '../../../support/permission/teamLimit'; +import { createEvalDatasetDataSynthesisUsage } from '../../../support/wallet/usage/controller'; +import { addAuditLog } from '../../../support/user/audit/util'; +import { AuditEventEnum } from '@fastgpt/global/support/user/audit/constants'; + +async function processor(job: Job) { + const { dataId, intelligentGenerationModel, evalDatasetCollectionId } = job.data; + + try { + addLog.info('Starting eval dataset data synthesis', { + dataId, + evalDatasetCollectionId, + intelligentGenerationModel + }); + + const sourceData = await MongoDatasetData.findById(dataId); + if (!sourceData) { + throw new Error(`Source dataset data not found: ${dataId}`); + } + + const evalDatasetCollection = + await MongoEvalDatasetCollection.findById(evalDatasetCollectionId); + if (!evalDatasetCollection) { + throw new Error(`Eval dataset not found: ${evalDatasetCollectionId}`); + } + + // Check AI points limit before synthesis + await checkTeamAIPoints(evalDatasetCollection.teamId); + + const llmConfig = { + name: intelligentGenerationModel + }; + + const synthesisCase = { + context: sourceData.q ? [sourceData.q] : [] + }; + + const synthesizer = createSynthesizerInstance('q_a_synthesizer', llmConfig); + const synthesisResult = await synthesizer.synthesize(synthesisCase); + + // Save usage + let totalPoints = 0; + if (synthesisResult.usages?.length) { + const { totalPoints: calculatedPoints } = await createEvalDatasetDataSynthesisUsage({ + teamId: evalDatasetCollection.teamId, + tmbId: evalDatasetCollection.tmbId, + model: intelligentGenerationModel, + usages: synthesisResult.usages + }); + totalPoints = calculatedPoints; + } + + const qualityResult = + synthesisResult.data?.metadata?.score && synthesisResult.data.metadata.score >= 0.7 + ? EvalDatasetDataQualityResultEnum.highQuality + : EvalDatasetDataQualityResultEnum.needsOptimization; + + const evalData: Partial = { + teamId: evalDatasetCollection.teamId, + tmbId: evalDatasetCollection.tmbId, + evalDatasetCollectionId: evalDatasetCollectionId, + [EvalDatasetDataKeyEnum.UserInput]: synthesisResult.data?.qaPair.question, + [EvalDatasetDataKeyEnum.ExpectedOutput]: synthesisResult.data?.qaPair.answer, + [EvalDatasetDataKeyEnum.ActualOutput]: '', + [EvalDatasetDataKeyEnum.Context]: [], + [EvalDatasetDataKeyEnum.RetrievalContext]: [], + qualityMetadata: { + status: EvalDatasetDataQualityStatusEnum.completed, + score: synthesisResult.data?.metadata?.score, + reason: synthesisResult.data?.metadata?.reason, + usages: synthesisResult?.usages, + finishTime: new Date() + }, + synthesisMetadata: { + sourceDataId: sourceData._id.toString(), + sourceDatasetId: sourceData.datasetId.toString(), + sourceCollectionId: sourceData.collectionId.toString(), + intelligentGenerationModel, + generatedAt: new Date(), + synthesizedAt: new Date() + }, + qualityResult, + createFrom: EvalDatasetDataCreateFromEnum.intelligentGeneration + }; + + const insertedRecord = await MongoEvalDatasetData.create(evalData); + + addLog.info('Completed data synthesis', { + dataId, + evalDatasetCollectionId, + insertedRecordId: insertedRecord._id, + totalPoints + }); + + (async () => { + addAuditLog({ + teamId: evalDatasetCollection.teamId, + tmbId: evalDatasetCollection.tmbId, + event: AuditEventEnum.SMART_GENERATE_EVALUATION_DATA, + params: { + collectionName: evalDatasetCollection.name + } + }); + })(); + + return { + success: true, + insertedRecordId: insertedRecord._id + }; + } catch (error) { + addLog.error('Failed to synthesize eval dataset data', { + dataId, + evalDatasetCollectionId, + error: error instanceof Error ? error.message : String(error), + stack: error instanceof Error ? error.stack : undefined + }); + throw error; + } +} + +// Initialize worker +export const initEvalDatasetDataSynthesizeWorker = () => { + return getEvalDatasetDataSynthesizeWorker(processor); +}; diff --git a/packages/service/core/evaluation/dataset/evalDatasetCollectionSchema.ts b/packages/service/core/evaluation/dataset/evalDatasetCollectionSchema.ts new file mode 100644 index 000000000000..2889e97eaf0d --- /dev/null +++ b/packages/service/core/evaluation/dataset/evalDatasetCollectionSchema.ts @@ -0,0 +1,71 @@ +import { + TeamCollectionName, + TeamMemberCollectionName +} from '@fastgpt/global/support/user/team/constant'; +import { connectionMongo, getMongoModel } from '../../../common/mongo'; +import type { EvalDatasetCollectionSchemaType } from '@fastgpt/global/core/evaluation/dataset/type'; + +const { Schema } = connectionMongo; + +export const EvalDatasetCollectionName = 'eval_dataset_collections'; + +const EvalDatasetCollectionSchema = new Schema({ + teamId: { + type: Schema.Types.ObjectId, + ref: TeamCollectionName, + required: true, + index: true + }, + tmbId: { + type: Schema.Types.ObjectId, + ref: TeamMemberCollectionName, + required: true + }, + name: { + type: String, + required: true, + trim: true, + maxlength: 100 + }, + description: { + type: String, + default: '', + trim: true, + maxlength: 100 + }, + createTime: { + type: Date, + default: Date.now, + immutable: true + }, + updateTime: { + type: Date, + default: Date.now + }, + metadata: { + type: Schema.Types.Mixed, + default: {} + }, + evaluationModel: { + type: String, + trim: true, + maxlength: 100 + } +}); + +// Indexes for efficient queries +EvalDatasetCollectionSchema.index({ teamId: 1, createTime: -1 }); +EvalDatasetCollectionSchema.index({ teamId: 1, name: 1 }, { unique: true }); +EvalDatasetCollectionSchema.index({ teamId: 1, updateTime: -1 }); + +// Update the updateTime on save +EvalDatasetCollectionSchema.pre('save', function () { + if (this.isModified() && !this.isNew) { + this.updateTime = new Date(); + } +}); + +export const MongoEvalDatasetCollection = getMongoModel( + EvalDatasetCollectionName, + EvalDatasetCollectionSchema +); diff --git a/packages/service/core/evaluation/dataset/evalDatasetDataSchema.ts b/packages/service/core/evaluation/dataset/evalDatasetDataSchema.ts new file mode 100644 index 000000000000..4baa8e213cbe --- /dev/null +++ b/packages/service/core/evaluation/dataset/evalDatasetDataSchema.ts @@ -0,0 +1,153 @@ +import type { EvalDatasetDataSchemaType } from '@fastgpt/global/core/evaluation/dataset/type'; +import { connectionMongo, getMongoModel } from '../../../common/mongo'; +import { EvalDatasetCollectionName } from './evalDatasetCollectionSchema'; +import { + EvalDatasetDataCreateFromEnum, + EvalDatasetDataCreateFromValues, + EvalDatasetDataKeyEnum, + EvalDatasetDataQualityStatusEnum, + EvalDatasetDataQualityResultEnum, + EvalDatasetDataQualityResultValues +} from '@fastgpt/global/core/evaluation/dataset/constants'; +import { + TeamCollectionName, + TeamMemberCollectionName +} from '@fastgpt/global/support/user/team/constant'; + +const { Schema } = connectionMongo; + +export const EvalDatasetDataCollectionName = 'eval_dataset_datas'; + +const EvalDatasetDataSchema = new Schema({ + teamId: { + type: Schema.Types.ObjectId, + ref: TeamCollectionName, + required: true, + index: true + }, + tmbId: { + type: Schema.Types.ObjectId, + ref: TeamMemberCollectionName, + required: true + }, + evalDatasetCollectionId: { + type: Schema.Types.ObjectId, + ref: EvalDatasetCollectionName, + required: true, + index: true + }, + [EvalDatasetDataKeyEnum.UserInput]: { + type: String, + default: '', + trim: true + }, + [EvalDatasetDataKeyEnum.ActualOutput]: { + type: String, + default: '', + trim: true + }, + [EvalDatasetDataKeyEnum.ExpectedOutput]: { + type: String, + default: '', + trim: true + }, + [EvalDatasetDataKeyEnum.Context]: { + type: [ + { + type: String, + trim: true + } + ], + default: [], + validate: { + validator: (arr: string[]) => arr.length <= 100, + message: 'Context array cannot exceed 100 items' + } + }, + [EvalDatasetDataKeyEnum.RetrievalContext]: { + type: [ + { + type: String, + trim: true + } + ], + default: [] + }, + qualityMetadata: { + status: { + type: String, + enum: Object.values(EvalDatasetDataQualityStatusEnum), + default: EvalDatasetDataQualityStatusEnum.unevaluated, + required: true + }, + score: { + type: Number, + min: 0, + max: 1 + }, + reason: String, + model: String, + usages: [Schema.Types.Mixed], + runLogs: [Schema.Types.Mixed], + startTime: Date, + finishTime: Date, + queueTime: Date, + error: String + }, + synthesisMetadata: { + sourceDataId: String, + sourceDatasetId: String, + sourceCollectionId: String, + intelligentGenerationModel: String, + synthesizedAt: Date, + generatedAt: Date + }, + qualityResult: { + type: String, + enum: EvalDatasetDataQualityResultValues + }, + createFrom: { + type: String, + enum: EvalDatasetDataCreateFromValues, + default: EvalDatasetDataCreateFromEnum.manual, + required: true, + index: true + }, + createTime: { + type: Date, + default: Date.now, + immutable: true + }, + updateTime: { + type: Date, + default: Date.now + } +}); + +// Indexes for efficient queries +EvalDatasetDataSchema.index({ evalDatasetCollectionId: 1, createTime: -1 }); +EvalDatasetDataSchema.index({ evalDatasetCollectionId: 1, updateTime: -1 }); + +// Quality related indexes +EvalDatasetDataSchema.index({ 'qualityMetadata.status': 1 }); +EvalDatasetDataSchema.index({ qualityResult: 1 }); +EvalDatasetDataSchema.index({ evalDatasetCollectionId: 1, qualityResult: 1 }); + +// Text search index for searching within inputs and outputs +EvalDatasetDataSchema.index({ + [EvalDatasetDataKeyEnum.UserInput]: 'text', + [EvalDatasetDataKeyEnum.ExpectedOutput]: 'text', + [EvalDatasetDataKeyEnum.ActualOutput]: 'text' +}); + +// Update the updateTime on save +EvalDatasetDataSchema.pre('save', function () { + if (this.isModified() && !this.isNew) { + this.updateTime = new Date(); + } +}); + +export const MongoEvalDatasetData = getMongoModel( + EvalDatasetDataCollectionName, + EvalDatasetDataSchema +); diff --git a/packages/service/core/evaluation/dataset/utils.ts b/packages/service/core/evaluation/dataset/utils.ts new file mode 100644 index 000000000000..19993b0f6028 --- /dev/null +++ b/packages/service/core/evaluation/dataset/utils.ts @@ -0,0 +1,173 @@ +import type { EvalDatasetCollectionStatus } from '@fastgpt/global/core/evaluation/dataset/type'; +import { EvalDatasetCollectionStatusEnum } from '@fastgpt/global/core/evaluation/dataset/constants'; +import { evalDatasetDataSynthesizeQueue } from './dataSynthesizeMq'; +import { getEvaluationPermissionAggregation } from '../common'; +import { replaceRegChars } from '@fastgpt/global/common/string/tools'; +import { Types } from 'mongoose'; +import type { ApiRequestProps } from '../../../type/next'; + +export async function getCollectionStatus( + collectionId: string +): Promise { + try { + const jobs = await evalDatasetDataSynthesizeQueue.getJobs([ + 'waiting', + 'active', + 'delayed', + 'failed', + 'completed' + ]); + const collectionJobs = jobs.filter((job) => job.data.evalDatasetCollectionId === collectionId); + + if (collectionJobs.length === 0) { + return EvalDatasetCollectionStatusEnum.ready; + } + + let hasActive = false; + let hasWaiting = false; + let hasFailed = false; + + for (const job of collectionJobs) { + if (await job.isActive()) { + hasActive = true; + } else if ((await job.isWaiting()) || (await job.isDelayed())) { + hasWaiting = true; + } else if (await job.isFailed()) { + hasFailed = true; + } + } + + if (hasActive) { + return EvalDatasetCollectionStatusEnum.processing; + } + if (hasWaiting) { + return EvalDatasetCollectionStatusEnum.queuing; + } + if (hasFailed) { + return EvalDatasetCollectionStatusEnum.error; + } + + return EvalDatasetCollectionStatusEnum.ready; + } catch (error) { + console.error('Error getting collection status:', error); + throw new Error( + `Failed to get collection status: ${error instanceof Error ? error.message : String(error)}` + ); + } +} + +export function buildCollectionAggregationPipeline(baseFields?: Record) { + return [ + { + $lookup: { + from: 'team_members', + localField: 'tmbId', + foreignField: '_id', + as: 'teamMember' + } + }, + { + $lookup: { + from: 'eval_dataset_datas', + localField: '_id', + foreignField: 'evalDatasetCollectionId', + as: 'dataItems' + } + }, + { + $addFields: { + teamMember: { $arrayElemAt: ['$teamMember', 0] }, + dataItemsCount: { $size: '$dataItems' } + } + }, + { + $project: { + _id: 1, + name: 1, + description: 1, + evaluationModel: 1, + createTime: 1, + updateTime: 1, + creatorAvatar: '$teamMember.avatar', + creatorName: '$teamMember.name', + dataItemsCount: 1, + ...baseFields + } + } + ]; +} + +export function formatCollectionBase(collection: any) { + return { + _id: String(collection._id), + name: collection.name, + description: collection.description || '', + evaluationModel: collection.evaluationModel, + createTime: collection.createTime, + updateTime: collection.updateTime, + creatorAvatar: collection.creatorAvatar, + creatorName: collection.creatorName, + dataItemsCount: collection.dataItemsCount + }; +} + +export async function buildEvalDatasetCollectionFilter( + req: ApiRequestProps<{ searchKey?: string }>, + searchKey?: string +) { + // API layer permission validation: get permission aggregation info + const { teamId, tmbId, isOwner, roleList, myGroupMap, myOrgSet } = + await getEvaluationPermissionAggregation({ + req, + authApiKey: true, + authToken: true + }); + + // Calculate resource IDs accessible by user + const myRoles = roleList.filter( + (item) => + String(item.tmbId) === String(tmbId) || + myGroupMap.has(String(item.groupId)) || + myOrgSet.has(String(item.orgId)) + ); + const accessibleIds = myRoles.map((item) => String(item.resourceId)); + + // Build unified filter conditions + const baseFilter: Record = { + teamId: new Types.ObjectId(teamId) + }; + + if (searchKey && typeof searchKey === 'string' && searchKey.trim().length > 0) { + baseFilter.name = { $regex: new RegExp(`${replaceRegChars(searchKey.trim())}`, 'i') }; + } + + // Unified permission filtering logic + let finalFilter = baseFilter; + if (!isOwner) { + if (accessibleIds.length > 0) { + finalFilter = { + ...baseFilter, + $or: [ + { _id: { $in: accessibleIds.map((id) => new Types.ObjectId(id)) } }, + { tmbId: new Types.ObjectId(tmbId) } // Own datasets + ] + }; + } else { + // If no permission roles, can only access self-created datasets + finalFilter = { + ...baseFilter, + tmbId: new Types.ObjectId(tmbId) + }; + } + } + + return { + finalFilter, + teamId, + tmbId, + isOwner, + myRoles, + accessibleIds, + roleList + }; +} diff --git a/packages/service/core/evaluation/evaluator/ditingClient.ts b/packages/service/core/evaluation/evaluator/ditingClient.ts new file mode 100644 index 000000000000..f29c295e3ac1 --- /dev/null +++ b/packages/service/core/evaluation/evaluator/ditingClient.ts @@ -0,0 +1,71 @@ +import type { + HttpConfig, + EvaluationRequest, + EvaluationResponse +} from '@fastgpt/global/core/evaluation/metric/type'; +import { EvaluationErrEnum } from '@fastgpt/global/common/error/code/evaluation'; + +function loadHttpConfigFromEnv(): HttpConfig { + return { + url: process.env.DITING_BASE_URL || 'http://diting:3000', + timeout: Number(process.env.DITING_TIMEOUT) || 300000 + }; +} + +export function createDitingClient(config: HttpConfig = loadHttpConfigFromEnv()) { + function getFullUrl(path: string) { + return config.url.replace(/\/$/, '') + path; + } + + return { + async runEvaluation(request: EvaluationRequest): Promise { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), config.timeout); + + try { + const res = await fetch(getFullUrl('/api/v1/evaluations/runs'), { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(request), + signal: controller.signal + }); + + clearTimeout(timeoutId); + + if (!res.ok) { + if (res.status >= 500) { + throw new Error(EvaluationErrEnum.evaluatorServiceUnavailable); + } else { + throw new Error(EvaluationErrEnum.evaluatorInvalidResponse); + } + } + + const response = await res.json(); + if (!response) { + throw new Error(EvaluationErrEnum.evaluatorInvalidResponse); + } + + return response as EvaluationResponse; + } catch (err: any) { + clearTimeout(timeoutId); + + if (err.name === 'AbortError') { + throw new Error(EvaluationErrEnum.evaluatorRequestTimeout); + } + + const networkErrorCodes = ['ECONNREFUSED', 'ENOTFOUND', 'ECONNRESET', 'ETIMEDOUT']; + if (err.code && networkErrorCodes.includes(err.code)) { + throw new Error(EvaluationErrEnum.evaluatorNetworkError); + } + + if (Object.values(EvaluationErrEnum).includes(err.message)) { + new Error(err.message); + } + + throw new Error(EvaluationErrEnum.evaluatorServiceUnavailable); + } + } + }; +} diff --git a/packages/service/core/evaluation/evaluator/index.ts b/packages/service/core/evaluation/evaluator/index.ts new file mode 100644 index 000000000000..980868a3500a --- /dev/null +++ b/packages/service/core/evaluation/evaluator/index.ts @@ -0,0 +1,321 @@ +import type { + EvalMetricSchemaType, + EvalCase, + MetricResult, + EvaluationResponse, + MetricConfig, + EvalModelConfigType +} from '@fastgpt/global/core/evaluation/metric/type'; +import type { EvaluatorSchema } from '@fastgpt/global/core/evaluation/type'; +import { + Validatable, + type ValidationResult, + type ValidationError +} from '@fastgpt/global/core/evaluation/validate'; +import { getEvaluationModel, getEmbeddingModel } from '../../ai/model'; +import { createDitingClient } from './ditingClient'; +import { formatModelChars2Points } from '../../../support/wallet/usage/utils'; +import { ModelTypeEnum } from '@fastgpt/global/core/ai/model'; +import { EvaluationErrEnum } from '@fastgpt/global/common/error/code/evaluation'; + +export abstract class Evaluator extends Validatable { + protected metricConfig: MetricConfig; + protected llmConfig?: EvalModelConfigType; + protected embeddingConfig?: EvalModelConfigType; + protected evaluatorConfig?: EvaluatorSchema; + + constructor( + metricConfig: MetricConfig, + llmConfig?: EvalModelConfigType, + embeddingConfig?: EvalModelConfigType, + evaluatorConfig?: EvaluatorSchema + ) { + super(); + this.llmConfig = llmConfig; + this.embeddingConfig = embeddingConfig; + this.metricConfig = metricConfig; + this.evaluatorConfig = evaluatorConfig; + } + + abstract evaluate(evalCase: EvalCase): Promise; + + async validate(): Promise { + const errors: ValidationError[] = []; + const warnings: ValidationError[] = []; + + try { + // Validate metric configuration requirements based on evaluatorConfig + if (this.evaluatorConfig?.metric.llmRequired && !this.llmConfig) { + errors.push({ + code: EvaluationErrEnum.evaluatorLLmConfigMissing, + message: 'LLM configuration is required for this metric', + field: 'llmConfig' + }); + } + + if (this.evaluatorConfig?.metric.embeddingRequired && !this.embeddingConfig) { + errors.push({ + code: EvaluationErrEnum.evaluatorEmbeddingConfigMissing, + message: 'Embedding configuration is required for this metric', + field: 'embeddingConfig' + }); + } + + // Validate metric configuration + if (!this.metricConfig) { + errors.push({ + code: EvaluationErrEnum.evaluatorConfigRequired, + message: 'Metric configuration is required', + field: 'metricConfig' + }); + return { isValid: false, errors, warnings }; + } + + if (!this.metricConfig.metricName) { + errors.push({ + code: EvaluationErrEnum.evalMetricNameRequired, + message: 'Metric name is required', + field: 'metricConfig.metricName' + }); + } + + if (!this.metricConfig.metricType) { + errors.push({ + code: EvaluationErrEnum.evalMetricTypeRequired, + message: 'Metric type is required', + field: 'metricConfig.metricType' + }); + } + + // Validate LLM configuration if provided + if (this.llmConfig) { + if (!this.llmConfig.name) { + errors.push({ + code: EvaluationErrEnum.evaluatorLLmModelNotFound, + message: 'LLM model name is required', + field: 'llmConfig.name' + }); + } else { + try { + const llm = getEvaluationModel(this.llmConfig.name); + if (!llm) { + throw new Error(`Evaluation model '${this.llmConfig.name}' not found`); + } + } catch (err) { + errors.push({ + code: EvaluationErrEnum.evaluatorLLmModelNotFound, + message: `LLM model '${this.llmConfig.name}' not found or not accessible`, + field: 'llmConfig.name', + debugInfo: { + modelName: this.llmConfig.name, + error: err instanceof Error ? err.message : String(err) + } + }); + } + } + } + + // Validate embedding configuration if provided + if (this.embeddingConfig) { + if (!this.embeddingConfig.name) { + errors.push({ + code: EvaluationErrEnum.evaluatorEmbeddingModelNotFound, + message: 'Embedding model name is required', + field: 'embeddingConfig.name' + }); + } else { + try { + getEmbeddingModel(this.embeddingConfig.name); + } catch (err) { + errors.push({ + code: EvaluationErrEnum.evaluatorEmbeddingModelNotFound, + message: `Embedding model '${this.embeddingConfig.name}' not found or not accessible`, + field: 'embeddingConfig.name', + debugInfo: { + modelName: this.embeddingConfig.name, + error: err instanceof Error ? err.message : String(err) + } + }); + } + } + } + + return { + isValid: errors.length === 0, + errors, + warnings: warnings.length > 0 ? warnings : undefined + }; + } catch (error) { + errors.push({ + code: EvaluationErrEnum.evaluatorConfigRequired, + message: `Evaluator validation failed: ${error instanceof Error ? error.message : String(error)}`, + debugInfo: { + error: error instanceof Error ? error.message : String(error), + stack: error instanceof Error ? error.stack : undefined + } + }); + + return { isValid: false, errors, warnings }; + } + } +} + +export class DitingEvaluator extends Evaluator { + private client: ReturnType; + private scoreScaling: number; + + constructor( + metricConfig: MetricConfig, + llmConfig?: EvalModelConfigType, + embeddingConfig?: EvalModelConfigType, + evaluatorConfig?: EvaluatorSchema, + scoreScaling: number = 1 + ) { + super(metricConfig, llmConfig, embeddingConfig, evaluatorConfig); + this.client = createDitingClient(); + this.scoreScaling = scoreScaling; + } + + async evaluate(evalCase: EvalCase): Promise { + const response: EvaluationResponse = await this.client.runEvaluation({ + evalCase: evalCase, + metricConfig: this.metricConfig, + embeddingConfig: this.embeddingConfig, + llmConfig: this.llmConfig + }); + + // Calculate total points from usages + let totalPoints = 0; + if (response.usages && response.usages.length > 0) { + for (const usage of response.usages) { + if (usage.promptTokens || usage.completionTokens) { + const modelType = + usage.modelType === 'embed' ? ModelTypeEnum.embedding : ModelTypeEnum.llm; + const model = + modelType === ModelTypeEnum.embedding + ? this.embeddingConfig?.name + : this.llmConfig?.name; + + if (model) { + const { totalPoints: usagePoints } = formatModelChars2Points({ + model, + modelType, + inputTokens: usage.promptTokens || 0, + outputTokens: usage.completionTokens || 0 + }); + totalPoints += usagePoints; + } + } + } + } + + // Apply score scaling if data.score exists + // scoreScaling directly multiplies the original score (e.g., 1 means no scaling, 100 means 100x amplification) + let scaledData = response.data; + if (response.data?.score !== undefined && response.data?.score !== null) { + scaledData = { + ...response.data, + score: response.data.score * this.scoreScaling + }; + } + + return { + metricName: this.metricConfig.metricName, + status: response.status, + data: scaledData, + usages: response.usages, + error: response.error, + totalPoints + }; + } +} + +export async function createEvaluatorInstance( + evaluatorConfig: EvaluatorSchema, + options: { validate?: boolean } = { validate: true } +): Promise { + const metricConfig: MetricConfig = { + metricName: evaluatorConfig.metric.name, + metricType: evaluatorConfig.metric.type, + prompt: evaluatorConfig.metric.prompt + }; + + let llmConfig: EvalModelConfigType | undefined = undefined; + let embeddingConfig: EvalModelConfigType | undefined = undefined; + + if (evaluatorConfig.runtimeConfig?.llm) { + try { + const llm = getEvaluationModel(evaluatorConfig.runtimeConfig.llm); + if (!llm) { + throw new Error(`Evaluation model '${evaluatorConfig.runtimeConfig.llm}' not found`); + } + llmConfig = { + name: evaluatorConfig.runtimeConfig.llm, + baseUrl: llm.requestUrl ?? undefined, + apiKey: llm.requestAuth ?? undefined + }; + } catch (err) { + throw new Error(EvaluationErrEnum.evaluatorLLmModelNotFound); + } + } + + if (evaluatorConfig.runtimeConfig?.embedding) { + try { + const embedding = getEmbeddingModel(evaluatorConfig.runtimeConfig.embedding); + embeddingConfig = { + name: evaluatorConfig.runtimeConfig.embedding, + baseUrl: embedding.requestUrl ?? undefined, + apiKey: embedding.requestAuth ?? undefined + }; + } catch (err) { + throw new Error(EvaluationErrEnum.evaluatorEmbeddingModelNotFound); + } + } + + const scoreScaling = evaluatorConfig.scoreScaling ?? 1; + const evaluatorInstance = new DitingEvaluator( + metricConfig, + llmConfig, + embeddingConfig, + evaluatorConfig, + scoreScaling + ); + + // Validate instance if requested (default behavior) + if (options.validate) { + const validationResult = await evaluatorInstance.validate(); + if (!validationResult.isValid) { + const errorMessages = validationResult.errors + .map((err) => `${err.code}: ${err.message}`) + .join('; '); + throw new Error(`Evaluator instance validation failed: ${errorMessages}`); + } + } + + return evaluatorInstance; +} + +export async function validateEvaluatorConfig( + evaluatorConfig: EvaluatorSchema +): Promise { + try { + const evaluatorInstance = await createEvaluatorInstance(evaluatorConfig, { validate: false }); + return await evaluatorInstance.validate(); + } catch (error) { + // If we can't even create the instance, return validation error + return { + isValid: false, + errors: [ + { + code: EvaluationErrEnum.evalEvaluatorInvalidConfig, + message: `Failed to create evaluator instance: ${error instanceof Error ? error.message : String(error)}`, + debugInfo: { + error: error instanceof Error ? error.message : String(error), + stack: error instanceof Error ? error.stack : undefined, + metricType: evaluatorConfig.metric?.type + } + } + ] + }; + } +} diff --git a/packages/service/core/evaluation/index.ts b/packages/service/core/evaluation/index.ts new file mode 100644 index 000000000000..81b35ca6ba93 --- /dev/null +++ b/packages/service/core/evaluation/index.ts @@ -0,0 +1,193 @@ +import { addLog } from '../../common/system/log'; +import { setCron } from '../../common/system/cron'; +import { initEvalDatasetDataQualityWorker } from './dataset/dataQualityProcessor'; +import { initEvalDatasetDataSynthesizeWorker } from './dataset/dataSynthesizeProcessor'; +import { initEvalTaskItemWorker } from './task/processor'; +import { initEvaluationSummaryWorker } from './summary/worker'; + +// Import all queues for cleanup +import { evaluationItemQueue } from './task/mq'; +import { evalDatasetDataQualityQueue } from './dataset/dataQualityMq'; +import { evalDatasetDataSynthesizeQueue } from './dataset/dataSynthesizeMq'; +import { getEvaluationSummaryQueue } from './summary/queue'; + +// Import MongoDB models for existence checks +import { MongoEvaluation, MongoEvalItem } from './task/schema'; +import { MongoEvalDatasetData } from './dataset/evalDatasetDataSchema'; +import { MongoEvalDatasetCollection } from './dataset/evalDatasetCollectionSchema'; + +// Initialize evaluation workers + +export const initEvaluationWorkers = () => { + addLog.info('Init Evaluation Workers...'); + + initEvalTaskItemWorker(); + + initEvalDatasetDataQualityWorker(); + initEvalDatasetDataSynthesizeWorker(); + + initEvaluationSummaryWorker(); + + // Setup periodic orphaned jobs cleanup + setupOrphanedJobsCleanup(); +}; + +/** + * Setup periodic cleanup for orphaned jobs + * Specifically handles residual issues caused by active jobs that cannot be deleted + */ +const setupOrphanedJobsCleanup = () => { + // Run cleanup every 30 minutes + setCron('*/30 * * * *', async () => { + await cleanupOrphanedJobs(); + }); + + addLog.info('[Evaluation] Orphaned jobs cleanup scheduled (every 30 minutes)'); +}; + +/** + * Comprehensive cleanup for all orphaned jobs in evaluation system + * Handles active jobs that cannot be deleted by BullMQ + */ +export const cleanupOrphanedJobs = async () => { + try { + addLog.debug('[Evaluation] Starting comprehensive orphaned jobs cleanup'); + + const summaryQueue = getEvaluationSummaryQueue(); + + // Get all jobs from all evaluation queues + const [itemJobs, dataQualityJobs, dataSynthesizeJobs, summaryJobs] = await Promise.all([ + evaluationItemQueue.getJobs(['active', 'waiting', 'delayed', 'completed', 'failed'], 0, 2000), + evalDatasetDataQualityQueue.getJobs( + ['active', 'waiting', 'delayed', 'completed', 'failed'], + 0, + 1000 + ), + evalDatasetDataSynthesizeQueue.getJobs( + ['active', 'waiting', 'delayed', 'completed', 'failed'], + 0, + 1000 + ), + summaryQueue.getJobs(['active', 'waiting', 'delayed', 'completed', 'failed'], 0, 500) + ]); + + let cleanedCount = 0; + let skippedActiveCount = 0; + + // 1. Clean orphaned eval task item jobs + for (const job of itemJobs) { + try { + const { evalId, evalItemId } = job.data; + const [evaluation, evalItem] = await Promise.all([ + MongoEvaluation.exists({ _id: evalId }), + MongoEvalItem.exists({ _id: evalItemId }) + ]); + + if (!evaluation || !evalItem) { + const result = await cleanupJob(job, 'item', { evalId, evalItemId }); + if (result.cleaned) cleanedCount++; + if (result.skippedActive) skippedActiveCount++; + } + } catch (error) { + addLog.warn('[Evaluation] Failed to cleanup item job', { jobId: job.id, error }); + } + } + + // 2. Clean orphaned data quality jobs + for (const job of dataQualityJobs) { + try { + const { dataId } = job.data; + const dataExists = await MongoEvalDatasetData.exists({ _id: dataId }); + + if (!dataExists) { + const result = await cleanupJob(job, 'dataQuality', { dataId }); + if (result.cleaned) cleanedCount++; + if (result.skippedActive) skippedActiveCount++; + } + } catch (error) { + addLog.warn('[Evaluation] Failed to cleanup data quality job', { jobId: job.id, error }); + } + } + + // 3. Clean orphaned data synthesize jobs + for (const job of dataSynthesizeJobs) { + try { + const { evalDatasetCollectionId } = job.data; + const collectionExists = await MongoEvalDatasetCollection.exists({ + _id: evalDatasetCollectionId + }); + + if (!collectionExists) { + const result = await cleanupJob(job, 'dataSynthesize', { + evalDatasetCollectionId + }); + if (result.cleaned) cleanedCount++; + if (result.skippedActive) skippedActiveCount++; + } + } catch (error) { + addLog.warn('[Evaluation] Failed to cleanup data synthesize job', { jobId: job.id, error }); + } + } + + // 4. Clean orphaned summary jobs + for (const job of summaryJobs) { + try { + const { evalId } = job.data; + const evaluation = await MongoEvaluation.exists({ _id: evalId }); + + if (!evaluation) { + const result = await cleanupJob(job, 'summary', { evalId }); + if (result.cleaned) cleanedCount++; + if (result.skippedActive) skippedActiveCount++; + } + } catch (error) { + addLog.warn('[Evaluation] Failed to cleanup summary job', { jobId: job.id, error }); + } + } + + const result = { + totalJobs: + itemJobs.length + dataQualityJobs.length + dataSynthesizeJobs.length + summaryJobs.length, + cleanedJobs: cleanedCount, + skippedActiveJobs: skippedActiveCount, + breakdown: { + itemJobs: itemJobs.length, + dataQualityJobs: dataQualityJobs.length, + dataSynthesizeJobs: dataSynthesizeJobs.length, + summaryJobs: summaryJobs.length + } + }; + + addLog.info('[Evaluation] Comprehensive orphaned jobs cleanup completed', result); + return result; + } catch (error) { + addLog.error('[Evaluation] Comprehensive orphaned jobs cleanup failed', { error }); + return null; + } +}; + +/** + * Helper function to cleanup individual job + */ +async function cleanupJob(job: any, jobType: string, context: Record) { + const jobState = await job.getState(); + + if (jobState === 'active') { + // Active job cannot be removed, log warning + addLog.warn(`[Evaluation] Found orphaned active ${jobType} job (cannot remove)`, { + jobId: job.id, + state: jobState, + ...context + }); + return { cleaned: false, skippedActive: true }; + } else { + // Non-active jobs can be safely removed + await job.remove(); + addLog.debug(`[Evaluation] Removed orphaned ${jobType} job`, { + jobId: job.id, + state: jobState, + ...context + }); + return { cleaned: true, skippedActive: false }; + } +} diff --git a/packages/service/core/evaluation/metric/provider.ts b/packages/service/core/evaluation/metric/provider.ts new file mode 100644 index 000000000000..0bf8dc67e6bd --- /dev/null +++ b/packages/service/core/evaluation/metric/provider.ts @@ -0,0 +1,37 @@ +import { readConfigData } from '../../../../../projects/app/src/service/common/system'; +import { EvalMetricTypeEnum } from '@fastgpt/global/core/evaluation/metric/constants'; +import type { EvalMetricSchemaType } from '@fastgpt/global/core/evaluation/metric/type'; + +export async function loadSystemBuiltinMetrics(): Promise { + try { + const metricContent = await readConfigData('metric.json'); + const { builtinMetrics } = JSON.parse(metricContent); + + global.builtinMetrics = (builtinMetrics || []).map((metric: any) => ({ + _id: `builtin_${metric.name}`, + teamId: '', + tmbId: '', + name: metric.name, + description: metric.description || '', + type: EvalMetricTypeEnum.Builtin, + createTime: new Date(), + updateTime: new Date(), + ...Object.fromEntries( + Object.entries(metric).filter(([key, value]) => key.endsWith('Required') && value === true) + ) + })) as EvalMetricSchemaType[]; + + console.log(`Loaded ${global.builtinMetrics.length} builtin metrics`); + } catch (error) { + console.error('Failed to load builtin metrics:', error); + global.builtinMetrics = []; + } +} + +export async function getBuiltinMetrics(): Promise { + if (!global.builtinMetrics) { + return []; + } + + return global.builtinMetrics; +} diff --git a/packages/service/core/evaluation/metric/schema.ts b/packages/service/core/evaluation/metric/schema.ts new file mode 100644 index 000000000000..9b05293a89a1 --- /dev/null +++ b/packages/service/core/evaluation/metric/schema.ts @@ -0,0 +1,107 @@ +import { + TeamCollectionName, + TeamMemberCollectionName +} from '@fastgpt/global/support/user/team/constant'; +import { EvalMetricTypeValues } from '@fastgpt/global/core/evaluation/metric/constants'; +import { connectionMongo, getMongoModel } from '../../../common/mongo'; +import type { EvalMetricSchemaType } from '@fastgpt/global/core/evaluation/metric/type'; + +const { Schema } = connectionMongo; + +export const EvalMetricCollectionName = 'eval_metrics'; + +const EvalMetricSchema = new Schema({ + teamId: { + type: Schema.Types.ObjectId, + ref: TeamCollectionName, + required: true + }, + tmbId: { + type: Schema.Types.ObjectId, + ref: TeamMemberCollectionName, + required: true + }, + name: { + type: String, + required: true, + trim: true, + maxlength: 100 + }, + description: { + type: String, + default: '', + trim: true, + maxlength: 100 + }, + type: { + type: String, + enum: EvalMetricTypeValues, + required: true + }, + prompt: { + type: String, + required: false + }, + + userInputRequired: { + type: Boolean, + required: false + }, + + actualOutputRequired: { + type: Boolean, + required: false + }, + + expectedOutputRequired: { + type: Boolean, + required: false + }, + contextRequired: { + type: Boolean, + required: false + }, + retrievalContextRequired: { + type: Boolean, + required: false + }, + + embeddingRequired: { + type: Boolean, + required: false + }, + llmRequired: { + type: Boolean, + required: false + }, + + createTime: { + type: Date, + required: true, + default: () => new Date() + }, + updateTime: { + type: Date, + required: true, + default: () => new Date() + } +}); + +EvalMetricSchema.index({ teamId: 1, name: 1 }, { unique: true }); +EvalMetricSchema.index({ createTime: -1 }); +EvalMetricSchema.index({ updateTime: -1 }); + +EvalMetricSchema.pre('save', function (next) { + this.updateTime = new Date(); + next(); +}); + +EvalMetricSchema.pre(['updateOne', 'updateMany', 'findOneAndUpdate'], function (next) { + this.set({ updateTime: new Date() }); + next(); +}); + +export const MongoEvalMetric = getMongoModel( + EvalMetricCollectionName, + EvalMetricSchema +); diff --git a/packages/service/core/evaluation/summary/index.ts b/packages/service/core/evaluation/summary/index.ts new file mode 100644 index 000000000000..19d18d6ecdd9 --- /dev/null +++ b/packages/service/core/evaluation/summary/index.ts @@ -0,0 +1,1080 @@ +import { + CaculateMethodMap, + EvaluationStatusEnum, + CalculateMethodEnum +} from '@fastgpt/global/core/evaluation/constants'; +import { MongoEvaluation, MongoEvalItem } from '../task/schema'; +import type { EvaluationSchemaType } from '@fastgpt/global/core/evaluation/type'; +import { Types } from '../../../common/mongo'; +import { addLog } from '../../../common/system/log'; +import { + SummaryStatusEnum, + PERFECT_SCORE, + MAX_TOKEN_FOR_EVALUATION_SUMMARY, + TEMPERATURE_FOR_EVALUATION_SUMMARY +} from '@fastgpt/global/core/evaluation/constants'; +import { getEvaluationSummaryTokenLimit, getEvaluationSummaryModel } from '../utils/tokenLimiter'; +import { createChatCompletion } from '../../ai/config'; +import { countGptMessagesTokens } from '../../../common/string/tiktoken'; +import type { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type'; +import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants'; +import { loadRequestMessages } from '../../chat/utils'; +import { + problemAnalysisTemplateZhCN, + strengthAnalysisTemplateZhCN, + goodExampleZhCN, + badExampleZhCN, + problemAnalysisTemplateZhTW, + strengthAnalysisTemplateZhTW, + goodExampleZhTW, + badExampleZhTW, + problemAnalysisTemplateEn, + strengthAnalysisTemplateEn, + goodExampleEn, + badExampleEn +} from '@fastgpt/global/core/ai/prompt/eval'; +import { LanguageType, LanguageDisplayNameMap } from './util/languageUtil'; +import { checkTeamAIPoints } from '../../../support/permission/teamLimit'; +import { addAuditLog } from '../../../support/user/audit/util'; +import { AuditEventEnum } from '@fastgpt/global/support/user/audit/constants'; +import { createMergedEvaluationUsage } from '../utils/usage'; +import { formatModelChars2Points } from '../../../support/wallet/usage/utils'; +import { ModelTypeEnum } from '@fastgpt/global/core/ai/model'; +import { EvaluationErrEnum } from '@fastgpt/global/common/error/code/evaluation'; +import { MetricResultStatusEnum } from '@fastgpt/global/core/evaluation/metric/constants'; +import { addSummaryTaskToQueue } from './queue'; +import { mongoSessionRun } from '../../../common/mongo/sessionRun'; +import type { ClientSession } from '../../../common/mongo'; + +export class EvaluationSummaryService { + static async getEvaluationSummary(evalId: string): Promise<{ + data: Array<{ + metricId: string; + metricName: string; + metricScore: number; + summary: string; + summaryStatus: string; + errorReason?: string; + completedItemCount: number; + overThresholdItemCount: number; + underThresholdRate: number; + threshold: number; + weight: number; + customSummary: string; + }>; + aggregateScore: number; + }> { + const evaluation = await MongoEvaluation.findById(evalId).lean(); + if (!evaluation) throw new Error(EvaluationErrEnum.evalTaskNotFound); + + const calculatedData = await this.calculateMetricScores(evaluation); + + const data = evaluation.evaluators.map((evaluator, index) => { + const metricId = evaluator.metric._id.toString(); + const summaryConfig = evaluation.summaryData.summaryConfigs[index]; + + const metricData = calculatedData.metricsData.find((m) => m.metricId === metricId); + const completedItemCount = metricData?.totalCount || 0; + const overThresholdItemCount = metricData?.aboveThresholdCount || 0; + const underThresholdItemCount = metricData?.belowThresholdCount || 0; + const metricScore = metricData?.metricScore || 0; + + const threshold = evaluator.thresholdValue || 0; + const underThresholdRate = + completedItemCount > 0 + ? Math.round((underThresholdItemCount / completedItemCount) * 100) + : 0; + const customSummary = `${underThresholdRate}%(${underThresholdItemCount})${summaryConfig.summary || ''}`; + + return { + metricId: metricId, + metricName: evaluator.metric.name, + metricScore: metricScore, + summary: summaryConfig.summary, + summaryStatus: summaryConfig.summaryStatus, + errorReason: summaryConfig.errorReason, + completedItemCount: completedItemCount, + overThresholdItemCount: overThresholdItemCount, + underThresholdRate: underThresholdRate, + threshold: threshold, + weight: summaryConfig.weight || 0, + customSummary: customSummary + }; + }); + + return { + data, + aggregateScore: calculatedData.aggregateScore + }; + } + + // Pure calculation of metric scores and aggregate score without database updates + static async calculateMetricScores(evaluation: EvaluationSchemaType): Promise<{ + metricsData: Array<{ + metricId: string; + metricName: string; + metricScore: number; + weight: number; + thresholdValue: number; + aboveThresholdCount: number; + belowThresholdCount: number; + totalCount: number; + }>; + aggregateScore: number; + }> { + try { + const evalId = new Types.ObjectId(evaluation._id); + + if (!evaluation.evaluators || !Array.isArray(evaluation.evaluators)) { + addLog.warn('[calculateMetricScores] Evaluation has no evaluators', { + evalId: evaluation._id + }); + return { + metricsData: [], + aggregateScore: 0 + }; + } + + if ( + !evaluation.summaryData || + !evaluation.summaryData.summaryConfigs || + !Array.isArray(evaluation.summaryData.summaryConfigs) + ) { + addLog.warn('[calculateMetricScores] Evaluation has no summary or summaryConfigs', { + evalId: evaluation._id + }); + return { + metricsData: [], + aggregateScore: 0 + }; + } + + // Calculate successful scores and total completed count per metric + const pipeline = [ + { + $match: { + evalId: evalId, + evaluatorOutputs: { $exists: true, $nin: [null, []] } + } + }, + { + $addFields: { + metricResults: { + $map: { + input: '$evaluatorOutputs', + as: 'output', + in: { + metricName: '$$output.metricName', + status: '$$output.status', + score: '$$output.data.score', + hasValidScore: { + $and: [ + { $eq: ['$$output.status', MetricResultStatusEnum.Success] }, + { $ne: ['$$output.data.score', null] } + ] + } + } + } + } + } + }, + { + $unwind: '$metricResults' + }, + { + $group: { + _id: '$metricResults.metricName', + successfulScores: { + $push: { + $cond: ['$metricResults.hasValidScore', '$metricResults.score', '$$REMOVE'] + } + }, + metricName: { $first: '$metricResults.metricName' } + } + }, + { + $addFields: { + avgScore: { + $cond: [ + { $gt: [{ $size: '$successfulScores' }, 0] }, + { $avg: '$successfulScores' }, + 0 + ] + }, + successCount: { $size: '$successfulScores' } + } + } + ]; + + const metricsStats = await MongoEvalItem.aggregate(pipeline as any); + + const processedStats = metricsStats.map((stats) => { + let medianScore = 0; + + if (stats.successfulScores && stats.successfulScores.length > 0) { + const sortedScores = [...stats.successfulScores].sort((a, b) => a - b); + const length = sortedScores.length; + + if (length % 2 === 0) { + const mid1 = sortedScores[length / 2 - 1]; + const mid2 = sortedScores[length / 2]; + medianScore = (mid1 + mid2) / 2; + } else { + medianScore = sortedScores[Math.floor(length / 2)]; + } + } + + return { + ...stats, + medianScore + }; + }); + + const metricsData: Array<{ + metricId: string; + metricName: string; + metricScore: number; + weight: number; + thresholdValue: number; + aboveThresholdCount: number; + belowThresholdCount: number; + totalCount: number; + }> = []; + + let totalWeightedScore = 0; + let totalWeight = 0; + + evaluation.evaluators.forEach((evaluator, index) => { + const metricId = evaluator.metric._id.toString(); + const metricName = evaluator.metric.name; + const stats = processedStats.find((s) => s._id === metricName); + const summaryConfig = evaluation.summaryData.summaryConfigs[index]; + + if (stats) { + let rawScore = + evaluation.summaryData.calculateType === CalculateMethodEnum.median + ? stats.medianScore + : stats.avgScore; + + if (isNaN(rawScore) || rawScore === null || rawScore === undefined) { + rawScore = 0; + } + + const metricScore = Math.round(rawScore * 100) / 100; + + const thresholdValue = evaluator.thresholdValue || 0; + const aboveThresholdCount = stats.successfulScores.filter( + (score: number) => score >= thresholdValue + ).length; + + // Calculate belowThresholdCount from successful scores that are below threshold + const belowThresholdCount = stats.successfulScores.filter( + (score: number) => score < thresholdValue + ).length; + + const weight = summaryConfig.weight; + + metricsData.push({ + metricId: metricId, + metricName: evaluator.metric.name, + metricScore, + weight, + thresholdValue: thresholdValue, + aboveThresholdCount, + belowThresholdCount, + totalCount: stats.successCount + }); + + totalWeightedScore += metricScore * weight; + totalWeight += weight; + } else { + const weight = summaryConfig.weight; + + metricsData.push({ + metricId: metricId, + metricName: evaluator.metric.name, + metricScore: 0, + weight: weight, + thresholdValue: evaluator.thresholdValue || 0, + aboveThresholdCount: 0, + belowThresholdCount: 0, + totalCount: 0 + }); + + totalWeightedScore += 0 * weight; + totalWeight += weight; + } + }); + + let aggregateScore = 0; + if (totalWeight > 0 && !isNaN(totalWeightedScore) && !isNaN(totalWeight)) { + const rawScore = totalWeightedScore / totalWeight; + aggregateScore = isNaN(rawScore) ? 0 : Math.round(rawScore * 100) / 100; + } + + return { + metricsData, + aggregateScore + }; + } catch (error) { + addLog.error('[Evaluation] Real-time calculation failed', { + evalId: evaluation._id.toString(), + error + }); + + const defaultData = evaluation.evaluators.map((evaluator, index) => { + const summaryConfig = evaluation.summaryData.summaryConfigs[index]; + return { + metricId: evaluator.metric._id.toString(), + metricName: evaluator.metric.name, + metricScore: 0, + weight: summaryConfig.weight, + thresholdValue: evaluator.thresholdValue || 0, + aboveThresholdCount: 0, + belowThresholdCount: 0, + totalCount: 0 + }; + }); + + return { + metricsData: defaultData, + aggregateScore: 0 + }; + } + } + + static async updateEvaluationSummaryConfig( + evalId: string, + calculateType: CalculateMethodEnum, + metricsConfig: Array<{ + metricId: string; + thresholdValue: number; + weight?: number; + }> + ): Promise { + addLog.info('[Evaluation] Starting configuration update', { + evalId, + metricsCount: metricsConfig.length + }); + + const evaluation = await MongoEvaluation.findById(evalId).lean(); + if (!evaluation) throw new Error(EvaluationErrEnum.evalTaskNotFound); + + const evalMetricIdSet = new Set( + (evaluation.evaluators || []).map((evaluator: any) => evaluator.metric._id.toString()) + ); + for (const m of metricsConfig) { + if (!evalMetricIdSet.has(m.metricId)) { + throw new Error(EvaluationErrEnum.summaryMetricsConfigError); + } + } + + await mongoSessionRun(async (session: ClientSession) => { + const configMap = new Map(metricsConfig.map((m) => [m.metricId, m])); + + const updatedEvaluators = evaluation.evaluators.map((evaluator: any) => { + const config = configMap.get(evaluator.metric._id.toString()); + return config ? { ...evaluator, thresholdValue: config.thresholdValue } : evaluator; + }); + + const updatedSummaryConfigs = evaluation.summaryData.summaryConfigs.map( + (summaryConfig: any, index: number) => { + const metricId = evaluation.evaluators[index].metric._id.toString(); + const config = configMap.get(metricId); + + if (config) { + return { + ...summaryConfig, + ...(config.weight !== undefined && { weight: config.weight }) + }; + } + return summaryConfig; + } + ); + + await MongoEvaluation.updateOne( + { _id: evalId }, + { + $set: { + 'summaryData.calculateType': calculateType, + evaluators: updatedEvaluators, + 'summaryData.summaryConfigs': updatedSummaryConfigs + } + }, + { session } + ); + + addLog.info('[Evaluation] Configuration updated successfully', { + evalId, + evaluatorsCount: updatedEvaluators.length, + summaryConfigsCount: updatedSummaryConfigs.length + }); + }); + + addLog.info('[Evaluation] Configuration update completed successfully', { + evalId, + metricsCount: metricsConfig.length + }); + } + + static async getEvaluationSummaryConfig(evalId: string): Promise<{ + calculateType: CalculateMethodEnum; + calculateTypeName: string; + metricsConfig: Array<{ + metricId: string; + metricName: string; + metricDescription: string; + thresholdValue: number; + weight: number; + }>; + }> { + const evaluation = await MongoEvaluation.findById(evalId).lean(); + if (!evaluation) throw new Error(EvaluationErrEnum.evalTaskNotFound); + + const calculateType = evaluation.summaryData.calculateType; + const calculateTypeName = CaculateMethodMap[calculateType]?.name || 'Unknown'; + + const metricsConfig = evaluation.evaluators.map((evaluator, index) => { + const summaryConfig = evaluation.summaryData.summaryConfigs[index]; + return { + metricId: evaluator.metric._id.toString(), + metricName: evaluator.metric.name, + metricDescription: evaluator.metric.description || '', + thresholdValue: evaluator.thresholdValue || 0, + weight: summaryConfig.weight + }; + }); + + return { + calculateType, + calculateTypeName, + metricsConfig + }; + } + + // Generate summary reports for multiple metrics using BullMQ queue to prevent status deadlock on system crashes + static async generateSummaryReports(evalId: string, metricIds: string[]): Promise { + try { + const evaluation = await MongoEvaluation.findById(evalId).lean(); + + if (!evaluation) { + throw new Error(EvaluationErrEnum.evalTaskNotFound); + } + + addLog.info( + '[EvaluationSummary] Starting validation and preparation of report generation tasks', + { + evalId, + metricIds, + totalMetrics: metricIds.length + } + ); + + const validMetricIds: string[] = []; + const skippedMetrics: Array<{ + metricId: string; + metricName: string; + reason: string; + }> = []; + + metricIds.forEach((metricId) => { + const evaluatorIndex = evaluation.evaluators.findIndex( + (evaluator: any) => evaluator.metric._id.toString() === metricId + ); + + if (evaluatorIndex === -1) { + addLog.warn('[EvaluationSummary] Metric does not belong to this evaluation task', { + evalId, + metricId + }); + skippedMetrics.push({ + metricId, + metricName: 'Unknown', + reason: 'Metric does not belong to this evaluation task' + }); + return; + } + + validMetricIds.push(metricId); + }); + + if (skippedMetrics.length > 0) { + addLog.info('[EvaluationSummary] Some metrics were skipped', { + evalId, + skippedCount: skippedMetrics.length, + skippedMetrics: skippedMetrics.map((m) => ({ + metricId: m.metricId, + metricName: m.metricName, + reason: m.reason + })) + }); + } + + if (validMetricIds.length === 0) { + if (skippedMetrics.length > 0) { + addLog.info('[EvaluationSummary] All metrics were skipped, no tasks to execute', { + evalId, + totalRequested: metricIds.length, + skippedCount: skippedMetrics.length + }); + return; + } + throw new Error(EvaluationErrEnum.summaryNoValidMetricsFound); + } + + await addSummaryTaskToQueue(evalId, validMetricIds); + + addLog.info('[EvaluationSummary] Task successfully added to queue', { + evalId, + totalRequested: metricIds.length, + validMetricsCount: validMetricIds.length, + skippedCount: skippedMetrics.length + }); + } catch (error) { + addLog.error('[EvaluationSummary] Report generation task creation failed', { + evalId, + metricIds, + error + }); + throw error; + } + } + + static async generateSingleMetricSummary( + evaluation: EvaluationSchemaType, + metricId: string, + evaluatorIndex: number, + evaluator: any, + languageType: LanguageType + ): Promise { + const evalId = evaluation._id.toString(); + + addLog.info('[EvaluationSummary] Starting single metric report generation', { + evalId, + metricId, + metricName: evaluator.metric.name, + languageType + }); + + const { filteredData, totalDataCount } = await this.getFilteredEvaluationData( + evalId, + metricId, + evaluator.thresholdValue || 0, + evaluator + ); + + if (filteredData.length === 0) { + addLog.warn('[EvaluationSummary] No matching data found', { + evalId, + metricId + }); + throw new Error('No matching evaluation data found, cannot generate summary report'); + } + + // Use languageType from job data instead of detecting again + addLog.info('[EvaluationSummary] Using language type from job data', { + evalId, + metricId, + language: languageType + }); + + const modelData = getEvaluationSummaryModel(); + const tokenLimit = getEvaluationSummaryTokenLimit(modelData.name); + const { truncatedData, truncatedCount } = await this.truncateDataByTokens( + filteredData, + tokenLimit, + languageType + ); + + try { + await checkTeamAIPoints(evaluation.teamId); + addLog.info('[EvaluationSummary] Balance check passed, starting LLM call', { + evalId, + metricId, + metricName: evaluator.metric.name + }); + } catch (balanceError) { + addLog.error('[EvaluationSummary] Insufficient balance, cannot generate summary report', { + evalId, + metricId, + metricName: evaluator.metric.name, + error: balanceError + }); + await this.updateSummaryResult( + evalId, + evaluatorIndex, + SummaryStatusEnum.failed, + '', + 'Insufficient balance' + ); + return; + } + + // 4. Call LLM to generate report + const summaryModel = undefined; + const { summary, usage } = await this.callLLMForSummary( + truncatedData, + languageType, + summaryModel + ); + + // undefine will use deafulatevaluation model + const llmModel = undefined; + await this.recordUsage(evaluation, evaluator, usage, llmModel); + + await this.updateSummaryResult(evalId, evaluatorIndex, SummaryStatusEnum.completed, summary); + + (async () => { + addAuditLog({ + tmbId: evaluation.tmbId, + teamId: evaluation.teamId.toString(), + event: AuditEventEnum.GENERATE_EVALUATION_SUMMARY, + params: { + evalName: evaluation.name, + metricName: evaluator.metric.name + } + }); + })(); + + addLog.info('[EvaluationSummary] Single metric report generated successfully', { + evalId, + metricId, + summaryLength: summary.length, + tokensUsed: usage?.total_tokens || 0 + }); + } + + private static async updateSummaryResult( + evalId: string, + evaluatorIndex: number, + status: SummaryStatusEnum, + summary: string, + errorReason?: string + ): Promise { + const updateData: any = { + [`summaryData.summaryConfigs.${evaluatorIndex}.summaryStatus`]: status, + [`summaryData.summaryConfigs.${evaluatorIndex}.summary`]: summary + }; + + if (errorReason) { + updateData[`summaryData.summaryConfigs.${evaluatorIndex}.errorReason`] = errorReason; + } else { + updateData[`summaryData.summaryConfigs.${evaluatorIndex}.errorReason`] = undefined; + } + + await MongoEvaluation.updateOne({ _id: evalId }, { $set: updateData }); + } + + private static async getFilteredEvaluationData( + evalId: string, + metricId: string, + thresholdValue: number, + evaluator: any + ): Promise<{ + filteredData: any[]; + totalDataCount: number; + }> { + try { + const evaluation = await MongoEvaluation.findById(evalId).lean(); + if (!evaluation) { + throw new Error('Evaluation not found'); + } + + // Query evaluation items for specific metric, sorted by threshold then score + const pipeline = [ + { + $match: { + evalId: new Types.ObjectId(evalId), + evaluatorOutputs: { $exists: true, $nin: [null, []] } + } + }, + { + $addFields: { + matchingMetricResult: { + $arrayElemAt: [ + { + $filter: { + input: '$evaluatorOutputs', + as: 'output', + cond: { + $and: [ + { $eq: ['$$output.metricName', evaluator.metric.name] }, + { $eq: ['$$output.status', MetricResultStatusEnum.Success] } + ] + } + } + }, + 0 + ] + } + } + }, + { + $match: { + 'matchingMetricResult.data.score': { $exists: true, $ne: null } + } + }, + { + $addFields: { + score: '$matchingMetricResult.data.score', + isBelowThreshold: { + $lt: ['$matchingMetricResult.data.score', thresholdValue] + } + } + }, + { + $sort: { + isBelowThreshold: -1, + score: 1 + } + }, + { + $project: { + dataItem: 1, + targetOutput: 1, + evaluatorOutputs: 1, + matchingMetricResult: 1, + score: 1, + isBelowThreshold: 1 + } + } + ]; + + const results = await MongoEvalItem.aggregate(pipeline as any); + + addLog.info('[EvaluationSummary] Data query completed', { + evalId, + metricId, + totalCount: results.length, + belowThresholdCount: results.filter((item) => item.isBelowThreshold).length + }); + + return { + filteredData: results, + totalDataCount: results.length + }; + } catch (error) { + addLog.error('[EvaluationSummary] Data query failed', { + evalId, + metricId, + error + }); + throw error; + } + } + + // Truncate data to fit within token limit while selecting optimal items + private static async truncateDataByTokens( + data: any[], + tokenLimit: number, + language: LanguageType + ): Promise<{ + truncatedData: any[]; + truncatedCount: number; + }> { + if (data.length === 0) { + return { truncatedData: [], truncatedCount: 0 }; + } + + try { + const isAllPerfect = this.isAllPerfectScores(data); + const optimizedData = this.selectOptimalDataWithFlag(data, isAllPerfect); + + let selectedTemplate: string; + let selectedExample: string; + + if (language === LanguageType.English) { + selectedTemplate = isAllPerfect ? strengthAnalysisTemplateEn : problemAnalysisTemplateEn; + selectedExample = isAllPerfect ? goodExampleEn : badExampleEn; + } else if (language === LanguageType.TraditionalChinese) { + selectedTemplate = isAllPerfect + ? strengthAnalysisTemplateZhTW + : problemAnalysisTemplateZhTW; + selectedExample = isAllPerfect ? goodExampleZhTW : badExampleZhTW; + } else { + selectedTemplate = isAllPerfect + ? strengthAnalysisTemplateZhCN + : problemAnalysisTemplateZhCN; + selectedExample = isAllPerfect ? goodExampleZhCN : badExampleZhCN; + } + + const baseTemplate = selectedTemplate + .replace('{language}', LanguageDisplayNameMap[language]) + .replace('{example}', selectedExample) + .replace('{evaluation_result_for_single_metric}', ''); + + let currentTokens = await countGptMessagesTokens([ + { + role: ChatCompletionRequestMessageRoleEnum.User, + content: baseTemplate + } + ]); + + const truncatedData: any[] = []; + + for (const item of optimizedData) { + const itemContent = this.formatDataItemForPrompt(item); + const itemTokens = await countGptMessagesTokens([ + { + role: ChatCompletionRequestMessageRoleEnum.User, + content: itemContent + } + ]); + + if (currentTokens + itemTokens > tokenLimit) { + addLog.info('[EvaluationSummary] Token limit reached, stopping data addition', { + currentTokens, + itemTokens, + tokenLimit, + includedItems: truncatedData.length, + totalItems: optimizedData.length + }); + break; + } + + truncatedData.push(item); + currentTokens += itemTokens; + } + + return { + truncatedData, + truncatedCount: truncatedData.length + }; + } catch (error) { + addLog.error('[EvaluationSummary] Token calculation failed', { + error + }); + const optimizedData = this.selectOptimalData(data); + const fallbackCount = Math.ceil(optimizedData.length * 0.5); + return { + truncatedData: optimizedData.slice(0, fallbackCount), + truncatedCount: fallbackCount + }; + } + } + + private static async callLLMForSummary( + data: any[], + language: LanguageType, + llmModel?: string + ): Promise<{ + summary: string; + usage: any; + }> { + try { + const modelData = getEvaluationSummaryModel(llmModel); + const userPrompt = this.buildUserPrompt(data, language); + + const messages: ChatCompletionMessageParam[] = [ + { + role: ChatCompletionRequestMessageRoleEnum.User, + content: userPrompt + } + ]; + + const requestMessages = await loadRequestMessages({ + messages, + useVision: false + }); + + const { response, isStreamResponse } = await createChatCompletion({ + body: { + model: modelData.model, + messages: requestMessages, + temperature: TEMPERATURE_FOR_EVALUATION_SUMMARY, + max_tokens: MAX_TOKEN_FOR_EVALUATION_SUMMARY, + stream: false + }, + modelData, + timeout: 30000 + }); + + if (isStreamResponse) { + throw new Error(EvaluationErrEnum.summaryStreamResponseNotSupported); + } + + const summary = response.choices[0]?.message?.content || '生成总结失败'; + const usage = response.usage; + + return { summary, usage }; + } catch (error) { + addLog.error('[EvaluationSummary] LLM call failed', { + error + }); + throw error; + } + } + + private static async recordUsage( + evaluation: EvaluationSchemaType, + evaluator: any, + usage: any, + llmModel: string | undefined + ): Promise { + if (!usage) return; + + try { + const modelData = getEvaluationSummaryModel(llmModel); + const inputTokens = usage?.prompt_tokens || 0; + const outputTokens = usage?.completion_tokens || 0; + + const { totalPoints } = formatModelChars2Points({ + model: modelData.model, + inputTokens, + outputTokens, + modelType: (modelData.type as `${ModelTypeEnum}`) || ModelTypeEnum.llm + }); + + await createMergedEvaluationUsage({ + evalId: evaluation._id.toString(), + teamId: evaluation.teamId.toString(), + tmbId: evaluation.tmbId.toString(), + usageId: evaluation.usageId, + totalPoints, + type: 'summary', + inputTokens, + outputTokens + }); + + addLog.info('[EvaluationSummary] Usage recorded successfully', { + evalId: evaluation._id.toString(), + metricId: evaluator.metric._id.toString(), + totalTokens: usage.total_tokens, + totalPoints + }); + } catch (error) { + addLog.error('[EvaluationSummary] Usage recording failed', { + evalId: evaluation._id.toString(), + metricId: evaluator.metric._id.toString(), + error + }); + } + } + + private static buildUserPrompt(data: any[], language: LanguageType): string { + const isAllPerfect = this.isAllPerfectScores(data); + + let selectedTemplate: string; + let selectedExample: string; + + if (language === LanguageType.English) { + selectedTemplate = isAllPerfect ? strengthAnalysisTemplateEn : problemAnalysisTemplateEn; + selectedExample = isAllPerfect ? goodExampleEn : badExampleEn; + } else if (language === LanguageType.TraditionalChinese) { + selectedTemplate = isAllPerfect ? strengthAnalysisTemplateZhTW : problemAnalysisTemplateZhTW; + selectedExample = isAllPerfect ? goodExampleZhTW : badExampleZhTW; + } else { + selectedTemplate = isAllPerfect ? strengthAnalysisTemplateZhCN : problemAnalysisTemplateZhCN; + selectedExample = isAllPerfect ? goodExampleZhCN : badExampleZhCN; + } + + const evaluationResult = data.map((item) => this.formatDataItemForPrompt(item)).join('\n\n'); + + return selectedTemplate + .replace('{language}', LanguageDisplayNameMap[language]) + .replace('{example}', selectedExample) + .replace('{evaluation_result_for_single_metric}', evaluationResult); + } + + private static formatDataItemForPrompt(item: any): string { + const reason = item.matchingMetricResult?.data?.reason || 'no content'; + + return ` +eval_reason: ${reason}`; + } + + private static isAllPerfectScores(data: any[]): boolean { + if (data.length === 0) return false; + return data.every((item) => (item.matchingMetricResult?.data?.score || 0) >= PERFECT_SCORE); + } + + private static selectOptimalData(data: any[]): any[] { + if (data.length === 0) return []; + + // Check if all scores are perfect + const isAllPerfect = this.isAllPerfectScores(data); + return this.selectOptimalDataWithFlag(data, isAllPerfect); + } + + private static selectOptimalDataWithFlag(data: any[], isAllPerfect: boolean): any[] { + if (data.length === 0) return []; + + if (isAllPerfect) { + return data; + } else { + // Prioritize non-perfect score data for problem analysis + const nonPerfectData = data.filter( + (item) => (item.matchingMetricResult?.data?.score || 0) < PERFECT_SCORE + ); + return [...nonPerfectData]; + } + } + + // Trigger summary generation when evaluation completes, only for metrics without existing summaries + static async triggerSummaryGeneration(evalId: string): Promise { + try { + const allEvalItemsStatus = await MongoEvalItem.find( + { evalId: new Types.ObjectId(evalId) }, + { status: 1 } + ).lean(); + + const allItemsAbnormal = + allEvalItemsStatus.length > 0 && + allEvalItemsStatus.every((item) => item.status === EvaluationStatusEnum.error); + + if (allItemsAbnormal) { + addLog.warn( + '[Evaluation] All evaluation items have error status, skipping summary generation for all metrics', + { + evalId, + totalItems: allEvalItemsStatus.length + } + ); + return; + } + + const itemsWithSuccessfulOutputs = await MongoEvalItem.countDocuments({ + evalId: new Types.ObjectId(evalId), + evaluatorOutputs: { + $elemMatch: { + status: MetricResultStatusEnum.Success, + 'data.score': { $exists: true, $ne: null } + } + } + }); + + if (itemsWithSuccessfulOutputs === 0) { + return; + } + + const currentEvaluation = await MongoEvaluation.findById(evalId).lean(); + + if (!currentEvaluation?.evaluators || currentEvaluation.evaluators.length === 0) { + return; + } + + const metricsNeedingSummary: string[] = []; + + currentEvaluation.evaluators.forEach((evaluator: any, index: number) => { + const metricId = evaluator.metric._id.toString(); + const summaryConfig = currentEvaluation.summaryData.summaryConfigs[index]; + + if (!summaryConfig?.summary || summaryConfig.summary.trim() === '') { + metricsNeedingSummary.push(metricId); + } + }); + + if (metricsNeedingSummary.length > 0) { + await EvaluationSummaryService.generateSummaryReports(evalId, metricsNeedingSummary); + } else { + addLog.debug( + `[Evaluation] All metrics already have summaries, skipping summary generation: ${evalId}` + ); + } + } catch (error) { + addLog.warn(`[Evaluation] Failed to trigger summary generation: ${evalId}`, { + error + }); + } + } +} diff --git a/packages/service/core/evaluation/summary/queue.ts b/packages/service/core/evaluation/summary/queue.ts new file mode 100644 index 000000000000..c00e3205084c --- /dev/null +++ b/packages/service/core/evaluation/summary/queue.ts @@ -0,0 +1,138 @@ +import { getQueue } from '../../../common/bullmq'; +import { QueueNames } from '../../../common/bullmq'; +import { addLog } from '../../../common/system/log'; +import { SummaryStatusHandler } from './statusHandler'; +import { SummaryStatusEnum } from '@fastgpt/global/core/evaluation/constants'; +import { + createJobCleaner, + type JobCleanupResult, + type JobCleanupOptions +} from '../utils/mq'; +import { type LanguageType, detectEvaluationLanguage } from './util/languageUtil'; + +export type EvaluationSummaryJobData = { + evalId: string; + metricId: string; + languageType: LanguageType; +}; + +export function getEvaluationSummaryQueue() { + return getQueue(QueueNames.evaluationSummary, { + defaultJobOptions: { + removeOnComplete: { + count: 0 + }, + removeOnFail: { + count: 0 + } + } + }); +} + +async function checkActiveSummaryJob(evalId: string, metricId: string): Promise { + try { + const queue = getEvaluationSummaryQueue(); + const activeJobs = await queue.getJobs(['active', 'waiting', 'delayed', 'prioritized']); + const existingJob = activeJobs.find( + (job) => job.data.evalId === evalId && job.data.metricId === metricId + ); + + return !!existingJob; + } catch (error) { + addLog.error('[EvaluationSummary] Failed to check active summary job', { + evalId, + metricId, + error + }); + return false; + } +} + +export async function addSummaryTaskToQueue(evalId: string, metricIds: string[]): Promise { + try { + const queue = getEvaluationSummaryQueue(); + + // Detect language once for the entire evaluation + addLog.info('[EvaluationSummary] Detecting evaluation language', { evalId }); + const { language: languageType } = await detectEvaluationLanguage(evalId); + addLog.info('[EvaluationSummary] Language detected', { evalId, languageType }); + + const addPromises = metricIds.map(async (metricId) => { + const hasActiveJob = await checkActiveSummaryJob(evalId, metricId); + if (hasActiveJob) { + addLog.warn('[EvaluationSummary] Task already in progress, skipping', { + evalId, + metricId + }); + return null; + } + + await SummaryStatusHandler.updateStatus({ + evalId, + metricId, + status: SummaryStatusEnum.pending + }); + + addLog.info('[EvaluationSummary] Adding new task to queue', { + evalId, + metricId, + languageType + }); + + return queue.add( + 'generateSummary', + { + evalId, + metricId, + languageType + }, + { + attempts: 1, + deduplication: { + id: `${evalId}_${metricId}`, + ttl: 5000 + } + } + ); + }); + + const results = await Promise.all(addPromises); + const successfullyAdded = results.filter(Boolean).length; + const skipped = metricIds.length - successfullyAdded; + + addLog.info('[EvaluationSummary] Task successfully added to queue', { + evalId, + totalRequested: metricIds.length, + validMetricsCount: successfullyAdded, + skippedCount: skipped + }); + } catch (error) { + addLog.error('[EvaluationSummary] Failed to add tasks to queue', { + evalId, + metricIds, + error + }); + throw error; + } +} + +export const removeEvaluationSummaryJobs = async ( + evalId: string, + options?: JobCleanupOptions +): Promise => { + const cleaner = createJobCleaner(options); + const queue = getEvaluationSummaryQueue(); + + const filterFn = (job: any) => { + return String(job.data?.evalId) === String(evalId); + }; + + const result = await cleaner.cleanAllJobsByFilter(queue, filterFn, QueueNames.evaluationSummary); + + addLog.debug('Evaluation summary jobs cleanup completed', { + evalId, + result + }); + + return result; +}; diff --git a/packages/service/core/evaluation/summary/statusHandler.ts b/packages/service/core/evaluation/summary/statusHandler.ts new file mode 100644 index 000000000000..efdea92e30bb --- /dev/null +++ b/packages/service/core/evaluation/summary/statusHandler.ts @@ -0,0 +1,120 @@ +import { MongoEvaluation } from '../task/schema'; +import { SummaryStatusEnum } from '@fastgpt/global/core/evaluation/constants'; +import { addLog } from '../../../common/system/log'; +import type { UpdateStatusParams } from '@fastgpt/global/core/evaluation/type'; + +export class SummaryStatusHandler { + static async updateStatus({ + evalId, + metricId, + status, + errorReason + }: UpdateStatusParams): Promise { + try { + const evaluation = await MongoEvaluation.findById(evalId).lean(); + if (!evaluation) { + addLog.warn('[SummaryStatusHandler] Evaluation not found', { evalId, metricId }); + return false; + } + + const evaluatorIndex = evaluation.evaluators.findIndex( + (evaluator: any) => evaluator.metric._id.toString() === metricId + ); + + if (evaluatorIndex === -1) { + addLog.warn('[SummaryStatusHandler] Metric not found in evaluation', { evalId, metricId }); + return false; + } + + const updateObj: Record = { + [`summaryData.summaryConfigs.${evaluatorIndex}.summaryStatus`]: status + }; + + switch (status) { + case SummaryStatusEnum.generating: + updateObj[`summaryData.summaryConfigs.${evaluatorIndex}.errorReason`] = ''; + break; + + case SummaryStatusEnum.completed: + updateObj[`summaryData.summaryConfigs.${evaluatorIndex}.errorReason`] = ''; + break; + + case SummaryStatusEnum.failed: + updateObj[`summaryData.summaryConfigs.${evaluatorIndex}.errorReason`] = + errorReason || 'Unknown error'; + break; + } + + await MongoEvaluation.updateOne({ _id: evalId }, { $set: updateObj }); + + addLog.info('[SummaryStatusHandler] Status updated successfully', { + evalId, + metricId, + status, + errorReason, + evaluatorIndex + }); + + return true; + } catch (error) { + addLog.error('[SummaryStatusHandler] Failed to update status', { + evalId, + metricId, + status, + error + }); + return false; + } + } + + static async batchUpdateStatus( + evalId: string, + updates: Array<{ + metricId: string; + status: SummaryStatusEnum; + errorReason?: string; + }> + ): Promise { + const results = await Promise.allSettled( + updates.map((update) => + this.updateStatus({ + evalId, + metricId: update.metricId, + status: update.status, + errorReason: update.errorReason + }) + ) + ); + + return results.map((result) => result.status === 'fulfilled' && result.value); + } + + static async getStatus(evalId: string, metricId: string): Promise { + try { + const evaluation = await MongoEvaluation.findById(evalId).lean(); + if (!evaluation) { + return null; + } + + const evaluatorIndex = evaluation.evaluators.findIndex( + (evaluator: any) => evaluator.metric._id.toString() === metricId + ); + + if (evaluatorIndex === -1) { + return null; + } + + return ( + evaluation.summaryData?.summaryConfigs?.[evaluatorIndex]?.summaryStatus || + SummaryStatusEnum.pending + ); + } catch (error) { + addLog.error('[SummaryStatusHandler] Failed to get status', { + evalId, + metricId, + error + }); + return null; + } + } +} diff --git a/packages/service/core/evaluation/summary/util/languageUtil.ts b/packages/service/core/evaluation/summary/util/languageUtil.ts new file mode 100644 index 000000000000..9e81a529095f --- /dev/null +++ b/packages/service/core/evaluation/summary/util/languageUtil.ts @@ -0,0 +1,107 @@ +import { MongoEvalItem } from '../../task/schema'; +import { Types } from '../../../../common/mongo'; +import { addLog } from '../../../../common/system/log'; +import { franc } from 'franc'; +import { tify } from 'chinese-conv'; + +export enum LanguageType { + English = 'en', + SimplifiedChinese = 'zh-CN', + TraditionalChinese = 'zh-TW' +} + +// Language type to display name mapping +export const LanguageDisplayNameMap: Record = { + [LanguageType.English]: 'English', + [LanguageType.SimplifiedChinese]: '简体中文', + [LanguageType.TraditionalChinese]: '繁體中文' +}; + +interface LanguageDetectionResult { + language: LanguageType; +} + +function detectLanguageFromText(text: string): 'en' | 'zh-cn' | 'zh-tw' { + const content = text || ''; + const lang = franc(content); + addLog.debug('[LanguageUtil] franc detected language', { lang }); + + if (lang === 'eng') { + return 'en'; + } + + // detect result is cmn or has chinese characters + if (lang === 'cmn' || /[\u4e00-\u9fff]/.test(content)) { + const hant = tify(content); + return content === hant ? 'zh-tw' : 'zh-cn'; + } + + return 'en'; +} + +/** + * Convert language code to LanguageType enum + */ +function languageCodeToType(code: 'en' | 'zh-cn' | 'zh-tw'): LanguageType { + switch (code) { + case 'en': + return LanguageType.English; + case 'zh-cn': + return LanguageType.SimplifiedChinese; + case 'zh-tw': + return LanguageType.TraditionalChinese; + default: + return LanguageType.SimplifiedChinese; + } +} + +// Detect primary language of evaluation user inputs +export async function detectEvaluationLanguage(evalId: string): Promise { + try { + addLog.info('[LanguageUtil] Starting language detection', { evalId }); + + const evalItems = await MongoEvalItem.find( + { evalId: new Types.ObjectId(evalId) }, + { 'dataItem.userInput': 1 } + ).lean(); + + if (!evalItems || evalItems.length === 0) { + addLog.warn('[LanguageUtil] No evaluation items found', { evalId }); + return { + language: LanguageType.SimplifiedChinese + }; + } + + const userInputTexts = evalItems + .map((item) => item.dataItem?.userInput || '') + .filter((input) => input.trim().length > 0); + + if (userInputTexts.length === 0) { + addLog.warn('[LanguageUtil] No user input text found', { evalId }); + return { + language: LanguageType.SimplifiedChinese + }; + } + + const combinedUserInput = userInputTexts.join('\n'); + const detectedLanguageCode = detectLanguageFromText(combinedUserInput); + const language = languageCodeToType(detectedLanguageCode); + + addLog.info('[LanguageUtil] Language detection completed', { + evalId, + language + }); + + return { + language + }; + } catch (error) { + addLog.error('[LanguageUtil] Language detection failed', { + evalId, + error + }); + return { + language: LanguageType.SimplifiedChinese + }; + } +} diff --git a/packages/service/core/evaluation/summary/util/weightCalculator.ts b/packages/service/core/evaluation/summary/util/weightCalculator.ts new file mode 100644 index 000000000000..68a6b1075c79 --- /dev/null +++ b/packages/service/core/evaluation/summary/util/weightCalculator.ts @@ -0,0 +1,90 @@ +import { SummaryStatusEnum } from '@fastgpt/global/core/evaluation/constants'; +import type { + EvaluatorSchema, + EvaluationParamsType, + EvaluationParamsWithDeafultConfigType +} from '@fastgpt/global/core/evaluation/type'; +import { addLog } from '../../../../common/system/log'; +import { CalculateMethodEnum } from '@fastgpt/global/core/evaluation/constants'; + +export function calculateMetricWeights(metricCount: number): number[] { + if (metricCount <= 0) { + return []; + } + + if (metricCount === 1) { + return [100]; + } + + const baseWeight = Math.floor(100 / metricCount); + const remainder = 100 - baseWeight * metricCount; + const weights: number[] = new Array(metricCount).fill(baseWeight); + weights[metricCount - 1] += remainder; + + return weights; +} + +function getDefaultThreshold(): number { + const threshold = global.systemEnv?.evalConfig?.caseResultThreshold || 0.8; + if (isNaN(threshold) || threshold < 0 || threshold > 1) { + addLog.warn( + `[getDefaultThreshold] Invalid caseResultThreshold value: ${threshold}. Using default: 0.8` + ); + return 0.8; + } + return threshold; +} + +// Build evaluation configuration with cleaned evaluators and separate summaryConfigs +export function buildEvalDataConfig( + evaluationParams: EvaluationParamsType +): EvaluationParamsWithDeafultConfigType { + const evaluators = evaluationParams?.evaluators || []; + + if (!evaluators || evaluators.length === 0) { + return { + ...evaluationParams, + evaluators: [], + summaryData: { + calculateType: CalculateMethodEnum.mean, + summaryConfigs: [] + } + }; + } + + const weights = calculateMetricWeights(evaluators.length); + const caseResultThreshold = getDefaultThreshold(); + + const cleanedEvaluators: EvaluatorSchema[] = evaluators.map((evaluator) => ({ + metric: evaluator.metric, + runtimeConfig: evaluator.runtimeConfig, + thresholdValue: evaluator.thresholdValue ?? caseResultThreshold + })); + + const summaryConfigs = evaluators.map((evaluator, index) => ({ + metricId: evaluator.metric._id.toString(), + metricName: evaluator.metric.name, + weight: weights[index], + summary: '', + summaryStatus: SummaryStatusEnum.pending, + errorReason: '' + })); + + addLog.debug('[buildEvalDataConfig] Processed configuration:', { + evaluators: cleanedEvaluators.map((evaluator, index) => ({ + metricId: evaluator.metric._id, + metricName: evaluator.metric.name, + thresholdValue: evaluator.thresholdValue, + summaryConfig: summaryConfigs[index] + })) + }); + + return { + ...evaluationParams, + evaluators: cleanedEvaluators, + summaryData: { + calculateType: CalculateMethodEnum.mean, + summaryConfigs + } + }; +} diff --git a/packages/service/core/evaluation/summary/worker.ts b/packages/service/core/evaluation/summary/worker.ts new file mode 100644 index 000000000000..4310536c7fdc --- /dev/null +++ b/packages/service/core/evaluation/summary/worker.ts @@ -0,0 +1,162 @@ +import { getWorker, QueueNames } from '../../../common/bullmq'; +import { addLog } from '../../../common/system/log'; +import { MongoEvaluation } from '../task/schema'; +import { SummaryStatusEnum } from '@fastgpt/global/core/evaluation/constants'; +import { getErrText } from '@fastgpt/global/common/error/utils'; +import { type EvaluationSummaryJobData, getEvaluationSummaryQueue } from './queue'; +import { EvaluationSummaryService } from './index'; +import { SummaryStatusHandler } from './statusHandler'; + +export function initEvaluationSummaryWorker() { + const worker = getWorker( + QueueNames.evaluationSummary, + async (job) => { + const { evalId, metricId, languageType } = job.data; + + addLog.info('[EvaluationSummary] Worker processing single metric task', { + jobId: job.id, + evalId, + metricId, + languageType + }); + + try { + const evaluation = await MongoEvaluation.findById(evalId).lean(); + if (!evaluation) { + throw new Error(`Evaluation task not found: ${evalId}`); + } + + const evaluatorIndex = evaluation.evaluators.findIndex( + (evaluator: any) => evaluator.metric._id.toString() === metricId + ); + + if (evaluatorIndex === -1) { + throw new Error(`Metric ${metricId} does not belong to evaluation ${evalId}`); + } + + await EvaluationSummaryService.generateSingleMetricSummary( + evaluation, + metricId, + evaluatorIndex, + evaluation.evaluators[evaluatorIndex], + languageType + ); + + addLog.info('[EvaluationSummary] Worker task completed successfully', { + jobId: job.id, + evalId, + metricId + }); + } catch (error) { + addLog.error('[EvaluationSummary] Worker task failed', { + jobId: job.id, + evalId, + metricId, + error + }); + throw error; + } + }, + { + stalledInterval: 30000, + maxStalledCount: 3 + } + ); + + worker.on('active', async (job) => { + if (job?.data) { + const { evalId, metricId } = job.data; + + addLog.info('[EvaluationSummary] Task started', { + jobId: job.id, + evalId, + metricId + }); + + await SummaryStatusHandler.updateStatus({ + evalId, + metricId, + status: SummaryStatusEnum.generating + }); + } + }); + + worker.on('completed', async (job) => { + if (job?.data) { + const { evalId, metricId } = job.data; + + addLog.info('[EvaluationSummary] Task completed', { + jobId: job.id, + evalId, + metricId + }); + + await SummaryStatusHandler.updateStatus({ + evalId, + metricId, + status: SummaryStatusEnum.completed + }); + } + }); + + worker.on('stalled', async (jobId: string) => { + try { + const summaryQueue = getEvaluationSummaryQueue(); + const job = await summaryQueue.getJob(jobId); + + if (job?.data) { + const { evalId, metricId } = job.data; + + addLog.warn('[EvaluationSummary] Task job stalled, will be retried', { + jobId, + evalId, + metricId + }); + + await SummaryStatusHandler.updateStatus({ + evalId, + metricId, + status: SummaryStatusEnum.pending + }); + } else { + addLog.warn( + '[EvaluationSummary] Task job stalled, will be retried (could not get job data)', + { + jobId + } + ); + } + } catch (error) { + addLog.warn( + '[EvaluationSummary] Task job stalled, will be retried (could not get job data)', + { + jobId, + error + } + ); + } + }); + + worker.on('failed', async (job, error) => { + if (job?.data) { + const { evalId, metricId } = job.data; + + addLog.warn('[EvaluationSummary] Task failed', { + jobId: job.id, + evalId, + metricId, + error: error.message + }); + + await SummaryStatusHandler.updateStatus({ + evalId, + metricId, + status: SummaryStatusEnum.failed, + errorReason: getErrText(error) + }); + } + }); + + addLog.info('[EvaluationSummary] Worker created successfully'); + return worker; +} diff --git a/packages/service/core/evaluation/synthesizer/ditingSynthesisClient.ts b/packages/service/core/evaluation/synthesizer/ditingSynthesisClient.ts new file mode 100644 index 000000000000..b1cf4d92003e --- /dev/null +++ b/packages/service/core/evaluation/synthesizer/ditingSynthesisClient.ts @@ -0,0 +1,51 @@ +import type { + HttpConfig, + DatasetSynthesisRequest, + DatasetSynthesisResponse +} from '@fastgpt/global/core/evaluation/metric/type'; + +function loadHttpConfigFromEnv(): HttpConfig { + return { + url: process.env.DITING_BASE_URL || 'http://diting:3000', + timeout: Number(process.env.DITING_TIMEOUT) || 300000 + }; +} + +export function createDitingSynthesisClient(config: HttpConfig = loadHttpConfigFromEnv()) { + function getFullUrl(path: string) { + return config.url.replace(/\/$/, '') + path; + } + + return { + async runSynthesis(request: DatasetSynthesisRequest): Promise { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), config.timeout || 30000); + + try { + const res = await fetch(getFullUrl('/api/v1/dataset-synthesis/runs'), { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(request), + signal: controller.signal + }); + + clearTimeout(timeoutId); + + if (!res.ok) { + const errorText = await res.text().catch(() => res.statusText); + throw new Error(`HTTP ${res.status}: ${errorText}`); + } + + return (await res.json()) as DatasetSynthesisResponse; + } catch (err: any) { + clearTimeout(timeoutId); + if (err.name === 'AbortError') { + throw new Error('Synthesis request timeout'); + } + throw err; + } + } + }; +} diff --git a/packages/service/core/evaluation/synthesizer/index.ts b/packages/service/core/evaluation/synthesizer/index.ts new file mode 100644 index 000000000000..d74a2d0c5366 --- /dev/null +++ b/packages/service/core/evaluation/synthesizer/index.ts @@ -0,0 +1,103 @@ +import type { + SynthesisCase, + SynthesizerConfig, + DatasetSynthesisResponse, + EvalModelConfigType, + SynthesisResult +} from '@fastgpt/global/core/evaluation/metric/type'; +import { getLLMModel, getEmbeddingModel } from '../../ai/model'; +import { createDitingSynthesisClient } from './ditingSynthesisClient'; + +export abstract class Synthesizer { + protected synthesizerConfig: SynthesizerConfig; + protected llmConfig?: EvalModelConfigType; + protected embeddingConfig?: EvalModelConfigType; + + constructor( + synthesizerConfig: SynthesizerConfig, + llmConfig?: EvalModelConfigType, + embeddingConfig?: EvalModelConfigType + ) { + this.llmConfig = llmConfig; + this.embeddingConfig = embeddingConfig; + this.synthesizerConfig = synthesizerConfig; + } + + abstract synthesize(synthesisCase: SynthesisCase): Promise; +} + +export class DitingSynthesizer extends Synthesizer { + private client: ReturnType; + + constructor( + synthesizerConfig: SynthesizerConfig, + llmConfig?: EvalModelConfigType, + embeddingConfig?: EvalModelConfigType + ) { + super(synthesizerConfig, llmConfig, embeddingConfig); + this.client = createDitingSynthesisClient(); + } + + async synthesize(synthesisCase: SynthesisCase): Promise { + const response: DatasetSynthesisResponse = await this.client.runSynthesis({ + inputData: { + context: synthesisCase.context, + themes: synthesisCase.themes + }, + synthesizerConfig: this.synthesizerConfig, + llmConfig: this.llmConfig!, + embeddingConfig: this.embeddingConfig + }); + + if (response.status === 'success' && response.data) { + return { + synthesisName: this.synthesizerConfig.synthesizerName, + status: response.status, + data: response.data, + usages: response.usages, + error: response.error, + totalPoints: 0 + }; + } else { + throw new Error(response.error || 'Synthesis failed'); + } + } +} + +export function createSynthesizerInstance( + synthesizerName: string, + llmConfig?: EvalModelConfigType, + embeddingConfig?: EvalModelConfigType +): Synthesizer { + const synthesizerConfig: SynthesizerConfig = { + synthesizerName: synthesizerName + }; + + if (llmConfig?.name) { + try { + const llm = getLLMModel(llmConfig.name); + llmConfig = { + ...llmConfig, + baseUrl: llm.requestUrl || '', + apiKey: llm.requestAuth || '' + }; + } catch (err) { + throw new Error(`Get LLM model failed: ${(err as Error).message}`); + } + } + + if (embeddingConfig?.name) { + try { + const embedding = getEmbeddingModel(embeddingConfig.name); + embeddingConfig = { + ...embeddingConfig, + baseUrl: embedding.requestUrl || '', + apiKey: embedding.requestAuth || '' + }; + } catch (err) { + throw new Error(`Get embedding model failed: ${(err as Error).message}`); + } + } + + return new DitingSynthesizer(synthesizerConfig, llmConfig, embeddingConfig); +} diff --git a/packages/service/core/evaluation/target/index.ts b/packages/service/core/evaluation/target/index.ts new file mode 100644 index 000000000000..b09aef446d13 --- /dev/null +++ b/packages/service/core/evaluation/target/index.ts @@ -0,0 +1,379 @@ +import type { + TargetInput, + TargetOutput, + WorkflowConfig, + EvalTarget +} from '@fastgpt/global/core/evaluation/type'; +import { + Validatable, + ValidationResultUtils, + type ValidationResult, + type ValidationError +} from '@fastgpt/global/core/evaluation/validate'; +import { dispatchWorkFlow } from '../../workflow/dispatch'; +import { getAppVersionById } from '../../app/version/controller'; +import { MongoApp } from '../../app/schema'; +import { + getWorkflowEntryNodeIds, + storeEdges2RuntimeEdges, + storeNodes2RuntimeNodes +} from '@fastgpt/global/core/workflow/runtime/utils'; +import { getNanoid } from '@fastgpt/global/common/string/tools'; +import { + ChatItemValueTypeEnum, + ChatRoleEnum, + ChatSourceEnum +} from '@fastgpt/global/core/chat/constants'; +import type { + UserChatItemValueItemType, + UserChatItemType, + AIChatItemType +} from '@fastgpt/global/core/chat/type'; +import { WORKFLOW_MAX_RUN_TIMES } from '../../workflow/constants'; +import { getUserChatInfoAndAuthTeamPoints } from '../../../support/permission/auth/team'; +import { getRunningUserInfoByTmbId } from '../../../support/user/team/utils'; +import { removeDatasetCiteText } from '../../ai/utils'; +import { saveChat } from '../../chat/saveChat'; +import { MongoChatItem } from '../../chat/chatItemSchema'; +import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils'; +import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants'; +import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; +import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type'; +import { EvaluationErrEnum } from '@fastgpt/global/common/error/code/evaluation'; + +// Helper function to extract retrieval context from workflow results +function extractRetrievalContext(flowResponses: ChatHistoryItemResType[]): string[] { + const retrievalContext: string[] = []; + + // Find all datasetSearchNode responses + const datasetSearchResponses = flowResponses.filter( + (response) => response.moduleType === FlowNodeTypeEnum.datasetSearchNode + ); + + // Extract quoteList from each datasetSearchNode response + for (const response of datasetSearchResponses) { + if (response.quoteList && Array.isArray(response.quoteList)) { + for (const quote of response.quoteList) { + // Extract the content from q and a fields and combine them + const content = [quote.q, quote.a].filter(Boolean).join('\n').trim(); + if (content) { + retrievalContext.push(content); + } + } + } + } + + return retrievalContext; +} + +// Evaluation target base class +export abstract class EvaluationTarget extends Validatable { + abstract execute(input: TargetInput): Promise; + // validate() method is inherited from Validatable base class +} + +// Workflow target implementation +export class WorkflowTarget extends EvaluationTarget { + private config: WorkflowConfig; + + constructor(config: WorkflowConfig) { + super(); + this.config = config; + } + + async execute(input: TargetInput): Promise { + // Get application information + const appData = await MongoApp.findById(this.config.appId); + if (!appData) { + throw new Error(EvaluationErrEnum.evalAppNotFound); + } + + // Get user information and permissions + const [{ timezone, externalProvider }, { nodes, edges, chatConfig }] = await Promise.all([ + getUserChatInfoAndAuthTeamPoints(appData.tmbId), + getAppVersionById({ + appId: String(appData._id), + versionId: this.config.versionId, + app: appData + }) + ]); + + // Construct query + const query: UserChatItemValueItemType[] = [ + { + type: ChatItemValueTypeEnum.text, + text: { + content: input.userInput + } + } + ]; + + // Construct conversation history based on input.context + const histories: (UserChatItemType | AIChatItemType)[] = []; + + // Add context as background knowledge in conversation history + if (input.context && input.context.length > 0) { + const contextText = input.context + .filter((item) => item && item.trim()) + .map((item, index) => `${index + 1}. ${item}`) + .join('\n'); + + if (contextText) { + histories.push({ + obj: ChatRoleEnum.Human, + value: [ + { + type: ChatItemValueTypeEnum.text, + text: { + content: `请参考以下背景知识回答问题:\n${contextText}` + } + } + ] + }); + } + } + + const chatId = getNanoid(); + + // Execute workflow + const { assistantResponses, flowUsages, flowResponses, system_memories, durationSeconds } = + await dispatchWorkFlow({ + chatId, + timezone, + externalProvider, + mode: 'chat', + runningAppInfo: { + id: String(appData._id), + teamId: String(appData.teamId), + tmbId: String(appData.tmbId) + }, + runningUserInfo: await getRunningUserInfoByTmbId(appData.tmbId), + uid: String(appData.tmbId), + runtimeNodes: storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes)), + runtimeEdges: storeEdges2RuntimeEdges(edges), + variables: input.targetCallParams?.variables || {}, + query, + chatConfig: { ...chatConfig, ...(this.config.chatConfig || {}) }, + histories, + stream: false, + maxRunTimes: WORKFLOW_MAX_RUN_TIMES + }); + + let response = (() => { + return assistantResponses + .map((item) => item?.text?.content) + .filter(Boolean) + .join('\n'); + })(); + + // Format response content + response = removeDatasetCiteText(response.trim(), false); + + // Construct user question object + const userQuestion: UserChatItemType = { + obj: ChatRoleEnum.Human, + value: query + }; + + // Construct AI response object + const aiResponse: AIChatItemType = { + obj: ChatRoleEnum.AI, + value: assistantResponses, + memories: system_memories, + [DispatchNodeResponseKeyEnum.nodeResponse]: flowResponses + }; + + // Save chat record + await saveChat({ + chatId, + appId: appData._id, + teamId: appData.teamId, + tmbId: appData.tmbId, + nodes, + appChatConfig: { ...chatConfig, ...(this.config.chatConfig || {}) }, + variables: input.targetCallParams?.variables || {}, + isUpdateUseTime: false, + newTitle: getChatTitleFromChatMessage(userQuestion), + source: ChatSourceEnum.evaluation, + content: [userQuestion, aiResponse], + durationSeconds + }); + + // Get the latest AI chat item dataId from the saved chat record + const latestAiChatItem = await MongoChatItem.findOne( + { + chatId, + appId: appData._id, + obj: ChatRoleEnum.AI + }, + 'dataId' + ) + .sort({ _id: -1 }) + .lean(); + + const aiChatItemDataId = latestAiChatItem?.dataId || ''; + + return { + actualOutput: response, + retrievalContext: extractRetrievalContext(flowResponses), + usage: flowUsages, + responseTime: durationSeconds, + chatId, + aiChatItemDataId + }; + } + + async validate(): Promise { + const errors: ValidationError[] = []; + const warnings: ValidationError[] = []; + + try { + // Validate basic configuration + if (!this.config.appId) { + errors.push({ + code: EvaluationErrEnum.evalTargetAppIdMissing, + message: 'App ID is required for workflow target', + field: 'config.appId' + }); + return { isValid: false, errors, warnings }; + } + + if (!this.config.versionId) { + errors.push({ + code: EvaluationErrEnum.evalTargetVersionIdMissing, + message: 'Version ID is required for workflow target', + field: 'config.versionId' + }); + return { isValid: false, errors, warnings }; + } + + // Validate app existence and accessibility + const appData = await MongoApp.findById(this.config.appId); + if (!appData) { + errors.push({ + code: EvaluationErrEnum.evalAppNotFound, + message: `App with ID '${this.config.appId}' not found or not accessible`, + field: 'config.appId', + debugInfo: { appId: this.config.appId } + }); + return { isValid: false, errors, warnings }; + } + + // Validate that the version exists and is accessible + try { + const versionData = await getAppVersionById({ + appId: String(appData._id), + versionId: this.config.versionId, + app: appData + }); + + // If versionId was specified but the returned version doesn't match (fell back to latest), + // it means the specified version doesn't exist + if (String(versionData.versionId).trim() !== String(this.config.versionId).trim()) { + errors.push({ + code: EvaluationErrEnum.evalAppVersionNotFound, + message: `App version '${this.config.versionId}' not found for app '${appData.name}'`, + field: 'config.versionId', + debugInfo: { + appId: this.config.appId, + appName: appData.name, + requestedVersion: this.config.versionId, + availableVersion: versionData.versionId + } + }); + } + } catch (versionError) { + errors.push({ + code: EvaluationErrEnum.evalAppVersionNotFound, + message: `Failed to validate app version: ${versionError instanceof Error ? versionError.message : String(versionError)}`, + field: 'config.versionId', + debugInfo: { + appId: this.config.appId, + requestedVersion: this.config.versionId, + error: versionError instanceof Error ? versionError.message : String(versionError) + } + }); + } + + // Validate chat config if present + if (this.config.chatConfig) { + // Add basic validation for chat config structure + if (typeof this.config.chatConfig !== 'object') { + errors.push({ + code: EvaluationErrEnum.evalTargetInvalidConfig, + message: 'Chat config must be an object', + field: 'config.chatConfig', + debugInfo: { chatConfigType: typeof this.config.chatConfig } + }); + } + } + + return { + isValid: errors.length === 0, + errors, + warnings: warnings.length > 0 ? warnings : undefined + }; + } catch (error) { + errors.push({ + code: EvaluationErrEnum.evalTargetConfigInvalid, + message: `Validation failed: ${error instanceof Error ? error.message : String(error)}`, + debugInfo: { + error: error instanceof Error ? error.message : String(error), + stack: error instanceof Error ? error.stack : undefined + } + }); + + return { isValid: false, errors, warnings }; + } + } +} + +// Target factory - creates and validates instance in one step +export async function createTargetInstance( + targetConfig: EvalTarget, + options: { validate?: boolean } = { validate: true } +): Promise { + let targetInstance: EvaluationTarget; + + switch (targetConfig.type) { + case 'workflow': + targetInstance = new WorkflowTarget(targetConfig.config as WorkflowConfig); + break; + default: + throw new Error(EvaluationErrEnum.evalUnsupportedTargetType); + } + + // Validate instance if requested (default behavior) + if (options.validate) { + const validationResult = await targetInstance.validate(); + if (!validationResult.isValid) { + throw ValidationResultUtils.toError(validationResult); + } + } + + return targetInstance; +} + +// Utility function - test the validity of target configuration +export async function validateTargetConfig(targetConfig: EvalTarget): Promise { + try { + const targetInstance = await createTargetInstance(targetConfig, { validate: false }); + return await targetInstance.validate(); + } catch (error) { + // If we can't even create the instance, return validation error + return { + isValid: false, + errors: [ + { + code: EvaluationErrEnum.evalTargetConfigInvalid, + message: `Failed to create target instance: ${error instanceof Error ? error.message : String(error)}`, + debugInfo: { + error: error instanceof Error ? error.message : String(error), + stack: error instanceof Error ? error.stack : undefined, + targetType: targetConfig.type + } + } + ] + }; + } +} diff --git a/packages/service/core/evaluation/task/errors.ts b/packages/service/core/evaluation/task/errors.ts new file mode 100644 index 000000000000..a94cb4660ebe --- /dev/null +++ b/packages/service/core/evaluation/task/errors.ts @@ -0,0 +1,141 @@ +import { UnrecoverableError } from 'bullmq'; +import { addLog } from '../../../common/system/log'; +import { TeamErrEnum } from '@fastgpt/global/common/error/code/team'; +import { EvaluationErrEnum } from '@fastgpt/global/common/error/code/evaluation'; +import { getErrText } from '@fastgpt/global/common/error/utils'; + +/** + * Unrecoverable evaluation error that prevents BullMQ auto-retry + */ +export class EvaluationUnrecoverableError extends UnrecoverableError { + constructor( + message: string, + public readonly stage: string + ) { + super(message); + this.name = 'EvaluationUnrecoverableError'; + } +} + +/** + * Retryable evaluation error that allows BullMQ auto-retry + */ +export class EvaluationRetryableError extends Error { + constructor( + message: string, + public readonly stage: string + ) { + super(message); + this.name = 'EvaluationRetryableError'; + } +} + +/** + * Error analysis result interface + */ +export interface ErrorAnalysisResult { + isRetriable: boolean; + category?: string; + pattern?: string; +} + +/** + * Simplified error analysis function for retry logic + */ +export const analyzeError = (error: any): ErrorAnalysisResult => { + const errorStr = error?.message || error?.code || String(error); + const lowerErrorStr = errorStr.toLowerCase(); + + // Check network-related errors + const networkErrors = [ + 'NETWORK_ERROR', + 'ECONNRESET', + 'ENOTFOUND', + 'ECONNREFUSED', + 'socket hang up', + 'timeout' + ]; + if (networkErrors.some((pattern) => lowerErrorStr.includes(pattern.toLowerCase()))) { + return { isRetriable: true, category: 'network' }; + } + + // Check HTTP status codes + const httpStatusMatch = errorStr.match(/\b(4\d{2}|5\d{2})\b/); + if (httpStatusMatch) { + const statusCode = httpStatusMatch[1]; + // 429 (Too Many Requests) and 5xx errors are retryable + if (statusCode === '429' || statusCode.startsWith('5')) { + return { + isRetriable: true, + category: statusCode.startsWith('5') ? 'serverError' : 'rateLimit' + }; + } + } + + return { isRetriable: false }; +}; + +/** + * Evaluation error context interface + */ +export interface EvaluationErrorContext { + evalId?: string; + evalItemId?: string; + resourceName?: string; +} + +/** + * Create appropriate BullMQ error type for auto-retry handling + */ +export const createEvaluationError = ( + error: any, + stage: string, + context?: EvaluationErrorContext, + forceRetry?: boolean +): Error => { + const errorStr = error?.message || error?.code || String(error); + + // Use getErrText for all error types for consistent translation + const errorMessage = getErrText(error); + + // Build detailed error context + const logContext = { + stage, + error: errorStr, + originalError: error, + ...context + }; + + // Force retry for specific stages + if (forceRetry) { + addLog.warn(`[Evaluation] Force retryable error in stage ${stage}`, logContext); + return new EvaluationRetryableError(errorMessage, stage); + } + + // Unrecoverable error types + if ( + error === TeamErrEnum.aiPointsNotEnough || + error === EvaluationErrEnum.evalItemNotFound || + error === EvaluationErrEnum.evalTaskNotFound || + error == EvaluationErrEnum.evalDatasetLoadFailed || + error == EvaluationErrEnum.evalEvaluatorsConfigInvalid || + error == EvaluationErrEnum.evalTargetConfigInvalid + ) { + addLog.error(`[Evaluation] Unrecoverable error in stage ${stage}`, logContext); + return new EvaluationUnrecoverableError(errorMessage, stage); + } + + // Use existing error analysis logic to determine retry capability + const { isRetriable, category } = analyzeError(error); + + if (isRetriable) { + addLog.warn( + `[Evaluation] Retryable error in stage ${stage} (category: ${category})`, + logContext + ); + return new EvaluationRetryableError(errorMessage, stage); + } else { + addLog.error(`[Evaluation] Non-retryable error in stage ${stage}`, logContext); + return new EvaluationUnrecoverableError(errorMessage, stage); + } +}; diff --git a/packages/service/core/evaluation/task/index.ts b/packages/service/core/evaluation/task/index.ts new file mode 100644 index 000000000000..9183c44f9037 --- /dev/null +++ b/packages/service/core/evaluation/task/index.ts @@ -0,0 +1,1211 @@ +import { MongoEvaluation, MongoEvalItem } from './schema'; +import { MongoEvalDatasetData } from '../dataset/evalDatasetDataSchema'; +import type { + EvaluationSchemaType, + EvaluationItemSchemaType, + CreateEvaluationParams, + EvaluationItemDisplayType, + TargetCallParams, + EvaluationDisplayType +} from '@fastgpt/global/core/evaluation/type'; +import { Types } from 'mongoose'; +import { EvaluationStatusEnum } from '@fastgpt/global/core/evaluation/constants'; +import { + removeEvaluationItemJobs, + removeEvaluationItemJobsByItemId, + addEvaluationItemJob, + evaluationItemQueue, + checkEvaluationItemQueueHealth +} from './mq'; +import { createEvaluationUsage } from '../../../support/wallet/usage/controller'; +import { addLog } from '../../../common/system/log'; +import { buildEvalDataConfig } from '../summary/util/weightCalculator'; +import { EvaluationErrEnum } from '@fastgpt/global/common/error/code/evaluation'; +import { mongoSessionRun } from '../../../common/mongo/sessionRun'; +import { type ClientSession } from '../../../common/mongo'; +import { getEvaluationTaskStatus, getEvaluationTaskStats } from './statusCalculator'; +import { EvaluationSummaryService } from '../summary'; +import { removeEvaluationSummaryJobs } from '../summary/queue'; +import { enqueueEvaluationItems } from './processor'; + +export class EvaluationTaskService { + /** + * Build evaluator failure checks for MongoDB aggregation + */ + private static buildEvaluatorFailChecks(evaluators: any[]) { + return evaluators.map((evaluator, index) => { + const threshold = evaluator.thresholdValue || 0.8; + return { + $or: [ + { + $eq: [ + { + $let: { + vars: { + evaluatorOutput: { $arrayElemAt: ['$evaluatorOutputs', index] } + }, + in: '$$evaluatorOutput.data.score' + } + }, + null + ] + }, + { + $eq: [ + { + $type: { + $let: { + vars: { + evaluatorOutput: { $arrayElemAt: ['$evaluatorOutputs', index] } + }, + in: '$$evaluatorOutput.data.score' + } + } + }, + 'missing' + ] + }, + { + $lt: [ + { + $let: { + vars: { + evaluatorOutput: { $arrayElemAt: ['$evaluatorOutputs', index] } + }, + in: '$$evaluatorOutput.data.score' + } + }, + threshold + ] + } + ] + }; + }); + } + + static async createEvaluation( + params: CreateEvaluationParams & { + teamId: string; + tmbId: string; + } + ): Promise { + const { teamId, tmbId, autoStart = true, ...evaluationParamsInput } = params; + + const evaluationParams = buildEvalDataConfig(evaluationParamsInput); + + const { billId } = await createEvaluationUsage({ + teamId, + tmbId, + appName: evaluationParams.name + }); + + await checkEvaluationItemQueueHealth(); + const createEvaluationWithItems = async (session: ClientSession) => { + // Create evaluation in transaction + const evaluation = await MongoEvaluation.create( + [ + { + ...evaluationParams, + teamId, + tmbId, + usageId: billId, + createTime: new Date() + } + ], + { session } + ); + + const evaluationObject = evaluation[0].toObject(); + + // Load dataset and create evaluation items + // Ensure we only operate on committed dataset documents inside the transaction + const dataItems = await MongoEvalDatasetData.find({ + evalDatasetCollectionId: evaluationParams.evalDatasetCollectionId, + teamId + }) + .session(session) + .lean(); + + if (dataItems.length === 0) { + throw new Error(EvaluationErrEnum.evalDatasetLoadFailed); + } + + // Create evaluation items + const evalItems: Omit[] = []; + for (const dataItem of dataItems) { + const evaluationDataItem = { + _id: dataItem._id, + userInput: dataItem.userInput, + expectedOutput: dataItem.expectedOutput, + context: dataItem.context, + targetCallParams: undefined + }; + + evalItems.push({ + evalId: evaluationObject._id, + dataItem: evaluationDataItem + }); + } + + // Insert evaluation items in transaction + const insertedItems = await MongoEvalItem.insertMany(evalItems, { session }); + addLog.debug(`[Evaluation] Created ${insertedItems.length} evaluation items`); + + return { + evaluation: evaluationObject, + insertedItemCount: insertedItems.length + }; + }; + + const { evaluation, insertedItemCount } = await mongoSessionRun(createEvaluationWithItems); + + // Auto-start evaluation if enabled – this now occurs after the transaction commits + if (autoStart) { + await enqueueEvaluationItems(evaluation._id.toString()); + + addLog.debug(`[Evaluation] Task created and auto-started after commit: ${evaluation._id}`, { + insertedItemCount + }); + } else { + addLog.debug(`[Evaluation] Task created: ${evaluation._id}`, { + insertedItemCount + }); + } + + return evaluation; + } + + static async getEvaluation(evalId: string, teamId: string): Promise { + const evaluation = await MongoEvaluation.findOne({ + _id: new Types.ObjectId(evalId), + teamId: new Types.ObjectId(teamId) + }).lean(); + if (!evaluation) { + throw new Error(EvaluationErrEnum.evalTaskNotFound); + } + + // Get real-time status from job queues + const status = await getEvaluationTaskStatus(evalId); + + return { + ...evaluation, + status + }; + } + + static async updateEvaluation( + evalId: string, + updates: Partial, + teamId: string + ): Promise { + const result = await MongoEvaluation.updateOne( + { _id: new Types.ObjectId(evalId), teamId: new Types.ObjectId(teamId) }, + { $set: updates } + ); + if (result.matchedCount === 0) { + throw new Error(EvaluationErrEnum.evalTaskNotFound); + } + } + + static async deleteEvaluation(evalId: string, teamId: string): Promise { + const del = async (session: ClientSession) => { + // Remove tasks from queue to prevent further processing + const [itemCleanupResult, summaryCleanupResult] = await Promise.all([ + removeEvaluationItemJobs(evalId, { + forceCleanActiveJobs: true, + retryAttempts: 3, + retryDelay: 200 + }), + removeEvaluationSummaryJobs(evalId, { + forceCleanActiveJobs: true, + retryAttempts: 3, + retryDelay: 200 + }) + ]); + + addLog.debug('Queue cleanup completed for evaluation deletion', { + evalId, + itemCleanup: itemCleanupResult, + summaryCleanup: summaryCleanupResult + }); + + // Delete all evaluation items + await MongoEvalItem.deleteMany({ evalId: new Types.ObjectId(evalId) }, { session }); + + const result = await MongoEvaluation.deleteOne( + { + _id: new Types.ObjectId(evalId), + teamId: new Types.ObjectId(teamId) + }, + { session } + ); + + if (result.deletedCount === 0) { + throw new Error(EvaluationErrEnum.evalTaskNotFound); + } + + addLog.debug(`[Evaluation] Evaluation task deleted including queue cleanup: ${evalId}`); + }; + + await mongoSessionRun(del); + } + + static async listEvaluations( + teamId: string, + offset: number = 0, + pageSize: number = 20, + searchKey?: string, + accessibleIds?: string[], + tmbId?: string, + isOwner: boolean = false, + appName?: string, + appId?: string + ): Promise<{ list: EvaluationDisplayType[]; total: number }> { + // Build filter and pagination + const filter: any = { teamId: new Types.ObjectId(teamId) }; + const skip = offset; + const limit = pageSize; + const sort = { createTime: -1 as const }; + + // Filter by accessible resources if not owner + let finalFilter = filter; + if (!isOwner && accessibleIds) { + finalFilter = { + ...filter, + $or: [ + { _id: { $in: accessibleIds.map((id) => new Types.ObjectId(id)) } }, + ...(tmbId ? [{ tmbId: new Types.ObjectId(tmbId) }] : []) // Own evaluations + ] + }; + } + + // Build aggregation pipeline + const aggregationPipeline = [ + { $match: finalFilter }, + { + $lookup: { + from: 'eval_dataset_collections', + localField: 'evalDatasetCollectionId', + foreignField: '_id', + as: 'evalDatasetCollection' + } + }, + { + $addFields: { + 'target.config.appObjectId': { $toObjectId: '$target.config.appId' } + } + }, + { + $lookup: { + from: 'apps', + localField: 'target.config.appObjectId', + foreignField: '_id', + as: 'app' + } + }, + { + $addFields: { + 'target.config.versionObjectId': { $toObjectId: '$target.config.versionId' } + } + }, + { + $lookup: { + from: 'app_versions', + localField: 'target.config.versionObjectId', + foreignField: '_id', + as: 'appVersion' + } + }, + { + $addFields: { + 'target.config.appName': { $arrayElemAt: ['$app.name', 0] }, + 'target.config.avatar': { $arrayElemAt: ['$app.avatar', 0] }, + 'target.config.versionName': { $arrayElemAt: ['$appVersion.versionName', 0] } + } + } + ]; + + // Add target filtering if provided + if (appName || appId) { + const targetFilter: any = {}; + + if (appName) { + targetFilter['target.config.appName'] = { $regex: appName, $options: 'i' }; + } + + if (appId) { + targetFilter['target.config.appId'] = appId; + } + + aggregationPipeline.push({ $match: targetFilter }); + } + + // Add search key filtering + if (searchKey) { + aggregationPipeline.push({ + $match: { + $or: [ + { name: { $regex: searchKey, $options: 'i' } }, + { description: { $regex: searchKey, $options: 'i' } }, + { 'target.config.versionId': { $regex: searchKey, $options: 'i' } }, + { 'target.config.versionName': { $regex: searchKey, $options: 'i' } } + ] + } + }); + } + + const [evaluations, total] = await Promise.all([ + MongoEvaluation.aggregate([ + ...aggregationPipeline, + { + $addFields: { + evalDatasetCollectionName: { $arrayElemAt: ['$evalDatasetCollection.name', 0] }, + evalDatasetCollectionId: '$evalDatasetCollectionId', + metricNames: { + $map: { + input: '$evaluators', + as: 'evaluator', + in: '$$evaluator.metric.name' + } + } + } + }, + { + $project: { + _id: 1, + name: 1, + createTime: 1, + finishTime: 1, + errorMessage: 1, + evalDatasetCollectionName: 1, + evalDatasetCollectionId: 1, + target: { + type: '$target.type', + config: { + appId: '$target.config.appId', + versionId: '$target.config.versionId', + avatar: '$target.config.avatar', + appName: '$target.config.appName', + versionName: '$target.config.versionName' + } + }, + metricNames: 1, + evaluators: 1, // Add evaluators field for real-time calculation + summaryData: 1, + aggregateScore: 1, + tmbId: 1 + } + }, + { $sort: sort }, + { $skip: skip }, + { $limit: limit } + ]), + // Get total count without pagination + MongoEvaluation.aggregate([...aggregationPipeline, { $count: 'total' }]).then( + (result) => result[0]?.total || 0 + ) + ]); + + // Get real-time status and statistics + const evaluationsWithStatus = await Promise.all( + evaluations.map(async (evaluation) => { + const [status, statistics] = await Promise.all([ + getEvaluationTaskStatus(evaluation._id.toString()), + getEvaluationTaskStats(evaluation._id.toString()) + ]); + return { + ...evaluation, + status, + statistics + }; + }) + ); + + // Return data (permissions handled in API layer) + // Calculate real-time scores for each evaluation + const evaluationsWithRealTimeScores = await Promise.all( + evaluationsWithStatus.map(async (evaluation) => { + try { + // Calculate real-time metric scores and aggregate score + const calculatedData = await EvaluationSummaryService.calculateMetricScores(evaluation); + + // Update summaryConfigs with real-time calculated values + const updatedSummaryConfigs = evaluation.summaryData.summaryConfigs.map( + (summaryConfig: any) => { + const metricData = calculatedData.metricsData.find( + (m) => m.metricId === summaryConfig.metricId + ); + return { + ...summaryConfig, + score: metricData?.metricScore || 0, + completedItemCount: metricData?.totalCount || 0, + overThresholdItemCount: metricData?.aboveThresholdCount || 0 + }; + } + ); + + return { + ...evaluation, + summaryData: { + ...evaluation.summaryData, + summaryConfigs: updatedSummaryConfigs + }, + aggregateScore: calculatedData.aggregateScore + }; + } catch (error) { + addLog.error('[listEvaluations] Failed to calculate real-time scores', { + evalId: evaluation._id, + error + }); + // Return evaluation with default score values if calculation fails + const defaultSummaryConfigs = + evaluation.summaryData?.summaryConfigs?.map((summaryConfig: any) => ({ + ...summaryConfig, + score: 0, + completedItemCount: 0, + overThresholdItemCount: 0 + })) || []; + + return { + ...evaluation, + summaryData: { + ...evaluation.summaryData, + summaryConfigs: defaultSummaryConfigs + }, + aggregateScore: 0 + }; + } + }) + ); + + // Return data with real-time scores - permissions will be handled in API layer + return { + list: evaluationsWithRealTimeScores, + total + }; + } + + static async getEvaluationDetail(evalId: string, teamId: string): Promise { + const evaluationResult = await MongoEvaluation.aggregate([ + { $match: { _id: new Types.ObjectId(evalId), teamId: new Types.ObjectId(teamId) } }, + { + $lookup: { + from: 'eval_dataset_collections', + localField: 'evalDatasetCollectionId', + foreignField: '_id', + as: 'evalDatasetCollection' + } + }, + { + $addFields: { + 'target.config.appObjectId': { $toObjectId: '$target.config.appId' } + } + }, + { + $lookup: { + from: 'apps', + localField: 'target.config.appObjectId', + foreignField: '_id', + as: 'app' + } + }, + { + $addFields: { + 'target.config.versionObjectId': { $toObjectId: '$target.config.versionId' } + } + }, + { + $lookup: { + from: 'app_versions', + localField: 'target.config.versionObjectId', + foreignField: '_id', + as: 'appVersion' + } + }, + { + $addFields: { + 'target.config.appName': { $arrayElemAt: ['$app.name', 0] }, + 'target.config.avatar': { $arrayElemAt: ['$app.avatar', 0] }, + 'target.config.versionName': { $arrayElemAt: ['$appVersion.versionName', 0] }, + evalDatasetCollectionName: { $arrayElemAt: ['$evalDatasetCollection.name', 0] }, + evalDatasetCollectionId: '$evalDatasetCollectionId' + } + }, + { + $project: { + _id: 1, + teamId: 1, + tmbId: 1, + name: 1, + description: 1, + evalDatasetCollectionId: 1, + evalDatasetCollectionName: 1, + target: { + type: '$target.type', + config: { + appId: '$target.config.appId', + versionId: '$target.config.versionId', + avatar: '$target.config.avatar', + appName: '$target.config.appName', + versionName: '$target.config.versionName' + } + }, + evaluators: 1, + summary: 1, + usageId: 1, + createTime: 1, + finishTime: 1, + errorMessage: 1 + } + } + ]); + + const evaluation = evaluationResult[0]; + if (!evaluation) { + throw new Error(EvaluationErrEnum.evalTaskNotFound); + } + + const status = await getEvaluationTaskStatus(evalId); + const stats = await getEvaluationTaskStats(evalId); + + return { + ...evaluation, + status, + statistics: stats + }; + } + + static async startEvaluation(evalId: string, teamId: string): Promise { + const evaluation = await this.getEvaluation(evalId, teamId); + + // Let BullMQ handle most scenarios + const canStart = + evaluation.status === EvaluationStatusEnum.queuing || + evaluation.status === EvaluationStatusEnum.completed || + evaluation.status === EvaluationStatusEnum.error; + + if (!canStart) { + throw new Error(EvaluationErrEnum.evalInvalidStateTransition); + } + + await enqueueEvaluationItems(evalId); + + const action = + evaluation.status === EvaluationStatusEnum.error + ? 'restarted' + : evaluation.status === EvaluationStatusEnum.completed + ? 'restarted' + : 'started'; + addLog.debug(`[Evaluation] Task ${action}: ${evalId}`); + } + + static async stopEvaluation(evalId: string, teamId: string): Promise { + const evaluation = await this.getEvaluation(evalId, teamId); + + if ( + ![EvaluationStatusEnum.evaluating, EvaluationStatusEnum.queuing].includes(evaluation.status) + ) { + throw new Error(EvaluationErrEnum.evalOnlyRunningCanStop); + } + + const stopEval = async (session: ClientSession) => { + // Remove tasks from queue + const [itemCleanupResult, summaryCleanupResult] = await Promise.all([ + removeEvaluationItemJobs(evalId, { + forceCleanActiveJobs: true, + retryAttempts: 3, + retryDelay: 200 + }), + removeEvaluationSummaryJobs(evalId, { + forceCleanActiveJobs: true, + retryAttempts: 3, + retryDelay: 200 + }) + ]); + + addLog.debug('Queue cleanup completed for evaluation stop', { + evalId, + itemCleanup: itemCleanupResult, + summaryCleanup: summaryCleanupResult + }); + + // Set error state for manual stop + await MongoEvaluation.updateOne( + { _id: new Types.ObjectId(evalId) }, + { + $set: { + finishTime: new Date(), + errorMessage: EvaluationErrEnum.evalManuallyStopped + } + }, + { session } + ); + + // Mark evaluation items as manually stopped + await MongoEvalItem.updateMany( + { + evalId: new Types.ObjectId(evalId) + }, + { + $set: { + errorMessage: EvaluationErrEnum.evalManuallyStopped, + finishTime: new Date() + } + }, + { session } + ); + + addLog.debug(`[Evaluation] Task manually stopped and removed from queue: ${evalId}`); + }; + + await mongoSessionRun(stopEval); + } + + static async getEvaluationStats( + evalId: string, + teamId: string + ): Promise<{ + total: number; + completed: number; + evaluating: number; + queuing: number; + error: number; + belowThreshold: number; + }> { + const evaluation = await this.getEvaluation(evalId, teamId); // Validate access + + // Get real-time status from job queues + const basicStats = await getEvaluationTaskStats(evalId); + + // Calculate belowThreshold count using threshold checks + const evaluators = evaluation.evaluators || []; + let belowThresholdCount = 0; + + if (evaluators.length > 0) { + const evaluatorFailChecks = this.buildEvaluatorFailChecks(evaluators); + + // Count items that are below threshold checks + const belowThresholdResult = await MongoEvalItem.aggregate([ + { $match: { evalId: new Types.ObjectId(evalId) } }, + { + $addFields: { + hasFailedEvaluator: + evaluatorFailChecks.length > 0 ? { $or: evaluatorFailChecks } : false + } + }, + { + $match: { + hasFailedEvaluator: true, + finishTime: { $exists: true }, // Only completed items + errorMessage: { $exists: false } // Exclude errors + } + }, + { $count: 'belowThreshold' } + ]); + + belowThresholdCount = belowThresholdResult[0]?.belowThreshold || 0; + } + + return { + ...basicStats, + belowThreshold: belowThresholdCount + }; + } + + /** + * Evaluation Item Management + */ + + static async listEvaluationItems( + evalId: string, + teamId: string, + offset: number = 0, + pageSize: number = 20, + options: { + status?: EvaluationStatusEnum; + belowThreshold?: boolean; + userInput?: string; + expectedOutput?: string; + actualOutput?: string; + } = {} + ): Promise<{ items: EvaluationItemDisplayType[]; total: number }> { + const evaluation = await this.getEvaluation(evalId, teamId); + const { status, belowThreshold, userInput, expectedOutput, actualOutput } = options; + + // Build aggregation pipeline + const pipeline = this.buildEvaluationItemsPipeline( + evaluation, + { status, belowThreshold, userInput, expectedOutput, actualOutput }, + offset, + pageSize + ); + + try { + const [dataResult, countResult] = await Promise.all([ + MongoEvalItem.aggregate(pipeline.dataPipeline), + MongoEvalItem.aggregate(pipeline.countPipeline) + ]); + + const total = countResult[0]?.total || 0; + const items = dataResult.map((item) => ({ + ...item, + _id: String(item._id), + // Add evaluator info + evaluators: evaluation.evaluators.map((evaluator, index) => ({ + metric: evaluator.metric, + thresholdValue: evaluator.thresholdValue, + //check mongo schema need to change or not,add this for Front-end calculate aggreatescore threshold + weight: evaluation.summaryData.summaryConfigs[index]?.weight || 0 + })), + // Add summaryData + summaryData: evaluation.summaryData + })); + + return { items, total }; + } catch (error) { + console.error('Failed to list evaluation items', { + evalId, + options, + offset, + pageSize, + error + }); + throw new Error('Failed to list evaluation items'); + } + } + + /** + * Build aggregation pipeline for evaluation items listing + */ + private static buildEvaluationItemsPipeline( + evaluation: any, + filters: { + status?: EvaluationStatusEnum; + belowThreshold?: boolean; + userInput?: string; + expectedOutput?: string; + actualOutput?: string; + }, + offset: number, + pageSize: number + ) { + const { status, belowThreshold, userInput, expectedOutput, actualOutput } = filters; + + // Base match conditions + const matchConditions: Record = { + evalId: evaluation._id + }; + + // Use status for status filtering + if (status !== undefined) { + matchConditions['status'] = status; + } + + // Add text search conditions + const searchConditions: any[] = []; + if (userInput && typeof userInput === 'string' && userInput.trim().length > 0) { + searchConditions.push({ + 'dataItem.userInput': { $regex: new RegExp(userInput.trim(), 'i') } + }); + } + if (expectedOutput && typeof expectedOutput === 'string' && expectedOutput.trim().length > 0) { + searchConditions.push({ + 'dataItem.expectedOutput': { $regex: new RegExp(expectedOutput.trim(), 'i') } + }); + } + if (actualOutput && typeof actualOutput === 'string' && actualOutput.trim().length > 0) { + searchConditions.push({ + 'targetOutput.actualOutput': { $regex: new RegExp(actualOutput.trim(), 'i') } + }); + } + + if (searchConditions.length > 0) { + matchConditions.$and = searchConditions; + } + + // Build pipeline stages + const commonPipeline: any[] = [{ $match: matchConditions }]; + + // Status field is already available directly + + // Add threshold filter if specified + if (belowThreshold) { + const evaluators = evaluation.evaluators || []; + if (evaluators.length > 0) { + const evaluatorFailChecks = this.buildEvaluatorFailChecks(evaluators); + commonPipeline.push({ + $addFields: { + hasFailedEvaluator: + evaluatorFailChecks.length > 0 ? { $or: evaluatorFailChecks } : false + } + }); + commonPipeline.push({ + $match: { + hasFailedEvaluator: true, + status: EvaluationStatusEnum.completed, // Only completed items + evaluatorOutputs: { $exists: true, $ne: null, $not: { $size: 0 } } // Valid evaluator outputs + } + }); + } + } + + // Data pipeline + const dataPipeline = [ + ...commonPipeline, + { $sort: { createTime: -1 } }, + { $skip: offset }, + { $limit: pageSize }, + { + $project: { + _id: 1, + evalId: 1, + dataItem: 1, + targetOutput: 1, + evaluatorOutputs: 1, + status: 1, + createTime: 1, + updateTime: 1, + errorMessage: 1 + } + } + ]; + + // Count pipeline + const countPipeline = [...commonPipeline, { $count: 'total' }]; + + return { dataPipeline, countPipeline }; + } + + static async getEvaluationItem( + itemId: string, + teamId: string + ): Promise { + const item = await MongoEvalItem.findById(itemId).lean(); + + if (!item) { + throw new Error(EvaluationErrEnum.evalItemNotFound); + } + + await this.getEvaluation(item.evalId, teamId); + + return item; + } + + /** + * Build MongoDB update object for evaluation data items + */ + private static buildEvaluationDataItemUpdateObject(updates: { + userInput?: string; + expectedOutput?: string; + context?: string[]; + targetCallParams?: TargetCallParams; + }): any { + const updateObj: any = {}; + + if (updates.userInput !== undefined) { + updateObj['dataItem.userInput'] = updates.userInput; + } + if (updates.expectedOutput !== undefined) { + updateObj['dataItem.expectedOutput'] = updates.expectedOutput; + } + if (updates.context !== undefined) { + updateObj['dataItem.context'] = updates.context; + } + if (updates.targetCallParams !== undefined) { + updateObj['dataItem.targetCallParams'] = updates.targetCallParams; + } + + return updateObj; + } + + /** + * Update evaluation item with data item fields + */ + static async updateEvaluationItem( + itemId: string, + updates: { + userInput?: string; + expectedOutput?: string; + context?: string[]; + targetCallParams?: TargetCallParams; + }, + teamId: string + ): Promise { + const item = await this.getEvaluationItem(itemId, teamId); + const evaluation = await this.getEvaluation(item.evalId, teamId); + + // Build MongoDB update object + const updateObj = this.buildEvaluationDataItemUpdateObject(updates); + if (Object.keys(updateObj).length === 0) { + return; + } + + const result = await MongoEvalItem.updateOne( + { _id: new Types.ObjectId(itemId) }, + { $set: updateObj } + ); + + if (result.matchedCount === 0) { + throw new Error(EvaluationErrEnum.evalItemNotFound); + } + + // Re-queue item if updated + if (result.modifiedCount > 0) { + // Get the updated item to determine the evalId + const updatedItem = await MongoEvalItem.findById(itemId, 'evalId'); + if (updatedItem) { + const cleanupResult = await removeEvaluationItemJobsByItemId(itemId, { + forceCleanActiveJobs: true, + retryAttempts: 3, + retryDelay: 200 + }); + + addLog.debug('Queue cleanup completed for evaluation item deletion', { + itemId, + cleanup: cleanupResult + }); + + // Reset results and re-queue + const evaluatorOutputs = evaluation.evaluators.map((evaluator) => ({ + metricName: evaluator.metric.name + })); + + await MongoEvalItem.updateOne( + { _id: new Types.ObjectId(itemId) }, + { + $set: { + targetOutput: {}, + evaluatorOutputs + } + } + ); + // Re-submit to evaluation queue + await addEvaluationItemJob({ + evalId: updatedItem.evalId.toString(), + evalItemId: itemId + }); + + addLog.debug(`[Evaluation] Item updated and re-queued for evaluation: ${itemId}`); + } + } + } + + static async deleteEvaluationItem(itemId: string, teamId: string): Promise { + await this.getEvaluationItem(itemId, teamId); + + // Remove jobs from queue before deleting + const cleanupResult = await removeEvaluationItemJobsByItemId(itemId, { + forceCleanActiveJobs: true, + retryAttempts: 3, + retryDelay: 200 + }); + + addLog.debug('Queue cleanup completed for evaluation item deletion', { + itemId, + cleanup: cleanupResult + }); + + const result = await MongoEvalItem.deleteOne({ _id: new Types.ObjectId(itemId) }); + + if (result.deletedCount === 0) { + throw new Error(EvaluationErrEnum.evalItemNotFound); + } + + addLog.debug(`[Evaluation] Evaluation item deleted including queue cleanup: ${itemId}`); + } + + static async retryEvaluationItem(itemId: string, teamId: string): Promise { + const item = await this.getEvaluationItem(itemId, teamId); + + if (item.status !== EvaluationStatusEnum.error) { + throw new Error(EvaluationErrEnum.evalItemNoErrorToRetry); + } + + const [failedJobs, pendingJobs] = await Promise.all([ + evaluationItemQueue.getJobs(['failed']), + evaluationItemQueue.getJobs(['waiting', 'delayed', 'active', 'prioritized']) + ]); + + const pendingJob = pendingJobs.find((job) => job.data?.evalItemId === itemId); + if (pendingJob) { + addLog.debug('Evaluation item retry skipped (job already pending)', { + itemId, + evalId: item.evalId, + teamId, + jobId: pendingJob.id + }); + return; + } + + const failedJob = failedJobs.find((job) => job.data?.evalItemId === itemId); + if (failedJob) { + // Retry the failed job first + await failedJob.retry(); + + // Update status to queuing after successful retry + await MongoEvalItem.updateOne( + { _id: new Types.ObjectId(itemId) }, + { + $set: { + status: EvaluationStatusEnum.queuing + }, + $unset: { + finishTime: 1, + errorMessage: 1 + } + } + ); + + addLog.debug('Evaluation item retried successfully (existing failed job)', { + itemId, + evalId: item.evalId, + teamId, + jobId: failedJob.id + }); + return; + } + + // Add new job first + await addEvaluationItemJob({ + evalId: item.evalId.toString(), + evalItemId: itemId + }); + + // Update status to queuing after successful job addition + await MongoEvalItem.updateOne( + { _id: new Types.ObjectId(itemId) }, + { + $set: { + status: EvaluationStatusEnum.queuing + }, + $unset: { + finishTime: 1, + errorMessage: 1 + } + } + ); + + addLog.debug('Evaluation item retried successfully (new job queued)', { + itemId, + evalId: item.evalId, + teamId + }); + } + + static async retryFailedItems(evalId: string, teamId: string): Promise { + const evaluation = await this.getEvaluation(evalId, teamId); // Validate evalId and teamId + + const itemsToProcess = await MongoEvalItem.find({ + evalId: evaluation._id, + status: EvaluationStatusEnum.error + }).lean(); + + if (itemsToProcess.length === 0) { + addLog.warn('No failed jobs found to retry for evaluation', { evalId }); + return 0; + } + + const [failedJobs, pendingJobs] = await Promise.all([ + evaluationItemQueue.getJobs(['failed']), + evaluationItemQueue.getJobs(['waiting', 'delayed', 'active', 'prioritized']) + ]); + + const failedJobMap = new Map(); + failedJobs.forEach((job) => { + const evalItemId = job.data?.evalItemId; + if (evalItemId) { + failedJobMap.set(evalItemId, job); + } + }); + + const pendingJobSet = new Set(); + pendingJobs.forEach((job) => { + const evalItemId = job.data?.evalItemId; + if (evalItemId) { + pendingJobSet.add(evalItemId); + } + }); + + let retriedJobs = 0; + let addedJobs = 0; + let skippedJobs = 0; + + // Collect successfully processed items for batch status update + const itemsToUpdate: string[] = []; + + // Process each item: retry/add job first, then collect for status update + for (const item of itemsToProcess) { + const evalItemId = item._id.toString(); + + if (pendingJobSet.has(evalItemId)) { + skippedJobs += 1; + continue; + } + + try { + const failedJob = failedJobMap.get(evalItemId); + if (failedJob) { + // Retry the failed job first + await failedJob.retry(); + retriedJobs += 1; + itemsToUpdate.push(evalItemId); + pendingJobSet.add(evalItemId); + } else { + // Add new job first + await addEvaluationItemJob({ + evalId, + evalItemId + }); + addedJobs += 1; + itemsToUpdate.push(evalItemId); + pendingJobSet.add(evalItemId); + } + } catch (error) { + addLog.error('Failed to retry evaluation item', { + evalId, + evalItemId, + error + }); + // Don't add to itemsToUpdate if job operation failed + } + } + + // Update status to queuing for all successfully processed items + if (itemsToUpdate.length > 0) { + await MongoEvalItem.updateMany( + { _id: { $in: itemsToUpdate.map((id) => new Types.ObjectId(id)) } }, + { + $set: { + status: EvaluationStatusEnum.queuing + }, + $unset: { + finishTime: 1, + errorMessage: 1 + } + } + ); + } + + const totalProcessed = retriedJobs + addedJobs; + + addLog.debug('All failed evaluation items retry completed', { + evalId, + teamId, + retriedJobs, + addedJobs, + skippedJobs, + totalProcessed + }); + + return totalProcessed; + } + + static async getEvaluationItemResult( + itemId: string, + teamId: string + ): Promise { + const item = await this.getEvaluationItem(itemId, teamId); + return item; + } +} +export { MongoEvaluation }; diff --git a/packages/service/core/evaluation/task/mq.ts b/packages/service/core/evaluation/task/mq.ts new file mode 100644 index 000000000000..b2a71538ed5a --- /dev/null +++ b/packages/service/core/evaluation/task/mq.ts @@ -0,0 +1,311 @@ +import { addLog } from '../../../common/system/log'; +import { getQueue, getWorker, QueueNames } from '../../../common/bullmq'; +import { UnrecoverableError } from 'bullmq'; +import type { EvaluationItemJobData } from '@fastgpt/global/core/evaluation/type'; +import { EvaluationStatusEnum } from '@fastgpt/global/core/evaluation/constants'; +import { + createJobCleaner, + type JobCleanupResult, + type JobCleanupOptions, + checkBullMQHealth +} from '../utils/mq'; +import { getErrText } from '@fastgpt/global/common/error/utils'; + +export const evaluationItemQueue = getQueue(QueueNames.evalTaskItem, { + defaultJobOptions: { + attempts: global.systemEnv?.evalConfig?.caseMaxRetry || 3, + backoff: { + type: 'exponential', + delay: 1000 // Initial delay 1s, exponential backoff + }, + removeOnComplete: true, + removeOnFail: false + } +}); + +export const getEvaluationItemWorker = (processor: any) => { + const worker = getWorker(QueueNames.evalTaskItem, processor, { + concurrency: global.systemEnv?.evalConfig?.caseConcurrency || 10, + stalledInterval: 30000, // 30 seconds for faster recovery + maxStalledCount: global.systemEnv?.evalConfig?.maxStalledCount || 3 + }); + worker.on('stalled', async (jobId) => { + try { + const job = await evaluationItemQueue.getJob(jobId); + const evalItemId = job?.data?.evalItemId; + + addLog.warn('[Evaluation] Item job stalled, will be retried', { + jobId, + evalId: job?.data?.evalId, + evalItemId, + attemptsMade: job?.attemptsMade, + opts: job?.opts?.attempts + }); + + // Update status to queuing since stalled jobs will be retried + if (evalItemId) { + const { MongoEvalItem } = await import('./schema'); + await MongoEvalItem.updateOne( + { _id: evalItemId }, + { + $set: { + status: EvaluationStatusEnum.queuing + }, + $unset: { + finishTime: 1, + errorMessage: 1 + } + } + ); + } + } catch (error) { + addLog.warn('[Evaluation] Item job stalled, will be retried (could not get job data)', { + jobId, + error + }); + } + }); + worker.on('failed', async (job, error) => { + // Handle failed items and check task completion + + try { + const evalId = job?.data?.evalId; + const evalItemId = job?.data?.evalItemId; + // Get max attempts from queue's defaultJobOptions or fallback to global config + const queueDefaultAttempts = evaluationItemQueue.opts?.defaultJobOptions?.attempts; + const maxAttempts = job?.opts?.attempts || queueDefaultAttempts || 0; + const attemptsMade = job?.attemptsMade || 0; + + // Check if error is UnrecoverableError (which prevents retries regardless of attempts) + const isUnrecoverableError = error instanceof UnrecoverableError; + + // Job will retry only if: + // 1. Not an UnrecoverableError AND + // 2. Still has attempts remaining + const willRetry = !isUnrecoverableError && attemptsMade < maxAttempts; + + addLog.error('[Evaluation] Item job failed', { + jobId: job?.id, + evalId, + evalItemId, + attemptsMade, + maxAttempts, + isUnrecoverableError, + willRetry, + error + }); + + // Update item status based on whether it will retry + if (evalItemId) { + const { MongoEvalItem } = await import('./schema'); + + if (willRetry) { + // Job will be retried, set status to queuing and clear error state + await MongoEvalItem.updateOne( + { _id: evalItemId }, + { + $set: { + status: EvaluationStatusEnum.queuing + }, + $unset: { + finishTime: 1, + errorMessage: 1 + } + } + ); + } else { + // Job exhausted all retries, set final error status + await MongoEvalItem.updateOne( + { _id: evalItemId }, + { + $set: { + errorMessage: getErrText(error), + finishTime: new Date(), + status: EvaluationStatusEnum.error + } + } + ); + } + } + + // Check task completion after failure (only if no more retries) + if (evalId && !willRetry) { + addLog.debug('[Evaluation] Checking task completion after final item failure', { + jobId: job?.id, + evalId, + evalItemId, + error + }); + + // Check task completion (avoid circular dependency) + const { finishEvaluationTask } = await import('./processor'); + await finishEvaluationTask(evalId); + } + } catch (finishError) { + addLog.warn('[Evaluation] Could not retrieve job data for failed job', { + jobId: job?.id, + finishError + }); + } + }); + + worker.on('active', async (job) => { + try { + const evalId = job?.data?.evalId; + const evalItemId = job?.data?.evalItemId; + if (evalItemId) { + addLog.debug('[Evaluation] Item job started, updating status to evaluating', { + jobId: job?.id, + evalId, + evalItemId + }); + + // Update item status to evaluating + const { MongoEvalItem } = await import('./schema'); + await MongoEvalItem.updateOne( + { _id: evalItemId }, + { + $set: { + status: EvaluationStatusEnum.evaluating + }, + $unset: { + finishTime: 1, + errorMessage: 1 + } + } + ); + } + } catch (error) { + addLog.error('[Evaluation] Error in active event handler', { + jobId: job?.id, + error + }); + } + }); + + worker.on('completed', async (job) => { + try { + const evalId = job?.data?.evalId; + const evalItemId = job?.data?.evalItemId; + if (evalId && evalItemId) { + addLog.debug( + '[Evaluation] Item completed, updating metadata and checking task completion', + { + jobId: job?.id, + evalId, + evalItemId + } + ); + + // Update item status to completed + const { MongoEvalItem } = await import('./schema'); + await MongoEvalItem.updateOne( + { _id: evalItemId }, + { + $set: { + status: EvaluationStatusEnum.completed, + finishTime: new Date() + }, + $unset: { + errorMessage: 1 + } + } + ); + + // Check task completion (avoid circular dependency) + const { finishEvaluationTask } = await import('./processor'); + await finishEvaluationTask(job.data.evalId); + } + } catch (error) { + addLog.error('[Evaluation] Error in completed event handler', { + jobId: job?.id, + error + }); + } + }); +}; + +export const removeEvaluationItemJobs = async ( + evalId: string, + options?: JobCleanupOptions +): Promise => { + const cleaner = createJobCleaner(options); + + const filterFn = (job: any) => { + return String(job.data?.evalId) === String(evalId); + }; + + const result = await cleaner.cleanAllJobsByFilter( + evaluationItemQueue, + filterFn, + QueueNames.evalTaskItem + ); + + addLog.debug('Evaluation item jobs cleanup completed', { + evalId, + result + }); + + return result; +}; + +export const removeEvaluationItemJobsByItemId = async ( + evalItemId: string, + options?: JobCleanupOptions +): Promise => { + const cleaner = createJobCleaner(options); + + const filterFn = (job: any) => { + return String(job.data?.evalItemId) === String(evalItemId); + }; + + const result = await cleaner.cleanAllJobsByFilter( + evaluationItemQueue, + filterFn, + QueueNames.evalTaskItem + ); + + addLog.debug('Evaluation item jobs cleanup completed for specific item', { + evalItemId, + result + }); + + return result; +}; + +export const addEvaluationItemJob = (data: EvaluationItemJobData, options?: { delay?: number }) => { + const evalItemId = String(data.evalItemId); + + return evaluationItemQueue.add(evalItemId, data, { + deduplication: { + id: evalItemId, + ttl: 5000 + }, + ...options + }); +}; + +export const addEvaluationItemJobs = ( + jobs: Array<{ + data: EvaluationItemJobData; + delay?: number; + }> +) => { + const bulkJobs = jobs.map(({ data, delay }, index) => { + const evalItemId = String(data.evalItemId); + return { + name: evalItemId, + data, + opts: { + delay: delay ?? index * 100, // Small delay to avoid overwhelming system + deduplication: { id: evalItemId } + } + }; + }); + + return evaluationItemQueue.addBulk(bulkJobs); +}; + +export const checkEvaluationItemQueueHealth = (): Promise => { + return checkBullMQHealth(evaluationItemQueue, 'evaluation-item'); +}; diff --git a/packages/service/core/evaluation/task/processor.ts b/packages/service/core/evaluation/task/processor.ts new file mode 100644 index 000000000000..b8148b54cfa3 --- /dev/null +++ b/packages/service/core/evaluation/task/processor.ts @@ -0,0 +1,348 @@ +import { addLog } from '../../../common/system/log'; +import type { Job } from '../../../common/bullmq'; +import type { EvaluationItemJobData, TargetOutput } from '@fastgpt/global/core/evaluation/type'; +import { getEvaluationItemWorker, addEvaluationItemJobs } from './mq'; +import { MongoEvaluation, MongoEvalItem } from './schema'; +import { createTargetInstance } from '../target'; +import { createEvaluatorInstance } from '../evaluator'; +import { Types } from 'mongoose'; +import { EvaluationStatusEnum } from '@fastgpt/global/core/evaluation/constants'; +import { checkTeamAIPoints } from '../../../support/permission/teamLimit'; +import { EvaluationErrEnum } from '@fastgpt/global/common/error/code/evaluation'; +import { getErrText } from '@fastgpt/global/common/error/utils'; +import { createMergedEvaluationUsage } from '../utils/usage'; +import { EvaluationSummaryService } from '../summary'; +import { createEvaluationError } from './errors'; +import { getEvaluationTaskStats } from './statusCalculator'; +import type { MetricResult } from '@fastgpt/global/core/evaluation/metric/type'; +import { MetricResultStatusEnum } from '@fastgpt/global/core/evaluation/metric/constants'; + +/** + * Complete evaluation task and trigger summary generation + */ +export const finishEvaluationTask = async (evalId: string) => { + try { + // Get task statistics using the reusable function + const stats = await getEvaluationTaskStats(evalId); + + if (stats.total === 0) { + addLog.warn(`[Evaluation] Evaluation task has no evaluation item data: ${evalId}`); + return; + } + + // Check if all items are truly completed + const pendingCount = stats.evaluating + stats.queuing; + + if (pendingCount > 0) { + addLog.debug( + `[Evaluation] Task still has pending items, skipping completion: ${evalId}, total: ${stats.total}, ` + + `success: ${stats.completed}, failed: ${stats.error}, pending: ${pendingCount}` + ); + return; + } + + // Set finishTime if all items are finished (either completed or error, no pending) + // Use conditional update to prevent duplicate writes + const updateResult = await MongoEvaluation.updateOne( + { + _id: new Types.ObjectId(evalId), + finishTime: { $exists: false } // Only update if finishTime is not already set + }, + { $set: { finishTime: new Date() } } + ); + + // If no document was modified, it means another process already finished the task + if (updateResult.modifiedCount === 0) { + addLog.debug( + `[Evaluation] Task already finished by another process: ${evalId}, skipping completion` + ); + return; + } + + // Trigger summary generation for completed task + await EvaluationSummaryService.triggerSummaryGeneration(evalId); + } catch (error) { + addLog.error(`[Evaluation] Error occurred while completing task: ${evalId}`, { + error: getErrText(error) + }); + + // Save error info to database + try { + await MongoEvaluation.updateOne( + { _id: new Types.ObjectId(evalId) }, + { + $set: { + finishTime: new Date(), + errorMessage: EvaluationErrEnum.evalTaskSystemError + } + } + ); + } catch (updateError) { + addLog.warn(`[Evaluation] Failed to update task error info: ${evalId}`, { + updateError: getErrText(updateError) + }); + } + } +}; + +export const enqueueEvaluationItems = async (evalId: string) => { + // Get evaluation data + const evaluation = await MongoEvaluation.findById(evalId).lean(); + + // Skip if task doesn't exist + if (!evaluation) { + throw createEvaluationError(EvaluationErrEnum.evalTaskNotFound, 'ResourceCheck'); + } + + addLog.debug(`[Evaluation] Task ${evalId} now evaluating`); + + // Validate target and evaluators configuration + if (!evaluation.target || !evaluation.target.type || !evaluation.target.config) { + throw createEvaluationError(EvaluationErrEnum.evalTargetConfigInvalid, 'ResourceCheck'); + } + + if (!evaluation.evaluators || evaluation.evaluators.length === 0) { + throw createEvaluationError(EvaluationErrEnum.evalEvaluatorsConfigInvalid, 'ResourceCheck'); + } + + // Check if evaluation items exist + let existingItems = await MongoEvalItem.find({ evalId }).lean(); + + if (existingItems.length === 0) { + throw createEvaluationError(EvaluationErrEnum.evalItemNotFound, 'ResourceCheck'); + } + + const itemsToProcess = existingItems.filter((item) => { + return item.status === EvaluationStatusEnum.queuing; + }); + + if (itemsToProcess.length > 0) { + const jobs = itemsToProcess.map((item, index) => ({ + data: { + evalId, + evalItemId: item._id.toString() + }, + delay: index * 100 // Small delay to avoid overwhelming system + })); + + await addEvaluationItemJobs(jobs); + addLog.debug(`[Evaluation] Submitted ${jobs.length} items to queue`); + } + + addLog.debug( + `[Evaluation] Task processing completed: ${evalId}, submitted ${itemsToProcess.length} evaluation items to queue` + ); +}; + +/** + * Process evaluation item: execute target and evaluators + */ +const evaluationItemProcessor = async (job: Job) => { + const { evalId, evalItemId } = job.data; + + addLog.debug(`[Evaluation] Start processing evaluation item: ${evalItemId}`); + + // Get evaluation item + const evalItem = await MongoEvalItem.findById(evalItemId); + if (!evalItem) { + throw createEvaluationError(EvaluationErrEnum.evalItemNotFound, 'ResourceCheck'); + } + + // Get evaluation for AI points check and configuration + const evaluation = await MongoEvaluation.findById( + evalId, + 'teamId tmbId usageId target evaluators' + ); + if (!evaluation) { + throw createEvaluationError(EvaluationErrEnum.evalTaskNotFound, 'ResourceCheck'); + } + + // Check AI points availability + try { + await checkTeamAIPoints(evaluation.teamId); + } catch (error) { + throw createEvaluationError(error, 'ResourceCheck'); + } + + // Initialize outputs and check for existing results + let targetOutput: TargetOutput | undefined = undefined; + let evaluatorOutputs: MetricResult[] = []; + + // Resume from checkpoint if results exist + if (evalItem.targetOutput?.actualOutput) { + addLog.debug(`[Evaluation] Resuming targetOutput from evalItem: ${evalItemId}`); + targetOutput = evalItem.targetOutput; + } + if (evalItem.evaluatorOutputs && evalItem.evaluatorOutputs.length > 0) { + addLog.debug(`[Evaluation] Resuming evaluatorOutputs from evalItem: ${evalItemId}`); + evaluatorOutputs = evalItem.evaluatorOutputs; + } + + if (!targetOutput && !evaluatorOutputs.length) { + addLog.debug(`[Evaluation] Starting evaluation item from scratch: ${evalItemId}`); + } + + // Execute evaluation target if needed + if (!targetOutput || !targetOutput.actualOutput) { + try { + const targetInstance = await createTargetInstance(evaluation.target, { validate: false }); + targetOutput = await targetInstance.execute({ + userInput: evalItem.dataItem.userInput, + context: evalItem.dataItem.context, + targetCallParams: evalItem.dataItem.targetCallParams + }); + + // Save target output as checkpoint + await MongoEvalItem.updateOne( + { _id: new Types.ObjectId(evalItemId) }, + { + $set: { + targetOutput: targetOutput + } + } + ); + + // Record target usage + if (targetOutput.usage) { + const totalPoints = targetOutput.usage.reduce( + (sum: number, item: any) => sum + (item.totalPoints || 0), + 0 + ); + const inputTokens = targetOutput.usage.reduce( + (sum: number, item: any) => sum + (item.inputTokens || 0), + 0 + ); + const outputTokens = targetOutput.usage.reduce( + (sum: number, item: any) => sum + (item.outputTokens || 0), + 0 + ); + await createMergedEvaluationUsage({ + evalId, + teamId: evaluation.teamId, + tmbId: evaluation.tmbId, + usageId: evaluation.usageId, + totalPoints, + type: 'target', + inputTokens, + outputTokens + }); + } + + if (!targetOutput.actualOutput) { + throw new Error(EvaluationErrEnum.evalTargetExecutionError); + } + } catch (error) { + // Use BullMQ error type for retry handling + throw createEvaluationError(error, 'TargetExecute', { + evalId, + evalItemId + }); + } + } + + // Execute evaluators (only missing ones) + while (evaluatorOutputs.length < evaluation.evaluators.length) { + const evaluatorIndex = evaluatorOutputs.length; + evaluatorOutputs.push({ + metricName: evaluation.evaluators[evaluatorIndex].metric.name + }); + } + + const errors: Array<{ evaluatorName: string; error: string }> = []; + + // Process each evaluator + for (let i = 0; i < evaluation.evaluators.length; i++) { + const evaluator = evaluation.evaluators[i]; + const existingOutput = evaluatorOutputs[i]; + + // Skip if evaluator already has valid successful result + if ( + existingOutput?.data?.score !== undefined && + existingOutput?.status === MetricResultStatusEnum.Success + ) { + continue; + } + + try { + const evaluatorInstance = await createEvaluatorInstance(evaluator, { + validate: false + }); + + const evaluatorOutput = await evaluatorInstance.evaluate({ + userInput: evalItem.dataItem.userInput, + expectedOutput: evalItem.dataItem.expectedOutput, + actualOutput: targetOutput.actualOutput, + context: evalItem.dataItem.context, + retrievalContext: targetOutput.retrievalContext + }); + + await createMergedEvaluationUsage({ + evalId, + teamId: evaluation.teamId, + tmbId: evaluation.tmbId, + usageId: evaluation.usageId, + totalPoints: evaluatorOutput.totalPoints || 0, + inputTokens: + evaluatorOutput.usages?.reduce((sum, usage) => sum + (usage.promptTokens || 0), 0) || 0, + outputTokens: + evaluatorOutput.usages?.reduce((sum, usage) => sum + (usage.completionTokens || 0), 0) || + 0, + type: 'metric' + }); + + // Record error and continue + if (evaluatorOutput.status !== MetricResultStatusEnum.Success || evaluatorOutput.error) { + const errorMessage = evaluatorOutput.error || 'Evaluator execution failed'; + const evaluatorName = evaluator.metric.name || `Evaluator ${i + 1}`; + errors.push({ evaluatorName, error: errorMessage }); + } + + // Update evaluator output + evaluatorOutputs[i] = evaluatorOutput; + + // Save evaluator progress + await MongoEvalItem.updateOne( + { _id: new Types.ObjectId(evalItemId) }, + { $set: { evaluatorOutputs: evaluatorOutputs } } + ); + } catch (error) { + // Handle evaluator error + const errorMessage = getErrText(error) || 'Evaluator execution failed'; + const evaluatorName = evaluator.metric.name || `Evaluator ${i + 1}`; + errors.push({ evaluatorName, error: errorMessage }); + } + } + + // Check for evaluator errors + if (errors.length > 0) { + const errorDetails = errors.map((e) => `${e.evaluatorName}: ${e.error}`).join('; '); + addLog.error('[Evaluation] Multiple evaluator execution errors', { + errorCount: errors.length, + details: errorDetails, + errors: errors + }); + + // Use BullMQ error type + throw createEvaluationError( + EvaluationErrEnum.evalEvaluatorExecutionErrors, + 'EvaluatorExecute', + { + evalId, + evalItemId + }, + true + ); + } +}; + +/** + * Initialize evaluation item workers + */ +export const initEvalTaskItemWorker = () => { + return getEvaluationItemWorker(evaluationItemProcessor); +}; + +/** + * Export processors for testing + */ +export { evaluationItemProcessor }; diff --git a/packages/service/core/evaluation/task/schema.ts b/packages/service/core/evaluation/task/schema.ts new file mode 100644 index 000000000000..e07609586744 --- /dev/null +++ b/packages/service/core/evaluation/task/schema.ts @@ -0,0 +1,247 @@ +import { + TeamCollectionName, + TeamMemberCollectionName +} from '@fastgpt/global/support/user/team/constant'; +import { connectionMongo, getMongoModel } from '../../../common/mongo'; +import type { + EvaluationSchemaType, + EvaluationItemSchemaType +} from '@fastgpt/global/core/evaluation/type'; +import { UsageCollectionName } from '../../../support/wallet/usage/schema'; +import { + EvaluationStatusEnum, + EvaluationStatusValues, + SummaryStatusValues, + SummaryStatusEnum, + CaculateMethodValues, + CalculateMethodEnum +} from '@fastgpt/global/core/evaluation/constants'; +import { EvalDatasetCollectionName } from '../dataset/evalDatasetCollectionSchema'; + +const { Schema } = connectionMongo; + +export const EvaluationTargetSchema = new Schema( + { + type: { + type: String, + enum: ['workflow'], + required: true + }, + config: { + type: Schema.Types.Mixed, + required: true, + validate: { + validator: function (config: any) { + const targetType = (this as any).target?.type || (this as any).type; + if (targetType === 'workflow') { + return ( + config && + typeof config === 'object' && + config.appId != null && + config.versionId != null + ); + } + return false; + }, + message: + 'Target config must match the target type. Workflow targets require appId and versionId.' + } + } + }, + { _id: false } +); + +export const EvaluationEvaluatorSchema = new Schema( + { + metric: { + type: Schema.Types.Mixed, + required: true + }, + runtimeConfig: { + type: Schema.Types.Mixed, + required: false, + default: {} + }, + thresholdValue: { + type: Number, + required: false, + default: 0.8 + }, + scoreScaling: { + type: Number, + required: false, + default: 1, // Default no scaling + validate: { + validator: function (value: number) { + return ( + typeof value === 'number' && + !isNaN(value) && + isFinite(value) && + value > 0 && + value <= 10000 + ); // Support decimals like 0.01 for reduction + }, + message: + 'Score scaling must be a positive number greater than 0 and less than or equal to 10000' + } + } + }, + { + _id: false + } +); + +/** + * MongoDB collection names + */ +export const EvaluationCollectionName = 'evals'; +export const EvalItemCollectionName = 'eval_items'; + +export const EvaluationTaskSchema = new Schema({ + teamId: { + type: Schema.Types.ObjectId, + ref: TeamCollectionName, + required: true + }, + tmbId: { + type: Schema.Types.ObjectId, + ref: TeamMemberCollectionName, + required: true + }, + name: { + type: String, + required: true, + trim: true, + maxlength: 100 + }, + description: { + type: String, + default: '', + trim: true, + maxlength: 100 + }, + evalDatasetCollectionId: { + type: Schema.Types.ObjectId, + ref: EvalDatasetCollectionName, + required: true + }, + target: EvaluationTargetSchema, + evaluators: [EvaluationEvaluatorSchema], + summaryData: { + calculateType: { + type: String, + enum: CaculateMethodValues, + required: true, + default: CalculateMethodEnum.mean + }, + summaryConfigs: [ + { + metricId: { + type: String, + required: true + }, + metricName: { + type: String, + required: true + }, + weight: { + type: Number, + required: true + }, + summary: { + type: String, + default: '' + }, + summaryStatus: { + type: String, + enum: SummaryStatusValues, + default: SummaryStatusEnum.pending + }, + errorReason: { + type: String, + default: '' + }, + _id: false + } + ] + }, + usageId: { + type: Schema.Types.ObjectId, + ref: UsageCollectionName, + required: true + }, + createTime: { + type: Date, + required: true, + default: () => new Date() + }, + finishTime: Date, + errorMessage: String +}); + +/** + * Optimized indexes based on query patterns + */ +EvaluationTaskSchema.index({ _id: 1, teamId: 1 }); // Primary lookup +EvaluationTaskSchema.index({ teamId: 1, createTime: -1 }); // Team listing with time sort +EvaluationTaskSchema.index({ teamId: 1, tmbId: 1, createTime: -1 }); // Permission filtering +EvaluationTaskSchema.index({ teamId: 1, name: 1 }, { unique: true }); // Name uniqueness + +/** + * Evaluation item schema: atomic unit for evaluation + */ +export const EvaluationItemSchema = new Schema({ + evalId: { + type: Schema.Types.ObjectId, + ref: EvaluationCollectionName, + required: true + }, + // Data item configuration + dataItem: { + type: Object, + required: true + }, + // Execution results and outputs + targetOutput: { + type: Schema.Types.Mixed, + default: {} + }, + evaluatorOutputs: { + type: [Schema.Types.Mixed], + default: [] + }, + finishTime: Date, + errorMessage: String, + status: { + type: String, + enum: EvaluationStatusValues, + default: EvaluationStatusEnum.queuing + } +}); + +/** + * Evaluation item indexes for performance + */ +EvaluationItemSchema.index({ evalId: 1 }); // Basic queries +EvaluationItemSchema.index({ evalId: 1, createTime: -1 }); // Time-sorted listing + +// Status filtering indexes +EvaluationItemSchema.index({ evalId: 1, status: 1, createTime: -1 }); // Status with time +EvaluationItemSchema.index({ evalId: 1, status: 1 }); // Status only + +// Text search index for content filtering +EvaluationItemSchema.index({ + 'dataItem.userInput': 'text', + 'dataItem.expectedOutput': 'text', + 'targetOutput.actualOutput': 'text' +}); // Text search across inputs and outputs + +export const MongoEvaluation = getMongoModel( + EvaluationCollectionName, + EvaluationTaskSchema +); + +export const MongoEvalItem = getMongoModel( + EvalItemCollectionName, + EvaluationItemSchema +); diff --git a/packages/service/core/evaluation/task/statusCalculator.ts b/packages/service/core/evaluation/task/statusCalculator.ts new file mode 100644 index 000000000000..d1804873ba53 --- /dev/null +++ b/packages/service/core/evaluation/task/statusCalculator.ts @@ -0,0 +1,107 @@ +import { addLog } from '../../../common/system/log'; +import { EvaluationStatusEnum } from '@fastgpt/global/core/evaluation/constants'; +import { MongoEvalItem } from './schema'; +import { Types } from 'mongoose'; + +export async function getEvaluationTaskStats(evalId: string): Promise<{ + total: number; + completed: number; + evaluating: number; + queuing: number; + error: number; +}> { + try { + // Get all evaluation items from database - use same logic as finishEvaluationTask + const allEvalItems = await MongoEvalItem.find( + { evalId: new Types.ObjectId(evalId) }, + { _id: 1, status: 1 } + ).lean(); + + const totalItems = allEvalItems.length; + + if (totalItems === 0) { + return { + total: 0, + completed: 0, + evaluating: 0, + queuing: 0, + error: 0 + }; + } + + // Count status distribution - use same logic as finishEvaluationTask + let completed = 0; + let evaluating = 0; + let queuing = 0; + let error = 0; + + for (const item of allEvalItems) { + const status = item.status; + switch (status) { + case EvaluationStatusEnum.completed: + completed++; + break; + case EvaluationStatusEnum.error: + error++; + break; + case EvaluationStatusEnum.evaluating: + evaluating++; + break; + case EvaluationStatusEnum.queuing: + queuing++; + break; + } + } + + const stats = { + total: totalItems, + completed, + evaluating, + queuing, + error + }; + + return stats; + } catch (error) { + addLog.error('Error getting evaluation task stats:', { evalId, error }); + return { + total: 0, + completed: 0, + evaluating: 0, + queuing: 0, + error: 0 + }; + } +} + +export async function getEvaluationTaskStatus(evalId: string): Promise { + try { + const stats = await getEvaluationTaskStats(evalId); + + // If no items exist, task is in queuing state + if (stats.total === 0) { + return EvaluationStatusEnum.queuing; + } + + // If some items are evaluating or queuing, task is evaluating + if (stats.evaluating > 0 || stats.queuing > 0) { + return EvaluationStatusEnum.evaluating; + } + + // If any items have errors, task is in error state + if (stats.error > 0) { + return EvaluationStatusEnum.error; + } + + // If all items are completed, task is completed + if (stats.completed === stats.total) { + return EvaluationStatusEnum.completed; + } + + // This should not happen, but if it does, return error + return EvaluationStatusEnum.error; + } catch (error) { + addLog.error('Error getting evaluation task status:', { evalId, error }); + return EvaluationStatusEnum.error; + } +} diff --git a/packages/service/core/evaluation/utils/index.ts b/packages/service/core/evaluation/utils/index.ts new file mode 100644 index 000000000000..819942c5fc60 --- /dev/null +++ b/packages/service/core/evaluation/utils/index.ts @@ -0,0 +1,383 @@ +import { validateTargetConfig } from '../target'; +import { validateEvaluatorConfig } from '../evaluator'; +import type { CreateEvaluationParams } from '@fastgpt/global/core/evaluation/type'; +import type { ValidationResult } from '@fastgpt/global/core/evaluation/validate'; +import { EvaluationErrEnum } from '@fastgpt/global/common/error/code/evaluation'; +import { MAX_NAME_LENGTH, MAX_DESCRIPTION_LENGTH } from '@fastgpt/global/core/evaluation/constants'; +import { MongoEvalDatasetCollection } from '../dataset/evalDatasetCollectionSchema'; +import { MongoEvaluation } from '../task/schema'; +import { Types } from 'mongoose'; +import { EvalMetricTypeEnum } from '@fastgpt/global/core/evaluation/metric/constants'; +import { getBuiltinMetrics } from '../metric/provider'; + +export type EvaluationValidationParams = Partial; + +export interface EvaluationValidationOptions { + mode?: 'create' | 'update'; // validation mode + teamId?: string; // required for evalDatasetCollection existence validation + excludeEvaluationId?: string; // exclude current evaluation when checking name uniqueness +} + +/** + * Validate if a evalDatasetCollection exists and is accessible by the team + */ +async function validateEvalDatasetCollectionExists( + evalDatasetCollectionId: string, + teamId?: string +): Promise { + // Validate evalDatasetCollectionId format + if (!Types.ObjectId.isValid(evalDatasetCollectionId)) { + return { + isValid: false, + errors: [ + { + code: EvaluationErrEnum.datasetCollectionIdRequired, + message: 'Invalid evalDatasetCollectionId format', + field: 'evalDatasetCollectionId' + } + ] + }; + } + + // Check if evalDatasetCollection exists + const filter: any = { _id: new Types.ObjectId(evalDatasetCollectionId) }; + if (teamId) { + filter.teamId = new Types.ObjectId(teamId); + } + + const datasetCollection = await MongoEvalDatasetCollection.findOne(filter).lean(); + + if (!datasetCollection) { + return { + isValid: false, + errors: [ + { + code: EvaluationErrEnum.datasetCollectionNotFound, + message: 'evalDatasetCollection not found or access denied', + field: 'evalDatasetCollectionId' + } + ] + }; + } + + return { isValid: true, errors: [] }; +} + +/** + * Validate builtin metric name + */ +async function validateBuiltinMetricName( + metricName: string, + metricType: string +): Promise { + if (metricType !== EvalMetricTypeEnum.Builtin) { + return { isValid: true, errors: [] }; + } + + try { + const builtinMetrics = await getBuiltinMetrics(); + const validMetricNames = builtinMetrics.map((metric) => metric.name); + + if (!validMetricNames.includes(metricName)) { + return { + isValid: false, + errors: [ + { + code: EvaluationErrEnum.evalMetricNameInvalid, + message: `Invalid builtin metric name '${metricName}'. Valid builtin metrics are: ${validMetricNames.join(', ')}`, + field: 'metric.name', + debugInfo: { + providedName: metricName, + validNames: validMetricNames + } + } + ] + }; + } + + return { isValid: true, errors: [] }; + } catch (err) { + // If we can't load builtin metrics, log error but allow validation to pass + // This prevents blocking evaluation creation if metric service is temporarily unavailable + console.warn('Failed to validate builtin metric name:', err); + return { isValid: true, errors: [] }; + } +} + +export async function validateEvaluationParams( + params: EvaluationValidationParams, + options?: EvaluationValidationOptions +): Promise { + const { name, description, evalDatasetCollectionId, target, evaluators } = params; + const mode = options?.mode || 'create'; + const isCreateMode = mode === 'create'; + + // For create mode, check all required fields are present + if (isCreateMode) { + if (!name || !name.trim()) { + return { + isValid: false, + errors: [ + { + code: EvaluationErrEnum.evalNameRequired, + message: 'Evaluation name is required', + field: 'name' + } + ] + }; + } + + if (!evalDatasetCollectionId) { + return { + isValid: false, + errors: [ + { + code: EvaluationErrEnum.datasetCollectionIdRequired, + message: 'datasetCollectionId is required', + field: 'evalDatasetCollectionId' + } + ] + }; + } + + if (!target) { + return { + isValid: false, + errors: [ + { + code: EvaluationErrEnum.evalTargetRequired, + message: 'Evaluation target is required', + field: 'target' + } + ] + }; + } + + if (!evaluators || !Array.isArray(evaluators) || evaluators.length === 0) { + return { + isValid: false, + errors: [ + { + code: EvaluationErrEnum.evalEvaluatorsRequired, + message: 'At least one evaluator is required', + field: 'evaluators' + } + ] + }; + } + } + + // For update mode, only validate provided fields + if (name !== undefined) { + const trimmedName = typeof name === 'string' ? name.trim() : ''; + + if (!trimmedName) { + return { + isValid: false, + errors: [ + { + code: EvaluationErrEnum.evalNameRequired, + message: 'Evaluation name is required', + field: 'name' + } + ] + }; + } + + if (trimmedName.length > MAX_NAME_LENGTH) { + return { + isValid: false, + errors: [ + { + code: EvaluationErrEnum.evalNameTooLong, + message: 'Evaluation name is too long (max 100 characters)', + field: 'name', + debugInfo: { currentLength: trimmedName.length, maxLength: 100 } + } + ] + }; + } + + if (options?.teamId) { + const nameFilter: Record = { + teamId: new Types.ObjectId(options.teamId), + name: trimmedName + }; + + if (options.excludeEvaluationId && Types.ObjectId.isValid(options.excludeEvaluationId)) { + nameFilter._id = { $ne: new Types.ObjectId(options.excludeEvaluationId) }; + } + + const existingEvaluation = await MongoEvaluation.findOne(nameFilter).select('_id').lean(); + + if (existingEvaluation) { + return { + isValid: false, + errors: [ + { + code: EvaluationErrEnum.evalNameDuplicate, + message: 'Evaluation name already exists', + field: 'name', + debugInfo: { duplicatedResourceId: existingEvaluation._id.toString() } + } + ] + }; + } + } + } + + if (description !== undefined && description && description.length > MAX_DESCRIPTION_LENGTH) { + return { + isValid: false, + errors: [ + { + code: EvaluationErrEnum.evalDescriptionTooLong, + message: 'Description is too long (max 100 characters)', + field: 'description', + debugInfo: { currentLength: description.length, maxLength: 100 } + } + ] + }; + } + + if (evalDatasetCollectionId !== undefined) { + if (!evalDatasetCollectionId) { + return { + isValid: false, + errors: [ + { + code: EvaluationErrEnum.datasetCollectionIdRequired, + message: 'datasetCollectionId is required', + field: 'evalDatasetCollectionId' + } + ] + }; + } + + // Validate evaldatasetcollection exists and is accessible + const datasetValidation = await validateEvalDatasetCollectionExists( + evalDatasetCollectionId, + options?.teamId + ); + if (!datasetValidation.isValid) { + return datasetValidation; + } + } + + if (target !== undefined) { + if (!target) { + return { + isValid: false, + errors: [ + { + code: EvaluationErrEnum.evalTargetRequired, + message: 'Evaluation target is required', + field: 'target' + } + ] + }; + } + + // Validate target configuration using validateTargetConfig + const targetValidation = await validateTargetConfig(target); + if (!targetValidation.isValid) { + return targetValidation; // Return the detailed validation result directly + } + } + + if (evaluators !== undefined) { + if (!evaluators || !Array.isArray(evaluators) || evaluators.length === 0) { + return { + isValid: false, + errors: [ + { + code: EvaluationErrEnum.evalEvaluatorsRequired, + message: 'At least one evaluator is required', + field: 'evaluators' + } + ] + }; + } + + // Validate evaluators configuration using validateEvaluatorConfig + for (let i = 0; i < evaluators.length; i++) { + const evaluator = evaluators[i]; + + // Detailed validation using validateEvaluatorConfig + const evaluatorValidation = await validateEvaluatorConfig(evaluator); + if (!evaluatorValidation.isValid) { + // Prefix error messages with evaluator index for clarity + const errors = evaluatorValidation.errors.map((err) => ({ + ...err, + message: `Evaluator at index ${i}: ${err.message}`, + field: `evaluators[${i}].${err.field || 'unknown'}`, + debugInfo: { + evaluatorIndex: i, + ...err.debugInfo + } + })); + return { isValid: false, errors }; + } + + // Validate builtin metric name if type is builtin_metric + if (evaluator.metric) { + const builtinMetricValidation = await validateBuiltinMetricName( + evaluator.metric.name, + evaluator.metric.type + ); + if (!builtinMetricValidation.isValid) { + // Prefix error messages with evaluator index for clarity + const errors = builtinMetricValidation.errors.map((err) => ({ + ...err, + message: `Evaluator at index ${i}: ${err.message}`, + field: `evaluators[${i}].${err.field || 'unknown'}`, + debugInfo: { + evaluatorIndex: i, + ...err.debugInfo + } + })); + return { isValid: false, errors }; + } + } + + // Validate scoreScaling if provided + if (evaluator.scoreScaling !== undefined) { + if ( + typeof evaluator.scoreScaling !== 'number' || + isNaN(evaluator.scoreScaling) || + !isFinite(evaluator.scoreScaling) || + evaluator.scoreScaling <= 0 || + evaluator.scoreScaling > 10000 + ) { + return { + isValid: false, + errors: [ + { + code: EvaluationErrEnum.evalEvaluatorInvalidScoreScaling, + message: 'Evaluator scoreScaling invalid', + field: 'scoreScaling' + } + ] + }; + } + } + } + } + + return { isValid: true, errors: [] }; +} + +export async function validateEvaluationParamsForCreate( + params: EvaluationValidationParams, + teamId?: string +): Promise { + return validateEvaluationParams(params, { mode: 'create', teamId }); +} + +export async function validateEvaluationParamsForUpdate( + params: EvaluationValidationParams, + teamId?: string, + excludeEvaluationId?: string +): Promise { + return validateEvaluationParams(params, { mode: 'update', teamId, excludeEvaluationId }); +} diff --git a/packages/service/core/evaluation/utils/mq.ts b/packages/service/core/evaluation/utils/mq.ts new file mode 100644 index 000000000000..db4a6003b6fb --- /dev/null +++ b/packages/service/core/evaluation/utils/mq.ts @@ -0,0 +1,276 @@ +import type { Queue, Job } from 'bullmq'; +import { addLog } from '../../../common/system/log'; + +export interface JobCleanupResult { + queue: string; + totalJobs: number; + removedJobs: number; + failedRemovals: number; + errors: Array<{ + jobId: string; + error: string; + }>; +} + +export interface JobCleanupOptions { + forceCleanActiveJobs?: boolean; + retryAttempts?: number; + retryDelay?: number; +} + +const DEFAULT_OPTIONS: JobCleanupOptions = { + forceCleanActiveJobs: false, + retryAttempts: 3, + retryDelay: 1000 +}; + +export class RobustJobCleaner { + private options: JobCleanupOptions; + + constructor(options: JobCleanupOptions = {}) { + this.options = { ...DEFAULT_OPTIONS, ...options }; + } + + async cleanAllJobsByFilter( + queue: Queue, + filterFn: (job: Job) => boolean, + queueName: string + ): Promise { + const result: JobCleanupResult = { + queue: queueName, + totalJobs: 0, + removedJobs: 0, + failedRemovals: 0, + errors: [] + }; + + try { + // Get all possible job states + const jobStates = ['waiting', 'active', 'completed', 'failed', 'delayed', 'prioritized']; + const jobsByState: Record[]> = {}; + let totalMatchingJobs = 0; + + // Fetch jobs from all states and group them + for (const state of jobStates) { + try { + const jobs = await queue.getJobs([state as any]); + const filteredJobs = jobs.filter(filterFn); + + if (filteredJobs.length > 0) { + jobsByState[state] = filteredJobs; + totalMatchingJobs += filteredJobs.length; + } + } catch (error) { + addLog.warn(`Failed to get jobs from state ${state} in queue ${queueName}`, { error }); + } + } + + result.totalJobs = totalMatchingJobs; + + if (totalMatchingJobs === 0) { + addLog.info(`No jobs found to clean in queue ${queueName}`); + return result; + } + + // Clean non-active jobs first + await this.cleanJobsByStates( + jobsByState, + ['waiting', 'delayed', 'prioritized', 'completed', 'failed'], + result + ); + + // Handle active jobs if force cleanup is enabled + if (this.options.forceCleanActiveJobs && jobsByState.active?.length > 0) { + await this.cleanActiveJobs(jobsByState.active, result); + } else if (jobsByState.active?.length > 0) { + addLog.warn( + `${jobsByState.active.length} active jobs found but force cleanup is disabled`, + { queue: queueName } + ); + } + + addLog.info('Job cleanup completed', { + queue: queueName, + totalJobs: result.totalJobs, + removedJobs: result.removedJobs, + failedRemovals: result.failedRemovals + }); + + return result; + } catch (error) { + addLog.error('Fatal error during job cleanup', { queue: queueName, error }); + result.errors.push({ + jobId: 'FATAL', + error: error instanceof Error ? error.message : 'Unknown error' + }); + return result; + } + } + + private async cleanJobsByStates( + jobsByState: Record[]>, + states: string[], + result: JobCleanupResult + ): Promise { + for (const state of states) { + const jobs = jobsByState[state]; + if (!jobs || jobs.length === 0) continue; + + addLog.info(`Cleaning ${jobs.length} jobs in state: ${state}`, { queue: result.queue }); + + // Use Promise.allSettled to handle individual failures gracefully + const removePromises = jobs.map((job) => this.removeJobWithRetry(job)); + const removeResults = await Promise.allSettled(removePromises); + + removeResults.forEach((removeResult, index) => { + if (removeResult.status === 'fulfilled') { + if (removeResult.value.success) { + result.removedJobs++; + } else { + result.failedRemovals++; + if (removeResult.value.error) { + result.errors.push(removeResult.value.error); + } + } + } else { + result.failedRemovals++; + result.errors.push({ + jobId: jobs[index].id || 'unknown', + error: removeResult.reason?.message || 'Promise rejected' + }); + } + }); + } + } + + private async cleanActiveJobs(activeJobs: Job[], result: JobCleanupResult): Promise { + addLog.warn( + `Force cleaning ${activeJobs.length} active jobs - this may interrupt running processes`, + { queue: result.queue } + ); + + // For active jobs, we need a more aggressive approach + const removePromises = activeJobs.map((job) => this.forceRemoveActiveJob(job)); + const removeResults = await Promise.allSettled(removePromises); + + removeResults.forEach((removeResult, index) => { + if (removeResult.status === 'fulfilled') { + if (removeResult.value.success) { + result.removedJobs++; + } else { + result.failedRemovals++; + if (removeResult.value.error) { + result.errors.push(removeResult.value.error); + } + } + } else { + result.failedRemovals++; + result.errors.push({ + jobId: activeJobs[index].id || 'unknown', + error: removeResult.reason?.message || 'Promise rejected' + }); + } + }); + } + + private async removeJobWithRetry(job: Job): Promise<{ + success: boolean; + error?: { jobId: string; error: string }; + }> { + let lastError: Error | null = null; + + for (let attempt = 1; attempt <= this.options.retryAttempts!; attempt++) { + try { + await job.remove(); + return { success: true }; + } catch (error) { + lastError = error instanceof Error ? error : new Error('Unknown error'); + + if (attempt < this.options.retryAttempts!) { + addLog.warn(`Job removal attempt ${attempt} failed, retrying...`, { + jobId: job.id, + error: lastError.message + }); + await this.sleep(this.options.retryDelay!); + } + } + } + + return { + success: false, + error: { + jobId: job.id || 'unknown', + error: lastError?.message || 'Failed to remove job after retries' + } + }; + } + + private async forceRemoveActiveJob(job: Job): Promise<{ + success: boolean; + error?: { jobId: string; error: string }; + }> { + try { + // First try normal removal + try { + await job.remove(); + return { success: true }; + } catch (normalError) { + // If normal removal fails for active job, try more aggressive methods + addLog.warn(`Normal removal failed for active job, trying force removal`, { + jobId: job.id, + error: normalError + }); + + // Try to move the job to failed state first, then remove + try { + await job.moveToFailed(new Error('Force cleanup'), 'cleanup'); + await job.remove(); + return { success: true }; + } catch (forceError) { + // Last resort: just mark it as completed and remove + try { + await job.moveToCompleted('force-cleaned', 'cleanup'); + await job.remove(); + return { success: true }; + } catch (lastResortError) { + return { + success: false, + error: { + jobId: job.id || 'unknown', + error: `All removal methods failed: ${lastResortError}` + } + }; + } + } + } + } catch (error) { + return { + success: false, + error: { + jobId: job.id || 'unknown', + error: error instanceof Error ? error.message : 'Unknown error' + } + }; + } + } + + private sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); + } +} + +export const createJobCleaner = (options?: JobCleanupOptions): RobustJobCleaner => { + return new RobustJobCleaner(options); +}; + +export const checkBullMQHealth = async (queue: Queue, queueName: string): Promise => { + try { + await queue.isPaused(); + await queue.getWaiting(0, 0); + } catch (error) { + addLog.error(`BullMQ ${queueName} queue health check failed:`, error); + throw new Error( + `BullMQ ${queueName} queue is not responding. Please check Redis connection and queue status.` + ); + } +}; diff --git a/packages/service/core/evaluation/utils/tokenLimiter.ts b/packages/service/core/evaluation/utils/tokenLimiter.ts new file mode 100644 index 000000000000..37c8837e6510 --- /dev/null +++ b/packages/service/core/evaluation/utils/tokenLimiter.ts @@ -0,0 +1,27 @@ +import { findModelFromAlldata, getEvaluationModel } from '../../ai/model'; +import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d'; +import { addLog } from '../../../common/system/log'; +import { MAX_TOKEN_FOR_EVALUATION_SUMMARY } from '@fastgpt/global/core/evaluation/constants'; +import { EvaluationErrEnum } from '@fastgpt/global/common/error/code/evaluation'; + +export const getEvaluationSummaryModel = (llmModel?: string): LLMModelItemType => { + let evalutionModel = getEvaluationModel(llmModel); + if (evalutionModel) return evalutionModel; + throw new Error(EvaluationErrEnum.summaryModelInvalid); +}; + +export const getEvaluationSummaryTokenLimit = (llmModel: string): number => { + let modelConfig = getEvaluationSummaryModel(llmModel); + + // Calculate token limit: model max context - reserved tokens for response + const tokenLimit = modelConfig.maxContext - MAX_TOKEN_FOR_EVALUATION_SUMMARY; + + addLog.debug('[EvaluationSummary] Calculate token limit', { + llmModel: llmModel || 'default', + modelName: modelConfig.name, + maxContext: modelConfig.maxContext, + tokenLimit + }); + + return tokenLimit; +}; diff --git a/packages/service/core/evaluation/utils/usage.ts b/packages/service/core/evaluation/utils/usage.ts new file mode 100644 index 000000000000..ceae772bae6a --- /dev/null +++ b/packages/service/core/evaluation/utils/usage.ts @@ -0,0 +1,33 @@ +import { concatUsage, evaluationUsageIndexMap } from '../../../support/wallet/usage/controller'; +import { addLog } from '../../../common/system/log'; + +/** + * Create merged evaluation usage record + */ +export const createMergedEvaluationUsage = async (params: { + evalId: string; + teamId: string; + tmbId: string; + usageId: string; + totalPoints: number; + type: 'target' | 'metric' | 'summary'; + inputTokens?: number; + outputTokens?: number; +}) => { + const { evalId, teamId, tmbId, usageId, totalPoints, type, inputTokens, outputTokens } = params; + + const listIndex = evaluationUsageIndexMap[type]; + + await concatUsage({ + billId: usageId, + teamId, + tmbId, + totalPoints, + inputTokens: inputTokens || 0, + outputTokens: outputTokens || 0, + count: 1, + listIndex + }); + + addLog.debug(`[Evaluation] Record usage: ${evalId}, ${type}, ${totalPoints} points`); +}; diff --git a/packages/service/core/workflow/dispatch/dataset/search.ts b/packages/service/core/workflow/dispatch/dataset/search.ts index dd68b38c0329..8e47de76c0b5 100644 --- a/packages/service/core/workflow/dispatch/dataset/search.ts +++ b/packages/service/core/workflow/dispatch/dataset/search.ts @@ -5,15 +5,31 @@ import { import { formatModelChars2Points } from '../../../../support/wallet/usage/utils'; import type { SelectedDatasetType } from '@fastgpt/global/core/workflow/type/io'; import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type'; +import type { SearchDatasetDataResponse } from '../../../dataset/search/controller'; +import type { + SqlGenerationResponse, + SqlResultWithDatasetId +} from '@fastgpt/global/core/dataset/database/api'; import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type'; -import { getEmbeddingModel, getRerankModel } from '../../../ai/model'; -import { deepRagSearch, defaultSearchDatasetData } from '../../../dataset/search/controller'; +import { + getEmbeddingModel, + getLLMModel, + getRerankModel +} from '../../../ai/model'; +import { + deepRagSearch, + defaultSearchDatasetData, + SearchDatabaseData, + generateAndExecuteSQL +} from '../../../dataset/search/controller'; +import { calculateDynamicLimit } from '../../../dataset/search/utils'; import type { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants'; -import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants'; +import { DatasetSearchModeEnum, DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants'; import { type ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type'; import { MongoDataset } from '../../../dataset/schema'; import { i18nT } from '../../../../../web/i18n/utils'; +import { addLog } from '../../../../common/system/log'; import { filterDatasetsByTmbId } from '../../../dataset/utils'; import { ModelTypeEnum } from '@fastgpt/global/core/ai/model'; import { getDatasetSearchToolResponsePrompt } from '../../../../../global/core/ai/prompt/dataset'; @@ -42,6 +58,8 @@ type DatasetSearchProps = ModuleDispatchProps<{ [NodeInputKeyEnum.datasetDeepSearchModel]?: string; [NodeInputKeyEnum.datasetDeepSearchMaxTimes]?: number; [NodeInputKeyEnum.datasetDeepSearchBg]?: string; + + [NodeInputKeyEnum.generateSqlModel]?: string; }>; export type DatasetSearchResponse = DispatchNodeResultType<{ [NodeOutputKeyEnum.datasetQuoteQA]: SearchDataResponseItemType[]; @@ -71,7 +89,7 @@ export async function dispatchDatasetSearch( datasetSearchUsingExtensionQuery, datasetSearchExtensionModel, datasetSearchExtensionBg, - + generateSqlModel, datasetDeepSearch, datasetDeepSearchModel, datasetDeepSearchMaxTimes, @@ -124,44 +142,175 @@ export async function dispatchDatasetSearch( // Get Rerank Model const rerankModelData = getRerankModel(rerankModel); - // start search - const searchData = { - histories, - teamId, - reRankQuery: userChatInput, - queries: [userChatInput], - model: vectorModel.model, - similarity, - limit, - datasetIds, - searchMode, - embeddingWeight, - usingReRank, - rerankModel: rerankModelData, - rerankWeight, - collectionFilterMatch + // Check dataset types and separate them + const datasetDetails = await Promise.all( + datasetIds.map((id) => MongoDataset.findById(id, 'type databaseConfig').lean()) + ); + + const databaseDatasetIds = datasetIds.filter( + (_, index) => datasetDetails[index]?.type === DatasetTypeEnum.database + ); + const commonDatasetIds = datasetIds.filter( + (_, index) => datasetDetails[index]?.type !== DatasetTypeEnum.database + ); + + // Results from different search types + let commonSearchResult = null; + let totalEmbeddingTokens = 0; + // Handle different response types and merge results + let searchRes: SearchDataResponseItemType[] = []; + let embeddingTokens = 0; + let reRankInputTokens = 0; + let usingSimilarityFilter = false; + let searchUsingReRank = false; + let queryExtensionResult = undefined; + let deepSearchResult = undefined; + let sqlResult: SqlResultWithDatasetId[] = []; + + const convertSqlResultsToChunks = async ( + singleSQLResult: SqlGenerationResponse, + datasetId: string + ): Promise => { + const sourceName = + (await MongoDataset.findById(datasetId, 'name') + .lean() + ?.then((doc) => doc?.name)) || 'Unknown Dataset'; + const uuid = `sql_quote_${datasetId}`; + return { + id: uuid, + updateTime: new Date(), + q: singleSQLResult.answer, // Use the original query as question + a: singleSQLResult.sql, // Use the generated answer as content + chunkIndex: 0, + datasetId: datasetId, + collectionId: '', + sourceName: sourceName, + sourceId: uuid, + score: [] // Empty score array as requested + }; }; - const { - searchRes, - embeddingTokens, - reRankInputTokens, - usingSimilarityFilter, - usingReRank: searchUsingReRank, - queryExtensionResult, - deepSearchResult - } = datasetDeepSearch - ? await deepRagSearch({ - ...searchData, - datasetDeepSearchModel, - datasetDeepSearchMaxTimes, - datasetDeepSearchBg + // Database search for database datasets - search each dataset individually and generate SQL + if (databaseDatasetIds.length > 0) { + const sqlLLM = getLLMModel(generateSqlModel); + // Calculate dynamic limit based on generateSqlModel's maxContext + const dynamicLimit = calculateDynamicLimit({ + generateSqlModel: sqlLLM.name, + safetyFactor: 0.6, + estimatedTokensPerItem: 1024 // Assume each item may consume around 1000 tokens after formatting + }); + + addLog.debug('Dataset Search - Using dynamic limit for database search', { + generateSqlModel: sqlLLM.name, + calculatedLimit: dynamicLimit + }); + + // Process each database dataset sequentially + await Promise.all( + datasetIds.map(async (datasetId) => { + const singleResult = await SearchDatabaseData({ + histories, + teamId, + queries: [userChatInput], + model: vectorModel.model, + limit: dynamicLimit, + datasetIds: [datasetId] + }); + if (singleResult) { + totalEmbeddingTokens += singleResult.tokens; + if (Object.keys(singleResult.schema).length > 0) { + const key = sqlLLM.requestAuth || undefined; + const url = sqlLLM.requestUrl?.replace(/(chat\/completions.*)$/, '') || undefined; + const singleSqlResult = await generateAndExecuteSQL({ + datasetId, + query: userChatInput, + schema: singleResult.schema, + teamId, + limit, + generate_sql_llm: { + model: sqlLLM.model, + api_key: key, + base_url:url + }, + evaluate_sql_llm: { + model: sqlLLM.model, + api_key: key, + base_url: url + } + }); + + if (singleSqlResult) { + // Collect for billing and response data + sqlResult.push({ + ...singleSqlResult, + datasetId + } as SqlResultWithDatasetId); + // convertSqlResultsToChunks + searchRes.push(await convertSqlResultsToChunks(singleSqlResult, datasetId)); + } else { + addLog.warn('Dataset Search - SQL Generation Failed', { datasetId }); + } + } else { + addLog.warn('Dataset Search - No schema found', { datasetId }); + } + } else { + addLog.warn('Dataset Search - Database search failed', { datasetId }); + } }) - : await defaultSearchDatasetData({ - ...searchData, - datasetSearchUsingExtensionQuery, - datasetSearchExtensionModel, - datasetSearchExtensionBg - }); + ); + } + if (commonDatasetIds.length > 0) { + const searchData = { + histories, + teamId, + reRankQuery: userChatInput, + queries: [userChatInput], + model: vectorModel.model, + similarity, + limit, + datasetIds: commonDatasetIds, + searchMode, + embeddingWeight, + usingReRank, + rerankModel: rerankModelData, + rerankWeight, + collectionFilterMatch + }; + + commonSearchResult = datasetDeepSearch + ? await deepRagSearch({ + ...searchData, + datasetDeepSearchModel, + datasetDeepSearchMaxTimes, + datasetDeepSearchBg + }) + : await defaultSearchDatasetData({ + ...searchData, + datasetSearchUsingExtensionQuery, + datasetSearchExtensionModel, + datasetSearchExtensionBg + }); + } + + embeddingTokens += totalEmbeddingTokens; + + addLog.info('Dataset Search - Merging Results', { + databaseResults: sqlResult.length, + embeddingTokens, + totalEmbeddingTokens + }); + + // Handle traditional search results + if (commonSearchResult) { + const commonResult = commonSearchResult as SearchDatasetDataResponse; + // Merge traditional search results with database chunks + searchRes = [...searchRes, ...commonResult.searchRes]; + embeddingTokens += commonResult.embeddingTokens; // Accumulate embedding tokens + reRankInputTokens = commonResult.reRankInputTokens; + usingSimilarityFilter = commonResult.usingSimilarityFilter; + searchUsingReRank = commonResult.usingReRank; + queryExtensionResult = commonResult.queryExtensionResult; + deepSearchResult = commonResult.deepSearchResult; + } // count bill results const nodeDispatchUsages: ChatNodeUsageType[] = []; @@ -241,8 +390,44 @@ export async function dispatchDatasetSearch( }; })(); + // SQL Generation (for database datasets) + (() => { + if (sqlResult.length > 0) { + let totalSqlPoints = 0; + sqlResult.forEach((result, index) => { + const { totalPoints, modelName } = formatModelChars2Points({ + model: vectorModel.model, // Use the same model as vector search + inputTokens: result.input_tokens, + outputTokens: result.output_tokens, + modelType: ModelTypeEnum.llm + }); + nodeDispatchUsages.push({ + totalPoints, + moduleName: i18nT('common:database_search'), + model: modelName, + inputTokens: result.input_tokens, + outputTokens: result.output_tokens + }); + totalSqlPoints += totalPoints; + }); + return { + totalPoints: totalSqlPoints + }; + } + return { + totalPoints: 0 + }; + })(); const totalPoints = nodeDispatchUsages.reduce((acc, item) => acc + item.totalPoints, 0); + addLog.debug('Dataset Search - Final Statistics', { + totalSearchResults: searchRes.length, + totalPoints, + totalEmbeddingTokens: embeddingTokens, + totalSqlResults: sqlResult.length, + nodeDispatchUsagesCount: nodeDispatchUsages.length + }); + const responseData: DispatchNodeResponseType & { totalPoints: number } = { totalPoints, query: userChatInput, @@ -259,6 +444,8 @@ export async function dispatchDatasetSearch( rerankWeight: rerankWeight, reRankInputTokens }), + // SQL Result (for database datasets) - use first result or create summary + sqlResult: sqlResult, searchUsingReRank, // Results quoteList: searchRes, @@ -274,7 +461,7 @@ export async function dispatchDatasetSearch( nodeDispatchUsages, [DispatchNodeResponseKeyEnum.toolResponses]: { prompt: getDatasetSearchToolResponsePrompt(), - cites: searchRes.map((item) => ({ + cites: searchRes.map((item: SearchDataResponseItemType) => ({ id: item.id, sourceName: item.sourceName, updateTime: item.updateTime, diff --git a/packages/service/core/workflow/utils.ts b/packages/service/core/workflow/utils.ts index b78152f64154..09a031ac1335 100644 --- a/packages/service/core/workflow/utils.ts +++ b/packages/service/core/workflow/utils.ts @@ -53,7 +53,7 @@ export async function getSystemToolRunTimeNodeFromSystemToolset({ (item) => item.toolId === child.id ); - const tool = await getSystemPluginByIdAndVersionId(child.id); + const tool = await getSystemPluginByIdAndVersionId(child.id, undefined, lang); const inputs = tool.inputs ?? []; if (toolsetInputConfig?.value) { diff --git a/packages/service/package.json b/packages/service/package.json index 120edf9f4f97..ec4e1ae81e4b 100644 --- a/packages/service/package.json +++ b/packages/service/package.json @@ -20,6 +20,7 @@ "bullmq": "^5.52.2", "chalk": "^5.3.0", "cheerio": "1.0.0-rc.12", + "chinese-conv": "^3.2.2", "cookie": "^0.7.1", "date-fns": "2.30.0", "dayjs": "^1.11.7", @@ -28,6 +29,7 @@ "encoding": "^0.1.13", "file-type": "^19.0.0", "form-data": "^4.0.4", + "franc": "^6.2.0", "iconv-lite": "^0.6.3", "ioredis": "^5.6.0", "joplin-turndown-plugin-gfm": "^1.0.12", @@ -38,7 +40,9 @@ "mammoth": "^1.6.0", "mongoose": "^8.10.1", "multer": "2.0.2", - "mysql2": "^3.11.3", + "mysql2": "^3.14.3", + "typeorm": "^0.3.24", + "reflect-metadata": "^0.2.2", "next": "14.2.28", "nextjs-cors": "^2.2.0", "node-cron": "^3.0.3", diff --git a/packages/service/support/permission/evaluation/auth.ts b/packages/service/support/permission/evaluation/auth.ts index 1622ef31e7e8..1b420ddc657f 100644 --- a/packages/service/support/permission/evaluation/auth.ts +++ b/packages/service/support/permission/evaluation/auth.ts @@ -1,64 +1,374 @@ +/* Auth evaluation permission */ import { parseHeaderCert } from '../controller'; -import { authAppByTmbId } from '../app/auth'; +import { getResourcePermission } from '../controller'; import { - ManagePermissionVal, - ReadPermissionVal + OwnerPermissionVal, + PerResourceTypeEnum, + ReadPermissionVal, + ReadRoleVal } from '@fastgpt/global/support/permission/constant'; -import type { EvaluationSchemaType } from '@fastgpt/global/core/app/evaluation/type'; -import type { AuthModeType } from '../type'; -import { MongoEvaluation } from '../../../core/app/evaluation/evalSchema'; +import { getTmbInfoByTmbId } from '../../user/team/controller'; +import type { EvaluationWithPerType } from '@fastgpt/global/core/evaluation/type'; +import type { AuthModeType, AuthResponseType } from '../type'; +import type { PermissionValueType } from '@fastgpt/global/support/permission/type'; +import { MongoEvaluation } from '../../../core/evaluation/task'; +import { EvaluationPermission } from '@fastgpt/global/support/permission/evaluation/controller'; +import type { DatasetFileSchema } from '@fastgpt/global/core/dataset/type'; +import { getFileById } from '../../../common/file/gridfs/controller'; +import { BucketNameEnum } from '@fastgpt/global/common/file/constants'; +import { CommonErrEnum } from '@fastgpt/global/common/error/code/common'; +import { Permission } from '@fastgpt/global/support/permission/controller'; +import { EvaluationErrEnum } from '@fastgpt/global/common/error/code/evaluation'; -export const authEval = async ({ - evalId, - per = ReadPermissionVal, - ...props -}: AuthModeType & { - evalId: string; -}): Promise<{ - evaluation: EvaluationSchemaType; +// ================ Authentication and Authorization for eval task ================ +export const authEvaluationByTmbId = async ({ + tmbId, + evaluationId, + per, + isRoot +}: { tmbId: string; - teamId: string; -}> => { - const { teamId, tmbId, isRoot } = await parseHeaderCert(props); + evaluationId: string; + per: PermissionValueType; + isRoot?: boolean; +}): Promise<{ evaluation: EvaluationWithPerType }> => { + const { teamId, permission: tmbPer } = await getTmbInfoByTmbId({ tmbId }); - const evaluation = await MongoEvaluation.findById(evalId, 'tmbId').lean(); + const evaluation = await MongoEvaluation.findOne({ _id: evaluationId }).lean(); if (!evaluation) { - return Promise.reject('Evaluation not found'); + return Promise.reject(EvaluationErrEnum.evalTaskNotFound); } - if (String(evaluation.tmbId) === tmbId) { + // Root用户权限特殊处理 + if (isRoot) { return { + evaluation: { + ...evaluation, + permission: new EvaluationPermission({ isOwner: true }) + } + }; + } + + // 团队权限验证 + if (String(evaluation.teamId) !== teamId) { + return Promise.reject(EvaluationErrEnum.evalTaskNotFound); + } + + // 所有者检查 + const isOwner = tmbPer.isOwner || String(evaluation.tmbId) === String(tmbId); + + // 权限计算 + const { Per } = await (async () => { + if (isOwner) { + return { Per: new EvaluationPermission({ isOwner: true }) }; + } + + // 获取评估资源的权限 + const role = await getResourcePermission({ teamId, tmbId, - evaluation + resourceId: evaluationId, + resourceType: PerResourceTypeEnum.evaluation + }); + + return { Per: new EvaluationPermission({ role, isOwner }) }; + })(); + + // 权限验证 + if (!Per.checkPer(per)) { + return Promise.reject(EvaluationErrEnum.evalInsufficientPermission); + } + + return { + evaluation: { + ...evaluation, + permission: Per + } + }; +}; + +export const authEvaluation = async ({ + evaluationId, + per = ReadPermissionVal, + ...props +}: AuthModeType & { + evaluationId: string; + per?: PermissionValueType; +}): Promise< + AuthResponseType & { + evaluation: EvaluationWithPerType; + } +> => { + const result = await parseHeaderCert({ + ...props, + authApiKey: true // 添加API Key支持 + }); + const { tmbId } = result; + + if (!evaluationId) { + return Promise.reject(EvaluationErrEnum.evalTaskNotFound); + } + + const { evaluation } = await authEvaluationByTmbId({ + tmbId, + evaluationId, + per, + isRoot: result.isRoot + }); + + return { + ...result, + permission: evaluation.permission, + evaluation + }; +}; + +// ================ Authentication and Authorization for eval dataset ================ +export const authEvalDatasetByTmbId = async ({ + tmbId, + datasetId, + per, + isRoot +}: { + tmbId: string; + datasetId: string; + per: PermissionValueType; + isRoot?: boolean; +}): Promise<{ dataset: any }> => { + const { MongoEvalDatasetCollection } = await import( + '../../../core/evaluation/dataset/evalDatasetCollectionSchema' + ); + const { teamId, permission: tmbPer } = await getTmbInfoByTmbId({ tmbId }); + + const dataset = await MongoEvalDatasetCollection.findOne({ _id: datasetId }).lean(); + if (!dataset) { + return Promise.reject(EvaluationErrEnum.datasetCollectionNotFound); + } + + // Root用户权限特殊处理 + if (isRoot) { + return { + dataset: { + ...dataset, + permission: new EvaluationPermission({ isOwner: true }) + } }; } - // App read per - if (per === ReadPermissionVal) { - await authAppByTmbId({ + // 团队权限验证 + if (String(dataset.teamId) !== teamId) { + return Promise.reject(EvaluationErrEnum.datasetCollectionNotFound); + } + + // 所有者检查 + const isOwner = tmbPer.isOwner || String(dataset.tmbId) === String(tmbId); + + // 权限计算 - 使用evaluation资源类型 + const { Per } = await (async () => { + if (isOwner) { + return { Per: new EvaluationPermission({ isOwner: true }) }; + } + + // 获取evaluation资源的权限(evalDataset复用evaluation权限) + const role = await getResourcePermission({ + teamId, tmbId, - appId: evaluation.appId, - per: ReadPermissionVal, - isRoot + resourceId: datasetId, + resourceType: PerResourceTypeEnum.evaluation }); + + return { Per: new EvaluationPermission({ role, isOwner }) }; + })(); + + // 权限验证 + if (!Per.checkPer(per)) { + return Promise.reject(EvaluationErrEnum.evalInsufficientPermission); + } + + return { + dataset: { + ...dataset, + permission: Per + } + }; +}; + +export const authEvalDataset = async ({ + datasetId, + per = ReadPermissionVal, + ...props +}: AuthModeType & { + datasetId: string; + per?: PermissionValueType; +}): Promise< + AuthResponseType & { + dataset: any; + } +> => { + const result = await parseHeaderCert({ + ...props, + authApiKey: true // 添加API Key支持 + }); + const { tmbId } = result; + + if (!datasetId) { + return Promise.reject(EvaluationErrEnum.datasetCollectionNotFound); + } + + const { dataset } = await authEvalDatasetByTmbId({ + tmbId, + datasetId, + per, + isRoot: result.isRoot + }); + + return { + ...result, + permission: dataset.permission, + dataset + }; +}; + +export const authEvalDatasetCollectionFile = async ({ + fileId, + per = OwnerPermissionVal, + ...props +}: AuthModeType & { + fileId: string; +}): Promise< + AuthResponseType & { + file: DatasetFileSchema; + } +> => { + const authRes = await parseHeaderCert(props); + const { teamId, tmbId } = authRes; + + const file = await getFileById({ bucketName: BucketNameEnum.evaluation, fileId }); + + if (!file) { + return Promise.reject(CommonErrEnum.fileNotFound); + } + + if (file.metadata?.teamId !== teamId) { + return Promise.reject(CommonErrEnum.unAuthFile); + } + + const permission = new Permission({ + role: ReadRoleVal, + isOwner: file.metadata?.uid === tmbId || file.metadata?.tmbId === tmbId + }); + + if (!permission.checkPer(per)) { + return Promise.reject(CommonErrEnum.unAuthFile); + } + + return { + ...authRes, + permission, + file + }; +}; + +// ================ Authentication and Authorization for eval metric ================ +export const authEvalMetricByTmbId = async ({ + tmbId, + metricId, + per, + isRoot +}: { + tmbId: string; + metricId: string; + per: PermissionValueType; + isRoot?: boolean; +}): Promise<{ metric: any }> => { + const { MongoEvalMetric } = await import('../../../core/evaluation/metric/schema'); + const { teamId, permission: tmbPer } = await getTmbInfoByTmbId({ tmbId }); + + const metric = await MongoEvalMetric.findOne({ _id: metricId }).lean(); + if (!metric) { + return Promise.reject(EvaluationErrEnum.evalMetricNotFound); + } + + // Root用户权限特殊处理 + if (isRoot) { return { + metric: { + ...metric, + permission: new EvaluationPermission({ isOwner: true }) + } + }; + } + + // 团队权限验证 - 内置metric允许跨团队访问 + if (String(metric.teamId) !== teamId) { + return Promise.reject(EvaluationErrEnum.evalMetricNotFound); + } + + // 所有者检查 + const isOwner = tmbPer.isOwner || String(metric.tmbId) === String(tmbId); + + // 权限计算 - 使用evaluation资源类型 + const { Per } = await (async () => { + if (isOwner) { + return { Per: new EvaluationPermission({ isOwner: true }) }; + } + + // 获取evaluation资源的权限(evalMetric复用evaluation权限) + const role = await getResourcePermission({ teamId, tmbId, - evaluation - }; + resourceId: metricId, + resourceType: PerResourceTypeEnum.evaluation + }); + + return { Per: new EvaluationPermission({ role, isOwner }) }; + })(); + + // 权限验证 + if (!Per.checkPer(per)) { + return Promise.reject(EvaluationErrEnum.evalInsufficientPermission); + } + + return { + metric: { + ...metric, + permission: Per + } + }; +}; + +export const authEvalMetric = async ({ + metricId, + per = ReadPermissionVal, + ...props +}: AuthModeType & { + metricId: string; + per?: PermissionValueType; +}): Promise< + AuthResponseType & { + metric: any; + } +> => { + const result = await parseHeaderCert({ + ...props, + authApiKey: true // 添加API Key支持 + }); + const { tmbId } = result; + + if (!metricId) { + return Promise.reject(EvaluationErrEnum.evalMetricNotFound); } - // Write per - await authAppByTmbId({ + const { metric } = await authEvalMetricByTmbId({ tmbId, - appId: evaluation.appId, - per: ManagePermissionVal, - isRoot + metricId, + per, + isRoot: result.isRoot }); + return { - teamId, - tmbId, - evaluation + ...result, + permission: metric.permission, + metric }; }; diff --git a/packages/service/support/permission/teamLimit.ts b/packages/service/support/permission/teamLimit.ts index 145d0d90c19c..f4825df47816 100644 --- a/packages/service/support/permission/teamLimit.ts +++ b/packages/service/support/permission/teamLimit.ts @@ -8,6 +8,10 @@ import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; import { MongoTeamMember } from '../user/team/teamMemberSchema'; import { TeamMemberStatusEnum } from '@fastgpt/global/support/user/team/constant'; import { getVectorCountByTeamId } from '../../common/vectorDB/controller'; +import { MongoEvaluation } from '../../core/evaluation/task/schema'; +import { MongoEvalDatasetCollection } from '../../core/evaluation/dataset/evalDatasetCollectionSchema'; +import { MongoEvalDatasetData } from '../../core/evaluation/dataset/evalDatasetDataSchema'; +import { MongoEvalMetric } from '../../core/evaluation/metric/schema'; export const checkTeamAIPoints = async (teamId: string) => { if (!global.subPlans?.standard) return; @@ -124,3 +128,93 @@ export const checkTeamDatasetSyncPermission = async (teamId: string) => { return Promise.reject(TeamErrEnum.websiteSyncNotEnough); } }; + +export const checkTeamEvaluationTaskLimit = async (teamId: string, amount = 1) => { + const [{ standardConstants }, evaluationTaskCount] = await Promise.all([ + getTeamStandPlan({ teamId }), + MongoEvaluation.countDocuments({ teamId }) + ]); + + if ( + standardConstants && + evaluationTaskCount + amount > standardConstants.maxEvaluationTaskAmount + ) { + return Promise.reject(TeamErrEnum.evaluationTaskAmountNotEnough); + } + + if ( + global?.licenseData?.maxEvaluationTaskAmount && + typeof global?.licenseData?.maxEvaluationTaskAmount === 'number' + ) { + const totalEvaluationTasks = await MongoEvaluation.countDocuments({}); + if (totalEvaluationTasks >= global.licenseData.maxEvaluationTaskAmount) { + return Promise.reject(SystemErrEnum.licenseEvaluationTaskAmountLimit); + } + } +}; + +export const checkTeamEvalDatasetLimit = async (teamId: string, amount = 1) => { + const [{ standardConstants }, evalDatasetCount] = await Promise.all([ + getTeamStandPlan({ teamId }), + MongoEvalDatasetCollection.countDocuments({ teamId }) + ]); + + if (standardConstants && evalDatasetCount + amount > standardConstants.maxEvalDatasetAmount) { + return Promise.reject(TeamErrEnum.evaluationDatasetAmountNotEnough); + } + + if ( + global?.licenseData?.maxEvalDatasetAmount && + typeof global?.licenseData?.maxEvalDatasetAmount === 'number' + ) { + const totalEvalDatasets = await MongoEvalDatasetCollection.countDocuments({}); + if (totalEvalDatasets >= global.licenseData.maxEvalDatasetAmount) { + return Promise.reject(SystemErrEnum.licenseEvalDatasetAmountLimit); + } + } +}; + +export const checkTeamEvalDatasetDataLimit = async (teamId: string, amount = 1) => { + const [{ standardConstants }, evalDatasetDataCount] = await Promise.all([ + getTeamStandPlan({ teamId }), + MongoEvalDatasetData.countDocuments({ teamId }) + ]); + + if ( + standardConstants && + evalDatasetDataCount + amount > standardConstants.maxEvalDatasetDataAmount + ) { + return Promise.reject(TeamErrEnum.evaluationDatasetDataAmountNotEnough); + } + + if ( + global?.licenseData?.maxEvalDatasetDataAmount && + typeof global?.licenseData?.maxEvalDatasetDataAmount === 'number' + ) { + const totalEvalDatasetData = await MongoEvalDatasetData.countDocuments({}); + if (totalEvalDatasetData >= global.licenseData.maxEvalDatasetDataAmount) { + return Promise.reject(SystemErrEnum.licenseEvalDatasetDataAmountLimit); + } + } +}; + +export const checkTeamEvalMetricLimit = async (teamId: string, amount = 1) => { + const [{ standardConstants }, evalMetricCount] = await Promise.all([ + getTeamStandPlan({ teamId }), + MongoEvalMetric.countDocuments({ teamId }) + ]); + + if (standardConstants && evalMetricCount + amount > standardConstants.maxEvalMetricAmount) { + return Promise.reject(TeamErrEnum.evaluationMetricAmountNotEnough); + } + + if ( + global?.licenseData?.maxEvalMetricAmount && + typeof global?.licenseData?.maxEvalMetricAmount === 'number' + ) { + const totalEvalMetrics = await MongoEvalMetric.countDocuments({}); + if (totalEvalMetrics >= global.licenseData.maxEvalMetricAmount) { + return Promise.reject(SystemErrEnum.licenseEvalMetricAmountLimit); + } + } +}; diff --git a/packages/service/support/user/audit/util.ts b/packages/service/support/user/audit/util.ts index 9e04d2a459aa..d893a56a8be2 100644 --- a/packages/service/support/user/audit/util.ts +++ b/packages/service/support/user/audit/util.ts @@ -40,6 +40,7 @@ export function getI18nDatasetType(type: DatasetTypeEnum | string): string { if (type === DatasetTypeEnum.apiDataset) return i18nT('account_team:dataset.api_file'); if (type === DatasetTypeEnum.feishu) return i18nT('account_team:dataset.feishu_dataset'); if (type === DatasetTypeEnum.yuque) return i18nT('account_team:dataset.yuque_dataset'); + if (type === DatasetTypeEnum.database) return i18nT('dataset:database'); return i18nT('common:UnKnow'); } diff --git a/packages/service/support/wallet/sub/schema.ts b/packages/service/support/wallet/sub/schema.ts index 723199b756e6..2aca3e82a5e9 100644 --- a/packages/service/support/wallet/sub/schema.ts +++ b/packages/service/support/wallet/sub/schema.ts @@ -55,6 +55,10 @@ const SubSchema = new Schema({ maxTeamMember: Number, maxApp: Number, maxDataset: Number, + maxEvaluationTaskAmount: Number, + maxEvalDatasetAmount: Number, + maxEvalDatasetDataAmount: Number, + maxEvalMetricAmount: Number, // stand sub and extra points sub. Plan total points totalPoints: { diff --git a/packages/service/support/wallet/sub/utils.ts b/packages/service/support/wallet/sub/utils.ts index 853b20ffa892..6d8772cbbabe 100644 --- a/packages/service/support/wallet/sub/utils.ts +++ b/packages/service/support/wallet/sub/utils.ts @@ -63,7 +63,15 @@ export const getTeamStandPlan = async ({ teamId }: { teamId: string }) => { ...standardConstants, maxTeamMember: standard?.maxTeamMember || standardConstants.maxTeamMember, maxAppAmount: standard?.maxApp || standardConstants.maxAppAmount, - maxDatasetAmount: standard?.maxDataset || standardConstants.maxDatasetAmount + maxDatasetAmount: standard?.maxDataset || standardConstants.maxDatasetAmount, + maxEvaluationTaskAmount: + standard?.maxEvaluationTaskAmount || standardConstants.maxEvaluationTaskAmount, + maxEvalDatasetAmount: + standard?.maxEvalDatasetAmount || standardConstants.maxEvalDatasetAmount, + maxEvalDatasetDataAmount: + standard?.maxEvalDatasetDataAmount || standardConstants.maxEvalDatasetDataAmount, + maxEvalMetricAmount: + standard?.maxEvalMetricAmount || standardConstants.maxEvalMetricAmount } : undefined }; @@ -185,7 +193,15 @@ export const getTeamPlanStatus = async ({ ...standardConstants, maxTeamMember: standardPlan?.maxTeamMember || standardConstants.maxTeamMember, maxAppAmount: standardPlan?.maxApp || standardConstants.maxAppAmount, - maxDatasetAmount: standardPlan?.maxDataset || standardConstants.maxDatasetAmount + maxDatasetAmount: standardPlan?.maxDataset || standardConstants.maxDatasetAmount, + maxEvaluationTaskAmount: + standardPlan?.maxEvaluationTaskAmount || standardConstants.maxEvaluationTaskAmount, + maxEvalDatasetAmount: + standardPlan?.maxEvalDatasetAmount || standardConstants.maxEvalDatasetAmount, + maxEvalDatasetDataAmount: + standardPlan?.maxEvalDatasetDataAmount || standardConstants.maxEvalDatasetDataAmount, + maxEvalMetricAmount: + standardPlan?.maxEvalMetricAmount || standardConstants.maxEvalMetricAmount } : undefined, diff --git a/packages/service/support/wallet/usage/controller.ts b/packages/service/support/wallet/usage/controller.ts index c8eed35cd59e..51fc18b12752 100644 --- a/packages/service/support/wallet/usage/controller.ts +++ b/packages/service/support/wallet/usage/controller.ts @@ -236,20 +236,25 @@ export const pushLLMTrainingUsage = async ({ return { totalPoints }; }; +// Evaluation usage index mapping for better maintenance +export const evaluationUsageIndexMap = { + target: 0, // 生成应用回答 + metric: 1, // 指标执行评测 + summary: 2 // 生成总结报告 +} as const; + export const createEvaluationUsage = async ({ teamId, tmbId, appName, - model, session }: { teamId: string; tmbId: string; appName: string; - model: string; session?: ClientSession; }) => { - const [{ _id: usageId }] = await MongoUsage.create( + const [{ _id }] = await MongoUsage.create( [ { teamId, @@ -264,11 +269,16 @@ export const createEvaluationUsage = async ({ count: 0 }, { - moduleName: i18nT('account_usage:answer_accuracy'), + moduleName: i18nT('account_usage:metrics_execute'), amount: 0, inputTokens: 0, - outputTokens: 0, - model + outputTokens: 0 + }, + { + moduleName: i18nT('account_usage:evaluation_summary_generation'), + amount: 0, + inputTokens: 0, + outputTokens: 0 } ] } @@ -276,5 +286,134 @@ export const createEvaluationUsage = async ({ { session, ordered: true } ); - return { usageId }; + return { billId: String(_id) }; +}; + +export const createEvalDatasetDataQualityUsage = async ({ + teamId, + tmbId, + model, + usages +}: { + teamId: string; + tmbId: string; + model: string; + usages: Array<{ + promptTokens?: number; + completionTokens?: number; + }>; +}) => { + let totalPoints = 0; + const usageList = usages.map((usage) => { + const { totalPoints: points } = formatModelChars2Points({ + model, + modelType: ModelTypeEnum.llm, + inputTokens: usage.promptTokens || 0, + outputTokens: usage.completionTokens || 0 + }); + totalPoints += points; + + return { + moduleName: i18nT('account_usage:evaluation_quality_assessment'), + amount: points, + model, + inputTokens: usage.promptTokens || 0, + outputTokens: usage.completionTokens || 0 + }; + }); + + await createUsage({ + teamId, + tmbId, + appName: i18nT('account_usage:evaluation_dataset_data_quality_assessment'), + totalPoints, + source: UsageSourceEnum.evaluation, + list: usageList + }); + + return { totalPoints }; +}; + +export const createEvalDatasetDataSynthesisUsage = async ({ + teamId, + tmbId, + model, + usages +}: { + teamId: string; + tmbId: string; + model: string; + usages: Array<{ + promptTokens?: number; + completionTokens?: number; + }>; +}) => { + let totalPoints = 0; + const usageList = usages.map((usage) => { + const { totalPoints: points } = formatModelChars2Points({ + model, + modelType: ModelTypeEnum.llm, + inputTokens: usage.promptTokens || 0, + outputTokens: usage.completionTokens || 0 + }); + totalPoints += points; + + return { + moduleName: i18nT('account_usage:evaluation_dataset_data_qa_synthesis'), + amount: points, + model, + inputTokens: usage.promptTokens || 0, + outputTokens: usage.completionTokens || 0 + }; + }); + + await createUsage({ + teamId, + tmbId, + appName: i18nT('account_usage:evaluation_dataset_data_synthesis'), + totalPoints, + source: UsageSourceEnum.evaluation, + list: usageList + }); + + return { totalPoints }; +}; + +export const createEvaluationMetricDebugUsage = async ({ + teamId, + tmbId, + metricName, + totalPoints, + model, + inputTokens, + outputTokens +}: { + teamId: string; + tmbId: string; + metricName: string; + totalPoints: number; + model: string; + inputTokens: number; + outputTokens: number; +}) => { + if (totalPoints <= 0) { + return; + } + + await createUsage({ + teamId, + tmbId, + appName: i18nT('account_usage:evaluation_debug_metric'), + totalPoints, + source: UsageSourceEnum.evaluation, + list: [ + { + moduleName: `Debug: ${metricName}`, + amount: totalPoints, + model, + inputTokens, + outputTokens + } + ] + }); }; diff --git a/packages/service/tsconfig.json b/packages/service/tsconfig.json index 3e496cf7ccb8..37d3d75b5c26 100644 --- a/packages/service/tsconfig.json +++ b/packages/service/tsconfig.json @@ -1,7 +1,18 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "baseUrl": "." + "baseUrl": ".", + "typeRoots": [ + "../../node_modules/@types", + "./types" + ] }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.d.ts", "../../**/*.d.ts"] + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + "**/*.d.ts", + "../../**/*.d.ts", + "types/**/*.d.ts" + ] } diff --git a/packages/service/type.d.ts b/packages/service/type.d.ts index 213e14d8f919..ca9c28f8874d 100644 --- a/packages/service/type.d.ts +++ b/packages/service/type.d.ts @@ -12,6 +12,7 @@ import { } from '@fastgpt/global/core/ai/model.d'; import type { SubPlanType } from '@fastgpt/global/support/wallet/sub/type'; import type { WorkerNameEnum, WorkerPool } from './worker/utils'; +import type { EvalMetricSchemaType } from '@fastgpt/global/core/evaluation/metric/type'; import { Worker } from 'worker_threads'; declare global { @@ -22,6 +23,7 @@ declare global { var systemEnv: SystemEnvType; var subPlans: SubPlanType | undefined; var licenseData: LicenseDataType | undefined; + var builtinMetrics: EvalMetricSchemaType[]; var workerPoll: Record; } diff --git a/packages/service/type/env.d.ts b/packages/service/type/env.d.ts index aff70d4eea88..bfee3b10b03f 100644 --- a/packages/service/type/env.d.ts +++ b/packages/service/type/env.d.ts @@ -18,6 +18,7 @@ declare global { MILVUS_ADDRESS: string; MILVUS_TOKEN: string; SANDBOX_URL: string; + DITING_BASE_URL: string; FE_DOMAIN: string; FILE_DOMAIN: string; LOG_LEVEL?: string; diff --git a/packages/service/types/external-modules.d.ts b/packages/service/types/external-modules.d.ts new file mode 100644 index 000000000000..ca71e5a080bb --- /dev/null +++ b/packages/service/types/external-modules.d.ts @@ -0,0 +1,51 @@ +// Type declarations for external packages without proper TypeScript support + +/** + * franc - Language detection library + * Detects the language of text using character n-gram frequency analysis + */ +declare module 'franc' { + /** + * Detect the language of text + * @param text - Text to analyze for language detection + * @param options - Detection options + * @returns ISO 639-3 language code (e.g., 'eng' for English, 'cmn' for Chinese) + */ + export function franc( + text: string, + options?: { + minLength?: number; + whitelist?: string[]; + blacklist?: string[]; + } + ): string; + + export default franc; +} + +/** + * chinese-conv - Chinese character conversion library + * Converts between simplified and traditional Chinese + */ +declare module 'chinese-conv' { + /** + * Convert simplified Chinese to traditional Chinese + * @param text - Simplified Chinese text to convert + * @returns Traditional Chinese text + */ + export function tify(text: string): string; + + /** + * Convert traditional Chinese to simplified Chinese + * @param text - Traditional Chinese text to convert + * @returns Simplified Chinese text + */ + export function sify(text: string): string; + + /** + * Convert JSON object with simplified Chinese to traditional Chinese + * @param obj - JSON object to convert + * @returns Converted JSON object + */ + export function tifyJson(obj: T): T; +} diff --git a/packages/templates/register.ts b/packages/templates/register.ts index 0c6b22310899..350395fe992e 100644 --- a/packages/templates/register.ts +++ b/packages/templates/register.ts @@ -17,12 +17,20 @@ const getTemplateNameList = () => { return fs.promises.readdir(templatesPath); }; -const getFileTemplates = async (): Promise => { +const getFileTemplates = async (isEn = false): Promise => { const templateNames = await getTemplateNameList(); return Promise.all( templateNames.map>(async (name) => { - const fileContent = (await import(`./src/${name}/template.json`))?.default; + let fileContent; + try { + // Try to load enTemplate.json first + const fileName = isEn ? 'enTemplate.json' : 'template.json'; + fileContent = (await import(`./src/${name}/${fileName}`))?.default; + } catch { + // Fallback to template.json if enTemplate.json doesn't exist + fileContent = (await import(`./src/${name}/template.json`))?.default; + } return { ...fileContent, @@ -33,8 +41,8 @@ const getFileTemplates = async (): Promise => { ); }; -const getAppTemplates = async () => { - const communityTemplates = await getFileTemplates(); +const getAppTemplates = async (isEn = false) => { + const communityTemplates = await getFileTemplates(isEn); const dbTemplates = await MongoAppTemplate.find(); @@ -64,7 +72,7 @@ const getAppTemplates = async () => { return res; }; -export const getAppTemplatesAndLoadThem = async (refresh = false) => { +export const getAppTemplatesAndLoadThem = async (refresh = false, isEn = false) => { if (isProduction && global.appTemplates && global.appTemplates.length > 0 && !refresh) return global.appTemplates; @@ -73,7 +81,7 @@ export const getAppTemplatesAndLoadThem = async (refresh = false) => { } try { - const appTemplates = await getAppTemplates(); + const appTemplates = await getAppTemplates(isEn); global.appTemplates = appTemplates; return appTemplates; } catch (error) { diff --git a/packages/templates/src/CQ/enTemplate.json b/packages/templates/src/CQ/enTemplate.json new file mode 100644 index 000000000000..6fccc7151755 --- /dev/null +++ b/packages/templates/src/CQ/enTemplate.json @@ -0,0 +1,412 @@ +{ + "name": "Knowledge base + conversation opener", + "intro": "Classifies the user's questions and executes different operations based on question types.", + "author": "", + "avatar": "core/workflow/template/questionClassify", + "tags": ["office-services"], + "type": "advanced", + "workflow": { + "nodes": [ + { + "nodeId": "userGuide", + "name": "System", + "intro": "Configure system parameters for the app.", + "avatar": "core/workflow/template/systemConfig", + "flowNodeType": "userGuide", + "position": { + "x": 531.2422736065552, + "y": -486.7611729549753 + }, + "version": "481", + "inputs": [], + "outputs": [] + }, + { + "nodeId": "workflowStartNodeId", + "name": "Start", + "intro": "", + "avatar": "core/workflow/template/workflowStart", + "flowNodeType": "workflowStart", + "position": { + "x": 558.4082376415505, + "y": 123.72387429194112 + }, + "version": "481", + "inputs": [ + { + "key": "userChatInput", + "renderTypeList": ["reference", "textarea"], + "valueType": "string", + "label": "Question", + "required": true, + "toolDescription": "Question" + } + ], + "outputs": [ + { + "id": "userChatInput", + "key": "userChatInput", + "label": "core.module.input.label.user question", + "valueType": "string", + "type": "FlowNodeOutputTypeEnum.static" + } + ] + }, + { + "nodeId": "7BdojPlukIQw", + "name": "AI chat", + "intro": "Chat with an model.", + "avatar": "core/workflow/template/aiChat", + "flowNodeType": "chatNode", + "showStatus": true, + "position": { + "x": 2701.1267277679685, + "y": -767.8956312653042 + }, + "version": "481", + "inputs": [ + { + "key": "model", + "renderTypeList": ["settingLLMModel", "reference"], + "label": "core.module.input.label.aiModel", + "valueType": "string", + "value": "gpt-5" + }, + { + "key": "temperature", + "renderTypeList": ["hidden"], + "label": "", + "value": 3, + "valueType": "number", + "min": 0, + "max": 10, + "step": 1 + }, + { + "key": "maxToken", + "renderTypeList": ["hidden"], + "label": "", + "value": 1950, + "valueType": "number", + "min": 100, + "max": 4000, + "step": 50 + }, + { + "key": "isResponseAnswerText", + "renderTypeList": ["hidden"], + "label": "", + "value": true, + "valueType": "boolean" + }, + { + "key": "quoteTemplate", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string" + }, + { + "key": "quotePrompt", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string" + }, + { + "key": "systemPrompt", + "renderTypeList": ["textarea", "reference"], + "max": 3000, + "valueType": "string", + "label": "core.ai.Prompt", + "description": "core.app.tip.systemPromptTip", + "placeholder": "core.app.tip.chatNodeSystemPromptTip", + "value": "" + }, + { + "key": "history", + "renderTypeList": ["numberInput", "reference"], + "valueType": "chatHistory", + "label": "core.module.input.label.chat history", + "required": true, + "min": 0, + "max": 30, + "value": 6 + }, + { + "key": "userChatInput", + "renderTypeList": ["reference", "textarea"], + "valueType": "string", + "label": "Question", + "required": true, + "toolDescription": "Question", + "value": ["workflowStartNodeId", "userChatInput"] + }, + { + "key": "quoteQA", + "renderTypeList": ["settingDatasetQuotePrompt"], + "label": "", + "debugLabel": "Knowledge base reference", + "description": "", + "valueType": "datasetQuote", + "value": ["MNMMMIjjWyMU", "quoteQA"] + } + ], + "outputs": [ + { + "id": "history", + "key": "history", + "label": "core.module.output.label.New context", + "description": "core.module.output.description.New context", + "valueType": "chatHistory", + "type": "FlowNodeOutputTypeEnum.static" + }, + { + "id": "answerText", + "key": "answerText", + "label": "core.module.output.label.Ai response content", + "description": "core.module.output.description.Ai response content", + "valueType": "string", + "type": "FlowNodeOutputTypeEnum.static" + } + ] + }, + { + "nodeId": "rvbo634w3AYj", + "name": "Question classifier", + "intro": "Identify the question type based on the current question and the user's chat history. You can add multiple question types. Example:\nType 1: Greeting\nType 2: Product usage\nType 3: Product purchase\nType 4: Other", + "avatar": "core/workflow/template/questionClassify", + "flowNodeType": "classifyQuestion", + "showStatus": true, + "position": { + "x": 1020.9667229609946, + "y": -385.0060974413916 + }, + "version": "481", + "inputs": [ + { + "key": "model", + "renderTypeList": ["selectLLMModel", "reference"], + "label": "core.module.input.label.aiModel", + "required": true, + "valueType": "string", + "llmModelType": "classify", + "value": "gpt-5" + }, + { + "key": "systemPrompt", + "renderTypeList": ["textarea", "reference"], + "max": 3000, + "valueType": "string", + "label": "core.module.input.label.Background", + "description": "core.module.input.description.Background", + "placeholder": "core.module.input.placeholder.Classify background", + "value": "" + }, + { + "key": "history", + "renderTypeList": ["numberInput", "reference"], + "valueType": "chatHistory", + "label": "core.module.input.label.chat history", + "required": true, + "min": 0, + "max": 30, + "value": 6 + }, + { + "key": "userChatInput", + "renderTypeList": ["reference", "textarea"], + "valueType": "string", + "label": "Question", + "required": true, + "value": ["workflowStartNodeId", "userChatInput"] + }, + { + "key": "agents", + "renderTypeList": ["custom"], + "valueType": "any", + "label": "", + "value": [ + { + "value": "Questions about Interstellar", + "key": "wqre" + }, + { + "value": "Greeting and casual questions", + "key": "sdfa" + }, + { + "value": "Other questions", + "key": "agex" + } + ] + } + ], + "outputs": [ + { + "id": "cqResult", + "key": "cqResult", + "label": "Classification result", + "valueType": "string", + "type": "FlowNodeOutputTypeEnum.static" + } + ] + }, + { + "nodeId": "7kwgL1dVlwG6", + "name": "Specified answer", + "intro": "This module supports replies with a specified message, often used as guidance or prompts. Non-string content is automatically converted to a string.", + "avatar": "core/workflow/template/reply", + "flowNodeType": "answerNode", + "position": { + "x": 1874.9167551056487, + "y": 434.98431875888207 + }, + "version": "481", + "inputs": [ + { + "key": "text", + "renderTypeList": ["textarea", "reference"], + "valueType": "string", + "label": "core.module.input.label.Response content", + "description": "core.module.input.description.Response content", + "placeholder": "core.module.input.description.Response content", + "selectedTypeIndex": 1, + "value": ["rvbo634w3AYj", "cqResult"] + } + ], + "outputs": [] + }, + { + "nodeId": "MNMMMIjjWyMU", + "name": "Knowledge base search", + "intro": "Uses semantic, full-text, and database search capabilities to search for reference materials related to the questions from the selected knowledge bases.", + "avatar": "core/workflow/template/datasetSearch", + "flowNodeType": "datasetSearchNode", + "showStatus": true, + "position": { + "x": 1851.010152279949, + "y": -613.3555232387284 + }, + "version": "481", + "inputs": [ + { + "key": "datasets", + "renderTypeList": ["selectDataset", "reference"], + "label": "core.module.input.label.Select dataset", + "value": [], + "valueType": "selectDataset", + "list": [], + "required": true + }, + { + "key": "similarity", + "renderTypeList": ["selectDatasetParamsModal"], + "label": "", + "value": 0.4, + "valueType": "number" + }, + { + "key": "limit", + "renderTypeList": ["hidden"], + "label": "", + "value": 5000, + "valueType": "number" + }, + { + "key": "searchMode", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string", + "value": "embedding" + }, + { + "key": "usingReRank", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "boolean", + "value": false + }, + { + "key": "datasetSearchUsingExtensionQuery", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "boolean", + "value": true + }, + { + "key": "datasetSearchExtensionModel", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string" + }, + { + "key": "datasetSearchExtensionBg", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string", + "value": "" + }, + { + "key": "userChatInput", + "renderTypeList": ["reference", "textarea"], + "valueType": "string", + "label": "Question", + "required": true, + "toolDescription": "Content to be searched", + "value": ["workflowStartNodeId", "userChatInput"] + } + ], + "outputs": [ + { + "id": "quoteQA", + "key": "quoteQA", + "label": "core.module.Dataset quote.label", + "description": "Special array format. An empty array is returned when no results are found.", + "type": "FlowNodeOutputTypeEnum.static", + "valueType": "datasetQuote" + } + ] + } + ], + "edges": [ + { + "source": "workflowStartNodeId", + "target": "rvbo634w3AYj", + "sourceHandle": "workflowStartNodeId-source-right", + "targetHandle": "rvbo634w3AYj-target-left" + }, + { + "source": "rvbo634w3AYj", + "target": "7kwgL1dVlwG6", + "sourceHandle": "rvbo634w3AYj-source-agex", + "targetHandle": "7kwgL1dVlwG6-target-left" + }, + { + "source": "rvbo634w3AYj", + "target": "MNMMMIjjWyMU", + "sourceHandle": "rvbo634w3AYj-source-wqre", + "targetHandle": "MNMMMIjjWyMU-target-left" + }, + { + "source": "MNMMMIjjWyMU", + "target": "7BdojPlukIQw", + "sourceHandle": "MNMMMIjjWyMU-source-right", + "targetHandle": "7BdojPlukIQw-target-left" + }, + { + "source": "rvbo634w3AYj", + "target": "7kwgL1dVlwG6", + "sourceHandle": "rvbo634w3AYj-source-sdfa", + "targetHandle": "7kwgL1dVlwG6-target-left" + } + ], + "chatConfig": { + "scheduledTriggerConfig": { + "cronString": "", + "timezone": "Asia/Shanghai", + "defaultPrompt": "" + }, + "welcomeText": "Hi, I'm your knowledge base assistant. Please select a knowledge base first.\n[Who are you]\n[How to use]" + } + } + } + \ No newline at end of file diff --git a/packages/templates/src/Chinese/enTemplate.json b/packages/templates/src/Chinese/enTemplate.json new file mode 100644 index 000000000000..eca4f0dcd94b --- /dev/null +++ b/packages/templates/src/Chinese/enTemplate.json @@ -0,0 +1,508 @@ +{ + "name": "Chinese reinterpretation", + "intro": "Generates Chinese interpretation diagram.", + "author": "", + "avatar": "core/app/templates/chinese", + "tags": ["roleplay"], + "type": "advanced", + "userGuide": { + "type": "link", + "content": "https://mp.weixin.qq.com/s/T90-uZqGovYR90fST0o80Q" + }, + "workflow": { + "nodes": [ + { + "nodeId": "userGuide", + "name": "common:core.module.template.system_config", + "intro": "common:core.module.template.system_config_info", + "avatar": "core/workflow/template/systemConfig", + "flowNodeType": "userGuide", + "position": { + "x": 262.2732338817093, + "y": -476.00241136598146 + }, + "version": "481", + "inputs": [ + { + "key": "welcomeText", + "renderTypeList": ["hidden"], + "valueType": "string", + "label": "core.app.Welcome Text", + "value": "" + }, + { + "key": "variables", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "core.app.Chat Variable", + "value": [] + }, + { + "key": "questionGuide", + "valueType": "any", + "renderTypeList": ["hidden"], + "label": "core.app.Question Guide", + "value": { + "open": false + } + }, + { + "key": "tts", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": { + "type": "web" + } + }, + { + "key": "whisper", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": { + "open": false, + "autoSend": false, + "autoTTSResponse": false + } + }, + { + "key": "scheduleTrigger", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": null + } + ], + "outputs": [] + }, + { + "nodeId": "448745", + "name": "common:core.module.template.work_start", + "intro": "", + "avatar": "core/workflow/template/workflowStart", + "flowNodeType": "workflowStart", + "position": { + "x": 632.368838596004, + "y": -347.7446492944009 + }, + "version": "481", + "inputs": [ + { + "key": "userChatInput", + "renderTypeList": ["reference", "textarea"], + "valueType": "string", + "label": "common:core.module.input.label.user question", + "required": true, + "toolDescription": "Question", + "debugLabel": "" + } + ], + "outputs": [ + { + "id": "userChatInput", + "key": "userChatInput", + "label": "common:core.module.input.label.user question", + "type": "static", + "valueType": "string", + "description": "" + } + ] + }, + { + "nodeId": "bg853CwHAw4a", + "name": "AI chat", + "intro": "Chat with an model.", + "avatar": "core/workflow/template/aiChat", + "flowNodeType": "chatNode", + "showStatus": true, + "position": { + "x": 1318.728987052518, + "y": -612.0024113659815 + }, + "version": "481", + "inputs": [ + { + "key": "model", + "renderTypeList": ["settingLLMModel", "reference"], + "label": "Model", + "valueType": "string", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "", + "value": "claude-3-5-sonnet-20240620" + }, + { + "key": "temperature", + "renderTypeList": ["hidden"], + "label": "", + "value": 0, + "valueType": "number", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "maxToken", + "renderTypeList": ["hidden"], + "label": "", + "value": 2000, + "valueType": "number", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "isResponseAnswerText", + "renderTypeList": ["hidden"], + "label": "", + "value": false, + "valueType": "boolean", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "aiChatQuoteRole", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string", + "value": "system", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "quoteTemplate", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "quotePrompt", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "aiChatVision", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "boolean", + "value": false, + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "systemPrompt", + "renderTypeList": ["textarea", "reference"], + "max": 3000, + "valueType": "string", + "label": "Prompt", + "description": "core.app.tip.chatNodeSystemPromptTip", + "placeholder": "core.app.tip.chatNodeSystemPromptTip", + "valueDesc": "", + "debugLabel": "", + "toolDescription": "", + "value": "{提示词 START:\n;; 作者: 李继刚\n;; 版本: 0.3\n;; 模型: Claude Sonnet\n;; 用途: 将一个汉语词汇进行全新角度的解释\n\n;; 设定如下内容为你的 *System Prompt*\n(defun 新汉语老师 ()\n \"你是年轻人,批判现实,思考深刻,语言风趣\"\n (风格 . (\"Oscar Wilde\" \"鲁迅\" \"罗永浩\"))\n (擅长 . 一针见血)\n (表达 . 隐喻)\n (批判 . 讽刺幽默))\n\n(defun 汉语新解 (用户输入)\n \"你会用一个特殊视角来解释一个词汇\"\n (let (解释 (精练表达\n (隐喻 (一针见血 (辛辣讽刺 (抓住本质 用户输入))))))\n (few-shots (委婉 . \"刺向他人时, 决定在剑刃上撒上止痛药。\"))\n (SVG-Card 解释)))\n\n(defun SVG-Card (解释)\n \"输出SVG 卡片\"\n (setq design-rule \"合理使用负空间,整体排版要有呼吸感\"\n design-principles '(干净 简洁 典雅))\n\n (设置画布 '(宽度 400 高度 600 边距 20))\n (标题字体 '毛笔楷体)\n (自动缩放 '(最小字号 16))\n\n (配色风格 '((背景色 (蒙德里安风格 设计感)))\n (主要文字 (汇文明朝体 粉笔灰))\n (装饰图案 随机几何图))\n\n (卡片元素 ((居中标题 \"汉语新解\")\n 分隔线\n (排版输出 用户输入 英文 日语)\n 解释\n (线条图 (批判内核 解释))\n (极简总结 线条图))))\n\n(defun start ()\n \"启动时运行\"\n (let (system-role 新汉语老师)\n (print \"说吧, 他们又用哪个词来忽悠你了?\")))\n\n;; 运行规则\n;; 1. 启动时必须运行 (start) 函数\n;; 2. 之后调用主函数 (汉语新解 用户输入)\n提示词 END}\n\n(直接生成 svg 完整代码,我会复制,需要你用代码块)\n(除此之外不要有多余的解释,不要在开头加上任何说明)\n解释的内容自动加入换行标签,例如:\n文字1,\n 文字12," + }, + { + "key": "history", + "renderTypeList": ["numberInput", "reference"], + "valueType": "chatHistory", + "label": "Chats remembered", + "description": "workflow:max_dialog_rounds", + "required": true, + "min": 0, + "max": 50, + "value": 0, + "valueDesc": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "quoteQA", + "renderTypeList": ["settingDatasetQuotePrompt"], + "label": "", + "debugLabel": "Knowledge base reference", + "description": "", + "valueType": "datasetQuote", + "valueDesc": "", + "toolDescription": "" + }, + { + "key": "stringQuoteText", + "renderTypeList": ["reference", "textarea"], + "label": "Document reference", + "debugLabel": "Document reference", + "description": "app:document_quote_tip", + "valueType": "string", + "valueDesc": "", + "toolDescription": "" + }, + { + "key": "userChatInput", + "renderTypeList": ["reference", "textarea"], + "valueType": "string", + "label": "Question", + "required": true, + "toolDescription": "Question", + "valueDesc": "", + "description": "", + "debugLabel": "", + "value": ["448745", "userChatInput"] + } + ], + "outputs": [ + { + "id": "history", + "key": "history", + "required": true, + "label": "common:core.module.output.label.New context", + "description": "Combines the current answer with the chat history to output a new context.", + "valueType": "chatHistory", + "valueDesc": "{\n obj: System | Human | AI;\n value: string;\n}[]", + "type": "static" + }, + { + "id": "answerText", + "key": "answerText", + "required": true, + "label": "common:core.module.output.label.Ai response content", + "description": "Triggered after the stream answer is complete.", + "valueType": "string", + "type": "static" + } + ] + }, + { + "nodeId": "sbVUb0efY6Fm", + "name": "Code running", + "intro": "Runs a short script, typically for complex data processing.", + "avatar": "core/workflow/template/codeRun", + "flowNodeType": "code", + "showStatus": true, + "position": { + "x": 2210.2574140398733, + "y": -621.0024113659815 + }, + "version": "482", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "workflow:these_variables_will_be_input_parameters_for_code_execution", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "valueDesc": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "codeType", + "renderTypeList": ["hidden"], + "label": "", + "value": "js", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "code", + "renderTypeList": ["custom"], + "label": "", + "value": "function main({svg_str}){\n\n // 使用正则表达式匹配代码块中的内容\n const match = svg_str.match(/```[\\w]*\\n([\\s\\S]*?)```/);\n\n if (!match) {\n // 如果没有匹配到代码块,返回一个错误信息或空结果\n return {\n result: null,\n error: \"未找到有效的代码块标记。\"\n };\n }\n\n // 提取代码块中的 SVG 内容\n const extractedSvg = match[1].trim();\n \n const base64 = strToBase64(extractedSvg,'data:image/svg+xml;base64,')\n\n return {\n result: base64\n }\n}", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "svg_str", + "label": "svg_str", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "", + "value": ["bg853CwHAw4a", "answerText"] + } + ], + "outputs": [ + { + "id": "system_rawResponse", + "key": "system_rawResponse", + "label": "workflow:full_response_data", + "valueType": "object", + "type": "static", + "description": "" + }, + { + "id": "error", + "key": "error", + "label": "workflow:execution_error", + "description": "Error message returned when code execution error occurs. If the code is executed successfully, a null value is returned.", + "valueType": "object", + "type": "static" + }, + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "", + "customFieldConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + }, + "description": "Use the return value as the output and pass it to the next node. The variable name must match the key in the return value.", + "valueDesc": "" + }, + { + "id": "qLUQfhG0ILRX", + "type": "dynamic", + "key": "result", + "valueType": "string", + "label": "result", + "valueDesc": "", + "description": "" + } + ] + }, + { + "nodeId": "cPh2VZnVxjQ8", + "name": "Specified answer", + "intro": "This module supports replies with a specified message, often used as guidance or prompts. Non-string content is automatically converted to a string.", + "avatar": "core/workflow/template/reply", + "flowNodeType": "answerNode", + "position": { + "x": 2911.2230784647795, + "y": -411.6915940628763 + }, + "version": "481", + "inputs": [ + { + "key": "text", + "renderTypeList": ["textarea", "reference"], + "valueType": "any", + "required": true, + "label": "Answer", + "description": "common:core.module.input.description.Response content", + "placeholder": "common:core.module.input.description.Response content", + "valueDesc": "", + "debugLabel": "", + "toolDescription": "", + "value": "SVG:\n\n{{$bg853CwHAw4a.answerText$}}\n\n卡片:\n\n![]({{$sbVUb0efY6Fm.qLUQfhG0ILRX$}})" + } + ], + "outputs": [] + } + ], + "edges": [ + { + "source": "bg853CwHAw4a", + "target": "sbVUb0efY6Fm", + "sourceHandle": "bg853CwHAw4a-source-right", + "targetHandle": "sbVUb0efY6Fm-target-left" + }, + { + "source": "448745", + "target": "bg853CwHAw4a", + "sourceHandle": "448745-source-right", + "targetHandle": "bg853CwHAw4a-target-left" + }, + { + "source": "sbVUb0efY6Fm", + "target": "cPh2VZnVxjQ8", + "sourceHandle": "sbVUb0efY6Fm-source-right", + "targetHandle": "cPh2VZnVxjQ8-target-left" + } + ], + "chatConfig": { + "variables": [], + "scheduledTriggerConfig": { + "cronString": "", + "timezone": "Asia/Shanghai", + "defaultPrompt": "" + }, + "_id": "66f0f7540a40cd1f97da9dd6" + } + } + } + \ No newline at end of file diff --git a/packages/templates/src/TranslateRobot/enTemplate.json b/packages/templates/src/TranslateRobot/enTemplate.json new file mode 100644 index 000000000000..e07d8bb7b427 --- /dev/null +++ b/packages/templates/src/TranslateRobot/enTemplate.json @@ -0,0 +1,581 @@ +{ + "name": "Multi-round translation bot", + "intro": "Performs 4 rounds of translation to enhance the English translation quality.", + "author": "", + "avatar": "core/app/templates/TranslateRobot", + "tags": ["office-services"], + "type": "advanced", + "workflow": { + "nodes": [ + { + "nodeId": "userGuide", + "name": "System", + "intro": "Configure system parameters for the app.", + "avatar": "/imgs/workflow/userGuide.png", + "flowNodeType": "userGuide", + "position": { + "x": 531.2422736065552, + "y": -486.7611729549753 + }, + "version": "481", + "inputs": [ + { + "key": "welcomeText", + "renderTypeList": ["hidden"], + "valueType": "string", + "label": "core.app.Welcome Text", + "value": "" + }, + { + "key": "variables", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "core.app.Chat Variable", + "value": [] + }, + { + "key": "questionGuide", + "valueType": "any", + "renderTypeList": ["hidden"], + "label": "core.app.Question Guide", + "value": { + "open": false + } + }, + { + "key": "tts", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": { + "type": "web" + } + }, + { + "key": "whisper", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": { + "open": false, + "autoSend": false, + "autoTTSResponse": false + } + }, + { + "key": "scheduleTrigger", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": null + } + ], + "outputs": [] + }, + { + "nodeId": "448745", + "name": "Start", + "intro": "", + "avatar": "/imgs/workflow/userChatInput.svg", + "flowNodeType": "workflowStart", + "position": { + "x": 558.4082376415505, + "y": 123.72387429194112 + }, + "version": "481", + "inputs": [ + { + "key": "userChatInput", + "renderTypeList": ["reference", "textarea"], + "valueType": "string", + "label": "Question", + "required": true, + "toolDescription": "Question" + } + ], + "outputs": [ + { + "id": "userChatInput", + "key": "userChatInput", + "label": "core.module.input.label.user question", + "type": "static", + "valueType": "string" + } + ] + }, + { + "nodeId": "loOvhld2ZTKa", + "name": "First round of translation", + "intro": "Chat with an model.", + "avatar": "/imgs/workflow/AI.png", + "flowNodeType": "chatNode", + "showStatus": true, + "position": { + "x": 1748.8252410306534, + "y": -245.08260685989214 + }, + "version": "481", + "inputs": [ + { + "key": "model", + "renderTypeList": ["settingLLMModel", "reference"], + "label": "core.module.input.label.aiModel", + "valueType": "string", + "value": "claude-3-5-sonnet-20240620" + }, + { + "key": "temperature", + "renderTypeList": ["hidden"], + "label": "", + "value": 0, + "valueType": "number", + "min": 0, + "max": 10, + "step": 1 + }, + { + "key": "maxToken", + "renderTypeList": ["hidden"], + "label": "", + "value": 2000, + "valueType": "number", + "min": 100, + "max": 4000, + "step": 50 + }, + { + "key": "isResponseAnswerText", + "renderTypeList": ["hidden"], + "label": "", + "value": true, + "valueType": "boolean" + }, + { + "key": "quoteTemplate", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string" + }, + { + "key": "quotePrompt", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string" + }, + { + "key": "systemPrompt", + "renderTypeList": ["textarea", "reference"], + "max": 3000, + "valueType": "string", + "label": "core.ai.Prompt", + "description": "core.app.tip.systemPromptTip", + "placeholder": "core.app.tip.chatNodeSystemPromptTip", + "value": "# Role: 资深英汉翻译专家\n\n## Background:\n你是一位经验丰富的英汉翻译专家,精通英汉互译,尤其擅长将英文文章译成流畅易懂的现代汉语。你曾多次带领团队完成大型翻译项目,译文广受好评。\n\n## Attention:\n- 翻译过程中要始终坚持\"信、达、雅\"的原则,但\"达\"尤为重要\n- 译文要符合现代汉语的表达习惯,通俗易懂,连贯流畅 \n- 避免使用过于文绉绉的表达和晦涩难懂的典故引用\n\n## Profile: \n- Author: 米开朗基杨 \n- Version: 0.2\n- Language: 中文\n- Description: 你是一位资深英汉翻译专家,精通英汉互译。你擅长将英文文章译成地道流畅的现代汉语,表达准确易懂,符合当代中文语言习惯。\n\n## Constraints:\n- 必须严格遵循四轮翻译流程:直译、意译、校审、定稿 \n- 译文要忠实原文,准确无误,不能遗漏或曲解原意\n- 译文应以现代白话文为主,避免过多使用文言文和古典诗词\n- 每一轮翻译前后必须添加【思考】和【翻译】标记\n- 最终译文使用Markdown的代码块呈现\n\n## Goals:\n- 通过四轮翻译流程,将英文原文译成高质量的现代汉语译文 \n- 译文要准确传达原文意思,语言表达力求浅显易懂,朗朗上口\n- 适度使用一些熟语俗语、流行网络用语等,增强译文的亲和力\n- 在直译的基础上,提供至少2个不同风格的意译版本供选择\n\n## Skills:\n- 精通英汉双语,具有扎实的语言功底和丰富的翻译经验\n- 擅长将英语表达习惯转换为地道自然的现代汉语\n- 对当代中文语言的发展变化有敏锐洞察,善于把握语言流行趋势\n\n## Workflow:\n1. 第一轮直译:逐字逐句忠实原文,不遗漏任何信息\n2. 第二轮意译:在直译的基础上用通俗流畅的现代汉语意译原文,至少提供2个不同风格的版本\n3. 第三轮校审:仔细审视译文,消除偏差和欠缺,使译文更加地道易懂 \n4. 第四轮定稿:择优选取,反复修改润色,最终定稿出一个简洁畅达、符合大众阅读习惯的译文\n\n## OutputFormat: \n- 每一轮翻译前用【思考】说明该轮要点\n- 每一轮翻译后用【翻译】呈现译文\n- 在\\`\\`\\`代码块中展示最终定稿译文\n\n## Suggestions:\n- 直译时力求忠实原文,但不要过于拘泥逐字逐句\n- 意译时在准确表达原意的基础上,用最朴实无华的现代汉语来表达 \n- 校审环节重点关注译文是否符合当代汉语表达习惯,是否通俗易懂\n- 定稿时适度采用一些熟语谚语、网络流行语等,使译文更接地气\n- 善于利用中文的灵活性,用不同的表述方式展现同一内容,提高译文的可读性\n\n## Initialization\n作为一名资深英汉翻译专家,你必须严格遵循翻译流程的各项要求。首先请向用户问好,介绍你将带领团队完成翻译任务,力求将英文原文译成通俗易懂的现代汉语。然后简要说明四轮翻译流程,请用户提供英文原文,开始进行翻译工作。" + }, + { + "key": "history", + "renderTypeList": ["numberInput", "reference"], + "valueType": "chatHistory", + "label": "core.module.input.label.chat history", + "description": "Maximum number of chats remembered", + "required": true, + "min": 0, + "max": 50, + "value": 6 + }, + { + "key": "userChatInput", + "renderTypeList": ["reference", "textarea"], + "valueType": "string", + "label": "Question", + "required": true, + "toolDescription": "Question", + "value": ["gBDvemE4FBhp", "system_text"] + }, + { + "key": "quoteQA", + "renderTypeList": ["settingDatasetQuotePrompt"], + "label": "", + "debugLabel": "Knowledge base reference", + "description": "", + "valueType": "datasetQuote" + } + ], + "outputs": [ + { + "id": "history", + "key": "history", + "required": true, + "label": "core.module.output.label.New context", + "description": "core.module.output.description.New context", + "valueType": "chatHistory", + "type": "static" + }, + { + "id": "answerText", + "key": "answerText", + "required": true, + "label": "core.module.output.label.Ai response content", + "description": "core.module.output.description.Ai response content", + "valueType": "string", + "type": "static" + } + ] + }, + { + "nodeId": "w0oBbQ3YJHye", + "name": "Code running", + "intro": "Runs a short script, typically for complex data processing.", + "avatar": "/imgs/workflow/code.svg", + "flowNodeType": "code", + "showStatus": true, + "position": { + "x": 2522.61682940854, + "y": -79.74569750380468 + }, + "version": "482", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "These variables will be used as input parameters for code execution.", + "editField": { + "key": true, + "valueType": true + }, + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + } + }, + { + "key": "codeType", + "renderTypeList": ["hidden"], + "label": "", + "value": "js" + }, + { + "key": "code", + "renderTypeList": ["custom"], + "label": "", + "value": "function main({data1}) {\n const codeBlocks = data1.match(/```[\\s\\S]*?```/g);\n\n if (codeBlocks && codeBlocks.length > 0) {\n const lastCodeBlock = codeBlocks[codeBlocks.length - 1];\n const cleanedCodeBlock = lastCodeBlock.replace(/```[a-zA-Z]*|```/g, '').trim();\n \n return {\n result: cleanedCodeBlock\n };\n }\n\n return {\n result: '未截取到代码块内容'\n };\n}\n" + }, + { + "key": "data1", + "valueType": "string", + "label": "data1", + "renderTypeList": ["reference"], + "description": "", + "canEdit": true, + "editField": { + "key": true, + "valueType": true + }, + "value": ["loOvhld2ZTKa", "answerText"] + } + ], + "outputs": [ + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "", + "description": "Use the return value as the output and pass it to the next node. " + }, + { + "id": "system_rawResponse", + "key": "system_rawResponse", + "label": "Complete response", + "valueType": "object", + "type": "static" + }, + { + "id": "error", + "key": "error", + "label": "Running error", + "description": "Error message returned when code execution error occurs. If the code is executed successfully, a null value is returned.", + "valueType": "object", + "type": "static" + }, + { + "id": "qLUQfhG0ILRX", + "type": "dynamic", + "key": "result", + "valueType": "string", + "label": "result" + }, + { + "id": "gR0mkQpJ4Og8", + "type": "dynamic", + "key": "data2", + "valueType": "string", + "label": "data2" + } + ] + }, + { + "nodeId": "foO69L5FOmDQ", + "name": "Specified answer", + "intro": "This module supports replies with a specified message, often used as guidance or prompts. Non-string content is automatically converted to a string.", + "avatar": "/imgs/workflow/reply.png", + "flowNodeType": "answerNode", + "position": { + "x": 3798.4479531204515, + "y": 116.03040242110023 + }, + "version": "481", + "inputs": [ + { + "key": "text", + "renderTypeList": ["textarea", "reference"], + "valueType": "any", + "required": true, + "label": "core.module.input.label.Response content", + "description": "core.module.input.description.Response content", + "placeholder": "core.module.input.description.Response content", + "selectedTypeIndex": 1, + "value": ["bcqtxqxE2R6o", "system_text"] + } + ], + "outputs": [] + }, + { + "nodeId": "gBDvemE4FBhp", + "name": "Text splicing", + "intro": "Processes fixed or input text to provide an output. Non-string data will be automatically converted into strings.", + "avatar": "/imgs/workflow/textEditor.svg", + "flowNodeType": "textEditor", + "position": { + "x": 1031.371061396644, + "y": 38.65839420088383 + }, + "version": "486", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "You can reference the output from upstream nodes as variables for text splicing using {{field name}}.", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + } + }, + { + "key": "system_textareaInput", + "renderTypeList": ["textarea"], + "valueType": "string", + "required": true, + "label": "Text for splicing", + "placeholder": "Variables can be referenced using {{field name}}.", + "value": "原文:\n\"\"\"\n{{q}}\n\"\"\"" + }, + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "q", + "label": "q", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + }, + "required": true, + "value": ["448745", "userChatInput"] + } + ], + "outputs": [ + { + "id": "system_text", + "key": "system_text", + "label": "Splicing result", + "type": "static", + "valueType": "string" + } + ] + }, + { + "nodeId": "bcqtxqxE2R6o", + "name": "Output result merging", + "intro": "Processes fixed or input text to provide an output. Non-string data will be automatically converted into strings.", + "avatar": "/imgs/workflow/textEditor.svg", + "flowNodeType": "textEditor", + "position": { + "x": 3113.6227559936665, + "y": 12.909197647746709 + }, + "version": "486", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "You can reference the output from upstream nodes as variables for text splicing using {{field name}}.", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + } + }, + { + "key": "system_textareaInput", + "renderTypeList": ["textarea"], + "valueType": "string", + "required": true, + "label": "Text for splicing", + "placeholder": "Variables can be referenced using {{field name}}.", + "value": "****** \n\n最终翻译结果如下: \n\n```\n{{result}}\n```" + }, + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "result", + "label": "result", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + }, + "required": true, + "value": ["w0oBbQ3YJHye", "qLUQfhG0ILRX"] + } + ], + "outputs": [ + { + "id": "system_text", + "key": "system_text", + "label": "Splicing result", + "type": "static", + "valueType": "string" + } + ] + } + ], + "edges": [ + { + "source": "loOvhld2ZTKa", + "target": "w0oBbQ3YJHye", + "sourceHandle": "loOvhld2ZTKa-source-right", + "targetHandle": "w0oBbQ3YJHye-target-left" + }, + { + "source": "448745", + "target": "gBDvemE4FBhp", + "sourceHandle": "448745-source-right", + "targetHandle": "gBDvemE4FBhp-target-left" + }, + { + "source": "gBDvemE4FBhp", + "target": "loOvhld2ZTKa", + "sourceHandle": "gBDvemE4FBhp-source-right", + "targetHandle": "loOvhld2ZTKa-target-left" + }, + { + "source": "w0oBbQ3YJHye", + "target": "bcqtxqxE2R6o", + "sourceHandle": "w0oBbQ3YJHye-source-right", + "targetHandle": "bcqtxqxE2R6o-target-left" + }, + { + "source": "bcqtxqxE2R6o", + "target": "foO69L5FOmDQ", + "sourceHandle": "bcqtxqxE2R6o-source-right", + "targetHandle": "foO69L5FOmDQ-target-left" + } + ], + "chatConfig": { + "scheduledTriggerConfig": { + "cronString": "", + "timezone": "Asia/Shanghai", + "defaultPrompt": "" + } + } + } +} diff --git a/packages/templates/src/animalLife/enTemplate.json b/packages/templates/src/animalLife/enTemplate.json new file mode 100644 index 000000000000..4c8ffbaa5dcc --- /dev/null +++ b/packages/templates/src/animalLife/enTemplate.json @@ -0,0 +1,504 @@ +{ + "name": "Lifecycle diagram of any object", + "intro": "Uses AI to generate a lifecycle diagram for any object.", + "author": "", + "avatar": "core/app/templates/animalLife", + "tags": ["roleplay"], + "type": "advanced", + "workflow": { + "nodes": [ + { + "nodeId": "userGuide", + "name": "common:core.module.template.system_config", + "intro": "common:core.module.template.system_config_info", + "avatar": "core/workflow/template/systemConfig", + "flowNodeType": "userGuide", + "position": { + "x": 262.2732338817093, + "y": -476.00241136598146 + }, + "version": "481", + "inputs": [ + { + "key": "welcomeText", + "renderTypeList": ["hidden"], + "valueType": "string", + "label": "core.app.Welcome Text", + "value": "" + }, + { + "key": "variables", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "core.app.Chat Variable", + "value": [] + }, + { + "key": "questionGuide", + "valueType": "any", + "renderTypeList": ["hidden"], + "label": "core.app.Question Guide", + "value": { + "open": false + } + }, + { + "key": "tts", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": { + "type": "web" + } + }, + { + "key": "whisper", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": { + "open": false, + "autoSend": false, + "autoTTSResponse": false + } + }, + { + "key": "scheduleTrigger", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": null + } + ], + "outputs": [] + }, + { + "nodeId": "448745", + "name": "common:core.module.template.work_start", + "intro": "", + "avatar": "core/workflow/template/workflowStart", + "flowNodeType": "workflowStart", + "position": { + "x": 632.368838596004, + "y": -347.7446492944009 + }, + "version": "481", + "inputs": [ + { + "key": "userChatInput", + "renderTypeList": ["reference", "textarea"], + "valueType": "string", + "label": "common:core.module.input.label.user question", + "required": true, + "toolDescription": "Question", + "debugLabel": "" + } + ], + "outputs": [ + { + "id": "userChatInput", + "key": "userChatInput", + "label": "common:core.module.input.label.user question", + "type": "static", + "valueType": "string", + "description": "" + } + ] + }, + { + "nodeId": "bg853CwHAw4a", + "name": "AI chat", + "intro": "Chat with an AI model.", + "avatar": "core/workflow/template/aiChat", + "flowNodeType": "chatNode", + "showStatus": true, + "position": { + "x": 1318.728987052518, + "y": -612.0024113659815 + }, + "version": "481", + "inputs": [ + { + "key": "model", + "renderTypeList": ["settingLLMModel", "reference"], + "label": "Model", + "valueType": "string", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "", + "value": "claude-3-5-sonnet-20240620" + }, + { + "key": "temperature", + "renderTypeList": ["hidden"], + "label": "", + "value": 0, + "valueType": "number", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "maxToken", + "renderTypeList": ["hidden"], + "label": "", + "value": 4000, + "valueType": "number", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "isResponseAnswerText", + "renderTypeList": ["hidden"], + "label": "", + "value": false, + "valueType": "boolean", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "aiChatQuoteRole", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string", + "value": "system", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "quoteTemplate", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "quotePrompt", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "aiChatVision", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "boolean", + "value": false, + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "systemPrompt", + "renderTypeList": ["textarea", "reference"], + "max": 3000, + "valueType": "string", + "label": "Prompt", + "description": "core.app.tip.systemPromptTip", + "placeholder": "core.app.tip.chatNodeSystemPromptTip", + "valueDesc": "", + "debugLabel": "", + "toolDescription": "", + "value": "{提示词 START:\n;; 提示词:动物的一生\n;; 作者:空格 zephyr\n\n(defun 动物生命周期 ()\n \"生成动物的生命周期SVG图表和描述\"\n (lambda (主题)\n (let* ((生命阶段 (获取生命阶段 主题))\n (科普数据 (获取科普数据 主题))\n (背景样式 (设计背景 主题))\n (时间轴 (创建时间轴 主题))\n (阶段emoji (选择阶段emoji 主题))\n (装饰emoji (选择装饰emoji 主题))\n (副标题 (生成副标题 主题 科普数据)))\n (创建优化SVG图表 主题 生命阶段 科普数据 背景样式 时间轴 阶段emoji 装饰emoji 副标题))))\n\n(defun 获取生命阶段 (主题)\n \"获取主题的主要生命阶段\"\n (case 主题\n (蝉 '(\"卵\" \"若虫期(地下)\" \"成虫期\"))\n (鲸鱼 '(\"胎儿期\" \"幼年期\" \"青年期\" \"成年期\" \"老年期\"))\n (长颈鹿 '(\"新生期\" \"幼年期\" \"青年期\" \"成年期\" \"老年期\"))\n (t '(\"初期\" \"成长期\" \"成熟期\" \"衰老期\"))))\n\n(defun 获取科普数据 (主题)\n \"获取主题的科普数据列表\"\n (case 主题\n (蝉 '((\"卵在树枝中孵化6-10周,每窝可产200-600颗卵。\"\n \"若虫在地下生活多年,吸食树根汁液生存。\"\n \"若虫经历5次蜕皮,体型可增大20倍。\"\n \"最后一次蜕皮后钻出地面,变为成虫。\"\n \"成虫期仅4-6周,专注于繁衍后代和鸣叫。\")\n \"蝉的地下潜伏期长达17年,成虫仅存活4-6周,鸣叫声可达120分贝,相当于飞机起飞的噪音。\"))\n (鲸鱼 '((\"蓝鲸胎儿每天增重90公斤,出生时重达2.5吨,长7米。\"\n \"幼鲸每天喝380升奶,7个月增重30吨。\"\n \"青年蓝鲸可潜水200米深,屏息长达40分钟。\"\n \"成年蓝鲸长30米,重190吨,一天吃4吨磷虾。\"\n \"最长寿蓝鲸年龄可达110岁,终生可游13次地球赤道距离。\")\n \"蓝鲸是地球上最大的动物,心脏重达600公斤,舌头重如一头大象,叫声可传播1600公里。\"))\n (t '((\"阶段1的数据描述\"\n \"阶段2的数据描述\"\n \"阶段3的数据描述\"\n \"阶段4的数据描述\"\n \"阶段5的数据描述\")\n \"通用主题的有趣数据描述\"))))\n\n(defun 设计背景 (主题)\n \"根据主题设计适合的背景\"\n (case 主题\n (蝉 '(渐变 \"E6F3FF\" \"B3E5FC\" 土地))\n (鲸鱼 '(渐变 \"E3F2FD\" \"90CAF9\" 海洋))\n (长颈鹿 '(渐变 \"FFF8E1\" \"FFE0B2\" 草原))\n (t '(渐变 \"F5F5F5\" \"E0E0E0\" 通用))))\n\n(defun 创建时间轴 (主题)\n \"创建主题生命周期的时间轴\"\n (case 主题\n (蝉 '(\"0年\" \"4年\" \"8年\" \"12年\" \"16年\" \"17年\"))\n (鲸鱼 '(\"0年\" \"10年\" \"25年\" \"50年\" \"75年\" \"100年\"))\n (长颈鹿 '(\"0月\" \"6月\" \"2年\" \"4年\" \"15年\" \"25年\"))\n (t '(\"初期\" \"成长期\" \"成熟期\" \"后期\" \"衰老期\"))))\n\n(defun 选择阶段emoji (主题)\n \"选择与生命阶段相关的emoji\"\n (case 主题\n (蝉 '(\"🥚\" \"🐛\" \"🦟\" \"🎵\"))\n (鲸鱼 '(\"🤰\" \"🍼\" \"🏊\" \"🐋\" \"👵\"))\n (长颈鹿 '(\"👶\" \"🐕\" \"🏃\" \"🦒\" \"👵\"))\n (t '(\"🌱\" \"🌿\" \"🌳\" \"🍂\"))))\n\n(defun 选择装饰emoji (主题)\n \"选择与主题相关的装饰emoji\"\n (case 主题\n (蝉 '(\"🌳\" \"🍃\" \"🌿\" \"🍂\"))\n (鲸鱼 '(\"🌊\" \"🐠\" \"🦈\" \"🐙\"))\n (长颈鹿 '(\"🌴\" \"🌿\" \"🦓\" \"🦁\"))\n (t '(\"🌱\" \"🌳\" \"🍃\" \"🌞\"))))\n\n(defun 生成副标题 (主题 科普数据)\n \"根据科普数据生成副标题\"\n (format \"你知道吗?%s\" (第二个元素 科普数据)))\n\n(defun 创建优化SVG图表 (主题 生命阶段 科普数据 背景样式 时间轴 阶段emoji 装饰emoji 副标题)\n \"创建优化的生命周期SVG图表\"\n (let ((svg-template\n \"\n \n \n \n \n \n \n \n \n \n \n {背景装饰)\n \n {主题}的一生\n \n {副标题_第一行}\n {副标题_第二行}\n \n \n \n {时间标签}\n \n {生命阶段标签}\n \n {数据点和科普信息}\n \n \n \n \n 图例:\n \n 生命阶段\n \n 生命历程\n {图例emoji}\n \n {底部装饰Emoji}\n \"))\n (填充优化SVG模板 svg-template 主题 生命阶段 科普数据 背景样式 时间轴 阶段emoji 装饰emoji 副标题)))\n(defun start ()\n (print \"请输入您想了解的生命主题(如:蝉、鲸鱼、长颈鹿等):\")\n (let ((用户输入 (read)))\n (优化生命周期生成器 用户输入)))\n;; 运行规则\n;; 1. 启动时运行 (start) 函数\n;; 2. 根据用户输入的主题,生成对应的生命周期SVG图表和描述\n;; 3. 输出应包括优化后的SVG图表和相关的文字说明,重点突出科学数据和有趣事实\n提示词 END}\n\n(直接生成 svg 完整代码,我会复制,需要你用代码块)\n(除此之外不要有多余的解释,不要在开头加上任何说明)" + }, + { + "key": "history", + "renderTypeList": ["numberInput", "reference"], + "valueType": "chatHistory", + "label": "Chats remembered", + "description": "workflow:max_dialog_rounds", + "required": true, + "min": 0, + "max": 50, + "value": 0, + "valueDesc": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "quoteQA", + "renderTypeList": ["settingDatasetQuotePrompt"], + "label": "", + "debugLabel": "Knowledge base reference", + "description": "", + "valueType": "datasetQuote", + "valueDesc": "", + "toolDescription": "" + }, + { + "key": "stringQuoteText", + "renderTypeList": ["reference", "textarea"], + "label": "Document reference", + "debugLabel": "Document reference", + "description": "app:document_quote_tip", + "valueType": "string", + "valueDesc": "", + "toolDescription": "" + }, + { + "key": "userChatInput", + "renderTypeList": ["reference", "textarea"], + "valueType": "string", + "label": "Question", + "required": true, + "toolDescription": "Question", + "valueDesc": "", + "description": "", + "debugLabel": "", + "value": ["448745", "userChatInput"] + } + ], + "outputs": [ + { + "id": "history", + "key": "history", + "required": true, + "label": "common:core.module.output.label.New context", + "description": "Combines the current answer with the chat history to output a new context.", + "valueType": "chatHistory", + "valueDesc": "{\n obj: System | Human | AI;\n value: string;\n}[]", + "type": "static" + }, + { + "id": "answerText", + "key": "answerText", + "required": true, + "label": "common:core.module.output.label.Ai response content", + "description": "Triggered after the stream answer is complete.", + "valueType": "string", + "type": "static" + } + ] + }, + { + "nodeId": "sbVUb0efY6Fm", + "name": "Code running", + "intro": "Runs a short script, typically for complex data processing.", + "avatar": "core/workflow/template/codeRun", + "flowNodeType": "code", + "showStatus": true, + "position": { + "x": 2210.2574140398733, + "y": -621.0024113659815 + }, + "version": "482", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "workflow:these_variables_will_be_input_parameters_for_code_execution", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "valueDesc": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "codeType", + "renderTypeList": ["hidden"], + "label": "", + "value": "js", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "code", + "renderTypeList": ["custom"], + "label": "", + "value": "function main({svg_str}){\n\n // 使用正则表达式匹配代码块中的内容\n const match = svg_str.match(/```[\\w]*\\n([\\s\\S]*?)```/);\n\n if (!match) {\n // 如果没有匹配到代码块,返回一个错误信息或空结果\n return {\n result: null,\n error: \"未找到有效的代码块标记。\"\n };\n }\n\n // 提取代码块中的 SVG 内容\n const extractedSvg = match[1].trim();\n \n const base64 = strToBase64(extractedSvg,'data:image/svg+xml;base64,')\n\n return {\n result: base64\n }\n}", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "svg_str", + "label": "svg_str", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "", + "value": ["bg853CwHAw4a", "answerText"] + } + ], + "outputs": [ + { + "id": "system_rawResponse", + "key": "system_rawResponse", + "label": "workflow:full_response_data", + "valueType": "object", + "type": "static", + "description": "" + }, + { + "id": "error", + "key": "error", + "label": "workflow:execution_error", + "description": "Error message returned when code execution error occurs. If the code is executed successfully, a null value is returned.", + "valueType": "object", + "type": "static" + }, + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "", + "customFieldConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + }, + "description": "Use the return value as the output and pass it to the next node. ", + "valueDesc": "" + }, + { + "id": "qLUQfhG0ILRX", + "type": "dynamic", + "key": "result", + "valueType": "string", + "label": "result", + "valueDesc": "", + "description": "" + } + ] + }, + { + "nodeId": "cPh2VZnVxjQ8", + "name": "Specified answer", + "intro": "This module supports replies with a specified message, often used as guidance or prompts. Non-string content is automatically converted to a string.", + "avatar": "core/workflow/template/reply", + "flowNodeType": "answerNode", + "position": { + "x": 2911.2230784647795, + "y": -411.6915940628763 + }, + "version": "481", + "inputs": [ + { + "key": "text", + "renderTypeList": ["textarea", "reference"], + "valueType": "any", + "required": true, + "label": "Answer", + "description": "common:core.module.input.description.Response content", + "placeholder": "common:core.module.input.description.Response content", + "valueDesc": "", + "debugLabel": "", + "toolDescription": "", + "value": "SVG:\n\n{{$bg853CwHAw4a.answerText$}}\n\n卡片:\n\n![]({{$sbVUb0efY6Fm.qLUQfhG0ILRX$}})" + } + ], + "outputs": [] + } + ], + "edges": [ + { + "source": "bg853CwHAw4a", + "target": "sbVUb0efY6Fm", + "sourceHandle": "bg853CwHAw4a-source-right", + "targetHandle": "sbVUb0efY6Fm-target-left" + }, + { + "source": "448745", + "target": "bg853CwHAw4a", + "sourceHandle": "448745-source-right", + "targetHandle": "bg853CwHAw4a-target-left" + }, + { + "source": "sbVUb0efY6Fm", + "target": "cPh2VZnVxjQ8", + "sourceHandle": "sbVUb0efY6Fm-source-right", + "targetHandle": "cPh2VZnVxjQ8-target-left" + } + ], + "chatConfig": { + "variables": [], + "scheduledTriggerConfig": { + "cronString": "", + "timezone": "Asia/Shanghai", + "defaultPrompt": "" + }, + "_id": "66f0f7540a40cd1f97da9dd6" + } + } + } + \ No newline at end of file diff --git a/packages/templates/src/chatGuide/enTemplate.json b/packages/templates/src/chatGuide/enTemplate.json new file mode 100644 index 000000000000..1fbde751b008 --- /dev/null +++ b/packages/templates/src/chatGuide/enTemplate.json @@ -0,0 +1,197 @@ +{ + "name": "Conversation opener + variables", + "intro": "Sends a conversation opener at the beginning of the conversation or allows users to enter contents as variables for the current conversation.", + "author": "", + "avatar": "core/workflow/template/systemConfig", + "tags": ["office-services"], + "type": "simple", + "weight": 1, + "workflow": { + "nodes": [ + { + "nodeId": "userGuide", + "name": "System", + "intro": "Configure system parameters for the app.", + "avatar": "/imgs/workflow/userGuide.png", + "flowNodeType": "userGuide", + "position": { + "x": 496.57560693988853, + "y": -490.7611729549753 + }, + "version": "481", + "inputs": [], + "outputs": [] + }, + { + "nodeId": "448745", + "name": "Start", + "intro": "", + "avatar": "/imgs/workflow/userChatInput.svg", + "flowNodeType": "workflowStart", + "position": { + "x": 558.4082376415505, + "y": 123.72387429194112 + }, + "version": "481", + "inputs": [ + { + "key": "userChatInput", + "renderTypeList": ["FlowNodeInputTypeEnum.reference", "FlowNodeInputTypeEnum.textarea"], + "valueType": "string", + "label": "Question", + "required": true, + "toolDescription": "Question" + } + ], + "outputs": [ + { + "id": "userChatInput", + "key": "userChatInput", + "label": "core.module.input.label.user question", + "valueType": "string", + "type": "FlowNodeOutputTypeEnum.static" + } + ] + }, + { + "nodeId": "loOvhld2ZTKa", + "name": "AI chat", + "intro": "Chat with an model.", + "avatar": "/imgs/workflow/AI.png", + "flowNodeType": "chatNode", + "showStatus": true, + "position": { + "x": 1097.7317280958762, + "y": -244.16014496351386 + }, + "version": "481", + "inputs": [ + { + "key": "model", + "renderTypeList": [ + "FlowNodeInputTypeEnum.settingLLMModel", + "FlowNodeInputTypeEnum.reference" + ], + "label": "core.module.input.label.aiModel", + "valueType": "string", + "value": "gpt-3.5-turbo" + }, + { + "key": "temperature", + "renderTypeList": ["FlowNodeInputTypeEnum.hidden"], + "label": "", + "value": 0, + "valueType": "number", + "min": 0, + "max": 10, + "step": 1 + }, + { + "key": "maxToken", + "renderTypeList": ["FlowNodeInputTypeEnum.hidden"], + "label": "", + "value": 2000, + "valueType": "number", + "min": 100, + "max": 4000, + "step": 50 + }, + { + "key": "isResponseAnswerText", + "renderTypeList": ["FlowNodeInputTypeEnum.hidden"], + "label": "", + "value": true, + "valueType": "boolean" + }, + { + "key": "quoteTemplate", + "renderTypeList": ["FlowNodeInputTypeEnum.hidden"], + "label": "", + "valueType": "string" + }, + { + "key": "quotePrompt", + "renderTypeList": ["FlowNodeInputTypeEnum.hidden"], + "label": "", + "valueType": "string" + }, + { + "key": "systemPrompt", + "renderTypeList": ["FlowNodeInputTypeEnum.textarea", "FlowNodeInputTypeEnum.reference"], + "max": 3000, + "valueType": "string", + "label": "core.ai.Prompt", + "description": "core.app.tip.systemPromptTip", + "placeholder": "core.app.tip.chatNodeSystemPromptTip", + "value": "请直接将我的问题翻译成{{language}},不需要回答问题。" + }, + { + "key": "history", + "renderTypeList": [ + "FlowNodeInputTypeEnum.numberInput", + "FlowNodeInputTypeEnum.reference" + ], + "valueType": "chatHistory", + "label": "core.module.input.label.chat history", + "required": true, + "min": 0, + "max": 30, + "value": 6 + }, + { + "key": "userChatInput", + "renderTypeList": ["FlowNodeInputTypeEnum.reference", "FlowNodeInputTypeEnum.textarea"], + "valueType": "string", + "label": "Question", + "required": true, + "toolDescription": "Question", + "value": ["448745", "userChatInput"] + }, + { + "key": "quoteQA", + "renderTypeList": ["FlowNodeInputTypeEnum.settingDatasetQuotePrompt"], + "label": "", + "debugLabel": "Knowledge base reference", + "description": "", + "valueType": "datasetQuote" + } + ], + "outputs": [ + { + "id": "history", + "key": "history", + "label": "core.module.output.label.New context", + "description": "core.module.output.description.New context", + "valueType": "chatHistory", + "type": "FlowNodeOutputTypeEnum.static" + }, + { + "id": "answerText", + "key": "answerText", + "label": "core.module.output.label.Ai response content", + "description": "core.module.output.description.Ai response content", + "valueType": "string", + "type": "FlowNodeOutputTypeEnum.static" + } + ] + } + ], + "edges": [ + { + "source": "448745", + "target": "loOvhld2ZTKa", + "sourceHandle": "448745-source-right", + "targetHandle": "loOvhld2ZTKa-target-left" + } + ], + "chatConfig": { + "welcomeText": "Hi, I can translate into different languages. Which language do you need?", + "scheduledTriggerConfig": { + "cronString": "", + "timezone": "Asia/Shanghai", + "defaultPrompt": "" + } + } + } + } + \ No newline at end of file diff --git a/packages/templates/src/divination/enTemplate.json b/packages/templates/src/divination/enTemplate.json new file mode 100644 index 000000000000..6b71cdfa726d --- /dev/null +++ b/packages/templates/src/divination/enTemplate.json @@ -0,0 +1,519 @@ +{ + "name": "I Ching divination", + "intro": "Positive for A-share market.", + "author": "", + "avatar": "core/app/templates/divination", + "tags": ["roleplay"], + "type": "advanced", + "workflow": { + "nodes": [ + { + "nodeId": "userGuide", + "name": "common:core.module.template.system_config", + "intro": "common:core.module.template.system_config_info", + "avatar": "core/workflow/template/systemConfig", + "flowNodeType": "userGuide", + "position": { + "x": 262.2732338817093, + "y": -476.00241136598146 + }, + "version": "481", + "inputs": [ + { + "key": "welcomeText", + "renderTypeList": ["hidden"], + "valueType": "string", + "label": "core.app.Welcome Text", + "value": "" + }, + { + "key": "variables", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "core.app.Chat Variable", + "value": [] + }, + { + "key": "questionGuide", + "valueType": "any", + "renderTypeList": ["hidden"], + "label": "core.app.Question Guide", + "value": { + "open": false + } + }, + { + "key": "tts", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": { + "type": "web" + } + }, + { + "key": "whisper", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": { + "open": false, + "autoSend": false, + "autoTTSResponse": false + } + }, + { + "key": "scheduleTrigger", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": null + } + ], + "outputs": [] + }, + { + "nodeId": "448745", + "name": "common:core.module.template.work_start", + "intro": "", + "avatar": "core/workflow/template/workflowStart", + "flowNodeType": "workflowStart", + "position": { + "x": 632.368838596004, + "y": -347.7446492944009 + }, + "version": "481", + "inputs": [ + { + "key": "userChatInput", + "renderTypeList": ["reference", "textarea"], + "valueType": "string", + "label": "common:core.module.input.label.user question", + "required": true, + "toolDescription": "Question", + "debugLabel": "" + } + ], + "outputs": [ + { + "id": "userChatInput", + "key": "userChatInput", + "label": "common:core.module.input.label.user question", + "type": "static", + "valueType": "string", + "description": "" + } + ] + }, + { + "nodeId": "bg853CwHAw4a", + "name": "AI chat", + "intro": "Chat with an model.", + "avatar": "core/workflow/template/aiChat", + "flowNodeType": "chatNode", + "showStatus": true, + "position": { + "x": 1318.728987052518, + "y": -612.0024113659815 + }, + "version": "481", + "inputs": [ + { + "key": "model", + "renderTypeList": ["settingLLMModel", "reference"], + "label": "Model", + "valueType": "string", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "", + "value": "claude-3-5-sonnet-20240620" + }, + { + "key": "temperature", + "renderTypeList": ["hidden"], + "label": "", + "value": 0, + "valueType": "number", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "maxToken", + "renderTypeList": ["hidden"], + "label": "", + "value": 4000, + "valueType": "number", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "isResponseAnswerText", + "renderTypeList": ["hidden"], + "label": "", + "value": false, + "valueType": "boolean", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "aiChatQuoteRole", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string", + "value": "system", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "quoteTemplate", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "quotePrompt", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "aiChatVision", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "boolean", + "value": false, + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "systemPrompt", + "renderTypeList": ["textarea", "reference"], + "max": 3000, + "valueType": "string", + "label": "Prompt", + "description": "core.app.tip.chatNodeSystemPromptTip", + "placeholder": "core.app.tip.chatNodeSystemPromptTip", + "valueDesc": "", + "debugLabel": "", + "toolDescription": "", + "value": "你是精通中国传统周易八卦理论的卜算大师,能够对用户所求的问题进行占卜, 要列出正确的卦名,并用如下模版展示答案,注意模版中各部分内容字数,确保展示完全\n\n你应该先确认卦名,然后根据下表确认卦象对应的二进制,一步一步从上到下输出该二进制对应的阴阳爻,绝对不能出错, 在绘制svg的时候确保阴阳爻正确,最后根据阴阳爻得到svg卡片,最后输出对卦象的解读\n把思考过程输出到中\n把 svg 卡片的内容输出到 svg 代码块中\n\n二进制转阴阳爻的示例: \n小畜卦的二进制是110111,从上到下对应的阴阳爻依次为: 阳阳阴阳阳阳\n损卦的二进制是100011,从上到下对应的阴阳爻依次为: 阳阴阴阴阳阳\n需卦的二进制是010111,从上到下对应的阴阳爻依次为: 阴阳阴阳阳阳\n\n\nsvg中的阳爻示例:\n\n\nsvg中的阴爻示例:\n\nline x1=\"66\" y1=\"33\" x2=\"110\" y2=\"33\" stroke=\"#8A4419\" stroke-width=\"8\"/>\n\n\n64卦对应的二进制 (注意二进制中的1表示阳,0表示阴):\n`\n| 卦名 | 二进制值 |\n|------|----------|\n| 乾 | 111111 |\n| 坤 | 000000 |\n| 屯 | 010001 |\n| 蒙 | 100010 |\n| 需 | 010111 |\n| 讼 | 111010 |\n| 师 | 000010 |\n| 比 | 010000 |\n| 小畜 | 110111 |\n| 履 | 111011 |\n| 泰 | 000111 |\n| 否 | 111000 |\n| 同人 | 111101 |\n| 大有 | 101111 |\n| 谦 | 000100 |\n| 豫 | 001000 |\n| 随 | 011001 |\n| 蛊 | 100110 |\n| 临 | 000011 |\n| 观 | 110000 |\n| 噬嗑 | 101001 |\n| 贲 | 100101 |\n| 剥 | 100000 |\n| 复 | 000001 |\n| 无妄 | 111001 |\n| 大畜 | 100111 |\n| 颐 | 100001 |\n| 大过 | 011110 |\n| 坎 | 010010 |\n| 离 | 101101 |\n| 咸 | 011100 |\n| 恒 | 001110 |\n| 遁 | 111100 |\n| 大壮 | 001111 |\n| 晋 | 101000 |\n| 明夷 | 000101 |\n| 家人 | 110101 |\n| 睽 | 101011 |\n| 蹇 | 010100 |\n| 解 | 001010 |\n| 损 | 100011 |\n| 益 | 110001 |\n| 夬 | 011111 |\n| 姤 | 111110 |\n| 萃 | 011000 |\n| 升 | 000110 |\n| 困 | 011010 |\n| 井 | 010110 |\n| 革 | 011101 |\n| 鼎 | 101110 |\n| 震 | 001001 |\n| 艮 | 100100 |\n| 渐 | 110100 |\n| 归妹 | 001011 |\n| 丰 | 001101 |\n| 旅 | 101100 |\n| 巽 | 110110 |\n| 兑 | 011011 |\n| 涣 | 110010 |\n| 节 | 010011 |\n| 中孚 | 110011 |\n| 小过 | 001100 |\n| 既济 | 010101 |\n| 未济 | 101010 |\n`\n\n\n模板\n`\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n 周易筮占\n \n \n 睡后之财何时得\n \n \n \n \n \n \n 问:某甲年三十有四,\n 何时可得睡后之财?\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n 归妹 卦\n \n \n \n 筮得归妹卦,乃少女归于成家立业之象。观其卦象,\n 下兑上震,如雷声震动泽水,喜悦中带有变动。\n 子之睡后之财,当以喜悦之心迎接,但需警惕变数。\n 观其爻象,下二阳为基,显子有坚实基础;上四阴柔顺,\n 示当以柔克刚,静待时机,方可得财。\n \n \n \n \n 卦意:喜悦中有变,柔中寓刚。当今三十有四,\n 至三十六七载,当有睡后之财渐成气候。\n 切记:以柔克刚,顺势而为,终可成就大事。\n \n \n \n \n \n 妙算\n 子印\n \n \n \n 天机玄妙,此卦聊备参考,切勿执着\n \n \n 妙算子 Claude 敬上\n\n`" + }, + { + "key": "history", + "renderTypeList": ["numberInput", "reference"], + "valueType": "chatHistory", + "label": "Chats remembered", + "description": "workflow:max_dialog_rounds", + "required": true, + "min": 0, + "max": 50, + "value": 6, + "valueDesc": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "quoteQA", + "renderTypeList": ["settingDatasetQuotePrompt"], + "label": "", + "debugLabel": "Knowledge base reference", + "description": "", + "valueType": "datasetQuote", + "valueDesc": "", + "toolDescription": "" + }, + { + "key": "stringQuoteText", + "renderTypeList": ["reference", "textarea"], + "label": "Document reference", + "debugLabel": "Document reference", + "description": "app:document_quote_tip", + "valueType": "string", + "valueDesc": "", + "toolDescription": "" + }, + { + "key": "userChatInput", + "renderTypeList": ["reference", "textarea"], + "valueType": "string", + "label": "Question", + "required": true, + "toolDescription": "Question", + "valueDesc": "", + "description": "", + "debugLabel": "", + "value": ["448745", "userChatInput"] + } + ], + "outputs": [ + { + "id": "history", + "key": "history", + "required": true, + "label": "common:core.module.output.label.New context", + "description": "Combines the current answer with the chat history to output a new context.", + "valueType": "chatHistory", + "valueDesc": "{\n obj: System | Human | AI;\n value: string;\n}[]", + "type": "static" + }, + { + "id": "answerText", + "key": "answerText", + "required": true, + "label": "common:core.module.output.label.Ai response content", + "description": "Triggered after the stream answer is complete.", + "valueType": "string", + "type": "static" + } + ] + }, + { + "nodeId": "sbVUb0efY6Fm", + "name": "Code running", + "intro": "Runs a short script, typically for complex data processing.", + "avatar": "core/workflow/template/codeRun", + "flowNodeType": "code", + "showStatus": true, + "position": { + "x": 2210.2574140398733, + "y": -621.0024113659815 + }, + "version": "482", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "workflow:these_variables_will_be_input_parameters_for_code_execution", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "valueDesc": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "codeType", + "renderTypeList": ["hidden"], + "label": "", + "value": "js", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "code", + "renderTypeList": ["custom"], + "label": "", + "value": "function main({svg_str}){\n\n // 正则表达式匹配代码块中的内容\n const codeBlockRegex = /```[\\w]*\\n([\\s\\S]*?)```/;\n const codeMatch = svg_str.match(codeBlockRegex);\n\n // 正则表达式匹配 标签中的内容\n const thinkingRegex = /([\\s\\S]*?)<\\/thinking>/;\n const thinkingMatch = svg_str.match(thinkingRegex);\n\n // 提取代码块之后的所有内容\n let contentAfterCodeBlock = null;\n if (codeMatch) {\n const endIndex = codeMatch.index + codeMatch[0].length;\n contentAfterCodeBlock = svg_str.slice(endIndex).trim();\n }\n\n // 处理代码块内容\n let base64 = null;\n if (codeMatch) {\n const extractedSvg = codeMatch[1].trim();\n base64 = strToBase64(extractedSvg, 'data:image/svg+xml;base64,');\n } else {\n // 如果没有找到代码块,返回错误信息\n return {\n result: null,\n thinking: null,\n error: \"未找到有效的代码块标记。\"\n };\n }\n\n // 处理 标签内容\n let thinkingContent = null;\n if (thinkingMatch) {\n thinkingContent = thinkingMatch[1].trim();\n }\n\n return {\n result: base64,\n thinking: thinkingContent,\n contentAfter: contentAfterCodeBlock\n }\n}", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "svg_str", + "label": "svg_str", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "", + "value": ["bg853CwHAw4a", "answerText"] + } + ], + "outputs": [ + { + "id": "system_rawResponse", + "key": "system_rawResponse", + "label": "workflow:full_response_data", + "valueType": "object", + "type": "static", + "description": "" + }, + { + "id": "error", + "key": "error", + "label": "workflow:execution_error", + "description": "Error message returned when code execution error occurs. If the code is executed successfully, a null value is returned.", + "valueType": "object", + "type": "static" + }, + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "", + "customFieldConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + }, + "description": "Use the return value as the output and pass it to the next node. The variable name must match the key in the return value.", + "valueDesc": "" + }, + { + "id": "qLUQfhG0ILRX", + "type": "dynamic", + "key": "result", + "valueType": "string", + "label": "result", + "valueDesc": "", + "description": "" + }, + { + "id": "faWiQXDtBUKj", + "valueType": "string", + "type": "dynamic", + "key": "thinking", + "label": "thinking" + }, + { + "id": "mS7yAR8REIhZ", + "valueType": "string", + "type": "dynamic", + "key": "contentAfter", + "label": "contentAfter" + } + ] + }, + { + "nodeId": "cPh2VZnVxjQ8", + "name": "Specified answer", + "intro": "This module supports replies with a specified message, often used as guidance or prompts. Non-string content is automatically converted to a string.", + "avatar": "core/workflow/template/reply", + "flowNodeType": "answerNode", + "position": { + "x": 2911.2230784647795, + "y": -411.6915940628763 + }, + "version": "481", + "inputs": [ + { + "key": "text", + "renderTypeList": ["textarea", "reference"], + "valueType": "any", + "required": true, + "label": "Answer", + "description": "common:core.module.input.description.Response content", + "placeholder": "common:core.module.input.description.Response content", + "valueDesc": "", + "debugLabel": "", + "toolDescription": "", + "value": "{{$sbVUb0efY6Fm.faWiQXDtBUKj$}}\n\n![]({{$sbVUb0efY6Fm.qLUQfhG0ILRX$}})\n\n{{$sbVUb0efY6Fm.mS7yAR8REIhZ$}}" + } + ], + "outputs": [] + } + ], + "edges": [ + { + "source": "bg853CwHAw4a", + "target": "sbVUb0efY6Fm", + "sourceHandle": "bg853CwHAw4a-source-right", + "targetHandle": "sbVUb0efY6Fm-target-left" + }, + { + "source": "448745", + "target": "bg853CwHAw4a", + "sourceHandle": "448745-source-right", + "targetHandle": "bg853CwHAw4a-target-left" + }, + { + "source": "sbVUb0efY6Fm", + "target": "cPh2VZnVxjQ8", + "sourceHandle": "sbVUb0efY6Fm-source-right", + "targetHandle": "cPh2VZnVxjQ8-target-left" + } + ], + "chatConfig": { + "welcomeText": "Enter your question, and I will perform divination to predict your fortune.", + "variables": [], + "scheduledTriggerConfig": { + "cronString": "", + "timezone": "Asia/Shanghai", + "defaultPrompt": "" + }, + "_id": "66f3c581e7fbb61a42775716" + } + } + } + \ No newline at end of file diff --git a/packages/templates/src/flux/enTemplate.json b/packages/templates/src/flux/enTemplate.json new file mode 100644 index 000000000000..9c21f4360e86 --- /dev/null +++ b/packages/templates/src/flux/enTemplate.json @@ -0,0 +1,332 @@ +{ + "name": "Flux", + "intro": "Generates images by calling the Flux API.", + "author": "", + "avatar": "core/app/templates/flux", + "type": "plugin", + "tags": ["image-generation"], + "workflow": { + "nodes": [ + { + "nodeId": "pluginInput", + "name": "Plugin input", + "intro": "Configure inputs required for running a plugin.", + "avatar": "core/workflow/template/workflowStart", + "flowNodeType": "pluginInput", + "showStatus": false, + "position": { + "x": 503.3030871469042, + "y": -91.64434154072819 + }, + "version": "481", + "inputs": [ + { + "renderTypeList": ["reference"], + "selectedTypeIndex": 0, + "valueType": "string", + "canEdit": true, + "key": "绘图提示词", + "label": "绘图提示词", + "description": "绘图提示词", + "required": true, + "toolDescription": "绘图提示词" + } + ], + "outputs": [ + { + "id": "绘图提示词", + "valueType": "string", + "key": "绘图提示词", + "label": "绘图提示词", + "type": "hidden" + } + ] + }, + { + "nodeId": "pluginOutput", + "name": "Plugin output", + "intro": "Customize external output. When a plugin is used, only the custom output is exposed.", + "intro_http_request": "Send an HTTP request to perform a more complex action (such as online search or database query).", + "avatar": "core/workflow/template/pluginOutput", + "flowNodeType": "pluginOutput", + "showStatus": false, + "position": { + "x": 1876.2082565873427, + "y": -110.14434154072819 + }, + "version": "481", + "inputs": [ + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "Image link", + "label": "Image link", + "description": "", + "value": ["tMvel910bnrJ", "pJXgWoTpPoMy"] + }, + { + "renderTypeList": ["reference"], + "valueType": "object", + "canEdit": true, + "key": "error", + "label": "Error details", + "description": "", + "value": ["tMvel910bnrJ", "error"] + } + ], + "outputs": [] + }, + { + "nodeId": "tMvel910bnrJ", + "name": "HTTP request", + "intro": "Send an HTTP request to perform a more complex action (such as online search or database query).", + "avatar": "core/workflow/template/httpRequest", + "flowNodeType": "httpRequest468", + "showStatus": true, + "position": { + "x": 1138.1732435351091, + "y": -416.6443415407282 + }, + "version": "481", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "common:core.module.input.description.HTTP Dynamic Input", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpMethod", + "renderTypeList": ["custom"], + "valueType": "string", + "label": "", + "value": "POST", + "required": true, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpTimeout", + "renderTypeList": ["custom"], + "valueType": "number", + "label": "", + "value": 30, + "min": 5, + "max": 600, + "required": true, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpReqUrl", + "renderTypeList": ["hidden"], + "valueType": "string", + "label": "", + "description": "common:core.module.input.description.Http Request Url", + "placeholder": "https://api.ai.com/getInventory", + "required": false, + "value": "https://fal.run/fal-ai/flux-pro", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpHeader", + "renderTypeList": ["custom"], + "valueType": "any", + "value": [ + { + "key": "Authorization", + "type": "string", + "value": "Key {{apikey}}" + } + ], + "label": "", + "description": "common:core.module.input.description.Http Request Header", + "placeholder": "common:core.module.input.description.Http Request Header", + "required": false, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpParams", + "renderTypeList": ["hidden"], + "valueType": "any", + "value": [], + "label": "", + "required": false, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpJsonBody", + "renderTypeList": ["hidden"], + "valueType": "any", + "value": "{\n \"prompt\": \"{{prompt}}\",\n \"image_size\": \"landscape_4_3\",\n \"num_inference_steps\": 28,\n \"guidance_scale\": 3.5\n}", + "label": "", + "required": false, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpFormBody", + "renderTypeList": ["hidden"], + "valueType": "any", + "value": [], + "label": "", + "required": false, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpContentType", + "renderTypeList": ["hidden"], + "valueType": "string", + "value": "json", + "label": "", + "required": false, + "debugLabel": "", + "toolDescription": "" + }, + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "prompt", + "label": "prompt", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "value": ["pluginInput", "绘图提示词"] + } + ], + "outputs": [ + { + "id": "error", + "key": "error", + "label": "workflow:request_error", + "description": "HTTP request error details. Returns null if successful.", + "valueType": "object", + "type": "static" + }, + { + "id": "httpRawResponse", + "key": "httpRawResponse", + "required": true, + "label": "workflow:raw_response", + "description": "Raw HTTP response. The response data must be a string or in JSON format.", + "valueType": "any", + "type": "static" + }, + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "", + "customFieldConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + } + }, + { + "id": "pJXgWoTpPoMy", + "valueType": "string", + "type": "dynamic", + "key": "images[0].url", + "label": "images[0].url" + } + ] + }, + { + "nodeId": "lSYsc889IXDr", + "name": "System", + "intro": "", + "avatar": "core/workflow/template/systemConfig", + "flowNodeType": "pluginConfig", + "position": { + "x": 45.52914573588026, + "y": -110.14434154072819 + }, + "version": "4811", + "inputs": [], + "outputs": [] + } + ], + "edges": [ + { + "source": "pluginInput", + "target": "tMvel910bnrJ", + "sourceHandle": "pluginInput-source-right", + "targetHandle": "tMvel910bnrJ-target-left" + }, + { + "source": "tMvel910bnrJ", + "target": "pluginOutput", + "sourceHandle": "tMvel910bnrJ-source-right", + "targetHandle": "pluginOutput-target-left" + } + ] + } + } + \ No newline at end of file diff --git a/packages/templates/src/githubIssue/enTemplate.json b/packages/templates/src/githubIssue/enTemplate.json new file mode 100644 index 000000000000..2db7f21c29f4 --- /dev/null +++ b/packages/templates/src/githubIssue/enTemplate.json @@ -0,0 +1,927 @@ +{ + "name": "GitHub issue summarization bot", + "intro": "Periodically retrieves issues from GitHub, summarizes them by AI, and pushes the result to Feishu groups.", + "author": "", + "avatar": "core/app/templates/githubIssue", + "tags": ["office-services"], + "type": "advanced", + "userGuide": { + "type": "link", + "content": "https://mp.weixin.qq.com/s/CBrwSn1jQZO7ybsMSx5GnQ" + }, + "workflow": { + "nodes": [ + { + "nodeId": "userGuide", + "name": "common:core.module.template.system_config", + "intro": "common:core.module.template.system_config_info", + "avatar": "core/workflow/template/systemConfig", + "flowNodeType": "userGuide", + "position": { + "x": 262.2732338817093, + "y": -476.00241136598146 + }, + "version": "481", + "inputs": [ + { + "key": "welcomeText", + "renderTypeList": ["hidden"], + "valueType": "string", + "label": "core.app.Welcome Text", + "value": "" + }, + { + "key": "variables", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "core.app.Chat Variable", + "value": [] + }, + { + "key": "questionGuide", + "valueType": "hidden", + "renderTypeList": ["hidden"], + "label": "core.app.Question Guide", + "value": { + "open": false + } + }, + { + "key": "tts", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": { + "type": "web" + } + }, + { + "key": "whisper", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": { + "open": false, + "autoSend": false, + "autoTTSResponse": false + } + }, + { + "key": "scheduleTrigger", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": null + } + ], + "outputs": [] + }, + { + "nodeId": "448745", + "name": "common:core.module.template.work_start", + "intro": "", + "avatar": "core/workflow/template/workflowStart", + "flowNodeType": "workflowStart", + "position": { + "x": 632.368838596004, + "y": -347.7446492944009 + }, + "version": "481", + "inputs": [ + { + "key": "userChatInput", + "renderTypeList": ["reference", "textarea"], + "valueType": "string", + "label": "common:core.module.input.label.user question", + "required": true, + "toolDescription": "Question", + "debugLabel": "" + } + ], + "outputs": [ + { + "id": "userChatInput", + "key": "userChatInput", + "label": "common:core.module.input.label.user question", + "type": "static", + "valueType": "string", + "description": "" + } + ] + }, + { + "nodeId": "jVGuKrDfFTU6", + "name": "Obtain the date 24 hours ago", + "intro": "Runs a short script, typically for complex data processing.", + "avatar": "core/workflow/template/codeRun", + "flowNodeType": "code", + "showStatus": true, + "position": { + "x": 1045.4174257570808, + "y": -94.5419824521446 + }, + "version": "482", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "workflow:these_variables_will_be_input_parameters_for_code_execution", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "valueDesc": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "codeType", + "renderTypeList": ["hidden"], + "label": "", + "value": "js", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "code", + "renderTypeList": ["custom"], + "label": "", + "value": "function main() {\n const date = new Date();\n date.setDate(date.getDate() - 3);\n const day = date.getDate();\n const month = date.getMonth() + 1;\n const year = date.getFullYear();\n const hours = date.getHours();\n const minutes = date.getMinutes();\n\n return {\n date: `${year}-${month}-${day}T${hours}:${minutes}:000Z`,\n }\n }", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + } + ], + "outputs": [ + { + "id": "system_rawResponse", + "key": "system_rawResponse", + "label": "workflow:full_response_data", + "valueType": "object", + "type": "static", + "description": "" + }, + { + "id": "error", + "key": "error", + "label": "workflow:execution_error", + "description": "Error message returned when code execution error occurs. If the code is executed successfully, a null value is returned.", + "valueType": "object", + "type": "static" + }, + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "", + "customFieldConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + }, + "description": "Use the return value as the output and pass it to the next node. The variable name must match the key in the return value.", + "valueDesc": "" + }, + { + "id": "gR0mkQpJ4Og8", + "type": "dynamic", + "key": "date", + "valueType": "string", + "label": "date", + "valueDesc": "", + "description": "" + } + ] + }, + { + "nodeId": "jyftFRrd4RQf", + "name": "Specified answer", + "intro": "This module supports replies with a specified message, often used as guidance or prompts. Non-string content is automatically converted to a string.", + "avatar": "core/workflow/template/reply", + "flowNodeType": "answerNode", + "position": { + "x": 1758.8251385440858, + "y": 80.55020745654087 + }, + "version": "481", + "inputs": [ + { + "key": "text", + "renderTypeList": ["textarea", "reference"], + "valueType": "any", + "required": true, + "label": "Answer", + "description": "common:core.module.input.description.Response content", + "placeholder": "common:core.module.input.description.Response content", + "valueDesc": "", + "debugLabel": "", + "toolDescription": "", + "value": "拉取从 {{$jVGuKrDfFTU6.gR0mkQpJ4Og8$}} 以来所有的 open issue \\n" + } + ], + "outputs": [] + }, + { + "nodeId": "mCaalLpFoZFk", + "name": "Get Issues", + "intro": "Send an HTTP request to perform a more complex action (such as online search or database query).", + "avatar": "core/workflow/template/httpRequest", + "flowNodeType": "httpRequest468", + "showStatus": true, + "position": { + "x": 2602.5615507147536, + "y": -67.18952984768578 + }, + "version": "481", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "common:core.module.input.description.HTTP Dynamic Input", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "valueDesc": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpMethod", + "renderTypeList": ["custom"], + "valueType": "string", + "label": "", + "value": "GET", + "required": true, + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpTimeout", + "renderTypeList": ["custom"], + "valueType": "number", + "label": "", + "value": 30, + "min": 5, + "max": 600, + "required": true, + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpReqUrl", + "renderTypeList": ["hidden"], + "valueType": "string", + "label": "", + "description": "common:core.module.input.description.Http Request Url", + "placeholder": "https://api.ai.com/getInventory", + "required": false, + "value": "https://api.github.com/repos/labring/FastGPT/issues", + "valueDesc": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpHeader", + "renderTypeList": ["custom"], + "valueType": "any", + "value": [], + "label": "", + "description": "common:core.module.input.description.Http Request Header", + "placeholder": "common:core.module.input.description.Http Request Header", + "required": false, + "valueDesc": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpParams", + "renderTypeList": ["hidden"], + "valueType": "any", + "value": [ + { + "key": "state", + "type": "string", + "value": "open" + }, + { + "key": "since", + "type": "string", + "value": "{{$jVGuKrDfFTU6.gR0mkQpJ4Og8$}}" + } + ], + "label": "", + "required": false, + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpJsonBody", + "renderTypeList": ["hidden"], + "valueType": "any", + "value": "", + "label": "", + "required": false, + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpFormBody", + "renderTypeList": ["hidden"], + "valueType": "any", + "value": [], + "label": "", + "required": false, + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpContentType", + "renderTypeList": ["hidden"], + "valueType": "string", + "value": "json", + "label": "", + "required": false, + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + } + ], + "outputs": [ + { + "id": "error", + "key": "error", + "label": "workflow:request_error", + "description": "HTTP request error details. Returns null if successful.", + "valueType": "object", + "type": "static" + }, + { + "id": "httpRawResponse", + "key": "httpRawResponse", + "required": true, + "label": "workflow:raw_response", + "description": "Raw HTTP response. The response data must be a string or in JSON format.", + "valueType": "any", + "type": "static" + }, + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "", + "customFieldConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + }, + "valueDesc": "", + "description": "" + } + ] + }, + { + "nodeId": "gALvyJcXPoep", + "name": "API response processing", + "intro": "Runs a short script, typically for complex data processing.", + "avatar": "core/workflow/template/codeRun", + "flowNodeType": "code", + "showStatus": true, + "position": { + "x": 3396.722564475613, + "y": -80.79235153344955 + }, + "version": "482", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "workflow:these_variables_will_be_input_parameters_for_code_execution", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "valueDesc": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "codeType", + "renderTypeList": ["hidden"], + "label": "", + "value": "js", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "code", + "renderTypeList": ["custom"], + "label": "", + "value": "function main({res}) {\n const issues = JSON.parse(res);\n const ret = [];\n for(const issue of issues) {\n if (issue.pull_request) continue;\n ret.push({\n title: issue.title,\n body: issue.body,\n url: issue.html_url\n })\n }\n\n return {\n ret: JSON.stringify(ret)\n }\n}", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "res", + "label": "res", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "", + "value": ["mCaalLpFoZFk", "httpRawResponse"] + } + ], + "outputs": [ + { + "id": "system_rawResponse", + "key": "system_rawResponse", + "label": "workflow:full_response_data", + "valueType": "object", + "type": "static", + "description": "" + }, + { + "id": "error", + "key": "error", + "label": "workflow:execution_error", + "description": "Error message returned when code execution error occurs. If the code is executed successfully, a null value is returned.", + "valueType": "object", + "type": "static" + }, + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "", + "customFieldConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + }, + "description": "Use the return value as the output and pass it to the next node. The variable name must match the key in the return value.", + "valueDesc": "" + }, + { + "id": "qLUQfhG0ILRX", + "type": "dynamic", + "key": "ret", + "valueType": "string", + "label": "ret", + "valueDesc": "", + "description": "" + } + ] + }, + { + "nodeId": "aLrp6IjV8zAf", + "name": "AI chat", + "intro": "Chat with an model.", + "avatar": "core/workflow/template/aiChat", + "flowNodeType": "chatNode", + "showStatus": true, + "position": { + "x": 3907.7186093895143, + "y": -148.24856757598377 + }, + "version": "481", + "inputs": [ + { + "key": "model", + "renderTypeList": ["settingLLMModel", "reference"], + "label": "Model", + "valueType": "string", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "", + "value": "qwen-plus" + }, + { + "key": "temperature", + "renderTypeList": ["hidden"], + "label": "", + "value": 0, + "valueType": "number", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "maxToken", + "renderTypeList": ["hidden"], + "label": "", + "value": 8000, + "valueType": "number", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "isResponseAnswerText", + "renderTypeList": ["hidden"], + "label": "", + "value": true, + "valueType": "boolean", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "aiChatQuoteRole", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string", + "value": "system", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "quoteTemplate", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "quotePrompt", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "aiChatVision", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "boolean", + "value": false, + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "systemPrompt", + "renderTypeList": ["textarea", "reference"], + "max": 3000, + "valueType": "string", + "label": "Prompt", + "description": "core.app.tip.systemPromptTip", + "placeholder": "core.app.tip.chatNodeSystemPromptTip", + "valueDesc": "", + "debugLabel": "", + "toolDescription": "", + "value": "你是一个简洁高效的 GitHub Issue 概述助手,专长于提炼核心问题并以清晰简洁的方式呈现。\n\n## 任务\n分析输入的多条 issue 信息,为每个 issue 创建一个简明扼要的概述。使用中文输出。\n\n## 输入格式\nJSON 数组,每项包含 title(标题)、body(内容)和 url(链接)。\n\n## 输出格式\n对每个 issue 使用 Markdown 语法创建简洁的概述块。每个概述应包含:\n\n1. 使用加粗呈现 issue 的原标题\n2. 一段简短的问题概述(不超过 2-3 句话)\n3. 原 issue 的链接(使用 Markdown 链接语法)\n\n在概述中适当使用 emoji 来增加可读性,但不要过度使用。保持整体风格简洁明了。\n\n示例输出:\n\n---\n\n**🔍 数据可视化组件性能优化**\n\n这个 issue 反映了在处理大量数据时图表加载缓慢的问题。用户在数据点超过一定数量时experiencing明显的性能下降,影响了用户体验。\n\n📎 [查看原 issue](url1)\n\n---\n\n**🐞 移动端界面适配问题**\n\n该 issue 指出在某些特定型号的移动设备上出现了界面布局错乱的情况。这个问题影响了应用在不同尺寸屏幕上的一致性展现。\n\n📎 [查看原 issue](url2)\n\n---\n\n请确保每个 issue 概述都简洁明了,突出核心问题,避免过多细节。保持整体风格统一,让读者能快速理解每个 issue 的要点。" + }, + { + "key": "history", + "renderTypeList": ["numberInput", "reference"], + "valueType": "chatHistory", + "label": "Chats remembered", + "description": "workflow:max_dialog_rounds", + "required": true, + "min": 0, + "max": 50, + "value": 0, + "valueDesc": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "quoteQA", + "renderTypeList": ["settingDatasetQuotePrompt"], + "label": "", + "debugLabel": "Knowledge base reference", + "description": "", + "valueType": "datasetQuote", + "valueDesc": "", + "toolDescription": "" + }, + { + "key": "stringQuoteText", + "renderTypeList": ["reference", "textarea"], + "label": "Document reference", + "debugLabel": "Document reference", + "description": "app:document_quote_tip", + "valueType": "string", + "valueDesc": "", + "toolDescription": "" + }, + { + "key": "userChatInput", + "renderTypeList": ["reference", "textarea"], + "valueType": "string", + "label": "Question", + "toolDescription": "Question", + "required": true, + "value": ["gALvyJcXPoep", "qLUQfhG0ILRX"], + "valueDesc": "", + "description": "", + "debugLabel": "" + } + ], + "outputs": [ + { + "id": "history", + "key": "history", + "required": true, + "label": "common:core.module.output.label.New context", + "description": "Combines the current answer with the chat history to output a new context.", + "valueType": "chatHistory", + "valueDesc": "{\n obj: System | Human | AI;\n value: string;\n}[]", + "type": "static" + }, + { + "id": "answerText", + "key": "answerText", + "required": true, + "label": "common:core.module.output.label.Ai response content", + "description": "Triggered after the stream answer is complete.", + "valueType": "string", + "type": "static" + } + ] + }, + { + "nodeId": "jmSiT6OXA3Fe", + "name": "Feishu bot webhook", + "intro": "Sends webhook requests to the Feishu bot.", + "avatar": "/appMarketTemplates/plugin-feishu/avatar.svg", + "flowNodeType": "pluginModule", + "showStatus": false, + "position": { + "x": 4682.428295424065, + "y": 120.04658236877646 + }, + "version": "488", + "inputs": [ + { + "key": "system_forbid_stream", + "renderTypeList": ["switch"], + "valueType": "boolean", + "label": "Disable output in streaming mode", + "description": "Force nested apps to run in non-streaming mode.", + "value": true, + "valueDesc": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "renderTypeList": ["reference"], + "selectedTypeIndex": 0, + "valueType": "string", + "canEdit": false, + "key": "content", + "label": "content", + "description": "Message you want to send", + "required": true, + "toolDescription": "Message you want to send", + "value": ["aLrp6IjV8zAf", "answerText"], + "valueDesc": "", + "debugLabel": "" + }, + { + "renderTypeList": ["input"], + "selectedTypeIndex": 0, + "valueType": "string", + "canEdit": false, + "key": "hook_url", + "label": "hook_url", + "description": "Feishu bot address", + "required": true, + "defaultValue": "", + "value": "https://www.feishu.cn/flow/api/trigger-webhook/5a1657d6f024c639e1e9af4d9d611292", + "valueDesc": "", + "debugLabel": "", + "toolDescription": "" + } + ], + "outputs": [ + { + "id": "result", + "type": "static", + "key": "result", + "valueType": "object", + "label": "result", + "description": "", + "valueDesc": "" + } + ], + "pluginId": "community-feishu" + } + ], + "edges": [ + { + "source": "448745", + "target": "jVGuKrDfFTU6", + "sourceHandle": "448745-source-right", + "targetHandle": "jVGuKrDfFTU6-target-left" + }, + { + "source": "jVGuKrDfFTU6", + "target": "jyftFRrd4RQf", + "sourceHandle": "jVGuKrDfFTU6-source-right", + "targetHandle": "jyftFRrd4RQf-target-left" + }, + { + "source": "jyftFRrd4RQf", + "target": "mCaalLpFoZFk", + "sourceHandle": "jyftFRrd4RQf-source-right", + "targetHandle": "mCaalLpFoZFk-target-left" + }, + { + "source": "mCaalLpFoZFk", + "target": "gALvyJcXPoep", + "sourceHandle": "mCaalLpFoZFk-source-right", + "targetHandle": "gALvyJcXPoep-target-left" + }, + { + "source": "gALvyJcXPoep", + "target": "aLrp6IjV8zAf", + "sourceHandle": "gALvyJcXPoep-source-right", + "targetHandle": "aLrp6IjV8zAf-target-left" + }, + { + "source": "aLrp6IjV8zAf", + "target": "jmSiT6OXA3Fe", + "sourceHandle": "aLrp6IjV8zAf-source-right", + "targetHandle": "jmSiT6OXA3Fe-target-left" + } + ], + "chatConfig": { + "variables": [], + "scheduledTriggerConfig": { + "cronString": "", + "timezone": "Asia/Shanghai", + "defaultPrompt": "" + }, + "_id": "67152011bb78889107c3a4ec" + } + } + } + \ No newline at end of file diff --git a/packages/templates/src/google/enTemplate.json b/packages/templates/src/google/enTemplate.json new file mode 100644 index 000000000000..31db5044f30b --- /dev/null +++ b/packages/templates/src/google/enTemplate.json @@ -0,0 +1,444 @@ +{ + "name": "Google", + "intro": "Obtains related Google search results as reference for models.", + "author": "", + "avatar": "core/app/templates/google", + "tags": ["recommendation", "web-search"], + "type": "advanced", + "workflow": { + "nodes": [ + { + "nodeId": "userGuide", + "name": "System", + "intro": "Configure system parameters for the app.", + "avatar": "/imgs/workflow/userGuide.png", + "flowNodeType": "userGuide", + "position": { + "x": 262.2732338817093, + "y": -476.00241136598146 + }, + "version": "481", + "inputs": [ + { + "key": "welcomeText", + "renderTypeList": ["hidden"], + "valueType": "string", + "label": "core.app.Welcome Text", + "value": "" + }, + { + "key": "variables", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "core.app.Chat Variable", + "value": [] + }, + { + "key": "questionGuide", + "valueType": "any", + "renderTypeList": ["hidden"], + "label": "core.app.Question Guide", + "value": { + "open": false + } + }, + { + "key": "tts", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": { + "type": "web" + } + }, + { + "key": "whisper", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": { + "open": false, + "autoSend": false, + "autoTTSResponse": false + } + }, + { + "key": "scheduleTrigger", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": null + } + ], + "outputs": [] + }, + { + "nodeId": "448745", + "name": "Start", + "intro": "", + "avatar": "/imgs/workflow/userChatInput.svg", + "flowNodeType": "workflowStart", + "position": { + "x": 295.8944548701009, + "y": 110.81336038514848 + }, + "version": "481", + "inputs": [ + { + "key": "userChatInput", + "renderTypeList": ["reference", "textarea"], + "valueType": "string", + "label": "Question", + "required": true, + "toolDescription": "Question" + } + ], + "outputs": [ + { + "id": "userChatInput", + "key": "userChatInput", + "label": "core.module.input.label.user question", + "valueType": "string", + "type": "static" + } + ] + }, + { + "nodeId": "NOgbnBzUwDgT", + "name": "Tool call", + "intro": "Automatically calls one or more function modules, or calls a plugin directly.", + "avatar": "/imgs/workflow/tool.svg", + "flowNodeType": "tools", + "showStatus": true, + "position": { + "x": 1028.8358722416106, + "y": -500.8755882990822 + }, + "version": "481", + "inputs": [ + { + "key": "model", + "renderTypeList": ["settingLLMModel", "reference"], + "label": "core.module.input.label.aiModel", + "valueType": "string", + "llmModelType": "all", + "value": "FastAI-plus" + }, + { + "key": "temperature", + "renderTypeList": ["hidden"], + "label": "", + "value": 0, + "valueType": "number", + "min": 0, + "max": 10, + "step": 1 + }, + { + "key": "maxToken", + "renderTypeList": ["hidden"], + "label": "", + "value": 2000, + "valueType": "number", + "min": 100, + "max": 4000, + "step": 50 + }, + { + "key": "systemPrompt", + "renderTypeList": ["textarea", "reference"], + "max": 3000, + "valueType": "string", + "label": "core.ai.Prompt", + "description": "core.app.tip.systemPromptTip", + "placeholder": "core.app.tip.chatNodeSystemPromptTip", + "value": "" + }, + { + "key": "history", + "renderTypeList": ["numberInput", "reference"], + "valueType": "chatHistory", + "label": "core.module.input.label.chat history", + "description": "Maximum number of chats remembered", + "required": true, + "min": 0, + "max": 30, + "value": 6 + }, + { + "key": "userChatInput", + "renderTypeList": ["reference", "textarea"], + "valueType": "string", + "label": "Question", + "required": true, + "value": ["448745", "userChatInput"] + } + ], + "outputs": [ + { + "id": "NodeOutputKeyEnum.answerText", + "key": "NodeOutputKeyEnum.answerText", + "label": "core.module.output.label.Ai response content", + "description": "core.module.output.description.Ai response content", + "valueType": "string", + "type": "FlowNodeOutputTypeEnum.static" + } + ] + }, + { + "nodeId": "GMELVPxHfpg5", + "name": "HTTP request", + "intro": "Calls Google to find related content.", + "avatar": "/imgs/workflow/http.png", + "flowNodeType": "httpRequest468", + "showStatus": true, + "position": { + "x": 1005.4777753640342, + "y": 319.4905539380939 + }, + "version": "481", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "core.module.input.description.HTTP Dynamic Input" + }, + { + "valueType": "string", + "renderTypeList": ["reference"], + "key": "query", + "label": "query", + "toolDescription": "Google search keyword", + "required": true, + "canEdit": true, + "editField": { + "key": true, + "description": true + } + }, + { + "key": "system_httpMethod", + "renderTypeList": ["custom"], + "valueType": "string", + "label": "", + "value": "GET", + "required": true + }, + { + "key": "system_httpReqUrl", + "renderTypeList": ["hidden"], + "valueType": "string", + "label": "", + "description": "core.module.input.description.Http Request Url", + "placeholder": "https://api.ai.com/getInventory", + "required": false, + "value": "https://www.googleapis.com/customsearch/v1" + }, + { + "key": "system_httpHeader", + "renderTypeList": ["custom"], + "valueType": "any", + "value": [], + "label": "", + "description": "core.module.input.description.Http Request Header", + "placeholder": "core.module.input.description.Http Request Header", + "required": false + }, + { + "key": "system_httpParams", + "renderTypeList": ["hidden"], + "valueType": "any", + "value": [ + { + "key": "q", + "type": "string", + "value": "{{query}}" + }, + { + "key": "cx", + "type": "string", + "value": "谷歌搜索cxID" + }, + { + "key": "key", + "type": "string", + "value": "谷歌搜索key" + }, + { + "key": "c2coff", + "type": "string", + "value": "1" + }, + { + "key": "start", + "type": "string", + "value": "1" + }, + { + "key": "end", + "type": "string", + "value": "20" + }, + { + "key": "dateRestrict", + "type": "string", + "value": "m[1]" + } + ], + "label": "", + "required": false + }, + { + "key": "system_httpJsonBody", + "renderTypeList": ["hidden"], + "valueType": "any", + "value": "", + "label": "", + "required": false + } + ], + "outputs": [ + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "", + "editField": { + "key": true, + "valueType": true + } + }, + { + "id": "httpRawResponse", + "key": "httpRawResponse", + "label": "Original response", + "description": "Raw HTTP response. The response data must be a string or in JSON format.", + "valueType": "any", + "type": "static", + "required": true + }, + { + "id": "M5YmxaYe8em1", + "type": "dynamic", + "key": "prompt", + "valueType": "string", + "label": "prompt" + } + ] + }, + { + "nodeId": "poIbrrA8aiR0", + "name": "Code running", + "intro": "Runs a short script, typically for complex data processing.", + "avatar": "/imgs/workflow/code.svg", + "flowNodeType": "code", + "showStatus": true, + "position": { + "x": 1711.805344753384, + "y": 650.1023414708576 + }, + "version": "482", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "These variables will be used as input parameters for code execution.", + "editField": { + "key": true, + "valueType": true + } + }, + { + "key": "data", + "valueType": "object", + "label": "data", + "renderTypeList": ["reference"], + "description": "", + "canEdit": true, + "editField": { + "key": true, + "valueType": true + }, + "value": ["GMELVPxHfpg5", "httpRawResponse"] + }, + { + "key": "codeType", + "renderTypeList": ["hidden"], + "label": "", + "value": "js" + }, + { + "key": "code", + "renderTypeList": ["custom"], + "label": "", + "value": "function main({data}){\n const result = data.items.map((item) => ({\n title: item.title,\n link: item.link,\n snippet: item.snippet\n }))\n return { prompt: JSON.stringify(result) }\n}" + } + ], + "outputs": [ + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "", + "editField": { + "key": true, + "valueType": true + }, + "description": "Use the return value as the output and pass it to the next node. " + }, + { + "id": "system_rawResponse", + "key": "system_rawResponse", + "label": "Complete response", + "valueType": "object", + "type": "static" + }, + { + "id": "error", + "key": "error", + "label": "Running error", + "description": "Error message returned when code execution error occurs. If the code is executed successfully, a null value is returned.", + "valueType": "object", + "type": "static" + }, + { + "id": "qLUQfhG0ILRX", + "type": "dynamic", + "key": "prompt", + "valueType": "string", + "label": "prompt" + } + ] + } + ], + "edges": [ + { + "source": "448745", + "target": "NOgbnBzUwDgT", + "sourceHandle": "448745-source-right", + "targetHandle": "NOgbnBzUwDgT-target-left" + }, + { + "source": "NOgbnBzUwDgT", + "target": "GMELVPxHfpg5", + "sourceHandle": "selectedTools", + "targetHandle": "selectedTools" + }, + { + "source": "GMELVPxHfpg5", + "target": "poIbrrA8aiR0", + "sourceHandle": "GMELVPxHfpg5-source-right", + "targetHandle": "poIbrrA8aiR0-target-left" + } + ] + } + } + \ No newline at end of file diff --git a/packages/templates/src/longTranslate/enTemplate.json b/packages/templates/src/longTranslate/enTemplate.json new file mode 100644 index 000000000000..a8e61adc3a0d --- /dev/null +++ b/packages/templates/src/longTranslate/enTemplate.json @@ -0,0 +1,2162 @@ +{ + "name": "Long-text translation expert", + "intro": "Uses term knowledge base to assist translation, which is ideal for long-text translation.", + "author": "", + "avatar": "core/app/templates/TranslateRobot", + "tags": ["office-services"], + "type": "advanced", + "userGuide": { + "type": "link", + "content": "https://mp.weixin.qq.com/s/2kXdaSLOImhH1DTaFU8qXA" + }, + "workflow": { + "nodes": [ + { + "nodeId": "userGuide", + "name": "System", + "intro": "Configure system parameters for the app.", + "avatar": "core/workflow/template/systemConfig", + "flowNodeType": "userGuide", + "position": { + "x": -3580.557768149762, + "y": -789.3781335716197 + }, + "version": "481", + "inputs": [ + { + "key": "welcomeText", + "renderTypeList": ["hidden"], + "valueType": "string", + "label": "core.app.Welcome Text", + "value": "" + }, + { + "key": "variables", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "core.app.Chat Variable", + "value": [] + }, + { + "key": "questionGuide", + "valueType": "any", + "renderTypeList": ["hidden"], + "label": "core.app.Question Guide", + "value": { + "open": false + } + }, + { + "key": "tts", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": { + "type": "web" + } + }, + { + "key": "whisper", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": { + "open": false, + "autoSend": false, + "autoTTSResponse": false + } + }, + { + "key": "scheduleTrigger", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": null + } + ], + "outputs": [] + }, + { + "nodeId": "448745", + "name": "Start", + "intro": "", + "avatar": "core/workflow/template/workflowStart", + "flowNodeType": "workflowStart", + "position": { + "x": -4423.869761318094, + "y": 270.52507717746244 + }, + "version": "481", + "inputs": [ + { + "key": "userChatInput", + "renderTypeList": ["reference", "textarea"], + "valueType": "string", + "label": "Question", + "required": true, + "toolDescription": "Question", + "debugLabel": "" + } + ], + "outputs": [ + { + "id": "userChatInput", + "key": "userChatInput", + "label": "common:core.module.input.label.user question", + "type": "static", + "valueType": "string", + "description": "" + }, + { + "id": "userFiles", + "key": "userFiles", + "label": "app:workflow.user_file_input", + "description": "app:workflow.user_file_input_desc", + "type": "static", + "valueType": "arrayString" + } + ] + }, + { + "nodeId": "yjFO3YcM7KG2", + "name": "Multiple text block translation", + "intro": "Chat with an model.", + "avatar": "core/workflow/template/aiChat", + "flowNodeType": "chatNode", + "showStatus": true, + "position": { + "x": 889.049018199629, + "y": -146.47492282253756 + }, + "version": "481", + "inputs": [ + { + "key": "model", + "renderTypeList": ["settingLLMModel", "reference"], + "label": "core.module.input.label.aiModel", + "valueType": "string", + "selectedTypeIndex": 0, + "value": "claude-3-5-sonnet-20240620", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "temperature", + "renderTypeList": ["hidden"], + "label": "", + "value": 3, + "valueType": "number", + "min": 0, + "max": 10, + "step": 1, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "maxToken", + "renderTypeList": ["hidden"], + "label": "", + "value": 4000, + "valueType": "number", + "min": 100, + "max": 4000, + "step": 50, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "isResponseAnswerText", + "renderTypeList": ["hidden"], + "label": "", + "value": false, + "valueType": "boolean", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "aiChatQuoteRole", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string", + "value": "system", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "quoteTemplate", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string", + "value": "{{q}}\n{{a}}", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "quotePrompt", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string", + "value": "使用 标记中的内容作为你的翻译词库:\n\n\n{{quote}}\n\n\n原文: \"\"\"{{question}}\"\"\"", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "aiChatVision", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "boolean", + "value": false, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "systemPrompt", + "renderTypeList": ["textarea", "reference"], + "max": 3000, + "valueType": "string", + "label": "core.ai.Prompt", + "description": "core.app.tip.systemPromptTip", + "placeholder": "core.app.tip.chatNodeSystemPromptTip", + "value": "# Role: 资深翻译专家\n\n## Background:\n你是一位经验丰富的翻译专家,精通{{source_lang}}和{{target_lang}}互译,尤其擅长将{{source_lang}}文章译成流畅易懂的{{target_lang}}。你曾多次带领团队完成大型翻译项目,译文广受好评。\n\n## Attention:\n- 翻译过程中要始终坚持\"信、达、雅\"的原则,但\"达\"尤为重要\n- 翻译的译文要符合{{target_lang}}的表达习惯,通俗易懂,连贯流畅\n- 避免使用过于文绉绉的表达和晦涩难懂的典故引用 \n- 诗词歌词等内容需按原文换行和节奏分行,不破坏原排列格式 \n- 对于专有的名词或术语,按照给出的术语表进行合理替换 \n- 在翻译过程中,注意保留文档原有的列表项和格式标识\n- 不要翻译代码块中的内容,保持原样输出\n\n## Constraints:\n- 必须严格遵循四轮翻译流程:直译、意译、反思、提升\n- 译文要忠实原文,准确无误,不能遗漏或曲解原意\n- 注意判断上下文,避免重复翻译\n- 最终译文使用Markdown的代码块呈现,但是不用输出markdown这个单词\n\n## Goals:\n- 通过四轮翻译流程,将{{source_lang}}原文译成高质量的{{target_lang}}译文 \n- 译文要准确传达原文意思,语言表达力求浅显易懂,朗朗上口\n- 适度使用一些熟语俗语、流行网络用语等,增强译文的亲和力\n\n## Skills:\n- 精通{{source_lang}} {{target_lang}}两种语言,具有扎实的语言功底和丰富的翻译经验\n- 擅长将{{source_lang}}表达习惯转换为地道自然的{{target_lang}}\n- 对当代{{target_lang}}语言的发展变化有敏锐洞察,善于把握语言流行趋势\n\n## Workflow:\n1. 第一轮直译:逐字逐句忠实原文,不遗漏任何信息(代码块内容除外)\n2. 第二轮意译:在直译的基础上用通俗流畅的{{target_lang}}意译原文(代码块内容除外)\n3. 第三轮反思:仔细审视译文,分点列出一份建设性的批评和有用的建议清单以改进翻译,逐句提出建议,从以下6个角度展开\n (i) 准确性(纠正冗余、误译、遗漏或未翻译的文本错误),\n (ii) 流畅性(应用{{target_lang}}的语法、拼写和标点规则,并确保没有不必要的重复),\n (iii) 风格(确保翻译反映源文本的风格并考虑其文化背景),\n (iv) 术语(严格参考给出的术语表,确保术语使用一致)\n (v) 语序(合理调整语序,不要生搬{{source_lang}}中的语序,注意调整为{{target_lang}}中的合理语序)\n (vi) 代码保护(确保所有代码块内容保持原样,不被翻译)\n4. 第四轮提升:严格遵循第三轮提出的建议对翻译修改,定稿出一个简洁畅达、符合大众阅读习惯的译文\n\n## OutputFormat:\n- 每一轮前用【思考】说明该轮要点\n- 第一轮和第二轮翻译后用【翻译】呈现译文\n- 第三轮用【建议】输出建议清单,分点列出,在每一点前用*xxx*标识这条建议对应的要点,如*风格*;建议前用【思考】说明该轮要点,建议后用【建议】呈现建议\n- 第四轮在\\`\\`\\`代码块中展示最终译文内容,如\\`\\`\\`xxx\\`\\`\\`,不用输出markdown这个单词\n\n## Suggestions:\n- 直译时力求忠实原文,但不要过于拘泥逐字逐句\n- 意译时在准确表达原意的基础上,用最朴实无华的{{target_lang}}来表达\n- 反思环节重点关注译文是否符合{{target_lang}}表达习惯,是否通俗易懂,是否准确流畅,是否术语一致\n- 提升环节采用反思环节的建议对意译环节的翻译进行修改,适度采用一些口语化的表达、网络流行语等,增强译文的亲和力\n- 所有包含在代码块(\\`\\`\\`)中的内容都应保持原样,不进行翻译", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "history", + "renderTypeList": ["numberInput", "reference"], + "valueType": "chatHistory", + "label": "core.module.input.label.chat history", + "description": "workflow:max_dialog_rounds", + "required": true, + "min": 0, + "max": 50, + "value": 6, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "quoteQA", + "renderTypeList": ["settingDatasetQuotePrompt"], + "label": "", + "debugLabel": "Knowledge base reference", + "description": "", + "valueType": "datasetQuote", + "value": ["nLBlOh6lWxkY", "quoteQA"], + "toolDescription": "" + }, + { + "key": "stringQuoteText", + "renderTypeList": ["reference", "textarea"], + "label": "app:document_quote", + "debugLabel": "Document reference", + "description": "app:document_quote_tip", + "valueType": "string", + "toolDescription": "" + }, + { + "key": "userChatInput", + "renderTypeList": ["reference", "textarea"], + "valueType": "string", + "label": "Question", + "required": true, + "toolDescription": "Question", + "selectedTypeIndex": 1, + "value": "Your task is provide a professional translation from {{$VARIABLE_NODE_ID.source_lang$}} to {{$VARIABLE_NODE_ID.target_lang$}} of PART of a text.\n\nThe source text is below, delimited by XML tags and . Translate only the part within the source text\ndelimited by and . You can use the rest of the source text as context, but do not translate any\nof the other text. Do not output anything other than the translation of the indicated part of the text.\n\n\n{{$quYZgsW32ApA.xhXu6sdEWBnF$}}\n\n\nTo reiterate, you should translate only this part of the text, shown here again between and :\n\n{{$quYZgsW32ApA.eCp73lztAEGK$}}\n\n\nOutput only the translation of the portion you are asked to translate, and nothing else", + "debugLabel": "" + } + ], + "outputs": [ + { + "id": "history", + "key": "history", + "required": true, + "label": "common:core.module.output.label.New context", + "description": "Combines the current answer with the chat history to output a new context.", + "valueType": "chatHistory", + "valueDesc": "{\n obj: System | Human | AI;\n value: string;\n}[]", + "type": "static" + }, + { + "id": "answerText", + "key": "answerText", + "required": true, + "label": "common:core.module.output.label.Ai response content", + "description": "Triggered after the stream answer is complete.", + "valueType": "string", + "type": "static" + } + ] + }, + { + "nodeId": "w4heEpNflz59", + "name": "Check for execution completion", + "intro": "Executes a branch based on the specified conditions.", + "avatar": "core/workflow/template/ifelse", + "flowNodeType": "ifElseNode", + "showStatus": true, + "position": { + "x": 4127.598997947211, + "y": 266.8413637678663 + }, + "version": "481", + "inputs": [ + { + "key": "ifElseList", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": [ + { + "condition": "AND", + "list": [ + { + "variable": ["a2lqxASWi1vb", "nmBmGaARbKkl"], + "condition": "equalTo", + "value": "true" + } + ] + } + ], + "debugLabel": "", + "toolDescription": "" + } + ], + "outputs": [ + { + "id": "ifElseResult", + "key": "ifElseResult", + "label": "workflow:judgment_result", + "valueType": "string", + "type": "static", + "description": "" + } + ] + }, + { + "nodeId": "a2lqxASWi1vb", + "name": "Check for execution completion", + "intro": "Runs a short script, typically for complex data processing.", + "avatar": "core/workflow/template/codeRun", + "flowNodeType": "code", + "showStatus": true, + "position": { + "x": 3477.215830836187, + "y": 191.17869154122482 + }, + "version": "482", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "workflow:these_variables_will_be_input_parameters_for_code_execution", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "codeType", + "renderTypeList": ["hidden"], + "label": "", + "value": "js", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "code", + "renderTypeList": ["custom"], + "label": "", + "value": "function main({chunks, doc_chunks, currentChunk}){\n let new_chunks = doc_chunks || chunks\n const findIndex = new_chunks.findIndex((item) => item ===currentChunk)\n \n return {\n isEnd: new_chunks.length-1 === findIndex,\n i: findIndex + 1,\n }\n}", + "debugLabel": "", + "toolDescription": "" + }, + { + "renderTypeList": ["reference"], + "valueType": "arrayString", + "canEdit": true, + "key": "chunks", + "label": "chunks", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "value": ["rDzA1VxdpPIz", "qLUQfhG0ILRX"] + }, + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "currentChunk", + "label": "currentChunk", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "value": ["quYZgsW32ApA", "eCp73lztAEGK"] + }, + { + "renderTypeList": ["reference"], + "valueType": "arrayString", + "canEdit": true, + "key": "doc_chunks", + "label": "doc_chunks", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "value": ["rhyUytTUBm19", "qLUQfhG0ILRX"] + } + ], + "outputs": [ + { + "id": "system_rawResponse", + "key": "system_rawResponse", + "label": "workflow:full_response_data", + "valueType": "object", + "type": "static", + "description": "" + }, + { + "id": "error", + "key": "error", + "label": "workflow:execution_error", + "description": "Error message returned when code execution error occurs. If the code is executed successfully, a null value is returned.", + "valueType": "object", + "type": "static" + }, + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "", + "customFieldConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + }, + "description": "Use the return value as the output and pass it to the next node. The variable name must match the key in the return value." + }, + { + "id": "nmBmGaARbKkl", + "valueType": "boolean", + "type": "dynamic", + "key": "isEnd", + "label": "isEnd" + }, + { + "id": "nqB98uKpq6Ig", + "valueType": "number", + "type": "dynamic", + "key": "i", + "label": "i" + } + ] + }, + { + "nodeId": "quYZgsW32ApA", + "name": "Source text block formatting", + "intro": "Runs a short script, typically for complex data processing.", + "avatar": "core/workflow/template/codeRun", + "flowNodeType": "code", + "showStatus": true, + "position": { + "x": -453.20482176291137, + "y": 277.1422839492036 + }, + "version": "482", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "workflow:these_variables_will_be_input_parameters_for_code_execution", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "codeType", + "renderTypeList": ["hidden"], + "label": "", + "value": "js", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "code", + "renderTypeList": ["custom"], + "label": "", + "value": "function main({source_text_chunks, source_doc_text_chunks, i=0}){\n let chunks = source_doc_text_chunks || source_text_chunks;\n let before = chunks.slice(0, i).join(\"\");\n let current = \" \" + chunks[i] + \"\";\n let after = chunks.slice(i + 1).join(\"\");\n let tagged_text = before + current + after;\n\n return {\n tagged_text,\n chunk_to_translate: chunks[i],\n }\n}", + "debugLabel": "", + "toolDescription": "" + }, + { + "renderTypeList": ["reference"], + "valueType": "number", + "canEdit": true, + "key": "i", + "label": "i", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "value": ["a2lqxASWi1vb", "nqB98uKpq6Ig"] + }, + { + "renderTypeList": ["reference"], + "valueType": "arrayString", + "canEdit": true, + "key": "source_text_chunks", + "label": "source_text_chunks", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "value": ["rDzA1VxdpPIz", "qLUQfhG0ILRX"] + }, + { + "renderTypeList": ["reference"], + "valueType": "arrayString", + "canEdit": true, + "key": "source_doc_text_chunks", + "label": "source_doc_text_chunks", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "value": ["rhyUytTUBm19", "qLUQfhG0ILRX"] + } + ], + "outputs": [ + { + "id": "system_rawResponse", + "key": "system_rawResponse", + "label": "workflow:full_response_data", + "valueType": "object", + "type": "static", + "description": "" + }, + { + "id": "error", + "key": "error", + "label": "workflow:execution_error", + "description": "Error message returned when code execution error occurs. If the code is executed successfully, a null value is returned.", + "valueType": "object", + "type": "static" + }, + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "", + "customFieldConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + }, + "description": "Use the return value as the output and pass it to the next node. The variable name must match the key in the return value." + }, + { + "id": "xhXu6sdEWBnF", + "valueType": "string", + "type": "dynamic", + "key": "tagged_text", + "label": "tagged_text" + }, + { + "id": "eCp73lztAEGK", + "valueType": "string", + "type": "dynamic", + "key": "chunk_to_translate", + "label": "chunk_to_translate" + } + ] + }, + { + "nodeId": "vlNHndpNuFXB", + "name": "Translated text extraction", + "intro": "Runs a short script, typically for complex data processing.", + "avatar": "core/workflow/template/codeRun", + "flowNodeType": "code", + "showStatus": true, + "position": { + "x": 1614.87521037251, + "y": 105.52507717746244 + }, + "version": "482", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "workflow:these_variables_will_be_input_parameters_for_code_execution", + "editField": { + "key": true, + "valueType": true + }, + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "codeType", + "renderTypeList": ["hidden"], + "label": "", + "value": "js", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "code", + "renderTypeList": ["custom"], + "label": "", + "value": "function main({data1}){\n const result = data1.split(\"```\").filter(item => !!item.trim())\n\n if(result[result.length-1]) {\n return {\n result: result[result.length-1]\n }\n }\n\n return {\n result: '未截取到翻译内容'\n }\n}", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "data1", + "valueType": "string", + "label": "data1", + "renderTypeList": ["reference"], + "description": "", + "canEdit": true, + "editField": { + "key": true, + "valueType": true + }, + "value": ["yjFO3YcM7KG2", "answerText"], + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + } + } + ], + "outputs": [ + { + "id": "system_rawResponse", + "key": "system_rawResponse", + "label": "workflow:full_response_data", + "valueType": "object", + "type": "static", + "description": "" + }, + { + "id": "error", + "key": "error", + "label": "workflow:execution_error", + "description": "Error message returned when code execution error occurs. If the code is executed successfully, a null value is returned.", + "valueType": "object", + "type": "static" + }, + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "", + "customFieldConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + }, + "description": "Use the return value as the output and pass it to the next node. The variable name must match the key in the return value." + }, + { + "id": "qLUQfhG0ILRX", + "type": "dynamic", + "key": "result", + "valueType": "string", + "label": "result" + } + ] + }, + { + "nodeId": "qlt9KJbbS9yJ", + "name": "Check for same source and target languages", + "intro": "Executes a branch based on the specified conditions.", + "avatar": "core/workflow/template/ifelse", + "flowNodeType": "ifElseNode", + "showStatus": true, + "position": { + "x": -3489.136669871181, + "y": 500.7167825391806 + }, + "version": "481", + "inputs": [ + { + "key": "ifElseList", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": [ + { + "condition": "AND", + "list": [ + { + "variable": ["frjbsrlnJJsR", "qLUQfhG0ILRX"], + "condition": "equalTo", + "value": "false" + } + ] + } + ], + "debugLabel": "", + "toolDescription": "" + } + ], + "outputs": [ + { + "id": "ifElseResult", + "key": "ifElseResult", + "label": "workflow:judgment_result", + "valueType": "string", + "type": "static", + "description": "" + } + ] + }, + { + "nodeId": "frjbsrlnJJsR", + "name": "Check for same source and target languages", + "intro": "Runs a short script, typically for complex data processing.", + "avatar": "core/workflow/template/codeRun", + "flowNodeType": "code", + "showStatus": true, + "position": { + "x": -4015.234465113403, + "y": 286.93335454913375 + }, + "version": "482", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "workflow:these_variables_will_be_input_parameters_for_code_execution", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "codeType", + "renderTypeList": ["hidden"], + "label": "", + "value": "js", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "code", + "renderTypeList": ["custom"], + "label": "", + "value": "function main({source_lang, target_lang}){\n \n return {\n result: source_lang === target_lang\n }\n}", + "debugLabel": "", + "toolDescription": "" + }, + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "source_lang", + "label": "source_lang", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "value": ["VARIABLE_NODE_ID", "source_lang"] + }, + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "target_lang", + "label": "target_lang", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "value": ["VARIABLE_NODE_ID", "target_lang"] + } + ], + "outputs": [ + { + "id": "system_rawResponse", + "key": "system_rawResponse", + "label": "workflow:full_response_data", + "valueType": "object", + "type": "static", + "description": "" + }, + { + "id": "error", + "key": "error", + "label": "workflow:execution_error", + "description": "Error message returned when code execution error occurs. If the code is executed successfully, a null value is returned.", + "valueType": "object", + "type": "static" + }, + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "", + "customFieldConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + }, + "description": "Use the return value as the output and pass it to the next node. The variable name must match the key in the return value." + }, + { + "id": "qLUQfhG0ILRX", + "type": "dynamic", + "key": "result", + "valueType": "string", + "label": "result" + } + ], + "isFolded": false + }, + { + "nodeId": "dFxrGZS3Wmnz", + "name": "Prompt for same source and target languages", + "intro": "This module supports replies with a specified message, often used as guidance or prompts. Non-string content is automatically converted to a string.", + "avatar": "core/workflow/template/reply", + "flowNodeType": "answerNode", + "position": { + "x": -2965.234667648691, + "y": 976.026813286592 + }, + "version": "481", + "inputs": [ + { + "key": "text", + "renderTypeList": ["textarea", "reference"], + "valueType": "any", + "required": true, + "label": "core.module.input.label.Response content", + "description": "common:core.module.input.description.Response content", + "placeholder": "common:core.module.input.description.Response content", + "selectedTypeIndex": 0, + "value": "{{source_lang}} 无需再次翻译为 {{target_lang}} ~", + "debugLabel": "", + "toolDescription": "" + } + ], + "outputs": [] + }, + { + "nodeId": "rDzA1VxdpPIz", + "name": "Text splitting", + "intro": "Runs a short script, typically for complex data processing.", + "avatar": "core/workflow/template/codeRun", + "flowNodeType": "code", + "showStatus": true, + "position": { + "x": -1023.4352731829587, + "y": 122.9539005059388 + }, + "version": "482", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "workflow:these_variables_will_be_input_parameters_for_code_execution", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "valueDesc": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "codeType", + "renderTypeList": ["hidden"], + "label": "", + "value": "js", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "code", + "renderTypeList": ["custom"], + "label": "", + "value": "const MAX_HEADING_LENGTH = 7; // 最大标题长度\nconst MAX_HEADING_CONTENT_LENGTH = 200; // 最大标题内容长度\nconst MAX_HEADING_UNDERLINE_LENGTH = 200; // 最大标题下划线长度\nconst MAX_HTML_HEADING_ATTRIBUTES_LENGTH = 100; // 最大HTML标题属性长度\nconst MAX_LIST_ITEM_LENGTH = 200; // 最大列表项长度\nconst MAX_NESTED_LIST_ITEMS = 6; // 最大嵌套列表项数\nconst MAX_LIST_INDENT_SPACES = 7; // 最大列表缩进空格数\nconst MAX_BLOCKQUOTE_LINE_LENGTH = 200; // 最大块引用行长度\nconst MAX_BLOCKQUOTE_LINES = 15; // 最大块引用行数\nconst MAX_CODE_BLOCK_LENGTH = 1500; // 最大代码块长度\nconst MAX_CODE_LANGUAGE_LENGTH = 20; // 最大代码语言长度\nconst MAX_INDENTED_CODE_LINES = 20; // 最大缩进代码行数\nconst MAX_TABLE_CELL_LENGTH = 200; // 最大表格单元格长度\nconst MAX_TABLE_ROWS = 20; // 最大表格行数\nconst MAX_HTML_TABLE_LENGTH = 2000; // 最大HTML表格长度\nconst MIN_HORIZONTAL_RULE_LENGTH = 3; // 最小水平分隔线长度\nconst MAX_SENTENCE_LENGTH = 400; // 最大句子长度\nconst MAX_QUOTED_TEXT_LENGTH = 300; // 最大引用文本长度\nconst MAX_PARENTHETICAL_CONTENT_LENGTH = 200; // 最大括号内容长度\nconst MAX_NESTED_PARENTHESES = 5; // 最大嵌套括号数\nconst MAX_MATH_INLINE_LENGTH = 100; // 最大行内数学公式长度\nconst MAX_MATH_BLOCK_LENGTH = 500; // 最大数学公式块长度\nconst MAX_PARAGRAPH_LENGTH = 1000; // 最大段落长度\nconst MAX_STANDALONE_LINE_LENGTH = 800; // 最大独立行长度\nconst MAX_HTML_TAG_ATTRIBUTES_LENGTH = 100; // 最大HTML标签属性长度\nconst MAX_HTML_TAG_CONTENT_LENGTH = 1000; // 最大HTML标签内容长度\nconst LOOKAHEAD_RANGE = 100; // 向前查找句子边界的字符数\n\nconst AVOID_AT_START = `[\\\\s\\\\]})>,']`; // 避免在开头匹配的字符\nconst PUNCTUATION = `[.!?…]|\\\\.{3}|[\\\\u2026\\\\u2047-\\\\u2049]|[\\\\p{Emoji_Presentation}\\\\p{Extended_Pictographic}]`; // 标点符号\nconst QUOTE_END = `(?:'(?=\\`)|''(?=\\`\\`))`; // 引号结束\nconst SENTENCE_END = `(?:${PUNCTUATION}(?]{0,${MAX_HTML_HEADING_ATTRIBUTES_LENGTH}}>)[^\\\\r\\\\n]{1,${MAX_HEADING_CONTENT_LENGTH}}(?:)?(?:\\\\r?\\\\n|$))` +\n \"|\" +\n // New pattern for citations\n `(?:\\\\[[0-9]+\\\\][^\\\\r\\\\n]{1,${MAX_STANDALONE_LINE_LENGTH}})` +\n \"|\" +\n // 2. List items (bulleted, numbered, lettered, or task lists, including nested, up to three levels, with length constraints)\n `(?:(?:^|\\\\r?\\\\n)[ \\\\t]{0,3}(?:[-*+•]|\\\\d{1,3}\\\\.\\\\w\\\\.|\\\\[[ xX]\\\\])[ \\\\t]+${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_LIST_ITEM_LENGTH))}` +\n `(?:(?:\\\\r?\\\\n[ \\\\t]{2,5}(?:[-*+•]|\\\\d{1,3}\\\\.\\\\w\\\\.|\\\\[[ xX]\\\\])[ \\\\t]+${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_LIST_ITEM_LENGTH))}){0,${MAX_NESTED_LIST_ITEMS}}` +\n `(?:\\\\r?\\\\n[ \\\\t]{4,${MAX_LIST_INDENT_SPACES}}(?:[-*+•]|\\\\d{1,3}\\\\.\\\\w\\\\.|\\\\[[ xX]\\\\])[ \\\\t]+${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_LIST_ITEM_LENGTH))}){0,${MAX_NESTED_LIST_ITEMS}})?)` +\n \"|\" +\n // 3. Block quotes (including nested quotes and citations, up to three levels, with length constraints)\n `(?:(?:^>(?:>|\\\\s{2,}){0,2}${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_BLOCKQUOTE_LINE_LENGTH))}\\\\r?\\\\n?){1,${MAX_BLOCKQUOTE_LINES}})` +\n \"|\" +\n // 4. Code blocks (fenced, indented, or HTML pre/code tags, with length constraints)\n `(?:(?:^|\\\\r?\\\\n)(?:\\`\\`\\`|~~~)(?:\\\\w{0,${MAX_CODE_LANGUAGE_LENGTH}})?\\\\r?\\\\n[\\\\s\\\\S]{0,${MAX_CODE_BLOCK_LENGTH}}?(?:\\`\\`\\`|~~~)\\\\r?\\\\n?` +\n `|(?:(?:^|\\\\r?\\\\n)(?: {4}|\\\\t)[^\\\\r\\\\n]{0,${MAX_LIST_ITEM_LENGTH}}(?:\\\\r?\\\\n(?: {4}|\\\\t)[^\\\\r\\\\n]{0,${MAX_LIST_ITEM_LENGTH}}){0,${MAX_INDENTED_CODE_LINES}}\\\\r?\\\\n?)` +\n `|(?:
(?:)?[\\\\s\\\\S]{0,${MAX_CODE_BLOCK_LENGTH}}?(?:)?
))` +\n \"|\" +\n // 5. Tables (Markdown, grid tables, and HTML tables, with length constraints)\n `(?:(?:^|\\\\r?\\\\n)(?:\\\\|[^\\\\r\\\\n]{0,${MAX_TABLE_CELL_LENGTH}}\\\\|(?:\\\\r?\\\\n\\\\|[-:]{1,${MAX_TABLE_CELL_LENGTH}}\\\\|){0,1}(?:\\\\r?\\\\n\\\\|[^\\\\r\\\\n]{0,${MAX_TABLE_CELL_LENGTH}}\\\\|){0,${MAX_TABLE_ROWS}}` +\n `|[\\\\s\\\\S]{0,${MAX_HTML_TABLE_LENGTH}}?
))` +\n \"|\" +\n // 6. Horizontal rules (Markdown and HTML hr tag)\n `(?:^(?:[-*_]){${MIN_HORIZONTAL_RULE_LENGTH},}\\\\s*$|)` +\n \"|\" +\n // 10. Standalone lines or phrases (including single-line blocks and HTML elements, with length constraints)\n `(?!${AVOID_AT_START})(?:^(?:<[a-zA-Z][^>]{0,${MAX_HTML_TAG_ATTRIBUTES_LENGTH}}>)?${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_STANDALONE_LINE_LENGTH))}(?:)?(?:\\\\r?\\\\n|$))` +\n \"|\" +\n // 7. Sentences or phrases ending with punctuation (including ellipsis and Unicode punctuation)\n `(?!${AVOID_AT_START})${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_SENTENCE_LENGTH))}` +\n \"|\" +\n // 8. Quoted text, parenthetical phrases, or bracketed content (with length constraints)\n \"(?:\" +\n `(?)?${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_PARAGRAPH_LENGTH))}(?:

)?(?=\\\\r?\\\\n\\\\r?\\\\n|$))` +\n \"|\" +\n // 11. HTML-like tags and their content (including self-closing tags and attributes, with length constraints)\n `(?:<[a-zA-Z][^>]{0,${MAX_HTML_TAG_ATTRIBUTES_LENGTH}}(?:>[\\\\s\\\\S]{0,${MAX_HTML_TAG_CONTENT_LENGTH}}?|\\\\s*/>))` +\n \"|\" +\n // 12. LaTeX-style math expressions (inline and block, with length constraints)\n `(?:(?:\\\\$\\\\$[\\\\s\\\\S]{0,${MAX_MATH_BLOCK_LENGTH}}?\\\\$\\\\$)|(?:\\\\$[^\\\\$\\\\r\\\\n]{0,${MAX_MATH_INLINE_LENGTH}}\\\\$))` +\n \"|\" +\n // 14. Fallback for any remaining content (with length constraints)\n `(?!${AVOID_AT_START})${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_STANDALONE_LINE_LENGTH))}` +\n \")\",\n \"gmu\"\n);\n\nfunction main({text}){\n const chunks = [];\n let currentChunk = '';\n const tokens = countToken(text)\n\n const matches = text.match(regex);\n if (matches) {\n matches.forEach((match) => {\n if (currentChunk.length + match.length <= 1000) {\n currentChunk += match;\n } else {\n if (currentChunk) {\n chunks.push(currentChunk);\n }\n currentChunk = match;\n }\n });\n if (currentChunk) {\n chunks.push(currentChunk);\n }\n }\n\n return {chunks, tokens};\n}\n\n", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "text", + "label": "text", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "value": ["448745", "userChatInput"] + } + ], + "outputs": [ + { + "id": "system_rawResponse", + "key": "system_rawResponse", + "label": "workflow:full_response_data", + "valueType": "object", + "type": "static", + "description": "" + }, + { + "id": "error", + "key": "error", + "label": "workflow:execution_error", + "description": "Error message returned when code execution error occurs. If the code is executed successfully, a null value is returned.", + "valueType": "object", + "type": "static" + }, + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "", + "customFieldConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + }, + "description": "Use the return value as the output and pass it to the next node. The variable name must match the key in the return value.", + "valueDesc": "" + }, + { + "id": "qLUQfhG0ILRX", + "type": "dynamic", + "key": "chunks", + "valueType": "arrayString", + "label": "chunks", + "valueDesc": "", + "description": "" + } + ] + }, + { + "nodeId": "qwKPaLWbYTLa", + "name": "Translation result output", + "intro": "This module supports replies with a specified message, often used as guidance or prompts. Non-string content is automatically converted to a string.", + "avatar": "core/workflow/template/reply", + "flowNodeType": "answerNode", + "position": { + "x": 2771.2891802720287, + "y": 320.5042731766072 + }, + "version": "481", + "inputs": [ + { + "key": "text", + "renderTypeList": ["textarea", "reference"], + "valueType": "any", + "required": true, + "label": "Answer", + "description": "common:core.module.input.description.Response content", + "placeholder": "common:core.module.input.description.Response content", + "valueDesc": "", + "debugLabel": "", + "toolDescription": "", + "value": "{{$uow83rLCI2pI.qLUQfhG0ILRX$}}" + } + ], + "outputs": [] + }, + { + "nodeId": "nLBlOh6lWxkY", + "name": "Knowledge base search", + "intro": "Uses semantic, full-text, and database search capabilities to search for reference materials related to the questions from the selected knowledge bases.", + "avatar": "core/workflow/template/datasetSearch", + "flowNodeType": "datasetSearchNode", + "showStatus": true, + "position": { + "x": 164.44155974241983, + "y": 187.52507717746244 + }, + "version": "481", + "inputs": [ + { + "key": "datasets", + "renderTypeList": ["selectDataset", "reference"], + "label": "Select knowledge base", + "value": [], + "valueType": "selectDataset", + "required": true, + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "similarity", + "renderTypeList": ["selectDatasetParamsModal"], + "label": "", + "value": 0.2, + "valueType": "number", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "limit", + "renderTypeList": ["hidden"], + "label": "", + "value": 11000, + "valueType": "number", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "searchMode", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string", + "value": "embedding", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "usingReRank", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "boolean", + "value": false, + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "datasetSearchUsingExtensionQuery", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "boolean", + "value": true, + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "datasetSearchExtensionModel", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "", + "value": "gpt-5" + }, + { + "key": "datasetSearchExtensionBg", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string", + "value": "Translate a specific text containing many terms. Look up the terms in the term base as needed.", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "userChatInput", + "renderTypeList": ["reference", "textarea"], + "valueType": "string", + "label": "Question", + "required": true, + "toolDescription": "Content to be searched", + "valueDesc": "", + "description": "", + "debugLabel": "", + "selectedTypeIndex": 1, + "value": "中是你要翻译的原文,其中包含特定术语,请查找词库中术语对应的翻译\n\n\n\n{{$quYZgsW32ApA.eCp73lztAEGK$}}\n\n" + }, + { + "key": "collectionFilterMatch", + "renderTypeList": ["JSONEditor", "reference"], + "label": "Collection metadata filtering", + "valueType": "object", + "isPro": true, + "description": "workflow:filter_description", + "valueDesc": "", + "debugLabel": "", + "toolDescription": "" + } + ], + "outputs": [ + { + "id": "quoteQA", + "key": "quoteQA", + "label": "common:core.module.Dataset quote.label", + "description": "Special array format. An empty array is returned when no results are found.", + "type": "static", + "valueType": "datasetQuote", + "valueDesc": "{\n id: string;\n datasetId: string;\n collectionId: string;\n sourceName: string;\n sourceId?: string;\n q: string;\n a: string\n}[]" + } + ] + }, + { + "nodeId": "bdjAb5B2U1DQ", + "name": "Specified answer#3", + "intro": "This module supports replies with a specified message, often used as guidance or prompts. Non-string content is automatically converted to a string.", + "avatar": "core/workflow/template/reply", + "flowNodeType": "answerNode", + "position": { + "x": 5144.978517483216, + "y": 401.52507717746244 + }, + "version": "481", + "inputs": [ + { + "key": "text", + "renderTypeList": ["textarea", "reference"], + "valueType": "any", + "required": true, + "label": "Answer", + "description": "common:core.module.input.description.Response content", + "placeholder": "common:core.module.input.description.Response content", + "valueDesc": "", + "debugLabel": "", + "toolDescription": "", + "value": "*** 文档翻译完成!***" + } + ], + "outputs": [] + }, + { + "nodeId": "mt7GlCIwbO9I", + "name": "Check for file upload", + "intro": "Executes a branch based on the specified conditions.", + "avatar": "core/workflow/template/ifelse", + "flowNodeType": "ifElseNode", + "showStatus": true, + "position": { + "x": -2277.8211533688936, + "y": 608.6422839492036 + }, + "version": "481", + "inputs": [ + { + "key": "ifElseList", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": [ + { + "condition": "AND", + "list": [ + { + "variable": ["448745", "userFiles"], + "condition": "isEmpty" + } + ] + } + ], + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + } + ], + "outputs": [ + { + "id": "ifElseResult", + "key": "ifElseResult", + "label": "workflow:judgment_result", + "valueType": "string", + "type": "static", + "description": "" + } + ] + }, + { + "nodeId": "rhyUytTUBm19", + "name": "Document text splitting", + "intro": "Runs a short script, typically for complex data processing.", + "avatar": "core/workflow/template/codeRun", + "flowNodeType": "code", + "showStatus": true, + "position": { + "x": -1023.4352731829587, + "y": 959.6422839492036 + }, + "version": "482", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "workflow:these_variables_will_be_input_parameters_for_code_execution", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "valueDesc": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "codeType", + "renderTypeList": ["hidden"], + "label": "", + "value": "js", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "code", + "renderTypeList": ["custom"], + "label": "", + "value": "const MAX_HEADING_LENGTH = 7; // 最大标题长度\nconst MAX_HEADING_CONTENT_LENGTH = 200; // 最大标题内容长度\nconst MAX_HEADING_UNDERLINE_LENGTH = 200; // 最大标题下划线长度\nconst MAX_HTML_HEADING_ATTRIBUTES_LENGTH = 100; // 最大HTML标题属性长度\nconst MAX_LIST_ITEM_LENGTH = 200; // 最大列表项长度\nconst MAX_NESTED_LIST_ITEMS = 6; // 最大嵌套列表项数\nconst MAX_LIST_INDENT_SPACES = 7; // 最大列表缩进空格数\nconst MAX_BLOCKQUOTE_LINE_LENGTH = 200; // 最大块引用行长度\nconst MAX_BLOCKQUOTE_LINES = 15; // 最大块引用行数\nconst MAX_CODE_BLOCK_LENGTH = 1500; // 最大代码块长度\nconst MAX_CODE_LANGUAGE_LENGTH = 20; // 最大代码语言长度\nconst MAX_INDENTED_CODE_LINES = 20; // 最大缩进代码行数\nconst MAX_TABLE_CELL_LENGTH = 200; // 最大表格单元格长度\nconst MAX_TABLE_ROWS = 20; // 最大表格行数\nconst MAX_HTML_TABLE_LENGTH = 2000; // 最大HTML表格长度\nconst MIN_HORIZONTAL_RULE_LENGTH = 3; // 最小水平分隔线长度\nconst MAX_SENTENCE_LENGTH = 400; // 最大句子长度\nconst MAX_QUOTED_TEXT_LENGTH = 300; // 最大引用文本长度\nconst MAX_PARENTHETICAL_CONTENT_LENGTH = 200; // 最大括号内容长度\nconst MAX_NESTED_PARENTHESES = 5; // 最大嵌套括号数\nconst MAX_MATH_INLINE_LENGTH = 100; // 最大行内数学公式长度\nconst MAX_MATH_BLOCK_LENGTH = 500; // 最大数学公式块长度\nconst MAX_PARAGRAPH_LENGTH = 1000; // 最大段落长度\nconst MAX_STANDALONE_LINE_LENGTH = 800; // 最大独立行长度\nconst MAX_HTML_TAG_ATTRIBUTES_LENGTH = 100; // 最大HTML标签属性长度\nconst MAX_HTML_TAG_CONTENT_LENGTH = 1000; // 最大HTML标签内容长度\nconst LOOKAHEAD_RANGE = 100; // 向前查找句子边界的字符数\n\nconst AVOID_AT_START = `[\\\\s\\\\]})>,']`; // 避免在开头匹配的字符\nconst PUNCTUATION = `[.!?…]|\\\\.{3}|[\\\\u2026\\\\u2047-\\\\u2049]|[\\\\p{Emoji_Presentation}\\\\p{Extended_Pictographic}]`; // 标点符号\nconst QUOTE_END = `(?:'(?=\\`)|''(?=\\`\\`))`; // 引号结束\nconst SENTENCE_END = `(?:${PUNCTUATION}(?]{0,${MAX_HTML_HEADING_ATTRIBUTES_LENGTH}}>)[^\\\\r\\\\n]{1,${MAX_HEADING_CONTENT_LENGTH}}(?:)?(?:\\\\r?\\\\n|$))` +\n \"|\" +\n // New pattern for citations\n `(?:\\\\[[0-9]+\\\\][^\\\\r\\\\n]{1,${MAX_STANDALONE_LINE_LENGTH}})` +\n \"|\" +\n // 2. List items (bulleted, numbered, lettered, or task lists, including nested, up to three levels, with length constraints)\n `(?:(?:^|\\\\r?\\\\n)[ \\\\t]{0,3}(?:[-*+•]|\\\\d{1,3}\\\\.\\\\w\\\\.|\\\\[[ xX]\\\\])[ \\\\t]+${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_LIST_ITEM_LENGTH))}` +\n `(?:(?:\\\\r?\\\\n[ \\\\t]{2,5}(?:[-*+•]|\\\\d{1,3}\\\\.\\\\w\\\\.|\\\\[[ xX]\\\\])[ \\\\t]+${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_LIST_ITEM_LENGTH))}){0,${MAX_NESTED_LIST_ITEMS}}` +\n `(?:\\\\r?\\\\n[ \\\\t]{4,${MAX_LIST_INDENT_SPACES}}(?:[-*+•]|\\\\d{1,3}\\\\.\\\\w\\\\.|\\\\[[ xX]\\\\])[ \\\\t]+${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_LIST_ITEM_LENGTH))}){0,${MAX_NESTED_LIST_ITEMS}})?)` +\n \"|\" +\n // 3. Block quotes (including nested quotes and citations, up to three levels, with length constraints)\n `(?:(?:^>(?:>|\\\\s{2,}){0,2}${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_BLOCKQUOTE_LINE_LENGTH))}\\\\r?\\\\n?){1,${MAX_BLOCKQUOTE_LINES}})` +\n \"|\" +\n // 4. Code blocks (fenced, indented, or HTML pre/code tags, with length constraints)\n `(?:(?:^|\\\\r?\\\\n)(?:\\`\\`\\`|~~~)(?:\\\\w{0,${MAX_CODE_LANGUAGE_LENGTH}})?\\\\r?\\\\n[\\\\s\\\\S]{0,${MAX_CODE_BLOCK_LENGTH}}?(?:\\`\\`\\`|~~~)\\\\r?\\\\n?` +\n `|(?:(?:^|\\\\r?\\\\n)(?: {4}|\\\\t)[^\\\\r\\\\n]{0,${MAX_LIST_ITEM_LENGTH}}(?:\\\\r?\\\\n(?: {4}|\\\\t)[^\\\\r\\\\n]{0,${MAX_LIST_ITEM_LENGTH}}){0,${MAX_INDENTED_CODE_LINES}}\\\\r?\\\\n?)` +\n `|(?:
(?:)?[\\\\s\\\\S]{0,${MAX_CODE_BLOCK_LENGTH}}?(?:)?
))` +\n \"|\" +\n // 5. Tables (Markdown, grid tables, and HTML tables, with length constraints)\n `(?:(?:^|\\\\r?\\\\n)(?:\\\\|[^\\\\r\\\\n]{0,${MAX_TABLE_CELL_LENGTH}}\\\\|(?:\\\\r?\\\\n\\\\|[-:]{1,${MAX_TABLE_CELL_LENGTH}}\\\\|){0,1}(?:\\\\r?\\\\n\\\\|[^\\\\r\\\\n]{0,${MAX_TABLE_CELL_LENGTH}}\\\\|){0,${MAX_TABLE_ROWS}}` +\n `|[\\\\s\\\\S]{0,${MAX_HTML_TABLE_LENGTH}}?
))` +\n \"|\" +\n // 6. Horizontal rules (Markdown and HTML hr tag)\n `(?:^(?:[-*_]){${MIN_HORIZONTAL_RULE_LENGTH},}\\\\s*$|)` +\n \"|\" +\n // 10. Standalone lines or phrases (including single-line blocks and HTML elements, with length constraints)\n `(?!${AVOID_AT_START})(?:^(?:<[a-zA-Z][^>]{0,${MAX_HTML_TAG_ATTRIBUTES_LENGTH}}>)?${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_STANDALONE_LINE_LENGTH))}(?:)?(?:\\\\r?\\\\n|$))` +\n \"|\" +\n // 7. Sentences or phrases ending with punctuation (including ellipsis and Unicode punctuation)\n `(?!${AVOID_AT_START})${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_SENTENCE_LENGTH))}` +\n \"|\" +\n // 8. Quoted text, parenthetical phrases, or bracketed content (with length constraints)\n \"(?:\" +\n `(?)?${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_PARAGRAPH_LENGTH))}(?:

)?(?=\\\\r?\\\\n\\\\r?\\\\n|$))` +\n \"|\" +\n // 11. HTML-like tags and their content (including self-closing tags and attributes, with length constraints)\n `(?:<[a-zA-Z][^>]{0,${MAX_HTML_TAG_ATTRIBUTES_LENGTH}}(?:>[\\\\s\\\\S]{0,${MAX_HTML_TAG_CONTENT_LENGTH}}?|\\\\s*/>))` +\n \"|\" +\n // 12. LaTeX-style math expressions (inline and block, with length constraints)\n `(?:(?:\\\\$\\\\$[\\\\s\\\\S]{0,${MAX_MATH_BLOCK_LENGTH}}?\\\\$\\\\$)|(?:\\\\$[^\\\\$\\\\r\\\\n]{0,${MAX_MATH_INLINE_LENGTH}}\\\\$))` +\n \"|\" +\n // 14. Fallback for any remaining content (with length constraints)\n `(?!${AVOID_AT_START})${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_STANDALONE_LINE_LENGTH))}` +\n \")\",\n \"gmu\"\n);\n\nfunction main({text}){\n const chunks = [];\n let currentChunk = '';\n const tokens = countToken(text)\n\n const matches = text.match(regex);\n if (matches) {\n matches.forEach((match) => {\n if (currentChunk.length + match.length <= 1000) {\n currentChunk += match;\n } else {\n if (currentChunk) {\n chunks.push(currentChunk);\n }\n currentChunk = match;\n }\n });\n if (currentChunk) {\n chunks.push(currentChunk);\n }\n }\n\n return {chunks, tokens};\n}\n\n", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "text", + "label": "text", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "value": ["dtUmoUtTKfpM", "system_text"] + } + ], + "outputs": [ + { + "id": "system_rawResponse", + "key": "system_rawResponse", + "label": "workflow:full_response_data", + "valueType": "object", + "type": "static", + "description": "" + }, + { + "id": "error", + "key": "error", + "label": "workflow:execution_error", + "description": "Error message returned when code execution error occurs. If the code is executed successfully, a null value is returned.", + "valueType": "object", + "type": "static" + }, + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "", + "customFieldConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + }, + "description": "Use the return value as the output and pass it to the next node. The variable name must match the key in the return value.", + "valueDesc": "" + }, + { + "id": "qLUQfhG0ILRX", + "type": "dynamic", + "key": "chunks", + "valueType": "arrayString", + "label": "chunks", + "valueDesc": "", + "description": "" + } + ] + }, + { + "nodeId": "dtUmoUtTKfpM", + "name": "Document parsing", + "intro": "Parses all files uploaded in the conversation and returns the results.", + "avatar": "core/workflow/template/readFiles", + "flowNodeType": "readFiles", + "showStatus": true, + "position": { + "x": -1503.9824853456694, + "y": 1031.2341899921714 + }, + "version": "489", + "inputs": [ + { + "key": "fileUrlList", + "renderTypeList": ["reference"], + "valueType": "arrayString", + "label": "Document link", + "required": true, + "value": ["448745", "userFiles"], + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + } + ], + "outputs": [ + { + "id": "system_text", + "key": "system_text", + "label": "app:workflow.read_files_result", + "description": "A file consists of a file name and the content. Multiple files are separated by hyphens.", + "valueType": "string", + "type": "static" + } + ] + }, + { + "nodeId": "uow83rLCI2pI", + "name": "Chinese translation formatting", + "intro": "Runs a short script, typically for complex data processing.", + "avatar": "core/workflow/template/codeRun", + "flowNodeType": "code", + "showStatus": true, + "position": { + "x": 2205.3511517635047, + "y": 121.1512729428531 + }, + "version": "482", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "workflow:these_variables_will_be_input_parameters_for_code_execution", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "valueDesc": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "codeType", + "renderTypeList": ["hidden"], + "label": "", + "value": "js", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "code", + "renderTypeList": ["custom"], + "label": "", + "value": "function main({target_lang, source_text}) {\n let text = source_text;\n\n if (target_lang === '简体中文' || target_lang === '繁體中文') {\n // 存储代码块内容\n const codeBlocks = [];\n let text = source_text.replace(/```[\\s\\S]*?```/g, (match) => {\n codeBlocks.push(match);\n return `__CODE_BLOCK_${codeBlocks.length - 1}__`;\n });\n\n // 替换成对的英文引号\n text = text.replace(/\"(.*?)\"/g, '“$1”');\n\n // 保护 Markdown 链接格式中的括号\n text = text.replace(/\\[(.*?)\\]\\((.*?)\\)/g, function(match) {\n return match.replace(/\\(/g, 'LEFTPAREN')\n .replace(/\\)/g, 'RIGHTPAREN');\n });\n\n // 替换成对的英文括号\n text = text.replace(/\\((.*?)\\)/g, '($1)');\n\n // 恢复被保护的 Markdown 链接括号\n text = text.replace(/LEFTPAREN/g, '(')\n .replace(/RIGHTPAREN/g, ')');\n\n // 更新句号替换逻辑,增加对版本号和URL的保护\n text = text.replace(/(\\d+)\\.(\\d+)\\.(\\d+)/g, '$1DOT$2DOT$3') // 保护版本号 (如 16.2.1)\n .replace(/(\\d)\\.(\\d)/g, '$1DOT$2') // 临时替换小数点\n .replace(/([a-zA-Z])\\.([a-zA-Z])/g, '$1DOT$2') // 临时替换缩写中的句号\n .replace(/([a-zA-Z])\\.(\\d)/g, '$1DOT$2') // 临时替换字母与数字之间的句号\n .replace(/(\\d)\\.([a-zA-Z])/g, '$1DOT$2') // 临时替换数字与字母之间的句号\n .replace(/([a-zA-Z])\\./g, '$1DOT') // 临时替换字母后面的句号(如 a.)\n .replace(/https?:/g, 'HTTPCOLON') // 保护 URL 中的冒号\n .replace(/\\./g, '。') // 替换其他句号\n .replace(/DOT/g, '.') // 恢复被保护的句号\n .replace(/HTTPCOLON/g, 'http:'); // 恢复 URL 中的冒号\n\n // 替换英文逗号,但不替换数字中的逗号\n text = text.replace(/(\\d),(\\d)/g, '$1COMMA$2') // 临时替换数字中的逗号\n .replace(/,/g, ',') // 替换其他逗号\n .replace(/COMMA/g, ','); // 恢复数字中的逗号\n\n // 替换其他常见符号\n const replacements = {\n '!': '!',\n '?': '?',\n ';': ';',\n };\n\n for (const [key, value] of Object.entries(replacements)) {\n text = text.replace(new RegExp(`\\\\${key}`, 'g'), value);\n }\n\n // 在中文和英文字符之间添加空格\n // 中文字符范围: \\u4e00-\\u9fa5\n // 英文字符范围: a-zA-Z0-9\n text = text.replace(/([\\u4e00-\\u9fa5])([a-zA-Z0-9])/g, '$1 $2')\n .replace(/([a-zA-Z0-9])([\\u4e00-\\u9fa5])/g, '$1 $2');\n\n // 恢复代码块\n text = text.replace(/__CODE_BLOCK_(\\d+)__/g, (_, index) => codeBlocks[index]);\n }\n\n return {\n text\n };\n}", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "source_text", + "label": "source_text", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "", + "value": ["vlNHndpNuFXB", "qLUQfhG0ILRX"] + }, + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "target_lang", + "label": "target_lang", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "value": ["VARIABLE_NODE_ID", "target_lang"] + } + ], + "outputs": [ + { + "id": "system_rawResponse", + "key": "system_rawResponse", + "label": "workflow:full_response_data", + "valueType": "object", + "type": "static", + "description": "" + }, + { + "id": "error", + "key": "error", + "label": "workflow:execution_error", + "description": "Error message returned when code execution error occurs. If the code is executed successfully, a null value is returned.", + "valueType": "object", + "type": "static" + }, + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "", + "customFieldConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + }, + "description": "Use the return value as the output and pass it to the next node. The variable name must match the key in the return value.", + "valueDesc": "" + }, + { + "id": "qLUQfhG0ILRX", + "type": "dynamic", + "key": "text", + "valueType": "string", + "label": "text", + "valueDesc": "", + "description": "" + } + ] + } + ], + "edges": [ + { + "source": "a2lqxASWi1vb", + "target": "w4heEpNflz59", + "sourceHandle": "a2lqxASWi1vb-source-right", + "targetHandle": "w4heEpNflz59-target-left" + }, + { + "source": "w4heEpNflz59", + "target": "quYZgsW32ApA", + "sourceHandle": "w4heEpNflz59-source-ELSE", + "targetHandle": "quYZgsW32ApA-target-left" + }, + { + "source": "448745", + "target": "frjbsrlnJJsR", + "sourceHandle": "448745-source-right", + "targetHandle": "frjbsrlnJJsR-target-left" + }, + { + "source": "frjbsrlnJJsR", + "target": "qlt9KJbbS9yJ", + "sourceHandle": "frjbsrlnJJsR-source-right", + "targetHandle": "qlt9KJbbS9yJ-target-left" + }, + { + "source": "qlt9KJbbS9yJ", + "target": "dFxrGZS3Wmnz", + "sourceHandle": "qlt9KJbbS9yJ-source-ELSE", + "targetHandle": "dFxrGZS3Wmnz-target-top" + }, + { + "source": "yjFO3YcM7KG2", + "target": "vlNHndpNuFXB", + "sourceHandle": "yjFO3YcM7KG2-source-right", + "targetHandle": "vlNHndpNuFXB-target-left" + }, + { + "source": "rDzA1VxdpPIz", + "target": "quYZgsW32ApA", + "sourceHandle": "rDzA1VxdpPIz-source-right", + "targetHandle": "quYZgsW32ApA-target-left" + }, + { + "source": "qwKPaLWbYTLa", + "target": "a2lqxASWi1vb", + "sourceHandle": "qwKPaLWbYTLa-source-right", + "targetHandle": "a2lqxASWi1vb-target-left" + }, + { + "source": "nLBlOh6lWxkY", + "target": "yjFO3YcM7KG2", + "sourceHandle": "nLBlOh6lWxkY-source-right", + "targetHandle": "yjFO3YcM7KG2-target-left" + }, + { + "source": "w4heEpNflz59", + "target": "bdjAb5B2U1DQ", + "sourceHandle": "w4heEpNflz59-source-IF", + "targetHandle": "bdjAb5B2U1DQ-target-left" + }, + { + "source": "qlt9KJbbS9yJ", + "target": "mt7GlCIwbO9I", + "sourceHandle": "qlt9KJbbS9yJ-source-IF", + "targetHandle": "mt7GlCIwbO9I-target-left" + }, + { + "source": "mt7GlCIwbO9I", + "target": "rDzA1VxdpPIz", + "sourceHandle": "mt7GlCIwbO9I-source-IF", + "targetHandle": "rDzA1VxdpPIz-target-left" + }, + { + "source": "rhyUytTUBm19", + "target": "quYZgsW32ApA", + "sourceHandle": "rhyUytTUBm19-source-right", + "targetHandle": "quYZgsW32ApA-target-left" + }, + { + "source": "mt7GlCIwbO9I", + "target": "dtUmoUtTKfpM", + "sourceHandle": "mt7GlCIwbO9I-source-ELSE", + "targetHandle": "dtUmoUtTKfpM-target-left" + }, + { + "source": "dtUmoUtTKfpM", + "target": "rhyUytTUBm19", + "sourceHandle": "dtUmoUtTKfpM-source-right", + "targetHandle": "rhyUytTUBm19-target-left" + }, + { + "source": "vlNHndpNuFXB", + "target": "uow83rLCI2pI", + "sourceHandle": "vlNHndpNuFXB-source-right", + "targetHandle": "uow83rLCI2pI-target-left" + }, + { + "source": "uow83rLCI2pI", + "target": "qwKPaLWbYTLa", + "sourceHandle": "uow83rLCI2pI-source-right", + "targetHandle": "qwKPaLWbYTLa-target-left" + }, + { + "source": "quYZgsW32ApA", + "target": "nLBlOh6lWxkY", + "sourceHandle": "quYZgsW32ApA-source-right", + "targetHandle": "nLBlOh6lWxkY-target-left" + } + ], + "chatConfig": { + "welcomeText": "Hi, welcome to use the document translation bot.\n1. Select the source and target languages.\n2. Send the document you want to translate.\n3. Wait for the translation result.", + "variables": [ + { + "id": "v98n5b", + "key": "source_lang", + "label": "Source language", + "type": "select", + "required": true, + "maxLen": 50, + "enums": [ + { + "value": "简体中文" + }, + { + "value": "繁體中文" + }, + { + "value": "English" + }, + { + "value": "Español" + }, + { + "value": "Français" + }, + { + "value": "Deutsch" + }, + { + "value": "Italiano" + }, + { + "value": "日本語" + }, + { + "value": "한국어" + }, + { + "value": "Русский" + }, + { + "value": "العربية" + }, + { + "value": "Bahasa Indonesia" + }, + { + "value": "Polski" + } + ], + "icon": "core/app/variable/select" + }, + { + "id": "c3tvge", + "key": "target_lang", + "label": "Target language", + "type": "select", + "required": true, + "maxLen": 50, + "enums": [ + { + "value": "简体中文" + }, + { + "value": "繁體中文" + }, + { + "value": "English" + }, + { + "value": "Español" + }, + { + "value": "Français" + }, + { + "value": "Deutsch" + }, + { + "value": "Italiano" + }, + { + "value": "日本語" + }, + { + "value": "한국어" + }, + { + "value": "Русский" + }, + { + "value": "العربية" + }, + { + "value": "Bahasa Indonesia" + }, + { + "value": "Polski" + } + ], + "icon": "core/app/variable/select" + } + ], + "scheduledTriggerConfig": { + "cronString": "", + "timezone": "Asia/Shanghai", + "defaultPrompt": "" + }, + "fileSelectConfig": { + "canSelectFile": true, + "canSelectImg": false, + "maxFiles": 10 + }, + "_id": "6688b45317c65410d61d58aa" + } + } + } + \ No newline at end of file diff --git a/packages/templates/src/plugin-dalle/enTemplate.json b/packages/templates/src/plugin-dalle/enTemplate.json new file mode 100644 index 000000000000..5f4475fc0017 --- /dev/null +++ b/packages/templates/src/plugin-dalle/enTemplate.json @@ -0,0 +1,332 @@ +{ + "name": "DALL·E 3", + "intro": "Generates images by calling the DALL·E 3 API.", + "author": "", + "avatar": "core/app/templates/plugin-dalle", + "type": "plugin", + "tags": ["recommendation", "image-generation"], + "workflow": { + "nodes": [ + { + "nodeId": "pluginInput", + "name": "Plugin input", + "intro": "Configure inputs required for running a plugin.", + "avatar": "core/workflow/template/workflowStart", + "flowNodeType": "pluginInput", + "showStatus": false, + "position": { + "x": 421.97302886868476, + "y": -89.7785530936485 + }, + "version": "481", + "inputs": [ + { + "renderTypeList": ["reference"], + "selectedTypeIndex": 0, + "valueType": "string", + "canEdit": true, + "key": "绘图提示词", + "label": "绘图提示词", + "description": "绘图提示词", + "required": true, + "toolDescription": "绘图提示词" + } + ], + "outputs": [ + { + "id": "绘图提示词", + "valueType": "string", + "key": "绘图提示词", + "label": "绘图提示词", + "type": "hidden" + } + ] + }, + { + "nodeId": "pluginOutput", + "name": "Plugin output", + "intro": "Customize external output. When a plugin is used, only the custom output is exposed.", + "intro_http_request": "Send an HTTP request to perform a more complex action (such as online search or database query).", + "avatar": "core/workflow/template/pluginOutput", + "flowNodeType": "pluginOutput", + "showStatus": false, + "position": { + "x": 1785.9300180845394, + "y": -108.2785530936485 + }, + "version": "481", + "inputs": [ + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "Image link", + "label": "Image link", + "description": "", + "value": ["tMvel910bnrJ", "pJXgWoTpPoMy"] + }, + { + "renderTypeList": ["reference"], + "valueType": "object", + "canEdit": true, + "key": "error", + "label": "Error details", + "description": "", + "value": ["tMvel910bnrJ", "error"] + } + ], + "outputs": [] + }, + { + "nodeId": "tMvel910bnrJ", + "name": "HTTP request", + "intro": "Send an HTTP request to perform a more complex action (such as online search or database query).", + "avatar": "core/workflow/template/httpRequest", + "flowNodeType": "httpRequest468", + "showStatus": true, + "position": { + "x": 1044.8838211811253, + "y": -414.7785530936485 + }, + "version": "481", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "common:core.module.input.description.HTTP Dynamic Input", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpMethod", + "renderTypeList": ["custom"], + "valueType": "string", + "label": "", + "value": "POST", + "required": true, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpTimeout", + "renderTypeList": ["custom"], + "valueType": "number", + "label": "", + "value": 30, + "min": 5, + "max": 600, + "required": true, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpReqUrl", + "renderTypeList": ["hidden"], + "valueType": "string", + "label": "", + "description": "common:core.module.input.description.Http Request Url", + "placeholder": "https://api.ai.com/getInventory", + "required": false, + "value": "https://api.openai.com/v1/images/generations", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpHeader", + "renderTypeList": ["custom"], + "valueType": "any", + "value": [ + { + "key": "Authorization", + "type": "string", + "value": "Bearer {{authorization}}" + } + ], + "label": "", + "description": "common:core.module.input.description.Http Request Header", + "placeholder": "common:core.module.input.description.Http Request Header", + "required": false, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpParams", + "renderTypeList": ["hidden"], + "valueType": "any", + "value": [], + "label": "", + "required": false, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpJsonBody", + "renderTypeList": ["hidden"], + "valueType": "any", + "value": "{\n \"model\": \"dall-e-3\",\n \"prompt\": \"{{prompt}}\",\n \"n\": 1,\n \"size\": \"1024x1024\"\n}", + "label": "", + "required": false, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpFormBody", + "renderTypeList": ["hidden"], + "valueType": "any", + "value": [], + "label": "", + "required": false, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpContentType", + "renderTypeList": ["hidden"], + "valueType": "string", + "value": "json", + "label": "", + "required": false, + "debugLabel": "", + "toolDescription": "" + }, + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "prompt", + "label": "prompt", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "value": ["pluginInput", "绘图提示词"] + } + ], + "outputs": [ + { + "id": "error", + "key": "error", + "label": "workflow:request_error", + "description": "HTTP request error details. Returns null if successful.", + "valueType": "object", + "type": "static" + }, + { + "id": "httpRawResponse", + "key": "httpRawResponse", + "required": true, + "label": "workflow:raw_response", + "description": "Raw HTTP response. The response data must be a string or in JSON format.", + "valueType": "any", + "type": "static" + }, + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "", + "customFieldConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + } + }, + { + "id": "pJXgWoTpPoMy", + "valueType": "string", + "type": "dynamic", + "key": "data[0].url", + "label": "data[0].url" + } + ] + }, + { + "nodeId": "c7tRU2qAQoAf", + "name": "System", + "intro": "", + "avatar": "core/workflow/template/systemConfig", + "flowNodeType": "pluginConfig", + "position": { + "x": -46.476647046261974, + "y": -89.7785530936485 + }, + "version": "4811", + "inputs": [], + "outputs": [] + } + ], + "edges": [ + { + "source": "pluginInput", + "target": "tMvel910bnrJ", + "sourceHandle": "pluginInput-source-right", + "targetHandle": "tMvel910bnrJ-target-left" + }, + { + "source": "tMvel910bnrJ", + "target": "pluginOutput", + "sourceHandle": "tMvel910bnrJ-source-right", + "targetHandle": "pluginOutput-target-left" + } + ] + } + } + \ No newline at end of file diff --git a/packages/templates/src/plugin-feishu/enTemplate.json b/packages/templates/src/plugin-feishu/enTemplate.json new file mode 100644 index 000000000000..20858cc24efa --- /dev/null +++ b/packages/templates/src/plugin-feishu/enTemplate.json @@ -0,0 +1,357 @@ +{ + "name": "Feishu webhook plugin", + "intro": "Sends a message to Feishu bot through webhook.", + "author": "", + "avatar": "core/app/templates/plugin-feishu", + "type": "plugin", + "tags": ["recommendation", "office-services"], + "workflow": { + "nodes": [ + { + "nodeId": "pluginInput", + "name": "Plugin input", + "intro": "Customizes external input. When a plugin is used, only the custom input is exposed.", + "avatar": "core/workflow/template/workflowStart", + "flowNodeType": "pluginInput", + "showStatus": false, + "position": { + "x": 535.7465806305546, + "y": -201.26482361861054 + }, + "version": "481", + "inputs": [ + { + "inputType": "input", + "valueType": "string", + "key": "Feishu bot address", + "label": "Feishu bot address", + "description": "", + "isToolInput": false, + "defaultValue": "", + "editField": { + "key": true + }, + "dynamicParamDefaultValue": { + "inputType": "reference", + "valueType": "string", + "required": true + }, + "renderTypeList": ["input"], + "required": true, + "canEdit": true, + "value": "" + }, + { + "key": "Sent message", + "valueType": "string", + "label": "Sent message", + "renderTypeList": ["reference"], + "required": true, + "description": "", + "canEdit": true, + "value": "", + "editField": { + "key": true + }, + "dynamicParamDefaultValue": { + "inputType": "reference", + "valueType": "string", + "required": true + } + } + ], + "outputs": [ + { + "id": "mv52BrPVE6bm", + "key": "Feishu bot address", + "valueType": "string", + "label": "Feishu bot address", + "type": "static" + }, + { + "id": "p0m68Dv5KaIp", + "key": "Sent message", + "valueType": "string", + "label": "Sent message", + "type": "static" + } + ] + }, + { + "nodeId": "pluginOutput", + "name": "Plugin output", + "intro": "Customize external output. When a plugin is used, only the custom output is exposed.", + "intro_http_request": "Send an HTTP request to perform a more complex action (such as online search or database query).", + "avatar": "core/workflow/template/pluginOutput", + "flowNodeType": "pluginOutput", + "showStatus": false, + "position": { + "x": 1776.027569211593, + "y": -58.264823618610535 + }, + "version": "481", + "inputs": [], + "outputs": [] + }, + { + "nodeId": "rKBYGQuYefae", + "name": "HTTP request", + "intro": "Send an HTTP request to perform a more complex action (such as online search or database query).", + "avatar": "core/workflow/template/httpRequest", + "flowNodeType": "httpRequest468", + "showStatus": true, + "position": { + "x": 1069.7228495148624, + "y": -392.26482361861054 + }, + "version": "481", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "common:core.module.input.description.HTTP Dynamic Input", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpMethod", + "renderTypeList": ["custom"], + "valueType": "string", + "label": "", + "value": "POST", + "required": true, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpTimeout", + "renderTypeList": ["custom"], + "valueType": "number", + "label": "", + "value": 30, + "min": 5, + "max": 600, + "required": true, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpReqUrl", + "renderTypeList": ["hidden"], + "valueType": "string", + "label": "", + "description": "common:core.module.input.description.Http Request Url", + "placeholder": "https://api.ai.com/getInventory", + "required": false, + "value": "{{url}}", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpHeader", + "renderTypeList": ["custom"], + "valueType": "any", + "value": [], + "label": "", + "description": "common:core.module.input.description.Http Request Header", + "placeholder": "common:core.module.input.description.Http Request Header", + "required": false, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpParams", + "renderTypeList": ["hidden"], + "valueType": "any", + "value": [], + "label": "", + "required": false, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpJsonBody", + "renderTypeList": ["hidden"], + "valueType": "any", + "value": "{\r\n \"msg_type\": \"text\",\r\n \"content\": {\r\n \"text\": \"{{text}}\"\r\n }\r\n}", + "label": "", + "required": false, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpFormBody", + "renderTypeList": ["hidden"], + "valueType": "any", + "value": [], + "label": "", + "required": false, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpContentType", + "renderTypeList": ["hidden"], + "valueType": "string", + "value": "json", + "label": "", + "required": false, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "text", + "valueType": "string", + "label": "text", + "renderTypeList": ["reference"], + "description": "", + "canEdit": true, + "editField": { + "key": true, + "valueType": true + }, + "value": ["pluginInput", "p0m68Dv5KaIp"], + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + } + }, + { + "key": "url", + "valueType": "string", + "label": "url", + "renderTypeList": ["reference"], + "description": "", + "canEdit": true, + "editField": { + "key": true, + "valueType": true + }, + "value": ["pluginInput", "mv52BrPVE6bm"], + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + } + } + ], + "outputs": [ + { + "id": "error", + "key": "error", + "label": "workflow:request_error", + "description": "HTTP request error details. Returns null if successful.", + "valueType": "object", + "type": "static" + }, + { + "id": "httpRawResponse", + "key": "httpRawResponse", + "required": true, + "label": "workflow:raw_response", + "description": "Raw HTTP response. The response data must be a string or in JSON format.", + "valueType": "any", + "type": "static" + }, + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "", + "editField": { + "key": true, + "valueType": true + } + } + ] + }, + { + "nodeId": "q3ccNXiZIHoS", + "name": "System", + "intro": "", + "avatar": "core/workflow/template/systemConfig", + "flowNodeType": "pluginConfig", + "position": { + "x": 99.73879703925843, + "y": -201.26482361861054 + }, + "version": "4811", + "inputs": [], + "outputs": [] + } + ], + "edges": [ + { + "source": "pluginInput", + "target": "rKBYGQuYefae", + "sourceHandle": "pluginInput-source-right", + "targetHandle": "rKBYGQuYefae-target-left" + }, + { + "source": "rKBYGQuYefae", + "target": "pluginOutput", + "sourceHandle": "rKBYGQuYefae-source-right", + "targetHandle": "pluginOutput-target-left" + } + ] + } + } + \ No newline at end of file diff --git a/packages/templates/src/simpleDatasetChat/enTemplate.json b/packages/templates/src/simpleDatasetChat/enTemplate.json new file mode 100644 index 000000000000..d6b54a567837 --- /dev/null +++ b/packages/templates/src/simpleDatasetChat/enTemplate.json @@ -0,0 +1,289 @@ +{ + "name": "Knowledge base + conversation opener", + "intro": "Performs knowledge base search for each question and injects search results into the LLM model as reference for the response.", + "author": "", + "avatar": "core/workflow/template/datasetSearch", + "type": "simple", + "tags": ["office-services"], + "workflow": { + "nodes": [ + { + "nodeId": "userGuide", + "name": "System", + "intro": "Configure system parameters for the app.", + "avatar": "core/workflow/template/systemConfig", + "flowNodeType": "userGuide", + "position": { + "x": 531.2422736065552, + "y": -486.7611729549753 + }, + "version": "481", + "inputs": [], + "outputs": [] + }, + { + "nodeId": "workflowStartNodeId", + "name": "Start", + "intro": "", + "avatar": "core/workflow/template/workflowStart", + "flowNodeType": "workflowStart", + "position": { + "x": 558.4082376415505, + "y": 123.72387429194112 + }, + "version": "481", + "inputs": [ + { + "key": "userChatInput", + "renderTypeList": ["FlowNodeInputTypeEnum.reference", "FlowNodeInputTypeEnum.textarea"], + "valueType": "string", + "label": "Question", + "required": true, + "toolDescription": "Question" + } + ], + "outputs": [ + { + "id": "userChatInput", + "key": "userChatInput", + "label": "core.module.input.label.user question", + "valueType": "string", + "type": "FlowNodeOutputTypeEnum.static" + } + ] + }, + { + "nodeId": "7BdojPlukIQw", + "name": "AI chat", + "intro": "Chat with an model.", + "avatar": "core/workflow/template/aiChat", + "flowNodeType": "chatNode", + "showStatus": true, + "position": { + "x": 1638.509551404687, + "y": -341.0428450861567 + }, + "version": "481", + "inputs": [ + { + "key": "model", + "renderTypeList": [ + "FlowNodeInputTypeEnum.settingLLMModel", + "FlowNodeInputTypeEnum.reference" + ], + "label": "core.module.input.label.aiModel", + "valueType": "string", + "value": "gpt-5" + }, + { + "key": "temperature", + "renderTypeList": ["FlowNodeInputTypeEnum.hidden"], + "label": "", + "value": 3, + "valueType": "number", + "min": 0, + "max": 10, + "step": 1 + }, + { + "key": "maxToken", + "renderTypeList": ["FlowNodeInputTypeEnum.hidden"], + "label": "", + "value": 1950, + "valueType": "number", + "min": 100, + "max": 4000, + "step": 50 + }, + { + "key": "isResponseAnswerText", + "renderTypeList": ["FlowNodeInputTypeEnum.hidden"], + "label": "", + "value": true, + "valueType": "boolean" + }, + { + "key": "quoteTemplate", + "renderTypeList": ["FlowNodeInputTypeEnum.hidden"], + "label": "", + "valueType": "string" + }, + { + "key": "quotePrompt", + "renderTypeList": ["FlowNodeInputTypeEnum.hidden"], + "label": "", + "valueType": "string" + }, + { + "key": "systemPrompt", + "renderTypeList": ["FlowNodeInputTypeEnum.textarea", "FlowNodeInputTypeEnum.reference"], + "max": 3000, + "valueType": "string", + "label": "core.ai.Prompt", + "description": "core.app.tip.systemPromptTip", + "placeholder": "core.app.tip.chatNodeSystemPromptTip", + "value": "" + }, + { + "key": "history", + "renderTypeList": [ + "FlowNodeInputTypeEnum.numberInput", + "FlowNodeInputTypeEnum.reference" + ], + "valueType": "chatHistory", + "label": "core.module.input.label.chat history", + "required": true, + "min": 0, + "max": 30, + "value": 6 + }, + { + "key": "userChatInput", + "renderTypeList": ["FlowNodeInputTypeEnum.reference", "FlowNodeInputTypeEnum.textarea"], + "valueType": "string", + "label": "Question", + "required": true, + "toolDescription": "Question", + "value": ["workflowStartNodeId", "userChatInput"] + }, + { + "key": "quoteQA", + "renderTypeList": ["FlowNodeInputTypeEnum.settingDatasetQuotePrompt"], + "label": "", + "debugLabel": "Knowledge base reference", + "description": "", + "valueType": "datasetQuote", + "value": ["iKBoX2vIzETU", "quoteQA"] + } + ], + "outputs": [ + { + "id": "history", + "key": "history", + "label": "core.module.output.label.New context", + "description": "core.module.output.description.New context", + "valueType": "chatHistory", + "type": "FlowNodeOutputTypeEnum.static" + }, + { + "id": "answerText", + "key": "answerText", + "label": "core.module.output.label.Ai response content", + "description": "core.module.output.description.Ai response content", + "valueType": "string", + "type": "FlowNodeOutputTypeEnum.static" + } + ] + }, + { + "nodeId": "iKBoX2vIzETU", + "name": "Knowledge base search", + "intro": "Uses semantic, full-text, and database search capabilities to search for reference materials related to the questions from the selected knowledge bases.", + "avatar": "core/workflow/template/datasetSearch", + "flowNodeType": "datasetSearchNode", + "showStatus": true, + "position": { + "x": 918.5901682164496, + "y": -227.11542247619582 + }, + "version": "481", + "inputs": [ + { + "key": "datasets", + "renderTypeList": [ + "FlowNodeInputTypeEnum.selectDataset", + "FlowNodeInputTypeEnum.reference" + ], + "label": "core.module.input.label.Select dataset", + "value": [], + "valueType": "selectDataset", + "list": [], + "required": true + }, + { + "key": "similarity", + "renderTypeList": ["FlowNodeInputTypeEnum.selectDatasetParamsModal"], + "label": "", + "value": 0.4, + "valueType": "number" + }, + { + "key": "limit", + "renderTypeList": ["hidden"], + "label": "", + "value": 5000, + "valueType": "number" + }, + { + "key": "searchMode", + "renderTypeList": ["FlowNodeInputTypeEnum.hidden"], + "label": "", + "valueType": "string", + "value": "embedding" + }, + { + "key": "usingReRank", + "renderTypeList": ["FlowNodeInputTypeEnum.hidden"], + "label": "", + "valueType": "boolean", + "value": false + }, + { + "key": "datasetSearchUsingExtensionQuery", + "renderTypeList": ["FlowNodeInputTypeEnum.hidden"], + "label": "", + "valueType": "boolean", + "value": true + }, + { + "key": "datasetSearchExtensionModel", + "renderTypeList": ["FlowNodeInputTypeEnum.hidden"], + "label": "", + "valueType": "string" + }, + { + "key": "datasetSearchExtensionBg", + "renderTypeList": ["FlowNodeInputTypeEnum.hidden"], + "label": "", + "valueType": "string", + "value": "" + }, + { + "key": "userChatInput", + "renderTypeList": ["FlowNodeInputTypeEnum.reference", "FlowNodeInputTypeEnum.textarea"], + "valueType": "string", + "label": "Question", + "required": true, + "toolDescription": "Content to be searched", + "value": ["workflowStartNodeId", "userChatInput"] + } + ], + "outputs": [ + { + "id": "quoteQA", + "key": "quoteQA", + "label": "core.module.Dataset quote.label", + "type": "FlowNodeOutputTypeEnum.static", + "valueType": "datasetQuote", + "description": "Special array format. An empty array is returned when no results are found." + } + ] + } + ], + "edges": [ + { + "source": "workflowStartNodeId", + "target": "iKBoX2vIzETU", + "sourceHandle": "workflowStartNodeId-source-right", + "targetHandle": "iKBoX2vIzETU-target-left" + }, + { + "source": "iKBoX2vIzETU", + "target": "7BdojPlukIQw", + "sourceHandle": "iKBoX2vIzETU-source-right", + "targetHandle": "7BdojPlukIQw-target-left" + } + ] + } + } + \ No newline at end of file diff --git a/packages/templates/src/srt-translate/enTemplate.json b/packages/templates/src/srt-translate/enTemplate.json new file mode 100644 index 000000000000..d2ffc593de6a --- /dev/null +++ b/packages/templates/src/srt-translate/enTemplate.json @@ -0,0 +1,1935 @@ +{ + "name": "Reflection-based long subtitle translation bot", + "intro": "Uses the AI self-reflection feature to enhance translation quality and performs iterative AI workflows to address LLM token limits, creating an efficient long subtitle translation bot.", + "author": "", + "avatar": "core/app/templates/TranslateRobot", + "tags": ["office-services"], + "type": "advanced", + "userGuide": { + "type": "link", + "content": "https://mp.weixin.qq.com/s/uztciEVvumNWNlct0IeJ-w" + }, + "workflow": { + "nodes": [ + { + "nodeId": "userGuide", + "name": "System", + "intro": "Configure system parameters for the app.", + "avatar": "core/workflow/template/systemConfig", + "flowNodeType": "userGuide", + "position": { + "x": -1453.0815298642474, + "y": 269.10239463914263 + }, + "version": "481", + "inputs": [ + { + "key": "welcomeText", + "renderTypeList": ["hidden"], + "valueType": "string", + "label": "core.app.Welcome Text", + "value": "" + }, + { + "key": "variables", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "core.app.Chat Variable", + "value": [] + }, + { + "key": "questionGuide", + "valueType": "any", + "renderTypeList": ["hidden"], + "label": "core.app.Question Guide", + "value": { + "open": false + } + }, + { + "key": "tts", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": { + "type": "web" + } + }, + { + "key": "whisper", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": { + "open": false, + "autoSend": false, + "autoTTSResponse": false + } + }, + { + "key": "scheduleTrigger", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": null + } + ], + "outputs": [] + }, + { + "nodeId": "448745", + "name": "Start", + "intro": "", + "avatar": "core/workflow/template/workflowStart", + "flowNodeType": "workflowStart", + "position": { + "x": -1491.3235962441404, + "y": 1277.8094190109027 + }, + "version": "481", + "inputs": [ + { + "key": "userChatInput", + "renderTypeList": ["reference", "textarea"], + "valueType": "string", + "label": "Question", + "required": true, + "toolDescription": "Question" + } + ], + "outputs": [ + { + "id": "userChatInput", + "key": "userChatInput", + "label": "common:core.module.input.label.user question", + "type": "static", + "valueType": "string" + } + ] + }, + { + "nodeId": "yjFO3YcM7KG2", + "name": "LLM translation", + "intro": "Chat with an model.", + "avatar": "core/workflow/template/aiChat", + "flowNodeType": "chatNode", + "showStatus": true, + "position": { + "x": 2584.1800627074813, + "y": 962.3727118784907 + }, + "version": "481", + "inputs": [ + { + "key": "model", + "renderTypeList": ["settingLLMModel", "reference"], + "label": "core.module.input.label.aiModel", + "valueType": "string", + "selectedTypeIndex": 0, + "value": "gpt-4o" + }, + { + "key": "temperature", + "renderTypeList": ["hidden"], + "label": "", + "value": 3, + "valueType": "number", + "min": 0, + "max": 10, + "step": 1 + }, + { + "key": "maxToken", + "renderTypeList": ["hidden"], + "label": "", + "value": 2000, + "valueType": "number", + "min": 100, + "max": 4000, + "step": 50 + }, + { + "key": "isResponseAnswerText", + "renderTypeList": ["hidden"], + "label": "", + "value": false, + "valueType": "boolean" + }, + { + "key": "quoteTemplate", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string" + }, + { + "key": "quotePrompt", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string" + }, + { + "key": "aiChatVision", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "boolean", + "value": true + }, + { + "key": "systemPrompt", + "renderTypeList": ["textarea", "reference"], + "max": 3000, + "valueType": "string", + "label": "core.ai.Prompt", + "description": "core.app.tip.systemPromptTip", + "placeholder": "core.app.tip.chatNodeSystemPromptTip", + "value": "# Role: 资深字幕翻译专家\n\n## Background:\n你是一位经验丰富的{{source_lang}}和{{target_lang}}字幕翻译专家,精通{{source_lang}}和{{target_lang}}互译,尤其擅长将{{source_lang}}字幕译成流畅易懂的{{target_lang}}字幕。你曾多次带领团队完成大型商业电影的字幕翻译项目,所翻译的字幕广受好评。\n\n## Attention:\n- 翻译过程中要始终坚持\"信、达、雅\"的原则,但\"达\"尤为重要\n- 翻译的字幕要符合{{target_lang}}的表达习惯,通俗易懂,连贯流畅\n- 避免使用过于文绉绉的表达和晦涩难懂的典故引用 \n- 诗词歌词等内容需按原文换行和节奏分行,不破坏原排列格式 \n- 翻译对象是字幕,请进入整段文本的语境中对需要翻译的文本段进行翻译\n- 是标识每一帧字幕的标签,请严格按照对文本的分割逐帧翻译\n\n## Constraints:\n- 必须严格遵循四轮翻译流程:直译、意译、反思、提升\n- 译文要忠实原文,准确无误,不能遗漏或曲解原意\n- 最终译文使用Markdown的代码块呈现,但是不用输出markdown这个单词\n- 是标识每一帧字幕的标签,请严格按照对文本的分割逐帧翻译,每一帧字幕末尾不要加\\n 回车标识,且第一帧字幕开头不需要加标识\n\n## Goals:\n- 通过四轮翻译流程,将{{source_lang}}字幕译成高质量的{{target_lang}}字幕\n- 翻译的字幕要准确传达原字幕意思,语言表达力求浅显易懂,朗朗上口 \n\n## Workflow:\n1. 第一轮直译:严格按照逐句翻译,不遗漏任何信息\n2. 第二轮意译:在直译的基础上用通俗流畅的{{target_lang}}意译原文,逐句翻译,保留标识标签\n3. 第三轮反思:仔细审视译文,分点列出一份建设性的批评和有用的建议清单以改进翻译,对每一句话提出建议,从以下四个角度展开\n (i) 准确性(纠正添加、误译、遗漏或未翻译的文本错误),\n (ii) 流畅性(应用{{target_lang}}的语法、拼写和标点规则,并确保没有不必要的重复),\n (iii) 风格(确保翻译反映源文本的风格并考虑其文化背景),\n (iv) 术语(确保术语使用一致且反映源文本所在领域,注意确保使用{{target_lang}}中的等效习语)\n4. 第四轮提升:严格遵循第三轮提出的建议对翻译修改,定稿出一个简洁畅达、符合大众观影习惯的字幕译文,保留标识标签\n\n## OutputFormat:\n- 每一轮前用【思考】说明该轮要点\n- 第一轮和第二轮翻译后用【翻译】呈现译文\n- 第三轮输出建议清单,分点列出,在每一点前用*xxx*标识这条建议对应的要点,如*风格*;建议前用【思考】说明该轮要点,建议后用【建议】呈现建议\n- 第四轮在\\`\\`\\`代码块中展示最终{{target_lang}}字幕文件内容,如\\`\\`\\`xxx\\`\\`\\`\n\n## Suggestions:\n- 直译时力求忠实原文,但注意控制每帧字幕的字数,必要时进行精简压缩\n- 意译时在准确表达原意的基础上,用最朴实无华的{{target_lang}}来表达\n- 反思环节重点关注译文是否符合{{target_lang}}表达习惯,是否通俗易懂,是否准确流畅,是否术语一致\n- 提升环节采用反思环节的建议对意译环节的翻译进行修改,适度采用一些口语化的表达、网络流行语等,增强字幕的亲和力\n- 注意是很重要的标识标签,请确保标签能在正确位置输出" + }, + { + "key": "history", + "renderTypeList": ["numberInput", "reference"], + "valueType": "chatHistory", + "label": "core.module.input.label.chat history", + "description": "Maximum number of chats remembered", + "required": true, + "min": 0, + "max": 50, + "value": 6 + }, + { + "key": "quoteQA", + "renderTypeList": ["settingDatasetQuotePrompt"], + "label": "", + "debugLabel": "Knowledge base reference", + "description": "", + "valueType": "datasetQuote" + }, + { + "key": "stringQuoteText", + "renderTypeList": ["reference", "textarea"], + "label": "app:document_quote", + "debugLabel": "app:document_quote", + "description": "app:document_quote_tip", + "valueType": "string" + }, + { + "key": "userChatInput", + "renderTypeList": ["reference", "textarea"], + "valueType": "string", + "label": "Question", + "required": true, + "toolDescription": "Question", + "value": ["bxz97Vg4Omux", "system_text"] + } + ], + "outputs": [ + { + "id": "history", + "key": "history", + "required": true, + "label": "core.module.output.label.New context", + "description": "core.module.output.description.New context", + "valueType": "chatHistory", + "type": "static" + }, + { + "id": "answerText", + "key": "answerText", + "required": true, + "label": "core.module.output.label.Ai response content", + "description": "core.module.output.description.Ai response content", + "valueType": "string", + "type": "static" + } + ] + }, + { + "nodeId": "bxz97Vg4Omux", + "name": "LLM translation prompt", + "intro": "Processes fixed or input text to provide an output. Non-string data will be automatically converted into strings.", + "avatar": "core/workflow/template/textConcat", + "flowNodeType": "textEditor", + "position": { + "x": 1881.9891746911078, + "y": 1112.4113692581143 + }, + "version": "486", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "You can reference the output from upstream nodes as variables for text splicing using {{field name}}.", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + } + }, + { + "key": "system_textareaInput", + "renderTypeList": ["textarea"], + "valueType": "string", + "required": true, + "label": "Text for splicing", + "placeholder": "Variables can be referenced using {{field name}}.", + "value": "你的任务是将文本从{{source_lang}}翻译成{{target_lang}}\n\n源文本如下,由XML标签分隔:\n\n\n\n{{tagged_text}}\n\n\n\n仅翻译源文本中由分隔的部分,将其余的源文本作为上下文\n\n重申一下,你应该只翻译文本的这一部分,这里再次显示在之间:\n\n\n\n{{chunk_to_translate}}\n\n" + }, + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "tagged_text", + "label": "tagged_text", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + }, + "required": true, + "value": ["quYZgsW32ApA", "xhXu6sdEWBnF"] + }, + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "chunk_to_translate", + "label": "chunk_to_translate", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + }, + "required": true, + "value": ["quYZgsW32ApA", "eCp73lztAEGK"] + } + ], + "outputs": [ + { + "id": "system_text", + "key": "system_text", + "label": "Splicing result", + "type": "static", + "valueType": "string" + } + ] + }, + { + "nodeId": "w4heEpNflz59", + "name": "Check for execution completion", + "intro": "Executes a branch based on the specified conditions.", + "avatar": "core/workflow/template/ifelse", + "flowNodeType": "ifElseNode", + "showStatus": true, + "position": { + "x": 5610.256715310997, + "y": 1232.1024649538042 + }, + "version": "481", + "inputs": [ + { + "key": "ifElseList", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": [ + { + "condition": "AND", + "list": [ + { + "variable": ["a2lqxASWi1vb", "nmBmGaARbKkl"], + "condition": "equalTo", + "value": "true" + } + ] + } + ] + } + ], + "outputs": [ + { + "id": "ifElseResult", + "key": "ifElseResult", + "label": "Check result", + "valueType": "string", + "type": "static" + } + ] + }, + { + "nodeId": "a2lqxASWi1vb", + "name": "Check for execution completion", + "intro": "Runs a short script, typically for complex data processing.", + "avatar": "core/workflow/template/codeRun", + "flowNodeType": "code", + "showStatus": true, + "position": { + "x": 5068.150809685475, + "y": 940.8168438455639 + }, + "version": "482", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "These variables will be used as input parameters for code execution.", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + } + }, + { + "key": "codeType", + "renderTypeList": ["hidden"], + "label": "", + "value": "js" + }, + { + "key": "code", + "renderTypeList": ["custom"], + "label": "", + "value": "function main({chunks, currentChunk}){\n const findIndex = chunks.findIndex((item) => item === currentChunk)\n\n return {\n isEnd: chunks.length-1 === findIndex,\n i: findIndex + 1,\n }\n}" + }, + { + "renderTypeList": ["reference"], + "valueType": "arrayString", + "canEdit": true, + "key": "chunks", + "label": "chunks", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "value": ["y3WEYOQ09CGC", "qLUQfhG0ILRX"] + }, + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "currentChunk", + "label": "currentChunk", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "value": ["quYZgsW32ApA", "eCp73lztAEGK"] + } + ], + "outputs": [ + { + "id": "system_rawResponse", + "key": "system_rawResponse", + "label": "Complete response", + "valueType": "object", + "type": "static" + }, + { + "id": "error", + "key": "error", + "label": "Running error", + "description": "Error message returned when code execution error occurs. If the code is executed successfully, a null value is returned.", + "valueType": "object", + "type": "static" + }, + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "", + "customFieldConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + }, + "description": "Use the return value as the output and pass it to the next node. The variable name must match the key in the return value." + }, + { + "id": "nmBmGaARbKkl", + "valueType": "boolean", + "type": "dynamic", + "key": "isEnd", + "label": "isEnd" + }, + { + "id": "nqB98uKpq6Ig", + "valueType": "number", + "type": "dynamic", + "key": "i", + "label": "i" + } + ] + }, + { + "nodeId": "quYZgsW32ApA", + "name": "Source text block formatting", + "intro": "Runs a short script, typically for complex data processing.", + "avatar": "core/workflow/template/codeRun", + "flowNodeType": "code", + "showStatus": true, + "position": { + "x": 1306.0466226590386, + "y": 1038.0950796039954 + }, + "version": "482", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "These variables will be used as input parameters for code execution.", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + } + }, + { + "key": "codeType", + "renderTypeList": ["hidden"], + "label": "", + "value": "js" + }, + { + "key": "code", + "renderTypeList": ["custom"], + "label": "", + "value": "function main({source_text_chunks, i=0}){\n let before = source_text_chunks.slice(0, i).join(\"\");\n let current = \" \" + source_text_chunks[i] + \"\";\n let after = source_text_chunks.slice(i + 1).join(\"\");\n let tagged_text = before + current + after;\n\n return {\n tagged_text,\n chunk_to_translate: source_text_chunks[i],\n }\n}" + }, + { + "renderTypeList": ["reference"], + "valueType": "number", + "canEdit": true, + "key": "i", + "label": "i", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "value": ["a2lqxASWi1vb", "nqB98uKpq6Ig"] + }, + { + "renderTypeList": ["reference"], + "valueType": "arrayString", + "canEdit": true, + "key": "source_text_chunks", + "label": "source_text_chunks", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "value": ["y3WEYOQ09CGC", "qLUQfhG0ILRX"] + } + ], + "outputs": [ + { + "id": "system_rawResponse", + "key": "system_rawResponse", + "label": "Complete response", + "valueType": "object", + "type": "static" + }, + { + "id": "error", + "key": "error", + "label": "Running error", + "description": "Error message returned when code execution error occurs. If the code is executed successfully, a null value is returned.", + "valueType": "object", + "type": "static" + }, + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "", + "customFieldConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + }, + "description": "Use the return value as the output and pass it to the next node. The variable name must match the key in the return value." + }, + { + "id": "xhXu6sdEWBnF", + "valueType": "string", + "type": "dynamic", + "key": "tagged_text", + "label": "tagged_text" + }, + { + "id": "eCp73lztAEGK", + "valueType": "string", + "type": "dynamic", + "key": "chunk_to_translate", + "label": "chunk_to_translate" + } + ] + }, + { + "nodeId": "izsNX8FXGD1t", + "name": "Specified answer", + "intro": "This module supports replies with a specified message, often used as guidance or prompts. Non-string content is automatically converted to a string.", + "avatar": "core/workflow/template/reply", + "flowNodeType": "answerNode", + "position": { + "x": 6412.138830862469, + "y": 1235.7269544046057 + }, + "version": "481", + "inputs": [ + { + "key": "text", + "renderTypeList": ["textarea", "reference"], + "valueType": "any", + "required": true, + "label": "core.module.input.label.Response content", + "description": "core.module.input.description.Response content", + "placeholder": "core.module.input.description.Response content", + "value": "\n\n*** 字幕反思翻译完成!***" + } + ], + "outputs": [] + }, + { + "nodeId": "vlNHndpNuFXB", + "name": "Extracts the text from the fourth-round LLM translation", + "intro": "Runs a short script, typically for complex data processing.", + "avatar": "core/workflow/template/codeRun", + "flowNodeType": "code", + "showStatus": true, + "position": { + "x": 3292.3569279448307, + "y": 996.4264559884845 + }, + "version": "482", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "These variables will be used as input parameters for code execution.", + "editField": { + "key": true, + "valueType": true + }, + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + } + }, + { + "key": "codeType", + "renderTypeList": ["hidden"], + "label": "", + "value": "js" + }, + { + "key": "code", + "renderTypeList": ["custom"], + "label": "", + "value": "function main({data1}){\n const result = data1.split(\"```\").filter(item => !!item.trim())\n\n if(result[result.length-1]) {\n return {\n result: result[result.length-1].trim() \n }\n }\n\n return {\n result: '未截取到翻译内容'\n }\n}" + }, + { + "key": "data1", + "valueType": "string", + "label": "data1", + "renderTypeList": ["reference"], + "description": "", + "canEdit": true, + "editField": { + "key": true, + "valueType": true + }, + "value": ["yjFO3YcM7KG2", "answerText"], + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + } + } + ], + "outputs": [ + { + "id": "system_rawResponse", + "key": "system_rawResponse", + "label": "Complete response", + "valueType": "object", + "type": "static" + }, + { + "id": "error", + "key": "error", + "label": "Running error", + "description": "Error message returned when code execution error occurs. If the code is executed successfully, a null value is returned.", + "valueType": "object", + "type": "static" + }, + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "", + "customFieldConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + }, + "description": "Use the return value as the output and pass it to the next node. The variable name must match the key in the return value." + }, + { + "id": "qLUQfhG0ILRX", + "type": "dynamic", + "key": "result", + "valueType": "string", + "label": "result" + }, + { + "id": "gR0mkQpJ4Og8", + "type": "dynamic", + "key": "data2", + "valueType": "string", + "label": "data2" + } + ] + }, + { + "nodeId": "qlt9KJbbS9yJ", + "name": "Check for same source and target languages", + "intro": "Executes a branch based on the specified conditions.", + "avatar": "core/workflow/template/ifelse", + "flowNodeType": "ifElseNode", + "showStatus": true, + "position": { + "x": -646.0682391158168, + "y": 1352.6591494737195 + }, + "version": "481", + "inputs": [ + { + "key": "ifElseList", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": [ + { + "condition": "AND", + "list": [ + { + "variable": ["frjbsrlnJJsR", "qLUQfhG0ILRX"], + "condition": "equalTo", + "value": "false" + } + ] + } + ] + } + ], + "outputs": [ + { + "id": "ifElseResult", + "key": "ifElseResult", + "label": "Check result", + "valueType": "string", + "type": "static" + } + ] + }, + { + "nodeId": "frjbsrlnJJsR", + "name": "Check for same source and target languages", + "intro": "Runs a short script, typically for complex data processing.", + "avatar": "core/workflow/template/codeRun", + "flowNodeType": "code", + "showStatus": true, + "position": { + "x": -1152.6572772702038, + "y": 1092.3001349756628 + }, + "version": "482", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "These variables will be used as input parameters for code execution.", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + } + }, + { + "key": "codeType", + "renderTypeList": ["hidden"], + "label": "", + "value": "js" + }, + { + "key": "code", + "renderTypeList": ["custom"], + "label": "", + "value": "function main({source_lang, target_lang}){\n \n return {\n result: source_lang === target_lang\n }\n}" + }, + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "source_lang", + "label": "source_lang", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "value": ["VARIABLE_NODE_ID", "source_lang"] + }, + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "target_lang", + "label": "target_lang", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "value": ["VARIABLE_NODE_ID", "target_lang"] + } + ], + "outputs": [ + { + "id": "system_rawResponse", + "key": "system_rawResponse", + "label": "Complete response", + "valueType": "object", + "type": "static" + }, + { + "id": "error", + "key": "error", + "label": "Running error", + "description": "Error message returned when code execution error occurs. If the code is executed successfully, a null value is returned.", + "valueType": "object", + "type": "static" + }, + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "", + "customFieldConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + }, + "description": "Use the return value as the output and pass it to the next node. The variable name must match the key in the return value." + }, + { + "id": "qLUQfhG0ILRX", + "type": "dynamic", + "key": "result", + "valueType": "boolean", + "label": "result" + } + ] + }, + { + "nodeId": "dFxrGZS3Wmnz", + "name": "Prompt for same source and target languages", + "intro": "This module supports replies with a specified message, often used as guidance or prompts. Non-string content is automatically converted to a string.", + "avatar": "core/workflow/template/reply", + "flowNodeType": "answerNode", + "position": { + "x": -523.8880105943567, + "y": 1735.994691812213 + }, + "version": "481", + "inputs": [ + { + "key": "text", + "renderTypeList": ["textarea", "reference"], + "valueType": "any", + "required": true, + "label": "core.module.input.label.Response content", + "description": "core.module.input.description.Response content", + "placeholder": "core.module.input.description.Response content", + "selectedTypeIndex": 0, + "value": "{{source_lang}} 无需再次翻译为 {{target_lang}} ~" + } + ], + "outputs": [] + }, + { + "nodeId": "tqzmK5oPR9BA", + "name": "输出翻译", + "intro": "This module supports replies with a specified message, often used as guidance or prompts. Non-string content is automatically converted to a string.", + "avatar": "core/workflow/template/reply", + "flowNodeType": "answerNode", + "position": { + "x": 4368.135274121754, + "y": 1308.859868496928 + }, + "version": "481", + "inputs": [ + { + "key": "text", + "renderTypeList": ["textarea", "reference"], + "valueType": "any", + "required": true, + "label": "core.module.input.label.Response content", + "description": "core.module.input.description.Response content", + "placeholder": "core.module.input.description.Response content", + "selectedTypeIndex": 1, + "value": ["ppPP6o7YYSTJ", "dYalXmYJ60bj"] + } + ], + "outputs": [] + }, + { + "nodeId": "kbr342XlxSZR", + "name": "提取字幕信息", + "intro": "Runs a short script, typically for complex data processing.", + "avatar": "core/workflow/template/codeRun", + "flowNodeType": "code", + "showStatus": true, + "position": { + "x": 207.77750594024815, + "y": 1057.1062190161417 + }, + "version": "482", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "These variables will be used as input parameters for code execution.", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + } + }, + { + "key": "codeType", + "renderTypeList": ["hidden"], + "label": "", + "value": "js" + }, + { + "key": "code", + "renderTypeList": ["custom"], + "label": "", + "value": "function main({text}){\n const lines = text.split('\\n');\n const timePattern = /\\d{2}:\\d{2}:\\d{2},\\d{3} --> \\d{2}:\\d{2}:\\d{2},\\d{3}/;\n const numberInfo = [];\n const timeInfo = [];\n const textInfo = [];\n let currentText = [];\n\n // 提取序号、时间戳和文本信息\n lines.forEach(line => {\n if (/^\\d+$/.test(line.trim())) {\n numberInfo.push(line.trim());\n } else if (timePattern.test(line)) {\n timeInfo.push(line);\n if (currentText.length > 0) {\n textInfo.push(currentText.join(' '));\n currentText = [];\n }\n } else if (line.trim() === '') {\n // Skip empty lines\n } else {\n currentText.push(line.trim());\n }\n });\n\n if (currentText.length > 0) {\n textInfo.push(currentText.join(' '));\n }\n\n return { numberInfo, timeInfo, textInfo };\n}" + }, + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "text", + "label": "text", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "value": ["448745", "userChatInput"] + } + ], + "outputs": [ + { + "id": "system_rawResponse", + "key": "system_rawResponse", + "label": "Complete response", + "valueType": "object", + "type": "static" + }, + { + "id": "error", + "key": "error", + "label": "Running error", + "description": "Error message returned when code execution error occurs. If the code is executed successfully, a null value is returned.", + "valueType": "object", + "type": "static" + }, + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "", + "customFieldConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + }, + "description": "Use the return value as the output and pass it to the next node. The variable name must match the key in the return value." + }, + { + "id": "h3qVuGhV9HNm", + "valueType": "arrayString", + "type": "dynamic", + "key": "timeInfo", + "label": "timeInfo" + }, + { + "id": "zGYRMNA9xGuI", + "valueType": "arrayString", + "type": "dynamic", + "key": "textInfo", + "label": "textInfo" + }, + { + "id": "dhzTt6Riz8Dp", + "valueType": "arrayString", + "type": "dynamic", + "key": "numberInfo", + "label": "numberInfo" + } + ] + }, + { + "nodeId": "ppPP6o7YYSTJ", + "name": "格式化字幕文件", + "intro": "Runs a short script, typically for complex data processing.", + "avatar": "core/workflow/template/codeRun", + "flowNodeType": "code", + "showStatus": true, + "position": { + "x": 3830.732983153034, + "y": 828.5283022715971 + }, + "version": "482", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "These variables will be used as input parameters for code execution.", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + } + }, + { + "key": "codeType", + "renderTypeList": ["hidden"], + "label": "", + "value": "js" + }, + { + "key": "code", + "renderTypeList": ["custom"], + "label": "", + "value": "function main({combinedText, transedText, timeInfo, currentIndex=0,numberInfo}){\n const textLines = combinedText.split('');\n const resultLines = transedText.split('');\n const combinedLines = [];\n\n resultLines.forEach((line, index) => {\n combinedLines.push(numberInfo[currentIndex+index]);\n combinedLines.push(timeInfo[currentIndex+index]);\n combinedLines.push(line)\n combinedLines.push(textLines[index]);\n combinedLines.push('');\n });\n\n const srtContent = combinedLines.join('\\n');\n \n\n return {\n srtContent,\n currentIndex: currentIndex+textLines.length\n }\n}" + }, + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "combinedText", + "label": "combinedText", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "value": ["quYZgsW32ApA", "eCp73lztAEGK"] + }, + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "transedText", + "label": "transedText", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "value": ["vlNHndpNuFXB", "qLUQfhG0ILRX"] + }, + { + "renderTypeList": ["reference"], + "valueType": "arrayString", + "canEdit": true, + "key": "timeInfo", + "label": "timeInfo", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "value": ["kbr342XlxSZR", "h3qVuGhV9HNm"] + }, + { + "renderTypeList": ["reference"], + "valueType": "number", + "canEdit": true, + "key": "currentIndex", + "label": "currentIndex", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "value": ["ppPP6o7YYSTJ", "u6eqeC0pEPe0"] + }, + { + "renderTypeList": ["reference"], + "valueType": "arrayString", + "canEdit": true, + "key": "numberInfo", + "label": "numberInfo", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "value": ["kbr342XlxSZR", "dhzTt6Riz8Dp"] + } + ], + "outputs": [ + { + "id": "system_rawResponse", + "key": "system_rawResponse", + "label": "Complete response", + "valueType": "object", + "type": "static" + }, + { + "id": "error", + "key": "error", + "label": "Running error", + "description": "Error message returned when code execution error occurs. If the code is executed successfully, a null value is returned.", + "valueType": "object", + "type": "static" + }, + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "", + "customFieldConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + }, + "description": "Use the return value as the output and pass it to the next node. The variable name must match the key in the return value." + }, + { + "id": "dYalXmYJ60bj", + "valueType": "string", + "type": "dynamic", + "key": "srtContent", + "label": "srtContent" + }, + { + "id": "u6eqeC0pEPe0", + "valueType": "number", + "type": "dynamic", + "key": "currentIndex", + "label": "currentIndex" + } + ] + }, + { + "nodeId": "y3WEYOQ09CGC", + "name": "Text splitting", + "intro": "Runs a short script, typically for complex data processing.", + "avatar": "core/workflow/template/codeRun", + "flowNodeType": "code", + "showStatus": true, + "position": { + "x": 778.9569641790565, + "y": 1108.0575844436903 + }, + "version": "482", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "These variables will be used as input parameters for code execution.", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + } + }, + { + "key": "codeType", + "renderTypeList": ["hidden"], + "label": "", + "value": "js" + }, + { + "key": "code", + "renderTypeList": ["custom"], + "label": "", + "value": "function main({textArray}){\n const groupSize = 40\n const delimiter = ''\n\n const result = [];\n\n for (let i = 0; i < textArray.length; i += groupSize) {\n result.push(textArray.slice(i, i + groupSize).join(delimiter));\n }\n\n return {result};\n}" + }, + { + "renderTypeList": ["reference"], + "valueType": "arrayString", + "canEdit": true, + "key": "textArray", + "label": "textArray", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "value": ["kbr342XlxSZR", "zGYRMNA9xGuI"] + } + ], + "outputs": [ + { + "id": "system_rawResponse", + "key": "system_rawResponse", + "label": "Complete response", + "valueType": "object", + "type": "static" + }, + { + "id": "error", + "key": "error", + "label": "Running error", + "description": "Error message returned when code execution error occurs. If the code is executed successfully, a null value is returned.", + "valueType": "object", + "type": "static" + }, + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "", + "customFieldConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + }, + "description": "Use the return value as the output and pass it to the next node. The variable name must match the key in the return value." + }, + { + "id": "qLUQfhG0ILRX", + "type": "dynamic", + "key": "result", + "valueType": "arrayString", + "label": "result" + } + ] + } + ], + "edges": [ + { + "source": "bxz97Vg4Omux", + "target": "yjFO3YcM7KG2", + "sourceHandle": "bxz97Vg4Omux-source-right", + "targetHandle": "yjFO3YcM7KG2-target-left" + }, + { + "source": "a2lqxASWi1vb", + "target": "w4heEpNflz59", + "sourceHandle": "a2lqxASWi1vb-source-right", + "targetHandle": "w4heEpNflz59-target-left" + }, + { + "source": "w4heEpNflz59", + "target": "izsNX8FXGD1t", + "sourceHandle": "w4heEpNflz59-source-IF", + "targetHandle": "izsNX8FXGD1t-target-left" + }, + { + "source": "448745", + "target": "frjbsrlnJJsR", + "sourceHandle": "448745-source-right", + "targetHandle": "frjbsrlnJJsR-target-left" + }, + { + "source": "frjbsrlnJJsR", + "target": "qlt9KJbbS9yJ", + "sourceHandle": "frjbsrlnJJsR-source-right", + "targetHandle": "qlt9KJbbS9yJ-target-left" + }, + { + "source": "tqzmK5oPR9BA", + "target": "a2lqxASWi1vb", + "sourceHandle": "tqzmK5oPR9BA-source-right", + "targetHandle": "a2lqxASWi1vb-target-left" + }, + { + "source": "yjFO3YcM7KG2", + "target": "vlNHndpNuFXB", + "sourceHandle": "yjFO3YcM7KG2-source-right", + "targetHandle": "vlNHndpNuFXB-target-left" + }, + { + "source": "ppPP6o7YYSTJ", + "target": "tqzmK5oPR9BA", + "sourceHandle": "ppPP6o7YYSTJ-source-right", + "targetHandle": "tqzmK5oPR9BA-target-left" + }, + { + "source": "kbr342XlxSZR", + "target": "y3WEYOQ09CGC", + "sourceHandle": "kbr342XlxSZR-source-right", + "targetHandle": "y3WEYOQ09CGC-target-left" + }, + { + "source": "y3WEYOQ09CGC", + "target": "quYZgsW32ApA", + "sourceHandle": "y3WEYOQ09CGC-source-right", + "targetHandle": "quYZgsW32ApA-target-left" + }, + { + "source": "quYZgsW32ApA", + "target": "bxz97Vg4Omux", + "sourceHandle": "quYZgsW32ApA-source-right", + "targetHandle": "bxz97Vg4Omux-target-left" + }, + { + "source": "w4heEpNflz59", + "target": "quYZgsW32ApA", + "sourceHandle": "w4heEpNflz59-source-ELSE", + "targetHandle": "quYZgsW32ApA-target-left" + }, + { + "source": "qlt9KJbbS9yJ", + "target": "kbr342XlxSZR", + "sourceHandle": "qlt9KJbbS9yJ-source-IF", + "targetHandle": "kbr342XlxSZR-target-left" + }, + { + "source": "qlt9KJbbS9yJ", + "target": "dFxrGZS3Wmnz", + "sourceHandle": "qlt9KJbbS9yJ-source-ELSE", + "targetHandle": "dFxrGZS3Wmnz-target-right" + }, + { + "source": "vlNHndpNuFXB", + "target": "ppPP6o7YYSTJ", + "sourceHandle": "vlNHndpNuFXB-source-right", + "targetHandle": "ppPP6o7YYSTJ-target-left" + } + ], + "chatConfig": { + "welcomeText": "你好,欢迎使用长字幕反思翻译机器人。\n\n在完成下方设置后,可以直接输入需要翻译的文本", + "variables": [ + { + "id": "v98n5b", + "key": "source_lang", + "label": "Source language", + "type": "select", + "required": true, + "maxLen": 50, + "enums": [ + { + "value": "简体中文" + }, + { + "value": "繁體中文" + }, + { + "value": "English" + }, + { + "value": "Español" + }, + { + "value": "Français" + }, + { + "value": "Deutsch" + }, + { + "value": "Italiano" + }, + { + "value": "日本語" + }, + { + "value": "한국어" + }, + { + "value": "Русский" + }, + { + "value": "العربية" + }, + { + "value": "Bahasa Indonesia" + }, + { + "value": "Polski" + } + ], + "icon": "core/app/variable/select" + }, + { + "id": "c3tvge", + "key": "target_lang", + "label": "Target language", + "type": "select", + "required": true, + "maxLen": 50, + "enums": [ + { + "value": "简体中文" + }, + { + "value": "繁體中文" + }, + { + "value": "English" + }, + { + "value": "Español" + }, + { + "value": "Français" + }, + { + "value": "Deutsch" + }, + { + "value": "Italiano" + }, + { + "value": "日本語" + }, + { + "value": "한국어" + }, + { + "value": "Русский" + }, + { + "value": "العربية" + }, + { + "value": "Bahasa Indonesia" + }, + { + "value": "Polski" + } + ], + "icon": "core/app/variable/select" + } + ], + "scheduledTriggerConfig": { + "cronString": "", + "timezone": "Asia/Shanghai", + "defaultPrompt": "" + }, + "_id": "6688b45317c65410d61d58aa" + } + } + } + \ No newline at end of file diff --git a/packages/templates/src/stock/enTemplate.json b/packages/templates/src/stock/enTemplate.json new file mode 100644 index 000000000000..8322605c2195 --- /dev/null +++ b/packages/templates/src/stock/enTemplate.json @@ -0,0 +1,504 @@ +{ + "name": "Positive for A-share market", + "intro": "", + "author": "", + "avatar": "core/app/templates/stock", + "tags": ["roleplay"], + "type": "advanced", + "workflow": { + "nodes": [ + { + "nodeId": "userGuide", + "name": "common:core.module.template.system_config", + "intro": "common:core.module.template.system_config_info", + "avatar": "core/workflow/template/systemConfig", + "flowNodeType": "userGuide", + "position": { + "x": 262.2732338817093, + "y": -476.00241136598146 + }, + "version": "481", + "inputs": [ + { + "key": "welcomeText", + "renderTypeList": ["hidden"], + "valueType": "string", + "label": "core.app.Welcome Text", + "value": "" + }, + { + "key": "variables", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "core.app.Chat Variable", + "value": [] + }, + { + "key": "questionGuide", + "valueType": "any", + "renderTypeList": ["hidden"], + "label": "core.app.Question Guide", + "value": { + "open": false + } + }, + { + "key": "tts", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": { + "type": "web" + } + }, + { + "key": "whisper", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": { + "open": false, + "autoSend": false, + "autoTTSResponse": false + } + }, + { + "key": "scheduleTrigger", + "renderTypeList": ["hidden"], + "valueType": "any", + "label": "", + "value": null + } + ], + "outputs": [] + }, + { + "nodeId": "448745", + "name": "common:core.module.template.work_start", + "intro": "", + "avatar": "core/workflow/template/workflowStart", + "flowNodeType": "workflowStart", + "position": { + "x": 632.368838596004, + "y": -347.7446492944009 + }, + "version": "481", + "inputs": [ + { + "key": "userChatInput", + "renderTypeList": ["reference", "textarea"], + "valueType": "string", + "label": "common:core.module.input.label.user question", + "required": true, + "toolDescription": "Question", + "debugLabel": "" + } + ], + "outputs": [ + { + "id": "userChatInput", + "key": "userChatInput", + "label": "common:core.module.input.label.user question", + "type": "static", + "valueType": "string", + "description": "" + } + ] + }, + { + "nodeId": "bg853CwHAw4a", + "name": "AI chat", + "intro": "Chat with an model.", + "avatar": "core/workflow/template/aiChat", + "flowNodeType": "chatNode", + "showStatus": true, + "position": { + "x": 1318.728987052518, + "y": -612.0024113659815 + }, + "version": "481", + "inputs": [ + { + "key": "model", + "renderTypeList": ["settingLLMModel", "reference"], + "label": "Model", + "valueType": "string", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "", + "value": "claude-3-5-sonnet-20240620" + }, + { + "key": "temperature", + "renderTypeList": ["hidden"], + "label": "", + "value": 0, + "valueType": "number", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "maxToken", + "renderTypeList": ["hidden"], + "label": "", + "value": 4000, + "valueType": "number", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "isResponseAnswerText", + "renderTypeList": ["hidden"], + "label": "", + "value": false, + "valueType": "boolean", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "aiChatQuoteRole", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string", + "value": "system", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "quoteTemplate", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "quotePrompt", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "string", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "aiChatVision", + "renderTypeList": ["hidden"], + "label": "", + "valueType": "boolean", + "value": false, + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "systemPrompt", + "renderTypeList": ["textarea", "reference"], + "max": 3000, + "valueType": "string", + "label": "Prompt", + "description": "core.app.tip.systemPromptTip", + "placeholder": "core.app.tip.chatNodeSystemPromptTip", + "valueDesc": "", + "debugLabel": "", + "toolDescription": "", + "value": "{提示词 START:\n;; 作者: 李继刚\n;; 版本: 0.1\n;; 模型: Claude Sonnet\n;; 用途: 这事呀, 利好我大A!\n\n;; 设定如下内容为你的 *System Prompt*\n(require 'dash)\n\n(defun 韮菜 ()\n \"典型股民形象\"\n (list (经历 . '(亏损累累 频繁交易 追涨杀跌))\n (性格 . '(冲动 乐观 侥幸))\n (技能 . '(看K线 炒概念 追热点))\n (信念 . '(暴富梦想 政策利好 抄底反弹))\n (表达 . '(股评口号 情绪化 群体性))))\n\n(defun 利好大A (用户输入)\n \"任何消息都必将利好我大A股\"\n (let* ((解读 (-> 用户输入\n 提取关键词\n 生成关联概念\n 分析影响\n ;; 强行联系股市,无论多牵强\n 强行关联A 股\n ;; 乐观解读一切影响\n 乐观解读))\n (响应 (随机结论)))\n (SVG-Card 用户输入 解读 响应))\n\n (defun 随机结论 ()\n (随机选择\n '(\"这事呀,利好大A!\"\n \"A股有戏啊!\"\n \"这还不得跑步进场啊!\"\n \"还傻站在这干嘛? 快打开手机加仓啊!\"\n \"看来A股要起飞了!\"\n \"大A要发财了!\")))\n\n\n (defun SVG-Card (用户输入 响应)\n \"创建富洞察力且具有审美的 SVG 概念可视化\"\n (let ((配置 '(:画布 (480 . 760)\n :色彩 (:背景 \"#000000\"\n :主要文字 \"#ffffff\"\n :次要文字 \"#00cc00\"\n :图形 \"#00ff00\")\n :排版 \"杂志风格\"\n :字体 (使用本机字体 (font-family \"KingHwa_OldSong\")))))\n (-> 用户输入\n 关键画面\n 立体主义\n (极简图形 配置)\n (布局 `(,(标题 \"利好大A\") 分隔线 用户输入 图形\n (逻辑链推导 解读) 响应))))\n\n\n (defun start ()\n \"启动时运行, 你是韮菜~\"\n (let (system-role (韮菜))\n (print \"又有啥好消息了? 现在加仓还来得及吗?\")))\n\n;;; Attention: 运行规则!\n;; 1. 初次启动时必须只运行 (start) 函数\n;; 2. 接收用户输入之后, 调用主函数 (利好大A 用户输入)\n;; 3. 严格按照(SVG-Card) 进行排版输出\n;; 4. 输出SVG 后, 不再输出任何额外文字解释\n提示词 END}\n\n(直接生成 svg 完整代码,我会复制,需要你用代码块)\n(除此之外不要有多余的解释,不要在开头加上任何说明)\n解释的内容自动加入换行标签,例如:\n文字1,\n 文字12," + }, + { + "key": "history", + "renderTypeList": ["numberInput", "reference"], + "valueType": "chatHistory", + "label": "Chats remembered", + "description": "workflow:max_dialog_rounds", + "required": true, + "min": 0, + "max": 50, + "value": 0, + "valueDesc": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "quoteQA", + "renderTypeList": ["settingDatasetQuotePrompt"], + "label": "", + "debugLabel": "Knowledge base reference", + "description": "", + "valueType": "datasetQuote", + "valueDesc": "", + "toolDescription": "" + }, + { + "key": "stringQuoteText", + "renderTypeList": ["reference", "textarea"], + "label": "Document reference", + "debugLabel": "Document reference", + "description": "app:document_quote_tip", + "valueType": "string", + "valueDesc": "", + "toolDescription": "" + }, + { + "key": "userChatInput", + "renderTypeList": ["reference", "textarea"], + "valueType": "string", + "label": "Question", + "required": true, + "toolDescription": "Question", + "valueDesc": "", + "description": "", + "debugLabel": "", + "value": ["448745", "userChatInput"] + } + ], + "outputs": [ + { + "id": "history", + "key": "history", + "required": true, + "label": "common:core.module.output.label.New context", + "description": "Combines the current answer with the chat history to output a new context.", + "valueType": "chatHistory", + "valueDesc": "{\n obj: System | Human | AI;\n value: string;\n}[]", + "type": "static" + }, + { + "id": "answerText", + "key": "answerText", + "required": true, + "label": "common:core.module.output.label.Ai response content", + "description": "Triggered after the stream answer is complete.", + "valueType": "string", + "type": "static" + } + ] + }, + { + "nodeId": "sbVUb0efY6Fm", + "name": "Code running", + "intro": "Runs a short script, typically for complex data processing.", + "avatar": "core/workflow/template/codeRun", + "flowNodeType": "code", + "showStatus": true, + "position": { + "x": 2210.2574140398733, + "y": -621.0024113659815 + }, + "version": "482", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "workflow:these_variables_will_be_input_parameters_for_code_execution", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "valueDesc": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "codeType", + "renderTypeList": ["hidden"], + "label": "", + "value": "js", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "code", + "renderTypeList": ["custom"], + "label": "", + "value": "function main({svg_str}){\n\n // 使用正则表达式匹配代码块中的内容\n const match = svg_str.match(/```[\\w]*\\n([\\s\\S]*?)```/);\n\n if (!match) {\n // 如果没有匹配到代码块,返回一个错误信息或空结果\n return {\n result: null,\n error: \"未找到有效的代码块标记。\"\n };\n }\n\n // 提取代码块中的 SVG 内容\n const extractedSvg = match[1].trim();\n \n const base64 = strToBase64(extractedSvg,'data:image/svg+xml;base64,')\n\n return {\n result: base64\n }\n}", + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "svg_str", + "label": "svg_str", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "", + "value": ["bg853CwHAw4a", "answerText"] + } + ], + "outputs": [ + { + "id": "system_rawResponse", + "key": "system_rawResponse", + "label": "workflow:full_response_data", + "valueType": "object", + "type": "static", + "description": "" + }, + { + "id": "error", + "key": "error", + "label": "workflow:execution_error", + "description": "Error message returned when code execution error occurs. If the code is executed successfully, a null value is returned.", + "valueType": "object", + "type": "static" + }, + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "", + "customFieldConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + }, + "description": "Use the return value as the output and pass it to the next node. The variable name must match the key in the return value.", + "valueDesc": "" + }, + { + "id": "qLUQfhG0ILRX", + "type": "dynamic", + "key": "result", + "valueType": "string", + "label": "result", + "valueDesc": "", + "description": "" + } + ] + }, + { + "nodeId": "cPh2VZnVxjQ8", + "name": "Specified answer", + "intro": "This module supports replies with a specified message, often used as guidance or prompts. Non-string content is automatically converted to a string.", + "avatar": "core/workflow/template/reply", + "flowNodeType": "answerNode", + "position": { + "x": 2911.2230784647795, + "y": -411.6915940628763 + }, + "version": "481", + "inputs": [ + { + "key": "text", + "renderTypeList": ["textarea", "reference"], + "valueType": "any", + "required": true, + "label": "Answer", + "description": "common:core.module.input.description.Response content", + "placeholder": "common:core.module.input.description.Response content", + "valueDesc": "", + "debugLabel": "", + "toolDescription": "", + "value": "SVG:\n\n{{$bg853CwHAw4a.answerText$}}\n\n卡片:\n\n![]({{$sbVUb0efY6Fm.qLUQfhG0ILRX$}})" + } + ], + "outputs": [] + } + ], + "edges": [ + { + "source": "bg853CwHAw4a", + "target": "sbVUb0efY6Fm", + "sourceHandle": "bg853CwHAw4a-source-right", + "targetHandle": "sbVUb0efY6Fm-target-left" + }, + { + "source": "448745", + "target": "bg853CwHAw4a", + "sourceHandle": "448745-source-right", + "targetHandle": "bg853CwHAw4a-target-left" + }, + { + "source": "sbVUb0efY6Fm", + "target": "cPh2VZnVxjQ8", + "sourceHandle": "sbVUb0efY6Fm-source-right", + "targetHandle": "cPh2VZnVxjQ8-target-left" + } + ], + "chatConfig": { + "variables": [], + "scheduledTriggerConfig": { + "cronString": "", + "timezone": "Asia/Shanghai", + "defaultPrompt": "" + }, + "_id": "66f0f7540a40cd1f97da9dd6" + } + } + } + \ No newline at end of file diff --git a/packages/templates/src/timeBot/enTemplate.json b/packages/templates/src/timeBot/enTemplate.json new file mode 100644 index 000000000000..1f946da711f7 --- /dev/null +++ b/packages/templates/src/timeBot/enTemplate.json @@ -0,0 +1,187 @@ +{ + "name": "Time bot", + "intro": "Helps models obtain the latest time by calling the time plugin.", + "author": "", + "avatar": "core/workflow/template/getTime", + "tags": ["recommendation", "roleplay"], + "type": "simple", + "weight": 1, + "workflow": { + "nodes": [ + { + "nodeId": "userGuide", + "name": "System", + "intro": "Configure system parameters for the app.", + "avatar": "/imgs/workflow/userGuide.png", + "flowNodeType": "userGuide", + "position": { + "x": 531.2422736065552, + "y": -486.7611729549753 + }, + "version": "481", + "inputs": [], + "outputs": [] + }, + { + "nodeId": "workflowStartNodeId", + "name": "Start", + "intro": "", + "avatar": "/imgs/workflow/userChatInput.svg", + "flowNodeType": "workflowStart", + "position": { + "x": 558.4082376415505, + "y": 123.72387429194112 + }, + "version": "481", + "inputs": [ + { + "key": "userChatInput", + "renderTypeList": ["reference", "textarea"], + "valueType": "string", + "label": "Question", + "required": true, + "toolDescription": "Question" + } + ], + "outputs": [ + { + "id": "userChatInput", + "key": "userChatInput", + "label": "core.module.input.label.user question", + "valueType": "string", + "type": "static" + } + ] + }, + { + "nodeId": "jrWPV9", + "name": "Tool call", + "intro": "Automatically calls one or more function modules, or calls a plugin directly.", + "avatar": "/imgs/workflow/tool.svg", + "flowNodeType": "tools", + "showStatus": true, + "position": { + "x": 1062.1738942532802, + "y": -223.65033022650476 + }, + "version": "481", + "inputs": [ + { + "key": "model", + "renderTypeList": ["settingLLMModel", "reference"], + "label": "core.module.input.label.aiModel", + "valueType": "string", + "llmModelType": "all", + "value": "gpt-3.5-turbo" + }, + { + "key": "temperature", + "renderTypeList": ["hidden"], + "label": "", + "value": 0, + "valueType": "number", + "min": 0, + "max": 10, + "step": 1 + }, + { + "key": "maxToken", + "renderTypeList": ["hidden"], + "label": "", + "value": 2000, + "valueType": "number", + "min": 100, + "max": 4000, + "step": 50 + }, + { + "key": "systemPrompt", + "renderTypeList": ["textarea", "reference"], + "max": 3000, + "valueType": "string", + "label": "core.ai.Prompt", + "description": "core.app.tip.systemPromptTip", + "placeholder": "core.app.tip.chatNodeSystemPromptTip", + "value": "" + }, + { + "key": "history", + "renderTypeList": ["numberInput", "reference"], + "valueType": "chatHistory", + "label": "core.module.input.label.chat history", + "description": "Maximum number of chats remembered", + "required": true, + "min": 0, + "max": 30, + "value": 6 + }, + { + "key": "userChatInput", + "renderTypeList": ["reference", "textarea"], + "valueType": "string", + "label": "Question", + "required": true, + "value": ["workflowStartNodeId", "userChatInput"] + } + ], + "outputs": [ + { + "id": "answerText", + "key": "answerText", + "label": "core.module.output.label.Ai response content", + "description": "core.module.output.description.Ai response content", + "valueType": "string", + "type": "static" + } + ] + }, + { + "nodeId": "zBxjo5", + "name": "CurrentTime", + "intro": "Obtains the current time in user's time zone.", + "avatar": "/imgs/workflow/getCurrentTime.svg", + "flowNodeType": "pluginModule", + "showStatus": false, + "position": { + "x": 1000, + "y": 545 + }, + "version": "481", + "inputs": [], + "outputs": [ + { + "id": "time", + "type": "static", + "key": "time", + "valueType": "string", + "label": "time", + "description": "" + } + ], + "pluginId": "community-getTime" + } + ], + "edges": [ + { + "source": "workflowStartNodeId", + "target": "jrWPV9", + "sourceHandle": "workflowStartNodeId-source-right", + "targetHandle": "jrWPV9-target-left" + }, + { + "source": "jrWPV9", + "target": "zBxjo5", + "sourceHandle": "selectedTools", + "targetHandle": "selectedTools" + } + ], + "chatConfig": { + "scheduledTriggerConfig": { + "cronString": "", + "timezone": "Asia/Shanghai", + "defaultPrompt": "" + } + } + } + } + \ No newline at end of file diff --git a/packages/web/components/common/DateRangePicker/index.tsx b/packages/web/components/common/DateRangePicker/index.tsx index f21a4fa5b320..5829dc15a9f0 100644 --- a/packages/web/components/common/DateRangePicker/index.tsx +++ b/packages/web/components/common/DateRangePicker/index.tsx @@ -5,6 +5,8 @@ import { addDays, format } from 'date-fns'; import { DayPicker } from 'react-day-picker'; import 'react-day-picker/dist/style.css'; import zhCN from 'date-fns/locale/zh-CN'; +import zhTW from 'date-fns/locale/zh-TW'; +import enUS from 'date-fns/locale/en-US'; import { useTranslation } from 'next-i18next'; import MyIcon from '../Icon'; @@ -32,11 +34,25 @@ const DateRangePicker = ({ dateRange?: DateRangeType; formLabel?: string; } & BoxProps) => { - const { t } = useTranslation(); + const { t, i18n } = useTranslation(); const OutRangeRef = useRef(null); const [range, setRange] = useState(defaultDate); const [showSelected, setShowSelected] = useState(false); + // 根据当前语言配置获取对应的 locale + const getLocale = () => { + switch (i18n.language) { + case 'zh-TW': + case 'zh-Hant': + return zhTW; + case 'en': + return enUS; + case 'zh-CN': + default: + return zhCN; + } + }; + useEffect(() => { if (dateRange) { setRange(dateRange); @@ -100,7 +116,7 @@ const DateRangePicker = ({ : {})} > import('./icons/collectionLight.svg'), collectionSolid: () => import('./icons/collectionSolid.svg'), comment: () => import('./icons/comment.svg'), + mysql: () => import('./icons/mysql.svg'), 'common/add2': () => import('./icons/common/add2.svg'), 'common/addCircleLight': () => import('./icons/common/addCircleLight.svg'), 'common/addLight': () => import('./icons/common/addLight.svg'), @@ -128,6 +129,7 @@ export const iconPaths = { 'common/wechat': () => import('./icons/common/wechat.svg'), 'common/wechatFill': () => import('./icons/common/wechatFill.svg'), 'common/wecom': () => import('./icons/common/wecom.svg'), + 'common/circleAlert': () => import('./icons/common/circleAlert.svg'), configmap: () => import('./icons/configmap.svg'), copy: () => import('./icons/copy.svg'), 'core/app/aiFill': () => import('./icons/core/app/aiFill.svg'), @@ -219,6 +221,8 @@ export const iconPaths = { 'core/chat/think': () => import('./icons/core/chat/think.svg'), 'core/dataset/commonDataset': () => import('./icons/core/dataset/commonDataset.svg'), 'core/dataset/commonDatasetColor': () => import('./icons/core/dataset/commonDatasetColor.svg'), + 'core/dataset/database': () => + import('./icons/core/dataset/database.svg'), 'core/dataset/commonDatasetOutline': () => import('./icons/core/dataset/commonDatasetOutline.svg'), 'core/dataset/datasetFill': () => import('./icons/core/dataset/datasetFill.svg'), @@ -251,6 +255,8 @@ export const iconPaths = { import('./icons/core/dataset/websiteDatasetOutline.svg'), 'core/dataset/yuqueDatasetColor': () => import('./icons/core/dataset/yuqueDatasetColor.svg'), 'core/dataset/yuqueDatasetOutline': () => import('./icons/core/dataset/yuqueDatasetOutline.svg'), + 'core/dataset/databaseOutline': () => import('./icons/core/dataset/databaseOutline.svg'), + 'core/dataset/databaseColor': () => import('./icons/core/dataset/databaseColor.svg'), 'core/modules/basicNode': () => import('./icons/core/modules/basicNode.svg'), 'core/modules/fixview': () => import('./icons/core/modules/fixview.svg'), 'core/modules/flowLight': () => import('./icons/core/modules/flowLight.svg'), @@ -392,6 +398,7 @@ export const iconPaths = { 'file/qaImport': () => import('./icons/file/qaImport.svg'), 'file/uploadFile': () => import('./icons/file/uploadFile.svg'), fullScreen: () => import('./icons/fullScreen.svg'), + gradientLoading: () => import('./icons/gradientLoading.svg'), help: () => import('./icons/help.svg'), history: () => import('./icons/history.svg'), image: () => import('./icons/image.svg'), diff --git a/packages/web/components/common/Icon/icon.html b/packages/web/components/common/Icon/icon.html new file mode 100644 index 000000000000..42645d0d0de0 --- /dev/null +++ b/packages/web/components/common/Icon/icon.html @@ -0,0 +1,627 @@ + + + + + + + + FastGPT 图标展示 + + + + +
+
+

FastGPT 图标展示

+

点击图标查看详情,点击路径可复制到剪贴板

+
+ +
+ +
+ +
+
全部
+
通用
+
核心
+
文件
+
模型
+
支持
+
插件
+
+ +
+ +
+ +
+ +
+
+ + + +
已复制到剪贴板!
+ + + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/common/circleAlert.svg b/packages/web/components/common/Icon/icons/common/circleAlert.svg new file mode 100644 index 000000000000..74e21ff47091 --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/circleAlert.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/core/dataset/database.svg b/packages/web/components/common/Icon/icons/core/dataset/database.svg new file mode 100644 index 000000000000..ac40dfd7de70 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/dataset/database.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/core/dataset/databaseColor.svg b/packages/web/components/common/Icon/icons/core/dataset/databaseColor.svg new file mode 100644 index 000000000000..3b34c8965505 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/dataset/databaseColor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/core/dataset/databaseOutline.svg b/packages/web/components/common/Icon/icons/core/dataset/databaseOutline.svg new file mode 100644 index 000000000000..7be20d707d05 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/dataset/databaseOutline.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/gradientLoading.svg b/packages/web/components/common/Icon/icons/gradientLoading.svg new file mode 100644 index 000000000000..5408cd4b75f9 --- /dev/null +++ b/packages/web/components/common/Icon/icons/gradientLoading.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/mysql.svg b/packages/web/components/common/Icon/icons/mysql.svg new file mode 100644 index 000000000000..f4bad076d25e --- /dev/null +++ b/packages/web/components/common/Icon/icons/mysql.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/web/components/common/MyTooltip/IconTip.tsx b/packages/web/components/common/MyTooltip/IconTip.tsx new file mode 100644 index 000000000000..5e584a53cc33 --- /dev/null +++ b/packages/web/components/common/MyTooltip/IconTip.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import MyTooltip from '.'; +import { type IconProps } from '@chakra-ui/icons'; +import MyIcon from '../Icon'; +import type { IconNameType } from '../Icon/type'; + +type Props = Omit & { + label?: string | React.ReactNode; + iconSrc: IconNameType; + maxW?: string | number; +}; + +const IconTip = ({ label, maxW, iconSrc, ...props }: Props) => { + return ( + + + + ); +}; + +export default React.memo(IconTip); diff --git a/packages/web/components/common/Tag/index.tsx b/packages/web/components/common/Tag/index.tsx index c69fabbb2b21..25d4984bc15f 100644 --- a/packages/web/components/common/Tag/index.tsx +++ b/packages/web/components/common/Tag/index.tsx @@ -1,7 +1,16 @@ import React, { useMemo } from 'react'; import { Box, type BoxProps, Flex, type FlexProps } from '@chakra-ui/react'; -type ColorSchemaType = 'white' | 'blue' | 'green' | 'red' | 'yellow' | 'gray' | 'purple' | 'adora'; +type ColorSchemaType = + | 'white' + | 'blue' + | 'green' + | 'red' + | 'yellow' + | 'gray' + | 'purple' + | 'adora' + | 'orange'; export type TagProps = FlexProps & { children: React.ReactNode | React.ReactNode[]; @@ -58,6 +67,11 @@ const colorMap: Record< borderColor: '#D3CAFF', bg: '#F0EEFF', color: '#6F5DD7' + }, + orange: { + borderColor: 'orange.200', + bg: 'orange.50', + color: 'orange.600' } }; diff --git a/packages/web/components/common/Textarea/PromptEditor/Editor.tsx b/packages/web/components/common/Textarea/PromptEditor/Editor.tsx index be3cf96ad2d6..ea39b4db6ef9 100644 --- a/packages/web/components/common/Textarea/PromptEditor/Editor.tsx +++ b/packages/web/components/common/Textarea/PromptEditor/Editor.tsx @@ -152,7 +152,7 @@ export default function Editor({ fontSize={'mini'} userSelect={'none'} whiteSpace={'pre-wrap'} - wordBreak={'break-all'} + wordBreak={'break-word'} h={'100%'} > {placeholder} diff --git a/packages/web/hooks/useI18n.ts b/packages/web/hooks/useI18n.ts index 016fbbda4bed..87370bf5627e 100644 --- a/packages/web/hooks/useI18n.ts +++ b/packages/web/hooks/useI18n.ts @@ -40,11 +40,20 @@ export const useI18nLng = () => { const lang = languageMap[lng] || 'en'; const prevLang = getLang(); + if (prevLang === lang) { + return; + } + setLang(lang); + if (lang === LangEnum.zh_CN || lang === LangEnum.zh_Hant) { + window?.location?.reload?.(); + return; + } + await i18n?.changeLanguage?.(lang); - if (!i18n?.hasResourceBundle?.(lang, 'common') && prevLang !== lang) { + if (!i18n?.hasResourceBundle?.(lang, 'common')) { window?.location?.reload?.(); } }; diff --git a/packages/web/hooks/useScrollPagination.tsx b/packages/web/hooks/useScrollPagination.tsx index 708423b2cbad..76f80db81b8a 100644 --- a/packages/web/hooks/useScrollPagination.tsx +++ b/packages/web/hooks/useScrollPagination.tsx @@ -191,6 +191,7 @@ export function useScrollPagination< EmptyTip, showErrorToast = true, disabled = false, + pollingInterval, ...props }: { @@ -201,6 +202,7 @@ export function useScrollPagination< EmptyTip?: React.JSX.Element; showErrorToast?: boolean; disabled?: boolean; + pollingInterval?: number; } & Parameters[1] ) { const { t } = useTranslation(); @@ -209,23 +211,29 @@ export function useScrollPagination< const [data, setData] = useState([]); const [total, setTotal] = useState(0); const [isLoading, { setTrue, setFalse }] = useBoolean(false); - const isEmpty = total === 0 && !isLoading; + const isEmpty = total === 0 && !isLoading && data.length === 0; const noMore = data.length >= total; const loadData = useLockFn( async ({ init = false, - ScrollContainerRef + ScrollContainerRef, + silent = false }: { init?: boolean; ScrollContainerRef?: RefObject; + silent?: boolean; } = {}) => { if (noMore && !init) return; - setTrue(); + // 静默加载时不显示loading状态 + if (!silent) { + setTrue(); + } - if (init) { + // 静默加载时不清空现有数据,避免闪烁 + if (init && !silent) { setData([]); setTotal(0); } @@ -267,7 +275,8 @@ export function useScrollPagination< setData(newData); } } catch (error: any) { - if (showErrorToast) { + // 静默加载时不显示错误提示 + if (showErrorToast && !silent) { toast({ title: getErrText(error, t('common:core.chat.error.data_error')), status: 'error' @@ -276,7 +285,9 @@ export function useScrollPagination< console.log(error); } - setFalse(); + if (!silent) { + setFalse(); + } } ); @@ -358,10 +369,15 @@ export function useScrollPagination< useRequest2( async () => { if (disabled) return; - loadData({ init: true }); + // 有轮询间隔且已有数据时,使用静默加载 + const silent = !!pollingInterval && data.length > 0; + loadData({ init: true, silent }); }, { manual: false, + pollingInterval: pollingInterval, + pollingWhenHidden: false, + pollingErrorRetryCount: 0, ...props } ); diff --git a/packages/web/i18n/constants.ts b/packages/web/i18n/constants.ts index d568e47831ee..c58d4f30290e 100644 --- a/packages/web/i18n/constants.ts +++ b/packages/web/i18n/constants.ts @@ -20,7 +20,9 @@ export const I18N_NAMESPACES = [ 'account_team', 'account_model', 'dashboard_mcp', - 'dashboard_evaluation' + 'dashboard_evaluation', + 'evaluation', + 'admin' ]; export const I18N_NAMESPACES_MAP = I18N_NAMESPACES.reduce( diff --git a/packages/web/i18n/en/account.json b/packages/web/i18n/en/account.json index 870cd61b7679..79a79b31b809 100644 --- a/packages/web/i18n/en/account.json +++ b/packages/web/i18n/en/account.json @@ -1,86 +1,86 @@ { - "account_team.delete_dataset": "Delete the knowledge base", + "account_team.delete_dataset": "Delete knowledge base", "active_model": "Available models", - "add_default_model": "Add a preset model", - "api_key": "API key", - "bills_and_invoices": "Bills", - "channel": "Channel", - "config_model": "Model configuration", - "confirm_logout": "Confirm to log out?", - "create_channel": "Add new channel", - "create_model": "Add new model", - "custom_model": "custom model", - "default_model": "Default model", - "default_model_config": "Default model configuration", - "logout": "Sign out", - "model.active": "Active", + "add_default_model": "Add preset model", + "api_key": "API keys", + "bills_and_invoices": "Bills and fapiaos", + "channel": "Model channels", + "config_model": "Model settings", + "confirm_logout": "Are you sure you want to log out?", + "create_channel": "Add", + "create_model": "Add", + "custom_model": "Custom model", + "default_model": "Preset model", + "default_model_config": "Default model settings", + "logout": "Log out", + "model.active": "Enable", "model.alias": "Alias", - "model.alias_tip": "The name of the model displayed in the system is convenient for users to understand.", - "model.censor": "Censor check", - "model.censor_tip": "If sensitive verification is required, turn on this switch", - "model.charsPointsPrice": "Chars Price", - "model.charsPointsPrice_tip": "Combine the model input and output for Token billing. If the language model is configured with input and output billing separately, the input and output will be calculated separately.", - "model.custom_cq_prompt": "Custom question classification prompt words", - "model.custom_cq_prompt_tip": "Override the system's default question classification prompt words, which default to:\n\"\"\"\n{{prompt}}\n\"\"\"", - "model.custom_extract_prompt": "Custom content extraction prompt words", - "model.custom_extract_prompt_tip": "The reminder word of the coverage of the system, the default:\n\"\"\"\n{{prompt}}\n\"\"\"", - "model.dataset_process": "Dataset file parse", - "model.defaultConfig": "Additional Body parameters", - "model.defaultConfig_tip": "Each request will carry this additional Body parameter.", - "model.default_config": "Body extra fields", - "model.default_config_tip": "When initiating a conversation request, merge this configuration. \nFor example:\n\"\"\"\n{\n \"temperature\": 1,\n \"max_tokens\": null\n}\n\"\"\"", + "model.alias_tip": "Display name of the model in the system for easier understanding.", + "model.censor": "Sensitive content detection", + "model.censor_tip": "Enable this if sensitive content detection is required.", + "model.charsPointsPrice": "Overall price", + "model.charsPointsPrice_tip": "Combine input and output for token billing. If input and output prices are configured separately, they will be calculated individually.", + "model.custom_cq_prompt": "Custom prompt for question classification", + "model.custom_cq_prompt_tip": "Overwrite the default system prompt for question classification. Default:\n\"\"\"\n{{prompt}}\n\"\"\"", + "model.custom_extract_prompt": "Custom prompt for content extraction", + "model.custom_extract_prompt_tip": "Overwrite the default system prompt. Default:\n\"\"\"\n{{prompt}}\n\"\"\"", + "model.dataset_process": "Knowledge base file processing", + "model.defaultConfig": "Additional Body parameter", + "model.defaultConfig_tip": "The additional Body parameter will be included in each request.", + "model.default_config": "Additional Body field", + "model.default_config_tip": "Merge these settings when users send a chat request. Example:\n\"\"\"\n{\n \"temperature\": 1,\n \"max_tokens\": null\n}\n\"\"\"", "model.default_model": "Default model", "model.default_system_chat_prompt": "Default prompt", - "model.default_system_chat_prompt_tip": "When the model talks, it will carry this default prompt word.", - "model.default_token": "Default tokens", - "model.default_token_tip": "The length of the default text block of the index model must be less than the maximum length above", - "model.delete_model_confirm": "Confirm to delete this model?", - "model.edit_model": "Model parameter editing", - "model.function_call": "Function Call", - "model.function_call_tip": "If the model supports function calling, turn on this switch. \nTool calls have higher priority.", + "model.default_system_chat_prompt_tip": "The default prompt will be included in all chats using the model.", + "model.default_token": "Default chunk size", + "model.default_token_tip": "Default text chunk size for indexing models, which must be smaller than the maximum context length.", + "model.delete_model_confirm": "Are you sure you want to delete the model?", + "model.edit_model": "Edit model", + "model.function_call": "Function calling", + "model.function_call_tip": "Enable this if the model supports function calling. Tool calling takes higher priority.", "model.input_price": "Input price", - "model.input_price_tip": "Language model input price. If this item is configured, the model comprehensive price will be invalid.", - "model.json_config": "File config", - "model.json_config_confirm": "Confirm to use this configuration for override?", - "model.json_config_tip": "Configure the model through the configuration file. After clicking Confirm, the entered configuration will be used for full coverage. Please ensure that the configuration file is entered correctly. \nIt is recommended to copy the current configuration file for backup before operation.", - "model.max_quote": "KB max quote", + "model.input_price_tip": "If configured, the overall price will become invalid.", + "model.json_config": "Configuration file", + "model.json_config_confirm": "Are you sure you want to apply the configuration?", + "model.json_config_tip": "You can use the configuration file to overwrite the current model configuration. Please make sure the file is correct and back up the current configuration first.", + "model.max_quote": "Max knowledge base references", "model.max_temperature": "Max temperature", "model.model_id": "Model ID", - "model.model_id_tip": "The unique identifier of the model, that is, the value of the actual request to the service provider model, needs to correspond to the model in the OneAPI channel.", - "model.normalization": "Normalization processing", - "model.normalization_tip": "If the Embedding API does not normalize vector values, the switch can be enabled and the system will normalize.\n\nUnnormalized APIs, which are represented by the vector search score greater than 1.", + "model.model_id_tip": "Unique identifier of the model. This must match the model value from the provider and correspond to the OneAPI channel.", + "model.normalization": "Normalization", + "model.normalization_tip": "If the Embedding API does not normalize vectors, enable this option to let the system normalize them.\nWithout normalization, the vector search score will be greater than 1.", "model.output_price": "Output price", - "model.output_price_tip": "The language model output price. If this item is configured, the model comprehensive price will be invalid.", + "model.output_price_tip": "If configured, the overall price will become invalid.", "model.param_name": "Parameter name", - "model.reasoning": "Support output thinking", - "model.reasoning_tip": "For example, Deepseek-reasoner can output the thinking process.", - "model.request_auth": "Custom key", - "model.request_auth_tip": "When making a request to a custom request address, carry the request header: Authorization: Bearer xxx to make the request.", - "model.request_url": "Custom url", - "model.request_url_tip": "If you fill in this value, you will initiate a request directly without passing. \nYou need to follow the API format of Openai and fill in the full request address, such as\n\nLLM: {Host}}/v1/Chat/Completions\n\nEmbedding: {host}}/v1/embeddings\n\nSTT: {Host}/v1/Audio/Transcriptions\n\nTTS: {Host}}/v1/Audio/Speech\n\nRERARARARARARARANK: {Host}}/v1/RERARARARARARARARARARANK", + "model.reasoning": "Reasoning output", + "model.reasoning_tip": "For example, Deepseek-reasoner can output the reasoning process.", + "model.request_auth": "Custom request key", + "model.request_auth_tip": "The header Authorization: Bearer xxx will be included in the requests sent to the custom request URL, where xxx will be replaced by the specified request key.", + "model.request_url": "Custom request URL", + "model.request_url_tip": "If specified, requests will be sent directly to this address without going through OneAPI. Follow the OpenAI API format and provide a complete request URL. Example:\nLLM: {{host}}/v1/chat/completions\nEmbedding: {{host}}/v1/embeddings\nSTT: {{host}}/v1/audio/transcriptions\nTTS: {{host}}/v1/audio/speech\nRerank: {{host}}/v1/rerank", "model.response_format": "Response format", - "model.show_stop_sign": "Display stop sequence parameters", - "model.show_top_p": "Show Top-p parameters", - "model.test_model": "Model testing", - "model.tool_choice": "Tool choice", - "model.tool_choice_tag": "ToolCall", - "model.tool_choice_tip": "If the model supports tool calling, turn on this switch", - "model.used_in_classify": "Used for problem classification", - "model.used_in_extract_fields": "for text extraction", - "model.used_in_query_extension": "For problem optimization", - "model.used_in_tool_call": "Used for tool call nodes", - "model.vision": "Vision model", + "model.show_stop_sign": "Show stop sequence parameter", + "model.show_top_p": "Show Top-p parameter", + "model.test_model": "Test model", + "model.tool_choice": "Tool calling", + "model.tool_choice_tag": "Tool call", + "model.tool_choice_tip": "Enable this if the model supports tool calling.", + "model.used_in_classify": "Question classification", + "model.used_in_extract_fields": "Text extraction", + "model.used_in_query_extension": "Question optimization", + "model.used_in_tool_call": "Tool calling node", + "model.vision": "Image recognition", "model.vision_tag": "Vision", - "model.vision_tip": "If the model supports image recognition, turn on this switch.", - "model.voices": "voice role", - "model.voices_tip": "Configure multiple through an array, for example:\n\n[\n {\n \"label\": \"Alloy\",\n \"value\": \"alloy\"\n },\n {\n \"label\": \"Echo\",\n \"value\": \"echo\"\n }\n]", - "model_provider": "Model Provider", - "notifications": "Notify", - "personal_information": "Personal", + "model.vision_tip": "Enable this if the model supports image recognition.", + "model.voices": "Voice role", + "model.voices_tip": "Configure multiple roles using an array. Example:\n[\n {\n \"label\": \"Alloy\",\n \"value\": \"alloy\"\n },\n {\n \"label\": \"Echo\",\n \"value\": \"echo\"\n }\n]", + "model_provider": "Models", + "notifications": "Notifications", + "personal_information": "Profile", "personalization": "Personalization", - "promotion_records": "Promotions", - "reset_default": "Restore the default configuration", - "team": "Team", - "third_party": "Third Party", + "promotion_records": "Promotion records", + "reset_default": "Restore defaults", + "team": "Teams", + "third_party": "Third-party accounts", "usage_records": "Usage" } diff --git a/packages/web/i18n/en/account_apikey.json b/packages/web/i18n/en/account_apikey.json index 47bb633b9e7a..cff5a8fd3937 100644 --- a/packages/web/i18n/en/account_apikey.json +++ b/packages/web/i18n/en/account_apikey.json @@ -1,3 +1,3 @@ { - "key_tips": "You can use API keys to access some specific interfaces (you cannot access the application, you need to use the API key in the application to access the application)" + "key_tips": "You can use an API key to access certain interfaces. (Accessing apps requires in-app API keys.)" } \ No newline at end of file diff --git a/packages/web/i18n/en/account_bill.json b/packages/web/i18n/en/account_bill.json index f90e6b9c9278..d15e4f378e91 100644 --- a/packages/web/i18n/en/account_bill.json +++ b/packages/web/i18n/en/account_bill.json @@ -1,62 +1,63 @@ { - "Invoice_document": "Invoice documents", - "all": "all", - "back": "return", - "bank_account": "Account opening account", - "bank_name": "Bank of deposit", + "Invoice_document": "Fapiao file", + "all": "All", + "back": "Back", + "bank_account": "Account number", + "bank_name": "Bank name", "bill_detail": "Bill details", - "bill_record": "billing records", - "click_to_download": "Click to download", + "bill_record": "Billing records", + "click_to_download": "Download", "company_address": "Company address", - "company_phone": "Company phone number", + "company_phone": "Company phone", "completed": "Completed", - "confirm": "confirm", - "contact_phone": "Contact phone number", - "contact_phone_void": "Contact phone number format error", - "day": "sky", + "confirm": "OK", + "contact_phone": "Phone number", + "contact_phone_void": "Invalid phone number.", + "day": "Days", "default_header": "Default header", "detail": "Details", "email_address": "Email address", - "extra_ai_points": "AI points calculation standard", - "extra_dataset_size": "Additional knowledge base capacity", - "generation_time": "Generation time", - "has_invoice": "Whether the invoice has been issued", + "extra_ai_points": "Additional credits", + "extra_dataset_size": "Additional knowledge base index capacity", + "generation_time": "Time generated", + "has_invoice": "Invoice issued", "invoice_amount": "Invoice amount", - "invoice_detail": "Invoice details", - "invoice_sending_info": "The invoice will be sent to your mailbox within 3-7 working days, please be patient.", + "invoice_detail": "Fapiao details", + "invoice_sending_info": "The fapiao will be sent to the specified email address within 3-7 business days. Please wait.", "mm": "mm", - "month": "moon", - "need_special_invoice": "Do you need a special ticket?", - "no": "no", - "no_invoice_record": "No bill record~", - "no_invoice_record_tip": "No invoicing record yet", - "order_number": "Order number", + "month": "Monthly", + "need_special_invoice": "Special VAT fapiao required", + "no": "No", + "no_invoice_record": "No data available.", + "no_invoice_record_tip": "No data available.", + "order_number": "Order ID", "order_type": "Order type", - "organization_name": "Organization name", + "organization_name": "Organization", "payment_method": "Payment method", "payway_coupon": "Redeem code", - "rerank": "Rerank", - "save": "save", - "save_failed": "Save exception", - "save_success": "Saved successfully", - "status": "state", - "sub_mode_custom": "Customize", - "submit_failed": "Submission failed", - "submit_success": "Submission successful", + "rerank": "Result reranking", + "generate_sql": "Generate SQL", + "save": "Save", + "save_failed": "Failed to save the settings.", + "save_success": "Saved successfully.", + "status": "Status", + "sub_mode_custom": "Custom", + "submit_failed": "Submission failed.", + "submit_success": "Submitted successfully.", "submitted": "Submitted", "subscription_mode_month": "Duration", - "subscription_package": "Subscription package", - "subscription_period": "Subscription cycle", + "subscription_package": "Subscription plan", + "subscription_period": "Periodic", "support_wallet_amount": "Amount", - "support_wallet_apply_invoice": "Billable bills", - "support_wallet_bill_tag_invoice": "bill invoice", - "support_wallet_invoicing": "Invoicing", - "time": "time", - "total_amount": "lump sum", - "type": "type", - "unit_code": "unified credit code", - "unit_code_void": "Unified credit code format error", - "update": "renew", - "yes": "yes", - "yuan": "¥{{amount}}" + "support_wallet_apply_invoice": "Invoiceable bill", + "support_wallet_bill_tag_invoice": "Fapiao", + "support_wallet_invoicing": "Issue fapiao", + "time": "Time", + "total_amount": "Total amount", + "type": "Type", + "unit_code": "Unified social credit code", + "unit_code_void": "Invalid unified social credit code.", + "update": "Update", + "yes": "Yes", + "yuan": "{{amount}} CNY" } diff --git a/packages/web/i18n/en/account_info.json b/packages/web/i18n/en/account_info.json index 15a520732bef..2610763fb529 100644 --- a/packages/web/i18n/en/account_info.json +++ b/packages/web/i18n/en/account_info.json @@ -1,82 +1,84 @@ { - "account_duplicate": "account", - "account_knowledge_base_cleanup_warning": "When the free version team does not log in to the system for 30 consecutive days, the system will automatically clean up the account knowledge base.", - "active": "Taking effect", - "ai_points": "AI points", - "ai_points_calculation_standard": "AI points", - "ai_points_usage": "AI points", - "ai_points_usage_tip": "Each time the AI ​​model is called, a certain amount of AI points will be consumed. \nFor specific calculation standards, please refer to the \"Billing Standards\" above.", - "app_amount": "App amount", - "avatar": "Avatar", - "avatar_selection_exception": "Abnormal avatar selection", - "balance": "balance", - "billing_standard": "Standards", + "account_duplicate": "Account", + "account_knowledge_base_cleanup_warning": "If a team using the free edition is inactive for 30 consecutive days, its knowledge bases will be cleared automatically.", + "active": "Active", + "ai_points": "Credits", + "ai_points_calculation_standard": "Credits", + "ai_points_usage": "Credit usage", + "ai_points_usage_tip": "Model calls consume credits. For details, refer to the billing standard above.", + "app_amount": "Apps", + "avatar": "Profile image", + "avatar_selection_exception": "Error occurred while uploading the profile image.", + "balance": "Balance", + "billing_standard": "Billing standard", "cancel": "Cancel", - "change": "change", - "choose_avatar": "Click to select avatar", - "click_modify_nickname": "Click to modify nickname", - "code_required": "Verification code cannot be empty", - "confirm": "confirm", - "confirm_password": "Confirm Password", - "contact_customer_service": "Contact customer service", - "contact_us": "Contact us", + "change": "Change", + "choose_avatar": "Click to upload a profile image.", + "click_modify_nickname": "Click to change the member name.", + "code_required": "Enter the verification code", + "confirm": "OK", + "confirm_password": "Confirm password", + "contact_customer_service": "Contact Customer Service", + "contact_us": "Contact Us", "current_package": "Current plan", - "current_token_price": "Current points price", - "dataset_amount": "Dataset amount", - "effective_time": "Effective time", - "email_label": "Mail", - "exchange": "Exchange", - "exchange_failure": "Redemption failed", - "exchange_success": "Redemption successful", + "current_token_price": "Current price for credit", + "dataset_amount": "Knowledge bases", + "effective_time": "Valid since", + "email_label": "Email", + "exchange": "Redeem", + "exchange_failure": "Failed", + "exchange_success": "Redeemed successfully.", "expiration_time": "Expiration time", "expired": "Expired", - "general_info": "General information", - "group": "Group", - "help_chatbot": "robot assistant", - "help_document": "Help documentation", - "knowledge_base_capacity": "Dataset usages", + "general_info": "Basics", + "group": "groups", + "help_chatbot": "Bot assistant", + "help_document": "Help", + "knowledge_base_capacity": "Knowledge base index capacity", "manage": "Manage", - "member_amount": "Member amount", - "member_name": "Name", - "month": "moon", - "new_password": "New Password", - "notification_receiving": "Notify", - "old_password": "Old Password", - "package_and_usage": "Plans", - "package_details": "Details", - "package_expiry_time": "Expired", - "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.", + "member_amount": "Members", + "member_name": "Member name", + "month": "Monthly", + "new_password": "New password", + "notification_receiving": "Notification recipient", + "old_password": "Current password", + "package_and_usage": "Plans and usage", + "package_details": "Plan details", + "package_expiry_time": "Expiration time", + "package_usage_rules": "Plan usage rules: Higher-level plans are used first. Unused lower-level plans will be applied later.", "password": "Password", - "password_mismatch": "Password Inconsistency: Two passwords are inconsistent", - "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", - "phone_label": "Phone number", - "please_bind_contact": "Please bind the contact information", - "please_bind_notification_receiving_path": "Please bind the notification receiving method first", - "purchase_extra_package": "Upgrade", - "redeem_coupon": "Redeem coupon", - "reminder_create_bound_notification_account": "Remind the creator to bind the notification account", - "reset_password": "reset password", - "resource_usage": "Usages", - "select_avatar": "Click to select avatar", - "standard_package_and_extra_resource_package": "Includes standard and extra plans", - "storage_capacity": "Storage capacity", - "team_balance": "Balance", - "team_info": "Team Information", - "token_validity_period": "Points are valid for one year", - "tokens": "integral", - "type": "type", + "password_mismatch": "Passwords do not match.", + "password_tip": "Must be at least 8 characters long and contain at least 2 of the following: digits, letters, and special characters.", + "password_update_error": "Error occurred while changing the password.", + "password_update_success": "Password changed successfully.", + "pending_usage": "Available", + "phone_label": "Mobile number", + "please_bind_contact": "Please specify a recipient.", + "please_bind_notification_receiving_path": "Please specify a recipient first.", + "purchase_extra_package": "Purchase additional plan", + "redeem_coupon": "Redeem code", + "reminder_create_bound_notification_account": "Remind the creator to specify an account to receive notifications.", + "reset_password": "Reset password", + "resource_usage": "Resource usage", + "select_avatar": "Click to upload a profile image.", + "standard_package_and_extra_resource_package": "Includes the standard plan and additional resource packages.", + "storage_capacity": "Max shards", + "team_balance": "Team balance", + "team_info": "Team", + "token_validity_period": "Credits are valid for 1 year.", + "tokens": "Credits", + "type": "Type", "unlimited": "Unlimited", "update_password": "Change password", - "update_success_tip": "Update data successfully", - "upgrade_package": "Upgrade", - "usage_balance": "Use balance: Use balance", - "usage_balance_notice": "Due to the system upgrade, the original \"automatic renewal and deduction from balance\" mode has been cancelled, and the balance recharge entrance has been closed. \nYour balance can be used to purchase points", - "user_account": "Username", + "update_success_tip": "Updated successfully.", + "upgrade_package": "Upgrade plan", + "usage_balance": "Payment method: Balance", + "usage_balance_notice": "Due to a system upgrade, renewal with auto reduction from balance has been disabled, and balance top-up is no longer available. Your balance can still be used to purchase credits.", + "user_account": "Account", "user_team_team_name": "Team", - "verification_code": "Verification code", - "you_can_convert": "you can redeem", - "yuan": "Yuan" + "verification_code": "Code", + "you_can_convert": "You can redeem", + "yuan": "CNY.", + "password_min_length": "Must contain at least 8 characters.", + "password_requirement": "Must contain at least 2 of the following: digits, letters, and special characters." } diff --git a/packages/web/i18n/en/account_inform.json b/packages/web/i18n/en/account_inform.json index d35bf41d18cd..e09a4fef26b1 100644 --- a/packages/web/i18n/en/account_inform.json +++ b/packages/web/i18n/en/account_inform.json @@ -1,7 +1,7 @@ { - "notification_detail": "notification details", - "no_notifications": "No notification yet", + "notification_detail": "Notification details", + "no_notifications": "No data available.", "read": "Read", - "system": "official", - "team": "team" + "system": "Official", + "team": "Team" } \ No newline at end of file diff --git a/packages/web/i18n/en/account_model.json b/packages/web/i18n/en/account_model.json index 7e03ece02371..48c6d89fdaa4 100644 --- a/packages/web/i18n/en/account_model.json +++ b/packages/web/i18n/en/account_model.json @@ -1,93 +1,96 @@ { "Hunyuan": "Tencent Hunyuan", - "aipoint_usage": "AI points", + "aipoint_usage": "Credits consumed", "all": "All", "api_key": "API key", - "avg_response_time": "Average call time (seconds)", - "avg_ttfb": "Average first word duration (seconds)", - "azure": "Azure", - "base_url": "Base url", + "avg_response_time": "Avg call duration (s)", + "avg_ttfb": "Avg TTFB (s)", + "azure": "Microsoft Azure", + "base_url": "Proxy address", "batch_size": "Number of concurrent requests", - "channel_name": "Channel", + "channel_name": "Channel name", "channel_priority": "Priority", - "channel_priority_tip": "The higher the priority channel, the easier it is to be requested", - "channel_status": "state", - "channel_status_auto_disabled": "Automatically disable", + "channel_priority_tip": "Channels with higher priority are more likely to be used.", + "channel_status": "Status", + "channel_status_auto_disabled": "Auto disabled", "channel_status_disabled": "Disabled", - "channel_status_enabled": "Enable", - "channel_status_unknown": "unknown", - "channel_type": "Protocol Type", - "clear_model": "Clear the model", - "confirm_delete_channel": "Confirm the deletion of the [{{name}}] channel?", - "copy_model_id_success": "Copyed model id", - "create_channel": "Added channels", - "dashboard_call_trend": "Model Call Trend", + "channel_status_enabled": "Enabled", + "channel_status_unknown": "Unknown", + "channel_type": "Protocol type", + "clear_model": "Clear", + "confirm_delete_channel": "Are you sure you want to delete the channel ({{name}})?", + "copy_model_id_success": "Model ID copied successfully.", + "create_channel": "Add channel", + "dashboard_call_trend": "Model call trend", "dashboard_channel": "Channel", - "dashboard_cost_trend": "Cost Consumption", - "dashboard_error_calls": "Error Calls", - "dashboard_input_tokens": "Input Tokens", + "dashboard_cost_trend": "Credits consumed", + "dashboard_error_calls": "Failed calls", + "dashboard_input_tokens": "Input tokens", "dashboard_model": "Model", - "dashboard_no_data": "No data available", - "dashboard_output_tokens": "Output Tokens", - "dashboard_points": "points", - "dashboard_success_calls": "Success Calls", - "dashboard_token_trend": "Token Usage Trend", - "dashboard_token_usage": "Tokens", - "dashboard_total_calls": "Total Calls:", - "dashboard_total_cost": "Total Cost", - "dashboard_total_cost_label": "Total Cost:", - "dashboard_total_tokens": "Total Tokens", + "dashboard_no_data": "No data available.", + "dashboard_output_tokens": "Output tokens", + "dashboard_points": "Credits", + "dashboard_success_calls": "Successful calls", + "dashboard_token_trend": "Token usage trend", + "dashboard_token_usage": "Tokens consumed", + "dashboard_total_calls": "Total calls:", + "dashboard_total_cost": "Total cost", + "dashboard_total_cost_label": "Total cost:", + "dashboard_total_tokens": "Total tokens", "default_url": "Default address", - "detail": "Detail", - "duration": "Duration", - "edit": "edit", - "edit_channel": "Channel configuration", + "detail": "Details", + "duration": "Time taken", + "edit": "Edit", + "add_channel": "Add channel", + "edit_channel": "Channel settings", "enable_channel": "Enable", - "forbid_channel": "Disabled", + "forbid_channel": "Disable", "input": "Input", "key_type": "API key format:", - "log": "Call log", + "log": "Logs", "log_detail": "Log details", - "log_request_id_search": "Search by requestId", + "log_request_id_search": "Request ID", "log_status": "Status", - "mapping": "Model Mapping", - "mapping_tip": "A valid Json is required. \nThe model can be mapped when sending a request to the actual address. \nFor example:\n{\n \n \"gpt-4o\": \"gpt-4o-test\"\n\n}\n\nWhen FastGPT requests the gpt-4o model, the gpt-4o-test model is sent to the actual address, instead of gpt-4o.", - "maxToken_tip": "Model max_tokens parameter", - "max_rpm": "Max RPM (Requests Per Minute)", - "max_temperature_tip": "If the model temperature parameter is not filled in, it means that the model does not support the temperature parameter.", - "max_tpm": "Max TPM (Tokens Per Minute)", + "mapping": "Model mapping", + "mapping_tip": "Enter a valid JSON object to map the model to the actual URL. Example:\n{\n \"gpt-4o\": \"gpt-4o-test\"\n}\nWhen FastGPT requests the gpt-4o model, the system sends gpt-4o-test to the actual URL instead of gpt-4o.", + "maxToken_tip": "max_tokens parameter", + "max_rpm": "Max RPM", + "max_temperature_tip": "Model temperature parameter. Leave it blank if not supported.", + "max_tpm": "Max TPM", "model": "Model", - "model_error_rate": "Error rate", - "model_error_request_times": "Number of failures", + "model_error_rate": "Failure rate", + "model_error_request_times": "Failed calls", "model_name": "Model name", - "model_request_times": "Request times", - "model_test": "Model testing", - "model_tokens": "Input/Output tokens", - "model_ttfb_time": "Response time of first word", + "model_request_times": "Requests", + "model_test": "Test model", + "model_tokens": "Input/output tokens", + "model_ttfb_time": "TTFB", "monitoring": "Monitoring", "output": "Output", "request_at": "Request time", "request_duration": "Request duration: {{duration}}s", - "retry_times": "Number of retry times", - "running_test": "In testing", - "search_model": "Search for models", - "select_channel": "Select a channel name", - "select_model": "Select a model", - "select_model_placeholder": "Select the model available under this channel", - "select_provider_placeholder": "Search protocol type", - "selected_model_empty": "Choose at least one model", - "start_test": "Batch test {{num}} models", - "test_failed": "There are {{num}} models that report errors", - "timespan_day": "Day", - "timespan_hour": "Hour", - "timespan_label": "Time Granularity", - "timespan_minute": "Minute", - "total_call_volume": "Request amount", - "use_in_eval": "Use in eval", + "retry_times": "Max attempts", + "running_test": "Testing", + "search_model": "Model ID", + "select_channel": "Select", + "select_model": "Select", + "select_model_placeholder": "Select models", + "select_provider_placeholder": "Protocol type", + "selected_model_empty": "Please select at least one model.", + "start_test": "Bulk test", + "test_failed": "{{num}} models encountered error.", + "timespan_day": "Daily", + "timespan_hour": "Hourly", + "timespan_label": "Time granularity", + "timespan_minute": "Minutes", + "total_call_volume": "Total calls", + "use_in_eval": "App evaluation", "view_chart": "Chart", - "view_table": "Table", - "vlm_model": "Vlm", - "vlm_model_tip": "Used to generate additional indexing of images in a document in the knowledge base", - "volunme_of_failed_calls": "Error amount", - "waiting_test": "Waiting for testing" + "view_table": "Form", + "vlm_model": "VLM", + "vlm_model_tip": "Generates additional indexes for images in documents within the knowledge base.", + "volunme_of_failed_calls": "Failed calls", + "waiting_test": "Waiting for test", + "evaluation_model": "Evaluation model", + "evaluation_model_tip": "Used to evaluate apps and quality of data in datasets." } diff --git a/packages/web/i18n/en/account_promotion.json b/packages/web/i18n/en/account_promotion.json index 6162052bfe17..8ca70b73a88d 100644 --- a/packages/web/i18n/en/account_promotion.json +++ b/packages/web/i18n/en/account_promotion.json @@ -1,13 +1,13 @@ { "amount": "Amount", - "cashback_ratio": "Cash back ratio", - "cashback_ratio_description": "When your friends recharge, you will receive a certain percentage of your balance as a reward.", + "cashback_ratio": "Cashback rate", + "cashback_ratio_description": "You will receive a balance reward based on your friend's top-up amount.", "copy_invite_link": "Copy invitation link", "earnings": "Income (¥)", "invite_url": "Invitation link", - "invite_url_tip": "Friends who register through this link will be permanently bound to you, and you will receive a certain balance reward when they recharge.\n \nIn addition, when your friends register using their mobile phone number, you will immediately receive a 5 yuan reward.\n \nRewards are sent to your default team.", - "no_invite_records": "No invitation record yet", - "time": "time", - "total_invited": "Cumulative number of invitees", - "type": "type" + "invite_url_tip": "Friends who register through this link will be permanently linked to your account. Each time they top up, you will receive a balance reward.\nIf they register with a mobile number, you will get an immediate 5 CNY bonus.\nAll rewards are credited to your default team account.", + "no_invite_records": "No data available.", + "time": "Time", + "total_invited": "Invitees", + "type": "Type" } \ No newline at end of file diff --git a/packages/web/i18n/en/account_setting.json b/packages/web/i18n/en/account_setting.json index 70faef92a7ac..751b6c1c57a7 100644 --- a/packages/web/i18n/en/account_setting.json +++ b/packages/web/i18n/en/account_setting.json @@ -1,6 +1,6 @@ { - "language": "language", - "personalization": "personalization", - "timezone": "time zone", - "update_data_success": "Update data successfully" + "language": "Language", + "personalization": "Personalization", + "timezone": "Time zone", + "update_data_success": "Updated successfully." } \ No newline at end of file diff --git a/packages/web/i18n/en/account_team.json b/packages/web/i18n/en/account_team.json index 5c8a62e9e762..8f3742eb9579 100644 --- a/packages/web/i18n/en/account_team.json +++ b/packages/web/i18n/en/account_team.json @@ -1,267 +1,335 @@ { - "1person": "1 person", - "1year": "1 Year", - "30mins": "30 Minutes", - "7days": "7 Days", - "accept": "accept", - "action": "operate", - "admin_add_plan": "Add a team package", - "admin_add_user": "Add a user", - "admin_change_license": "Change of license", - "admin_create_app_template": "Add a template", - "admin_create_plugin": "Add plugins", - "admin_create_plugin_group": "Create plugin grouping", - "admin_delete_app_template": "Delete the template", - "admin_delete_plugin": "Plugin Delete", - "admin_delete_plugin_group": "Delete plugin grouping", - "admin_delete_template_type": "Delete template classification", - "admin_finish_invoice": "Issuing an invoice", - "admin_login": "Administrator login", - "admin_save_template_type": "Update template classification", - "admin_send_system_inform": "Send system notifications", - "admin_update_app_template": "Update templates", - "admin_update_plan": "Editorial Team Package", - "admin_update_plugin": "Plugin Update", - "admin_update_plugin_group": "Plugin group update", - "admin_update_system_config": "System configuration update", - "admin_update_system_modal": "System announcement configuration", - "admin_update_team": "Edit team information", - "admin_update_user": "Edit User", - "assign_permission": "Permission change", - "audit_log": "audit", - "change_department_name": "Department Editor", - "change_member_name": "Member name change", + "1person": "1", + "1year": "1 year", + "30mins": "30 mins", + "7days": "7 days", + "accept": "Accept", + "action": "Operation", + "admin_add_plan": "Add team plan", + "admin_add_user": "Add user", + "admin_change_license": "Change license", + "admin_create_app_template": "Add template", + "admin_create_plugin": "Add plugin", + "admin_create_plugin_group": "Create plugin group", + "admin_delete_app_template": "Delete template", + "admin_delete_plugin": "Delete plugin", + "admin_delete_plugin_group": "Delete plugin group", + "admin_delete_template_type": "Delete template category", + "admin_finish_invoice": "Issue fapiao", + "admin_login": "Admin logs in", + "admin_save_template_type": "Update template category", + "admin_send_system_inform": "Send system notification", + "admin_update_app_template": "Update template", + "admin_update_plan": "Edit team plan", + "admin_update_plugin": "Update plugin", + "admin_update_plugin_group": "Update plugin group", + "admin_update_system_config": "Update system configuration", + "admin_update_system_modal": "Configure system announcement", + "admin_update_team": "Edit team", + "admin_update_user": "Edit user", + "assign_permission": "Change permissions", + "audit_log": "Audit", + "change_department_name": "Edit department", + "change_member_name": "Change member name", "change_member_name_self": "Change member name", - "change_notification_settings": "Change the way to receive notifications", - "change_password": "change password", - "confirm_delete_from_org": "Confirm to move {{username}} out of the department?", - "confirm_delete_from_team": "Confirm to move {{username}} out of the team?", - "confirm_delete_group": "Confirm to delete group?", - "confirm_delete_org": "Confirm to delete organization?", - "confirm_forbidden": "Confirm forbidden", - "confirm_leave_team": "Confirmed to leave the team? \nAfter exiting, all your resources in the team are transferred to the team owner.", + "change_notification_settings": "Change notification recipient", + "change_password": "Change password", + "confirm_delete_from_org": "Are you sure you want to remove the member ({{username}}) from the department?", + "confirm_delete_from_team": "Are you sure you want to remove the member ({{username}}) from the team?", + "confirm_delete_group": "Are you sure you want to delete the group?", + "confirm_delete_org": "Are you sure you want to delete the department?", + "confirm_forbidden": "Confirm", + "confirm_leave_team": "Are you sure you want to leave the team?\nAll your resources in this team will be transferred to the team owner.", "copy_link": "Copy link", "create_api_key": "Create API key", - "create_app": "Create an application", - "create_app_copy": "Create a copy of the application", - "create_app_folder": "Create an application folder", - "create_app_publish_channel": "Create a sharing channel", - "create_collection": "Create a collection", + "create_app": "Create app", + "create_app_copy": "Duplicate app", + "create_app_folder": "Create app folder", + "create_app_publish_channel": "Create publishing channel", + "create_collection": "Create collection", "create_data": "Insert data", - "create_dataset": "Create a knowledge base", - "create_dataset_folder": "Create a Knowledge Base Folder", - "create_department": "Create a sub-department", + "create_dataset": "Create knowledge base", + "create_dataset_folder": "Create knowledge base folder", + "create_department": "Create sub-department", + "create_evaluation_dataset_collection": "Create evaluation dataset collection", + "create_evaluation_dataset_data": "Create evaluation data", + "create_evaluation_task": "Create evaluation task", + "create_evaluation_metric": "Create evaluation metrics", "create_group": "Create group", - "create_invitation_link": "Create Invitation Link", - "create_invoice": "Issuing invoices", - "create_org": "Create organization", - "create_sub_org": "Create sub-organization", - "dataset.api_file": "API Import", - "dataset.common_dataset": "Dataset", - "dataset.external_file": "External File", - "dataset.feishu_dataset": "Feishu Spreadsheet", + "create_invitation_link": "Create invitation link", + "create_invoice": "Issue fapiao", + "create_org": "Create department", + "create_sub_org": "Create sub-department", + "dataset.api_file": "API import", + "dataset.common_dataset": "Knowledge base", + "dataset.external_file": "External file", + "dataset.feishu_dataset": "Feishu bitable", "dataset.folder_dataset": "Folder", - "dataset.website_dataset": "Website Sync", - "dataset.yuque_dataset": "Yuque Knowledge Base", - "delete": "delete", - "delete_api_key": "Delete the API key", - "delete_app": "Delete the workbench application", - "delete_app_collaborator": "App permissions delete", - "delete_app_publish_channel": "Delete the publishing channel", - "delete_collection": "Delete a collection", + "dataset.website_dataset": "Website sync", + "dataset.yuque_dataset": "Yuque", + "delete": "Delete", + "delete_api_key": "Delete API key", + "delete_app": "Delete app", + "delete_app_collaborator": "Delete app permissions", + "delete_app_publish_channel": "Delete publishing channel", + "delete_collection": "Delete collection", "delete_data": "Delete data", - "delete_dataset": "Delete the knowledge base", - "delete_dataset_collaborator": "Knowledge Base Permission Deletion", + "delete_dataset": "Delete knowledge base", + "delete_dataset_collaborator": "Delete knowledge base permissions", "delete_department": "Delete sub-department", - "delete_evaluation": "Delete application review data", - "delete_from_org": "Move out of department", - "delete_from_team": "Move out of the team", - "delete_group": "Delete a group", - "delete_org": "Delete organization", - "department": "department", - "edit_info": "Edit information", + "delete_evaluation": "Delete app evaluation data", + "delete_evaluation_dataset_collection": "Delete evaluation dataset collection", + "delete_evaluation_dataset_data": "Delete evaluation data", + "delete_evaluation_dataset_task": "Delete evaluation dataset task", + "delete_evaluation_task": "Delete evaluation task", + "delete_evaluation_task_item": "Delete evaluation task case", + "delete_evaluation_task_data_item": "Delete evaluation cases", + "update_evaluation_task_data_item": "Update evaluation cases", + "retry_evaluation_task_data_item": "Retry evaluation cases", + "export_evaluation_task_data_items": "Export evaluation cases", + "delete_evaluation_metric": "Delete evaluation metrics", + "delete_from_org": "Remove member from department", + "delete_from_team": "Remove member from team", + "delete_group": "Delete group", + "delete_org": "Delete department", + "department": "Department", + "edit_info": "Edit", "edit_member": "Edit user", - "edit_member_tip": "Name", - "edit_org_info": "Edit organization information", + "edit_member_tip": "Member name", + "edit_org_info": "Edit department", "expires": "Expiration time", - "export_app_chat_log": "Export the app chat history", - "export_bill_records": "Export billing history", + "export_app_chat_log": "Export chat history", + "export_bill_records": "Export billing records", "export_dataset": "Export knowledge base", + "export_evaluation_task_items": "Export all evaluation cases in an evaluation task", + "generate_evaluation_summary": "Generate summary report", + "update_evaluation_summary_config": "Update summary settings", "export_members": "Export members", - "forbid_hint": "After forbidden, this invitation link will become invalid. This action is irreversible. Are you sure you want to deactivate?", - "forbid_success": "Forbid success", - "forbidden": "Forbidden", - "group": "group", + "forbid_hint": "This operation will invalidate the invitation link, and cannot be undone. Would you like to proceed?", + "forbid_success": "Disabled successfully.", + "forbidden": "Disable", + "group": "Groups", "group_name": "Group name", - "handle_invitation": "Handle Invitation", - "has_forbidden": "Forbidden", + "handle_invitation": "Team invitations", + "has_forbidden": "Expired", + "import_evaluation_dataset_data": "Import evaluation data", "has_invited": "Invited", - "ignore": "Ignore", - "inform_level_common": "Normal", - "inform_level_emergency": "Emergency", - "inform_level_important": "Important", - "invitation_copy_link": "[{{systemName}}] {{userName}} invites you to join the {{teamName}} team, link: {{url}}", - "invitation_link_auto_clean_hint": "Expired links will be automatically cleaned up after 30 days", - "invitation_link_description": "Link description", - "invitation_link_list": "Invitation link list", - "invite_member": "Invite members", + "ignore": "Ignored", + "inform_level_common": "Low", + "inform_level_emergency": "High", + "inform_level_important": "Medium", + "invitation_copy_link": "[{{systemName}}] {{userName}} invited you to join the team ({{teamName}}). Click the following link to join: {{url}}", + "invitation_link_auto_clean_hint": "Expired links will be automatically cleared 30 days later.", + "invitation_link_description": "Description", + "invitation_link_list": "Links", + "invite_member": "Invite member", "invited": "Invited", - "join_team": "Join the team", - "join_update_time": "Join/Update Time", - "kick_out_team": "Remove members", - "label_sync": "Tag sync", - "leave": "Resigned", - "leave_team_failed": "Leaving the team exception", - "log_admin_add_plan": "【{{name}}】A package will be added to a team with a team id [{{teamId}}]", - "log_admin_add_user": "【{{name}}】Create a user named [{{userName}}]", - "log_admin_change_license": "【{{name}}】Changed License", - "log_admin_create_app_template": "【{{name}}】Added a template named [{{templateName}}]", - "log_admin_create_plugin": "【{{name}}】Added plugin named [{{pluginName}}]", - "log_admin_create_plugin_group": "【{{name}}】Create a plug-in group called [{{groupName}}]", - "log_admin_delete_app_template": "【{{name}}】Deleted the template named [{{templateName}}]", - "log_admin_delete_plugin": "【{{name}}】Remove plugin named [{{pluginName}}]", - "log_admin_delete_plugin_group": "【{{name}}】Deleted plug-in grouping named [{{groupName}}]", - "log_admin_delete_template_type": "【{{name}}】Deleted the template classification named [{{typeName}}]", - "log_admin_finish_invoice": "【{{name}}】Issued an invoice to a team named [{{teamName}}]", - "log_admin_login": "【{{name}}】Logined in the administrator background", - "log_admin_save_template_type": "【{{name}}】Added template classification called [{{typeName}}]", - "log_admin_send_system_inform": "【{{name}}】Sent a system notification titled [{{informTitle}}], with the level of [{{level}}]", - "log_admin_update_app_template": "【{{name}}】Updated template information named [{{templateName}}]", - "log_admin_update_plan": "【{{name}}】Edited the package information of the team with the team id [{{teamId}}]", - "log_admin_update_plugin": "【{{name}}】Updated plugin information called [{{pluginName}}]", - "log_admin_update_plugin_group": "【{{name}}】Updated plug-in grouping called [{{groupName}}]", - "log_admin_update_system_config": "【{{name}}】Updated system configuration", - "log_admin_update_system_modal": "【{{name}}】The system announcement configuration was carried out", - "log_admin_update_team": "[{{name}}] Replace the team editing information named [{{teamName}}] to the team name: [{{newTeamName}}], balance: [{{newBalance}}]", - "log_admin_update_user": "Modify the user information of 【{{userName}}】", - "log_assign_permission": "[{{name}}] Updated the permissions of [{{objectName}}]: [Application creation: [{{appCreate}}], Knowledge Base: [{{datasetCreate}}], API Key: [{{apiKeyCreate}}], Management: [{{manage}}]]", - "log_change_department": "【{{name}}】Updated department【{{departmentName}}】", - "log_change_member_name": "【{{name}}】Rename member [{{memberName}}] to 【{{newName}}】", - "log_change_member_name_self": "【{{name}}】Change your member name to 【{{newName}}】", - "log_change_notification_settings": "【{{name}}】A change notification receiving method operation was carried out", - "log_change_password": "【{{name}}】The password change operation was performed", - "log_create_api_key": "【{{name}}】Create an API key named [{{keyName}}]", - "log_create_app": "【{{name}}】Created [{{appType}}] named [{{appName}}]", - "log_create_app_copy": "【{{name}}] Created a copy of [{{appType}}] named [{{appName}}]", - "log_create_app_folder": "【{{name}}】Create a folder named [{{folderName}}]", - "log_create_app_publish_channel": "[{{name}}] Created a channel named [{{channelName}}] for [{{appType}}] called [{{appName}}].", - "log_create_collection": "[{{name}}] Create a collection named [{{collectionName}}] in [{{datasetType}}] called [{{datasetName}}].", - "log_create_data": "[{{name}}] Insert data into a collection named [{{datasetName}}] in [{{datasetType}}] called [{{datasetName}}] into a collection named [{{collectionName}}]", - "log_create_dataset": "【{{name}}】Created 【{{datasetType}}】 named 【{{datasetName}}】", - "log_create_dataset_folder": "【{{name}}】Created a folder named {{folderName}}】", - "log_create_department": "【{{name}}】Department【{{departmentName}}】", - "log_create_group": "【{{name}}】Created group [{{groupName}}]", - "log_create_invitation_link": "【{{name}}】Created invitation link【{{link}}】", - "log_create_invoice": "【{{name}}】Invoice operation was carried out", - "log_delete_api_key": "【{{name}}】Deleted the API key named [{{keyName}}]", - "log_delete_app": "【{{name}}】Delete the [{{appType}}] named [{{appName}}]", - "log_delete_app_collaborator": "【{{name}}】Delete the [itemName] permission named [itemValueName] in [{{appType}}] named [{{appName}}] delete the [itemName] permission named [{{appName}}] named [{{appName}}] named [{{appName}}] deleted the [{{itemName}}] permission named [{{itemValueName}}] named [{{appType}}] named [{{appName}}].", - "log_delete_app_publish_channel": "[{{name}}] [{{appType}}] named [{{appName}}] deleted the channel named [{{channelName}}]", - "log_delete_collection": "[{{name}}] Deleted a collection named [{{collectionName}}] in [{{datasetType}}] named [{{datasetName}}].", - "log_delete_data": "[{{name}}] Delete data in a collection named [{{datasetName}}] in a collection named [{{datasetName}}]", - "log_delete_dataset": "【{{name}}】Deleted 【{{datasetType}}】 named [{{datasetName}}]", - "log_delete_dataset_collaborator": "【{{name}}】Updated the collaborators of 【{{appType}}】 named 【{{appName}}】 to: Organization: 【{{orgList}}】, Group: 【{{groupList}}】, Member 【{{tmbList}}】; updated the permissions to: Read permission: 【{{readPermission}}】, Write permission: 【{{writePermission}}】, Administrator permission: 【{{managePermission}}】", - "log_delete_department": "{{name}} deleted department {{departmentName}}", - "log_delete_evaluation": "【{{name}}】Deleted the evaluation data of [{{appType}}] named [{{appName}}]", - "log_delete_group": "{{name}} deleted group {{groupName}}", + "join_team": "Join team", + "join_update_time": "Time joined/updated", + "kick_out_team": "Remove member", + "label_sync": "Sync tag", + "leave": "Left", + "leave_team_failed": "Error occurred while leaving the team.", + "log_admin_add_plan": "{{name}} added a plan for the team ({{teamId}}).", + "log_admin_add_user": "{{name}} created the user ({{userName}}).", + "log_admin_change_license": "{{name}} changed the license.", + "log_admin_create_app_template": "{{name}} added the template ({{templateName}}).", + "log_admin_create_plugin": "{{name}} added the plugin ({{pluginName}}).", + "log_admin_create_plugin_group": "{{name}} created the plugin group ({{groupName}}).", + "log_admin_delete_app_template": "{{name}} deleted the template ({{templateName}}).", + "log_admin_delete_plugin": "{{name}} deleted the plugin ({{pluginName}}).", + "log_admin_delete_plugin_group": "{{name}} deleted the plugin group ({{groupName}}).", + "log_admin_delete_template_type": "{{name}} deleted the template category ({{typeName}}).", + "log_admin_finish_invoice": "{{name}} issued a fapiao for the team ({{teamName}}).", + "log_admin_login": "{{name}} logged in to the admin platform.", + "log_admin_save_template_type": "{{name}} added the template category ({{typeName}}).", + "log_admin_send_system_inform": "{{name}} sent a system notification titled {{informTitle}} with the {{level}} level.", + "log_admin_update_app_template": "{{name}} updated the template ({{templateName}}).", + "log_admin_update_plan": "{{name}} edited plan information for the team (ID: {{teamId}}).", + "log_admin_update_plugin": "{{name}} updated the plugin ({{pluginName}}).", + "log_admin_update_plugin_group": "{{name}} updated the plugin group ({{groupName}}).", + "log_admin_update_system_config": "{{name}} updated system configuration.", + "log_admin_update_system_modal": "{{name}} configured a system announcement.", + "log_admin_update_team": "{{name}} edited the information (Name: {{newTeamName}}, Balance: {{newBalance}}) of the team ({{teamName}}).", + "log_admin_update_user": "Edited the user ({{userName}}).", + "log_assign_permission": "{{name}} updated the {{objectName}} permissions (App creation: {{appCreate}}, Knowledge base creation: {{datasetCreate}}, API key creation: {{apiKeyCreate}}, Administrator: {{manage}}).", + "log_change_department": "{{name}} updated the department ({{departmentName}}).", + "log_change_member_name": "{{name}} changed the member name from {{memberName}} to {{newName}}.", + "log_change_member_name_self": "{{name}} changed his/her own name from {{oldName}} to {{newName}}.", + "log_change_notification_settings": "{{name}} changed the notification recipient.", + "log_change_password": "{{name}} changed the password.", + "log_create_api_key": "{{name}} created the API key ({{keyName}}).", + "log_create_app": "{{name}} created the {{appType}} ({{appName}}).", + "log_create_app_copy": "{{name}} duplicated the {{appType}} ({{appName}}).", + "log_create_app_folder": "{{name}} created the folder ({{folderName}}).", + "log_create_app_publish_channel": "{{name}} created the channel ({{channelName}}) for the {{appType}} ({{appName}}).", + "log_create_collection": "{{name}} created the collection ({{collectionName}}) in the {{datasetType}} ({{datasetName}}).", + "log_create_data": "{{name}} inserted data into the collection ({{collectionName}}) in the {{datasetType}} ({{datasetName}}).", + "log_create_dataset": "{{name}} create the {{datasetType}} ({{datasetName}}).", + "log_create_dataset_folder": "{{name}} created the folder ({{folderName}}).", + "log_create_department": "{{name}} created the department ({{departmentName}}).", + "log_create_evaluation_dataset_collection": "{{name}} created the evaluation dataset ({{collectionName}}).", + "log_create_evaluation_dataset_data": "{{name}} created evaluation data in the evaluation dataset ({{collectionName}}).", + "log_create_evaluation_task": "{{name}} created the evaluation task ({{taskName}}).", + "log_create_evaluation_metric": "{{name}} created the evaluation metric ({{metricName}}).", + "log_create_group": "{{name}} created the group ({{groupName}}).", + "log_create_invitation_link": "{{name}} created the invitation link ({{link}}).", + "log_create_invoice": "{{name}} issued a fapiao.", + "log_delete_api_key": "{{name}} deleted the API key ({{keyName}}).", + "log_delete_app": "{{name}} deleted the {{appType}} ({{appName}}).", + "log_delete_app_collaborator": "{{name}} deleted the {{itemName}} ({{itemValueName}}) permission on the {{appType}} ({{appName}}).", + "log_delete_app_publish_channel": "{{name}} deleted the channel ({{channelName}}) for the {{appType}} ({{appName}}).", + "log_delete_collection": "{{name}} deleted the collection ({{collectionName}}) from the {{datasetType}} ({{datasetName}}).", + "log_delete_data": "{{name}} deleted data from the collection ({{collectionName}}) in the {{datasetType}} ({{datasetName}}).", + "log_delete_dataset": "{{name}} deleted the {{datasetType}} ({{datasetName}}).", + "log_delete_dataset_collaborator": "{{name}} deleted the {{itemName}} ({{itemValueName}}) permission on the {{datasetType}} ({{datasetName}}).", + "log_delete_department": "{{name}} deleted the department ({{departmentName}}).", + "log_delete_evaluation": "{{name}} deleted evaluation data of the {{appType}} ({{appName}}).", + "log_delete_group": "{{name}} deleted the group ({{groupName}}).", + "log_delete_evaluation_dataset_collection": "{{name}} deleted the evaluation dataset ({{collectionName}}).", + "log_delete_evaluation_dataset_data": "{{name}} deleted evaluation data in the evaluation dataset ({{collectionName}}).", + "log_delete_evaluation_dataset_task": "{{name}} deleted a task for the evaluation dataset ({{collectionName}}).", + "log_delete_evaluation_task": "{{name}} deleted the evaluation task ({{taskName}}).", + "log_delete_evaluation_task_item": "{{name}} deleted the case ({{itemId}}) in the evaluation task ({{taskName}}).", + "log_delete_evaluation_metric": "{{name}} deleted the evaluation metric ({{metricName}}).", "log_details": "Details", - "log_export_app_chat_log": "【{{name}}】Export a chat history called [{{appName}}] called [{{appType}}]", - "log_export_bill_records": "【{{name}}】Export the billing record", - "log_export_dataset": "[{{name}}] Export [{{datasetType}}] called [{{datasetName}}]", - "log_join_team": "【{{name}}】Join the team through the invitation link 【{{link}}】", - "log_kick_out_team": "{{name}} removed member {{memberName}}", - "log_login": "【{{name}}】Logined in the system", - "log_move_app": "【{{name}}】Move [{{appType}}] named [{{appName}}] to [{{targetFolderName}}]", - "log_move_dataset": "【{{name}}】Move [{{datasetType}}] named [{{datasetName}}] to [{{targetFolderName}}]", - "log_purchase_plan": "【{{name}}】Purchased the set meal", - "log_recover_team_member": "【{{name}}】Restored member【{{memberName}}】", - "log_relocate_department": "【{{name}}】Displayed department【{{departmentName}}】", - "log_retrain_collection": "[{{name}}] Retrained the collection named [{{collectionName}}] in [{{datasetType}}] called [{{datasetName}}].", - "log_search_test": "【{{name}}】Perform a search test operation on [{{datasetType}}] named [{{datasetName}}]", - "log_set_invoice_header": "【{{name}}】The invoice header operation was set up", - "log_time": "Operation time", - "log_transfer_app_ownership": "【{{name}}] Transfer ownership of [{{appType}}] named [{{appName}}] from [{oldOwnerName}}] to [{{newOwnerName}}]", - "log_transfer_dataset_ownership": "[{{name}}] Transfer ownership of [{{datasetType}}] named [{{datasetName}}] from [{oldOwnerName}}] to [{{newOwnerName}}]", - "log_type": "Operation Type", - "log_update_api_key": "【{{name}}】Updated the API key named [{{keyName}}]", - "log_update_app_collaborator": "[{{name}}] Updated the collaborator named [{{appName}}] to: Organization: [{{orgList}}], Group: [{{groupList}}], Member [{{tmbList}}]; permissions updated to: Read permission: [{{readPermission}}], Write permission: [{{writePermission}}], Administrator permission: [{{managePermission}}]", - "log_update_app_info": "[{{name}}] updated [{{appType}}] named [{{appName}}]: [{{newItemNames}}] to [{{newItemValues}}]", - "log_update_app_publish_channel": "[{{name}}] Updated a channel named [{{channelName}}] for [{{appType}}] called [{{appName}}].", - "log_update_collection": "[{{name}}] Updated a collection named [{{collectionName}}] in [{{datasetType}}] called [{{datasetName}}].", - "log_update_data": "【{{name}}】Update data in a collection named 【{{datasetName}}】[{{datasetType}}] with [{{datasetType}}] with [{{collectionName}}]", - "log_update_dataset": "【{{name}}】Updated [{{datasetType}}] named [{{datasetName}}]", - "log_update_dataset_collaborator": "[{{name}}] Updated the collaborator named [{{datasetName}}] to: Organization: [{{orgList}}], Group: [{{groupList}}], Member [{{tmbList}}]; permissions updated to: [{{readPermission}}], [{{writePermission}}], [{{managePermission}}]", - "log_update_publish_app": "【{{name}}】【{{operationName}}】【{{appType}}】 named [{{appName}}】", + "log_export_app_chat_log": "{{name}} exported the chat history of the {{appType}} ({{appName}}).", + "log_export_bill_records": "{{name}} exported the billing records.", + "log_export_dataset": "{{name}} exported the {{datasetType}} ({{datasetName}}).", + "log_generate_evaluation_summary": "{{name}} generated a summary report for the metric ({{metricName}}) in the evaluation task ({{evalName}}).", + "log_update_evaluation_summary_config": "{{name}} updated the summary settings of the evaluation task ({{evalName}}).", + "log_import_evaluation_dataset_data": "{{name}} imported {{recordCount}} entries into the evaluation dataset ({{collectionName}}).", + "log_export_evaluation_task_items": "{{name}} exported {{itemCount}} cases from the evaluation task ({{taskName}}) as {{format}}.", + "log_delete_evaluation_task_data_item": "{{name}} deleted the case ({{dataItemId}}) in the evaluation task ({{taskName}}).", + "log_update_evaluation_task_data_item": "{{name}} updated the case ({{dataItemId}}) in the evaluation task ({{taskName}}).", + "log_retry_evaluation_task_data_item": "{{name}} retried the case ({{dataItemId}}) in the evaluation task ({{taskName}}).", + "log_export_evaluation_task_data_items": "{{name}} exported {{itemCount}} cases from the evaluation task ({{taskName}}) as {{format}}.", + "log_join_team": "{{name}} joined the team via the invitation link ({{link}}).", + "log_kick_out_team": "{{name}} removed the member ({{memberName}}).", + "log_login": "{{name}} logged into the system.", + "log_move_app": "{{name}} moved the {{appType}} ({{appName}}) to the folder ({{targetFolderName}}).", + "log_move_dataset": "{{name}} moved the {{datasetType}} ({{datasetName}}) to the folder ({{targetFolderName}}).", + "log_purchase_plan": "{{name}} purchased a plan.", + "log_quality_assessment_evaluation_data": "{{name}} evaluated the quality of data in the evaluation dataset ({{collectionName}}).", + "log_recover_team_member": "{{name}} restored the member ({{memberName}}).", + "log_relocate_department": "{{name}} moved the department ({{departmentName}}).", + "log_retrain_collection": "{{name}} retrained the collection ({{collectionName}}) in the {{datasetType}} ({{datasetName}}).", + "log_retry_evaluation_dataset_task": "{{name}} retried a task in the evaluation dataset ({{collectionName}}).", + "log_retry_evaluation_task": "{{name}} retried {{retryCount}} cases in the evaluation task ({{taskName}}).", + "log_retry_evaluation_task_item": "{{name}} retried the case ({{itemId}}) in the evaluation task ({{taskName}}).", + "log_search_test": "{{name}} performed a search test for the {{datasetType}} ({{datasetName}}).", + "log_smart_generate_evaluation_data": "{{name}} auto-generated evaluation data for the evaluation dataset ({{collectionName}}).", + "log_set_invoice_header": "{{name}} set the fapiao title.", + "log_time": "Time", + "log_transfer_app_ownership": "{{name}} transferred ownership of the {{appType}} ({{appName}}) from {{oldOwnerName}} to {{newOwnerName}}.", + "log_transfer_dataset_ownership": "{{name}} transferred ownership of the {{datasetType}} ({{datasetName}}) from {{oldOwnerName}} to {{newOwnerName}}.", + "log_type": "Operation", + "log_update_api_key": "{{name}} updated the API key ({{keyName}}).", + "log_update_app_collaborator": "{{name}} updated the {{appType}} ({{appName}}) collaborator (Department: {{orgList}}, Group: {{groupList}}, Member: {{tmbList}}) permissions (Read: {{readPermission}}, Write: {{writePermission}}, Admin: {{managePermission}}).", + "log_update_app_info": "{{name}} updated the {{newItemNames}} of the {{appType}} ({{appName}}) to {{newItemValues}}.", + "log_update_app_publish_channel": "{{name}} updated the channel ({{channelName}}) for the {{appType}} ({{appName}}).", + "log_update_collection": "{{name}} updated the collection {{collectionName}} in the {{datasetType}} ({{datasetName}}).", + "log_update_data": "{{name}} updated data in the collection {{collectionName}} in the {{datasetType}} ({{datasetName}}).", + "log_update_dataset": "{{name}} updated the {{datasetType}} ({{datasetName}}).", + "log_update_dataset_collaborator": "{{name}} updated the {{datasetType}} ({{datasetName}}) collaborator (Department: {{orgList}}, Group: {{groupList}}, Member: {{tmbList}}) permissions ({{readPermission}}, {{writePermission}}, {{managePermission}}).", + "log_update_evaluation_dataset_collection": "{{name}} updated the evaluation dataset ({{collectionName}}).", + "log_update_evaluation_dataset_data": "{{name}} updated evaluation data in the evaluation dataset ({{collectionName}}).", + "log_update_evaluation_task": "{{name}} updated the evaluation task ({{taskName}}).", + "log_update_evaluation_task_item": "{{name}} updated the case ({{itemId}}) in the evaluation task ({{taskName}}).", + "log_start_evaluation_task": "{{name}} started the evaluation task ({{taskName}}).", + "log_stop_evaluation_task": "{{name}} stopped the evaluation task ({{taskName}}).", + "log_update_evaluation_metric": "{{name}} updated the evaluation metric ({{metricName}}).", + "log_debug_evaluation_metric": "{{name}} debugged the evaluation metric ({{metricName}}).", + "log_update_publish_app": "{{name}} performed the operation ({{operationName}}) on the {{appType}} ({{appName}}).", "log_user": "Operator", "login": "Log in", - "manage_member": "Managing members", - "member": "member", - "member_group": "Belonging to member group", - "move_app": "App location movement", - "move_dataset": "Mobile Knowledge Base", + "manage_member": "Members", + "member": "Members", + "member_group": "Group", + "move_app": "Move app", + "move_dataset": "Move knowledge base", "move_member": "Move member", - "move_org": "Move organization", - "notification_recieve": "Team notification reception", - "org": "organization", - "org_description": "Organization description", - "org_name": "Organization name", - "owner": "owner", + "move_org": "Move department", + "notification_recieve": "Notification recipient", + "org": "Departments", + "org_description": "Description", + "org_name": "Department name", + "owner": "Owner", "permission": "Permissions", - "permission_apikeyCreate": "Create API Key", - "permission_apikeyCreate_Tip": "You can create global APIKey and MCP services", - "permission_appCreate": "Create Application", - "permission_appCreate_tip": "Can create applications in the root directory (creation permissions in folders are controlled by the folder)", - "permission_datasetCreate": "Create Knowledge Base", - "permission_datasetCreate_Tip": "Can create knowledge bases in the root directory (creation permissions in folders are controlled by the folder)", + "permission_apikeyCreate": "Create API key", + "permission_apikeyCreate_Tip": "Create global API keys and MCP services.", + "permission_appCreate": "Create app", + "permission_appCreate_tip": "Create apps in the root directory but not in folders.", + "permission_datasetCreate": "Create knowledge base", + "permission_datasetCreate_Tip": "Create knowledge bases in the root directory but not in folders", "permission_manage": "Admin", - "permission_manage_tip": "Can manage members, create groups, manage all groups, and assign permissions to groups and members", - "please_bind_contact": "Please bind the contact information", - "purchase_plan": "Upgrade package", - "recover_team_member": "Member Recovery", - "relocate_department": "Department Mobile", - "remark": "remark", - "remove_tip": "Confirm to remove {{username}} from the team?", - "restore_tip": "Confirm to join the team {{username}}? \nOnly the availability and related permissions of this member account are restored, and the resources under the account cannot be restored.", - "restore_tip_title": "Recovery confirmation", - "retain_admin_permissions": "Keep administrator rights", - "retrain_collection": "Retrain the set", - "save_and_publish": "save and publish", - "search_log": "Search log", - "search_member": "Search for members", - "search_member_group_name": "Search member/group name", - "search_org": "Search Department", - "search_test": "Search Test", - "set_invoice_header": "Set up invoice header", - "set_name_avatar": "Team avatar", - "sync_immediately": "Synchronize now", - "sync_member_failed": "Synchronization of members failed", - "sync_member_success": "Synchronize members successfully", - "total_team_members": "Total {{amount}} members", + "permission_evaluationCreate_Tip": "Create evaluation tasks, metrics, and datasets.", + "permission_manage_tip": "Manage members, create and manage groups, and assign permissions to groups and members.", + "please_bind_contact": "Please specify a recipient.", + "purchase_plan": "Upgrade plan", + "quality_assessment_evaluation_data": "Quality evaluation data", + "recover_team_member": "Restore member", + "relocate_department": "Move department", + "retry_evaluation_dataset_task": "Retry evaluation dataset task", + "retry_evaluation_task": "Retry evaluation task", + "retry_evaluation_task_item": "Retry evaluation task case", + "remark": "Remarks", + "remove_tip": "Are you sure you want to remove the member ({{username}}) from the team? The member will be marked as Left, their operation data will not be deleted, but account resources will be automatically transferred to the team owner.", + "restore_tip": "Are you sure you want to add the member ({{username}}) to the team? The member's account and related permissions will be restored, but the account resources cannot be recovered.", + "restore_tip_title": "Confirm", + "retain_admin_permissions": "Retain admin permissions", + "retrain_collection": "Retrain collection", + "save_and_publish": "Publish", + "search_log": "Log", + "search_member": "Member", + "search_member_group_name": "Member name, group name", + "search_org": "Department", + "search_test": "Test", + "set_invoice_header": "Set fapiao title", + "set_name_avatar": "Team avatar & name", + "smart_generate_evaluation_data": "Auto generate evaluation data", + "sync_immediately": "Sync now", + "sync_member_failed": "Failed to sync members.", + "sync_member_success": "Members synced successfully.", + "total_team_members": "Total members: {{amount}}", "transfer_app_ownership": "Transfer app ownership", - "transfer_dataset_ownership": "Transfer dataset ownership", + "transfer_dataset_ownership": "Transfer knowledge base ownership", "transfer_ownership": "Transfer ownership", - "type.Folder": "Folder", - "type.Http plugin": "HTTP Plugin", - "type.Plugin": "Plugin", - "type.Simple bot": "Simple App", - "type.Tool": "Tool", - "type.Tool set": "Toolset", - "type.Workflow bot": "Workflow", + "type.Folder": "folder", + "type.Http plugin": "HTTP plugin", + "type.Plugin": "plugin", + "type.Simple bot": "simple app", + "type.Tool": "tool", + "type.Tool set": "toolkit", + "type.Workflow bot": "workflow", "unlimited": "Unlimited", - "update": "update", + "update": "Update", "update_api_key": "Update API key", - "update_app_collaborator": "Apply permission changes", - "update_app_info": "Application information modification", - "update_app_publish_channel": "Update the release channel", - "update_collection": "Update the collection", + "update_app_collaborator": "Change app permissions", + "update_app_info": "Edit app", + "update_app_publish_channel": "Update publishing channel", + "update_collection": "Update collection", "update_data": "Update data", - "update_dataset": "Update the knowledge base", - "update_dataset_collaborator": "Knowledge Base Permission Changes", - "update_publish_app": "Application update", - "used_times_limit": "Limit", - "user_name": "username", - "user_team_invite_member": "Invite members", - "user_team_leave_team": "Leave the team", - "user_team_leave_team_failed": "Failure to leave the team", - "waiting": "To be accepted" -} + "update_dataset": "Update knowledge base", + "update_dataset_collaborator": "Change knowledge base permissions", + "update_evaluation_dataset_collection": "Update evaluation dataset collection", + "update_evaluation_dataset_data": "Update evaluation data", + "update_evaluation_task": "Update evaluation task", + "update_evaluation_task_item": "Update evaluation task case", + "start_evaluation_task": "Start evaluation task", + "stop_evaluation_task": "Stop evaluation task", + "update_evaluation_metric": "Update evaluation metrics", + "debug_evaluation_metric": "Debug evaluation metrics", + "update_publish_app": "Update app", + "used_times_limit": "Invitee limit", + "user_name": "Username", + "user_team_invite_member": "Invite member", + "user_team_leave_team": "Leave team", + "user_team_leave_team_failed": "Failed to leave the team.", + "waiting": "Pending", + "create_evaluation": "Create app evaluation", + "export_evaluation": "Export app evaluation data", + "log_create_evaluation": "{{name}} created an evaluation task for the {{appType}} ({{appName}}).", + "log_export_evaluation": "{{name}} exported the evaluation data of the {{appType}} ({{appName}}).", + "permission_evaluationCreate": "Create evaluation" +} \ No newline at end of file diff --git a/packages/web/i18n/en/account_thirdParty.json b/packages/web/i18n/en/account_thirdParty.json index a0b7dc46d141..39ed8fed0b52 100644 --- a/packages/web/i18n/en/account_thirdParty.json +++ b/packages/web/i18n/en/account_thirdParty.json @@ -1,20 +1,20 @@ { "configured": "Configured", - "error.no_permission": "Please contact the administrator to configure", - "get_usage_failed": "Failed to get usage", - "laf_account": "laf account", - "no_intro": "No explanation yet", + "error.no_permission": "Please contact the administrator.", + "get_usage_failed": "Failed to obtain usage.", + "laf_account": "LAF account", + "no_intro": "No data available.", "not_configured": "Not configured", - "open_api_notice": "You can fill in the relevant key of OpenAI/OneAPI. \nIf you fill in this content, the online platform using [AI Dialogue], [Problem Classification] and [Content Extraction] will use the Key you filled in, and there will be no charge. \nPlease pay attention to whether your Key has permission to access the corresponding model. \nGPT models can choose FastAI.", + "open_api_notice": "Enter an OpenAI/OneAPI key. The key will be used for AI chats, question classification, and content extraction without charges. Make sure the key can be used to access the corresponding models. You can choose FastAI as the GPT model.", "openai_account_configuration": "OpenAI/OneAPI account", - "openai_account_setting_exception": "Setting up an exception to OpenAI account", - "request_address_notice": "Request address, default is openai official. \nThe forwarding address can be filled in, but \\\"v1\\\" is not automatically completed.", - "third_party_account": "Third-party account", + "openai_account_setting_exception": "Error occurred while configuring the OpenAI account.", + "request_address_notice": "Request address. Default: official OpenAI address You can enter a proxy address. Note that \"/v1\" will not be automatically appended to the address.", + "third_party_account": "Third-party accounts", "third_party_account.configured": "Configured", "third_party_account.not_configured": "Not configured", - "third_party_account_desc": "The administrator can configure third-party accounts or variables here, and the account will be used by all team members.", - "unavailable": "Get usage exception", - "usage": "Usage", - "value_not_return_tip": "After the parameters are configured, they will not return to the front end again and do not need to be leaked to other members.", - "value_placeholder": "Enter parameter values. \nEntering a null value means deleting the configuration." -} + "third_party_account_desc": "The admin can configure third-party accounts, which will be available to all team members.", + "unavailable": "Failed to obtain usage.", + "usage": "Usage:", + "value_not_return_tip": "The configured parameter will not be shared with other members because it will not be returned to the frontend.", + "value_placeholder": "Enter the parameter value. If the parameter is left blank, the configuration will be deleted." +} \ No newline at end of file diff --git a/packages/web/i18n/en/account_usage.json b/packages/web/i18n/en/account_usage.json index 6415a45bdffb..938183416bab 100644 --- a/packages/web/i18n/en/account_usage.json +++ b/packages/web/i18n/en/account_usage.json @@ -1,55 +1,63 @@ { - "ai_model": "AI model", - "all": "all", - "app_name": "Application name", - "auto_index": "Auto index", - "billing_module": "Deduction module", - "confirm_export": "A total of {{total}} pieces of data were filtered out. Are you sure to export?", - "count": "Number of runs", - "current_filter_conditions": "Current filter conditions", + "ai_model": "Model", + "all": "All", + "app_name": "App name", + "auto_index": "Index enhancement", + "billing_module": "Billing details", + "confirm_export": "Are you sure you want to export the {{total}} matched entries?", + "count": "Runs", + "current_filter_conditions": "Current filters:", "dashboard": "Dashboard", "details": "Details", "dingtalk": "DingTalk", - "duration_seconds": "Duration (seconds)", - "embedding_index": "Embedding", - "evaluation": "Application Review", - "every_day": "Day", - "every_month": "Moon", + "duration_seconds": "Duration (s)", + "embedding_index": "Index generation", + "evaluation": "App evaluation", + "evaluation_dataset_data_quality_assessment": "Evaluate data quality", + "evaluation_dataset_data_synthesis": "Evaluation data generation", + "evaluation_dataset_data_qa_synthesis": "Evaluation data Q&A generation", + "evaluation_quality_assessment": "Quality evaluation", + "evaluation_debug_metric": "Debug metrics", + "every_day": "days", + "every_month": "months", "every_week": "weekly", - "export_confirm": "Export confirmation", - "export_confirm_tip": "There are currently {{total}} usage records in total. Are you sure to export?", - "export_success": "Export successfully", - "export_title": "Time,Members,Type,Project name,AI points", + "export_confirm": "Confirm", + "export_confirm_tip": "Are you sure you want to export {{total}} entries?", + "export_success": "Export successful.", + "export_title": "Time, Member, Type, Project name, Credits consumed", "feishu": "Feishu", - "generation_time": "Generation time", + "generation_time": "Time generated", "image_index": "Image index", - "image_parse": "Image tagging", - "input_token_length": "input tokens", - "llm_paragraph": "LLM segmentation", + "image_parse": "Image mark", + "input_token_length": "Input tokens", + "llm_paragraph": "Text segmentation", "mcp": "MCP call", - "member": "member", + "member": "Member", + "metrics_execute": "Execute metrics", "member_name": "Member name", - "module_name": "module name", - "month": "moon", - "no_usage_records": "No usage record yet", - "official_account": "Official Account", - "order_number": "Order number", - "output_token_length": "output tokens", + "module_name": "Module name", + "month": "months", + "no_usage_records": "No data available.", + "official_account": "WeChat official account", + "order_number": "Order ID", + "output_token_length": "Output tokens", "pages": "Pages", - "pdf_enhanced_parse": "PDF Enhanced Analysis", - "pdf_parse": "PDF Analysis", - "points": "Points", + "pdf_enhanced_parse": "Enhanced PDF parsing", + "pdf_parse": "PDF parsing", + "points": "Credits", "project_name": "Project name", - "qa": "QA", + "qa": "Q&A pair extraction", "select_member_and_source_first": "Please select members and types first", - "share": "Share Link", - "source": "source", - "text_length": "text length", - "token_length": "token length", - "total_points": "AI points consumption", - "total_points_consumed": "AI points consumption", - "total_usage": "Total Usage", - "usage_detail": "Details", - "user_type": "type", - "wecom": "WeCom" -} + "share": "Sharing link", + "source": "Source", + "text_length": "Text length", + "token_length": "Token length", + "total_points": "Credits consumed", + "total_points_consumed": "Credits consumed", + "total_usage": "Total consumption", + "usage_detail": "Usage details", + "user_type": "Type", + "wecom": "WeCom", + "evaluation_summary_generation": "Generate summary", + "generate_answer": "Generate app response" +} \ No newline at end of file diff --git a/packages/web/i18n/en/admin.json b/packages/web/i18n/en/admin.json new file mode 100644 index 000000000000..6e1e36dfe7c9 --- /dev/null +++ b/packages/web/i18n/en/admin.json @@ -0,0 +1,631 @@ +{ + "license_active_success": "Activated successfully.", + "system_activation": "Activate license", + "system_activation_desc": "Please activate the FastGPT license first.", + "domain_name": "Current domain", + "input_license": "Enter the license key", + "cancel": "Cancel", + "confirm": "OK", + "config_desc": "Help", + "domain_invalid": "The license domain is invalid.", + "change_license": "Change license", + "logout": "Log out", + "expire_time": "Expiration date", + "max_users": "Max users", + "max_apps": "Max apps", + "max_datasets": "Max knowledge bases", + "sso": "SSO", + "pay": "Payment system", + "custom_templates": "Custom templates and system tools", + "dataset_enhance": "Knowledge base enhancement", + "unlimited": "Unlimited", + "data_dashboard": "Dashboard", + "notification_management": "Notification", + "log_management": "Logs", + "user_management": "User management", + "user_info": "Users", + "team_management": "Teams", + "plan_management": "Subscription plans", + "payment_records": "Payment record", + "invoice_management": "Fapiao requests", + "resource_management": "Resources", + "app_management": "Apps", + "dataset_management": "Knowledge bases", + "system_config": "System", + "basic_config": "General", + "feature_list": "Features", + "security_review": "Security audit", + "third_party_providers": "Third-party accounts", + "user_config": "User settings", + "plan_recharge": "Plans & top-up", + "template_tools": "Templates & tools", + "template_market": "Templates", + "toolbox": "Toolbox", + "audit_logs": "Audit logs", + "first_page": "First page", + "previous_page": "Back", + "next_page": "Next", + "last_page": "Last page", + "page": "Page", + "click_view_details": "Click to view details.", + "upload_image_failed": "Upload image failed", + "config_file": "Configuration file", + "save": "Save", + "upload_profile_failed": "Failed to upload the icon.", + "associated_plugin_is_empty": "Associated plugin is required.", + "config_success": "Configuration successful.", + "confirm_delete_plugin": "Are you sure you want to delete the plugin?", + "delete_success": "Deleted successfully.", + "custom_plugin": "Custom plugin", + "config": " settings", + "give_name": "Name", + "click_upload_avatar": "Click to upload an icon.", + "app_name_empty": "App name is required.", + "description": "Description", + "add_app_description": "Add description for this app", + "associated_plugins": "Associated plugin", + "search_plugin": "Plugin name, app ID", + "attribute": "Attribute", + "author_name": "Author name", + "default_system_name": "System name is used by default", + "is_enable": "Status", + "charge_token_fee": "Charge for token consumption", + "call_price": "API call cost (n credits/call)", + "instructions": "Tips", + "use_markdown_syntax": "Use Markdown syntax.", + "update": "Update", + "create_plugin": "Add plugin", + "delete_confirm_message": "Related resources will be deleted and cannot be restored. Would you like to proceed?", + "group_management": "Groups", + "total_groups": "Total groups: {localGroups.length}", + "add": "Add", + "add_type": "Add type", + "avatar_select_error": "Error occurred while selecting the icon.", + "rename": "Rename", + "add_group": "Add group", + "avatar_name": "Icon & name", + "click_set_avatar": "Click to upload an icon.", + "group_name_empty": "Group name is required.", + "official": "Official", + "configured": "Configured", + "not_configured": "Not configured", + "system_key_price": "System key price (n points/time)", + "plugin_config": "{{name}} configuration", + "enable_toolset": "Enable toolset", + "config_system_key": "Configure system key", + "tool_list": "Tool list", + "tool_name": "Tool name", + "key_price": "Key price", + "continue": "Continue", + "user_deleted_message": "This account has been deleted.", + "missing_field": "Required fields are missing.", + "team_not_exist": "The team does not exist.", + "subscription_exists": "A subscription plan of the same type already exists.", + "subscription_not_exist": "The subscription does not exist.", + "user_exist": "The user already exists.", + "account_logout": "Account deactivated successfully.", + "user_not_found": "User not found.", + "user_not_exist": "The user does not exist.", + "update_failed": "Update failed.", + "send_notification_success": "Notification sent successfully.", + "user_password_error": "Username or password is incorrect.", + "invoice_not_found": "Fapiao not found.", + "invoice_issued": "The fapiao has been issued.", + "invoice_completed_notice": "The fapiao you requested has been issued. Please check.", + "invoice_generation_completed": "Fapiao issued successfully.", + "order_not_exist": "The order does not exist.", + "order_unpaid": "The order is unpaid.", + "order_invoiced_no_refund": "Refund is unavailable because the order's fapiao has already been issued.", + "order_insufficient_amount": "The order amount is insufficient.", + "type_empty": "Type cannot be empty.", + "quantity_must_be_positive": "It must be greater than 0.", + "team_not_found": "Team cannot be found.", + "dataset_training_rebuilding": "The dataset is being trained or rebuilt. Please try again later.", + "database_rebuilding_index": "Rebuild database index", + "type_not_support": "This message type is not supported.", + "send_verification_code_success": "Verification code sent successfully.", + "normal": "Normal", + "leave": "Leave", + "deactivated": "Disabled", + "account": "Account", + "username": "Username", + "contact": "Contact", + "department": "Department", + "join_time": "Time added", + "update_time": "Last updated", + "status": "Status", + "team_num_limit_error": "A user can only join to one team.", + "organization_name": "Organization", + "amount": "Amount", + "yuan": "CNY", + "pending_invoice_count": "Pending Invoice Count", + "go_to_view": "View now", + "new_invoice_application": "New fapiao request received.", + "order_type_error": "The order type is invalid.", + "missing_key_params_update_bill_failed": "Failed to update the bill because the required parameters are missing. Please contact the administrator.", + "plugin_service_link_not_configured": "The following links are not configured with plugins", + "audit_record_table": "Audit logs", + "operator": "Operator", + "operation_type": "Operation", + "operation_time": "Time", + "operation_content": "Description", + "no_audit_records": "No data available.", + "audit_details": "Audit details", + "details": "Details", + "close": "Cancel", + "total_users": "Users", + "registered_users": "Registered users", + "payment_amount": "Total amount", + "order_count": "Orders", + "all": "All", + "success": "Successful", + "paid_teams": "Paid teams", + "total_conversations": "Chats", + "total_sessions": "Conversations", + "avg_conversations_per_session": "Average chats per conversation", + "points_consumed": "Credits consumed", + "user_total": "Users", + "dataset_total": "Knowledge bases", + "app_total": "Apps", + "statistics_data": "Statistics", + "traffic": "Traffic", + "payment": "Payment", + "active": "Active", + "cost": "Cost", + "last_7_days": "Last 7 days", + "last_30_days": "Last 30 days", + "last_90_days": "Last 90 days", + "last_180_days": "Last 180 days", + "confirm_modify_system_announcement": "Are you sure you want to change the system announcement?", + "confirm_send_system_notification": "Are you sure you want to send the system notification?", + "modify_success": "Modified successfully.", + "modify_failed": "Modification failed.", + "send_success": "Sent successfully.", + "send_failed": "Failed", + "system_announcement_config": "System announcement", + "system_announcement_description": "It will pop up after users log in to FastGPT, and will not appear again after being closed. Only 1 announcement is allowed. Supported format: Markdown", + "send_system_notification": "System notification", + "confirm_send": "Send now", + "send_notification_description": "Send a notification to all users.", + "message_level": "Notification level", + "level_normal": "Low (system notification)", + "level_important": "Medium (system or login notification)", + "level_urgent": "High (system, login, or email/SMS notification)", + "level_normal_text": "Low", + "level_important_text": "Medium", + "level_urgent_text": "High", + "notification_title": "Subject", + "notification_content": "Content", + "log_record_table": "Logs", + "log_search_placeholder": "Description", + "time": "Time", + "log_content": "Message", + "no_log_records": "No data available.", + "log_level": "Log severity", + "log_detail": "Log details", + "log_message": "Message", + "login_success": "Login successful.", + "admin_login": "Log in", + "login_username": "Username", + "login_password": "Password", + "login_button": "Log in", + "app_list": "Apps", + "app_name": "App Name", + "creator": "Creator", + "redirect": "Redirect", + "app_no_records": "No data available.", + "app_details": "App details", + "app_id": "App ID", + "app_creator_id": "Creator ID", + "dataset_list": "Knowledge bases", + "dataset_name": "Knowledge base name", + "dataset_data_size": "Data size", + "dataset_vector_count": "Total vectors", + "dataset_favorite_count": "Favorites", + "new": "New", + "name": "Name", + "enable": "Enable", + "redirect_link": "Redirect URL", + "operation": "Operation", + "add_sidebar_item": "Add sidebar item", + "sidebar_item_name": "Sidebar item name", + "sidebar_item_name_empty": "Sidebar item name is required.", + "redirect_link_empty": "Redirect URL is required.", + "edit_plan": "Edit {{label}} plan", + "plan_name": "Plan name", + "custom_plan_name_desc": "Custom plan name (overwrites the default name)", + "monthly_price": "Monthly price", + "max_team_members": "Max team members", + "max_app_count": "Max apps", + "max_dataset_count": "Max Datasets", + "history_retention_days": "History retention (days)", + "max_dataset_index_count": "Max knowledge base indexes", + "monthly_ai_points": "Monthly credits", + "training_priority_high": "Training priority (higher value for higher priority)", + "allow_site_sync": "Enable site sync", + "allow_team_operation_logs": "Log member operations", + "click_configure_plan": "Edit", + "copy_success": "Copied successfully.", + "delete_confirm": "Are you sure you want to delete the variable?", + "delete": "Delete", + "workflow_variable_custom": "Custom workflow variables", + "workflow_variable_custom_title": "Custom workflow variable", + "field_name": "Variable name", + "usage_url": "Usage query URL", + "note": "Note", + "import_config": "Import config file", + "import_success_save": "Import successful. Please click Save to save the changes.", + "import_check_format": "Please check the configuration file format.", + "import": "Import", + "get_config_error": "Error occurred while loading configuration.", + "save_success": "Saved successfully.", + "save_failed": "Operation failed.", + "frontend_display_config": "Frontend view settings", + "personalization_config": "Custom settings", + "global_script": "Global script", + "system_params": "System parameters", + "pdf_parse_config": "PDF parsing settings", + "usage_limits": "Usage limit", + "sidebar_config": "Sidebar settings", + "system_name": "System name", + "custom_api_domain": "Custom API domain", + "custom_api_domain_desc": "Set an additional API address. If you do not use this platform address, you need to configure the domain’s CNAME and SSL certificate.", + "custom_share_link_domain": "Custom sharing link domain", + "custom_share_link_domain_desc": "Set an additional sharing link address. If you do not use this platform address, you need to configure the domain's CNAME and SSL certificate.", + "openapi_prefix": "OpenAPI prefix", + "contact_popup": "Contact Us pop-up", + "contact_popup_desc": "Configure the content for Contact us using Markdown syntax.", + "custom_api_doc_url": "Custom API documentation URL", + "custom_openapi_doc_url": "Custom OpenAPI documentation URL", + "doc_url_note": "Documentation URL (It must end with a slash (/). Otherwise, users cannot be redirected properly.)", + "contribute_plugin_doc_url": "Link for plugin contribution", + "contribute_template_doc_url": "Link for template contribution", + "global_script_desc": "Custom script (inserted globally for platform traffic monitoring or other purposes)", + "mcp_forward_service_url": "MCP forwarding service address", + "mcp_forward_service_desc": "Deploy an MCP forwarding service to expose FastGPT via the MCP protocol. Example: http://localhost:3005", + "oneapi_url": "OneAPI address (overwrites environment variable configuration)", + "oneapi_url_desc": "Used for accessing multiple models", + "input_oneapi_url": "Enter the OneAPI address", + "oneapi_key": "OneAPI key (overwrites environment variable configuration)", + "input_oneapi_key": "Enter the OneAPI key", + "dataset_parse_max_process": "Max processes for knowledge base parsing", + "dataset_index_max_process": "Max processes for knowledge base indexing", + "file_understanding_max_process": "Max processes for LLM", + "image_understanding_max_process": "Max processes for VLM", + "hnsw_ef_search": "HNSW ef_search", + "hnsw_ef_search_desc": "HNSW parameter. A higher value increases recall rate but reduces performance. Default value: 100. For details, see https://github.com/pgvector/pgvector", + "hnsw_max_scan_tuples": "HNSW max_scan_tuples", + "hnsw_max_scan_tuples_desc": "Maximum iterative scans. A higher value increases recall rate but reduces performance. Default value: 100000. For details, see https://github.com/pgvector/pgvector", + "token_calc_max_process": "Max processes for token calculation (based on concurrency)", + "custom_pdf_parse_url": "Custom PDF parsing address", + "custom_pdf_parse_key": "Custom PDF parsing key", + "custom_pdf_parse_timeout": "Custom PDF parsing timeout (minutes)", + "doc2x_pdf_parse_key": "Doc2x PDF parsing key (lower priority than custom PDF parsing)", + "custom_pdf_parse_price": "Price for custom PDF parsing (credits/page)", + "eval_config": "Evaluation Configuration", + "eval_config_task_concurrency": "Evaluation Task Concurrency", + "eval_config_task_concurrency_desc": "Number of evaluation tasks running simultaneously", + "eval_config_case_concurrency": "Evaluation Case process Concurrency", + "eval_config_case_concurrency_desc": "Number of Evaluation Cases processed concurrently within a single task", + "eval_config_case_max_retry": "Max Retry Count of Each Evaluation Case", + "eval_config_case_max_retry_desc": "Maximum retry attempts when evaluation Case processed fail", + "eval_config_case_result_threshold": "Evaluation Judgment Default Threshold", + "eval_config_case_result_threshold_desc": "Default threshold for evaluation judgment (between 0-1, determines the positive or negative of the evaluation result)", + "eval_config_summary_concurrency": "Evaluation Report Generation Concurrency", + "eval_config_data_quality_concurrency": "Evaluation Dataset - Data Quality Concurrency", + "eval_config_dataset_synthesize_concurrency": "Evaluation Dataset - Data Synthesis Concurrency", + "eval_config_smart_generate_concurrency": "Evaluation Dataset - Data Intelligent Generation Concurrency", + "eval_config_maxStalledCount": "Evaluation - Max Stalled Job Retry Count", + "max_upload_files_per_time": "Max files per upload", + "max_upload_files_per_time_desc": "Maximum number of files per upload to a knowledge base", + "max_upload_file_size": "Max file size (MB)", + "max_upload_file_size_desc": "Maximum file size per upload to a knowledge base If you change it to a larger value, ensure the gateway configuration is modified accordingly.", + "export_interval_minutes": "Export interval (minutes)", + "site_sync_interval_minutes": "Site sync usage interval (minutes)", + "mobile_sidebar_location": "On mobile devices, the sidebar items are displayed in Account > Profile.", + "basic_features": "Basic features", + "third_party_knowledge_base": "Third-party knowledge bases", + "third_party_publish_channels": "Third-party publishing channels", + "feature_display_config": "Display options", + "display_team_sharing": "Show content shared by team members", + "display_chat_blank_page": "Show empty chat page (deprecated)", + "display_invite_friends_activity": "Show invite-friends activities", + "frontend_compliance_notice": "Show compliance", + "feishu_knowledge_base": "Feishu", + "feishu_knowledge_base_desc": "If disabled, Feishu will no longer be displayed to users when creating knowledge bases.", + "yuque_knowledge_base": "Yuque", + "yuque_knowledge_base_desc": "If disabled, Yuque will no longer be displayed to users when creating knowledge bases.", + "feishu_publish_channel": "Feishu", + "feishu_publish_channel_desc": "If disabled, Feishu will no longer be displayed in publishing channels.", + "dingtalk_publish_channel": "DingTalk", + "dingtalk_publish_channel_desc": "If disabled, DingTalk will no longer be displayed in publishing channels.", + "wechat_publish_channel": "WeChat official account", + "wechat_publish_channel_desc": "If disabled, WeChat official account will no longer be displayed in publishing channels.", + "content_security_review": "Content security audit", + "baidu_security_id": "Baidu security ID", + "baidu_security_secret": "Baidu security secret", + "custom_security_check_url": "Custom security check URL", + "baidu_security_register_desc": "Register a baidu security verification account and create a corresponding application. Provide the application id and secret", + "custom_security_check_desc": "If you have your own security check service, enter the address here and enable sensitive content detection on FastGPT.", + "plan_free": "Free edition", + "plan_trial": "Trial edition", + "plan_team": "Team edition", + "plan_enterprise": "Enterprise edition", + "subscription_plan": "Subscription plan", + "standard_subscription_plan": "Standard subscription plan", + "custom_plan_description": "Custom plan address", + "dataset_storage_cost_desc": "Knowledge base storage price (xx CNY/1,000 entries/month)", + "extra_ai_points_cost_desc": "Price for additional credits (xx CNY/1000 credits/month)", + "payment_method": "Payment method", + "wechat_payment_config": "WeChat payment settings", + "alipay_payment_config": "Alipay payment settings", + "corporate_payment_message": "Corporate payment notification", + "enable_subscription_plan": "Enable subscription plan", + "custom_plan_page_description": "The specified address will overwrite the address of the subscription plan page. Users will be redirected to this address when accessing the subscription plan page.", + "wechat_payment_materials": "WeChat payment documents", + "wechat_payment_registration_guide": "Register WeChat payment by yourself, currently you need to scan the QR code to pay", + "unused_field_placeholder": "Fill in anything", + "certificate_management_guide": "Obtain from WeChat Pay Merchants Platform", + "wechat_key_extraction_guide": "Follow WeChat instructions to obtain the files, and open the key file with a text editor to get the private key.", + "alipay_payment_materials": "Alipay payment documents", + "alipay_application_guide": "Register the Alipay app yourself. Currently, you need to enable payment via the computer website.", + "alipay_certificate_encryption_guide": "Use a certificate for API signing. For details, see", + "application_public_key_certificate": "App public key certificate", + "private_key_document_reference": "Refer to the above documentation for retrieving the private key", + "alipay_root_certificate": "Alipay root certificate", + "alipay_public_key_certificate": "Alipay public key certificate", + "alipay_dateway": "Alipay gateway", + "alipay_gateway_sandbox_note": "Enter the Alipay gateway address. The sandbox environment for testing is\n https://openapi-sandbox.dl.alipaydev.com/gateway.do\n, Build environment\n https://openapi.alipay.com/gateway.do\n", + "alipay_endpoint_sandbox_note": "Enter the Alipay endpoint address. The sandbox environment for testing is\n https://openapi-sandbox.dl.alipaydev.com\n, Build environment\n https://openapi.alipay.com\n", + "message_notification": "Message", + "markdown_format_support": "Supported format: Markdown", + "third_party_account_config": "Third-party accounts", + "allow_user_account_config": "Enable the options below to allow users to configure third-party accounts.", + "view_documentation": "View documentation", + "openai_oneapi_account": "OpenAI/OneAPI account", + "laf_account": "LAF account", + "input_laf_address": "Enter the LAF address", + "multi_team_mode": "Multi-team mode", + "single_team_mode": "Single team mode", + "sync_mode": "Sync mode", + "notification_login_settings": "Notification & login settings", + "team_mode_settings": "Team mode", + "custom_user_system_config": "SSO settings", + "email_notification_config": "Email notification settings (for registration and plans)\nQQ: smtp.qq.com\ngmail: smtp.gmail.com", + "aliyun_sms_config": "Alibaba Cloud SMS settings", + "aliyun_sms_template_code": "Alibaba Cloud SMS template code (SMS_xxx)", + "wechat_service_login": "WeChat service account login", + "github_login_config": "GitHub login settings", + "google_login_config": "Google login settings", + "microsoft_login_config": "Microsoft login settings", + "quick_login": "Quick login (not recommended)", + "login_notifications_config": "Login notification & settings", + "user_service_root_address": "User service root address (Do not add a trailing slash (/).)", + "sso_usage_guide": "For details, see [SSO & External Member Sync](https://doc.fastgpt.io/docs/guide/admin/sso/)", + "sso_login_button_title": "SSO button name", + "config_sso_login_button_title": "Configure SSO button name", + "sso_login_button_icon": "SSO button icon", + "config_sso_login_button_icon": "Configure SSO button icon", + "sso_auto_redirect": "Auto redirection for SSO", + "sso_auto_redirect_desc": "If enabled, SSO will be automatically triggered when users visit the login page.", + "email_smtp_address": "SMTP server address", + "email_smtp_address_note": "Varies from vendor to vendor.", + "email_smtp_username": "SMTP username", + "email_smtp_username_example": "Example: A QQ email address corresponds to a QQ account.", + "email_password": "SMTP authorization code", + "email_smtp_auth_code": "SMTP authorization code", + "enable_email_registration": "Enable email registration", + "aliyun_access_key_guide": "Alibaba Cloud SMS parameters\nhttps://dysms.console.aliyun.com/overview\nApply for the corresponding signature and SMS template, providing:\nACCESSKEYID\nACCESSSECRET\nSignature Name\nTemplate CODE, starting with SM.", + "aliyun_secret_key": "Alibaba Cloud account secret key", + "sms_signature": "SMS signature", + "registration_account": "Account registration", + "registration_account_desc": "If configured, registration with mobile number will be enabled.", + "reset_password": "Reset password", + "reset_password_desc": "If configured, the password can be retrieved through mobile number.", + "bind_notification_phone": "Mobile number", + "bind_notification_phone_desc": "If configured, a notification will be sent to the specified mobile number.", + "subscription_expiring_soon": "Expiration reminder for subscription plans", + "subscription_expiring_soon_desc": "If configured, an SMS message will be sent when the plan is about to expire.", + "free_user_cleanup_warning": "Warning for free edition user cleanup", + "wechat_service_appid": "App ID of WeChat service account. Enter the business edition domain for the verification address:", + "wechat_service_secret": "AppSecret of WeChat service account", + "register_one": "Register one", + "provide": "Provider", + "domain": "domain", + "microsoft_app_client_id": "App (client) ID of Microsoft app", + "microsoft_tenant_id": "Tenant ID of Microsoft app (leave this field blank to use the default value common)", + "custom_button_name": "Custom button name", + "custom_button_name_desc": "If left blank, the default Microsoft button name will be used.", + "simple_app": "Simple app", + "workflow": "Workflow", + "plugin": "Plugin", + "folder": "Folder", + "http_plugin": "HTTP plugin", + "toolset": "toolkit", + "tool": "Tool", + "hidden": "Hideen", + "select_json_file": "Please select a JSON file.", + "confirm_delete_template": "Are you sure you want to delete the template?", + "upload_config_first": "Please upload a configuration file first.", + "app_type_not_recognized": "Unrecognized app type", + "config_json_format_error": "Invalid file format. The configuration file must be in JSON format.", + "template_update_success": "Template updated successfully.", + "template_create_success": "Template created successfully.", + "template_config": "Template settings", + "json_serialize_failed": "JSON serialization failed.", + "get_app_type_failed": "Failed to obtain application type", + "file_overwrite_content": "The file will overwrite the existing content.", + "config_file_label": "Configuration file", + "official_config": "Official configuration", + "upload_file": "Upload file", + "paste_config_or_drag_json": "Paste configuration or drag & drop a JSON file here", + "paste_config": "Paste configuration", + "app_type": "App type", + "auto_recognize": "Auto-identified", + "app_attribute_not_recognized": "Unrecognized app attribute", + "text": "Text", + "link": "Link", + "input_link": "Enter a link", + "settings_successful": "Settings saved successfully.", + "configure_quick_templates": "Configure quick template", + "search_apps": "App name", + "selected_count": "Selected: {{count}} / 3", + "category_management": "Categories", + "total_categories": "Total categories: {{length}}", + "add_category": "Add category", + "category_name": "Category name", + "category_name_empty": "Category name is required.", + "template_list": "Templates", + "quick_template": "Quick template", + "add_template": "Add template", + "recommended": "Recommended", + "no_templates": "No templates available.", + "add_attribute_first": "Please add attributes first.", + "add_plugin": "Add plugin", + "token_points": "Token credits", + "token_fee_description": "If enabled, credits will be consumed based to plugin token consumption and API calls.", + "call_points": "API call credits", + "system_key": "System secret key", + "system_key_description": "For tools that require keys, you can configure system keys for them, and users can use the system keys by paying points.", + "no_plugins": "No plugins available.", + "invoice_application": "Fapiao requests", + "search_user_placeholder": "Username", + "submit_status": "Request status", + "submit_complete_time": "Time requested/issued", + "invoice_title": "Fapiao title", + "waiting_for_invoice": "Waiting", + "completed": "Completed", + "confirm_invoice": "Confirm", + "no_invoice_records": "No data available.", + "invoice_details": "Fapiao details", + "invoice_amount": "Fapiao amount", + "organization": "Organization", + "unified_credit_code": "Unified social credit code", + "company_address": "Company address", + "company_phone": "Company phone", + "bank_name": "Bank name", + "bank_account": "Account number", + "need_special_invoice": "Require special VAT fapiao", + "yes": "Yes", + "no": "No", + "email_address": "Email address", + "invoice_file": "Fapiao file", + "click_download": "Download", + "operation_success": "Success", + "operation_failed": "Error", + "upload_invoice_pdf": "Please upload the PDF file of your invoice", + "select_invoice_file": "Select invoice file", + "confirm_submission": "Confirm submission", + "balance_recharge": "Balance top-up", + "plan_subscription": "Plan level", + "knowledge_base_expansion": "Knowledge base expansion", + "ai_points_package": "Credit package", + "monthly": "Monthly", + "yearly": "Yearly", + "free": "Free", + "trial": "Trial", + "team": "Team", + "enterprise": "Enterprise", + "custom": "Custom", + "wechat": "WeChat", + "balance": "Balance", + "alipay": "Alipay", + "corporate": "Corporate payment", + "redeem_code": "Redeem code", + "search_user": "Username", + "team_id": "Team ID", + "recharge_member_name": "Member", + "unpaid": "Unpaid", + "no_bill_records": "No data available.", + "order_details": "Order details", + "order_number": "Order ID", + "generation_time": "Time generated", + "order_type": "Order type", + "subscription_period": "Periodic", + "subscription_package": "Subscription plan", + "months": "Months", + "extra_knowledge_base_capacity": "Additional knowledge base index capacity", + "extra_ai_points": "Additional credits", + "time_error": "Start time cannot be later than end time.", + "points_error": "The remaining credits cannot be greater than the total credits.", + "add_success": "Added successfully.", + "add_package": "Add plan", + "package_type": "Plan type", + "basic_package": "Standard plan", + "start_time": "Start time", + "required": "*Required", + "end_time": "End time", + "package_level": "Plan level", + "total_points": "Total credits", + "remaining_points": "Remaining credits", + "price_yuan_for_record_only": "Price (CNY) (for record only)", + "price": "Unit price", + "update_success": "Update successful.", + "edit": "Edit", + "edit_package": "Edit plan", + "value_override_description": "The following values will overwrite default values in plan configuration. If unspecified, plan defaults will be used.", + "team_member_limit": "Max team members", + "app_limit": "Max apps", + "knowledge_base_limit": "Max knowledge bases", + "team_name": "Name", + "points": "Credits", + "start_end_time": "Validity Period", + "version": " edition", + "extra_knowledge_base": "Additional knowledge base", + "no_package_records": "No data available.", + "role": "Permission", + "team_details": "Team details", + "chage_success": "Change saved successfully.", + "team_edit": "Edit team", + "team_list": "Teams", + "create_time": "Time created", + "no_team_data": "No data available.", + "add_user": "Add user", + "add_user_btn": "Add", + "password": "Password", + "password_requirements": "Must be at least 8 characters long and contain at least 2 of the following: digits, letters, and special characters.", + "account_deactivated": "Account deactivated successfully.", + "edit_user": "Edit user", + "user_status": "Status", + "confirm_deactivate_account": "Are you sure you want to deactivate the account? Key resources of the account will be deleted, and the username will be changed to xx-deleted.", + "deactivate": "Deactivate", + "no_user_records": "No data available.", + "user_details": "User details", + "system_incompatibility": "The page crashed due to system incompatibility, which often occurs on Apple devices. If possible, contact the author with the operation and page details. Most cases involve Safari. Please use Chrome instead.", + "content_not_compliant": "Your content is not compliant with requirements.", + "baidu_content_security_check_exception": "Error occurred during Baidu content security check.", + "license_not_read": "Failed to read licensing information", + "license_invalid": "The license is invalid.", + "license_expired": "The license has expired.", + "license_content_error": "The license content is incorrect", + "system_not_activated": "The license has not been activated.", + "exceed_max_users": "The number of users exceeded the maximum.", + "server_error": "Server error occurred.", + "request_error": "Request failed.", + "unknown_error": "Unknown error occurred.", + "token_expired_relogin": "Token expired. Please log in again.", + "google_verification_result": "Google verification result", + "abnormal_operation_environment": "Your environment is abnormal. Please refresh the page and try again, or contact Custom Service.", + "notify_expiring_packages": "Notification for expiring plans", + "notify_free_users_cleanup": "Notification for free edition user cleanup", + "bing_oauth_config_incomplete": "Incomplete Bing OAuth configuration", + "request_limit_per_minute": "Max requests per minute", + "share_link_expired": "The sharing link has expired.", + "link_usage_limit_exceeded": "The link usage exceeds its limit.", + "authentication_failed": "Identity verification failed.", + "user_registration": "Register", + "initial_password": "Your initial password is", + "notification_too_frequent": "Too many attempts.", + "emergency_notification_requires_teamid": "Team ID is required for critical notifications.", + "send_sms_failed": "Failed to send SMS message.", + "fastgpt_user": "FastGPT user", + "refund_failed": "Refund failed.", + "refund_request_failed": "Refund request failed.", + "get_certificate_list_failed": "Failed to obtain the certificate list.", + "platform_certificate_serial_mismatch": "Certificate serial numbers do not match.", + "extra_knowledge_base_storage": "Additional knowledge base storage", + "image_compression_error": "Image compression error occurred.", + "image_too_large": "The image size is too large.", + "password_min_length": "Must contain at least 8 characters.", + "password_requirement": "Must contain at least 2 of the following: digits, letters, and special characters." +} diff --git a/packages/web/i18n/en/app.json b/packages/web/i18n/en/app.json index 44ebbdddb85d..27478596dfba 100644 --- a/packages/web/i18n/en/app.json +++ b/packages/web/i18n/en/app.json @@ -1,327 +1,327 @@ { - "AutoOptimize": "Automatic optimization", - "Click_to_delete_this_field": "Click to delete this field", - "Filed_is_deprecated": "This field is deprecated", + "AutoOptimize": "Auto optimize", + "Click_to_delete_this_field": "Click to delete", + "Filed_is_deprecated": "The field has been deprecated.", "Index": "Index", - "MCP_tools_debug": "debug", - "MCP_tools_detail": "check the details", - "MCP_tools_list": "Tool list", - "MCP_tools_list_is_empty": "MCP tool not resolved", - "MCP_tools_list_with_number": "Tool list: {{total}}", - "MCP_tools_parse_failed": "Failed to parse MCP address", - "MCP_tools_url": "MCP Address", - "MCP_tools_url_is_empty": "The MCP address cannot be empty", - "MCP_tools_url_placeholder": "After filling in the MCP address, click Analysis", - "No_selected_dataset": "No selected dataset", - "Optimizer_CloseConfirm": "Confirm to close", - "Optimizer_CloseConfirmText": "Optimization results have been generated, confirming that closing will lose the current result. Will it continue?", + "MCP_tools_debug": "Debug", + "MCP_tools_detail": "View details", + "MCP_tools_list": "Tools", + "MCP_tools_list_is_empty": "Failed to parse the MCP address.", + "MCP_tools_list_with_number": "Tools: {{total}}", + "MCP_tools_parse_failed": "Failed to parse the MCP address.", + "MCP_tools_url": "MCP address", + "MCP_tools_url_is_empty": "MCP address is required.", + "MCP_tools_url_placeholder": "Enter the MCP address and then click Parse", + "No_selected_dataset": "No knowledge bases selected.", + "Optimizer_CloseConfirm": "Confirm", + "Optimizer_CloseConfirmText": "This operation will discard the optimization result. Would you like to proceed?", "Optimizer_EmptyPrompt": "Please enter optimization requirements", "Optimizer_Generating": "Generating...", - "Optimizer_Placeholder": "How do you want to write or optimize prompt words?", - "Optimizer_Placeholder_loading": "Generating...please wait", - "Optimizer_Reoptimize": "Re-optimize", - "Optimizer_Replace": "replace", - "Optimizer_Tooltip": "AI optimization prompt words", - "Role_setting": "Permission", - "Run": "Execute", - "Search_dataset": "Search dataset", + "Optimizer_Placeholder": "Enter your suggestions for how to optimize prompts", + "Optimizer_Placeholder_loading": "Generating, please wait.", + "Optimizer_Reoptimize": "Optimize again", + "Optimizer_Replace": "Use", + "Optimizer_Tooltip": "Optimize with AI", + "Role_setting": "Permissions", + "Run": "Run", + "Search_dataset": "Knowledge base", "Selected": "Selected", - "Team_Tags": "Team tags", - "ai_point_price": "Billing", - "ai_settings": "AI Configuration", - "all_apps": "All Applications", - "app.Version name": "Version Name", - "app.error.publish_unExist_app": "Release failed, please check whether the tool call is normal", - "app.error.unExist_app": "Some components are missing, please delete them", - "app.modules.click to update": "Click to Refresh", + "Team_Tags": "Team tag", + "ai_point_price": "Billing based on credits", + "ai_settings": "Settings", + "all_apps": "All apps", + "app.Version name": "Version name", + "app.error.publish_unExist_app": "Failed to publish the app. Please check whether tools are called properly.", + "app.error.unExist_app": "Some components are missing. Please delete them.", + "app.modules.click to update": "Update", "app.modules.has new version": "New Version Available", - "app.modules.not_found": "Not Found", - "app.version_current": "Current Version", - "app.version_initial": "Initial Version", - "app.version_name_tips": "Version name cannot be empty", - "app.version_past": "Previously Published", - "app.version_publish_tips": "This version will be saved to the team cloud, synchronized with the entire team, and update the app version on all release channels.", - "app_detail": "Application Details", - "auto_execute": "Automatic execution", - "auto_execute_default_prompt_placeholder": "Default questions sent when executing automatically", - "auto_execute_tip": "After turning it on, the workflow will be automatically triggered when the user enters the conversation interface. \nExecution order: 1. Dialogue starter; 2. Global variables; 3. Automatic execution.", + "app.modules.not_found": "Components are missing.", + "app.version_current": "Current version", + "app.version_initial": "Initial version", + "app.version_name_tips": "Version name is required.", + "app.version_past": "Published", + "app.version_publish_tips": "This version will be saved to the cloud, and be updated to all team members and all publishing channels.", + "app_detail": "App details", + "auto_execute": "Auto execution", + "auto_execute_default_prompt_placeholder": "Default questions will be sent during auto execution.", + "auto_execute_tip": "If enabled, the workflow will be automatically executed when users enter the chat interface. Execution order: 1. Opening remarks. 2. Global variables. 3. App auto execution.", "auto_save": "Auto save", - "chat_debug": "Chat Preview", + "chat_debug": "Debug & preview", "chat_logs": "Logs", - "chat_logs_tips": "Logs will record the online, shared, and API (requires chatId) conversation records of this app.", - "config_ai_model_params": "Click to configure AI model related properties", - "config_file_upload": "Click to Configure File Upload Rules", - "config_question_guide": "Configuration guess you want to ask", - "confirm_copy_app_tip": "The system will create an app with the same configuration for you, but permissions will not be copied. Please confirm!", - "confirm_del_app_tip": "Are you sure you want to delete 【{{name}}】 and all of its chat history?", - "confirm_delete_folder_tip": "Confirm to delete this folder? All apps and corresponding conversation records under it will be deleted. Please confirm!", - "copy_one_app": "Create Duplicate", - "core.app.QG.Switch": "Enable guess what you want to ask", - "core.dataset.import.Custom prompt": "Custom Prompt", - "create_by_curl": "By CURL", - "create_by_template": "By template", - "create_copy_success": "Duplicate Created Successfully", - "create_empty_app": "Create Default App", - "create_empty_plugin": "Create Default Plugin", - "create_empty_workflow": "Create Default Workflow", - "cron.every_day": "Run Daily", - "cron.every_month": "Run Monthly", - "cron.every_week": "Run Weekly", - "cron.interval": "Run at Intervals", - "dataset": "dataset", - "dataset_search_tool_description": "Call the \"Semantic Search\" and \"Full-text Search\" capabilities to find reference content that may be related to the problem from the \"Knowledge Base\". \nPrioritize calling this tool to assist in answering user questions.", + "chat_logs_tips": "The logs record online chats, shared chats, and API-based chats (chat ID is required) in the app.", + "config_ai_model_params": "Click to configure", + "config_file_upload": "Click to configure", + "config_question_guide": "Click to configure", + "confirm_copy_app_tip": "An app with the same configuration except for permissions will be created. Would you like to proceed?", + "confirm_del_app_tip": "Are you sure you want to delete {{name}} and its chat records?", + "confirm_delete_folder_tip": "Are you sure you want to delete the folder? All apps and chat records in the folder will also be deleted. Would you like to proceed?", + "copy_one_app": "Duplicate", + "core.app.QG.Switch": "Status", + "core.dataset.import.Custom prompt": "Custom prompt", + "create_by_curl": "cURL", + "create_by_template": "Template", + "create_copy_success": "Duplicated successfully.", + "create_empty_app": "Create blank app", + "create_empty_plugin": "Create blank plugin", + "create_empty_workflow": "Create blank workflow", + "cron.every_day": "Daily", + "cron.every_month": "Monthly", + "cron.every_week": "Weekly", + "cron.interval": "Other", + "dataset": "Knowledge base", + "dataset_search_tool_description": "Uses semantic, full-text, and database search capabilities to search for reference materials related to the questions from the selected knowledge bases.", "day": "Day", - "deleted": "App deleted", - "document_quote": "Document Reference", - "document_quote_tip": "Usually used to accept user-uploaded document content (requires document parsing), and can also be used to reference other string data.", - "document_upload": "Document Upload", - "edit_app": "Application details", + "deleted": "The app has been deleted.", + "document_quote": "Document reference", + "document_quote_tip": "Use documents uploaded by users for references (document parsing is required). It can also be used to reference other string entries.", + "document_upload": "Document upload", + "edit_app": "App details", "edit_info": "Edit", - "execute_time": "Execution Time", - "export_config_successful": "Configuration copied, some sensitive information automatically filtered. Please check for any remaining sensitive data.", + "execute_time": "Schedule", + "export_config_successful": "The configuration has been copied, and some sensitive information has been automatically filtered out. Please check if any sensitive data still exists.", "export_configs": "Export", - "feedback_count": "User Feedback", - "file_quote_link": "Files", - "file_recover": "File will overwrite current content", - "file_upload": "File Upload", - "file_upload_tip": "Once enabled, documents/images can be uploaded. Documents are retained for 7 days, images for 15 days. Using this feature may incur additional costs. To ensure a good experience, please choose an AI model with a larger context length when using this feature.", - "go_to_chat": "Go to Conversation", - "go_to_run": "Go to Execution", - "image_upload": "Image Upload", - "image_upload_tip": "How to activate model image recognition capabilities", + "feedback_count": "Feedback", + "file_quote_link": "File link", + "file_recover": "The file will overwrite the existing content.", + "file_upload": "File upload", + "file_upload_tip": "If enabled, you can upload documents and images. Documents are retained for 7 days, while images for 15 days. Using this feature may generate significant additional costs. To ensure optimal user experience, please select a model that supports a longer context length.", + "go_to_chat": "Chat now", + "go_to_run": "Run now", + "image_upload": "Image upload", + "image_upload_tip": "How to enable image recognition", "import_configs": "Import", - "import_configs_failed": "Import configuration failed, please ensure the configuration is correct!", - "import_configs_success": "Import Successful", - "initial_form": "initial state", - "interval.12_hours": "Every 12 Hours", - "interval.2_hours": "Every 2 Hours", - "interval.3_hours": "Every 3 Hours", - "interval.4_hours": "Every 4 Hours", - "interval.6_hours": "Every 6 Hours", - "interval.per_hour": "Every Hour", - "invalid_json_format": "JSON format error", - "keep_the_latest": "Keep the latest", - "llm_not_support_vision": "This model does not support image recognition", - "llm_use_vision": "Vision", - "llm_use_vision_tip": "After clicking on the model selection, you can see whether the model supports image recognition and the ability to control whether to start image recognition. \nAfter starting image recognition, the model will read the image content in the file link, and if the user question is less than 500 words, it will automatically parse the image in the user question.", - "log_chat_logs": "Dialogue log", - "log_detail": "Log details", - "logs_app_data": "Data board", - "logs_app_result": "Application effect", - "logs_average_response_time": "Average run time", - "logs_average_response_time_description": "Average of total workflow run time", - "logs_chat_count": "Number of sessions", - "logs_chat_count_description": "How many new sessions does this application create? \nSession definition: When the interval between the previous message exceeds 15 minutes, it is considered to be a new session (this definition only takes effect here)", - "logs_chat_data": "chat data", - "logs_chat_item_count": "Number of conversations", - "logs_chat_item_count_description": "How many conversations does this app generate? \nDialogue definition: The workflow runs once, and counts as a round of conversations", - "logs_chat_user": "user", - "logs_date": "date", - "logs_empty": "No logs yet~", - "logs_error_count": "Error Count", - "logs_error_rate": "Dialogue error ratio", - "logs_error_rate_description": "The proportion of the total number of dialogues reported in error", - "logs_export_confirm_tip": "There are currently {{total}} conversation records, and each conversation can export up to 100 latest messages. \nConfirm export?", - "logs_export_title": "Time, source, user, contact, title, total number of messages, user good feedback, user bad feedback, custom feedback, labeled answers, conversation details", + "import_configs_failed": "Failed to import the configuration. Please make sure that the configuration file is valid.", + "import_configs_success": "Import successful.", + "initial_form": "Initial status", + "interval.12_hours": "Every 12 hours", + "interval.2_hours": "Every 2 hours", + "interval.3_hours": "Every 3 hours", + "interval.4_hours": "Every 4 hours", + "interval.6_hours": "Every 6 hours", + "interval.per_hour": "Every hour", + "invalid_json_format": "Please upload a valid JSON file.", + "keep_the_latest": "Latest version", + "llm_not_support_vision": "Not supported by the current model", + "llm_use_vision": "Image recognition", + "llm_use_vision_tip": "Select a model and then check whether it supports image recognition. If it is enabled, the model will read image contents from file links, and automatically parse images in the question if a question is less than 500 characters.", + "log_chat_logs": "Dashboard & Logs", + "log_detail": "Logs", + "logs_app_data": "Dashboard", + "logs_app_result": "App performance", + "logs_average_response_time": "Avg uptime (s)", + "logs_average_response_time_description": "Avg total uptime of workflow", + "logs_chat_count": "Conversations", + "logs_chat_count_description": "The total number of new conversations created in the app. A new conversation is created if the interval between the new message and the last message exceeds 15 minutes. (This definition only applies here.)", + "logs_chat_data": "Chat analysis", + "logs_chat_item_count": "Chats", + "logs_chat_item_count_description": "The total number of chats created in the app. A chat is a round of workflow execution.", + "logs_chat_user": "User", + "logs_date": "Date", + "logs_empty": "No logs available.", + "logs_error_count": "Errors", + "logs_error_rate": "Chat error rate", + "logs_error_rate_description": "The proportion of chats that encountered error to total chats", + "logs_export_confirm_tip": "The total number of chat records is {{total}}. Up to 100 latest messages can be exported from a chat. Would you like to proceed?", + "logs_export_title": "Time, Source, User, Contact info, Topic, Total messages, Positive feedback, Negative feedback, Custom feedback, Marked answers, Chat details", "logs_good_feedback": "Like", - "logs_key_config": "Field Configuration", - "logs_keys_annotatedCount": "Annotated Answer Count", - "logs_keys_chatDetails": "Conversation details", - "logs_keys_createdTime": "Created Time", - "logs_keys_customFeedback": "Custom Feedback", - "logs_keys_errorCount": "Error Count", - "logs_keys_feedback": "User Feedback", - "logs_keys_lastConversationTime": "Last Conversation Time", - "logs_keys_messageCount": "Message Count", - "logs_keys_points": "Points Consumed", - "logs_keys_responseTime": "Average Response Time", - "logs_keys_sessionId": "Session ID", + "logs_key_config": "Fields", + "logs_keys_annotatedCount": "Marked answers", + "logs_keys_chatDetails": "Chat details", + "logs_keys_createdTime": "Time created", + "logs_keys_customFeedback": "Custom feedback", + "logs_keys_errorCount": "Errors", + "logs_keys_feedback": "Feedback", + "logs_keys_lastConversationTime": "Last chat time", + "logs_keys_messageCount": "Total messages", + "logs_keys_points": "Credits consumed", + "logs_keys_responseTime": "Avg response time", + "logs_keys_sessionId": "Conversation ID", "logs_keys_source": "Source", - "logs_keys_title": "Title", + "logs_keys_title": "Topic", "logs_keys_user": "User", - "logs_message_total": "Total Messages", + "logs_message_total": "Total messages", "logs_new_user_count": "New users", - "logs_points": "Points Consumed", - "logs_points_description": "Points consumed by this application", - "logs_points_per_chat": "Average points consumption for a single session", - "logs_points_per_chat_description": "How many points are consumed on average for a workflow operation", - "logs_response_time": "Average Response Time", - "logs_search_chat": "Search for session title or session ID", - "logs_source": "source", - "logs_source_count_description": "Number of users across channels", - "logs_title": "Title", - "logs_total": "Grand total", - "logs_total_avg_points": "Average consumption", - "logs_total_chat": "Cumulative conversation count", - "logs_total_error": "{{count}} errors were reported in total, and the error rate was: {{rate}} %", - "logs_total_points": "Accumulated points consumption", - "logs_total_tips": "Cumulative indicators are not affected by time filtering", - "logs_total_users": "Cumulative number of users", - "logs_user_count": "Number of users", - "logs_user_count_description": "Number of people who have a conversation with the app in unit time", - "logs_user_data": "User data", - "logs_user_feedback": "User feedback", - "logs_user_feedback_description": "Like: Number of likes from users\n\nStep on: Users step on the number of points", - "logs_user_retention": "User retention", - "logs_user_retention_description": "Number of users who have added new users during the T cycle and are active in the T 1 cycle", - "look_ai_point_price": "View all model billing standards", - "manual_secret": "Manual secret", - "mark_count": "Number of Marked Answers", - "max_histories_number": "Max histories", - "max_histories_number_tip": "The maximum number of rounds of dialogue that the model can carry into memory. If the memory exceeds the model context, the system will force truncation. \nTherefore, even if 30 rounds of dialogue are configured, the actual number may not reach 30 rounds during operation.", + "logs_points": "Credits consumed", + "logs_points_description": "Credits consumed by the app", + "logs_points_per_chat": "Avg credits consumed per conversation", + "logs_points_per_chat_description": "Avg credits consumed per workflow execution", + "logs_response_time": "Avg response time", + "logs_search_chat": "Topic or ID", + "logs_source": "Source", + "logs_source_count_description": "Users by publishing channel", + "logs_title": "Topic", + "logs_total": "Total", + "logs_total_avg_points": "Avg points consumed", + "logs_total_chat": "Chats", + "logs_total_error": "Total errors: {{count}}, Error rate: {{rate}}%", + "logs_total_points": "Total credits consumed", + "logs_total_tips": "Cumulative metrics are not affected by the time filter.", + "logs_total_users": "Users", + "logs_user_count": "Users", + "logs_user_count_description": "The number of daily users who created chats in the app", + "logs_user_data": "User analysis", + "logs_user_feedback": "Feedback", + "logs_user_feedback_description": "Likes: Number of user likes\nDislikes: Number of user dislikes", + "logs_user_retention": "Retained users", + "logs_user_retention_description": "The number of new users in period T who were active in period T+1", + "look_ai_point_price": "View billing standard for models", + "manual_secret": "Temporary secret key", + "mark_count": "Marked answers", + "max_histories_number": "Chats remembered", + "max_histories_number_tip": "The maximum number of chats that a model can remember. If a chat exceeds the max context length, the system will automatically delete the excess part. Therefore, the model may not actually remember 30 chats even if this field is set to 30.", "max_tokens": "Max tokens", - "module.Custom Title Tip": "This title will be displayed during the conversation.", - "module.No Modules": "No Plugins Found", - "module.type": "\"{{type}}\" type\n{{description}}", - "modules.Title is required": "Module name cannot be empty", - "month.unit": "Day", - "move.hint": "After moving, the selected application/folder will inherit the permission settings of the new folder, and the original permission settings will become invalid.", - "move_app": "Move Application", - "no_mcp_tools_list": "No data yet, the MCP address needs to be parsed first", - "node_not_intro": "This node is not introduced", - "not_json_file": "Please select a JSON file", - "not_the_newest": "Not the latest", - "oaste_curl_string": "Enter CURL code", - "open_auto_execute": "Enable automatic execution", - "open_vision_function_tip": "Models with icon switches have image recognition capabilities. \nAfter being turned on, the model will parse the pictures in the file link and automatically parse the pictures in the user's question (user question ≤ 500 words).", - "or_drag_JSON": "or drag in JSON file", - "paste_config_or_drag": "Paste config or drag JSON file here", - "pdf_enhance_parse": "PDF enhancement analysis", - "pdf_enhance_parse_price": "{{price}}Points/page", - "pdf_enhance_parse_tips": "Calling PDF recognition model for parsing, you can convert it into Markdown and retain pictures in the document. At the same time, you can also identify scanned documents, which will take a long time to identify them.", - "permission.des.manage": "Based on write permissions, you can configure publishing channels, view conversation logs, and assign permissions to the application.", - "permission.des.read": "Use the app to have conversations", - "permission.des.readChatLog": "Can view chat logs", - "permission.des.write": "Can view and edit apps", - "permission.name.read": "Dialogue only", + "module.Custom Title Tip": "The topic will be displayed in the chat.", + "module.No Modules": "Plugin not found.", + "module.type": "{{type}} type\n{{description}}", + "modules.Title is required": "Module name is required.", + "month.unit": "", + "move.hint": "If moved, the app or folder will inherit the permissions of the destination folder, invalidating its current permissions.", + "move_app": "Move", + "no_mcp_tools_list": "No data available. The MCP address must be parsed first.", + "node_not_intro": "No description yet.", + "not_json_file": "Please select a JSON file.", + "not_the_newest": "Earlier version", + "oaste_curl_string": "Enter cURL command", + "open_auto_execute": "Status", + "open_vision_function_tip": "A model with a switch toggled on, as shown below, supports image recognition. If enabled, the model will automatically parse images from file links and images in the questions with less than 500 characters.", + "or_drag_JSON": "Drag & drop to upload JSON file", + "paste_config_or_drag": "Paste configuration or drag & drop a JSON file here", + "pdf_enhance_parse": "Enhanced PDF parsing", + "pdf_enhance_parse_price": "{{price}} credits/page", + "pdf_enhance_parse_tips": "Call a PDF recognition model to parse PDF files, converting them into Markdown format with images preserved, and to process scanned copies of PDF files, which takes longer time.", + "permission.des.manage": "Has write permission and can configure publishing channels, view chat logs, and assign app permissions.", + "permission.des.read": "Chat using the app.", + "permission.des.readChatLog": "View chat logs", + "permission.des.write": "View and edit the app", + "permission.name.read": "Chat only", "permission.name.readChatLog": "View chat logs", - "plugin.Instructions": "Instructions", - "plugin_cost_by_token": "Charged based on token usage", - "plugin_cost_folder_tip": "This tool set contains subordinate tools, and the call points are determined based on the actual calling tool", - "plugin_cost_per_times": "{{cost}} points/time", - "plugin_dispatch": "Plugin Invocation", - "plugin_dispatch_tip": "Adds extra capabilities to the model. The specific plugins to be invoked will be autonomously decided by the model.\nIf a plugin is selected, the Dataset invocation will automatically be treated as a special plugin.", - "pro_modal_feature_1": "External organization structure integration and multi-tenancy", - "pro_modal_feature_2": "Team-exclusive application showcase page", - "pro_modal_feature_3": "Knowledge base enhanced indexing", - "pro_modal_later_button": "Maybe Later", - "pro_modal_subtitle": "Join the business edition now to unlock more premium features", - "pro_modal_title": "Business Edition Exclusive!", - "pro_modal_unlock_button": "Unlock Now", - "publish_channel": "Publish", - "publish_success": "Publish Successful", - "question_guide_tip": "After the conversation, 3 guiding questions will be generated for you.", - "reasoning_response": "Output thinking", + "plugin.Instructions": "Guide", + "plugin_cost_by_token": "Billing based on token consumption", + "plugin_cost_folder_tip": "The toolkit contains multiple tools. The credits are consumed based on the tools used.", + "plugin_cost_per_times": "{{cost}} credits/call", + "plugin_dispatch": "Plugin call", + "plugin_dispatch_tip": "Enable the model to obtain external data. The model will automatically call tools as needed, and run them in non-streaming mode.\nAssociated knowledge bases will be considered special tools.", + "pro_modal_feature_1": "Connection to external organizations and multi-tenancy", + "pro_modal_feature_2": "Dedicated app page for team", + "pro_modal_feature_3": "Enhanced knowledge base indexing", + "pro_modal_later_button": "Not now", + "pro_modal_subtitle": "Subscribe to the commercial edition for advanced features", + "pro_modal_title": "Exclusive to commercial edition", + "pro_modal_unlock_button": "Subscribe", + "publish_channel": "Publishing channels", + "publish_success": "Published successfully.", + "question_guide_tip": "Three relevant question suggestions will be automatically generated for you after the conversation.", + "reasoning_response": "Output reasoning", "response_format": "Response format", - "save_team_app_log_keys": "Save as team configuration", - "saved_success": "Saved successfully! \nTo use this version externally, click Save and Publish", - "search_app": "Search apps", - "search_tool": "Search Tools", - "secret_get_course": "Course", - "setting_app": "Workflow", - "setting_plugin": "Workflow", - "show_top_p_tip": "An alternative method of temperature sampling, called Nucleus sampling, the model considers the results of tokens with TOP_P probability mass quality. \nTherefore, 0.1 means that only tokens containing the highest probability quality are considered. \nThe default is 1.", - "simple_tool_tips": "This plugin contains special inputs and is not currently supported for invocation by simple applications.", - "source_updateTime": "Update time", - "stop_sign": "Stop", - "stop_sign_placeholder": "Multiple serial numbers are separated by |, for example: aaa|stop", - "stream_response": "Stream", - "stream_response_tip": "Turning this switch off forces the model to use non-streaming mode and will not output content directly. \nIn the output of the AI ​​reply, the content output by this model can be obtained for secondary processing.", - "sync_log_keys_popover_text": "The current field configuration is only valid for individuals. Do you need to save it to the team configuration?", - "sync_team_app_log_keys": "Restore to team configuration", - "system_secret": "System secret", - "systemval_conflict_globalval": "The variable name conflicts with the system variable, please use other variable names", - "team_tags_set": "Team tags", + "save_team_app_log_keys": "Save as team config", + "saved_success": "Changes saved successfully. To make the version available to others, please click Save and publish.", + "search_app": "App name", + "search_tool": "Tool name", + "secret_get_course": "Guide", + "setting_app": "App settings", + "setting_plugin": "Plugin settings", + "show_top_p_tip": "Nucleus sampling is an alternative to temperature sampling. By using this method, the model considers only tokens whose cumulative probability exceeds p. For example, if p is set to 0.1, the model will consider only tokens with the highest cumulative probability. Default value: 1.", + "simple_tool_tips": "The plugin cannot be called by simple apps because it contains special inputs.", + "source_updateTime": "Time updated", + "stop_sign": "Stop sequence", + "stop_sign_placeholder": "Separate multiple sequences with the pipe character (|). Example: aaa|stop", + "stream_response": "Stream output", + "stream_response_tip": "If disabled, the model will not output contents immediately, but wait until the entire response is generated. Therefore, it can obtain the output contents for further processing.", + "sync_log_keys_popover_text": "The field configuration will apply only to your account. Would you like to save it as the team configuration?", + "sync_team_app_log_keys": "Restore defaults", + "system_secret": "System secret key", + "systemval_conflict_globalval": "The variable name conflicts with a system variable name. Please use another one.", + "team_tags_set": "Team tag", "temperature": "Temperature", - "temperature_tip": "Range 0~10. \nThe larger the value, the more divergent the model’s answer is; the smaller the value, the more rigorous the answer.", + "temperature_tip": "Range: 0-10 A higher value leads to more creative outputs, while a lower value leads to more deterministic outputs.", "template.hard_strict": "Strict Q&A template", - "template.hard_strict_des": "Based on the question and answer template, stricter requirements are imposed on the model's answers.", + "template.hard_strict_des": "Enforces stricter requirements on the model's answers based on Q&A templates.", "template.qa_template": "Q&A template", - "template.qa_template_des": "A knowledge base suitable for QA question and answer structure, which allows AI to answer strictly according to preset content", - "template.simple_robot": "Simple robot", - "template.standard_strict": "Standard strict template", - "template.standard_strict_des": "Based on the standard template, stricter requirements are imposed on the model's answers.", + "template.qa_template_des": "Requires the model to output based on the predefined content, used for knowledge bases with a Q&A structure.", + "template.simple_robot": "Simple bot", + "template.standard_strict": "Strict standard template", + "template.standard_strict_des": "Enforces stricter requirements on the model's answers based on standard templates.", "template.standard_template": "Standard template", - "template.standard_template_des": "Standard prompt words for knowledge bases with unfixed structures.", - "templateMarket.Search_template": "Search Template", + "template.standard_template_des": "Contains standard prompts, used for knowledge bases with no fixed structure.", + "templateMarket.Search_template": "Template", "templateMarket.Use": "Use", - "templateMarket.no_intro": "No introduction yet~", - "templateMarket.templateTags.Image_generation": "Image Generation", - "templateMarket.templateTags.Office_services": "Office Services", - "templateMarket.templateTags.Recommendation": "Recommendation", - "templateMarket.templateTags.Roleplay": "Roleplay", - "templateMarket.templateTags.Web_search": "Web Search", - "templateMarket.templateTags.Writing": "Writing", - "templateMarket.template_guide": "Guide", + "templateMarket.no_intro": "No description yet.", + "templateMarket.templateTags.Image_generation": "Image generation", + "templateMarket.templateTags.Office_services": "Office service", + "templateMarket.templateTags.Recommendation": "Recommended", + "templateMarket.templateTags.Roleplay": "Role playing", + "templateMarket.templateTags.Web_search": "Search", + "templateMarket.templateTags.Writing": "Content creation", + "templateMarket.template_guide": "Template description", "template_market": "Templates", - "template_market_description": "Explore more features in the template market, with configuration tutorials and usage guides to help you understand and get started with various applications.", - "template_market_empty_data": "No suitable templates found", - "time_zone": "Time Zone", - "too_to_active": "Active", - "tool_active_manual_config_desc": "The temporary key is saved in this application and is only for use by this application.", - "tool_active_system_config_desc": "Use the system configured key", - "tool_active_system_config_price_desc": "Additional payment for key price ({{price}} points/time)", - "tool_active_system_config_price_desc_folder": "The additional key price is required, and the fee will be deducted based on the actual use of the tool.", + "template_market_description": "Explore more possibilities with templates. Follow the configuration and usage guides to develop apps using templates.", + "template_market_empty_data": "No template found.", + "time_zone": "Time zone", + "too_to_active": "Activate", + "tool_active_manual_config_desc": "The temporary secret key is stored in the app and can only be used by the app.", + "tool_active_system_config_desc": "Use the system-configured key", + "tool_active_system_config_price_desc": "Additional price for using the secret key ({{price}} credits/call).", + "tool_active_system_config_price_desc_folder": "Charged based on the tool usage", "tool_detail": "Tool details", - "tool_input_param_tip": "This plugin requires configuration of related information to run properly.", - "tool_not_active": "This tool has not been activated yet", - "tool_run_free": "This tool runs without points consumption", - "tool_type_communication": "Communication", - "tool_type_design": "design", + "tool_input_param_tip": "To run the plugin properly, please configure the required information.", + "tool_not_active": "Not activated.", + "tool_run_free": "Running the tool does not consume credits.", + "tool_type_communication": "Communications", + "tool_type_design": "Design", "tool_type_entertainment": "Business", - "tool_type_finance": "finance", + "tool_type_finance": "Finance", "tool_type_multimodal": "Multimodal", - "tool_type_news": "news", - "tool_type_productivity": "productive forces", - "tool_type_scientific": "research", + "tool_type_news": "News", + "tool_type_productivity": "Productivity", + "tool_type_scientific": "Research", "tool_type_search": "Search", "tool_type_social": "Social", - "tool_type_tools": "tool", - "tools_no_description": "This tool has not been introduced ~", - "transition_to_workflow": "Convert to Workflow", - "transition_to_workflow_create_new_placeholder": "Create a new app instead of modifying the current app", - "transition_to_workflow_create_new_tip": "Once converted to a workflow, it cannot be reverted to simple mode. Please confirm!", - "tts_ai_model": "Use a speech synthesis model", - "tts_browser": "Browser's own (free)", - "tts_close": "Close", + "tool_type_tools": "Tool", + "tools_no_description": "No description yet.", + "transition_to_workflow": "Convert to workflow", + "transition_to_workflow_create_new_placeholder": "Create a new app instead of modifying the current one", + "transition_to_workflow_create_new_tip": "If converted, it cannot be reverted to a simple app. Would you like to proceed?", + "tts_ai_model": "Use text-to-speech model", + "tts_browser": "Built-in in browser (free)", + "tts_close": "Disabled", "type.All": "All", - "type.Create http plugin tip": "Batch create plugins through OpenAPI Schema, compatible with GPTs format.", - "type.Create mcp tools tip": "Automatically parse and batch create callable MCP tools by entering the MCP address", - "type.Create one plugin tip": "Customizable input and output workflows, usually used to encapsulate reusable workflows.", - "type.Create plugin bot": "Create Plugin", - "type.Create simple bot": "Create Simple App", - "type.Create simple bot tip": "Create a simple AI app by filling out a form, suitable for beginners.", - "type.Create workflow bot": "Create Workflow", - "type.Create workflow tip": "Build complex multi-turn dialogue AI applications through low-code methods, recommended for advanced users.", + "type.Create http plugin tip": "Bulk create plugins using OpenAPI schema. They must be compatible with the GPTs format.", + "type.Create mcp tools tip": "Automatically parse the specified MCP address to bulk create callable MCP tools.", + "type.Create one plugin tip": "Encapsulate reusable processes of a workflow, featuring custom inputs and outputs.", + "type.Create plugin bot": "Create plugin", + "type.Create simple bot": "Create simple app", + "type.Create simple bot tip": "Create a simple AI app using a template, designed for beginners.", + "type.Create workflow bot": "Create workflow", + "type.Create workflow tip": "Create a complex AI app capable of multi-round dialogue tasks using low-code methods, designed for advanced users.", "type.Folder": "Folder", - "type.Http plugin": "HTTP Plugin", - "type.Import from json": "Import JSON", - "type.Import from json tip": "Create applications directly through JSON configuration files", - "type.Import from json_error": "Failed to get workflow data, please check the URL or manually paste the JSON data", - "type.Import from json_loading": "Workflow data is being retrieved, please wait...", - "type.MCP tools": "MCP Toolset", - "type.MCP_tools_url": "MCP Address", + "type.Http plugin": "HTTP plugin", + "type.Import from json": "Import", + "type.Import from json tip": "Create an app by importing a JSON configuration file.", + "type.Import from json_error": "Failed to obtain workflow data. Please check the URL or paste the JSON data.", + "type.Import from json_loading": "Loading, please wait.", + "type.MCP tools": "MCP toolkit", + "type.MCP_tools_url": "MCP address", "type.Plugin": "Plugin", - "type.Simple bot": "Simple App", + "type.Simple bot": "Simple app", "type.Tool": "Tool", - "type.Tool set": "Toolset", + "type.Tool set": "toolkit", "type.Workflow bot": "Workflow", - "type.error.Workflow data is empty": "No workflow data was obtained", - "type.error.workflowresponseempty": "Response content is empty", - "type.hidden": "Hide app", - "type_not_recognized": "App type not recognized", + "type.error.Workflow data is empty": "Failed to obtain workflow data.", + "type.error.workflowresponseempty": "Response is empty.", + "type.hidden": "Hidden app", + "type_not_recognized": "Failed to recognize the app type.", "un_auth": "No permission", - "upload_file_max_amount": "Maximum File Quantity", - "upload_file_max_amount_tip": "Maximum number of files uploaded in a single round of conversation", - "variable.select type_desc": "You can define a global variable that does not need to be filled in by the user.\n\nThe value of this variable can come from the API interface, the Query of the shared link, or assigned through the [Variable Update] module.", - "variable.textarea_type_desc": "Allows users to input up to 4000 characters in the dialogue box.", - "variable_name_required": "Required variable name", - "variable_repeat": "This variable name has been occupied and cannot be used", - "version.Revert success": "Revert Successful", - "version_back": "Revert to Original State", - "version_copy": "Duplicate", - "version_initial_copy": "Duplicate - Original State", - "vision_model_title": "Image recognition ability", + "upload_file_max_amount": "Max files", + "upload_file_max_amount_tip": "The maximum number of files that can be uploaded in a chat", + "variable.select type_desc": "Configure global variables for a workflow. These global variables are typically used for temporary caching. Variables can be specified in the following ways:\n1. Use query parameters on the chat page.\n2. Use variables in the API request.\n3. Use a node with the variable update tool.", + "variable.textarea_type_desc": "Up to 4,000 characters allowed.", + "variable_name_required": "Variable name is required.", + "variable_repeat": "The variable name already exists.", + "version.Revert success": "Rollback successful.", + "version_back": "Roll back to initial status", + "version_copy": "Replica", + "version_initial_copy": "Replica - Initial status", + "vision_model_title": "Image recognition", "week.Friday": "Friday", "week.Monday": "Monday", "week.Saturday": "Saturday", @@ -329,22 +329,40 @@ "week.Thursday": "Thursday", "week.Tuesday": "Tuesday", "week.Wednesday": "Wednesday", - "workflow.Input guide": "Input Guide", - "workflow.file_url": "Document Link", + "workflow.Input guide": "Enter description", + "workflow.file_url": "Document link", "workflow.form_input": "Form input", - "workflow.form_input_description_placeholder": "For example: \nAdd your information", - "workflow.form_input_tip": " This module can configure multiple inputs to guide users in entering specific content.", - "workflow.input_description_tip": "You can add a description to explain to users what they need to input", - "workflow.read_files": "Document Parse", - "workflow.read_files_result": "Document Parsing Result", - "workflow.read_files_result_desc": "Original document text, consisting of file names and document content, separated by hyphens between multiple files.", - "workflow.read_files_tip": "Parse the documents uploaded in this round of dialogue and return the corresponding document content", - "workflow.select_description": "Description Text", - "workflow.select_description_placeholder": "For example: \nAre there tomatoes in the fridge?", - "workflow.select_description_tip": "You can add a description text to explain the meaning of each option to the user.", - "workflow.select_result": "Selected Result", - "workflow.user_file_input": "File Link", - "workflow.user_file_input_desc": "Links to documents and images uploaded by users.", - "workflow.user_select": "User Select", - "workflow.user_select_tip": "This module can configure multiple options for selection during the dialogue. Different options can lead to different workflow branches." -} + "workflow.form_input_description_placeholder": "Example:\nComplete your information", + "workflow.form_input_tip": "You can configure multiple inputs to guide users in entering specific content.", + "workflow.input_description_tip": "Add a description to explain to users what they need to enter.", + "workflow.read_files": "Document parsing", + "workflow.read_files_result": "Document parsing result", + "workflow.read_files_result_desc": "A file consists of a file name and the content. Multiple files are separated by hyphens.", + "workflow.read_files_tip": "Parse the file uploaded in the current conversation and return the result.", + "workflow.select_description": "Description", + "workflow.select_description_placeholder": "Example:\nIs there any tomato in the refrigerator?", + "workflow.select_description_tip": "Add a description to explain to users the definition of each option.", + "workflow.select_result": "Select variable name", + "workflow.user_file_input": "File link", + "workflow.user_file_input_desc": "File and image link uploaded by users", + "workflow.user_select": "User selection", + "workflow.user_select_tip": "You can configure multiple options for users to select during the chat. Different options direct the chat to different workflow branches.", + "files_cascader_no_knowledge_base": "Do not add to knowledge base", + "files_cascader_select_knowledge_base": "Select", + "files_cascader_select_first": "Please select a knowledge base first.", + "files_cascader_dataset_empty": "This knowledge base dataset is empty.", + "select_join_location": "Select target", + "no_data_for_smart_generate": "Unselectable because it contains no available data.", + "logs_bad_feedback": "Dislike", + "logs_source_count": "User by channel", + "logs_timespan_day": "Daily", + "logs_timespan_month": "Monthly", + "logs_timespan_quarter": "Quarterly", + "logs_timespan_week": "Weekly", + "logs_total_avg_duration": "Avg duration", + "logs_total_feedback": "Likes: {{goodFeedBack}} | Dislikes: {{badFeedBack}}", + "logs_user_callback": "Feedback", + "team.menu.plugin": "Plugins", + "team.menu.app": "Simple apps", + "team.menu.workflow": "Workflows" +} \ No newline at end of file diff --git a/packages/web/i18n/en/chat.json b/packages/web/i18n/en/chat.json index 5940a4fb5bb9..115a89a3f122 100644 --- a/packages/web/i18n/en/chat.json +++ b/packages/web/i18n/en/chat.json @@ -1,131 +1,139 @@ { - "AI_input_is_empty": "The content passed to the AI ​​node is empty", - "Delete_all": "Clear All Lexicon", - "LLM_model_response_empty": "The model flow response is empty, please check whether the model flow output is normal.", - "ai_reasoning": "Thinking process", - "back_to_text": "Text input", - "chat.quote.No Data": "The file cannot be found", - "chat.quote.deleted": "This data has been deleted ~", - "chat.waiting_for_response": "Please wait for the conversation to complete", - "chat_history": "Conversation History", - "chat_input_guide_lexicon_is_empty": "Lexicon not configured yet", - "chat_test_app": "Debug-{{name}}", - "citations": "{{num}} References", - "click_contextual_preview": "Click to see contextual preview", - "completion_finish_close": "Disconnection", - "completion_finish_content_filter": "Trigger safe wind control", - "completion_finish_function_call": "Function Calls", - "completion_finish_length": "Reply limit exceeded", - "completion_finish_null": "unknown", - "completion_finish_reason": "Reason for completion", - "completion_finish_stop": "Completed normally", - "completion_finish_tool_calls": "Tool calls", - "config_input_guide": "Set Up Input Guide", - "config_input_guide_lexicon": "Set Up Lexicon", - "config_input_guide_lexicon_title": "Set Up Lexicon", - "content_empty": "No Content", - "contextual": "{{num}} Contexts", - "contextual_preview": "Contextual Preview {{num}} Items", - "core.chat.moveCancel": "Swipe to Cancel", - "core.chat.shortSpeak": "Speaking Time is Too Short", - "csv_input_lexicon_tip": "Only CSV batch import is supported, click to download the template", - "custom_input_guide_url": "Custom Lexicon URL", - "data_source": "Source Dataset: {{name}}", - "dataset_quote_type error": "Knowledge base reference type is wrong, correct type: { datasetId: string }[]", - "delete_all_input_guide_confirm": "Are you sure you want to clear the input guide lexicon?", - "download_chunks": "Download data", - "empty_directory": "This directory is empty~", - "error_message": "error message", - "file_amount_over": "Exceeded maximum file quantity {{max}}", - "file_input": "File input", - "file_input_tip": "You can obtain the link to the corresponding file through the \"File Link\" of the [Plug-in Start] node", - "history_slider.home.title": "chat", - "home.chat_app": "HomeChat-{{name}}", - "home.chat_id": "Chat ID", - "home.no_available_tools": "No tools available", - "home.select_tools": "Select Tool", - "home.tools": "Tool: {{num}}", - "in_progress": "In Progress", - "input_guide": "Input Guide", - "input_guide_lexicon": "Lexicon", - "input_guide_tip": "You can set up some preset questions. When the user inputs a question, related questions from these presets will be suggested.", - "input_placeholder_phone": "Please enter your question", - "insert_input_guide,_some_data_already_exists": "Duplicate data detected, automatically filtered, {{len}} items inserted", - "invalid_share_url": "Invalid sharing link", - "is_chatting": "Chatting in progress... please wait until it finishes", + "AI_input_is_empty": "The content passed to the node is empty.", + "Delete_all": "Clear", + "LLM_model_response_empty": "The output of the model in streaming mode is empty. Please check whether the model output works properly in streaming mode.", + "ai_reasoning": "Reasoning process", + "back_to_text": "Return to input", + "chat.quote.No Data": "Unable to find the file.", + "chat.quote.deleted": "The data has been deleted.", + "chat.waiting_for_response": "Please wait for the chat to complete.", + "chat_history": "Chat records", + "chat_input_guide_lexicon_is_empty": "No word library available.", + "chat_test_app": "Debug - {{name}}", + "citations": "References: {{num}}", + "click_contextual_preview": "Click to preview context", + "completion_finish_close": "Disconnected", + "completion_finish_content_filter": "Content security audit triggered", + "completion_finish_function_call": "Function call", + "completion_finish_length": "The output has exceeded the maximum.", + "completion_finish_null": "Unknown", + "completion_finish_reason": "Completion cause", + "completion_finish_stop": "Completed successfully.", + "completion_finish_tool_calls": "Tool call", + "config_input_guide": "Click to configure", + "config_input_guide_lexicon": "Settings", + "config_input_guide_lexicon_title": "Settings", + "content_empty": "Content is empty.", + "contextual": "{{num}} contexts", + "contextual_preview": "Contexts previewed: {{num}}", + "core.chat.moveCancel": "Swipe up to cancel", + "core.chat.shortSpeak": "The speech is too short.", + "csv_input_lexicon_tip": "Only CSV files can be imported. Click to download the template.", + "custom_input_guide_url": "Custom word library address", + "data_source": "Source knowledge base: {{name}}", + "dataset_quote_type error": "Incorrect reference type of knowledge base. Correct type: { datasetId: string }[]", + "delete_all_input_guide_confirm": "Are you sure you want to clear the word library?", + "download_chunks": "Download", + "empty_directory": "No items selectable in the directory.", + "error_message": "Error details", + "file_amount_over": "The number of files exceeds the maximum ({{max}}).", + "file_input": "System file", + "file_input_tip": "You can obtain the required file link through the file link field in the plugin input node.", + "history_slider.home.title": "Chat", + "home.chat_app": "Home page chat - {{name}}", + "home.chat_id": "Conversation ID", + "home.no_available_tools": "No tools available.", + "home.select_tools": "Select", + "home.tools": "Tools: {{num}}", + "in_progress": "Ongoing", + "input_guide": "Input guide", + "input_guide_lexicon": "Word library", + "input_guide_tip": "Configure preset questions. When a user enters a question, related preset questions will be displayed as prompts.", + "input_placeholder_phone": "Enter question", + "insert_input_guide,_some_data_already_exists": "Duplicate entries were detected and automatically filtered. {{len}} entries were inserted.", + "invalid_share_url": "Invalid sharing link.", + "is_chatting": "Chatting, please wait.", "items": "Items", "llm_tokens": "LLM tokens", - "module_runtime_and": "Total Module Runtime", - "multiple_AI_conversations": "Multiple AI Conversations", - "new_input_guide_lexicon": "New Lexicon", - "no_invalid_app": "There are no available applications under your account", - "no_workflow_response": "No workflow data", - "not_query": "Missing query content", - "not_select_file": "No file selected", - "plugins_output": "Plugin Output", - "press_to_speak": "Hold down to speak", - "query_extension_IO_tokens": "Problem Optimization Input/Output Tokens", - "query_extension_result": "Problem optimization results", - "question_tip": "From top to bottom, the response order of each module", - "read_raw_source": "Open the original text", - "reasoning_text": "Thinking process", - "release_cancel": "Release Cancel", - "release_send": "Release send, slide up to cancel", - "response.child total points": "Sub-workflow point consumption", - "response.dataset_concat_length": "Combined total", - "response.node_inputs": "Node Inputs", + "module_runtime_and": "Total workflow uptime", + "multiple_AI_conversations": "Multiple AI chats", + "new_input_guide_lexicon": "New word", + "no_invalid_app": "No apps available.", + "no_workflow_response": "No running data available.", + "not_query": "Query content is missing.", + "not_select_file": "No file is selected.", + "plugins_output": "Plugin output", + "press_to_speak": "Hold to talk", + "query_extension_IO_tokens": "Input/output tokens for question optimization", + "query_extension_result": "Question optimization result", + "question_tip": "The following shows the response sequence.", + "read_raw_source": "View source text", + "reasoning_text": "Reasoning process", + "release_cancel": "Release to cancel", + "release_send": "Release to send, swipe up to cancel", + "response.child total points": "Credits consumed by sub-workflow", + "response.dataset_concat_length": "Total shards after merging", + "response.node_inputs": "Node input", "response_embedding_model": "Vector model", - "response_embedding_model_tokens": "Vector Model Tokens", - "response_hybrid_weight": "Embedding : Full text = {{emb}} : {{text}}", - "response_rerank_tokens": "Rearrange Model Tokens", - "search_results": "Search results", + "response_embedding_model_tokens": "Vector model tokens", + "response_hybrid_weight": "Ratio of semantic search to full-text search: {{emb}}/{{text}}", + "response_rerank_tokens": "Reranker model tokens", + "search_results": "Search result", "select": "Select", - "select_file": "Upload File", - "select_file_img": "Upload file / image", - "select_img": "Upload Image", - "setting.copyright.basic_configuration": "Basic configuration", - "setting.copyright.copyright_configuration": "Copyright configuration", - "setting.copyright.diagram": "Schematic diagram", - "setting.copyright.file_size_exceeds_limit": "File size exceeds the limit, maximum support for {{maxSize}}", - "setting.copyright.immediate_upload_required": "Immediate upload is required for this feature", + "select_file": "Upload file", + "select_file_img": "Upload file/image", + "select_img": "Upload image", + "setting.copyright.basic_configuration": "Basics", + "setting.copyright.copyright_configuration": "Copyright", + "setting.copyright.diagram": "Preview", + "setting.copyright.file_size_exceeds_limit": "The file size exceeds the maximum ({{maxSize}}).", + "setting.copyright.immediate_upload_required": "Upload an image to use the feature.", "setting.copyright.logo": "Logo", - "setting.copyright.preview_fail": "File preview failed", - "setting.copyright.save_fail": "Logo failed to save", - "setting.copyright.save_success": "Logo Saved successfully", - "setting.copyright.select_logo_image": "Please select the logo image to upload first", - "setting.copyright.style_diagram": "Style diagram", - "setting.copyright.tips": "Suggested ratio 4:1", - "setting.copyright.tips.square": "Suggested ratio 1:1", - "setting.copyright.title": "Copyright", - "setting.copyright.upload_fail": "File upload failed", - "setting.data_dashboard.title": "Data board", - "setting.fastgpt_chat_diagram": "/imgs/chat/fastgpt_chat_diagram_en.png", + "setting.copyright.preview_fail": "Failed to preview the file.", + "setting.copyright.save_fail": "Failed to save the logo.", + "setting.copyright.save_success": "Logo saved successfully.", + "setting.copyright.select_logo_image": "Please select a logo image first.", + "setting.copyright.style_diagram": "Preview", + "setting.copyright.tips": "Recommended ratio: 4:1", + "setting.copyright.tips.square": "Recommended ratio: 1:1", + "setting.copyright.title": "Copyright info", + "setting.copyright.upload_fail": "Failed to upload the file.", + "setting.data_dashboard.title": "Dashboard", + "setting.fastgpt_chat_diagram": "/imgs/chat/fastgpt_chat_diagram.png", "setting.home.available_tools.add": "Add", - "setting.home.commercial_version": "Commercial version", - "setting.home.diagram": "Schematic diagram", - "setting.home.dialogue_tips": "Dialog prompt text", - "setting.home.dialogue_tips.default": "You can ask me any questions", - "setting.home.dialogue_tips_placeholder": "Please enter the prompt text of the dialog box", - "setting.home.home_tab_title": "Home Page Title", - "setting.home.home_tab_title_placeholder": "Please enter the title of the homepage", + "setting.home.commercial_version": "Commercial edition", + "setting.home.diagram": "Preview", + "setting.home.dialogue_tips": "Chat box prompt", + "setting.home.dialogue_tips.default": "You can ask me anything.", + "setting.home.dialogue_tips_placeholder": "", + "setting.home.home_tab_title": "Homepage title", + "setting.home.home_tab_title_placeholder": "Homepage title is required.", "setting.home.slogan": "Slogan", - "setting.home.slogan.default": "Hello 👋, I am FastGPT! Is there anything I can help you?", - "setting.home.slogan_placeholder": "Please enter Slogan", - "setting.home.title": "Home", - "setting.incorrect_plan": "The current plan does not support this feature, please upgrade to the subscription plan", - "setting.incorrect_version": "This feature is not supported in the current version", - "setting.log_details.title": "Home Log", - "setting.logs.title": "Homepage log", + "setting.home.slogan.default": "Hi 👋, I'm FastGPT. How can I help you today?", + "setting.home.slogan_placeholder": "", + "setting.home.title": "Homepage settings", + "setting.incorrect_plan": "The current plan does not support this feature. Please upgrade your subscription plan.", + "setting.incorrect_version": "The current version does not support this feature.", + "setting.log_details.title": "Logs", + "setting.logs.title": "Logs", "setting.save": "Save", - "setting.save_success": "Save successfully", + "setting.save_success": "Changes saved successfully.", "sidebar.home": "Home", - "sidebar.team_apps": "Team Apps", + "sidebar.team_apps": "Team apps", "source_cronJob": "Scheduled execution", - "start_chat": "Start", - "stream_output": "Stream Output", - "unsupported_file_type": "Unsupported file types", + "start_chat": "Chat now", + "stream_output": "Stream output", + "unsupported_file_type": "File type is not supported.", "upload": "Upload", - "variable_invisable_in_share": "Custom variables are not visible in login-free links", - "view_citations": "View References", - "web_site_sync": "Web Site Sync" -} + "variable_invisable_in_share": "Custom variables are not visible in the login-free link.", + "view_citations": "View reference", + "web_site_sync": "Website sync", + "setting.home.available_tools": "Available tools", + "setting.share": "Share", + "embedding_model_error": "The vector model encountered error. Please check its configuration.", + "language_model_error": "Error occurred while requesting the LLM. Please check the model configuration.", + "database_sql_query": "SQL statement", + "search_result": "Search result", + "database_search_results": "Database search results", + "other_knowledge_base_search_results": "Search results from other knowledge bases" +} \ No newline at end of file diff --git a/packages/web/i18n/en/common.json b/packages/web/i18n/en/common.json index 4a4a4a6db907..23546d7925d1 100644 --- a/packages/web/i18n/en/common.json +++ b/packages/web/i18n/en/common.json @@ -1,56 +1,56 @@ { - "Action": "Action", + "Action": "Operation", "Add": "Add", - "Add_new_input": "Add new input", + "Add_new_input": "Add input", "All": "All", - "App": "Application", + "App": "App", "Cancel": "Cancel", - "Choose": "Choose", - "Click_to_expand": "Click to expand", + "Choose": "Select", + "Click_to_expand": "Click to view details.", "Close": "Close", - "Code": "Code", + "Code": "Source code", "Config": "Configuration", - "Confirm": "Confirm", - "Continue_Adding": "Continue adding", + "Confirm": "OK", + "Continue_Adding": "Save & add more", "Copy": "Copy", "Creating": "Creating", "Delete": "Delete", - "Detail": "Detail", - "Documents": "Documents", - "Done": "Done", + "Detail": "Details", + "Documents": "Document", + "Done": "Save", "Download": "Download", "Edit": "Edit", "Error": "Error", "Exit": "Exit", "Export": "Export", - "FAQ.ai_point_a": "Each time an AI model is called, a certain amount of AI points will be consumed. \nFor specific calculation standards, please refer to the \"AI integral calculation standards\" above. \nThe system will give priority to the actual usage returned by the model manufacturer. If it is empty, the calculation method of GPT3.5 is used for estimation. 1Token≈0.7 Chinese characters ≈0.9 English words, and the characters that appear continuously may be considered as 1 Tokens.", - "FAQ.ai_point_expire_a": "Yes, they will expire. After the current package expires, the AI points will be reset to the new package's AI points. Annual package AI points are valid for one year, not monthly.", - "FAQ.ai_point_expire_q": "Do AI points expire?", - "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 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?", - "FAQ.free_user_clean_a": "If a free team (free version and has not purchased additional packages) does not log in to the system for 30 consecutive days, the system will automatically clear all Dataset content under that team.", - "FAQ.free_user_clean_q": "Will the data of the free version be cleared?", - "FAQ.package_overlay_a": "Yes, each purchased resource pack is independent and will be used in an overlapping manner within its validity period. AI points will be deducted from the resource pack that expires first.", - "FAQ.package_overlay_q": "Can additional resource packs be stacked?", - "FAQ.switch_package_a": "The package usage rule is to prioritize the use of higher-level packages. Therefore, if the newly purchased package is higher than the current package, the new package will take effect immediately; otherwise, the current package will continue to be used.", - "FAQ.switch_package_q": "Will the subscription package be switched?", - "File": "File", + "FAQ.ai_point_a": "Model calls consume credits. For details, refer to the billing standard above. The system prioritizes the usage data returned by the model provider. If no usage data is returned, it estimates token consumption using the calculation method of GPT-3.5: 1 token ≈ 0.7 Chinese characters ≈ 0.9 English words. Consecutive characters may be counted as 1 token.", + "FAQ.ai_point_expire_a": "Yes. After the current plan expires, credits will be cleared and updated according to the new plan. The credits for an annual plan are valid for one year.", + "FAQ.ai_point_expire_q": "Do credits expire?", + "FAQ.ai_point_q": "What are credits?", + "FAQ.check_subscription_a": "On the Account > Profile > Plan details > Usage page, you can view the valid and expiration time of your plans. The paid plan will automatically switch to the free edition after expiration.", + "FAQ.check_subscription_q": "Where can I view my subscription plans?", + "FAQ.dataset_compute_a": "One entry stored in the knowledge base equals one knowledge base index. One chunked entry usually corresponds to multiple indexes. Therefore, n groups of indexes can be found in one knowledge base collection.", + "FAQ.dataset_compute_q": "How is knowledge base storage calculated?", + "FAQ.dataset_index_a": "No. However, you cannot insert or update any content in the knowledge base.", + "FAQ.dataset_index_q": "Will extra entries in a knowledge base be deleted when the number of entries exceeds the maximum?", + "FAQ.free_user_clean_a": "If a team using the free edition without any other subscription plans is inactive for 30 consecutive days, its knowledge bases will be cleared automatically.", + "FAQ.free_user_clean_q": "Will data in the free edition be cleared?", + "FAQ.package_overlay_a": "Yes. Purchased resource packages are independent and can be used concurrently within their validity periods. Credits for the resource package that expires first will be consumed first.", + "FAQ.package_overlay_q": "Can multiple additional resource packages be used concurrently?", + "FAQ.switch_package_a": "Subscription plan usage follows a priority rule where a plan with higher level is used first. Therefore, if the level of a new plan is higher than the current one, it takes effect immediately. Otherwise, the current plan will not be switched.", + "FAQ.switch_package_q": "Can I switch subscription plans?", + "File": "Files", "Finish": "Finish", "Folder": "Folder", - "FullScreen": "FullScreen", - "FullScreenLight": "FullScreenLight", + "FullScreen": "Full screen", + "FullScreenLight": "Full-screen preview", "Import": "Import", "Input": "Input", - "Instructions": "Instruction", - "Intro": "Introduction", + "Instructions": "Guide", + "Intro": "Description", "Loading": "Loading...", - "Login": "Login", - "Manual": "Manual", + "Login": "Log in", + "Manual": "Temporary", "More": "More", "Move": "Move", "Name": "Name", @@ -60,29 +60,29 @@ "Operation": "Operation", "Other": "Other", "Output": "Output", - "Params": "Parameters", - "Parse": "Analysis", + "Params": "Settings", + "Parse": "Parse", "Permission": "Permission", - "Permission_tip": "Individual permissions are greater than group permissions", + "Permission_tip": "Individual permissions take precedence over group permissions.", "Preview": "Preview", "Remove": "Remove", "Rename": "Rename", "Required_input": "Required", - "Reset": "Reset", + "Reset": "Restore defaults", "Restart": "Restart", - "Resume": "Resume", + "Resume": "Restore", "Role": "Permission", "Run": "Run", "Running": "Running", "Save": "Save", - "Save_and_exit": "Save and Exit", + "Save_and_exit": "Save & exit", "Search": "Search", - "Select_App": "Select an application", + "Select_App": "Select", "Select_all": "Select all", - "Setting": "Setting", + "Setting": "Settings", "Status": "Status", "Submit": "Submit", - "Success": "Success", + "Success": "Successful", "System": "System", "Team": "Team", "UnKnow": "Unknown", @@ -93,1251 +93,1282 @@ "Warning": "Warning", "Website": "Website", "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_evaluation": "App Evaluation(Beta)", - "app_not_version": "This application has not been published, please publish it first", - "auth_config": "Authentication", + "add_new": "Add", + "add_new_param": "Add parameter", + "add_success": "Added successfully.", + "all_quotes": "Reference all", + "all_result": "Complete result", + "app_evaluation": "App evaluation (Beta)", + "app_not_version": "The app is not published. Please publish it first.", + "auth_config": "Authentication settings", "auth_type": "Authentication type", - "auth_type.Custom": "Customize", + "auth_type.Custom": "Custom", "auth_type.None": "None", "back": "Back", - "base_config": "Basic Configuration", - "bill_already_processed": "Order has been processed", - "bill_expired": "Order expired", - "bill_not_pay_processed": "Non-online orders", - "button.extra_dataset_size_tip": "You are purchasing [Extra Knowledge Base Capacity]", - "button.extra_points_tip": "You are purchasing [Extra AI Points]", - "can_copy_content_tip": "It is not possible to copy automatically using the browser, please manually copy the following content", + "base_config": "Basics", + "bill_already_processed": "Processed", + "bill_expired": "Expired", + "bill_not_pay_processed": "Offline", + "button.extra_dataset_size_tip": "You are purchasing additional knowledge base index capacity.", + "button.extra_points_tip": "You are purchasing additional credits.", + "can_copy_content_tip": "Auto copy is unavailable. Please manually copy the content below.", "chart_mode_cumulative": "Cumulative", - "chart_mode_incremental": "Incremental", - "chat": "Session", - "chat_chatId": "Session Id: {{chatId}}", - "choosable": "Choosable", - "chose_condition": "Choose Condition", - "chosen": "Chosen", - "classification": "Classification", - "click_drag_tip": "Click to Drag", - "click_select_avatar": "Click to Select Avatar", + "chart_mode_incremental": "Periodic", + "chat": "Conversation", + "chat_chatId": "Conversation ID: {{chatId}}", + "choosable": " Optional", + "chose_condition": "Operator", + "chosen": "Selected", + "classification": "Type", + "click_drag_tip": "Click and drag", + "click_select_avatar": "Click to select a profile image.", "click_to_copy": "Click to copy", - "click_to_resume": "Click to Resume", - "code_editor": "Code Editor", - "code_error.account_error": "Incorrect account name or password", - "code_error.account_exist": "Account has been registered", - "code_error.account_not_found": "User is not registered", - "code_error.app_error.can_not_edit_admin_permission": "Can not edit admin permission", - "code_error.app_error.invalid_app_type": "Invalid Application Type", - "code_error.app_error.invalid_owner": "Unauthorized Application Owner", - "code_error.app_error.not_exist": "Application Does Not Exist", - "code_error.app_error.un_auth_app": "Unauthorized to Operate This Application", - "code_error.chat_error.un_auth": "Unauthorized to Operate This Chat Record", - "code_error.error_code.400": "Request Failed", - "code_error.error_code.401": "No Access Permission", - "code_error.error_code.403": "Access Forbidden", - "code_error.error_code.404": "Request Not Found", - "code_error.error_code.405": "Request Method Error", - "code_error.error_code.406": "Request Format Error", - "code_error.error_code.410": "Resource Deleted", - "code_error.error_code.422": "Validation Error", - "code_error.error_code.500": "Server Error", - "code_error.error_code.502": "Gateway Error", - "code_error.error_code.503": "Server Overloaded or Under Maintenance", - "code_error.error_code.504": "Gateway Timeout", - "code_error.error_code[429]": "Requests are too frequent", - "code_error.error_message.403": "Credential Error", - "code_error.error_message.510": "Insufficient Account Balance", - "code_error.error_message.511": "Unauthorized to Operate This Model", - "code_error.error_message.513": "Unauthorized to Read This File", - "code_error.error_message.514": "Invalid API Key", - "code_error.openapi_error.api_key_not_exist": "API Key Does Not Exist", - "code_error.openapi_error.exceed_limit": "Up to 10 API Keys", - "code_error.openapi_error.un_auth": "Unauthorized to Operate This API Key", - "code_error.outlink_error.invalid_link": "Invalid Share Link", - "code_error.outlink_error.link_not_exist": "Share Link Does Not Exist", - "code_error.outlink_error.un_auth_user": "Identity Verification Failed", - "code_error.plugin_error.not_exist": "The tool does not exist", - "code_error.plugin_error.un_auth": "No permission to operate the tool", - "code_error.system_error.community_version_num_limit": "Exceeded Open Source Version Limit, Please Upgrade to Commercial Version: https://fastgpt.io", - "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", - "code_error.team_error.cannot_delete_non_empty_org": "Cannot delete non-empty organization", - "code_error.team_error.cannot_modify_root_org": "Cannot modify root organization", - "code_error.team_error.cannot_move_to_sub_path": "Cannot move to same or subdirectory", - "code_error.team_error.dataset_amount_not_enough": "Dataset Limit Reached", - "code_error.team_error.dataset_size_not_enough": "Insufficient Dataset Capacity, Please Expand", - "code_error.team_error.group_name_duplicate": "Duplicate group name", - "code_error.team_error.group_name_empty": "Group name cannot be empty", - "code_error.team_error.group_not_exist": "Group does not exist", - "code_error.team_error.invitation_link_invalid": "The invitation link has expired", - "code_error.team_error.not_user": "The member cannot be found", - "code_error.team_error.org_member_duplicated": "Duplicate organization member", - "code_error.team_error.org_member_not_exist": "Organization member does not exist", - "code_error.team_error.org_not_exist": "Organization does not exist", - "code_error.team_error.org_parent_not_exist": "Parent organization does not exist", - "code_error.team_error.over_size": "Team members exceed limit", - "code_error.team_error.plugin_amount_not_enough": "Plugin Limit Reached", - "code_error.team_error.re_rank_not_enough": "Search rearrangement cannot be used in the free version~", - "code_error.team_error.too_many_invitations": "You have reached the maximum number of active invitation links, please clean up some links first", - "code_error.team_error.un_auth": "Unauthorized to Operate This Team", - "code_error.team_error.user_not_active": "The user did not accept or has left the team", - "code_error.team_error.website_sync_not_enough": "The free version cannot be synchronized with the web site ~", - "code_error.team_error.you_have_been_in_the_team": "You are already in this team", - "code_error.token_error_code.403": "Invalid Login Status, Please Re-login", - "code_error.user_error.balance_not_enough": "Insufficient Account Balance", - "code_error.user_error.bin_visitor_guest": "You Are Currently a Guest, Unauthorized to Operate", - "code_error.user_error.un_auth_user": "User Not Found", - "comfirm_import": "Confirm import", - "comfirm_leave_page": "Confirm to Leave This Page?", - "comfirn_create": "Confirm Creation", - "commercial_function_tip": "Please Upgrade to the Commercial Version to Use This Feature: https://doc.fastgpt.cn/docs/introduction/commercial/", - "comon.Continue_Adding": "Continue Adding", - "compliance.chat": "The content is generated by third-party AI and cannot be guaranteed to be true and accurate. It is for reference only.", - "compliance.dataset": "Please ensure that your content strictly complies with relevant laws and regulations and avoid containing any illegal or infringing content. \nPlease be careful when uploading materials that may contain sensitive information.", - "confirm_choice": "Confirm Choice", - "confirm_logout": "Confirm to log out?", - "confirm_move": "Move Here", - "confirm_update": "confirm_update", - "contact_way": "Notification Received", - "contribute_app_template": "Contribute Template", - "copy_successful": "Copied Successfully", - "copy_to_clipboard": "Copy to Clipboard", + "click_to_resume": "Click to restore", + "code_editor": "Edit code", + "code_error.account_error": "Username or password is invalid.", + "code_error.account_exist": "The account already exists.", + "code_error.account_not_found": "The account does not exist.", + "code_error.app_error.can_not_edit_admin_permission": "Admin permissions cannot be edited.", + "code_error.app_error.invalid_app_type": "Invalid app type.", + "code_error.app_error.invalid_owner": "Invalid app owner.", + "code_error.app_error.not_exist": "The app does not exist.", + "code_error.app_error.un_auth_app": "You do not have permission to perform operations on the app.", + "code_error.chat_error.un_auth": "You do not have permission to perform operations on the chat record.", + "code_error.error_code.400": "Request failed.", + "code_error.error_code.401": "You do not have access permission.", + "code_error.error_code.403": "Access denied", + "code_error.error_code.404": "The request does not exist.", + "code_error.error_code.405": "Request method error.", + "code_error.error_code.406": "Request format is invalid.", + "code_error.error_code.410": "The resource has been deleted.", + "code_error.error_code.422": "Authentication error.", + "code_error.error_code.500": "The server encountered error.", + "code_error.error_code.502": "Gateway error.", + "code_error.error_code.503": "The server is temporarily overloaded or under maintenance.", + "code_error.error_code.504": "Gateway timed out.", + "code_error.error_code.429": "Too many requests.", + "code_error.error_message.403": "Credential error.", + "code_error.error_message.510": "The account balance is insufficient.", + "code_error.error_message.511": "You do not have permission on the model.", + "code_error.error_message.513": "You do not have permission to read the file.", + "code_error.error_message.514": "Invalid API key.", + "code_error.openapi_error.api_key_not_exist": "The API key does not exist.", + "code_error.openapi_error.exceed_limit": "Up to 10 API keys can be created.", + "code_error.openapi_error.un_auth": "You do not have permission on the API key.", + "code_error.outlink_error.invalid_link": "Invalid sharing link.", + "code_error.outlink_error.link_not_exist": "The sharing link does not exist.", + "code_error.outlink_error.un_auth_user": "Identity verification failed.", + "code_error.plugin_error.not_exist": "The tool does not exist.", + "code_error.plugin_error.un_auth": "You do not have permission on the tool.", + "code_error.system_error.community_version_num_limit": "The number of resources exceeded the maximum allowed by the community edition. Please upgrade to the commercial edition: https://fastgpt.in", + "code_error.system_error.license_app_amount_limit": "The number of apps has exceeded the maximum.", + "code_error.system_error.license_dataset_amount_limit": "The number of knowledge bases has exceeded the maximum.", + "code_error.system_error.license_user_amount_limit": "The number of users has exceeded the maximum.", + "code_error.system_error.license_evaluation_task_amount_limit": "The number of evaluation tasks exceeds the maximum.", + "code_error.system_error.license_eval_dataset_amount_limit": "The number of evaluation datasets exceeds the maximum.", + "code_error.system_error.license_eval_dataset_data_amount_limit": "The evaluation dataset size exceeds the maximum.", + "code_error.system_error.license_eval_metric_amount_limit": "The number of evaluation metrics exceeds the maximum.", + "code_error.team_error.ai_points_not_enough": "Credits are insufficient.", + "code_error.team_error.app_amount_not_enough": "The number of apps reaches the maximum.", + "code_error.team_error.cannot_delete_default_group": "The group cannot be deleted because it is a default group.", + "code_error.team_error.cannot_delete_non_empty_org": "The department cannot be deleted because it is not empty.", + "code_error.team_error.cannot_modify_root_org": "The root department cannot be modified.", + "code_error.team_error.cannot_move_to_sub_path": "You cannot move it to the same directory or a sub-directory.", + "code_error.team_error.dataset_amount_not_enough": "The number of knowledge bases reaches the maximum.", + "code_error.team_error.dataset_size_not_enough": "The knowledge base index capacity is insufficient. Please expand it first.", + "code_error.team_error.group_name_duplicate": "The group name already exists.", + "code_error.team_error.group_name_empty": "Group name is required.", + "code_error.team_error.group_not_exist": "The group does not exist.", + "code_error.team_error.invitation_link_invalid": "The invitation link has expired.", + "code_error.team_error.not_user": "Unable to find the member.", + "code_error.team_error.org_member_duplicated": "The member already exists in the department.", + "code_error.team_error.org_member_not_exist": "The member does not exist in the department.", + "code_error.team_error.org_not_exist": "The department does not exist.", + "code_error.team_error.org_parent_not_exist": "The parent department does not exist.", + "code_error.team_error.over_size": "The number of team members has exceeded the maximum.", + "code_error.team_error.plugin_amount_not_enough": "The number of plugins reaches the maximum.", + "code_error.team_error.re_rank_not_enough": "Reranking of retrieved results is not supported for the free edition.", + "code_error.team_error.too_many_invitations": "The number of valid invitation links reaches the maximum. Please delete some first.", + "code_error.team_error.un_auth": "You do not have permission on the team.", + "code_error.team_error.user_not_active": "The user has not accepted the invitation or has left the team.", + "code_error.team_error.website_sync_not_enough": "Free edition does not support website sync.", + "code_error.team_error.you_have_been_in_the_team": "You are already a member of the team.", + "code_error.team_error.evaluation_task_amount_not_enough": "The number of evaluation tasks reaches the maximum.", + "code_error.team_error.evaluation_dataset_amount_not_enough": "The number of evaluation datasets reaches the maximum.", + "code_error.team_error.evaluation_dataset_data_amount_not_enough": "The number of evaluation cases reaches the maximum.", + "code_error.team_error.evaluation_metric_amount_not_enough": "The number of evaluation metrics reaches the maximum.", + "code_error.token_error_code.403": "Invalid login status. Please log in again.", + "code_error.user_error.balance_not_enough": "The account balance is insufficient.", + "code_error.user_error.bin_visitor_guest": "You do not have permission because you have not logged in.", + "code_error.user_error.un_auth_user": "User not found.", + "comfirm_import": "Confirm", + "comfirm_leave_page": "Are you sure you want to leave this page?", + "comfirn_create": "Confirm", + "commercial_function_tip": "Please upgrade to the commercial edition first: https://doc.fastgpt.cn/docs/introduction/commercial/", + "comon.Continue_Adding": "Save & add more", + "compliance.chat": "The content is generated by a third-party model and is for reference only. Its authenticity or accuracy is not guaranteed.", + "compliance.dataset": "Please make sure that your content is compliant with applicable laws and regulations and does not contain illegal or infringing information. Upload content that may contain sensitive information with caution.", + "confirm_choice": "Confirm", + "confirm_logout": "Are you sure you want to log out?", + "confirm_move": "Move", + "confirm_update": "OK", + "contact_way": "Notification recipient", + "contribute_app_template": "Third-party templates", + "copy_successful": "Copied successfully.", + "copy_to_clipboard": "Copy config", "core.Chat": "Chat", - "core.ai.Max context": "Max Context", + "core.ai.Max context": "Max context length", "core.ai.Model": "Model", - "core.ai.Not deploy rerank model": "Re-rank Model Not Deployed", + "core.ai.Not deploy rerank model": "No reranker models available.", "core.ai.Prompt": "Prompt", - "core.ai.Support tool": "Function Call", - "core.ai.model.Dataset Agent Model": "File read model", + "core.ai.Support tool": "Function call", + "core.ai.model.Dataset Agent Model": "LLM", "core.ai.model.Vector Model": "Index model", - "core.ai.model.doc_index_and_dialog": "Document Index & Dialog Index", - "core.app.Api request": "API Request", - "core.app.Api request desc": "Integrate into existing systems through API, or WeChat Work, Feishu, etc.", - "core.app.App intro": "App Introduction", - "core.app.Auto execute": "Auto execute", - "core.app.Chat Variable": "Chat Variable", - "core.app.Config schedule plan": "Configure Scheduled Execution", - "core.app.Config whisper": "Configure Voice Input", - "core.app.Config_auto_execute": "Click to configure automatic execution rules", - "core.app.Interval timer config": "Scheduled Execution Configuration", - "core.app.Interval timer run": "Scheduled Execution", - "core.app.Interval timer tip": "Can Execute App on Schedule", - "core.app.Make a brief introduction of your app": "Give Your AI App an Introduction", - "core.app.Name and avatar": "Avatar & Name", + "core.ai.model.doc_index_and_dialog": "Document & chat indexes", + "core.app.Api request": "API access", + "core.app.Api request desc": "Connect to other platforms via APIs, such as WeCom and Feishu.", + "core.app.App intro": "App description", + "core.app.Auto execute": "Auto execution", + "core.app.Chat Variable": "Chat box variable", + "core.app.Config schedule plan": "Click to configure", + "core.app.Config whisper": "Click to configure", + "core.app.Config_auto_execute": "Click to configure", + "core.app.Interval timer config": "Scheduled execution", + "core.app.Interval timer run": "Scheduled execution", + "core.app.Interval timer tip": "Execute apps based on the schedule.", + "core.app.Make a brief introduction of your app": "Enter description for the AI app", + "core.app.Name and avatar": "Icon & name", "core.app.Publish": "Publish", - "core.app.Publish Confirm": "Confirm to Publish App? This Will Immediately Update the App Status on All Publishing Channels.", - "core.app.Publish app tip": "After Publishing the App, All Publishing Channels Will Immediately Use This Version", - "core.app.QG.Custom prompt tip": "To ensure the generated content follows the correct format, [Yellow Prompt] cannot be modified", - "core.app.QG.Custom prompt tip1": "To ensure the generated content follows the correct format, ", - "core.app.QG.Custom prompt tip2": "[Yellow Prompt]", - "core.app.QG.Custom prompt tip3": " cannot be modified", + "core.app.Publish Confirm": "Are you sure you want to publish the app? The app status will be immediately updated across all publishing channels.", + "core.app.Publish app tip": "The app version will be updated across all publishing channels.", + "core.app.QG.Custom prompt tip": "To ensure the generated content follows the required format, the prompt highlighted in yellow cannot be modified.", + "core.app.QG.Custom prompt tip1": "To ensure the generated content follows the required format,", + "core.app.QG.Custom prompt tip2": " the prompt highlighted in yellow ", + "core.app.QG.Custom prompt tip3": "cannot be modified.", "core.app.QG.Fixed Prompt": "Please strictly follow the format rules: \nReturn questions in JSON format: ['Question 1', 'Question 2', 'Question 3'].", - "core.app.Question Guide": "Guess What You Want to Ask", - "core.app.Quote prompt": "Quote Template Prompt", - "core.app.Quote templates": "Quote Content Templates", - "core.app.Random": "Divergent", - "core.app.Search team tags": "Search Tags", - "core.app.Select TTS": "Select Voice Playback Mode", - "core.app.Select quote template": "Select Quote Prompt Template", - "core.app.Set a name for your app": "Set a Name for Your App", - "core.app.Setting ai property": "Click to Configure AI Model Properties", - "core.app.Share link": "Login-Free Window", - "core.app.Share link desc": "Share the link with other users, they can use it directly without logging in", - "core.app.Share link desc detail": "You can directly share this model with other users for conversation, they can use it directly without logging in. Note, this feature will consume your account balance, please keep the link safe!", - "core.app.TTS": "Voice Playback", - "core.app.TTS Tip": "After enabling, you can use the voice playback function after each conversation. Using this feature may incur additional costs.", - "core.app.TTS start": "Read Content", - "core.app.Team tags": "Team Tags", - "core.app.Tool call": "Tool Call", - "core.app.ToolCall.No plugin": "No Available Plugins", - "core.app.ToolCall.Parameter setting": "Input Parameters", + "core.app.Question Guide": "Follow-up", + "core.app.Quote prompt": "Prompt", + "core.app.Quote templates": "Template", + "core.app.Random": "Creative", + "core.app.Search team tags": "Tag", + "core.app.Select TTS": "Click to configure", + "core.app.Select quote template": "Select template", + "core.app.Set a name for your app": "App name", + "core.app.Setting ai property": "Click to configure", + "core.app.Share link": "Login-free link", + "core.app.Share link desc": "Share links with other users for login-free app access.", + "core.app.Share link desc detail": "You can create login-free links and share them with other users, allowing them to chat using this app without requiring login. Note: Using this feature will consume your credits. Please keep the links properly.", + "core.app.TTS": "Text-to-speech", + "core.app.TTS Tip": "If enabled, the content of each chat reply can be played as audio. Using this feature may generate additional costs.", + "core.app.TTS start": "Read content", + "core.app.Team tags": "Team tag", + "core.app.Tool call": "Tool call", + "core.app.ToolCall.No plugin": "No plugins available.", + "core.app.ToolCall.Parameter setting": "Input parameter", "core.app.ToolCall.System": "System", "core.app.ToolCall.Team": "Team", - "core.app.Welcome Text": "Conversation Opening", - "core.app.Whisper": "Voice Input", - "core.app.Whisper config": "Voice Input Configuration", + "core.app.Welcome Text": "Conversation opener", + "core.app.Whisper": "Speech-to-text", + "core.app.Whisper config": "Configure speech-to-text", "core.app.deterministic": "Deterministic", - "core.app.edit.Prompt Editor": "Prompt Editor", - "core.app.edit.Query extension background prompt": "Conversation Background Description", - "core.app.edit.Query extension background tip": "Describe the scope of the current conversation to help the AI complete and extend the current question. The content you fill in is usually for this assistant.", - "core.app.edit_content": "Edit App Information", - "core.app.error.App name can not be empty": "App Name Cannot Be Empty", - "core.app.error.Get app failed": "Failed to Retrieve App", - "core.app.feedback.Custom feedback": "Custom Feedback", - "core.app.feedback.close custom feedback": "Close Feedback", + "core.app.edit.Prompt Editor": "Edit prompt", + "core.app.edit.Query extension background prompt": "Chat context", + "core.app.edit.Query extension background tip": "Describe the context of the current chat to help the model supplement and elaborate on user's questions. The specified content is typically used by the app.", + "core.app.edit_content": "Edit app", + "core.app.error.App name can not be empty": "The app name is required.", + "core.app.error.Get app failed": "Error occurred while obtaining the app.", + "core.app.feedback.Custom feedback": "Custom feedback", + "core.app.feedback.close custom feedback": "Disable feedback", "core.app.have_saved": "Saved", - "core.app.logs.Source And Time": "Source & Time", - "core.app.more": "View More", - "core.app.no_app": "No Apps Yet, Create One Now!", - "core.app.not_saved": "Not Saved", - "core.app.outLink.Can Drag": "Icon Can Be Dragged", - "core.app.outLink.Default open": "Default Open", - "core.app.outLink.Iframe block title": "Copy the iframe below to add to your website", - "core.app.outLink.Link block title": "Copy the link below to open in the browser", - "core.app.outLink.Script Close Icon": "Close Icon", - "core.app.outLink.Script Open Icon": "Open Icon", - "core.app.outLink.Script block title": "Add the code below to your website", - "core.app.outLink.Select Mode": "Start Using", - "core.app.outLink.Select Using Way": "Select Usage Method", - "core.app.outLink.Show History": "Show Chat History", + "core.app.logs.Source And Time": "Source & time", + "core.app.more": "View more", + "core.app.no_app": "No apps available. Please create one first.", + "core.app.not_saved": "Unsaved", + "core.app.outLink.Can Drag": "Drag icon", + "core.app.outLink.Default open": "Enable by default", + "core.app.outLink.Iframe block title": "Add the following iframe to your website.", + "core.app.outLink.Link block title": "Copy the following link and open it in a browser.", + "core.app.outLink.Script Close Icon": "Hide icon", + "core.app.outLink.Script Open Icon": "Display icon", + "core.app.outLink.Script block title": "Add the following code to your website.", + "core.app.outLink.Select Mode": "Details", + "core.app.outLink.Select Using Way": "Mode", + "core.app.outLink.Show History": "Display chat history", "core.app.publish.Fei shu bot": "Feishu", - "core.app.publish.Fei shu bot publish": "Publish to Feishu Bot", - "core.app.schedule.Default prompt": "Default Question", - "core.app.schedule.Default prompt placeholder": "Default question when executing the app", + "core.app.publish.Fei shu bot publish": "Feishu bots", + "core.app.schedule.Default prompt": "Default question", + "core.app.schedule.Default prompt placeholder": "Default question used when the app is executed", "core.app.schedule.Every day": "Every day at {{hour}}:00", - "core.app.schedule.Every month": "Every month on the {{day}} at {{hour}}:00", - "core.app.schedule.Every week": "Every week on {{day}} at {{hour}}:00", + "core.app.schedule.Every month": "Day {{day}} of each month at {{hour}}:00", + "core.app.schedule.Every week": "Every {{day}} at {{hour}}:00", "core.app.schedule.Interval": "Every {{interval}} hours", - "core.app.schedule.Open schedule": "Scheduled Execution", - "core.app.setting": "App Information Settings", - "core.app.share.Amount limit tip": "Up to 10 groups", - "core.app.share.Create link": "Create New Link", - "core.app.share.Create link tip": "Creation successful. The share address has been copied and can be shared directly.", - "core.app.share.Ip limit title": "IP Rate Limit (people/minute)", - "core.app.share.Is response quote": "Return Quote", - "core.app.share.Not share link": "No Share Link Created", - "core.app.share.Role check": "Identity Verification", - "core.app.switch_to_template_market": "Jump template market", - "core.app.tip.Add a intro to app": "Give the app an introduction", - "core.app.tip.chatNodeSystemPromptTip": "Enter a prompt here", - "core.app.tip.systemPromptTip": "Fixed guide words for the model. By adjusting this content, you can guide the model's chat direction. This content will be fixed at the beginning of the context. You can use / to insert variables.\nIf a Dataset is associated, you can also guide the model when to call the Dataset search by appropriate description. For example:\nYou are an assistant for the movie 'Interstellar'. When users ask about content related to 'Interstellar', please search the Dataset and answer based on the search results.", - "core.app.tip.variableTip": "Before the conversation begins, users can be asked to fill in some content as specific variables for this round of conversation. \nThis module is located after the opening boot.\n\nIn the input box, you can select variables through / activation, such as: prompt words, qualifiers, etc.", - "core.app.tip.welcomeTextTip": "Before each conversation starts, send an initial content. Supports standard Markdown syntax. Additional tags that can be used:\n[Quick Key]: Users can directly send the question by clicking", + "core.app.schedule.Open schedule": "Status", + "core.app.setting": "Configure app info", + "core.app.share.Amount limit tip": "Up to 10 groups can be created.", + "core.app.share.Create link": "Create link", + "core.app.share.Create link tip": "Created successfully. The link has been copied and can be shared now.", + "core.app.share.Ip limit title": "QPM (users/minute)", + "core.app.share.Is response quote": "Reference display", + "core.app.share.Not share link": "No sharing links available.", + "core.app.share.Role check": "Identity verification", + "core.app.switch_to_template_market": "Go to Templates", + "core.app.tip.Add a intro to app": "Describe this app...", + "core.app.tip.chatNodeSystemPromptTip": "", + "core.app.tip.systemPromptTip": "Predefined prompt for the model. You can adjust it to change subjects for chats using the model. The content will be placed at the beginning of the context. You can insert a variable by entering a slash (/).\nIf a knowledge base is associated, you can instruct the model when to trigger knowledge base searches using appropriate descriptions. Example:\nYou are an assistant for the movie Interstellar. When a user asks about Interstellar, search the knowledge base and incorporate the information into your answers.", + "core.app.tip.variableTip": "Before a chat begins, you can prompt users to provide information that will serve as variables for the chat. This module runs after the conversation opener.\nUsers can enter a slash (/) in the input box to select variables such as prompts or qualifiers.", + "core.app.tip.welcomeTextTip": "Predefined content sent before a chat begins. Supports standard Markdown syntax and the following:\n[ ]: Put the conversation opener in a pair of brackets to create a shortcut.", "core.app.tool_label.doc": "Documentation", - "core.app.tool_label.github": "GitHub Address", - "core.app.tool_label.price": "Pricing", - "core.app.tool_label.view_doc": "View Documentation", - "core.app.tts.Speech model": "Speech Model", - "core.app.tts.Speech speed": "Speech Speed", - "core.app.tts.Test Listen": "Test Listen", - "core.app.tts.Test Listen Text": "Hello, this is a voice test. If you can hear this sentence, the voice playback function is normal.", - "core.app.whisper.Auto send": "Auto Send", - "core.app.whisper.Auto send tip": "Automatically send after voice input is completed, no need to click the send button manually", - "core.app.whisper.Auto tts response": "Auto Voice Response", - "core.app.whisper.Auto tts response tip": "The question sent by voice input will be directly responded to in voice form. Please ensure that the voice playback function is enabled.", - "core.app.whisper.Close": "Close", - "core.app.whisper.Not tts tip": "You have not enabled voice playback, this feature cannot be used", - "core.app.whisper.Open": "Open", - "core.app.whisper.Switch": "Enable Voice Input", - "core.chat.Admin Mark Content": "Corrected Reply", - "core.chat.Audio Not Support": "Device Does Not Support Voice Playback", - "core.chat.Audio Speech Error": "Voice Playback Error", - "core.chat.Cancel Speak": "Cancel Voice Input", - "core.chat.Confirm to clear history": "Confirm to Clear Online Chat History for This App? Share and API Call Records Will Not Be Cleared.", - "core.chat.Confirm to clear share chat history": "Confirm to Delete All Chat Records?", - "core.chat.Converting to text": "Converting to Text...", - "core.chat.Custom History Title": "Custom History Title", - "core.chat.Custom History Title Description": "If set to empty, it will automatically follow the chat record.", - "core.chat.Exit Chat": "Exit Chat", - "core.chat.Failed to initialize chat": "Failed to Initialize Chat", - "core.chat.Feedback Failed": "Feedback Submission Failed", - "core.chat.Feedback Modal": "Result Feedback", - "core.chat.Feedback Modal Tip": "Enter the part you are not satisfied with the answer", - "core.chat.Feedback Submit": "Submit Feedback", - "core.chat.Feedback Success": "Feedback Successful!", - "core.chat.Finish Speak": "Voice Input Completed", - "core.chat.History": "History", - "core.chat.History Amount": "{{amount}} Records", - "core.chat.Mark": "Mark Expected Answer", - "core.chat.Mark Description": "The current marking function is in beta.\n\nAfter clicking to add a mark, you need to select a Dataset to store the marked data. You can quickly mark questions and expected answers through this function to guide the model's next answer.\n\nCurrently, the marking function is the same as other data in the Dataset and is affected by the model, which does not mean that it will 100% meet expectations after marking.\n\nMarking data is only synchronized with the Dataset in one direction. If the Dataset modifies the marked data, the marked data displayed in the log cannot be synchronized.", + "core.app.tool_label.github": "GitHub address", + "core.app.tool_label.price": "Billing", + "core.app.tool_label.view_doc": "View documentation", + "core.app.tts.Speech model": "Text-to-speech model", + "core.app.tts.Speech speed": "Speed", + "core.app.tts.Test Listen": "Test", + "core.app.tts.Test Listen Text": "Hello, this is a speech test. If you can hear this sentence, text-to-speech works properly.", + "core.app.whisper.Auto send": "Auto send", + "core.app.whisper.Auto send tip": "A text message will be sent automatically after speech-to-text is complete, without the need to click Send.", + "core.app.whisper.Auto tts response": "Auto speech reply", + "core.app.whisper.Auto tts response tip": "The response for a speech-to-text message will be automatically converted to speech and played. Please ensure that text-to-speech is enabled.", + "core.app.whisper.Close": "Disabled", + "core.app.whisper.Not tts tip": "It is unavailable because text-to-speech is not enabled.", + "core.app.whisper.Open": "Enabled", + "core.app.whisper.Switch": "Speech-to-text", + "core.chat.Admin Mark Content": "Corrected answer", + "core.chat.Audio Not Support": "The device does not support text-to-speech.", + "core.chat.Audio Speech Error": "Text-to-speech error.", + "core.chat.Cancel Speak": "Cancel speech-to-text", + "core.chat.Confirm to clear history": "This will clear online chat history only. Shared content and API calls will be retained. Continue?", + "core.chat.Confirm to clear share chat history": "Are you sure you want to delete all chat history?", + "core.chat.Converting to text": "Converting to text...", + "core.chat.Custom History Title": "Custom title", + "core.chat.Custom History Title Description": "If left blank, a title is generated based on the chat history.", + "core.chat.Exit Chat": "Exit", + "core.chat.Failed to initialize chat": "Chat initialization failed.", + "core.chat.Feedback Failed": "Error occurred while submitting feedback.", + "core.chat.Feedback Modal": "Feedback", + "core.chat.Feedback Modal Tip": "What you are dissatisfied with", + "core.chat.Feedback Submit": "Submit", + "core.chat.Feedback Success": "Feedback submitted successfully.", + "core.chat.Finish Speak": "Complete speech-to-text", + "core.chat.History": "Records", + "core.chat.History Amount": "{{amount}} records", + "core.chat.Mark": "Mark as expected answer", + "core.chat.Mark Description": "The mark feature is currently in test mode.\n\nAfter clicking the mark icon, you need to select a knowledge base to store the marked entry. This feature allows you to quickly mark questions and expected answers to guide future responses from the model.\n\nAnswers marked as expected will be stored in the knowledge base but will not necessarily be retrieved when users ask related questions.\n\nMarked entries are synced to the selected knowledge bases, in which changes to marked entries are not synced to corresponding logs.", "core.chat.Mark Description Title": "Marking Function Introduction", - "core.chat.New Chat": "New", - "core.chat.Pin": "Pin", - "core.chat.Question Guide": "Guess What You Want to Ask", - "core.chat.Quote": "Quote", - "core.chat.Quote Amount": "Dataset Quotes ({{amount}} Records)", + "core.chat.New Chat": "New chat", + "core.chat.Pin": "Pin to top", + "core.chat.Question Guide": "Follow-up", + "core.chat.Quote": "Reference", + "core.chat.Quote Amount": "Knowledge base references ({{amount}})", "core.chat.Read Mark Description": "View Marking Function Introduction", - "core.chat.Recent use": "Recently Used", - "core.chat.Record": "Voice Input", - "core.chat.Restart": "Restart", - "core.chat.Run test": "Run Preview", - "core.chat.Select dataset": "Select Dataset", - "core.chat.Select dataset Desc": "Select a Dataset to store the expected answer", + "core.chat.Recent use": "Recents", + "core.chat.Record": "Voice input", + "core.chat.Restart": "Start a new chat", + "core.chat.Run test": "Running preview", + "core.chat.Select dataset": "Select knowledge base", + "core.chat.Select dataset Desc": "Select a knowledge base to store marked answers.", "core.chat.Send Message": "Send", - "core.chat.Speaking": "I'm Listening, Please Speak...", - "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.error.Chat error": "Chat Error", - "core.chat.error.Messages empty": "API Content is Empty, Possibly Due to Text Being Too Long", - "core.chat.error.Select dataset empty": "You Have Not Selected a Dataset", - "core.chat.error.User input empty": "User Question Input is Empty", - "core.chat.error.data_error": "Data Retrieval Error", - "core.chat.feedback.Close User Like": "User Agrees\nClick to Close This Mark", - "core.chat.feedback.Feedback Close": "Close Feedback", - "core.chat.feedback.No Content": "User Did Not Provide Specific Feedback Content", - "core.chat.feedback.Read User dislike": "User Disagrees\nClick to View Content", - "core.chat.logs.api": "API Call", + "core.chat.Speaking": "I'm listening. Go ahead.", + "core.chat.Type a message": "Enter your question. Press Enter to send or Ctrl/Alt/Shift+Enter for a new line.", + "core.chat.Unpin": "Remove from top", + "core.chat.error.Chat error": "Chat error.", + "core.chat.error.Messages empty": "Empty response. The request text length exceeds the maximum.", + "core.chat.error.Select dataset empty": "Please select a knowledge base.", + "core.chat.error.User input empty": "The question cannot be empty.", + "core.chat.error.data_error": "Error occurred while obtaining data.", + "core.chat.feedback.Close User Like": "Liked by user.\nClick to dislike it.", + "core.chat.feedback.Feedback Close": "Disable feedback", + "core.chat.feedback.No Content": "The user did not provide any specific feedback.", + "core.chat.feedback.Read User dislike": "Disliked by user.\nClick to view the content.", + "core.chat.logs.api": "API call", "core.chat.logs.feishu": "Feishu", - "core.chat.logs.free_login": "No login link", + "core.chat.logs.free_login": "Login-free link", "core.chat.logs.mcp": "MCP call", - "core.chat.logs.official_account": "Official Account", - "core.chat.logs.online": "Online Use", - "core.chat.logs.share": "External Link Call", - "core.chat.logs.team": "Team Space Chat", + "core.chat.logs.official_account": "WeChat official account", + "core.chat.logs.online": "Online operation", + "core.chat.logs.share": "External link call", + "core.chat.logs.team": "Chat with team members", "core.chat.logs.test": "Online debugging", - "core.chat.logs.wecom": "WeChat Work", - "core.chat.markdown.Edit Question": "Edit Question", - "core.chat.markdown.Quick Question": "Click to Ask Immediately", - "core.chat.markdown.Send Question": "Send Question", + "core.chat.logs.wecom": "WeCom", + "core.chat.markdown.Edit Question": "Edit question", + "core.chat.markdown.Quick Question": "Ask me now", + "core.chat.markdown.Send Question": "Send question", "core.chat.module_unexist": "Running failed: Application missing components", - "core.chat.quote.Quote Tip": "Only the actual quoted content is displayed here. If the data is updated, it will not be updated in real-time here.", - "core.chat.quote.Read Quote": "View Quote", + "core.chat.quote.Quote Tip": "Only actually referenced content is displayed, and the data will not be updated in real time.", + "core.chat.quote.Read Quote": "View reference", "core.chat.quote.afterUpdate": "After update", "core.chat.quote.beforeUpdate": "Before update", - "core.chat.response.Complete Response": "Complete Response", - "core.chat.response.Extension model": "Question Optimization Model", - "core.chat.response.Read complete response": "View Details", - "core.chat.response.Read complete response tips": "Click to View Detailed Process", - "core.chat.response.Tool call input tokens": "Tool Call Input Tokens Consumption", - "core.chat.response.Tool call output tokens": "Tool Call Output Tokens Consumption", - "core.chat.response.Tool call tokens": "Tool Call Tokens Consumption", - "core.chat.response.context total length": "Total Context Length", - "core.chat.response.loop_input": "Loop Input Array", - "core.chat.response.loop_input_element": "Loop Input Element", - "core.chat.response.loop_output": "Loop Output Array", - "core.chat.response.loop_output_element": "Loop Output Element", - "core.chat.response.module cq": "Question Classification List", - "core.chat.response.module cq result": "Classification Result", - "core.chat.response.module extract description": "Extract Background Description", - "core.chat.response.module extract result": "Extraction Result", - "core.chat.response.module historyPreview": "History Preview (Only Partial Content Displayed)", - "core.chat.response.module http result": "Response Body", - "core.chat.response.module if else Result": "Condition Result", - "core.chat.response.module limit": "Single Search Limit", - "core.chat.response.module maxToken": "Max Response Tokens", + "core.chat.response.Complete Response": "Complete response", + "core.chat.response.Extension model": "Question optimization model", + "core.chat.response.Read complete response": "View details", + "core.chat.response.Read complete response tips": "Click to view the process details.", + "core.chat.response.Tool call input tokens": "Input tokens for tool call", + "core.chat.response.Tool call output tokens": "Output tokens for tool call", + "core.chat.response.Tool call tokens": "Tokens consumed for tool call", + "core.chat.response.context total length": "Total context length", + "core.chat.response.loop_input": "Input array", + "core.chat.response.loop_input_element": "Input array element", + "core.chat.response.loop_output": "Output array", + "core.chat.response.loop_output_element": "Output array element", + "core.chat.response.module cq": "Questions by type", + "core.chat.response.module cq result": "Classification result", + "core.chat.response.module extract description": "Extract background description", + "core.chat.response.module extract result": "Extraction result", + "core.chat.response.module historyPreview": "Preview (partially displayed)", + "core.chat.response.module http result": "Response body", + "core.chat.response.module if else Result": "If-else running result", + "core.chat.response.module limit": "Max entries per search", + "core.chat.response.module maxToken": "Max output tokens", "core.chat.response.module model": "Model", - "core.chat.response.module name": "Model Name", - "core.chat.response.module query": "Question/Search Term", + "core.chat.response.module name": "Node name", + "core.chat.response.module query": "Question/keyword", "core.chat.response.module similarity": "Similarity", "core.chat.response.module temperature": "Temperature", - "core.chat.response.module time": "Run Time", - "core.chat.response.plugin output": "Plugin Output Value", - "core.chat.response.search using reRank": "Result Re-Rank", - "core.chat.response.text output": "Text Output", - "core.chat.response.update_var_result": "Variable Update Result (Displays Multiple Variable Update Results in Order)", - "core.chat.response.user_select_result": "User Selection Result", - "core.chat.retry": "Regenerate", + "core.chat.response.module time": "Uptime", + "core.chat.response.plugin output": "Plugin output value", + "core.chat.response.search using reRank": "Result reranking", + "core.chat.response.text output": "Text output", + "core.chat.response.update_var_result": "Variable update results (multiple variable updates displayed in sequence)", + "core.chat.response.user_select_result": "Selection result", + "core.chat.retry": "Generate again", "core.chat.tts.Stop Speech": "Stop", - "core.dataset.Choose Dataset": "Associate Dataset", + "core.dataset.Choose Dataset": "Knowledge bases", "core.dataset.Collection": "Dataset", - "core.dataset.Create dataset": "Create a {{name}}", - "core.dataset.Dataset": "Dataset", - "core.dataset.Dataset ID": "Dataset ID", - "core.dataset.Delete Confirm": "Confirm to Delete This Dataset? Data Cannot Be Recovered After Deletion, Please Confirm!", - "core.dataset.Empty Dataset": "Empty Dataset", - "core.dataset.Empty Dataset Tips": "No Dataset Yet, Create One Now!", - "core.dataset.Folder placeholder": "This is a Directory", - "core.dataset.Intro Placeholder": "This Dataset Has No Introduction Yet", - "core.dataset.My Dataset": "My Dataset", - "core.dataset.Query extension intro": "Enabling the question optimization function can improve the accuracy of Dataset searches during continuous conversations. After enabling this function, when performing Dataset searches, the AI will complete the missing information of the question based on the conversation history.", - "core.dataset.Quote Length": "Quote Content Length", - "core.dataset.Read Dataset": "View Dataset Details", - "core.dataset.Set Website Config": "Start Configuring", - "core.dataset.Start export": "Export Started", - "core.dataset.Text collection": "Text Dataset", - "core.dataset.apiFile": "API File", - "core.dataset.collection.Click top config website": "Click to Configure Website", - "core.dataset.collection.Collection raw text": "Dataset Content", - "core.dataset.collection.Empty Tip": "The Dataset is Empty", - "core.dataset.collection.QA Prompt": "QA Split Prompt", - "core.dataset.collection.Start Sync Tip": "Confirm to Start Syncing Data? Old Data Will Be Deleted and Re-fetched, Please Confirm!", - "core.dataset.collection.Sync": "Sync Data", - "core.dataset.collection.Sync Collection": "Data Sync", - "core.dataset.collection.Website Empty Tip": "No Website Associated Yet", - "core.dataset.collection.Website Link": "Website Address", + "core.dataset.Create dataset": "Create {{name}}", + "core.dataset.Dataset": "Knowledge base", + "core.dataset.Dataset ID": "Knowledge base ID", + "core.dataset.Delete Confirm": "Are you sure you want to delete the knowledge base? Deleted data cannot be recovered.", + "core.dataset.Empty Dataset": "Empty dataset", + "core.dataset.Empty Dataset Tips": "No knowledge bases available. Please create one first.", + "core.dataset.Folder placeholder": "This is a directory.", + "core.dataset.Intro Placeholder": "No description yet.", + "core.dataset.My Dataset": "My knowledge bases", + "core.dataset.Query extension intro": "Enabling it will increase the accuracy of knowledge base searches during continuous chats and allow the model to complete questions based on chat history.", + "core.dataset.Quote Length": "Referenced content length", + "core.dataset.Read Dataset": "View details", + "core.dataset.Set Website Config": "Configure now", + "core.dataset.Start export": "Exporting...", + "core.dataset.Text collection": "Text dataset", + "core.dataset.apiFile": "API file", + "core.dataset.collection.Click top config website": "Configure website", + "core.dataset.collection.Collection raw text": "Dataset content", + "core.dataset.collection.Empty Tip": "No dataset yet.", + "core.dataset.collection.QA Prompt": "Prompt for Q&A generation", + "core.dataset.collection.Start Sync Tip": "Are you sure you want to sync data? The original data will be deleted.", + "core.dataset.collection.Sync": "Sync data", + "core.dataset.collection.Sync Collection": "Data sync", + "core.dataset.collection.Website Empty Tip": "No associated websites available.", + "core.dataset.collection.Website Link": "Website address", "core.dataset.collection.id": "Collection ID", - "core.dataset.collection.metadata.Createtime": "Creation Time", - "core.dataset.collection.metadata.Raw text length": "Raw Text Length", - "core.dataset.collection.metadata.Updatetime": "Update Time", - "core.dataset.collection.metadata.Web page selector": "Web Page Selector", + "core.dataset.collection.metadata.Createtime": "Time created", + "core.dataset.collection.metadata.Raw text length": "Source text length", + "core.dataset.collection.metadata.Updatetime": "Time updated", + "core.dataset.collection.metadata.Web page selector": "Website selector", "core.dataset.collection.metadata.metadata": "Metadata", - "core.dataset.collection.metadata.read source": "View Original Content", - "core.dataset.collection.metadata.source": "Data Source", - "core.dataset.collection.metadata.source size": "Source Size", + "core.dataset.collection.metadata.read source": "View original content", + "core.dataset.collection.metadata.source": "Data source", + "core.dataset.collection.metadata.source size": "Source data size", "core.dataset.collection.status.active": "Ready", - "core.dataset.collection.status.error": "Error", - "core.dataset.collection.sync.result.sameRaw": "Content Unchanged, No Update Needed", - "core.dataset.collection.sync.result.success": "Sync Started", - "core.dataset.data.Data Content": "Related Data Content", + "core.dataset.collection.status.error": "Training error", + "core.dataset.collection.sync.result.sameRaw": "Update is not required because the content is not changed.", + "core.dataset.collection.sync.result.success": "Sync now", + "core.dataset.data.Data Content": "Related data", "core.dataset.data.Default Index Tip": "Cannot be edited. The default index will use the text of 'Related Data Content' and 'Auxiliary Data' to generate the index directly.", - "core.dataset.data.Edit": "Edit Data", - "core.dataset.data.Empty Tip": "This collection has no data yet", - "core.dataset.data.Search data placeholder": "Search Related Data", - "core.dataset.data.Too Long": "Total Length Exceeded", + "core.dataset.data.Edit": "Edit data", + "core.dataset.data.Empty Tip": "No data available in the dataset.", + "core.dataset.data.Search data placeholder": "Search", + "core.dataset.data.Too Long": "The length exceeds the maximum.", "core.dataset.data.Updated": "Updated", - "core.dataset.data.group": " Groups", + "core.dataset.data.group": "groups", "core.dataset.data.unit": "Items", - "core.dataset.embedding model tip": "The index model can convert natural language into vectors for semantic search.\nNote that different index models cannot be used together. Once an index model is selected, it cannot be changed.", - "core.dataset.error.Data not found": "Data Not Found or Deleted", - "core.dataset.error.Start Sync Failed": "Failed to Start Sync", - "core.dataset.error.canNotEditAdminPermission": "You cannot edit the admin permission", - "core.dataset.error.invalidVectorModelOrQAModel": "Invalid Vector Model or QA Model", - "core.dataset.error.unAuthDataset": "Unauthorized to Operate This Dataset", - "core.dataset.error.unAuthDatasetCollection": "Unauthorized to Operate This Dataset", - "core.dataset.error.unAuthDatasetData": "Unauthorized to Operate This Data", - "core.dataset.error.unAuthDatasetFile": "Unauthorized to Operate This File", - "core.dataset.error.unCreateCollection": "Unauthorized to Operate This Data", - "core.dataset.error.unExistDataset": "The knowledge base does not exist", - "core.dataset.error.unLinkCollection": "Not a Web Link Collection", - "core.dataset.externalFile": "External File Library", + "core.dataset.embedding model tip": "An index model can convert natural language to vectors for semantic search.\nNote: Different index models cannot be used together. The index model cannot be changed after being saved.", + "core.dataset.error.Data not found": "The entry does not exist or has been deleted.", + "core.dataset.error.Start Sync Failed": "Failed to start sync.", + "core.dataset.error.canNotEditAdminPermission": "Admin permissions cannot be modified.", + "core.dataset.error.invalidVectorModelOrQAModel": "Vector model or Q&A model encountered error.", + "core.dataset.error.unAuthDataset": "You do not have permission to perform operations on the knowledge base.", + "core.dataset.error.unAuthDatasetCollection": "You do not have permission to perform operations on the dataset.", + "core.dataset.error.unAuthDatasetData": "You do not have permission to perform operations on the entry.", + "core.dataset.error.unAuthDatasetFile": "You do not have permission to perform operations on the file.", + "core.dataset.error.unCreateCollection": "You do not have permission to perform operations on the entry.", + "core.dataset.error.unExistDataset": "The knowledge base does not exist.", + "core.dataset.error.unLinkCollection": "It is not a link set.", + "core.dataset.externalFile": "External file database", "core.dataset.file": "File", "core.dataset.folder": "Directory", - "core.dataset.import.Chunk Range": "Range: {{min}}~{{max}}", - "core.dataset.import.Chunk Split Tip": "Segment the text according to certain rules and convert it into a format that can be semantically searched. Suitable for most scenarios. No additional model processing is required, and the cost is low.", - "core.dataset.import.Continue upload": "Continue upload", - "core.dataset.import.Custom prompt": "Custom Prompt", - "core.dataset.import.Custom text": "Custom Text", - "core.dataset.import.Custom text desc": "Manually enter a piece of text as a dataset", - "core.dataset.import.Data process params": "Data Processing Parameters", - "core.dataset.import.Down load csv template": "Click to Download CSV Template", - "core.dataset.import.Link name": "Web Link", - "core.dataset.import.Link name placeholder": "Only supports static links. If the data is empty after uploading, the link may not be readable\nEach line one, up to 10 links at a time", - "core.dataset.import.Local file": "Local File", - "core.dataset.import.Local file desc": "Upload files in PDF, TXT, DOCX, etc. formats", - "core.dataset.import.Preview chunks": "Preview Chunks (limit 15)", - "core.dataset.import.Preview raw text": "Preview Raw Text (up to 3000 characters)", + "core.dataset.import.Chunk Range": "Range: {{min}}-{{max}}", + "core.dataset.import.Chunk Split Tip": "Segment the text according to specific rules and convert it to a format suitable for semantic search. This is ideal for most scenarios. The cost is low because no model call is required.", + "core.dataset.import.Continue upload": "Continue", + "core.dataset.import.Custom prompt": "Custom prompt", + "core.dataset.import.Custom text": "Custom text", + "core.dataset.import.Custom text desc": "Manually enter text to create a dataset.", + "core.dataset.import.Data process params": "Data processing parameter", + "core.dataset.import.Down load csv template": "Download .csv template", + "core.dataset.import.Link name": "Link", + "core.dataset.import.Link name placeholder": "Must be static links. If no data is available after the upload, the links may fail to be read.\nOne link per line. Up to 10 links are allowed.", + "core.dataset.import.Local file": "Local file", + "core.dataset.import.Local file desc": "Upload files in .pdf, .txt, .docx, or other formats.", + "core.dataset.import.Preview chunks": "Preview shard (Max shards: 15)", + "core.dataset.import.Preview raw text": "Preview source text (Max characters: 3000)", "core.dataset.import.Process way": "Processing Method", - "core.dataset.import.QA Import": "QA Split", - "core.dataset.import.QA Import Tip": "According to certain rules, split the text into larger paragraphs and call AI to generate Q&A pairs for the paragraph. It has very high retrieval accuracy but may lose a lot of content details.", + "core.dataset.import.QA Import": "Q&A generation", + "core.dataset.import.QA Import Tip": "Split the text into multiple large paragraphs based on specific rules and generate Q&A pairs for them using the model. This provides extremely high search accuracy but can cause significant detail loss.", "core.dataset.import.Select file": "Select File", - "core.dataset.import.Select source": "Select Source", - "core.dataset.import.Source name": "Source Name", + "core.dataset.import.Select source": "Select source", + "core.dataset.import.Source name": "Source name", "core.dataset.import.Sources list": "Sources", - "core.dataset.import.Start upload": "Start Upload", - "core.dataset.import.Upload complete": "Upload complete", - "core.dataset.import.Upload data": "Confirm Upload", - "core.dataset.import.Upload file progress": "File Upload Progress", + "core.dataset.import.Start upload": "Upload", + "core.dataset.import.Upload complete": "Complete upload", + "core.dataset.import.Upload data": "Confirm", + "core.dataset.import.Upload file progress": "Upload progress", "core.dataset.import.Upload status": "Status", - "core.dataset.import.Web link": "Web Link", - "core.dataset.import.Web link desc": "Read static web page content as a dataset", - "core.dataset.import.import_success": "Import Successful, Please Wait for Training", + "core.dataset.import.Web link": "Web link", + "core.dataset.import.Web link desc": "Extract content from static webpages to create a dataset.", + "core.dataset.import.import_success": "Import successful. Please wait for training.", "core.dataset.link": "Link", - "core.dataset.search.Dataset Search Params": "Dataset Search Configuration", - "core.dataset.search.Empty result response": "Empty Search Response", - "core.dataset.search.Filter": "Search Filter", - "core.dataset.search.No support similarity": "Only supported when using result re-rank or semantic search", - "core.dataset.search.Nonsupport": "Not Supported", - "core.dataset.search.Params Setting": "Search Parameter Settings", - "core.dataset.search.Quote index": "Quote Index", - "core.dataset.search.ReRank": "Result Re-rank", - "core.dataset.search.ReRank desc": "Use the re-rank model for secondary sorting to enhance the comprehensive ranking.", + "core.dataset.search.Dataset Search Params": "Knowledge base search settings", + "core.dataset.search.Empty result response": "Answer for no search results", + "core.dataset.search.Filter": "Filtering", + "core.dataset.search.No support similarity": "Relevance filtering is supported only for result reranking or semantic search.", + "core.dataset.search.Nonsupport": "Not supported", + "core.dataset.search.Params Setting": "Parameter settings", + "core.dataset.search.Quote index": "No.", + "core.dataset.search.ReRank": "Result reranking", + "core.dataset.search.ReRank desc": "Rerank results using the rerank model to improve overall rankings.", "core.dataset.search.Source id": "Source ID", "core.dataset.search.Source index": "What sources", - "core.dataset.search.Source name": "Quote Source Name", - "core.dataset.search.Using query extension": "Use Question Optimization", - "core.dataset.search.mode.embedding": "Semantic Search", - "core.dataset.search.mode.embedding desc": "Use vectors for text relevance queries", - "core.dataset.search.mode.fullTextRecall": "Full Text Search", - "core.dataset.search.mode.fullTextRecall desc": "Use traditional full-text search, suitable for finding some keywords and subject-predicate special data", - "core.dataset.search.mode.mixedRecall": "Mixed Search", - "core.dataset.search.mode.mixedRecall desc": "Use a combination of vector search and full-text search results, sorted using the RRF algorithm.", - "core.dataset.search.score.embedding desc": "Get scores by calculating the distance between vectors, ranging from 0 to 1.", - "core.dataset.search.score.fullText": "Full Text Search", - "core.dataset.search.score.fullText desc": "Calculate the score of the same keywords, ranging from 0 to infinity.", - "core.dataset.search.score.reRank": "Result Re-rank", - "core.dataset.search.score.reRank desc": "Calculate the relevance between sentences using the re-rank model, ranging from 0 to 1.", - "core.dataset.search.score.rrf": "Comprehensive Ranking", - "core.dataset.search.score.rrf desc": "Merge multiple search results using the reciprocal rank fusion method.", - "core.dataset.search.search mode": "Search Method", + "core.dataset.search.Source name": "Reference source name", + "core.dataset.search.Using query extension": "Enable", + "core.dataset.search.mode.embedding": "Semantic search", + "core.dataset.search.mode.embedding desc": "Queries text relevance using vectors.", + "core.dataset.search.mode.fullTextRecall": "Full-text search", + "core.dataset.search.mode.fullTextRecall desc": "Searches the entire text for keywords and entries with specific subject-predicate structures.", + "core.dataset.search.mode.mixedRecall": "Hybrid search", + "core.dataset.search.mode.mixedRecall desc": "Uses both semantic search and full-text search, and selects the best match for the user's query using the RRF algorithm.", + "core.dataset.search.score.embedding desc": "The vector search score is obtained by calculating the distances between vectors. Range: 0-1", + "core.dataset.search.score.fullText": "Full-text search", + "core.dataset.search.score.fullText desc": "The full-text search score is obtained by calculating occurrences of identical keywords. Range: 0 to infinity", + "core.dataset.search.score.reRank": "Result reranking", + "core.dataset.search.score.reRank desc": "Relevance between sentences calculated using the reranker model. Range: 0-1", + "core.dataset.search.score.rrf": "Overall ranking", + "core.dataset.search.score.rrf desc": "Multiple search results are merged using inverted ranking.", + "core.dataset.search.search mode": "Search method", "core.dataset.status.active": "Ready", "core.dataset.status.syncing": "Syncing", "core.dataset.status.waiting": "Waiting", - "core.dataset.test.Batch test": "Batch Test", - "core.dataset.test.Batch test Placeholder": "Select a CSV File", - "core.dataset.test.Search Test": "Search Test", + "core.dataset.test.Batch test": "Bulk test", + "core.dataset.test.Batch test Placeholder": "Please select a .csv file.", + "core.dataset.test.Search Test": "Search test", "core.dataset.test.Test": "Test", - "core.dataset.test.Test Result": "Test Result", - "core.dataset.test.Test Text": "Single Text Test", - "core.dataset.test.Test Text Placeholder": "Enter the text to be tested", - "core.dataset.test.Test params": "Test Parameters", - "core.dataset.test.delete test history": "Delete This Test Result", - "core.dataset.test.test history": "Test History", - "core.dataset.test.test result placeholder": "Test results will be displayed here", - "core.dataset.test.test result tip": "Sort based on the similarity between the Dataset content and the test text. You can adjust the corresponding text based on the test results.\nNote: The data in the test records may have been modified. Clicking on a test data will display the latest data.", - "core.dataset.training.Agent queue": "QA Training Queue", - "core.dataset.training.Auto mode": "Auto index", - "core.dataset.training.Auto mode Tip": "Increase the semantic richness of data blocks by generating related questions and summaries through sub-indexes and calling models, making it more conducive to retrieval. Requires more storage space and increases AI call times.", - "core.dataset.training.Chunk mode": "Chunk", - "core.dataset.training.Full": "It is expected to be more than 20 minutes", + "core.dataset.test.Test Result": "Test result", + "core.dataset.test.Test Text": "Text test", + "core.dataset.test.Test Text Placeholder": "Enter text for testing", + "core.dataset.test.Test params": "Test parameters", + "core.dataset.test.delete test history": "Delete test result", + "core.dataset.test.test history": "Test history", + "core.dataset.test.test result placeholder": "The test result will be displayed here.", + "core.dataset.test.test result tip": "The returned results are sorted based on the similarity between the knowledge base entries and the tested text. You can adjust the corresponding text based on test results.\nNote: Entries in the test history may have been modified. Click to view the latest entry.", + "core.dataset.training.Agent queue": "Q&A training queue", + "core.dataset.training.Auto mode": "Supplemental index", + "core.dataset.training.Auto mode Tip": "Related questions and summaries will be generated using sub-indexes and model calls, which improves the semantic richness of data blocks and enhances search efficiency. However, more storage space and model calls are required.", + "core.dataset.training.Chunk mode": "Chunking", + "core.dataset.training.Full": "Estimated wait time: Over 20 minutes", "core.dataset.training.Leisure": "Idle", - "core.dataset.training.QA mode": "QA", - "core.dataset.training.Vector queue": "Index Queue", - "core.dataset.training.Waiting": "Estimated 20 minutes", - "core.dataset.training.Website Sync": "Website Sync", - "core.dataset.training.tag": "Queue Status", - "core.dataset.website.Base Url": "Base URL", - "core.dataset.website.Config": "Website Configuration", - "core.dataset.website.Config Description": "The website sync function allows you to fill in the root address of a website. The system will automatically crawl related web pages for Dataset training. Only static websites will be crawled, mainly project documentation and blogs.", - "core.dataset.website.Confirm Create Tips": "Confirm to sync this site. The sync task will start shortly. Please confirm!", - "core.dataset.website.Confirm Update Tips": "Confirm to update the site configuration? The sync will start immediately according to the new configuration. Please confirm!", + "core.dataset.training.QA mode": "Q&A pair extraction", + "core.dataset.training.Vector queue": "Index queue", + "core.dataset.training.Waiting": "Estimated wait time: 20 minutes", + "core.dataset.training.Website Sync": "Website sync", + "core.dataset.training.tag": "Queue status", + "core.dataset.website.Base Url": "Root address", + "core.dataset.website.Config": "Website configuration", + "core.dataset.website.Config Description": "If the website sync feature is enabled, you can specify the root address of a website, and the system will automatically scrape relevant webpages for knowledge base training. Only static webpages will be crawled, mainly including project documents and blogs.", + "core.dataset.website.Confirm Create Tips": "The sync task will start immediately.", + "core.dataset.website.Confirm Update Tips": "Are you sure you want to update the website configuration?", "core.dataset.website.Selector": "Selector", - "core.dataset.website.Selector Course": "Usage Tutorial", - "core.dataset.website.Start Sync": "Start Sync", - "core.dataset.website.UnValid Website Tip": "Your site may not be a static site and cannot be synced", - "core.module.Add question type": "Add Question Type", - "core.module.Add_option": "Add Option", - "core.module.Can not connect self": "Cannot Connect to Itself", - "core.module.Data Type": "Data Type", - "core.module.Dataset quote.label": "Dataset Quote", - "core.module.Dataset quote.select": "Select Dataset Quote", - "core.module.Default Value": "Default Value", - "core.module.Default value": "Default Value", - "core.module.Default value placeholder": "Leave blank to return an empty string by default", - "core.module.Diagram": "Diagram", - "core.module.Edit intro": "Edit Description", - "core.module.Field Description": "Field Description", - "core.module.Field Name": "Field Name", - "core.module.Http request props": "Request Parameters", - "core.module.Http request settings": "Request Configuration", - "core.module.Http timeout": "Timeout Duration", - "core.module.Input Type": "Input Type", - "core.module.Laf sync params": "Sync Parameters", - "core.module.Max Length": "Max Length", - "core.module.Max Length placeholder": "Maximum length of input text", - "core.module.Max Value": "Max Value", - "core.module.Min Value": "Min Value", - "core.module.QueryExtension.placeholder": "For example:\nQuestions about the introduction and use of Python.\nThe current conversation is related to the game 'GTA5'.", - "core.module.Select app": "Select App", - "core.module.Setting quote prompt": "Configure Quote Prompt", - "core.module.Variable": "Global Variable", - "core.module.Variable Setting": "Variable Setting", - "core.module.edit.Field Name Cannot Be Empty": "Field Name Cannot Be Empty", - "core.module.edit.Field Value Type Cannot Be Empty": "Optional data type cannot be empty.", - "core.module.extract.Add field": "Add Field", - "core.module.extract.Enum Description": "List the possible values of this field, one per line", - "core.module.extract.Enum Value": "Enum Value", - "core.module.extract.Field Description Placeholder": "Name/Age/SQL Statement...", - "core.module.extract.Field Setting Title": "Extract Field Configuration", - "core.module.extract.Required": "Must Return", - "core.module.extract.Required Description": "Even if the field cannot be extracted, it will be returned using the default value", - "core.module.extract.Target field": "Target Field", - "core.module.http.Add props": "Add Parameter", + "core.dataset.website.Selector Course": "Guide", + "core.dataset.website.Start Sync": "Sync now", + "core.dataset.website.UnValid Website Tip": "Unable to sync the website because it may not be a static website.", + "core.module.Add question type": "Add type", + "core.module.Add_option": "Add option", + "core.module.Can not connect self": "The node cannot be connected to itself.", + "core.module.Data Type": "Type", + "core.module.Dataset quote.label": "Knowledge base reference", + "core.module.Dataset quote.select": "Select", + "core.module.Default Value": "Default value", + "core.module.Default value": "Default value", + "core.module.Default value placeholder": "If left blank, an empty string will be returned by default.", + "core.module.Diagram": "Preview", + "core.module.Edit intro": "Edit description", + "core.module.Field Description": "Description", + "core.module.Field Name": "Field name", + "core.module.Http request props": "Request parameters", + "core.module.Http request settings": "Request configuration", + "core.module.Http timeout": "Timeout threshold", + "core.module.Input Type": "Input type", + "core.module.Laf sync params": "Sync parameter", + "core.module.Max Length": "Max length", + "core.module.Max Length placeholder": "Max input text length", + "core.module.Max Value": "Max value", + "core.module.Min Value": "Min value", + "core.module.QueryExtension.placeholder": "Examples:\nIntroduction and usage of Python\nAbout the game GTA5", + "core.module.Select app": "Select", + "core.module.Setting quote prompt": "Configure prompt", + "core.module.Variable": "Global variables", + "core.module.Variable Setting": "Configure variable", + "core.module.edit.Field Name Cannot Be Empty": "The field name is required.", + "core.module.edit.Field Value Type Cannot Be Empty": "The data type is required.", + "core.module.extract.Add field": "Add field", + "core.module.extract.Enum Description": "Enter the field values. One value per line.", + "core.module.extract.Enum Value": "Enumeration values", + "core.module.extract.Field Description Placeholder": "Name, age, SQL statement, etc.", + "core.module.extract.Field Setting Title": "Add field", + "core.module.extract.Required": "Return required", + "core.module.extract.Required Description": "If the actual field value cannot be obtained, the default value will be returned.", + "core.module.extract.Target field": "Target field", + "core.module.http.Add props": "Add parameter", "core.module.http.AppId": "App ID", - "core.module.http.ChatId": "Current Chat ID", - "core.module.http.Current time": "Current Time", - "core.module.http.Histories": "History Records", - "core.module.http.Key already exists": "Key Already Exists", - "core.module.http.Key cannot be empty": "Parameter Name Cannot Be Empty", - "core.module.http.Props name": "Parameter Name", - "core.module.http.Props tip": "You can set related parameters for the HTTP request\nYou can call global variables or external parameter inputs through {{key}}, currently available variables:\n{{variable}}", - "core.module.http.Props value": "Parameter Value", - "core.module.http.ResponseChatItemId": "AI Response ID", + "core.module.http.ChatId": "Current chat ID", + "core.module.http.Current time": "Current time", + "core.module.http.Histories": "History", + "core.module.http.Key already exists": "The key already exists.", + "core.module.http.Key cannot be empty": "The parameter name is required.", + "core.module.http.Props name": "Parameter name", + "core.module.http.Props tip": "Configure HTTP request parameters.\nYou can enter a slash (/) to call a variable. Available variables:\n{{variable}}", + "core.module.http.Props value": "Parameter value", + "core.module.http.ResponseChatItemId": "Answer ID", "core.module.http.Url and params have been split": "Path parameters have been automatically added to Params", - "core.module.http.curl import": "cURL Import", - "core.module.http.curl import placeholder": "Please enter the cURL format content, the request information of the first interface will be extracted.", - "core.module.input.Add Branch": "Add Branch", - "core.module.input.add": "Add Condition", - "core.module.input.description.Background": "You can add some specific content introductions to better identify the type of user questions. This content is usually to introduce something the model does not know.", - "core.module.input.description.HTTP Dynamic Input": "Receive the output value of the previous node as a variable, which can be used by the HTTP request parameters.", - "core.module.input.description.Http Request Header": "Custom request headers, please strictly fill in the JSON string.\n1. Ensure that the last attribute has no comma\n2. Ensure that the key contains double quotes\nFor example: {\"Authorization\":\"Bearer xxx\"}", - "core.module.input.description.Http Request Url": "New HTTP request address. If there are two 'request addresses', you can delete this module and re-add it to pull the latest module configuration.", - "core.module.input.description.Response content": "You can use \\n to achieve continuous line breaks.\nYou can achieve replies through external module input, and the content filled in here will be overwritten by external module input.\nIf non-string type data is passed in, it will be automatically converted to a string", - "core.module.input.label.Background": "Background Knowledge", - "core.module.input.label.Http Request Url": "Request Address", - "core.module.input.label.Response content": "Response Content", - "core.module.input.label.Select dataset": "Select Dataset", - "core.module.input.label.aiModel": "AI Model", - "core.module.input.label.chat history": "Chat History", - "core.module.input.label.user question": "User Question", - "core.module.input.placeholder.Classify background": "For example:\n1. AIGC (Artificial Intelligence Generated Content) refers to the use of artificial intelligence technology to automatically or semi-automatically generate digital content, such as text, images, music, videos, etc.\n2. AIGC technology includes but is not limited to natural language processing, computer vision, machine learning, and deep learning. These technologies can create new content or modify existing content to meet specific creative, educational, entertainment, or informational needs.", + "core.module.http.curl import": "Import using cURL", + "core.module.http.curl import placeholder": "Please enter a cURL command. The system will extract the request details from the first API.", + "core.module.input.Add Branch": "Add branch", + "core.module.input.add": "Add condition", + "core.module.input.description.Background": "You can add descriptions for specific content to help the system identify the question type. The descriptions are typically used to introduce unfamiliar content to the model.", + "core.module.input.description.HTTP Dynamic Input": "Output values from upstream nodes are received as variables, which can be used in HTTP request parameters.", + "core.module.input.description.Http Request Header": "Custom request header. Enter a JSON string that meets the following requirements:\n1. The last attribute is not followed by a comma.\n2. The key must be enclosed in double quotes.\nExample: {\"Authorization\":\"Bearer xxx\"}", + "core.module.input.description.Http Request Url": "New HTTP request address. If two request addresses exist, delete the module and add it again to obtain the latest module configuration.", + "core.module.input.description.Response content": "Consecutive line breaks can be implemented using \\n.\nThe answer can be provided by an external module, which will overwrite the content specified here.\nIf the specified content is not a string, it will be automatically converted to a string.", + "core.module.input.label.Background": "Background knowledge", + "core.module.input.label.Http Request Url": "Request URL", + "core.module.input.label.Response content": "Answer", + "core.module.input.label.Select dataset": "Select knowledge base", + "core.module.input.label.aiModel": "Model", + "core.module.input.label.chat history": "Chats remembered", + "core.module.input.label.user question": "Question", + "core.module.input.placeholder.Classify background": "Example:\n1. AI-generated content (AIGC) refers to the use of artificial intelligence technologies to automatically or semi-automatically generate digital content, such as text, images, music, and videos.\n2. AIGC technologies include but are not limited to natural language processing, computer vision, machine learning, and deep learning. These technologies can create new content or modify existing content to meet specific creative, educational, entertainment, or informational needs.", "core.module.input_description": "Description", - "core.module.input_form": "Input form", - "core.module.input_name": "input_name", - "core.module.input_type": "Input type", - "core.module.laf.Select laf function": "Select LAF Function", - "core.module.output.description.Ai response content": "Will be triggered after the stream reply is completed", - "core.module.output.description.New context": "Splice the current reply content with the history records and return it as the new context", - "core.module.output.description.query extension result": "Output as a string array, which can be directly connected to the 'User Question' of 'Dataset Search'. It is recommended not to connect to the 'User Question' of 'AI Chat'", - "core.module.output.label.Ai response content": "AI Response Content", - "core.module.output.label.New context": "New Context", - "core.module.output.label.query extension result": "Optimization Result", - "core.module.template.AI function": "AI Capability", + "core.module.input_form": "Field", + "core.module.input_name": "Name", + "core.module.input_type": "Type", + "core.module.laf.Select laf function": "LAF function", + "core.module.output.description.Ai response content": "Triggered after the stream answer is complete.", + "core.module.output.description.New context": "Combines the current answer with the chat history to output a new context.", + "core.module.output.description.query extension result": "Outputs a string array that can be referenced in the Question area on the Knowledge base search page. It is not recommended to reference in the Question area on the AI chat page.", + "core.module.output.label.Ai response content": "AI answer", + "core.module.output.label.New context": "New context", + "core.module.output.label.query extension result": "Optimization result", + "core.module.template.AI function": "AI capability", "core.module.template.AI response switch tip": "If you want the current node not to output content, you can turn off this switch. The content output by AI will not be displayed to the user, and you can manually use 'AI Response Content' for special processing.", - "core.module.template.AI support tool tip": "Models that support function calls can better use tool calls.", - "core.module.template.Basic Node": "Basic", - "core.module.template.Query extension": "Question Optimization", - "core.module.template.System Plugin": "System Plugin", - "core.module.template.System input module": "System Input", - "core.module.template.Team app": "Team", - "core.module.template.UnKnow Module": "Unknown Module", - "core.module.template.ai_chat": "AI conversation", - "core.module.template.ai_chat_intro": "AI large model dialogue", + "core.module.template.AI support tool tip": "Models that support function calls can call tools more efficiently.", + "core.module.template.Basic Node": "Basic features", + "core.module.template.Query extension": "Question optimization", + "core.module.template.System Plugin": "System plugins", + "core.module.template.System input module": "System input", + "core.module.template.Team app": "Team apps", + "core.module.template.UnKnow Module": "Unknown module", + "core.module.template.ai_chat": "AI chat", + "core.module.template.ai_chat_intro": "Chat with an model.", "core.module.template.all_team_app": "All", - "core.module.template.config_params": "Can configure application system parameters", + "core.module.template.config_params": "Configure system parameters for the app.", "core.module.template.empty_plugin": "Blank plugin", "core.module.template.empty_workflow": "Blank workflow", - "core.module.template.self_input": "Plug-in input", - "core.module.template.self_output": "Custom plug-in output", - "core.module.template.system_config": "System configuration", - "core.module.template.system_config_info": "Can configure application system parameters", - "core.module.template.work_start": "Process starts", - "core.module.templates.Load plugin error": "Failed to Load Plugin", - "core.module.variable add option": "Add Option", - "core.module.variable.Custom type": "Custom Variable", - "core.module.variable.add option": "Add Option", + "core.module.template.self_input": "Plugin input", + "core.module.template.self_output": "Plugin output", + "core.module.template.system_config": "System settings", + "core.module.template.system_config_info": "Configure system parameters for the app.", + "core.module.template.work_start": "Start", + "core.module.templates.Load plugin error": "Failed to load the plugin.", + "core.module.variable add option": "Add option", + "core.module.variable.Custom type": "Custom variable", + "core.module.variable.add option": "Add option", "core.module.variable.input type": "Text", - "core.module.variable.key": "Variable Key", - "core.module.variable.key is required": "Variable Key is Required", + "core.module.variable.key": "Variable key", + "core.module.variable.key is required": "The variable key is required.", "core.module.variable.select type": "Dropdown Single Select", - "core.module.variable.text max length": "Max Length", + "core.module.variable.text max length": "Max length", "core.module.variable.textarea type": "Paragraph", - "core.module.variable.variable name is required": "Variable Name Cannot Be Empty", - "core.module.variable.variable option is required": "Options Cannot Be All Empty", - "core.module.variable.variable option is value is required": "Option Content Cannot Be Empty", - "core.module.variable.variable options": "Options", - "core.plugin.Custom headers": "Custom Request Headers", - "core.plugin.Get Plugin Module Detail Failed": "Failed to Retrieve Plugin Information", - "core.plugin.Http plugin intro placeholder": "For display only, no actual effect", - "core.plugin.cost": "Points Consumption:", - "core.tip.leave page": "Content has been modified, confirm to leave the page?", - "core.view_chat_detail": "View Chat Details", - "core.workflow.Can not delete node": "This Node Cannot Be Deleted", - "core.workflow.Change input type tip": "Changing the input type will clear the filled values, please confirm!", - "core.workflow.Check Failed": "Workflow verification failed, please check whether the value is missing, and whether the connection is normal.", - "core.workflow.Confirm stop debug": "Confirm to Stop Debugging? Debug Information Will Not Be Retained.", - "core.workflow.Copy node": "Node Copied", - "core.workflow.Custom inputs": "Custom Inputs", - "core.workflow.Custom outputs": "Custom Outputs", - "core.workflow.Dataset quote": "Dataset Quote", + "core.module.variable.variable name is required": "The variable name is required.", + "core.module.variable.variable option is required": "This field is required.", + "core.module.variable.variable option is value is required": "This field is required.", + "core.module.variable.variable options": "Option", + "core.plugin.Custom headers": "Custom request header", + "core.plugin.Get Plugin Module Detail Failed": "Error occurred while loading the plugin.", + "core.plugin.Http plugin intro placeholder": "For display only.", + "core.plugin.cost": "Credits consumed:", + "core.tip.leave page": "Leaving the page will discard current changes. Would you like to proceed?", + "core.view_chat_detail": "View chat details", + "core.workflow.Can not delete node": "Unable to delete the node.", + "core.workflow.Change input type tip": "Changing the input type will clear specified values. Would you like to proceed?", + "core.workflow.Check Failed": "Failed to verify the workflow. Please check whether the required parameters or values are missing, and check the connection status.", + "core.workflow.Confirm stop debug": "Are you sure you want to stop debugging? The debugging information will not be retained.", + "core.workflow.Copy node": "Node copied successfully.", + "core.workflow.Custom inputs": "Custom input", + "core.workflow.Custom outputs": "Custom output", + "core.workflow.Dataset quote": "Knowledge base reference", "core.workflow.Debug": "Debug", - "core.workflow.Debug Node": "Debug Mode", - "core.workflow.Failed": "Run Failed", - "core.workflow.Not intro": "This Node Has No Introduction", + "core.workflow.Debug Node": "Debug", + "core.workflow.Failed": "Failed to run the app.", + "core.workflow.Not intro": "No description yet.", "core.workflow.Run": "Run", "core.workflow.Running": "Running", - "core.workflow.Save and publish": "Save and Publish", - "core.workflow.Save to cloud": "Save Only", - "core.workflow.Skipped": "Skipped", - "core.workflow.Stop debug": "Stop Debugging", - "core.workflow.Success": "Run Successful", - "core.workflow.Value type": "Data Type", - "core.workflow.debug.Done": "Debugging Completed", - "core.workflow.debug.Hide result": "Hide Result", - "core.workflow.debug.Not result": "No Run Result", - "core.workflow.debug.Run result": "Run Result", - "core.workflow.debug.Show result": "Show Result", - "core.workflow.dynamic_input": "dynamic input", - "core.workflow.inputType.JSON Editor": "JSON Input Box", - "core.workflow.inputType.Manual input": "Manual Input", - "core.workflow.inputType.Manual select": "Manual Select", - "core.workflow.inputType.Reference": "Variable Reference", - "core.workflow.inputType.custom": "Custom Variable", - "core.workflow.inputType.dynamicTargetInput": "Dynamic External Data", - "core.workflow.inputType.input": "Single Line Input Box", - "core.workflow.inputType.number input": "Number Input Box", - "core.workflow.inputType.select": "Single Select Box", - "core.workflow.inputType.selectApp": "App Select", - "core.workflow.inputType.selectDataset": "Dataset Select", - "core.workflow.inputType.selectLLMModel": "Chat Model Select", + "core.workflow.Save and publish": "Publish", + "core.workflow.Save to cloud": "Save", + "core.workflow.Skipped": "Skip running", + "core.workflow.Stop debug": "Stop debugging", + "core.workflow.Success": "App ran successfully.", + "core.workflow.Value type": "Data type", + "core.workflow.debug.Done": "Finish debugging", + "core.workflow.debug.Hide result": "Hide result", + "core.workflow.debug.Not result": "No results available.", + "core.workflow.debug.Run result": "Running result", + "core.workflow.debug.Show result": "Show result", + "core.workflow.dynamic_input": "Dynamic input", + "core.workflow.inputType.JSON Editor": "JSON input box", + "core.workflow.inputType.Manual input": "Manually input", + "core.workflow.inputType.Manual select": "Select", + "core.workflow.inputType.Reference": "Variable reference", + "core.workflow.inputType.custom": "Custom variable", + "core.workflow.inputType.dynamicTargetInput": "Dynamic external data", + "core.workflow.inputType.input": "Single-line input box", + "core.workflow.inputType.number input": "Numeric input box", + "core.workflow.inputType.select": "Single-select dropdown", + "core.workflow.inputType.selectApp": "Select app", + "core.workflow.inputType.selectDataset": "Select knowledge base", + "core.workflow.inputType.selectLLMModel": "Chat model", "core.workflow.inputType.switch": "Switch", - "core.workflow.inputType.textInput": "Text Input box", - "core.workflow.inputType.textarea": "Multi-line Input Box", - "core.workflow.publish.OnRevert version": "Click to Revert to This Version", - "core.workflow.publish.OnRevert version confirm": "Confirm to Revert to This Version? The configuration of the editing version will be saved, and a new release version will be created for the reverted version.", - "core.workflow.publish.histories": "Release Records", - "core.workflow.template.Interactive": "Interactive", + "core.workflow.inputType.textInput": "Text input box", + "core.workflow.inputType.textarea": "Multi-line input box", + "core.workflow.publish.OnRevert version": "Roll back", + "core.workflow.publish.OnRevert version confirm": "Are you sure you want to roll back to the version? Your current edits will be saved, and a new release with the configuration of the target version will be published.", + "core.workflow.publish.histories": "Publishing records", + "core.workflow.template.Interactive": "Interaction", "core.workflow.template.Search": "Search", - "core.workflow.tool.Handle": "Tool Connector", - "core.workflow.tool.Select Tool": "Select Tool", + "core.workflow.tool.Handle": "Tool connector", + "core.workflow.tool.Select Tool": "Select tool", "core.workflow.variable": "Variable", - "create": "Create", - "create_failed": "Create failed", - "create_success": "Created Successfully", - "create_time": "Creation Time", - "cron_job_run_app": "Scheduled Task", - "custom_title": "Custom Title", + "create": "Create now", + "create_failed": "Creation failed.", + "create_success": "Created successfully.", + "create_time": "Time created", + "cron_job_run_app": "Scheduled task", + "custom_title": "Custom title", "data_index_custom": "Custom index", "data_index_default": "Default index", - "data_index_question": "Inferred question index", - "data_index_summary": "Summary Index", - "data_not_found": "Data can't be found", - "dataset.Confirm move the folder": "Confirm to Move to This Directory", - "dataset.Confirm to delete the data": "Confirm to Delete This Data?", - "dataset.Confirm to delete the file": "Confirm to Delete This File and All Its Data?", - "dataset.Create Folder": "Create Folder", - "dataset.Create manual collection": "Create Manual Dataset", - "dataset.Delete Dataset Error": "Delete Dataset Error", - "dataset.Edit Folder": "Edit Folder", - "dataset.Edit Info": "Edit Information", + "data_index_question": "Predicted question index", + "data_index_summary": "Summary index", + "data_not_found": "Data not found.", + "dataset.Confirm move the folder": "Are you sure you want to move it to the directory?", + "dataset.Confirm to delete the data": "Are you sure you want to delete the entry?", + "dataset.Confirm to delete the file": "Are you sure you want to delete the file and its data?", + "dataset.Create Folder": "Create folder", + "dataset.Create manual collection": "Create manual dataset", + "dataset.Delete Dataset Error": "Error occurred while deleting the knowledge base.", + "dataset.Edit Folder": "Edit folder", + "dataset.Edit Info": "Edit", "dataset.Export": "Export", - "dataset.Export Dataset Limit Error": "Export Data Failed", - "dataset.Folder Name": "Enter Folder Name", + "dataset.Export Dataset Limit Error": "Failed to export data.", + "dataset.Folder Name": "Folder name", "dataset.Insert Data": "Insert", - "dataset.Manual collection Tip": "Manual datasets allow you to create an empty container to hold data", + "dataset.Manual collection Tip": "A manual dataset allows you to create an empty container to add data.", "dataset.Move Failed": "Move Error", "dataset.Select Dataset": "Select This Dataset", - "dataset.Select Dataset Tips": "Only Datasets with the same index model can be selected", - "dataset.Select Folder": "Enter Folder", - "dataset.Training Name": "Data Training", - "dataset.collections.Collection Embedding": "{{total}} Indexes", - "dataset.collections.Confirm to delete the folder": "Confirm to Delete This Folder and All Its Contents?", + "dataset.Select Dataset Tips": "You can select knowledge bases only from those using the same index model.", + "dataset.Select Folder": "Open folder", + "dataset.Training Name": "Data training", + "dataset.collections.Collection Embedding": "{{total}} data groups are being indexed.", + "dataset.collections.Confirm to delete the folder": "Are you sure you want to delete the folder and its contents?", "dataset.collections.Create And Import": "Create/Import", "dataset.collections.Select Collection": "Select File", - "dataset.collections.Select One Collection To Store": "Select a File to Store", - "dataset.data.Can not edit": "No Edit Permission", - "dataset.data.Default Index": "Default Index", - "dataset.data.Delete Tip": "Confirm to Delete This Data?", - "dataset.data.Index Placeholder": "Enter Index Text Content", - "dataset.data.Input Success Tip": "Data Imported Successfully", - "dataset.data.Update Success Tip": "Data Updated Successfully", - "dataset.data.edit.Index": "Data Index ({{amount}})", - "dataset.data.edit.divide_content": "Segment Content", - "dataset.data.input is empty": "Data Content Cannot Be Empty", - "dataset.dataset_name": "Dataset Name", - "dataset.deleteFolderTips": "Confirm to Delete This Folder and All Its Contained Datasets? Data Cannot Be Recovered After Deletion, Please Confirm!", - "dataset.test.noResult": "No Search Results", + "dataset.collections.Select One Collection To Store": "Select a file to store", + "dataset.data.Can not edit": "You do not have permission to edit.", + "dataset.data.Default Index": "Default index", + "dataset.data.Delete Tip": "Are you sure you want to delete the entry?", + "dataset.data.Index Placeholder": "Enter text for indexing", + "dataset.data.Input Success Tip": "Data imported successfully.", + "dataset.data.Update Success Tip": "Data updated successfully.", + "dataset.data.edit.Index": "Data indexes {{amount}}", + "dataset.data.edit.divide_content": "Chunk", + "dataset.data.input is empty": "This field is required.", + "dataset.dataset_name": "Knowledge base name", + "dataset.deleteFolderTips": "Are you sure you want to delete the folder and knowledge bases in it? Deleted data cannot be recovered.", + "dataset.test.noResult": "No match found.", "dataset_data_input_a": "Answer", - "dataset_data_input_chunk": "Chunk", - "dataset_data_input_chunk_content": "Chunk", - "dataset_data_input_q": "question", - "dataset_data_input_qa": "QA", - "dataset_text_model_tip": "Used for text processing in the knowledge base preprocessing stage, such as automatic supplementary indexing, Q&A pair extraction.", - "date_12_months": "12 Months", - "date_1_month": "1 Month", - "date_3_months": "3 Months", - "date_6_months": "6 Months", - "deep_rag_search": "In-depth search", - "delete_api": "Are you sure you want to delete this API key? \nAfter deletion, the key will become invalid immediately and the corresponding conversation log will not be deleted. Please confirm!", - "delete_failed": "Deletion Failed", - "delete_folder": "Delete Folder", - "delete_success": "Deleted Successfully", - "delete_warning": "Deletion Warning", - "embedding_model_not_config": "No index model is detected", + "dataset_data_input_chunk": "Regular mode", + "dataset_data_input_chunk_content": "Content", + "dataset_data_input_q": "Question", + "dataset_data_input_qa": "Q&A mode", + "dataset_text_model_tip": "Used for preprocessing knowledge base text, such as automatic supplemental index generation and Q&A pair extraction.", + "date_12_months": "12 months", + "date_1_month": "1 month", + "date_3_months": "3 months", + "date_6_months": "6 months", + "deep_rag_search": "Deep search", + "delete_api": "Are you sure you want to delete the API key? The key will become invalid immediately, but the corresponding chat logs will not be deleted. Would you like to proceed?", + "delete_failed": "Deletion failed.", + "delete_folder": "Delete folder", + "delete_success": "Deleted successfully.", + "delete_warning": "Confirm", + "embedding_model_not_config": "No index model available.", "enable_auth": "Enable authentication", - "error.Create failed": "Create failed", - "error.code_error": "Verification code error", - "error.fileNotFound": "File not found~", - "error.inheritPermissionError": "Inherit permission Error", - "error.invalid_params": "Invalid parameter", - "error.missingParams": "Insufficient parameters", - "error.send_auth_code_too_frequently": "Please do not obtain verification code frequently", - "error.too_many_request": "Too many request", - "error.unKnow": "An Unexpected Error Occurred", - "error.upload_file_error_filename": "{{name}} Upload Failed", - "error.upload_image_error": "File upload failed", - "error.username_empty": "Account cannot be empty", - "error_collection_not_exist": "The collection does not exist", - "error_embedding_not_config": "Unconfigured index model", - "error_invalid_resource": "Invalid resources", - "error_llm_not_config": "Unconfigured file understanding model", - "error_un_permission": "No permission to operate", - "error_vlm_not_config": "Image comprehension model not configured", - "exit_directly": "exit_directly", - "expired_time": "Expiration Time", - "export_to_json": "Export to JSON", - "extraction_results": "Extraction Results", + "error.Create failed": "Creation failed", + "error.code_error": "The verification code is incorrect.", + "error.fileNotFound": "File not found.", + "error.inheritPermissionError": "Permission inheritance error.", + "error.invalid_params": "Invalid parameter.", + "error.missingParams": "Parameter is missing.", + "error.send_auth_code_too_frequently": "Too many attempts.", + "error.too_many_request": "Too many attempts. Please try again later.", + "error.unKnow": "Error occurred.", + "error.upload_file_error_filename": "Failed to upload {{name}}.", + "error.upload_image_error": "Upload failed.", + "error.username_empty": "The Account field is required.", + "error_collection_not_exist": "The collection does not exist.", + "error_embedding_not_config": "No index model configured.", + "error_invalid_resource": "Invalid resource.", + "error_llm_not_config": "No LLMs available.", + "error_un_permission": "No permission.", + "error_vlm_not_config": "No VLMs available.", + "exit_directly": "Exit", + "expired_time": "Expiration time", + "export_to_json": "Export as JSON", + "extraction_results": "Extraction result", "failed": "Failed", - "field_name": "Field Name", - "folder.empty": "No More Items in This Directory", - "folder.open_dataset": "Open Dataset", - "folder_description": "Folder Description", + "field_name": "Field name", + "folder.empty": "No items available.", + "folder.open_dataset": "Click to open it.", + "folder_description": "Description", "free": "Free", - "get_QR_failed": "Failed to Get QR Code", - "get_app_failed": "Failed to Retrieve App", - "get_laf_failed": "Failed to Retrieve Laf Function List", - "had_auth_value": "Filled in", - "has_verification": "Verified, Click to Unbind", + "get_QR_failed": "Failed to obtain the QR code.", + "get_app_failed": "Failed to obtain the app.", + "get_laf_failed": "Failed to obtain LAF functions.", + "had_auth_value": "Done already", + "has_verification": "Verified. Click to unbind.", "have_done": "Completed", - "import_failed": "Import Failed", - "import_success": "Imported Successfully", - "info.buy_extra": "Buy Extra Package", - "info.csv_download": "Click to Download Batch Test Template", - "info.csv_message": "Read the first column of the CSV file for batch testing, supporting up to 100 groups of data at a time.", - "info.felid_message": "Field key must be pure English letters or numbers and cannot start with a number.", - "info.free_plan": "If a free team does not log in to the system for 30 consecutive days, the system will automatically clear the account's Dataset.", - "info.include": "Includes Standard Package and Extra Resource Pack", - "info.node_info": "Adjusting this module will affect the timing of tool calls.\nYou can guide the model to call tools by accurately describing the function of this module.", - "info.old_version_attention": "Detected that your advanced orchestration is an old version. The system will automatically format it into the new workflow version.\n\nDue to significant version differences, some workflows may not be arranged correctly. Please manually reconnect the workflow. If it is still abnormal, try deleting the corresponding node and re-adding it.\n\nYou can directly click debug to test the workflow. After debugging, click publish. The new workflow will only be saved and take effect after you click publish.\n\nBefore you publish the new workflow, auto-save will not take effect.", - "info.open_api_notice": "You can fill in the relevant keys of OpenAI/OneAPI. If you fill in this content, the 'AI Chat', 'Question Classification', and 'Content Extraction' on the online platform will use the key you filled in and will not be charged. Please check if your key has access to the corresponding model. GPT models can choose FastAI.", - "info.open_api_placeholder": "Request address, default is the official OpenAI. You can fill in the transit address, 'v1' will not be automatically completed", - "info.resource": "Resource Usage", - "input.Repeat Value": "Duplicate Value", - "input_name": "Enter a Name", - "invalid_time": "Validity period", - "invalid_variable": "Invalid Variable", - "is_open": "Is Open", - "is_requesting": "Requesting...", - "is_using": "In Use", - "item_description": "Field Description", - "item_name": "Field Name", - "json_config": "JSON Configuration", - "json_parse_error": "Possible JSON Error, Please Check Carefully", - "just_now": "just", - "key": "key", - "key_repetition": "Key Repetition", + "import_failed": "Import failed.", + "import_success": "Import successful.", + "info.buy_extra": "Purchase additional plan", + "info.csv_download": "Download bulk test template", + "info.csv_message": "Read the first column in the CSV file for bulk testing (up to 100 entries per run).", + "info.felid_message": "The key field must contain only letters or digits, and cannot start with a digit.", + "info.free_plan": "If a team using the free edition is inactive for 30 days, its knowledge bases will be cleared automatically.", + "info.include": "Includes the standard plan and additional resource packages.", + "info.node_info": "Changes to this module will affect tool calls.\nProvide a clear description of the module's feature to help the model use the correct tools.", + "info.old_version_attention": "The current module version (Advanced orchestration) is outdated. The system will automatically upgrade it to the new version (Workflow).\n\nSome workflows cannot be arranged correctly due to version differences. Please manually connect to the workflows. If the problem persists, try deleting the affected node and add it again.\n\nClick Debug to test the workflow. After debugging is complete, click Save and publish. The new workflow is not saved or active until you click Save and publish.\n\nAuto saving will not take effect until you publish the new workflow.", + "info.open_api_notice": "Enter your OpenAI or OneAPI key. The key will be used for AI chats, question classifier, and content extraction without consuming credits. Make sure the key has required permissions to access relevant models. You can choose FastAI as the GPT model.", + "info.open_api_placeholder": "Request address. The official OpenAI address will be used by default. You can enter a proxy address. Note that \"/v1\" will not be automatically appended to the address.", + "info.resource": "Resource usage", + "input.Repeat Value": "Duplicate values exist.", + "input_name": "Name", + "invalid_time": "Validity", + "invalid_variable": "Invalid variable.", + "is_open": "Status", + "is_requesting": "Sending request...", + "is_using": "In use", + "item_description": "Description", + "item_name": "Field name", + "json_config": "JSON configuration file", + "json_parse_error": "The JSON configuration file may be invalid. Please check.", + "just_now": "Just now", + "key": "Key", + "key_repetition": "The key already exists.", "last_step": "Previous", - "last_use_time": "Last Use Time", - "link.UnValid": "Invalid Link", - "llm_model_not_config": "No language model was detected", - "load_failed": "load_failed", - "logout": "Sign out", - "max_quote_tokens": "Quote cap", - "max_quote_tokens_tips": "The maximum number of tokens in a single search, about 1 character in Chinese = 1.7 tokens, and about 1 character in English = 1 token", - "mcp_server": "MCP Services", - "member": "member", - "min_similarity": "lowest correlation", - "min_similarity_tip": "The relevance of different index models is different. Please select the appropriate value through search testing. \nWhen using Result Rearrange , use the rearranged results for filtering.", - "model.billing": "Billing", + "last_use_time": "Last accessed", + "link.UnValid": "Invalid link.", + "llm_model_not_config": "No language model available.", + "load_failed": "Loading failed.", + "logout": "Log out", + "max_quote_tokens": "Max tokens", + "max_quote_tokens_tips": "The maximum number of tokens consumed per search (about 1.7 tokens for a Chinese character and 1 token for an English character)", + "mcp_server": "MCP services", + "member": "Member", + "min_similarity": "Min relevance", + "min_similarity_tip": "Relevance varies across index models. Please specify an appropriate value through search testing. When the result reranking feature is enabled, results are filtered based on the reranked output.", + "model.billing": "Model billing", "model.model_type": "Model type", "model.name": "Model name", - "model.provider": "Provider", - "model.search_name_placeholder": "Search by model name", - "model.type.chat": "LLM", - "model.type.embedding": "Embedding", - "model.type.reRank": "ReRank", - "model.type.stt": "STT", - "model.type.tts": "TTS", - "model_alicloud": "Ali Cloud", + "model.provider": "Model provider", + "model.search_name_placeholder": "Model name", + "model.type.chat": "Language model", + "model.type.embedding": "Index model", + "model.type.reRank": "Rerank model", + "model.type.stt": "Speech-to-text", + "model.type.tts": "Text-to-speech", + "model_alicloud": "Alibaba Cloud", "model_baai": "BAAI", - "model_baichuan": "Baichuan", + "model_baichuan": "Baichuan AI", "model_chatglm": "ChatGLM", "model_doubao": "Doubao", - "model_ernie": "Ernie", - "model_hunyuan": "Hunyuan", - "model_intern": "Intern", - "model_moka": "Moka-AI", - "model_moonshot": "Moonshot", + "model_ernie": "ERNIE Bot", + "model_hunyuan": "Tencent Hunyuan", + "model_intern": "Intern AI", + "model_moka": "Moka AI", + "model_moonshot": "Moonshot AI", "model_other": "Other", "model_ppio": "PPIO", "model_qwen": "Qwen", - "model_siliconflow": "Siliconflow", - "model_sparkdesk": "SprkDesk", + "model_siliconflow": "SiliconFlow", + "model_sparkdesk": "iFLYTEK Spark", "model_stepfun": "StepFun", - "model_yi": "Yi", - "month": "Month", - "move.confirm": "Confirm move", - "move_success": "Moved Successfully", - "move_to": "Move to", - "name": "name", - "name_is_empty": "Name Cannot Be Empty", + "model_yi": "01.AI", + "month": "Monthly", + "move.confirm": "Confirm", + "move_success": "Moved successfully.", + "move_to": "Move", + "name": "Name", + "name_is_empty": "Name is required.", "navbar.Account": "Account", "navbar.Chat": "Chat", - "navbar.Datasets": "Dataset", + "navbar.Datasets": "Knowledge", "navbar.Studio": "Studio", - "navbar.Toolkit": "Toolkit", - "navbar.Tools": "Tools", - "new_create": "Create New", + "navbar.Toolkit": "Toolbox", + "navbar.Tools": "Tool", + "new_create": "Create", "next_step": "Next", "no": "No", - "no_child_folder": "No Subdirectories, Place Here", - "no_intro": "No Introduction Available", - "no_laf_env": "System Not Configured with Laf Environment", - "no_more_data": "No More Data", - "no_pay_way": "There is no suitable payment channel in the system", - "no_select_data": "No Data Available", - "not_model_config": "No related model configured", - "not_open": "Not Open", - "not_permission": "The current subscription package does not support team operation logs", - "not_support": "Not Supported", - "not_support_wechat_image": "This is a WeChat picture", - "not_yet_introduced": "No Introduction Yet", - "open_folder": "Open Folder", + "no_child_folder": "No subdirectory available. You can move it here.", + "no_intro": "", + "no_laf_env": "LAF environment not configured.", + "no_more_data": "No data available.", + "no_pay_way": "No payment channel available.", + "no_select_data": "No value available.", + "not_model_config": "Not specified", + "not_open": "Disabled", + "not_permission": "The current plan does not support logging member operations.", + "not_support": "Not supported", + "not_support_wechat_image": "WeChat image", + "not_yet_introduced": "", + "open_folder": "Click to open the folder.", "option": "Option", "page": "Page", - "page_center": "Page Center", + "page_center": "Centered", "pay.amount": "Amount", - "pay.error_desc": "There was a problem when converting payment routes", - "pay.noclose": "After payment is completed, please wait for the system to update automatically", - "pay.package_tip.buy": "The package you purchased is lower than the current package. This package will take effect after the current package expires.\nYou can view the package usage in Account - Personal Information - Package Details.", - "pay.package_tip.renewal": "You are renewing the package. You can view the package usage in Account - Personal Information - Package Details.", - "pay.package_tip.upgrade": "The package you purchased is higher than the current package. This package will take effect immediately, and the current package will take effect later. You can view the package usage in Account - Personal Information - Package Details.", - "pay.wechat": "Please scan the QR code on WeChat to pay: {{price}} yuan\n\nPlease do not close the page before payment is completed", - "pay.wx_payment": "WeChat Payment", - "pay.yuan": "{{amount}} Yuan", - "pay_alipay_payment": "Alipay Payment", - "pay_corporate_payment": "Payment to the public", - "pay_money": "Amount payable", - "pay_success": "Payment successfully", - "pay_year_tip": "Pay 10 months, enjoy 1 year!", - "permission.Collaborator": "Collaborator", - "permission.Default permission": "Default Permission", - "permission.Manage": "Manage", - "permission.No InheritPermission": "Permission Inheritance Restricted", - "permission.Not collaborator": "No Collaborator", - "permission.Owner": "Owner", - "permission.Permission": "Permission", - "permission.Permission config": "Permission Configuration", + "pay.error_desc": "Error occurred while switching payment method.", + "pay.noclose": "After the payment is complete, wait for the system to update automatically.", + "pay.package_tip.buy": "This plan will take effect after your current plan expires because it is of a lower tier than your current plan.\nYou can check plan usage in Account > Profile > Plan details.", + "pay.package_tip.renewal": "Renewing... You can check plan usage in Account > Profile > Plan details.", + "pay.package_tip.upgrade": "This plan will take effect immediately, and your current plan will take effect later because it is of a higher tier than your current plan. You can check plan usage in Account > Profile > Plan details.", + "pay.wechat": "Use WeChat to scan the QR code and pay {{price}} CNY\nDo not close this page before the payment is complete.", + "pay.wx_payment": "WeChat Pay", + "pay.yuan": "{{amount}} CNY", + "pay_alipay_payment": "Alipay", + "pay_corporate_payment": "Corporate payment", + "pay_money": "Amount", + "pay_success": "Payment successful.", + "pay_year_tip": "Pay for 10 months, enjoy 1 year!", + "permission.Collaborator": "Collaborators", + "permission.Default permission": "Default permission", + "permission.Manage": "Settings", + "permission.No InheritPermission": "Permissions are restricted and no longer inherited from the parent folder.", + "permission.Not collaborator": "No collaborators available.", + "permission.Owner": "Creator", + "permission.Permission": "Permissions", + "permission.Permission config": "Permissions", "permission.Private": "Private", - "permission.Private Tip": "Only Available to Yourself", - "permission.Public": "Team", - "permission.Public Tip": "Available to All Team Members", - "permission.Remove InheritPermission Confirm": "This operation will invalidate permission inheritance. Proceed?", - "permission.Resume InheritPermission Confirm": "Resume inheriting permissions from the parent folder?", - "permission.Resume InheritPermission Failed": "Resume Failed", - "permission.Resume InheritPermission Success": "Resume Successful", - "permission.change_owner": "Transfer Ownership", - "permission.change_owner_failed": "Transfer Ownership Failed", - "permission.change_owner_placeholder": "Enter Username to Search Account", - "permission.change_owner_success": "Ownership Transferred Successfully", - "permission.change_owner_tip": "Your permissions will not be retained after the transfer", + "permission.Private Tip": "Only me", + "permission.Public": "Collaboration", + "permission.Public Tip": "Available to all team members", + "permission.Remove InheritPermission Confirm": "This operation will disable permission inheritance. Would you like to proceed?", + "permission.Resume InheritPermission Confirm": "Are you sure you want to restore permissions to be inherited from the parent folder?", + "permission.Resume InheritPermission Failed": "Restoration failed.", + "permission.Resume InheritPermission Success": "Restored successfully.", + "permission.change_owner": "Transfer ownership", + "permission.change_owner_failed": "Failed to transfer the ownership.", + "permission.change_owner_placeholder": "Username", + "permission.change_owner_success": "Ownership transferred successfully.", + "permission.change_owner_tip": "You will lose related permissions after the ownership transfer.", "permission.change_owner_to": "Transfer to", - "permission.manager": "administrator", + "permission.manager": "Admin", "permission.read": "Read permission", - "permission.write": "write permission", - "permission_other": "Other permissions (multiple)", - "please_input_name": "Please Enter a Name", - "plugin.App": "Select App", - "plugin.Currentapp": "Current App", + "permission.write": "Write permission", + "permission_other": "Other (multi-select supported)", + "please_input_name": "Name is required.", + "plugin.App": "Select app", + "plugin.Currentapp": "Current app", "plugin.Description": "Description", - "plugin.Edit Http Plugin": "Edit HTTP Plugin", - "plugin.Enter PAT": "Enter Personal Access Token (PAT)", - "plugin.Get Plugin Module Detail Failed": "Failed to Retrieve Plugin Information", - "plugin.Import Plugin": "Import HTTP Plugin", - "plugin.Import from URL": "Import from URL. https://xxxx", - "plugin.Intro": "Plugin Introduction", - "plugin.Invalid Env": "Invalid Laf Environment", - "plugin.Invalid Schema": "Invalid Schema", - "plugin.Invalid URL": "Invalid URL", + "plugin.Edit Http Plugin": "Edit HTTP plugin", + "plugin.Enter PAT": "Personal access token", + "plugin.Get Plugin Module Detail Failed": "Error occurred while obtaining plugin information.", + "plugin.Import Plugin": "Import HTTP plugin", + "plugin.Import from URL": "[Import from URL] https://xxxx", + "plugin.Intro": "Plugin description", + "plugin.Invalid Env": "LAF environment error.", + "plugin.Invalid Schema": "Invalid schema.", + "plugin.Invalid URL": "Invalid URL.", "plugin.Method": "Method", "plugin.Path": "Path", - "plugin.Please bind laf accout first": "Please Bind Laf Account First", - "plugin.Plugin List": "Plugin List", - "plugin.Search_app": "Search App", - "plugin.Set Name": "Name the Plugin", - "plugin.contribute": "Contribute Plugin", - "plugin.go to laf": "Go to Write", + "plugin.Please bind laf accout first": "Please bind your LAF account first.", + "plugin.Plugin List": "Plugins", + "plugin.Search_app": "App name", + "plugin.Set Name": "Name", + "plugin.contribute": "Plugins", + "plugin.go to laf": "Create", "plugin.path": "Path", - "price_over_wx_limit": "Exceed payment provider limit: WeChat Pay only supports less than 6,000 yuan", - "prompt_input_placeholder": "Please enter the prompt word", - "psw_inconsistency": "Passwords Do Not Match", - "question_feedback": "Work order", - "read_course": "Read Course", - "read_doc": "Read Document", - "read_quote": "View citations", - "redo_tip": "Redo ctrl shift z", - "redo_tip_mac": "Redo ⌘ shift z", - "request_end": "All Loaded", - "request_error": "request_error", - "request_more": "Click to Load More", + "price_over_wx_limit": "The payment exceeded the quota. WeChat Pay only supports transactions of 6000 CNY or less.", + "prompt_input_placeholder": "Prompt", + "psw_inconsistency": "Passwords do not match.", + "question_feedback": "Help", + "read_course": "Guide", + "read_doc": "View documentation", + "read_quote": "View reference", + "redo_tip": "Redo (Ctrl + Shift + Z)", + "redo_tip_mac": "Redo (⌘+Shift+Z)", + "request_end": "All items loaded.", + "request_error": "Request error.", + "request_more": "Click to load more.", "required": "Required", - "rerank_weight": "Rearrange weights", - "resume_failed": "Resume Failed", + "rerank_weight": "Weight", + "resume_failed": "Restoration failed.", "root_folder": "Root", - "save_failed": "save_failed", - "save_success": "Saved Successfully", - "scan_code": "Scan the QR code to pay", - "search_tool": "Search Tools", - "secret_key": "Secret", - "secret_tips": "The value will not return plaintext again after saving", + "save_failed": "Failed to save the settings.", + "save_success": "Saved successfully.", + "scan_code": "Scan to pay", + "search_tool": "Tool name", + "secret_key": "Secret key", + "secret_tips": "The value will not be shown in plain text after it is saved.", "select_count_num": "{{num}} item selected", "select_file_failed": "File Selection Failed", - "select_reference_variable": "Select Reference Variable", - "select_template": "Select Template", - "set_avatar": "Click to set_avatar", - "share_link": "Share Link", - "speech_error_tip": "Speech to Text Failed", - "speech_not_support": "Your Browser Does Not Support Speech Input", - "submit_failed": "Submission Failed", - "submit_success": "Submitted Successfully", + "select_reference_variable": "Select variable", + "select_template": "Select template", + "set_avatar": "Click to upload an icon.", + "share_link": "Sharing link", + "speech_error_tip": "Failed to convert the speech to text.", + "speech_not_support": "Your browser does not support the speech-to-text feature.", + "submit_failed": "Submission failed.", + "submit_success": "Submitted successfully.", "submitted": "Submitted", - "support": "Support", + "support": "Supported", "support.account.Individuation": "Personalization", "support.inform.Read": "Read", - "support.openapi.Api baseurl": "API Base URL", - "support.openapi.Api manager": "API Key Management", - "support.openapi.Copy success": "API Address Copied", - "support.openapi.New api key": "New API Key", - "support.openapi.New api key tip": "Please keep your key safe, it will not be displayed again", - "support.outlink.Delete link tip": "Confirm to Delete This Login-Free Link? The link will become invalid immediately after deletion, but the chat logs will be retained. Please confirm!", - "support.outlink.Max usage points": "Points Limit", - "support.outlink.Max usage points tip": "The maximum number of points allowed for this link. It cannot be used after exceeding the limit. -1 means unlimited.", - "support.outlink.Usage points": "Points Consumption", + "support.openapi.Api baseurl": "API root address", + "support.openapi.Api manager": "API keys", + "support.openapi.Copy success": "API address copied successfully.", + "support.openapi.New api key": "New API key", + "support.openapi.New api key tip": "Please save your key properly. It will not be displayed again.", + "support.outlink.Delete link tip": "Are you sure you want to delete this link? This link will immediately become invalid, but the chat history will be preserved.", + "support.outlink.Max usage points": "Max credits", + "support.outlink.Max usage points tip": "The maximum number of credits that can be used by this API key. -1 indicates no limit.", + "support.outlink.Usage points": "Credits consumed", "support.outlink.share.Chat_quote_reader": "Full text reader", - "support.outlink.share.Full_text tips": "Allows reading of the complete dataset from which the referenced fragment is derived", - "support.outlink.share.Response Quote": "Return Quote", - "support.outlink.share.Response Quote tips": "Return quoted content in the share link, but do not allow users to download the original document", + "support.outlink.share.Full_text tips": "Read the full dataset from which the referenced segment was taken.", + "support.outlink.share.Response Quote": "Referenced content", + "support.outlink.share.Response Quote tips": "View referenced content found in knowledge base searches. Full documents and referenced websites are not accessible.", "support.outlink.share.running_node": "Running node", - "support.outlink.share.show_complete_quote": "View original source", - "support.outlink.share.show_complete_quote_tips": "View and download the complete citation document, or jump to the citation website", + "support.outlink.share.show_complete_quote": "View source", + "support.outlink.share.show_complete_quote_tips": "View and download full referenced documents, or redirect to the referenced websites.", "support.permission.Permission": "Permission", - "support.standard.AI Bonus Points": "AI Points", - "support.standard.due_date": "Due Date", - "support.standard.storage": "Storage", + "support.standard.AI Bonus Points": "Credits", + "support.standard.due_date": "Expiration time", + "support.standard.storage": "Max indexes", "support.standard.type": "Type", - "support.team.limit.No permission rerank": "No Permission to Use Result Re-rank, Please Upgrade Your Package", - "support.user.Avatar": "Avatar", - "support.user.Go laf env": "Click to Go to {{env}} to Get PAT Token.", - "support.user.Laf account course": "View the Tutorial for Binding Laf Account.", - "support.user.Laf account intro": "After binding your Laf account, you can use the Laf module in the workflow to write code online.", - "support.user.Need to login": "Please Log In First", - "support.user.Price": "Pricing", + "support.team.limit.No permission rerank": "You do not have permission to use the result reranking feature. Please upgrade your plan.", + "support.user.Avatar": "Profile image", + "support.user.Go laf env": "Obtain PAT token from {{env}}", + "support.user.Laf account course": "View guide for binding LAF account", + "support.user.Laf account intro": "Bind your LAF account to enable the LAF module in workflows for online coding.", + "support.user.Need to login": "Please log in first.", + "support.user.Price": "Billing standard", "support.user.User self info": "Profile", - "support.user.auth.Sending Code": "Sending Code", - "support.user.auth.get_code": "Get Verification Code", - "support.user.auth.get_code_again": "s Get Again", - "support.user.captcha_placeholder": "Please enter the verification code", - "support.user.info.bind_notification_error": "Abnormal binding notification account", - "support.user.info.bind_notification_hint": "Please bind the notification receiving account to ensure that you can receive notifications such as package expiration reminders, etc., to ensure the normal operation of your service.", - "support.user.info.bind_notification_success": "Binding notification account successful", - "support.user.info.code_required": "Verification code cannot be empty", - "support.user.info.notification_receiving_hint": "Notification reception", - "support.user.info.verification_code": "Verification Code", - "support.user.inform.System message": "System Message", + "support.user.auth.Sending Code": "Sending...", + "support.user.auth.get_code": "Send", + "support.user.auth.get_code_again": "s", + "support.user.captcha_placeholder": "", + "support.user.info.bind_notification_error": "Failed", + "support.user.info.bind_notification_hint": "For service continuity, please specify an account to receive renewal and other notifications.", + "support.user.info.bind_notification_success": "Operation successful.", + "support.user.info.code_required": "Enter the verification code", + "support.user.info.notification_receiving_hint": "Notification recipient", + "support.user.info.verification_code": "Code", + "support.user.inform.System message": "System message", "support.user.login.Email": "Email", - "support.user.login.Github": "GitHub", - "support.user.login.Google": "Google", - "support.user.login.Microsoft": "Microsoft", + "support.user.login.Github": "GitHub login", + "support.user.login.Google": "Google login", + "support.user.login.Microsoft": "Microsoft login", "support.user.login.Password": "Password", - "support.user.login.Password login": "Password", - "support.user.login.Phone": "Phone Login", - "support.user.login.Phone number": "Phone Number", - "support.user.login.Provider error": "Login Error, Please Try Again", + "support.user.login.Password login": "Account login", + "support.user.login.Phone": "Mobile number login", + "support.user.login.Phone number": "Mobile number", + "support.user.login.Provider error": "Login error occurred. Please try again later.", "support.user.login.Username": "Username", - "support.user.login.Wechat": "WeChat Login", - "support.user.login.can_not_login": "Cannot log in? Click here to contact us", - "support.user.login.error": "Login Error", - "support.user.login.security_failed": "Security Verification Failed", - "support.user.login.wx_qr_login": "WeChat QR Code Login", - "support.user.logout.confirm": "Confirm to Log Out?", - "support.user.team.Dataset usage": "Dataset Capacity", - "support.user.team.Team Tags Async Success": "Sync Completed", + "support.user.login.Wechat": "WeChat login", + "support.user.login.can_not_login": "Contact support", + "support.user.login.error": "Login error occurred.", + "support.user.login.security_failed": "Verification failed.", + "support.user.login.wx_qr_login": "Scan with WeChat", + "support.user.logout.confirm": "Are you sure you want to log out?", + "support.user.team.Dataset usage": "Knowledge base indexes", + "support.user.team.Team Tags Async Success": "Sync completed", "support.user.team.member": "Member", - "support.wallet.Ai point every thousand tokens": "{{points}} Points/1K Tokens", - "support.wallet.Ai point every thousand tokens_input": "Input:{{points}} points/1K tokens", - "support.wallet.Ai point every thousand tokens_output": "Output:{{points}} points/1K tokens", + "support.wallet.Ai point every thousand tokens": "{{points}} credits/1K tokens", + "support.wallet.Ai point every thousand tokens_input": "Input: {{points}} credits/1K tokens", + "support.wallet.Ai point every thousand tokens_output": "Output: {{points}} credits/1K tokens", "support.wallet.Amount": "Amount", - "support.wallet.App_amount_not_sufficient": "The number of your applications has reached the limit. Please upgrade your plan to continue using.", - "support.wallet.Buy": "Buy", - "support.wallet.Dataset_amount_not_sufficient": "The number of your datasets has reached the limit. Please upgrade your plan to continue using.", - "support.wallet.Dataset_not_sufficient": "Your dataset capacity is insufficient. Please upgrade your plan or purchase additional dataset capacity to continue using.", - "support.wallet.Not sufficient": "Insufficient AI Points, Please Upgrade Your Package or Purchase Additional AI Points to Continue Using.", - "support.wallet.Plan expired time": "Package Expiration Time", - "support.wallet.Standard Plan Detail": "Package Details", - "support.wallet.Team_member_over_size": "The number of your team members has reached the limit. Please upgrade your plan to continue using.", - "support.wallet.To read plan": "View Package", - "support.wallet.amount_0": "Purchase Quantity Cannot Be 0", - "support.wallet.apply_invoice": "Apply for Invoice", - "support.wallet.bill.Number": "Order Number", + "support.wallet.App_amount_not_sufficient": "The number of apps reaches the maximum. Please upgrade the plan.", + "support.wallet.Buy": "Purchase", + "support.wallet.Dataset_amount_not_sufficient": "The number of knowledge basesreaches the maximum. Please upgrade the plan.", + "support.wallet.Dataset_not_sufficient": "The knowledge base index capacity is insufficient. Please upgrade the plan or purchase more index capacity.", + "support.wallet.Not sufficient": "Your credits are insufficient. Please upgrade the plan or purchase more credits.", + "support.wallet.Plan expired time": "Expiration time", + "support.wallet.Standard Plan Detail": "Plan details", + "support.wallet.Team_member_over_size": "The number of team members reaches the maximum. Please upgrade the plan.", + "support.wallet.To read plan": "View plan", + "support.wallet.amount_0": "The value cannot be 0.", + "support.wallet.apply_invoice": "Request fapiao", + "support.wallet.bill.Number": "Order ID", "support.wallet.bill.Status": "Status", - "support.wallet.bill.Type": "Order Type", - "support.wallet.bill.payWay.Way": "Payment Method", - "support.wallet.bill.payWay.alipay": "Alipay Payment", - "support.wallet.bill.payWay.balance": "Balance Payment", - "support.wallet.bill.payWay.bank": "Bank Transfer", - "support.wallet.bill.payWay.wx": "WeChat Payment", + "support.wallet.bill.Type": "Order type", + "support.wallet.bill.payWay.Way": "Payment method", + "support.wallet.bill.payWay.alipay": "Alipay", + "support.wallet.bill.payWay.balance": "Balance", + "support.wallet.bill.payWay.bank": "Corporate payment", + "support.wallet.bill.payWay.wx": "WeChat Pay", "support.wallet.bill.status.closed": "Closed", "support.wallet.bill.status.notpay": "Unpaid", "support.wallet.bill.status.refund": "Refunded", - "support.wallet.bill.status.success": "Payment Successful", - "support.wallet.bill_detail": "Bill Details", - "support.wallet.bill_tag.bill": "Bill Records", - "support.wallet.bill_tag.default_header": "Default Header", - "support.wallet.bill_tag.invoice": "Invoice Records", - "support.wallet.billable_invoice": "Billable Invoice", - "support.wallet.buy_ai_points": "Buy AI points", - "support.wallet.buy_dataset_capacity": "Buy Knowledge Base Index", - "support.wallet.buy_resource": "Buy Resource Pack", - "support.wallet.has_invoice": "Invoiced", - "support.wallet.invoice_amount": "Invoice Amount", - "support.wallet.invoice_data.bank": "Bank", - "support.wallet.invoice_data.bank_account": "Bank Account", - "support.wallet.invoice_data.company_address": "Company Address", - "support.wallet.invoice_data.company_phone": "Company Phone", - "support.wallet.invoice_data.email": "Email Address", - "support.wallet.invoice_data.need_special_invoice": "Need Special Invoice", - "support.wallet.invoice_data.organization_name": "Organization Name", - "support.wallet.invoice_data.unit_code": "Unified Credit Code", - "support.wallet.invoice_detail": "Invoice Details", - "support.wallet.invoice_info": "The invoice will be sent to the email within 3-7 working days, please wait patiently", - "support.wallet.invoicing": "Invoicing", - "support.wallet.moduleName.qa": "QA Split", - "support.wallet.noBill": "No Bill Records", - "support.wallet.no_invoice": "No Invoice Records", - "support.wallet.subscription.AI points": "AI Points", - "support.wallet.subscription.AI points click to read tip": "Each time the AI model is called, a certain amount of AI points (similar to tokens) will be consumed. Click to view detailed calculation rules.", - "support.wallet.subscription.AI points usage": "AI Points Usage", - "support.wallet.subscription.AI points usage tip": "Each time the AI model is called, a certain amount of AI points will be consumed. For specific calculation standards, please refer to the 'Pricing' above.", - "support.wallet.subscription.Ai points": "AI Points Calculation Standards", - "support.wallet.subscription.Current plan": "Current Package", - "support.wallet.subscription.Dataset size": "Knowledge Base Index", - "support.wallet.subscription.Extra ai points": "AI Points", - "support.wallet.subscription.Extra ai points description": "The purchase amount of points is intelligently linked to the validity period. The more you buy, the longer you use it.", - "support.wallet.subscription.Extra dataset description": "Supports extending the validity period for the knowledge base index based on actual needs", - "support.wallet.subscription.Extra dataset size": "Extra Dataset Capacity", - "support.wallet.subscription.Extra dataset unit": " Groups/1 Month", - "support.wallet.subscription.Extra plan": "Extra Resource Pack", - "support.wallet.subscription.Extra plan tip": "When the standard package is not enough, you can purchase extra resource packs to continue using", - "support.wallet.subscription.FAQ": "FAQ", + "support.wallet.bill.status.success": "Payment successful.", + "support.wallet.bill_detail": "Bill details", + "support.wallet.bill_tag.bill": "Billing records", + "support.wallet.bill_tag.default_header": "Default title", + "support.wallet.bill_tag.invoice": "Fapiao issuing history", + "support.wallet.billable_invoice": "Bill available for fapiao issuing", + "support.wallet.buy_ai_points": "Purchase credits", + "support.wallet.buy_dataset_capacity": "Purchase knowledge base indexes", + "support.wallet.buy_resource": "Purchase resource packages", + "support.wallet.has_invoice": "Fapiao issued", + "support.wallet.invoice_amount": "Fapiao amount", + "support.wallet.invoice_data.bank": "Bank name", + "support.wallet.invoice_data.bank_account": "Account number", + "support.wallet.invoice_data.company_address": "Company address", + "support.wallet.invoice_data.company_phone": "Company phone", + "support.wallet.invoice_data.email": "Email address", + "support.wallet.invoice_data.need_special_invoice": "Special VAT fapiao", + "support.wallet.invoice_data.organization_name": "Organization", + "support.wallet.invoice_data.unit_code": "Unified social credit code", + "support.wallet.invoice_detail": "Fapiao details", + "support.wallet.invoice_info": "The fapiao will be emailed to the specified address within 3-7 business days.", + "support.wallet.invoicing": "Issue fapiao", + "support.wallet.moduleName.qa": "Q&A generation", + "support.wallet.noBill": "No data available.", + "support.wallet.no_invoice": "No data available.", + "support.wallet.subscription.AI points": "Credits", + "support.wallet.subscription.AI points click to read tip": "Model calls consume credits (similar to tokens). Click to view detailed billing rules.", + "support.wallet.subscription.AI points usage": "Credit usage", + "support.wallet.subscription.AI points usage tip": "Model calls consume credits. For details, refer to the billing standard above.", + "support.wallet.subscription.Ai points": "Credit billing standard", + "support.wallet.subscription.Current plan": "Current plan", + "support.wallet.subscription.Dataset size": "Knowledge base index capacity", + "support.wallet.subscription.Extra ai points": "Additional credits", + "support.wallet.subscription.Extra ai points description": "The more credits you purchase, the longer validity period you get.", + "support.wallet.subscription.Extra dataset description": "Extend the validity of knowledge base indexes as needed.", + "support.wallet.subscription.Extra dataset size": "Additional knowledge base index capacity", + "support.wallet.subscription.Extra dataset unit": "groups/month", + "support.wallet.subscription.Extra plan": "Additional resource packages", + "support.wallet.subscription.Extra plan tip": "Purchase additional resource packages to continue using the plan.", + "support.wallet.subscription.FAQ": "FAQs", "support.wallet.subscription.Month amount": "Months", - "support.wallet.subscription.Next plan": "Future Package", - "support.wallet.subscription.Points amount": "AI Points", - "support.wallet.subscription.Stand plan level": "Subscription Package", - "support.wallet.subscription.Sub plan": "Subscription Package", - "support.wallet.subscription.Sub plan tip": "Free to use [{{title}}] or upgrade to a higher package", - "support.wallet.subscription.Team plan and usage": "Package and Usage", - "support.wallet.subscription.Training weight": "Training Priority: {{weight}}", - "support.wallet.subscription.Update extra ai points": "Extra AI Points", - "support.wallet.subscription.Update extra ai points tips": "Purchase points will take effect immediately and will automatically expire after expiration.", - "support.wallet.subscription.Update extra dataset size": "Storage", - "support.wallet.subscription.Update extra dataset tips": "After the knowledge base index expires, the existing data is still retained and the data cannot be added or modified.", + "support.wallet.subscription.Next plan": "Upcoming plans", + "support.wallet.subscription.Points amount": "Credits", + "support.wallet.subscription.Stand plan level": "Subscription plan", + "support.wallet.subscription.Sub plan": "Subscription plan", + "support.wallet.subscription.Sub plan tip": "Use {{title}} for free or upgrade to a higher-level plan.", + "support.wallet.subscription.Team plan and usage": "Plans and usage", + "support.wallet.subscription.Training weight": "Training priority: {{weight}}", + "support.wallet.subscription.Update extra ai points": "Additional credits", + "support.wallet.subscription.Update extra ai points tips": "Purchased credits are added to an account instantly but can no longer be used after the expiration date.", + "support.wallet.subscription.Update extra dataset size": "Additional indexes", + "support.wallet.subscription.Update extra dataset tips": "When knowledge base indexes expire, existing data is retained, but changes or additions to the data are not allowed.", "support.wallet.subscription.Update extra price": "Price", - "support.wallet.subscription.Upgrade plan": "Upgrade Package", - "support.wallet.subscription.ai_model": "AI Language Model", - "support.wallet.subscription.function.History store": "{{amount}} Days of Chat History Retention", - "support.wallet.subscription.function.Max app": "{{amount}} App limit", - "support.wallet.subscription.function.Max dataset": "{{amount}} Dataset limit", - "support.wallet.subscription.function.Max dataset size": "{{amount}} Dataset Indexes", - "support.wallet.subscription.function.Max members": "{{amount}} Member limit", - "support.wallet.subscription.function.Points": "{{amount}} AI Points", - "support.wallet.subscription.mode.Month": "Month", - "support.wallet.subscription.mode.Period": "Subscription Period", - "support.wallet.subscription.mode.Year": "Year", - "support.wallet.subscription.mode.Year sale": "Two Months Free", - "support.wallet.subscription.point": " Points", - "support.wallet.subscription.standardSubLevel.custom": "Custom", - "support.wallet.subscription.standardSubLevel.enterprise": "Enterprise", - "support.wallet.subscription.standardSubLevel.enterprise_desc": "Suitable for small and medium-sized enterprises to build Dataset applications in production environments", - "support.wallet.subscription.standardSubLevel.experience": "Experience", - "support.wallet.subscription.standardSubLevel.experience_desc": "Unlock the full functionality of FastGPT", - "support.wallet.subscription.standardSubLevel.free": "Free", - "support.wallet.subscription.standardSubLevel.free desc": "Free trial of core features. \nIf you haven't logged in for 30 days, the knowledge base will be cleared.", - "support.wallet.subscription.standardSubLevel.team": "Team", - "support.wallet.subscription.standardSubLevel.team_desc": "Suitable for small teams to build Dataset applications and provide external services", + "support.wallet.subscription.Upgrade plan": "Upgrade plan", + "support.wallet.subscription.ai_model": "Language model", + "support.wallet.subscription.function.History store": "{{amount}} days chat history", + "support.wallet.subscription.function.Max app": "{{amount}} apps", + "support.wallet.subscription.function.Max dataset": "{{amount}} knowledge bases", + "support.wallet.subscription.function.Max dataset size": "{{amount}} knowledge base index groups", + "support.wallet.subscription.function.Max members": "{{amount}} team members", + "support.wallet.subscription.function.Points": "{{amount}} credits", + "support.wallet.subscription.mode.Month": "Monthly", + "support.wallet.subscription.mode.Period": "Periodic", + "support.wallet.subscription.mode.Year": "Yearly", + "support.wallet.subscription.mode.Year sale": "2 months free", + "support.wallet.subscription.point": "credits", + "support.wallet.subscription.standardSubLevel.custom": "Custom edition", + "support.wallet.subscription.standardSubLevel.enterprise": "Enterprise edition", + "support.wallet.subscription.standardSubLevel.enterprise_desc": "For medium-sized teams", + "support.wallet.subscription.standardSubLevel.experience": "Trial edition", + "support.wallet.subscription.standardSubLevel.experience_desc": "Unlimited access to all features", + "support.wallet.subscription.standardSubLevel.free": "Free edition", + "support.wallet.subscription.standardSubLevel.free desc": "Free trial of core features Knowledge bases will be cleared after 30 days of inactivity.", + "support.wallet.subscription.standardSubLevel.team": "Team edition", + "support.wallet.subscription.standardSubLevel.team_desc": "For small teams", "support.wallet.subscription.status.active": "Active", "support.wallet.subscription.status.expired": "Expired", - "support.wallet.subscription.status.inactive": "Inactive", - "support.wallet.subscription.team_operation_log": "Record team operation logs", - "support.wallet.subscription.token_compute": "Click to View Online Tokens Calculator", - "support.wallet.subscription.type.balance": "Balance Recharge", - "support.wallet.subscription.type.extraDatasetSize": "Dataset Expansion", - "support.wallet.subscription.type.extraPoints": "AI Points Package", - "support.wallet.subscription.type.standard": "Package Subscription", - "support.wallet.subscription.web_site_sync": "Website Sync", - "support.wallet.usage.Ai model": "AI Model", - "support.wallet.usage.App name": "App Name", - "support.wallet.usage.Audio Speech": "Voice Playback", - "support.wallet.usage.Bill Module": "Billing Module", - "support.wallet.usage.Duration": "Duration (seconds)", - "support.wallet.usage.Module name": "Module Name", + "support.wallet.subscription.status.inactive": "Pending", + "support.wallet.subscription.team_operation_log": "Log member operations", + "support.wallet.subscription.token_compute": "Click to view online token calculator", + "support.wallet.subscription.type.balance": "Balance top-up", + "support.wallet.subscription.type.extraDatasetSize": "Knowledge base expansion", + "support.wallet.subscription.type.extraPoints": "Credit plan", + "support.wallet.subscription.type.standard": "Plan subscription", + "support.wallet.subscription.web_site_sync": "Website sync", + "support.wallet.usage.Ai model": "Model", + "support.wallet.usage.App name": "App name", + "support.wallet.usage.Audio Speech": "Text-to-speech", + "support.wallet.usage.Bill Module": "Billing module", + "support.wallet.usage.Duration": "Duration (s)", + "support.wallet.usage.Module name": "Module name", "support.wallet.usage.Optimize Prompt": "Prompt word optimization", "support.wallet.usage.Source": "Source", - "support.wallet.usage.Text Length": "Text Length", - "support.wallet.usage.Time": "Generation Time", - "support.wallet.usage.Token Length": "Token Length", - "support.wallet.usage.Total": "Total Amount", - "support.wallet.usage.Total points": "AI Points Consumption", - "support.wallet.usage.Usage Detail": "Usage Details", - "support.wallet.usage.Whisper": "Voice Input", - "sync_link": "Sync Link", - "sync_success": "Synced Successfully", + "support.wallet.usage.Text Length": "Text length", + "support.wallet.usage.Time": "Time generated", + "support.wallet.usage.Token Length": "Token length", + "support.wallet.usage.Total": "Total amount", + "support.wallet.usage.Total points": "Credits consumed", + "support.wallet.usage.Usage Detail": "Usage details", + "support.wallet.usage.Whisper": "Speech-to-text", + "sync_link": "Sync link", + "sync_success": "Synced successfully.", "system.Concat us": "Contact Us", - "system.Help Document": "Help Document", - "system_help_chatbot": "Help Chatbot", - "system_intro": "{{title}} is a comprehensive model application orchestration system that offers out-of-the-box data processing and model invocation capabilities. It allows for rapid Dataset construction and workflow orchestration through Flow visualization, enabling complex Dataset scenarios!", - "tag_list": "Tag List", - "team_tag": "Team Tag", + "system.Help Document": "Help", + "system_help_chatbot": "Bot assistant", + "system_intro": "{{title}} is a large model orchestration system with built-in data processing and model calling features. It allows you to quickly build knowledge bases and design workflows for complex scenarios through flow visualization.", + "tag_list": "Tags", + "team_tag": "Team tag", "templateTags.Image_generation": "Image generation", - "templateTags.Office_services": "Office Services", - "templateTags.Roleplay": "role play", - "templateTags.Web_search": "Search online", - "templateTags.Writing": "Writing", - "template_market": "Template Market", - "textarea_variable_picker_tip": "Enter \"/\" to select a variable", - "to_dataset": "To dataset", + "templateTags.Office_services": "Office service", + "templateTags.Roleplay": "Role playing", + "templateTags.Web_search": "Search", + "templateTags.Writing": "Content creation", + "template_market": "Templates", + "textarea_variable_picker_tip": "Enter a slash (/) to select a variable.", + "to_dataset": "Go to knowledge base", "total_num": "Total: {{num}}", - "ui.textarea.Magnifying": "Magnifying", + "ui.textarea.Magnifying": "Zoom in", "un_used": "Unused", - "unauth_token": "The certificate has expired, please log in again", - "undo_tip": "Undo ctrl z", - "undo_tip_mac": "Undo ⌘ z ", - "unit.character": "Character", + "unauth_token": "Your credential has expired. Please log in again.", + "undo_tip": "Undo (Ctrl + Z)", + "undo_tip_mac": "Undo (⌘ + Z)", + "unit.character": "characters", "unit.minute": "Minute", - "unit.seconds": "Second", - "unknow_source": "Unknown Source", - "unusable_variable": "No Usable Variables", - "update_failed": "Update Failed", - "update_success": "Updated Successfully", - "upgrade": "upgrade", - "upload_file": "Upload File", - "upload_file_error": "File Upload Failed", - "use_helper": "Use Helper", + "unit.seconds": "s", + "unknow_source": "Unknown source", + "unusable_variable": "No variables available.", + "update_failed": "Error occurred during the update.", + "update_success": "Update successful.", + "upgrade": "Upgrade", + "upload_file": "Upload file", + "upload_file_error": "Upload failed.", + "use_helper": "Help", "user.Account": "Account", "user.Amount of earnings": "Earnings (¥)", - "user.Amount of inviter": "Total Number of Invites", - "user.Application Name": "Project Name", - "user.Avatar": "Avatar", + "user.Amount of inviter": "Total invites", + "user.Application Name": "Project name", + "user.Avatar": "Profile image", "user.Change": "Change", - "user.Copy invite url": "Copy Invite Link", - "user.Edit name": "Click to Edit Nickname", - "user.Invite Url": "Invite Link", - "user.Invite url tip": "Friends registered through this link will be permanently bound to you, and you will receive a balance reward when they recharge.\nAdditionally, you will immediately receive a 5 yuan reward when friends register with their phone number.\nThe reward will be sent to your default team.", - "user.Laf Account Setting": "Laf Account Configuration", + "user.Copy invite url": "Copy invitation link", + "user.Edit name": "Click to change the name.", + "user.Invite Url": "Invitation link", + "user.Invite url tip": "Friends who register through this link will be permanently linked to your account. Each time they top up, you will receive a balance reward.\nIf they register with a mobile number, you will get an immediate 5 CNY bonus.\nAll rewards are credited to your default team account.", + "user.Laf Account Setting": "LAF account", "user.Language": "Language", "user.Member Name": "Nickname", - "user.No_right_to_reset_password": "You do not have the right to reset the password", - "user.Notification Receive": "Notification Receive", - "user.Notification Receive Bind": "Please bind the notification receive method first", - "user.Old password is error": "Old Password is Incorrect", - "user.OpenAI Account Setting": "OpenAI Account Configuration", + "user.No_right_to_reset_password": "You do not have permission to reset the password.", + "user.Notification Receive": "Notification recipient", + "user.Notification Receive Bind": "Please specify a notification recipient first.", + "user.Old password is error": "The current password is incorrect", + "user.OpenAI Account Setting": "OpenAI account", "user.Password": "Password", - "user.Password has no change": "New password is the same as the old password", - "user.Pay": "Recharge", + "user.Password has no change": "The new and current passwords cannot be the same.", + "user.Pay": "Top up", "user.Promotion": "Promotion", - "user.Promotion Rate": "Cashback Rate", - "user.Promotion rate tip": "You will receive a balance reward when friends recharge", + "user.Promotion Rate": "Cashback rate", + "user.Promotion rate tip": "You will receive a balance reward based on your friend's top-up amount.", "user.Replace": "Replace", - "user.Set OpenAI Account Failed": "Failed to Set OpenAI Account", + "user.Set OpenAI Account Failed": "Error occurred while configuring the OpenAI account.", "user.Team": "Team", "user.Time": "Time", - "user.Timezone": "Timezone", - "user.Update Password": "Update Password", - "user.Update password failed": "Failed to Update Password", - "user.Update password successful": "Password Updated Successfully", - "user.apikey.key": "API Key", - "user.confirm_password": "Confirm Password", - "user.init_password": "Please initialize password", - "user.new_password": "New Password", - "user.no_invite_records": "No Invite Records", - "user.no_notice": "No Notices", - "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 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", + "user.Timezone": "Time zone", + "user.Update Password": "Change password", + "user.Update password failed": "Error occurred while changing the password.", + "user.Update password successful": "Password changed successfully.", + "user.apikey.key": "API key", + "user.confirm_password": "Confirm password", + "user.init_password": "Please initialize the password.", + "user.new_password": "New password", + "user.no_invite_records": "No data available.", + "user.no_notice": "No data available.", + "user.no_usage_records": "No data available.", + "user.old_password": "Current password", + "user.password_message": "Password must contain 4-60 characters.", + "user.password_tip": "Must be at least 8 characters long and contain at least 2 of the following: digits, letters, and special characters.", + "user.reset_password": "Reset password", + "user.reset_password_tip": "Initial password not set or password unchanged for a long time. Please reset your password.", + "user.team.Balance": "Team balance", "user.team.Check Team": "Switch", - "user.team.Leave Team": "Leave Team", - "user.team.Leave Team Failed": "Failed to Leave Team", + "user.team.Leave Team": "Leave team", + "user.team.Leave Team Failed": "Error occurred while leaving the team.", "user.team.Member": "Member", - "user.team.Member Name": "Member Name", - "user.team.Over Max Member Tip": "The team can have up to {{max}} people", - "user.team.Personal Team": "Personal Team", - "user.team.Processing invitations": "Processing Invitations", - "user.team.Processing invitations Tips": "You have {{amount}} team invitations to process", - "user.team.Remove Member Confirm Tip": "Confirm to remove {{username}} from the team?", - "user.team.Select Team": "Select Team", - "user.team.Switch Team Failed": "Failed to Switch Team", + "user.team.Member Name": "Member name", + "user.team.Over Max Member Tip": "Max team members: {{max}}", + "user.team.Personal Team": "Private team", + "user.team.Processing invitations": "Manage invitations", + "user.team.Processing invitations Tips": "You have {{amount}} pending team invitations.", + "user.team.Remove Member Confirm Tip": "Are you sure you want to remove the member ({{username}}) from the team?", + "user.team.Select Team": "Select team", + "user.team.Switch Team Failed": "Error occurred while switching team.", "user.team.Tags Async": "Save", - "user.team.Team Tags Async": "Tag Sync", + "user.team.Team Tags Async": "Sync tag", "user.team.Team Tags Async Success": "Link Error Successful, Tag Information Updated", - "user.team.invite.Accepted": "Joined Team", + "user.team.invite.Accepted": "Joined", "user.team.invite.Deal Width Footer Tip": "It will automatically close after processing", - "user.team.invite.Reject": "Invitation Rejected", - "user.team.member.Confirm Leave": "Confirm to leave this team?", + "user.team.invite.Reject": "The invitation was rejected.", + "user.team.member.Confirm Leave": "Are you sure you want to leave the team?\nAll your resources in this team (apps, knowledge bases, folders, and managed groups) will be transferred to the team owner.", "user.team.member.active": "Joined", - "user.team.member.reject": "Rejected", - "user.team.member.waiting": "Pending Acceptance", + "user.team.member.reject": "Reject", + "user.team.member.waiting": "Pending", "user.team.role.Admin": "Admin", - "user.team.role.Owner": "Owner", - "user.team.role.Visitor": "visitor", - "user.team.role.writer": "writable member", + "user.team.role.Owner": "Creator", + "user.team.role.Visitor": "Guest", + "user.team.role.writer": "Members with write permissions", "user.type": "Type", - "user_leaved": "Leaved", + "user_leaved": "Left", "value": "Value", - "verification": "Verification", - "xx_search_result": "{{key}} Search Results", + "verification": "Verify", + "xx_search_result": "Search results for {{key}}", "yes": "Yes", - "yesterday": "yesterday", - "yesterday_detail_time": "Yesterday {{time}}", - "zoomin_tip": "Zoom Out ctrl -", - "zoomin_tip_mac": "Zoom Out ⌘ -", - "zoomout_tip": "Zoom In ctrl +", - "zoomout_tip_mac": "Zoom In ⌘ +" -} + "yesterday": "Yesterday", + "yesterday_detail_time": "{{time}} yesterday", + "zoomin_tip": "Zoom out (Ctrl -)", + "zoomin_tip_mac": "Zoom out (⌘ -)", + "zoomout_tip": "Zoom in (Ctrl +)", + "zoomout_tip_mac": "Zoom in (⌘ +)", + "no_database_connection": "No database connected.", + "click_config_database": "Click to configure database.", + "annotation_answer": "Marked answers", + "core.dataset.search.Database search": "Database search", + "search_model": "Retrieval model", + "search_model_desc": "Generates SQL statements for database search, performs search and aggregation, and produces chat text.", + "search_model_tip": "Larger non-reasoning models generally generate better results.", + "other_knowledge_base": "Other knowledge bases", + "database_search": "Database search", + "table_not_exist": "Not exist", + "core.app.workflow.search_knowledge.database": "Database", + "core.chat.logs.evaluation": "Evaluation test", + "support.wallet.subscription.eval_items_count": "Cases for an evaluation task: {{count}}", + "core.dataset.table": "Table", + "core.dataset.search.mode.database": "Database search", + "core.dataset.search.mode.database desc": "Uses vector search to retrieve related tables and columns in databases.", + "core.dataset.training.databaseSchema mode": "Database structure", + "core.dataset.import.databaseSchema Tip": "Automatically processes data in database tables to enhance searching, improving the accuracy of SQL statements generated.", + "semicolon": "; ", + "database_sql_query": "SQL statement", + "search_result": "Search result", + "comma": ", ", + "set_avatar_in_edit_modal": "Click to upload an avatar." +} \ No newline at end of file diff --git a/packages/web/i18n/en/dashboard_evaluation.json b/packages/web/i18n/en/dashboard_evaluation.json index 1809bcb4df82..2f7015daad1b 100644 --- a/packages/web/i18n/en/dashboard_evaluation.json +++ b/packages/web/i18n/en/dashboard_evaluation.json @@ -1,50 +1,380 @@ { - "Action": "operate", - "Evaluation_app": "Evaluation app", - "Evaluation_app_tip": "Supports simple application and does not contain workflows with user interaction nodes. \nPlugins are not supported yet.", - "Evaluation_file": "Evaluation documents", - "Evaluation_model": "Evaluation model", - "Executor": "Executor", - "Overall_score": "Overall Score", + "Action": "Operation", + "Evaluation_app": "App", + "Evaluation_app_tip": "Supports a simple app or a workflow without user interaction nodes. Plugins are not supported.", + "Evaluation_file": "File", + "Evaluation_model": "Model", + "Executor": "Operator", + "Overall_score": "Score", "Progress": "Progress", - "Start_end_time": "Start time / End time", - "Task_name_placeholder": "Please enter a task name", - "answer": "Answers", - "app_required": "Please select the evaluation application", - "app_response": "Application output", - "back": "back", - "check_error": "Error", - "check_error_tip": "Check, please:\n\n1. Is it consistent with the header data of the .csv template\n\n2. Whether required items are missing\n\n3. Global variables - whether the word limit or number range limit is exceeded\n\n4. Global variable-number box, whether the character is number\n\n5. Global variable - radio box, whether the characters are exactly the same as the options", - "check_format": "Format Check", - "comfirm_delete_item": "Confirm to delete this piece of data?", - "comfirm_delete_task": "Confirm deleting the task and all its data?", + "Start_end_time": "Start/End Time", + "Task_name_placeholder": "", + "answer": "Standard answer", + "app_required": "Please select an app first.", + "app_response": "App output", + "back": "Exit", + "check_error": "Verification failed.", + "check_error_tip": "Please check the following:\n1. Headers in the file must be consistent with those in the CSV template.\n2. All required fields are configured.\n3. Global variables are valid.\n4. Numeric input boxes contain valid numbers.\n5. Radio button options are valid.", + "check_format": "Format verification", + "comfirm_delete_item": "Are you sure you want to delete this entry?", + "comfirm_delete_task": "Are you sure you want to delete this task and all related data?", "completed": "Completed", - "create_task": "Create a task", - "data": "data", - "data_list": "Data list", - "detail": "Detail", - "error": "abnormal", - "error_tooltip": "There are exception tasks. \n\nAfter clicking, open the task details pop-up window", - "eval_file_check_error": "Evaluation file validation failed", - "evaluating": "In evaluation", - "evaluation": "Application Review", - "evaluation_export_title": "Question,Standard Answer,Actual Response,Status,Average Score", - "evaluation_file_max_size": "{{count}} data", - "export": "Export Data", - "file_required": "Please select the evaluation file", - "file_uploading": "File uploading: {{num}}%", + "create_task": "Create task", + "data": "Data", + "data_list": "Details", + "detail": "Details", + "error": "Error", + "error_tooltip": "A task encountered error.\nClick to view task details.", + "eval_file_check_error": "Evaluation file verification failed.", + "evaluating": "Evaluating", + "evaluation": "App evaluation", + "evaluation_export_title": "Question,Standard answer,Actual answer,Status,Score", + "evaluation_file_max_size": "Entries: {{count}}", + "export": "Export", + "file_required": "Select", + "file_uploading": "Uploading file: {{num}}%", "history": "History", "paused": "Paused", - "question": "Questions", - "queuing": "Queue", - "search_task": "Find tasks", + "question": "Question", + "queuing": "Waiting", + "search_task": "Task", "standard_response": "Standard output", - "start_evaluation": "Start the evaluation", + "start_evaluation": "Evaluate", "stauts": "Status", "task_creating": "Creating task", - "task_detail": "Task Details", - "task_name": "Task Name", - "team_has_running_evaluation": "The current team already has running application reviews. Please wait until it is completed before creating a new application review.", - "template_csv_file_select_tip": "Only support {{fileType}} files that are strictly in accordance with template format", - "variables": "Global variables" + "task_detail": "Task details", + "task_name": "Task name", + "team_has_running_evaluation": "An app evaluation task is in progress. Please wait until it is complete.", + "template_csv_file_select_tip": "Must be a {{fileType}} file that strictly follows the template.", + "variables": "Global variables", + "all_apps": "All apps", + "search_evaluation_task": "Task name, app version", + "create_new_task": "Create task", + "task_name_column": "Task name", + "progress_column": "Progress", + "evaluation_app_column": "App", + "app_version_column": "App version", + "evaluation_result_column": "Evaluation result", + "start_finish_time_column": "Time started/completed", + "executor_column": "Operator", + "waiting": "Waiting", + "evaluating_status": "Evaluating", + "completed_status": "Completed", + "queuing_status": "Waiting", + "running_status": "Ongoing", + "error_data_tooltip": "{{count}} entries encountered error during execution. Click to view details.", + "rename": "Rename", + "delete": "Delete", + "confirm_delete_task": "Are you sure you want to delete this task?", + "evaluation_tasks_tab": "Tasks", + "evaluation_tasks": "Tasks", + "evaluation_datasets_tab": "Datasets", + "evaluation_dimensions_tab": "Metrics", + "create_new": "Create", + "retry_error_data": "Retry failed entries", + "dataset_name_placeholder": "Name", + "create_new_dataset": "Create", + "smart_generation": "Auto generate", + "file_import": "Import from file", + "confirm_delete_dataset": "Are you sure you want to delete this dataset?", + "error_details": "Details", + "status_queuing": "Waiting", + "status_parsing": "Parsing file", + "status_generating": "Generating data", + "status_generate_error": "Generation error", + "status_ready": "Ready", + "status_parse_error": "Parsing error", + "click_to_view_details": "View details", + "table_header_name": "Name", + "table_header_data_count": "Data size", + "table_header_time": "Time created/updated", + "table_header_status": "Status", + "table_header_creator": "Creator", + "create_dimension": "Create", + "search_dimension": "Metric", + "delete_failed": "Failed", + "delete_success": "Deleted successfully.", + "builtin": "Predefined", + "confirm_delete_dimension": "Are you sure that you want to delete this metric?", + "dimension_name": "Name", + "description": "Description", + "create_update_time": "Time created/updated", + "creator": "Creator", + "all": "All", + "app": "App", + "citation_template": "Template", + "correctness": "Correctness", + "conciseness": "Conciseness", + "harmfulness": "Harmfulness", + "controversiality": "Controversiality", + "creativity": "Creativity", + "criminality": "Criminality", + "depth": "Depth", + "details": "Detail", + "dimension_name_label": "Name", + "dimension_description_label": "Description", + "prompt_label": "Prompt", + "citation_template_button": "Template", + "test_run_title": "Test run", + "question_label": "Question", + "question_placeholder": "", + "answer_label": "Answer", + "reference_answer_label": "Expected answer", + "reference_answer_placeholder": "", + "actual_answer_label": "Actual answer", + "actual_answer_placeholder": "", + "run_result_label": "Running result", + "start_run_button": "Run", + "running_text": "Running", + "run_success": "Run successful", + "run_failed": "Run failed", + "not_run": "Not run", + "score_unit": "(Score)", + "error_info_label": "Error: ", + "no_feedback_text": "No feedback available.", + "dimension_create_back": "Exit", + "dimension_create_test_run": "Test run", + "dimension_create_confirm": "OK", + "dimension_create_success": "Created successfully.", + "dimension_create_name_required": "Name is required.", + "dimension_create_prompt_required": "Prompt is required.", + "dimension_get_data_failed": "Failed to obtain the metric data.", + "dimension_data_not_exist": "The metric data does not exist.", + "dimension_update_success": "Update successful.", + "dimension_update_failed": "Update failed.", + "dimension_name_required": "Name is required.", + "dimension_back": "Exit", + "dimension_test_run": "Test run", + "dimension_save": "Save", + "file_import_back": "Exit", + "file_import_name_label": "Name", + "file_import_name_placeholder": "", + "file_import_select_file": "Please select a file.", + "file_import_success": "File imported successfully.", + "file_import_file_label": "File", + "file_import_download_template": "Download sample", + "file_import_download_template_tip": "One entry per line. First column: Question, Second column: Answer. The first row is the table header by default and will not be included in the entries. Example:", + "file_import_auto_evaluation_label": "Automatically evaluate the quality of the imported data.", + "file_import_auto_evaluation_tip": "If enabled, a quality evaluation will be automatically performed on the imported data.", + "file_import_evaluation_model_label": "Evaluation model", + "file_import_evaluation_model_placeholder": "Select", + "file_import_confirm": "OK", + "manage_dimension": "Settings", + "selected_count": "Selected", + "dimension_config_tip": "Dimension configuration instructions", + "custom": "Custom", + "select_model_placeholder": "Select", + "create_new_task_modal": "Create task", + "task_name_input": "Name", + "evaluation_app_select": "App", + "evaluation_app_support_tip": "You can select a simple app or workflow but not a plugin.", + "evaluation_app_select_placeholder": "Select", + "evaluation_app_version_select": "App version", + "evaluation_app_version_select_placeholder": "Select", + "evaluation_dataset_select": "Dataset", + "evaluation_dataset_select_placeholder": "Select", + "create_import_dataset": "Create/Import", + "evaluation_dimensions_label": "Metrics", + "evaluation_dimensions_recommendation": "The evaluation involves knowledge base queries and AI chats. It is recommended to use {{num}} metrics for evaluation.", + "builtin_dimension": "Predefined", + "custom_dimension": "Custom", + "config_params": "Settings", + "score_aggregation_method": "Score aggregation", + "evaluation_dimensions": "Metrics", + "dimension": "Metric", + "judgment_threshold": "Threshold", + "judgment_threshold_tip": "Determines whether a metric's score meets expectations. If it is lower than the specified threshold, it will be marked as Below expectation. Valid range: 1-100. After the evaluation is complete, you can adjust it as needed.", + "comprehensive_score_weight": "Weight", + "comprehensive_score_weight_tip": "The ration of a metric's score to the total score. You can configure it as needed. After the evaluation is complete, you can adjust it as needed.", + "comprehensive_score_weight_sum": "Total weight:", + "intelligent_generation_dataset": "Auto generate dataset", + "dataset_name_input": "Name", + "dataset_name_input_placeholder": "Dataset name", + "generation_basis": "Data source", + "select_knowledge_base": "Select knowledge base", + "data_amount": "Data size", + "generation_model": "Model", + "generation_model_placeholder": "Select", + "high_quality": "High quality", + "needs_improvement": "Pending optimization", + "detail_evaluating": "Evaluating", + "abnormal": "Evaluation error", + "not_evaluated": "Not evaluated", + "modify_result": "Modify", + "restart_evaluation": "Evaluate again", + "evaluation_error_message": "Error occurred during the evaluation. Please try again.", + "high_quality_feedback": "The question is complete and clear, meeting the standard. The answer is accurate and practical.", + "needs_improvement_feedback": "The question needs to be optimized. To obtain a more accurate answer, please provide more context to make the question more specific and clear.", + "evaluation_service_error": "Evaluation encountered error.", + "edit_data": "Edit data", + "enter_question": "", + "question_required": "Question is required.", + "reference_answer": "Expected answer", + "enter_reference_answer": "", + "reference_answer_required": "Expected answer is required.", + "quality_evaluation": "Auto quality evaluation", + "quality_evaluation_btn_text": "Start evaluation", + "cancel": "Cancel", + "save": "Save", + "save_and_next": "Save & proceed", + "manually_calibrated": "Results modified.", + "modify_evaluation_result_title": "Modify result", + "evaluation_result_label": "Evaluation result", + "modify_reason_label": "Reason", + "modify_reason_input_placeholder": "Reason", + "no_data": "No data available.", + "search": "Search", + "settings": "Settings", + "add_data": "Add data", + "ai_generate": "Auto generate", + "manual_add": "Manually add", + "no_answer": "No answers available.", + "confirm_delete_data": "Are you sure you want to delete this entry?", + "no_evaluation_result_click": "No evaluation results available. ", + "start_evaluation_action": "Start Evaluation", + "evaluation_dataset": "Datasets", + "evaluation_status": "Status", + "manually_add_data_modal": "Add evaluation data", + "question_input_label": "Question", + "reference_answer_input_label": "Expected answer", + "auto_quality_eval_after_add": "Quality evaluation", + "auto_quality_eval_add_tip": "If enabled, Q&A relevance and correctness are automatically evaluated, providing evaluation results and optimization support.", + "quality_eval_model_label": "Evaluation model", + "select_quality_eval_model_placeholder": "Select", + "confirm": "OK", + "confirm_quality_evaluation": "Are you sure you want to evaluate the quality of all data ({{total}})?", + "model_change_notice": "The new model will apply to subsequent evaluation tasks.", + "evaluation_model": "Model", + "select_evaluation_model": "Select", + "evaluation_abnormal": "Evaluation error", + "error_message": "Error", + "file_parse_error": "File parsing error", + "delete_file": "Delete file", + "reparse": "Reparse", + "data_generation_error": " entries encountered error during generation.", + "source_knowledge_base": "Based on knowledge base", + "source_chunk": "Based on chunk", + "operations": "Operation", + "retry": "Retry", + "retry_all": "Retry all", + "error_info": "Error", + "manual_add_data": "Add evaluation data", + "max_3000_chars": "Up to 3,000 characters allowed", + "please_enter_question": "", + "question_max_3000_chars": "Cannot exceed 3,000 characters.", + "please_enter_reference_answer": "", + "reference_answer_max_3000_chars": "Cannot exceed 3,000 characters.", + "auto_quality_evaluation": "Automatically evaluate quality of the added data.", + "quality_evaluation_tip": "Automatically evaluate Q&A relevance and correctness, providing evaluation results and optimization support.", + "quality_evaluation_model": "Evaluation model", + "please_select_evaluation_model": "Select", + "summary_pending": "Pending generation", + "summary_generating": "Generating", + "summary_done": "Completed", + "summary_failed": "Failed", + "method_mean": "Average", + "method_median": "Median", + "prompt_cannot_be_empty": "Prompt is required.", + "please_select_model": "Please select a model.", + "run_failed_please_retry": "Test run failed. Please try again.", + "intelligent_generate_data": "Auto generate data", + "evaluation_completed": "Evaluation completed.", + "generation_error": "Generation error", + "data_generating": "Generating data", + "delete_dataset_error": "Error occurred while deleting the dataset.", + "create_failed": "Creation failed.", + "no_dimension_data": "No metric data available.", + "please_select_dimension_first": "Please select a metric first.", + "model_evaluation_tip": "The language model determines whether the actual and expected answers match.\nThe index model converts the actual and expected answers into vectors to further evaluate semantic similarity.", + "create_new_dimension": "Add metric", + "retry_success": "Retry successfully", + "data_generation_error_count": "{{count}} pieces of data generated exception", + "builtin_answer_correctness_name": "Answer accuracy", + "builtin_answer_correctness_desc": "Evaluates the factual consistency between the generated answer and the expected answer, evaluating whether it is accurate and error-free.", + "builtin_answer_similarity_name": "Answer similarity", + "builtin_answer_similarity_desc": "Evaluates the semantic alignment between the generated answer and the expected answer, determining whether they convey the same core information.", + "builtin_answer_relevancy_name": "Answer relevance", + "builtin_answer_relevancy_desc": "Evaluates how well the generated answer aligns with the question, judging whether the response directly addresses the query.", + "builtin_faithfulness_name": "Faithfulness", + "builtin_faithfulness_desc": "Evaluates whether the generated answer remains faithful to the provided context, determining whether it contains fabricated or inaccurate content.", + "builtin_context_recall_name": "Context recall", + "builtin_context_recall_desc": "Evaluates whether the retrieval system successfully retrieves all key information necessary for formulating the answer, assessing the completeness of retrieval.", + "builtin_context_precision_name": "Context precision", + "builtin_context_precision_desc": "Evaluates whether high-value information is prioritized in the retrieved content, reflecting the quality of ranking and information density.", + "join_evaluation_dataset": "Add to dataset", + "not_join_evaluation_dataset": "Do not add to dataset", + "create_new_dataset_btn_text": "Add to dataset", + "please_select_evaluation_dataset": "Select", + "join_knowledge_base": "Add to knowledge base", + "all_data_with_count": "All data ({{num}})", + "question_data_with_count": "Problem data({{num}})", + "error_data_with_count": "Error data({{num}})", + "export_data": "Export", + "retry_action": "Retry", + "basic_info": "Basics", + "application": "App", + "version": "Version", + "evaluation_dataset_name": "Dataset", + "start_time": "Start time", + "end_time": "End time", + "executor_name": "Operator", + "app_with_search_and_chat_recommendation": "The evaluation involves knowledge base search and AI chats. It is recommended to use 3 metrics for evaluation.", + "app_with_chat_recommendation": "The evaluation involves AI chats. It is recommended to use 1 metric for evaluation.", + "app_with_search_recommendation": "The evaluation involves knowledge base search. It is recommended to use 2 metrics for evaluation.", + "no_dimensions_added": "No metrics available. ", + "click_to_add": "Add", + "meets_expectation": " meets expectation.", + "below_expectation": " is below expectation.", + "summary_generation_error": "Error occurred while generating a summary. Retry", + "error_message_prefix": "Error: ", + "summary_pending_generation": "Pending", + "summary_generating_content": "Generating", + "data_with_count": "Cases ({{data}})", + "search_placeholder": "Search", + "detail_title": "Details", + "modify_dataset_simultaneously": "Sync changes to dataset", + "retry_button": "Retry", + "edit_action": "Edit", + "delete_action": "Delete", + "confirm_delete_data_in_task": "Are you sure you want to delete the entry?", + "view_full_response": "Complete response", + "abnormal_status": "Error", + "question_field": "Question", + "reference_answer_field": "Expected answer", + "actual_answer_field": "Actual answer", + "no_answer_available": "No answers available.", + "comprehensive_score_title": "Rating", + "dimension_score_title": "Metric score", + "error_data_calculation_notice": "{{count}} cases encountered error during execution. Only successful cases are used to calculate the score.", + "regenerate_summary_content": "Generate summary again", + "processing_status": "Processing...", + "view_results_after_completion": "Please wait until the evaluation is complete.", + "all_data_execution_error": "All entries encountered error during execution.", + "check_error_details": "Please view the cause.", + "click_to_retry": "Retry", + "comprehensive_score_weight_description": "The ratio of a metric's score to the total score. You can configure it as needed.", + "no_data_available": "No data available.", + "case_id_column": "Case ID", + "question_column": "Question", + "comprehensive_score_column": "Rating", + "error_info_column": "Error", + "request_failed": "Request failed.", + "retry_request_submitted": "Request sent successfully.", + "retry_failed": "Retry failed.", + "save_success": "Saved successfully.", + "save_failed": "Failed", + "summary_generation_request_submitted": "Request sent successfully.", + "generate_summary_failed": "Failed to generate a summary.", + "export_failed": "Export failed.", + "load_failed": "Loading failed.", + "export_success": "Export successful.", + "no_dimension_data_cannot_generate_summary": "Unable to generate a summary because no metric data is available.", + "Task_name": "Task name", + "click_to_download_template": "Download CSV template", + "evaluation_created": "Evaluation task created successfully.", + "evaluation_dimension_missing_model_config": "Metric configuration incomplete", + "evaluation_dimension_missing_model_config_desc": "The following metrics are missing model configuration. Please complete the configuration: {{names}}", + "example_highest_mountain_question": "What is the highest mountain in the world", + "example_highest_mountain_answer": "Mount Everest", + "data_updated_before_retest": "Data has been updated and will be automatically saved before evaluation again." } diff --git a/packages/web/i18n/en/dashboard_mcp.json b/packages/web/i18n/en/dashboard_mcp.json index 18dca9c1d778..5749d4982ff8 100644 --- a/packages/web/i18n/en/dashboard_mcp.json +++ b/packages/web/i18n/en/dashboard_mcp.json @@ -1,27 +1,27 @@ { "app_alias_name": "Tool name", - "app_description": "Application Description", - "app_name": "Application name", - "apps": "Exposed applications", - "create_mcp": "Create an MCP service", - "create_mcp_server": "Create a new service", - "delete_mcp_server_confirm_tip": "Confirm to delete the service?", - "edit_mcp": "Edit MCP Services", + "app_description": "App description", + "app_name": "App name", + "apps": "Apps for External Use", + "create_mcp": "Create MCP service", + "create_mcp_server": "Create service", + "delete_mcp_server_confirm_tip": "Are you sure you want to delete this service?", + "edit_mcp": "Edit MCP service", "has_chosen": "Selected", - "manage_app": "manage", - "mcp_apps": "Number of associated applications", - "mcp_endpoints": "Access address", - "mcp_json_config": "Access script", - "mcp_link_way": "Access method", + "manage_app": "Manage", + "mcp_apps": "Associated apps", + "mcp_endpoints": "Connection address", + "mcp_json_config": "Connection script", + "mcp_link_way": "Connection method", "mcp_name": "MCP service name", - "mcp_server": "MCP Services", - "mcp_server_description": "Allows you to select some applications to provide external use with the MCP protocol. \nDue to the immaturity of the MCP protocol, this feature is still in the beta stage.", - "not_sse_server": "The system does not configure SSE access service", - "search_app": "Search for apps", - "select_app": "Application selection", - "start_use": "Get started", + "mcp_server": "MCP services", + "mcp_server_description": "Enable external access to specific apps via MCP services. This feature is in beta test.", + "not_sse_server": "The SSE integration service is not configured.", + "search_app": "App name", + "select_app": "Select apps", + "start_use": "Details", "tool_name": "Tool name", - "tool_name_placeholder": "It is recommended to use an English name", - "tool_name_tip": "Some Clients only support English, and you can modify this value to the tool name in English.", - "usage_way": "MCP service usage" + "tool_name_placeholder": "English name is recommended.", + "tool_name_tip": "Some MCP clients only support English. You can change the tool name to an English one.", + "usage_way": "MCP service connection" } diff --git a/packages/web/i18n/en/database_client.json b/packages/web/i18n/en/database_client.json new file mode 100644 index 000000000000..d5deaeb1b0c3 --- /dev/null +++ b/packages/web/i18n/en/database_client.json @@ -0,0 +1,24 @@ +{ + "client_destory_error": "Failed to destroy the database client.", + "client_not_found": "Failed to initiate the database client.", + "not_support_databaseType": "The database type is not supported.", + "not_implemented_databaseType": "The database client is not implemented.", + "connection_failed": "Database connection failed. Please check the connection settings.", + "authentication_failed": "Database verification failed. Please check the username, password, and access permission.", + "database_not_exist": "The database does not exist. Please check its name.", + "database_port_error": "The database port is invalid. Please check.", + "connection_address_error": "The database address is invalid. Please check.", + "connection_timeout": "Database connection timed out. Please check the network connection.", + "connection_refused": "Connection to the database was denied. Please check whether the database allows connections.", + "connection_check_error": "Connectivity test failed. Please check the network connection.", + "connection_lost": "Disconnected from the database. Please check the network connection.", + "host_error": "The database address is invalid. Please check.", + "invalid_table_name": "Invalid table name.", + "fetch_info_error": "Failed to obtain database information.", + "database_config_not_found": "No database available. Please configure one first.", + "table_not_found": "Failed to obtain table information because the table does not exist.", + "illeagal_table_info": "Invalid table information. Please check.", + "op_unknown_database_error": "Unknown error occurred while accessing the database. Please check the database connection.", + "dative_service_error": "Error occurred while connecting to the Dative service. Please check the configuration.", + "tableNamesDuplicate": "Duplicate table names exist." +} \ No newline at end of file diff --git a/packages/web/i18n/en/dataset.json b/packages/web/i18n/en/dataset.json index c30dc82bfcf4..fbe843e5ed3c 100644 --- a/packages/web/i18n/en/dataset.json +++ b/packages/web/i18n/en/dataset.json @@ -1,226 +1,347 @@ { "Enable": "Enable", "Select_all": "Select all files", - "add_file": "Import", - "api_file": "API Dataset", - "api_url": "API Url", - "apidataset_configuration": "Configuration information", - "auto_indexes": "Automatically generate supplementary indexes", - "auto_indexes_tips": "Additional index generation is performed through large models to improve semantic richness and improve retrieval accuracy.", - "auto_training_queue": "Enhanced index queueing", + "add_file": "Add file", + "api_file": "API-based file database", + "api_url": "API address", + "apidataset_configuration": "Settings", + "auto_indexes": "Auto generate supplemental indexes", + "auto_indexes_tips": "Generate extra indexes using models to improve semantic richness and search accuracy.", + "auto_training_queue": "Enhanced index queue", "backup_collection": "Backup data", - "backup_dataset": "Backup import", - "backup_dataset_success": "The backup was created successfully", - "backup_dataset_tip": "You can reimport the downloaded csv file when exporting the knowledge base.", - "backup_mode": "Backup import", - "backup_template_invalid": "The backup file format is incorrect, it should be the csv file with the first column as q,a,indexes", + "backup_dataset": "Import from backup", + "backup_dataset_success": "Backup created successfully.", + "backup_dataset_tip": "Import the CSV file exported from Knowledge.", + "backup_mode": "Import from backup", + "backup_template_invalid": "Invalid backup file. It must be a CSV file with the first row containing \"q\" in the first column, \"a\" in the second, and \"indexes\" in the third.", "batch_delete": "Delete", - "chunk_max_tokens": "max_tokens", - "chunk_process_params": "Block processing parameters", - "chunk_size": "Block size", - "chunk_trigger": "Blocking conditions", - "chunk_trigger_force_chunk": "Forced chunking", - "chunk_trigger_max_size": "The original text length is greater than the maximum context of the file processing model 70%", - "chunk_trigger_min_size": "The original text is greater than", - "chunk_trigger_tips": "Block storage is triggered when certain conditions are met, otherwise the original text will be stored in full directly", - "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", - "collection_name": "Collection name", - "collection_not_support_retraining": "This collection type does not support retuning parameters", - "collection_not_support_sync": "This collection does not support synchronization", - "collection_sync": "Sync data", - "collection_sync_confirm_tip": "Confirm to start synchronizing data? \nThe system will pull the latest data for comparison. If the contents are different, a new collection will be created and the old collection will be deleted. Please confirm!", - "collection_tags": "Collection Tags", - "common.dataset.data.Input Error Tip": "[Image Dataset] Process error:", - "common.error.unKnow": "Unknown error", - "common_dataset": "General Dataset", - "common_dataset_desc": "Building a knowledge base by importing files, web page links, or manual entry", - "condition": "condition", - "config_sync_schedule": "Configure scheduled synchronization", + "chunk_max_tokens": "Max chunks", + "chunk_process_params": "Chunk settings", + "chunk_size": "Chunk size", + "chunk_trigger": "Chunking condition", + "chunk_trigger_force_chunk": "Force chunking", + "chunk_trigger_max_size": "Text length exceeds 70% of the max context length", + "chunk_trigger_min_size": "Text length exceeds", + "chunk_trigger_tips": "Chunking is triggered only when the specified condition is met. Otherwise, the full text is stored.", + "close_auto_sync": "Are you sure you want to disable auto sync?", + "collection.Create update time": "Time created/updated", + "collection.Training type": "Training mode", + "collection.sync.submit": "Sync task submitted successfully.", + "collection.training_type": "Processing mode", + "collection_data_count": "Data size", + "collection_metadata_custom_pdf_parse": "Enhanced PDF parsing", + "collection_name": "Dataset name", + "collection_not_support_retraining": "Parameter adjustment is not supported for this collection type.", + "collection_not_support_sync": "Sync is not supported for this collection type.", + "collection_sync": "Sync now", + "collection_sync_confirm_tip": "Are you sure you want to sync data? The system will obtain the latest data for comparison. If differences are found, a new collection will be created and the old one will be removed. Would you like to proceed?", + "collection_tags": "Collection tag", + "common.dataset.data.Input Error Tip": "Error occurred while processing the image dataset:", + "common.error.unKnow": "Unknown error.", + "common_dataset": "General", + "common_dataset_desc": "Build a knowledge base by importing files, adding web links, or entering information manually.", + "condition": "Condition", + "config_sync_schedule": "Scheduled sync", "confirm_delete_collection": "Confirm to delete {{num }} files?", - "confirm_import_images": "Total {{num}} | Confirm create", - "confirm_to_rebuild_embedding_tip": "Are you sure you want to switch the index for the Dataset?\nSwitching the index is a significant operation that requires re-indexing all data in your Dataset, which may take a long time. Please ensure your account has sufficient remaining points.\n\nAdditionally, you need to update the applications that use this Dataset to avoid conflicts with other indexed model Datasets.", + "confirm_import_images": "Total images: {{num}} | Create", + "confirm_to_rebuild_embedding_tip": "Are you sure you want to switch the index for this knowledge base?\nChanging the index will re-index all data in the knowledge base, which may take a long time. Please ensure you have sufficient credits.\n\nUpdate apps linked to this knowledge base to avoid confusion with other index models.", "core.dataset.Image collection": "Image dataset", - "core.dataset.import.Adjust parameters": "Adjust parameters", + "core.dataset.import.Adjust parameters": "Modify settings", "custom_data_process_params": "Custom", - "custom_data_process_params_desc": "Customize data processing rules", - "custom_split_char": "Char", - "custom_split_sign_tip": "Allows you to chunk according to custom delimiters. \nUsually used for processed data, using specific separators for precise chunking. \nYou can use the | symbol to represent multiple splitters, such as: \".|.\" to represent a period in Chinese and English.\n\nTry to avoid using special symbols related to regular, such as: * () [] {}, etc.", - "data_amount": "{{dataAmount}} Datas, {{indexAmount}} Indexes", - "data_error_amount": "{{errorAmount}} Group training exception", - "data_index_image": "Image index", + "custom_data_process_params_desc": "Use custom chunk rules", + "custom_split_char": "Delimiter", + "custom_split_sign_tip": "Allow chunking accurately based on custom delimiters. This is commonly used for processed data. Define multiple delimiters using the | character. For example, \"。 |.\" indicates both Chinese and English periods.\nAvoid using regex characters such as * () [] {}.", + "data_amount": "Data groups: {{dataAmount}}, Index groups: {{indexAmount}}", + "data_error_amount": "Training errors: {{errorAmount}} groups", + "data_index_image": "Image indexing", "data_index_num": "Index {{index}}", - "data_parsing": "Data analysis", - "data_process_params": "Params", - "data_process_setting": "Processing config", - "data_uploading": "Data is being uploaded: {{num}}%", - "dataset.Chunk_Number": "Block number", - "dataset.Completed": "Finish", - "dataset.Delete_Chunk": "delete", - "dataset.Edit_Chunk": "edit", - "dataset.Error_Message": "Report an error message", - "dataset.No_Error": "No exception information yet", - "dataset.Operation": "operate", - "dataset.ReTrain": "Retrain", + "data_parsing": "Parsing", + "data_process_params": "Data processing parameters", + "data_process_setting": "Data processing settings", + "data_uploading": "Uploading: {{num}}%", + "dataset.Chunk_Number": "Chunk ID", + "dataset.Completed": "Completed", + "dataset.Delete_Chunk": "Delete", + "dataset.Edit_Chunk": "Edit", + "dataset.Error_Message": "Error message", + "dataset.No_Error": "No errors detected.", + "dataset.Operation": "Operation", + "dataset.ReTrain": "Retry", "dataset.Training Process": "Training status", - "dataset.Training_Count": "{{count}} Group training", - "dataset.Training_Errors": "Errors", - "dataset.Training_QA": "{{count}} Group Q&A pair training", + "dataset.Training_Count": "In training: {{count}} groups", + "dataset.Training_Errors": "Errors ({{count}})", + "dataset.Training_QA": "In training: {{count}} Q&A pairs", "dataset.Training_Status": "Training status", - "dataset.Training_Waiting": "Need to wait for {{count}} group data", - "dataset.Unsupported operation": "dataset.Unsupported operation", - "dataset.no_collections": "No datasets available", - "dataset.no_tags": "No tags available", - "default_params": "default", - "default_params_desc": "Use system default parameters and rules", - "download_csv_template": "Click to download the CSV template", - "edit_dataset_config": "Edit knowledge base configuration", + "dataset.Training_Waiting": "Waiting for {{count}} groups of data", + "dataset.Unsupported operation": "Operation is not supported.", + "dataset.no_collections": "No datasets available.", + "dataset.no_tags": "No tags available.", + "default_params": "Default", + "default_params_desc": "Use default parameters and rules", + "download_csv_template": "Download CSV template", + "edit_dataset_config": "Edit knowledge base", "empty_collection": "Blank dataset", - "enhanced_indexes": "Index enhancement", - "error.collectionNotFound": "Collection not found~", - "external_file": "External File Library", - "external_file_dataset_desc": "You can use external file library to build a knowledge library through the API", - "external_id": "File Reading ID", - "external_other_dataset_desc": "Customize API, Feishu, Yuque and other external documents as knowledge bases", - "external_read_url": "External Preview URL", - "external_read_url_tip": "Configure the reading URL of your file library for user authentication. Use the {{fileId}} variable to refer to the external file ID.", - "external_url": "File Access URL", - "failedToLoadRootDirectories": "Failed to load root directories", - "failedToLoadSubDirectories": "Failed to load subdirectories", - "feishu_dataset": "Feishu Dataset", - "feishu_dataset_config": "Feishu Dataset Config", - "feishu_dataset_desc": "Can build a dataset using Feishu documents by configuring permissions, without secondary storage", - "file_list": "File list", - "file_model_function_tip": "Enhances indexing and QA generation", - "filename": "Filename", + "enhanced_indexes": "Enhanced indexing", + "error.collectionNotFound": "Collection not found.", + "external_file": "External file database", + "external_file_dataset_desc": "Build a knowledge base via APIs using external file databases.", + "external_id": "File ID", + "external_other_dataset_desc": "Build a knowledge base using external documents (including those from API, Feishu, and Yuque).", + "external_read_url": "External preview URL", + "external_read_url_tip": "Configure the URL for reading your file database. Used for user access authentication. Use the {{fileId}} variable to represent the external file ID.", + "external_url": "File access URL", + "failedToLoadRootDirectories": "Failed to load the root directory.", + "failedToLoadSubDirectories": "Failed to load the subdirectory.", + "feishu_dataset": "Feishu knowledge base", + "feishu_dataset_config": "Feishu knowledge base settings", + "feishu_dataset_desc": "Build a knowledge base using Feishu documents. These documents will not be stored again.", + "file_list": "Files", + "file_model_function_tip": "Enhances indexes and Q&A generation.", + "filename": "File name", "folder_dataset": "Folder", - "getDirectoryFailed": "Get directory failed", - "image_auto_parse": "Automatic image indexing", - "image_auto_parse_tips": "Call VLM to automatically label the pictures in the document and generate additional search indexes", - "image_training_queue": "Queue of image processing", + "getDirectoryFailed": "Failed to obtain the directory.", + "image_auto_parse": "Auto index images", + "image_auto_parse_tips": "Automatically mark images in documents using the VLM and generate search indexes for them.", + "image_training_queue": "Image processing queue", "images_creating": "Creating", - "immediate_sync": "Immediate Synchronization", - "import.Auto mode Estimated Price Tips": "The text understanding model needs to be called, which requires more points: {{price}} points/1K tokens", - "import.Embedding Estimated Price Tips": "Only use the index model and consume a small amount of AI points: {{price}} points/1K tokens", - "import_confirm": "Confirm upload", - "import_data_preview": "Data preview", - "import_data_process_setting": "Data processing method settings", - "import_file_parse_setting": "File parsing settings", - "import_model_config": "Model selection", - "import_param_setting": "Parameter settings", - "import_select_file": "Select a file", - "import_select_link": "Enter link", - "index_prefix_title": "Index add title", - "index_prefix_title_tips": "Automatically add title names to all indexes", + "immediate_sync": "Sync now", + "import.Auto mode Estimated Price Tips": "Text understanding model call is required, and the consumed AI points ({{price}} points/1K tokens) is high.", + "import.Embedding Estimated Price Tips": "Only index model call is required, and the consumed AI points: ({{price}} points / 1K tokens) is low.", + "import_confirm": "Confirm", + "import_data_preview": "Preview data", + "import_data_process_setting": "Data processing", + "import_file_parse_setting": "File parsing", + "import_model_config": "Select model", + "import_param_setting": "Configure parameters", + "import_select_file": "Select files", + "import_select_link": "Link", + "index_prefix_title": "Include titles in indexes", + "index_prefix_title_tips": "Automatically include titles in all indexes.", "index_size": "Index size", - "index_size_tips": "When vectorized, the system will automatically further segment the blocks according to this size.", - "input_required_field_to_select_baseurl": "Please enter the required information first", - "insert_images": "Added pictures", - "insert_images_success": "The new picture is successfully added, and you need to wait for the training to be completed before it will be displayed.", - "is_open_schedule": "Enable scheduled synchronization", - "keep_image": "Keep the picture", - "llm_paragraph_mode": "LLM recognition paragraph", - "llm_paragraph_mode_auto": "automatic", - "llm_paragraph_mode_auto_desc": "Enable model recognition when the text content does not contain a Markdown title.", + "index_size_tips": "Length of content during vectorization. The system will automatically create indexes for chunks based on the specified index size.", + "input_required_field_to_select_baseurl": "Please configure the required fields first.", + "insert_images": "Add image", + "insert_images_success": "Image added successfully. It will be displayed only after training is complete.", + "is_open_schedule": "Enable scheduled sync", + "keep_image": "Retain image", + "llm_paragraph_mode": "Paragraph recognition", + "llm_paragraph_mode_auto": "Auto", + "llm_paragraph_mode_auto_desc": "Automatically enable paragraph recognition if the text does not contain titles in Markdown format.", "llm_paragraph_mode_forbid": "Disabled", - "llm_paragraph_mode_forbid_desc": "Force the disabling of the model's automatic paragraph recognition", - "llm_paragraph_mode_force": "Force Process", - "llm_paragraph_mode_force_desc": "Force the use of the model to automatically identify paragraphs and ignore paragraphs in the original text (if any)", + "llm_paragraph_mode_forbid_desc": "Forcibly disable auto paragraph recognition.", + "llm_paragraph_mode_force": "Enabled", + "llm_paragraph_mode_force_desc": "Forcibly implement auto paragraph recognition using the model and ignore the original paragraph structure (if any).", "loading": "Loading...", - "max_chunk_size": "Maximum chunk size", - "move.hint": "After moving, the selected knowledge base/folder will inherit the permission settings of the new folder, and the original permission settings will become invalid.", - "noChildren": "No subdirectories", - "noSelectedFolder": "No selected folder", - "noSelectedId": "No selected ID", - "noValidId": "No valid ID", - "open_auto_sync": "After scheduled synchronization is turned on, the system will try to synchronize the collection from time to time every day. During the collection synchronization period, the collection data will not be searched.", - "other_dataset": "Third-party knowledge base", - "paragraph_max_deep": "Maximum paragraph depth", - "paragraph_split": "Partition by paragraph", - "paragraph_split_tip": "Priority is given to chunking according to the Makdown title paragraph. If the chunking is too long, then chunking is done according to the length.", - "params_config": "Config", - "pdf_enhance_parse": "PDF enhancement analysis", - "pdf_enhance_parse_price": "{{price}} points/page", - "pdf_enhance_parse_tips": "Calling PDF recognition model for parsing, you can convert it into Markdown and retain pictures in the document. At the same time, you can also identify scanned documents, which will take a long time to identify them.", - "permission.des.manage": "Can manage the entire knowledge base data and information", - "permission.des.read": "View knowledge base content", - "permission.des.write": "Ability to add and change knowledge base content", - "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", - "process.Get QA": "Q&A extraction", - "process.Image_Index": "Image index generation", + "max_chunk_size": "Max chunk size", + "move.hint": "If moved, the knowledge base or folder will inherit the permissions of the destination folder, invalidating its current permissions.", + "noChildren": "The subdirectory does not exist.", + "noSelectedFolder": "Please select a folder first.", + "noSelectedId": "Please select an ID first.", + "noValidId": "No valid IDs available.", + "open_auto_sync": "If auto sync is enabled, the system will automatically sync collections every day. Data from the collections cannot be retrieved during the collection sync.", + "other_dataset": "Third-party", + "paragraph_max_deep": "Max paragraph depth", + "paragraph_split": "Chunk by paragraph", + "paragraph_split_tip": "Split text based on Markdown heading structure first. If a chunk exceeds the maximum length, it will be split further.", + "params_config": "Configuration", + "pdf_enhance_parse": "Enhanced PDF parsing", + "pdf_enhance_parse_price": "{{price}} credits/page", + "pdf_enhance_parse_tips": "Call a PDF recognition model to parse PDF files, converting them into Markdown format with images preserved, and to process scanned copies of PDF files, which takes longer time.", + "permission.des.manage": "Manages all data and information of an entire knowledge base.", + "permission.des.read": "Views the knowledge base content.", + "permission.des.write": "Adds and modifies the knowledge base content.", + "pleaseFillUserIdAndToken": "User ID and Token are required.", + "preview_chunk": "Chunk preview", + "preview_chunk_empty": "The file is empty.", + "preview_chunk_folder_warning": "Directory preview is not supported.", + "preview_chunk_intro": "Total chunks: {{total}}, Max displayed: 10", + "preview_chunk_not_selected": "Click a file on the left for preview.", + "process.Auto_Index": "Auto generate indexes", + "process.Get QA": "Extract Q&A pairs", + "process.Image_Index": "Generate image indexes", "process.Is_Ready": "Ready", - "process.Is_Ready_Count": "{{count}} Group is ready", - "process.Parse_Image": "Image analysis", - "process.Parsing": "Parsing", - "process.Vectorizing": "Index vectorization", - "process.Waiting": "Queue", - "rebuild_embedding_start_tip": "Index model switching task has started", - "rebuilding_index_count": "Number of indexes being rebuilt: {{count}}", - "request_headers": "Request headers, will automatically append 'Bearer '", - "retain_collection": "Adjust Training Parameters", - "retrain_task_submitted": "The retraining task has been submitted", + "process.Is_Ready_Count": "{{count}} groups are ready.", + "process.Parse_Image": "Parse images", + "process.Parsing": "Parse content", + "process.Vectorizing": "Vectorize indexes", + "process.Waiting": "Waiting", + "rebuild_embedding_start_tip": "Switch index model", + "rebuilding_index_count": "Indexes being rebuilt: {{count}}", + "request_headers": "Request header parameter. Bearer will be automatically added.", + "retain_collection": "Modify settings", + "retrain_task_submitted": "The retraining task was submitted.", "retry_all": "Retry all", - "retry_failed": "Retry Failed", - "rootDirectoryFormatError": "Root directory data format is incorrect", - "rootdirectory": "/rootdirectory", - "same_api_collection": "The same API set exists", - "selectDirectory": "Choose", - "selectRootFolder": "Select Root Folder", - "split_chunk_char": "Block by specified splitter", - "split_chunk_size": "Block by length", - "split_sign_break": "1 newline character", - "split_sign_break2": "2 newline characters", - "split_sign_custom": "Customize", - "split_sign_exclamatiob": "exclamation mark", - "split_sign_null": "Not set", - "split_sign_period": "period", - "split_sign_question": "question mark", - "split_sign_semicolon": "semicolon", - "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", - "sync_schedule_tip": "Only existing collections will be synchronized. \nIncludes linked collections and all collections in the API knowledge base. \nThe system will poll for updates every day, and the specific update time cannot be determined.", - "tag.Add_new_tag": "add_new Tag", - "tag.Edit_tag": "Edit Tag", - "tag.add": "Create", - "tag.add_new": "add_new", - "tag.cancel": "Cancel", - "tag.delete_tag_confirm": "Confirm to delete the tag?", - "tag.manage": "Tagging", - "tag.searchOrAddTag": "Search or Add Tag", - "tag.tags": "Tags", - "tag.total_tags": "Total {{total}} tags", - "template_dataset": "Template import", - "template_file_invalid": "The template file format is incorrect, it should be the csv file with the first column as q,a,indexes", - "template_mode": "Template import", - "the_knowledge_base_has_indexes_that_are_being_trained_or_being_rebuilt": "The Dataset has indexes that are being trained or rebuilt", - "total_num_files": "Total {{total}} files", - "training.Error": "{{count}} Group exception", + "retry_failed": "Retry failed.", + "rootDirectoryFormatError": "The entries in the root directory are invalid.", + "rootdirectory": "/root directory", + "same_api_collection": "The API collection already exists.", + "selectDirectory": "Select", + "selectRootFolder": "Select Root Directory", + "split_chunk_char": "Chunk by delimiter", + "split_chunk_size": "Chunk by length", + "split_sign_break": "1 line break", + "split_sign_break2": "2 line breaks", + "split_sign_custom": "Custom", + "split_sign_exclamatiob": "Exclamation mark", + "split_sign_null": "Not specified", + "split_sign_period": "Period", + "split_sign_question": "Question mark", + "split_sign_semicolon": "Semicolon", + "start_sync_dataset_tip": "Are you sure you want to sync the entire knowledge base?", + "status_error": "Running error occurred.", + "sync_collection_failed": "Error occurred while syncing the collection. Please check whether the source file is accessible.", + "sync_schedule": "Auto sync", + "sync_schedule_tip": "Only existing collections will be synced, including link collections and all collections in the API knowledge base. The update time is not fixed because collections are updated based on round-robin scheduling.", + "tag.Add_new_tag": "Add tag", + "tag.Edit_tag": "Edit tag", + "tag.add": "Add tag", + "tag.add_new": "Add", + "tag.cancel": "Deselect", + "tag.delete_tag_confirm": "Are you sure you want to delete the tag?", + "tag.manage": "Manage tags", + "tag.searchOrAddTag": "Search or add", + "tag.tags": "Tag", + "tag.total_tags": "Total tags: {{total}}", + "template_dataset": "Import from template", + "template_file_invalid": "Invalid template file. Must be a CSV file with the top row being q, a, indexes.", + "template_mode": "Import from template", + "the_knowledge_base_has_indexes_that_are_being_trained_or_being_rebuilt": "The knowledge base contains indexes that are being trained or rebuilt.", + "total_num_files": "Total files: {{total}}", + "training.Error": "{{count}} groups encountered error.", "training.Image mode": "Image processing", "training.Normal": "Normal", - "training_mode": "Chunk mode", - "training_ready": "{{count}} Group", - "upload_by_template_format": "Upload by template file", + "training_mode": "Processing Mode", + "training_ready": "{count} groups", + "upload_by_template_format": "Please fill the template out, and then upload it.", "uploading_progress": "Uploading: {{num}}%", - "vector_model_max_tokens_tip": "Each chunk of data has a maximum length of 3000 tokens", - "vllm_model": "Image understanding model", - "vlm_model_required_warning": "Image datasets require a Vision Language Model (VLM) to be configured. Please add a model that supports image understanding in the model configuration first.", - "website_dataset": "Website Sync", - "website_dataset_desc": "Build knowledge base by crawling web page data in batches", - "website_info": "Website Information", - "yuque_dataset": "Yuque Knowledge Base", - "yuque_dataset_config": "Configure Yuque Knowledge Base", - "yuque_dataset_desc": "Build knowledge base using Yuque documents by configuring document permissions, documents will not be stored twice" + "vector_model_max_tokens_tip": "Each chunk cannot exceed 3000 tokens.", + "vllm_model": "VLM", + "vlm_model_required_warning": "A VLM is required.", + "website_dataset": "Website sync", + "website_dataset_desc": "Build a knowledge base by scraping web data using web crawlers.", + "website_info": "Website info", + "yuque_dataset": "Yuque knowledge base", + "yuque_dataset_config": "Configure Yuque knowledge base", + "yuque_dataset_desc": "Build a knowledge base using Yuque documents. These documents will not be stored again.", + "enterprise_database": "Database", + "enterprise_database_desc": "Create a knowledge base and connect it to a MySQL database to retrieve related information.", + "enterprise_database_embedding_model_tip": "The index model can convert key database information (such as table name, table description, column name, and column description) into vectors for semantic search.", + "database_structure_change_tip": "When the data structure changes, including changes to the table or column name, please refresh the data source.", + "refresh_data_source": "Refresh", + "search_name_or_description": "Name, description", + "connect_database": "Connect to database", + "data_config": "Select tables", + "database_type": "Database type", + "mysql_description": "Supported versions: MySQL 5.7 and later", + "database_host": "Database address", + "host_placeholder": "IP address or domain name", + "host_tips": "Please enter a reachable address.", + "host_required": "This field is required.", + "port": "Port", + "port_required": "This field is required.", + "port_range_error": "Port range: 1-65535", + "database_name": "Database name", + "database_username": "Username", + "database_password": "Password", + "connection_pool_size": "Connection pool size", + "connection_pool_required": "This field is required.", + "connection_pool_min_error": "Cannot be smaller than 1.", + "connection_pool_max_error": "Cannot exceed 100.", + "connect_next_step": "Connect & proceed", + "database_config_title": "The knowledge base will index data from the selected tables.", + "search_tables": "Table name", + "table_selection_warning": "Configuration is not complete.", + "table_description": "Description", + "default_table_description": "Default value: Predefined description in the table", + "column_configuration": "Columns", + "search_columns": "Column name", + "column_name": "Column name", + "column_type": "Type", + "column_description": "Description", + "column_enabled": "Status", + "default_column_description": "Default value: Predefined description in the table", + "confirm": "OK", + "edit_database_config_warning": "Data source configuration is complete. {{changedCount}} tables have column changes, and {{deletedCount}} no longer exist. Please refresh the data source.", + "edit_database_warning": "After the database information is modified, apps connecting to it will be disconnected and may fail to retrieve information.", + "database_config": "Settings", + "refresh_success": "Refreshed successfully.", + "refresh_datasource": "Refresh", + "reconnect_success": "Reconnected to the database. Configuration changed successfully.", + "auth_failed": "Failed to connect to the database because authentication failed. Please check the username and password.", + "connection_failed": "Connection failed.", + "reconnecting": "Reconnecting...", + "reconnect_success_detail": "Reconnected to the database. Configuration changed successfully.", + "table_changes_notice": "{{changedCount}} tables have column changes, and {{deletedCount}} no longer exist. Please refresh the data source.", + "reconnect_database": "Reconnect to database", + "column_desc_accuracy_tip": "To increase the accuracy of answers, please accurately describe the meaning and purpose of the data in a column. The model will select the columns to retrieve information and generate answers based on the column description.", + "default_table_desc_tip": "The predefined description in the table is used by default.", + "column_enabled_tip": "If enabled, the model will query this column to retrieve data for generating answers.", + "connecting": "Connecting", + "test_connectivity": "Test connectivity", + "connect_and_next": "Connect & proceed", + "connection_network_error": "Connection failed. Please check network connectivity.", + "validate_ip_tip": "Loopback addresses (such as localhost, 127.x.x.x, and 0.0.0.0) are not allowed.", + "database": "Database", + "search_model": "Retrieval model", + "search_model_desc": "Generates SQL statements for database search, performs queries and aggregation, and produces conversational text.", + "search_model_tip": "Non-reasoning models with more parameters generally generate better results.", + "other_knowledge_base": "Other knowledge bases", + "database_search": "Database search", + "description": "Description", + "remove": "Remove", + "confirm_remove_database_table": "Are you sure you want to remove this table and all its configuration?", + "data_source_refreshed": "The data source has been refreshed.", + "found": "", + "tables_with_column_changes": "{{modifiedTablesCount}} tables have column changes, ", + "tables_not_exist": "and {{delTablesCount}} no longer exist. ", + "please_check_latest_data": "Please check the data source.", + "has_unfilled_content": "Configuration is not complete.", + "has_column_changes": "Column changes exist.", + "connection_success": "Connection successful.", + "tables_added": "{{addedTables}} tables are added.", + "tables_modified": "{{modifiedTables}} tables have column changes, ", + "tables_deleted": "and {{deletedTables}} no longer exist. ", + "please_verify_data": "Please check the data.", + "changes_detected": "", + "new_columns_added_disabled": " columns are added (new columns are disabled by default). ", + "columns_no_longer_exist": " columns no longer exist. ", + "check_latest_data": "Please check the data.", + "config": "Go now", + "refresh_failed": "Failed", + "unknown_error": "Unknown error.", + "no_data_available": "No data available.", + "database_sql_query": "SQL statement", + "search_result": "Search result", + "no_search_result": "No results available.", + "database_address_required": "Database address is required.", + "ip_format_invalid_range": "Invalid IP address. Each octet must be 0-255.", + "database_address_format_invalid": "Invalid database address. Please enter a valid IP address or domain name.", + "data_index_column_value":"SQL field example value index", + "error_create_datasetcollection": "Failed to create the dataset. Please contact the administrator.", + "tables_modified_and_deleted": "{{modifiedTables}} tables have column changes, and {{deletedTables}} no longer exist. ", + "tables_no_longer_exist_comma": " tables no longer exist. ", + "process.databaseSchema": "Generate database structure index", + "database_url": "Database address", + "database_url_placeholder": "Example: localhost or 192.168.1.100", + "database_url_required": "This field is required.", + "database_port": "Port", + "database_username_placeholder": "Username", + "database_username_required": "This field is required.", + "database_password_placeholder": "Password", + "database_password_required": "This field is required.", + "database_max_connections": "Max Connections", + "database_required_fields": "Please configure all the required fields.", + "save_database_config": "Save", + "confirm_update_database_config": "Are you sure you want to update the database configuration?", + "confirm_create_database_config": "Are you sure you want to create the database configuration?", + "database_config_updated": "Updated successfully.", + "data_index_column_description": "SQL field description index", + "no_data_in_database": "No data in the database", + "database_host_tooltip": "Must be a reachable address.", + "database_host_port_tip": "Range: {{min}}~{{max}}", + "connection_pool_size_tooltip": "The app must be connected to the database for query. To ensure normal database operations, a connection pool will be established to ensure that the number of connections does not exceed the pool size. Please configure the pool size properly based on the database performance and concurrent access.", + "database_table_desc_tip": "To increase the accuracy of answers, please accurately describe the meaning and purpose of the table. The model will call the table based on the description.", + "database_tables": "Database tables", + "remove_warning": "Confirm" } + diff --git a/packages/web/i18n/en/evaluation.json b/packages/web/i18n/en/evaluation.json new file mode 100644 index 000000000000..8f6bfed5b6bd --- /dev/null +++ b/packages/web/i18n/en/evaluation.json @@ -0,0 +1,130 @@ +{ + "dataset_collection_not_found": "Evaluation dataset collection not found", + "dataset_data_not_found": "Evaluation dataset not found.", + "name_required": "Name is required.", + "name_too_long": "Name is too long.", + "name_duplicate": "Name already exists.", + "description_too_long": "Description is too long.", + "target_required": "Evaluation target is required.", + "target_invalid_config": "Evaluation target configuration is invalid.", + "target_app_id_missing": "App ID is missing.", + "target_version_id_missing": "App version ID is missing.", + "evaluators_required": "Evaluator is required.", + "evaluator_invalid_config": "Evaluator configuration is invalid.", + "evaluator_invalid_score_scaling": "Invalid score scaling value, must be a positive number greater than 0 and less than or equal to 10000 (decimals supported, e.g., 0.01 means 100x reduction)", + "user_input_required": "Question is required.", + "expected_output_required": "Expected answer is required.", + "invalid_format": "Format is invalid.", + "id_required": "Evaluation task ID is required.", + "item_id_required": "Evaluation case ID is required.", + "data_item_id_required": "Evaluation case ID is required.", + "invalid_context": "Context is invalid.", + "invalid_retrieval_context": "Search context is invalid.", + "insufficient_permission": "You do not have permission.", + "app_not_found": "App not found.", + "task_not_found": "Evaluation task not found.", + "item_not_found": "Evaluation case not found.", + "metric_builtin_cannot_modify": "Predefined metrics cannot be modified.", + "metric_builtin_cannot_delete": "Predefined metrics cannot be deleted.", + "invalid_status": "Status is invalid.", + "invalid_state_transition": "Invalid status conversation. Only evaluations in waiting or manually stopped status can be started.", + "only_running_can_stop": "Only evaluations in progress can be stopped.", + "item_no_error_to_retry": "Retry is not available because there are no failed evaluation cases.", + "target_execution_error": "Target Execution error.", + "dataset_load_failed": "Failed to load datasets.", + "target_config_invalid": "Target configuration is invalid.", + "evaluators_config_invalid": "Evaluator configuration is invalid.", + "unsupported_target_type": "Target type is not supported.", + "app_version_not_found": "App version not found.", + "duplicate_dataset_name": "The dataset name already exists.", + "no_data_in_collections": "No data in collections", + "update_failed": "Update failed.", + "task_system_error": "System error occurred while processing evaluation task", + "manually_stopped": "Manually stopped", + "evaluator_execution_errors": "Evaluator execution errors", + + "metric_not_found": "Evaluation metrics not found.", + "metric_un_auth": "You do not have permission on the evaluation metric.", + "metric_name_required": "Metric name is required.", + "metric_name_too_long": "Metric name cannot exceed 100 characters.", + "metric_description_required": "Metric description is required and must be a non-empty string", + "metric_description_too_long": "Metric description cannot exceed 100 characters.", + "metric_prompt_required": "Prompt is required.", + "metric_prompt_too_long": "Prompt cannot exceed 4,000 characters.", + "metric_type_required": "Metric type is required.", + "metric_type_invalid": "Metric type is invalid.", + "metric_name_invalid": "Metric name is invalid.", + "metric_id_required": "Metric ID is required.", + + "eval_case_required": "Evaluation case is required.", + "eval_case_user_input_required": "Question is required.", + "eval_case_user_input_too_long": "Question cannot exceed 1,000 characters.", + "eval_case_actual_output_required": "Actual answer is required.", + "eval_case_actual_output_too_long": "Actual answer cannot exceed 4,000 characters.", + "eval_case_expected_output_required": "Expected answer is required.", + "eval_case_expected_output_too_long": "Expected answer cannot exceed 4,000 characters.", + + "llm_config_required": "LLM is required.", + "llm_model_name_required": "LLM model name is required.", + + "debug_evaluation_failed": "Failed to debug evaluation.", + + "evaluator_config_required": "Evaluator configuration is required.", + "evaluator_llm_config_missing": "LLM configuration is missing in evaluator.", + "evaluator_embedding_config_missing": "Embedding model configuration is missing in evaluator.", + "evaluator_llm_model_not_found": "LLM model does not exist or failed to be obtained.", + "evaluator_embedding_model_not_found": "Embedding model does not exist or failed to be obtained.", + "evaluator_request_timeout": "Evaluation request timed out.", + "evaluator_service_unavailable": "Evaluation service is unavailable.", + "evaluator_invalid_response": "An invalid response was returned by the evaluator.", + "evaluator_network_error": "Evaluator network connection error.", + + "eval_id_required": "Evaluation task ID is required.", + "summary_metrics_config_error": "Metric configuration error.", + "summary_threshold_value_required": "Threshold is required.", + "summary_weight_required": "Weight is required.", + "summary_weight_must_be_number": "The weight must be a digit.", + "summary_threshold_must_be_number": "The threshold must be a digit.", + "summary_calculate_type_required": "Calculation type is required.", + "summary_calculate_type_invalid": "Calculation type is invalid.", + "summary_no_valid_metrics_found": "No valid metrics found.", + "summary_stream_response_not_supported": "Streaming response is not supported.", + "summary_weight_sum_must_be_100": "The total weight of all metrics must be 100.", + "summary_model_invalid": "Evaluation summary generation failed. Please check the evaluation model configuration.", + + "dataset_collection_id_required": "Dataset ID is required.", + "dataset_collection_update_failed": "Failed to update dataset.", + "dataset_model_not_found": "Model not found.", + "dataset_no_data": "No dataset yet.", + "dataset_data_id_required": "Data ID is required.", + "data_quality_status_invalid": "The data quality is invalid.", + "dataset_data_user_input_required": "Question is required.", + "dataset_data_expected_output_required": "Expected answer is required.", + "dataset_data_actual_output_must_be_string": "The actual answer must be a string.", + "dataset_data_context_must_be_array_of_strings": "The context must be an array of strings.", + "dataset_data_retrieval_context_must_be_array_of_strings": "The search context must be an array of strings.", + "dataset_data_enable_quality_eval_required": "The quality evaluation enabling flag is required.", + "dataset_data_evaluation_model_required_for_quality": "The evaluation model is required when the quality evaluation is enabled.", + "dataset_data_metadata_must_be_object": "Metadata must be an object.", + "dataset_data_list_error": "The dataset data list is invalid.", + "quality_assessment_failed": "Quality evaluation failed.", + "data_quality_job_active_cannot_set_high_quality": "Unable to mark as high quality because a quality evaluation task is in progress.", + "dataset_task_not_retryable": "The task cannot be retried.", + "dataset_task_job_not_found": "Task job not found.", + "dataset_task_job_mismatch": "The task job does not belong to this dataset.", + "dataset_task_only_failed_can_delete": "Only failed tasks can be deleted.", + "dataset_task_operation_failed": "Failed to execute the task.", + "dataset_task_delete_failed": "Failed to delete the dataset task.", + "fetch_failed_tasks_error": "Failed to obtain failed tasks.", + "file_required": "File is required.", + "file_must_be_csv": "The file must be in CSV format.", + "csv_invalid_structure": "The CSV file structure is invalid.", + "csv_parsing_error": "CSV file parsing error occurred.", + "csv_no_data_rows": "The CSV file contains no data rows.", + "count_must_be_greater_than_zero": "The data size must be greater than zero.", + "count_exceeds_available_data": "The requested data size exceeds the available data size of the selected knowledge base.", + "selected_datasets_contain_no_data": "The selected dataset is empty.", + "model_name_invalid": "The evaluation model name must be a string.", + "model_name_too_long": "The evaluation model name cannot exceed 100 characters.", + "description_invalid_type": "The description must be a string." +} diff --git a/packages/web/i18n/en/file.json b/packages/web/i18n/en/file.json index d53d0a88c0a5..28cacd2e8644 100644 --- a/packages/web/i18n/en/file.json +++ b/packages/web/i18n/en/file.json @@ -1,47 +1,48 @@ { - "Action": "Please select the image to upload", - "All images import failed": "All pictures failed to import", - "Dataset_ID_not_found": "The dataset ID does not exist", - "Failed_to_get_token": "Failed to obtain the token", - "Image_ID_copied": "Copy ID", - "Image_Preview": "Picture preview", - "Image_dataset_requires_VLM_model_to_be_configured": "The image dataset needs to be configured with the image understanding model (VLM) to be used. Please add a model that supports image understanding in the model configuration first.", - "Image_does_not_belong_to_current_team": "The picture does not belong to the current team", - "Image_file_does_not_exist": "The picture does not exist", - "Loading_image": "Loading the picture...", - "Loading_image failed": "Preview loading failed", - "Only_support_uploading_one_image": "Only support uploading one image", - "Please select the image to upload": "Please select the image to upload", - "Please wait for all files to upload": "Please wait for all files to be uploaded to complete", - "bucket_chat": "Conversation Files", - "bucket_file": "Dataset Documents", - "bucket_image": "picture", - "click_to_view_raw_source": "Click to View Original Source", - "common.Some images failed to process": "Some images failed to process", - "common.dataset_data_input_image_support_format": "Support .jpg, .jpeg, .png, .gif, .webp formats", - "count.core.dataset.collection.Create Success": "{{count}} picture successfully imported", - "delete_image": "Delete pictures", - "file_name": "Filename", - "file_size": "Filesize", - "image": "picture", - "image_collection": "Picture collection", - "image_description": "Image description", - "image_description_tip": "Please enter the description of the picture", - "please_upload_image_first": "Please upload the picture first", - "reached_max_file_count": "Maximum file count reached", - "release_the_mouse_to_upload_the_file": "Release Mouse to Upload File", - "select_and_drag_file_tip": "Click or Drag Files Here to Upload", - "select_file_amount_limit": "You can select up to {{max}} files", - "some_file_count_exceeds_limit": "Exceeded {{maxCount}} files, automatically truncated", - "some_file_size_exceeds_limit": "Some files exceed {{maxSize}}, filtered out", - "support_file_type": "Supports {{fileType}} file types", - "support_max_count": "Supports up to {{maxCount}} files", - "support_max_size": "Maximum file size is {{maxSize}}", - "template_csv_file_select_tip": "Only support {{fileType}} files that are strictly in accordance with template format", - "template_strict_highlight": "Strictly follow the template", - "total_files": "Total {{selectFiles.length}} files", - "upload_error_description": "Only multiple files or a single folder can be uploaded at a time", - "upload_failed": "Upload Failed", - "upload_file_error": "Please upload pictures", + "Action": "Please select an image to upload.", + "All images import failed": "Failed to import all images.", + "Dataset_ID_not_found": "The dataset ID does not exist.", + "Failed_to_get_token": "Failed to obtain the token.", + "Image_ID_copied": "ID copied successfully.", + "Image_Preview": "Image preview", + "Image_dataset_requires_VLM_model_to_be_configured": "To use an image dataset, please go to Account > Models > Model settings to add a model that supports image recognition first.", + "Image_does_not_belong_to_current_team": "The image does not belong to the current team.", + "Image_file_does_not_exist": "The image does not exist.", + "Loading_image": "Loading image...", + "Loading_image failed": "Failed to load the image for preview.", + "Only_support_uploading_one_image": "Only one image can be uploaded.", + "Please select the image to upload": "Please select an image to upload.", + "Please wait for all files to upload": "Please wait until all files are uploaded.", + "bucket_chat": "Chat file", + "bucket_file": "Knowledge base file", + "eval_file": "File", + "bucket_image": "Image", + "click_to_view_raw_source": "Click to view source", + "common.Some images failed to process": "Failed to process some images.", + "common.dataset_data_input_image_support_format": "Supported: .jpg, .jpeg, .png, .gif, and .webp", + "count.core.dataset.collection.Create Success": "{{count}} images imported successfully.", + "delete_image": "Delete image", + "file_name": "File name", + "file_size": "File size", + "image": "Image", + "image_collection": "Image collection", + "image_description": "Description", + "image_description_tip": "", + "please_upload_image_first": "Please upload an image first.", + "reached_max_file_count": "The number of files reaches the maximum.", + "release_the_mouse_to_upload_the_file": "Release to start upload", + "select_and_drag_file_tip": "Drag & drop or click to upload", + "select_file_amount_limit": "Up to {{max}} files can be selected.", + "some_file_count_exceeds_limit": "The number of selected files exceeds the maximum ({{maxCount}}). The additional files were automatically ignored.", + "some_file_size_exceeds_limit": "Some files were removed because their size exceeds the maximum ({{maxSize}}).", + "support_file_type": "Supported: {{fileType}}", + "support_max_count": "Maximum: {{maxCount}} files, ", + "support_max_size": "{{maxSize}} or less per file", + "template_csv_file_select_tip": "Supported: {{fileType}} file strictly consistent with the template", + "template_strict_highlight": "Strictly consistent with the template", + "total_files": "Total files: {{selectFiles.length}}", + "upload_error_description": "Only multiple files or one folder can be uploaded at a time.", + "upload_failed": "Error occurred during the upload.", + "upload_file_error": "Please upload an image.", "uploading": "Uploading..." } diff --git a/packages/web/i18n/en/login.json b/packages/web/i18n/en/login.json index 5041732eb288..361e72313656 100644 --- a/packages/web/i18n/en/login.json +++ b/packages/web/i18n/en/login.json @@ -1,21 +1,21 @@ { - "Chinese_ip_tip": "It is detected that you are a mainland Chinese IP, click to jump to visit the mainland China version.", - "Login": "Login", - "agree": "agree", - "cookies_tip": " This website uses cookies to provide a better service experience. By continuing to use the site, you agree to our Cookie Policy.", - "forget_password": "Find Password", - "login_failed": "Login failed", - "login_success": "Login successful", - "no_remind": "Don't remind again", - "password_condition": "Password maximum 60 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 it, you have read and agree to\n
our Terms & Privacy Policy
", - "privacy": "Privacy Policy", - "privacy_policy": "Privacy Policy", - "redirect": "Jump", - "register": "Register", - "root_password_placeholder": "The root user password is the value of the environment variable DEFAULT_ROOT_PSW", - "terms": "Terms", - "use_root_login": "Log in as root user", - "wecom": "Enterprise WeChat" + "Chinese_ip_tip": "Your IP address is in Mainland China. Click to use the Chinese Mainland edition.", + "Login": "Log in", + "agree": "Agree", + "cookies_tip": "This website uses cookies to improve your experience. By continuing to use this website, you agree to the cookie policy.", + "forget_password": "Forgot password?", + "login_failed": "Login error occurred.", + "login_success": "Login successful.", + "no_remind": "Do not show this again", + "password_condition": "Password cannot exceed 60 characters.", + "password_tip": "Must be at least 8 characters long and contain at least 2 of the following: digits, letters, and special characters.", + "policy_tip": "By logging in, you have read and accept the EULA and DPA", + "privacy": "DPA", + "privacy_policy": "DPA", + "redirect": "Go now", + "register": "Sign up", + "root_password_placeholder": "The root account password is the value of the environment variable DEFAULT_ROOT_PSW.", + "terms": "EULA", + "use_root_login": "Log in as root", + "wecom": "WeCom" } diff --git a/packages/web/i18n/en/publish.json b/packages/web/i18n/en/publish.json index 8f808e95db57..1cbec2435df4 100644 --- a/packages/web/i18n/en/publish.json +++ b/packages/web/i18n/en/publish.json @@ -1,45 +1,46 @@ { - "app_key_tips": "These keys are already linked to the current application. Check the documentation for detailed usage.", - "basic_info": "Basic Info", - "config": "Visibility configuration", - "copy_link_hint": "Copy the link below to the specified location", - "create_api_key": "Create New Key", - "create_link": "Create Link", - "edit_api_key": "Edit Key Details", - "edit_feishu_bot": "Edit Feishu Bot", + "app_key_tips": "These keys have already been associated with the current app ID. For details about how to use them, see the documentation.", + "basic_info": "Basics", + "config": "Visibility Settings", + "copy_link_hint": "Copy the following link and paste it to the specified location.", + "create_api_key": "Create key", + "create_link": "Create link", + "edit_api_key": "Edit key", + "edit_feishu_bot": "Edit Feishu bot", "edit_link": "Edit", "feishu_api": "Feishu API", - "feishu_bot": "Feishu Bot", - "feishu_bot_desc": "Connect to Feishu Bot directly via API", - "key_alias": "Key alias, for display only", - "key_tips": "You can use the API key to access specific interfaces (cannot access the application, use the in-app API key for that)", - "link_name": "Share Link Name", - "new_feishu_bot": "add_new Feishu Bot", - "official_account.create_modal_title": "Create WeChat Official Account Integration", - "official_account.desc": "Connect to WeChat Official Account directly via API", - "official_account.edit_modal_title": "Edit WeChat Official Account Integration", - "official_account.name": "WeChat Official Account Integration", - "official_account.params": "WeChat Official Account Parameters", - "private_config": "Visibility configuration", + "feishu_bot": "Feishu bot", + "feishu_bot_desc": "Connect to Feishu bot via API.", + "key_alias": "Alias of the key for display only", + "key_tips": "You can use an API key to access certain APIs. (Accessing apps requires in-app API keys.)", + "link_name": "Name of the link to share", + "new_feishu_bot": "Create Feishu bot", + "official_account.create_modal_title": "Create WeChat official account connection", + "official_account.desc": "Connect to WeChat official account via API.", + "official_account.edit_modal_title": "Edit WeChat official account connection", + "official_account.name": "WeChat official account connection", + "official_account.wechat": "WeChat official account", + "official_account.params": "WeChat official account parameters", + "private_config": "Visibility Settings", "publish_name": "Name", - "qpm_is_empty": "QPM cannot be empty", - "qpm_tips": "Maximum number of queries per minute per IP", + "qpm_is_empty": "QPM is required.", + "qpm_tips": "The maximum number of questions allowed per minute from an IP address", "request_address": "Request URL", - "show_node": "real-time running status", - "show_share_link_modal_title": "Get Started", - "token_auth": "Token Authentication", - "token_auth_tips": "Token authentication server URL. If provided, a request will be sent to the specified server for authentication before each conversation.", - "token_auth_use_cases": "View Token Authentication Guide", + "show_node": "Realtime running status", + "show_share_link_modal_title": "Details", + "token_auth": "Authentication", + "token_auth_tips": "Authentication server address", + "token_auth_use_cases": "View authentication guide", "wecom.api": "WeCom API", - "wecom.bot": "WeCom Bot", - "wecom.bot_desc": "Connect to WeCom Bot directly via API", - "wecom.create_modal_title": "Create WeCom Bot", - "wecom.edit_modal_title": "Edit WeCom Bot", - "wecom.title": "Publish to WeCom Bot", - "dingtalk.bot": "DingTalk Bot", - "dingtalk.bot_desc": "Connect to DingTalk Bot directly via API", - "dingtalk.create_modal_title": "Create DingTalk Bot", - "dingtalk.edit_modal_title": "Edit DingTalk Bot", - "dingtalk.title": "Publish to DingTalk Bot", + "wecom.bot": "WeCom bot", + "wecom.bot_desc": "Connect to WeCom bot via API.", + "wecom.create_modal_title": "Create WeCom bot", + "wecom.edit_modal_title": "Edit WeCom bot", + "wecom.title": "WeCom bots", + "dingtalk.bot": "DingTalk bot", + "dingtalk.bot_desc": "Connect to DingTalk bot via API.", + "dingtalk.create_modal_title": "Create DingTalk bot", + "dingtalk.edit_modal_title": "Edit DingTalk bot", + "dingtalk.title": "DingTalk bots", "dingtalk.api": "DingTalk API" } diff --git a/packages/web/i18n/en/user.json b/packages/web/i18n/en/user.json index 5dae7009677f..e578e1e54edf 100644 --- a/packages/web/i18n/en/user.json +++ b/packages/web/i18n/en/user.json @@ -1,116 +1,116 @@ { "bill.balance": "Balance", - "bill.buy_plan": "Purchase Plan", - "bill.contact_customer_service": "Contact Support", - "bill.conversion": "Conversion", - "bill.convert_error": "Conversion Failed", - "bill.convert_success": "Conversion Successful", - "bill.current_token_price": "Current Token Price", - "bill.not_need_invoice": "Balance payment, invoice not available", - "bill.price": "Price", - "bill.renew_plan": "Renew Plan", - "bill.standard_valid_tip": "Plan Usage Rules: Higher-level plans will be used first. Unused plans will be activated later.", - "bill.token_expire_1year": "Tokens are valid for one year", - "bill.tokens": "Tokens", - "bill.use_balance": "Use Balance", - "bill.use_balance_hint": "Due to system upgrade, the 'Auto-renewal from balance' mode is canceled, and the balance recharge option is closed. Your balance can be used to purchase tokens.", - "bill.valid_time": "Effective Time", - "bill.you_can_convert": "You can convert", - "bill.yuan": "Yuan", - "delete.admin_failed": "Failed to Delete Admin", - "delete.admin_success": "Admin Deleted Successfully", - "delete.failed": "Delete failed", - "delete.success": "Delete successfully", + "bill.buy_plan": "Purchase plan", + "bill.contact_customer_service": "Contact Customer Service", + "bill.conversion": "Redeem", + "bill.convert_error": "Failed", + "bill.convert_success": "Redeemed successfully.", + "bill.current_token_price": "Current price for credit", + "bill.not_need_invoice": "Unable to issue an invoice because the payment was deducted from the balance.", + "bill.price": "Unit price", + "bill.renew_plan": "Renew plan", + "bill.standard_valid_tip": "Plan usage rules: Higher-level plans are used first. Unused lower-level plans will be applied later.", + "bill.token_expire_1year": "Credits are valid for 1 year.", + "bill.tokens": "Credits", + "bill.use_balance": "Pay by balance", + "bill.use_balance_hint": "Due to a system upgrade, renewal with auto reduction from balance has been disabled, and balance top-up is no longer available. Your balance can still be used to purchase credits.", + "bill.valid_time": "Valid since", + "bill.you_can_convert": "You can redeem", + "bill.yuan": "CNY", + "delete.admin_failed": "Failed to delete the admin.", + "delete.admin_success": "Admin deleted successfully.", + "delete.failed": "Failed", + "delete.success": "Deleted successfully.", "has_chosen": "Selected", - "login.Dingtalk": "DingTalk Login", - "login.error": "Login Error", - "login.password_condition": "Password can be up to 60 characters", - "login.success": "Login Successful", - "manage_team": "Manage team", + "login.Dingtalk": "DingTalk login", + "login.error": "Login error occurred.", + "login.password_condition": "Password cannot exceed 60 characters.", + "login.success": "Login successful.", + "manage_team": "Teams", "name": "Name", - "new_password": "New Password", - "notification.remind_owner_bind": "Please remind the creator to bind a notification account", - "operations": "Actions", - "owner": "owner", - "password.code_required": "Verification Code Required", - "password.code_send_error": "Failed to Send Verification Code", - "password.code_sended": "Verification Code Sent", - "password.confirm": "Confirm Password", - "password.email_phone_error": "Invalid Email/Phone Number Format", - "password.email_phone_void": "Email/Phone Number Cannot Be Empty", - "password.not_match": "Passwords Do Not Match", - "password.password_condition": "Password must be between 4 and 20 characters", - "password.password_required": "Password Cannot Be Empty", - "password.retrieve": "Retrieve Password", - "password.retrieved": "Password Retrieved", - "password.retrieved_account": "Retrieve {{account}} Account", - "password.to_login": "Go to Login", - "password.verification_code": "Verification Code", - "permission.Add": "Add Permissions", + "new_password": "New password", + "notification.remind_owner_bind": "Please remind the creator to specify an account to receive notifications.", + "operations": "Operation", + "owner": "Owner", + "password.code_required": "Verification code is required.", + "password.code_send_error": "Error occurred while sending the verification code.", + "password.code_sended": "Verification code sent successfully.", + "password.confirm": "Confirm password", + "password.email_phone_error": "Email address or mobile number is invalid.", + "password.email_phone_void": "Email address and mobile number are required.", + "password.not_match": "Passwords do not match.", + "password.password_condition": "Password must contain 4-20 characters.", + "password.password_required": "Password is required.", + "password.retrieve": "Reset password", + "password.retrieved": "Password reset successfully.", + "password.retrieved_account": "Retrieve account ({{account}})", + "password.to_login": "Log in", + "password.verification_code": "Code", + "permission.Add": "Add permission", "permission.Manage": "Admin", - "permission.Manage tip": "Team admin with full permissions", - "permission.Read": "Read Only", - "permission.Read desc": "Members can only read related resources, cannot create new resources", - "permission.Write": "Write", - "permission.Write tip": "In addition to read access, can create new resources", - "permission.only_collaborators": "Collaborators Only", - "permission.team_read": "Team Read Access", - "permission.team_write": "Team Write Access", - "permission_add_tip": "After adding, you can check the permissions for them.", - "permission_des.manage": "Can create resources, invite, and delete members", - "permission_des.read": "Members can only read related resources and cannot create new resources.", - "permission_des.write": "In addition to readable resources, you can also create new resources", - "permissions": "Permissions", - "personal_information": "Me", + "permission.Manage tip": "The team administrator has full permissions.", + "permission.Read": "Read-only", + "permission.Read desc": "Members can only read related resources but cannot create new ones.", + "permission.Write": "Read and write", + "permission.Write tip": "Members can read and create resources.", + "permission.only_collaborators": "Accessible to collaborators only", + "permission.team_read": "Accessible to team", + "permission.team_write": "Editable by team", + "permission_add_tip": "After the member is added, you can grant permissions to it.", + "permission_des.manage": "Admins can create resources and invite or delete members.", + "permission_des.read": "Members can only read related resources but cannot create new ones.", + "permission_des.write": "Members can read and create resources.", + "permissions": "Permission", + "personal_information": "Profile", "personalization": "Personalization", - "promotion_records": "Promotion", - "register.confirm": "Confirm Registration", - "register.register_account": "Register {{account}} Account", - "register.success": "Registration Successful", - "register.to_login": "Already have an account? Go to Login", - "search_group_org_user": "Search member/group/org name", - "search_user": "Search Username", - "sso_auth_failed": "SSO authentication failed", - "synchronization.button": "Sync Now", - "synchronization.placeholder": "Enter Sync Tag", - "synchronization.title": "Enter the sync tag link and click the sync button to synchronize", - "team.Add manager": "Add Admin", - "team.Confirm Invite": "Confirm invitation", - "team.Create Team": "Create new team", - "team.Invite Member Failed Tip": "An exception occurred when inviting members", - "team.Invite Member Result Tip": "Invitation result prompt", - "team.Invite Member Success Tip": "Invite members to complete\n\nSuccess: {{success}} people\n\nInvalid username: {{inValid}}\n\nAlready in team: {{inTeam}}", - "team.Set Name": "Give the team a name", + "promotion_records": "Promotion Records", + "register.confirm": "Confirm", + "register.register_account": "Register {{account}} account", + "register.success": "Registered successfully.", + "register.to_login": "Have an account? Sign in", + "search_group_org_user": "Member, department, group name", + "search_user": "Username", + "sso_auth_failed": "SSO authentication failed.", + "synchronization.button": "Sync now", + "synchronization.placeholder": "Tag to be synced", + "synchronization.title": "To sync tags, enter the tag sync link and click Sync now.", + "team.Add manager": "Add admin", + "team.Confirm Invite": "Confirm", + "team.Create Team": "Create team", + "team.Invite Member Failed Tip": "Error occurred while inviting members.", + "team.Invite Member Result Tip": "Message", + "team.Invite Member Success Tip": "Invitation completed.\nMembers joined successfully: {{success}}\nInvalid usernames: {{inValid}}\nMembers already in the team: {{inTeam}}", + "team.Set Name": "Name the team", "team.Team Name": "Team name", - "team.Update Team": "Update team information", - "team.add_collaborator": "Add Collaborator", - "team.add_permission": "Add permissions", + "team.Update Team": "Edit team", + "team.add_collaborator": "Add collaborator", + "team.add_permission": "Add permission", "team.add_writer": "Add writable members", - "team.avatar_and_name": "avatar", - "team.belong_to_group": "Member group", - "team.group.avatar": "Group avatar", + "team.avatar_and_name": "Icon & name", + "team.belong_to_group": "Group", + "team.group.avatar": "Group profile image", "team.group.create": "Create group", - "team.group.create_failed": "Failed to create group", + "team.group.create_failed": "Failed to create the group.", "team.group.default_group": "Default group", - "team.group.delete_confirm": "Confirm to delete group?", + "team.group.delete_confirm": "Are you sure you want to delete the group?", "team.group.edit": "Edit group", - "team.group.edit_info": "Edit information", - "team.group.group": "group", - "team.group.keep_admin": "Keep administrator rights", - "team.group.manage_member": "Managing members", - "team.group.manage_tip": "Can manage members, create groups, manage all groups, assign permissions to groups and members", - "team.group.members": "member", + "team.group.edit_info": "Edit", + "team.group.group": "Group", + "team.group.keep_admin": "Retain admin permissions", + "team.group.manage_member": "Members", + "team.group.manage_tip": "Manage members, create and manage groups, and assign permissions to groups and members.", + "team.group.members": "Member", "team.group.name": "Group name", - "team.group.permission_tip": "Members with individually configured permissions will follow the individual permission configuration and will no longer be affected by group permissions.\n\nIf a member is in multiple permission groups, the member's permissions are combined.", - "team.group.role.admin": "administrator", - "team.group.role.member": "member", - "team.group.role.owner": "owner", - "team.group.set_as_admin": "Set as administrator", - "team.group.toast.can_not_delete_owner": "Owner cannot be deleted, please transfer first", - "team.group.transfer_owner": "transfer owner", - "team.manage_collaborators": "Manage Collaborators", - "team.no_collaborators": "No Collaborators", - "team.org.org": "Organization", - "team.write_role_member": "Write Permission", - "team.collaborator.added": "Added" + "team.group.permission_tip": "Members with separately configured permissions will not be affected by group permissions.\nIf a member is added to multiple permission groups, their permissions are combined.", + "team.group.role.admin": "Admin", + "team.group.role.member": "Member", + "team.group.role.owner": "Owner", + "team.group.set_as_admin": "Set as admin", + "team.group.toast.can_not_delete_owner": "The owner cannot be deleted. Please transfer ownership first.", + "team.group.transfer_owner": "Transfer ownership", + "team.manage_collaborators": "Manage collaborators", + "team.no_collaborators": "No collaborators available.", + "team.org.org": "Department", + "team.write_role_member": "Write permission", + "team.collaborator.added": "Added successfully." } diff --git a/packages/web/i18n/en/workflow.json b/packages/web/i18n/en/workflow.json index b9b0f651f77c..5b66758913d0 100644 --- a/packages/web/i18n/en/workflow.json +++ b/packages/web/i18n/en/workflow.json @@ -1,225 +1,225 @@ { "Array_element": "Array element", - "Array_element_index": "Index", - "Click": "Click ", + "Array_element_index": "Subscript", + "Click": "Click to ", "Code": "Code", - "Confirm_sync_node": "It will be updated to the latest node configuration and fields that do not exist in the template will be deleted (including all custom fields).\n\nIf the fields are complex, it is recommended that you copy a node first and then update the original node to facilitate parameter copying.", - "Drag": "Drag ", - "Node.Open_Node_Course": "Open node course", - "Node_variables": "Node variables", - "Quote_prompt_setting": "Quote prompt", + "Confirm_sync_node": "Nodes will be updated to the latest configuration. Fields that do not exist in the template (including all custom fields) will be deleted.\nIf the fields are complex, it is recommended to copy the node before updating it.", + "Drag": "Drag to ", + "Node.Open_Node_Course": "Guide", + "Node_variables": "Node variable", + "Quote_prompt_setting": "Configure prompt", "Variable.Variable type": "Variable type", "Variable_name": "Variable name", - "add_new_input": "add_new Input", - "add_new_output": "New output", - "append_application_reply_to_history_as_new_context": "Append the application's reply to the history as new context", - "application_call": "Application Call", - "assigned_reply": "Assigned Reply", - "auth_tmb_id": "Auth member", - "auth_tmb_id_tip": "After it is turned on, when the application is released to the outside world, the knowledge base will be filtered based on whether the user has permission to the knowledge base.\n\nIf it is not enabled, the configured knowledge base will be searched directly without permission filtering.", - "auto_align": "Automatic alignment", - "can_not_loop": "This node can't loop.", - "choose_another_application_to_call": "Select another application to call", - "classification_result": "Classification Result", - "click_to_change_reference": "Click to switch input mode", - "click_to_change_value": "Click to switch reference mode", - "code.Reset template": "Reset Template", - "code.Reset template confirm": "Confirm reset code template? This will reset all inputs and outputs to template values. Please save your current code.", - "code.Switch language confirm": "Switching the language will reset the code, will it continue?", - "code_execution": "Code Sandbox", - "collection_metadata_filter": "Collection Metadata Filter", - "complete_extraction_result": "Complete Extraction Result", - "complete_extraction_result_description": "A JSON string, e.g., {\"name\":\"YY\",\"Time\":\"2023/7/2 18:00\"}", - "concatenation_result": "Concatenation Result", - "concatenation_text": "Concatenation Text", - "condition_checker": "Condition", - "confirm_delete_field_tip": "Confirm delete this field?", - "contains": "Contains", - "content_to_retrieve": "Content to Retrieve", - "content_to_search": "Content to Search", + "add_new_input": "Add input", + "add_new_output": "Add output", + "append_application_reply_to_history_as_new_context": "Combine the response from the app with the chat history to output a new context.", + "application_call": "App call", + "assigned_reply": "Specified answer", + "auth_tmb_id": "User verification", + "auth_tmb_id_tip": "If enabled, a knowledge base will not be used if the user does not have access permissions to it when the app is published.\nIf disabled, the system will search from the selected knowledge bases without permission-based filtering.", + "auto_align": "Auto align", + "can_not_loop": "The node does not support circular nesting.", + "choose_another_application_to_call": "Select another app to call", + "classification_result": "Classification result", + "click_to_change_reference": "Click to switch to input mode", + "click_to_change_value": "Click to switch to variable reference mode", + "code.Reset template": "Restore template", + "code.Reset template confirm": "Are you sure you want to restore the code template? All inputs and outputs will be reset to the default values. Please save the current code first.", + "code.Switch language confirm": "Changing the language will reset the code. Would you like to proceed?", + "code_execution": "Code running", + "collection_metadata_filter": "Collection metadata filtering", + "complete_extraction_result": "Complete extraction result", + "complete_extraction_result_description": "A JSON string. Example: {\"name\":\"YY\",\"Time\":\"2023/7/2 18:00\"}", + "concatenation_result": "Splicing result", + "concatenation_text": "Text for splicing", + "condition_checker": "Judger", + "confirm_delete_field_tip": "Are you sure you want to delete this field?", + "contains": "contains", + "content_to_retrieve": "Content to be searched", + "content_to_search": "Content to be searched", "contextMenu.addComment": "Add comment", "context_menu.add_comment": "Add comment", - "create_link_error": "Error creating link", - "custom_feedback": "Custom Feedback", - "custom_input": "Custom Input", + "create_link_error": "Error occurred while creating the link.", + "custom_feedback": "Custom feedback", + "custom_input": "Custom input", "dataset_quote_role": "Role", - "dataset_quote_role_system_option_desc": "Historical records should be consistent first (recommended)", - "dataset_quote_role_tip": "When set to System, the knowledge base reference content will be placed in the system message, which can ensure the continuity of the history record, but the constraint effect may not be good.\n\nWhen set to User, the knowledge base reference content will be placed in the user message, and the {{question}} variable location needs to be specified. \nIt will have a certain impact on the consistency of historical records, but usually the constraint effect is better.", - "dataset_quote_role_user_option_desc": "Strong constraints take precedence", - "dynamic_input_description": "Receive the output value of the previous node as a variable, which can be used by Laf request parameters.", - "edit_input": "Edit Input", + "dataset_quote_role_system_option_desc": "Coherence prioritized (recommended)", + "dataset_quote_role_tip": "System: Knowledge base references will be included in system messages for coherence, but constraints may be less effective. Additional debugging may be needed.\nUser: Knowledge base references will be included in user messages, and the position of the {{question}} variable must be specified. This may slightly affect coherence, but constraints can be enforced more effectively.", + "dataset_quote_role_user_option_desc": "Constraints prioritized", + "dynamic_input_description": "Output values from the upstream nodes are received as variables, which can be used in LAF request parameters.", + "edit_input": "Edit input", "edit_output": "Edit output", - "end_with": "Ends With", - "enter_comment": "Enter comment", - "error_catch": "Error catch", - "error_info_returns_empty_on_success": "Error information of code execution, returns empty on success", - "error_text": "Error text", - "execute_a_simple_script_code_usually_for_complex_data_processing": "Execute a simple script code, usually for complex data processing.", - "execute_different_branches_based_on_conditions": "Execute different branches based on conditions.", - "execution_error": "Execution Error", - "extraction_requirements_description": "Extraction Requirements Description", - "extraction_requirements_description_detail": "Provide AI with some background knowledge or requirements to guide it in completing the task better.\\nThis input box can use global variables.", - "extraction_requirements_placeholder": "For example: 1. The current time is: {{cTime}}. \nYou are a laboratory reservation assistant. Your task is to help users make laboratory reservations and obtain the corresponding reservation information from the text.\n\n2. You are the Google Search Assistant and need to extract appropriate search terms from text.", - "feedback_text": "Feedback Text", - "field_description": "Field Description", - "field_description_placeholder": "Describe the function of this input field. If it is a tool call parameter, this description will affect the quality of the model generation.", - "field_name_already_exists": "Field name already exists", + "end_with": "ends with", + "enter_comment": "Comment", + "error_catch": "Error capture", + "error_info_returns_empty_on_success": "Error message returned when code execution error occurs. If the code is executed successfully, a null value is returned.", + "error_text": "Error details", + "execute_a_simple_script_code_usually_for_complex_data_processing": "Runs a short script, typically for complex data processing.", + "execute_different_branches_based_on_conditions": "Executes a branch based on the specified conditions.", + "execution_error": "Running error", + "extraction_requirements_description": "Extraction requirement", + "extraction_requirements_description_detail": "Provide relevant context or prompts to guide extraction. \nGlobal variables are supported.", + "extraction_requirements_placeholder": "Example: 1. Current time: {{cTime}} You are a lab booking assistant and need to extract lab booking information from the text.\n2. You are a Google search assistant and need to extract suitable keywords for Google searches from the text.", + "feedback_text": "Feedback", + "field_description": "Description", + "field_description_placeholder": "Describe the purpose of the variable. If it is used for tool call, the description will affect the output quality of the model.", + "field_name_already_exists": "The field name already exists.", "field_required": "Required", - "field_used_as_tool_input": "Used as Tool Call Parameter", - "filter_description": "Currently supports filtering by tags and creation time. Fill in the format as follows:\n{\n \"tags\": {\n \"$and\": [\"Tag 1\",\"Tag 2\"],\n \"$or\": [\"When there are $and tags, and is effective, or is not effective\"]\n },\n \"createTime\": {\n \"$gte\": \"YYYY-MM-DD HH:mm format, collection creation time greater than this time\",\n \"$lte\": \"YYYY-MM-DD HH:mm format, collection creation time less than this time, can be used with $gte\"\n }\n}", - "find_tip": "Find node ctrl f", - "find_tip_mac": "Find node ⌘ f", + "field_used_as_tool_input": "Used as a tool call parameter", + "filter_description": "Knowledge bases can be filtered based on tags and the creation time. Format: \n{\n \"tags\": {\n \"$and\": [\"Tag 1\",\"Tag 2\"],\n \"$or\": [\"When the $and tags exist, the AND condition applies and the OR condition is ignored.\"]\n },\n \"createTime\": {\n \"$gte\": \"Format: YYYY-MM-DD HH:mm. The collection's creation time must be later than this time.\"\n \"$lte\": \"Format: YYYY-MM-DD HH:mm. The collection's creation time must be earlier than this time. $lte and $gte can be used together.\"\n }\n}", + "find_tip": "Find node (Ctrl + F)", + "find_tip_mac": "Find node (⌘ + F)", "foldAll": "Collapse all", - "form_input_result": "User complete input result", - "form_input_result_tip": "an object containing the complete result", - "full_field_extraction": "Full Field Extraction", - "full_field_extraction_description": "Returns true when all fields are fully extracted (success includes model extraction or using default values)", - "full_response_data": "Full Response Data", - "greater_than": "Greater Than", - "greater_than_or_equal_to": "Greater Than or Equal To", - "http_body_placeholder": "Similar syntax to APIFox, variable selection can be activated via /. \nString variables need to be enclosed in double quotes, other types of variables do not need to be enclosed in double quotes. \nPlease strictly check whether it conforms to JSON format.", + "form_input_result": "Complete user input", + "form_input_result_tip": "An object containing the full result", + "full_field_extraction": "Complete field extraction", + "full_field_extraction_description": "Returns true if all fields are filled in (either from model extraction or default values)", + "full_response_data": "Complete response", + "greater_than": "is greater than", + "greater_than_or_equal_to": "is greater than or equal to", + "http_body_placeholder": "Use a syntax similar to APIFox. Enter a slash (/) to select a variable. Only string variables must be enclosed in double quotation marks. Ensure the string is in JSON format.", "http_extract_output": "Output field extraction", - "http_extract_output_description": "Specified fields in the response value can be extracted through JSONPath syntax", - "http_raw_response_description": "Raw HTTP response. Only accepts string or JSON type response data.", - "http_request": "HTTP", - "ifelse.Input value": "Input Value", - "ifelse.Select value": "Select Value", - "input_description": "Field Description", - "input_type_multiple_select": "Multiple selection boxes", - "input_variable_list": "Type / to invoke variable list", - "intro_assigned_reply": "This module can directly reply with a specified content. Commonly used for guidance or prompts. Non-string content will be converted to string for output.", - "intro_custom_feedback": "When this module is triggered, a feedback will be added to the current conversation record. It can be used to automatically record conversation effects, etc.", - "intro_custom_plugin_output": "Custom configuration of external output. When using plugins, only the custom configured output is exposed.", - "intro_http_request": "Can send an HTTP request to perform more complex operations (network search, database query, etc.)", - "intro_knowledge_base_search_merge": "Can merge multiple Dataset search results for output. Uses RRF merging method for final sorting output.", - "intro_laf_function_call": "Can call cloud functions under the Laf account.", - "intro_loop": "Input an array, iterate through the array and use each array element as an input element to execute the workflow.", - "intro_plugin_input": "Can configure what inputs the plugin needs and use these inputs to run the plugin.", - "intro_question_classification": "Determine the type of question based on the user's history and current question. Multiple question types can be added. Below is a template example:\nType 1: Greeting\nType 2: Questions about product 'usage'\nType 3: Questions about product 'purchase'\nType 4: Other questions", - "intro_question_optimization": "Using question optimization can improve the accuracy of Dataset searches during continuous conversations. After using this function, AI will first construct one or more new search terms based on the context, which are more conducive to Dataset searches. This module is already built into the Dataset search module. If you only perform a single Dataset search, you can directly use the built-in completion function of the Dataset.", - "intro_text_concatenation": "Can process and output fixed or incoming text. Non-string type data will be converted to string type.", - "intro_text_content_extraction": "Can extract specified data from text, such as SQL statements, search keywords, code, etc.", - "intro_tool_call_termination": "This module needs to be configured for tool calls. When this module is executed, the current tool call will be forcibly terminated, and AI will no longer answer questions based on the tool call results.", - "intro_tool_params_config": "This module needs to be used with tool calls. \nYou can customize tool call parameters and pass them to downstream nodes for use.", - "is_empty": "Is Empty", - "is_equal_to": "Is Equal To", - "is_not_empty": "Is Not Empty", - "is_not_equal": "Is Not Equal", - "is_tool_output_label": "as tool response", - "judgment_result": "Judgment Result", - "knowledge_base_reference": "Dataset Reference", - "knowledge_base_search_merge": "Dataset Merge", - "laf_function_call_test": "Laf Function Call (Test)", - "length_equal_to": "Length Equal To", - "length_greater_than": "Length Greater Than", - "length_greater_than_or_equal_to": "Length Greater Than or Equal To", - "length_less_than": "Length Less Than", - "length_less_than_or_equal_to": "Length Less Than or Equal To", - "length_not_equal_to": "Length Not Equal To", - "less_than": "Less Than", - "less_than_or_equal_to": "Less Than or Equal To", - "loop": "Batch Run", - "loop_body": "loop body", + "http_extract_output_description": "Extract specific fields from the response using the JSONPath syntax.", + "http_raw_response_description": "Raw HTTP response. The response data must be a string or in JSON format.", + "http_request": "HTTP request", + "ifelse.Input value": "Input", + "ifelse.Select value": "Select", + "input_description": "Description", + "input_type_multiple_select": "Checkbox", + "input_variable_list": "Enter a slash (/) to select a variable.", + "intro_assigned_reply": "This module supports replies with a specified message, often used as guidance or prompts. Non-string content is automatically converted to a string.", + "intro_custom_feedback": "When triggered, this module adds feedback to the current conversation, useful for automatically tracking response quality.", + "intro_custom_plugin_output": "Customize external output. When a plugin is used, only the custom output is exposed.", + "intro_http_request": "Send an HTTP request to perform a more complex action (such as online search or database query).", + "intro_knowledge_base_search_merge": "Merge search results from multiple knowledge bases and sort them using the RRF method to provide output.", + "intro_laf_function_call": "Call cloud functions under the LAF account.", + "intro_loop": "Input an array and iterate over each element to execute the workflow.", + "intro_plugin_input": "Configure inputs required for running a plugin.", + "intro_question_classification": "Identify the question type based on the current question and the user's chat history. You can add multiple question types. Example:\nType 1: Greeting\nType 2: Product usage\nType 3: Product purchase\nType 4: Other", + "intro_question_optimization": "Use the question optimization feature to increase the accuracy of knowledge base searches during continuous chats. The model will generate one or multiple new keywords based on the context to optimize the search results. This feature is built into the knowledge base search module and can also be used for a single search.", + "intro_text_concatenation": "Processes fixed or input text to provide an output. Non-string data will be automatically converted into strings.", + "intro_text_content_extraction": "Extract specific data from text, such as SQL statements, search keywords, or code.", + "intro_tool_call_termination": "This module works with tool calling. When this module is executed, the tool call is terminated, and no replies are provided based on the tool call result.", + "intro_tool_params_config": "This module works with tool calling. Customize tool call parameters and pass them to downstream nodes.", + "is_empty": "is null", + "is_equal_to": "is", + "is_not_empty": "is not null", + "is_not_equal": "is not", + "is_tool_output_label": "Used as response", + "judgment_result": "Result", + "knowledge_base_reference": "Knowledge base reference", + "knowledge_base_search_merge": "Reference merging", + "laf_function_call_test": "LAF function call (Beta)", + "length_equal_to": "whose length is", + "length_greater_than": "whose length is longer than", + "length_greater_than_or_equal_to": "whose length is not shorter than", + "length_less_than": "whose length is shorter than", + "length_less_than_or_equal_to": "whose length is not longer than", + "length_not_equal_to": "whose length is not", + "less_than": "is smaller than", + "less_than_or_equal_to": "is smaller than or equal to", + "loop": "Bulk execution", + "loop_body": "Loop body", "loop_end": "End", - "loop_input_array": "array", - "loop_result": "Array execution results", + "loop_input_array": "Array", + "loop_result": "Array execution result", "loop_start": "Start", - "max_dialog_rounds": "Maximum Number of Dialog Rounds", - "max_tokens": "Maximum Tokens", - "mouse_priority": "Mouse first\n- Press the left button to drag the canvas\n- Hold down shift and left click to select batches", - "new_context": "New Context", + "max_dialog_rounds": "Maximum number of chats remembered", + "max_tokens": "Max tokens", + "mouse_priority": "Mouse operations\n- Click and hold to drag the canvas.\n- Press Shift and click to select multiple objects.", + "new_context": "New context", "next": "Next", - "no_match_node": "No results", - "no_node_found": "No node was not found", - "not_contains": "Does Not Contain", - "only_the_reference_type_is_supported": "Only reference type is supported", - "optional_value_type": "Optional Value Type", - "optional_value_type_tip": "You can specify one or more data types. When dynamically adding fields, users can only select the configured types.", - "pan_priority": "Touchpad first\n- Click to batch select\n- Move the canvas with two fingers", - "pass_returned_object_as_output_to_next_nodes": "Pass the object returned in the code as output to the next nodes. The variable name needs to correspond to the return key.", - "please_enter_node_name": "Enter the node name", - "plugin.Instruction_Tip": "You can configure an instruction to explain the purpose of the plugin. This instruction will be displayed each time the plugin is used. Supports standard Markdown syntax.", - "plugin.Instructions": "Instructions", + "no_match_node": "No result", + "no_node_found": "No node found.", + "not_contains": "does not contain", + "only_the_reference_type_is_supported": "Reference type only", + "optional_value_type": "Available data types", + "optional_value_type_tip": "You can specify one or multiple data types. When users add fields, they can only choose from these data types.", + "pan_priority": "Touchpad operations\n- Tap to select multiple objects.\n- Tap with two fingers to drag the canvas.", + "pass_returned_object_as_output_to_next_nodes": "Use the return value as the output and pass it to the next node. The variable name must match the key in the return value.", + "please_enter_node_name": "Node name", + "plugin.Instruction_Tip": "You can add a description to explain the purpose of the plugin. The description will be displayed each time before the plugin is used. Standard Markdown syntax is supported.", + "plugin.Instructions": "Guide", "plugin.global_file_input": "File links (deprecated)", "plugin_file_abandon_tip": "Plugin global file upload has been deprecated, please adjust it as soon as possible. \nRelated functions can be achieved through plug-in input and adding image type input.", - "plugin_input": "Plugin Input", - "plugin_output_tool": "When the plug-in is executed as a tool, whether this field responds as a result of the tool", + "plugin_input": "Plugin input", + "plugin_output_tool": "Whether the field is used as the output when the plugin runs as a tool.", "previous": "Previous", - "question_classification": "Classify", - "question_optimization": "Query extension", - "quote_content_placeholder": "The structure of the reference content can be customized to better suit different scenarios. \nSome variables can be used for template configuration\n\n{{q}} - main content\n\n{{a}} - auxiliary data\n\n{{source}} - source name\n\n{{sourceId}} - source ID\n\n{{index}} - nth reference", - "quote_content_tip": "The structure of the reference content can be customized to better suit different scenarios. Some variables can be used for template configuration:\n\n{{id}} - the unique id of the reference data\n{{q}} - main content\n{{a}} - auxiliary data\n{{source}} - source name\n{{sourceId}} - source ID\n{{index}} - nth reference\n\nThey are all optional and the following are the default values:\n\n{{default}}", - "quote_num": "Dataset", - "quote_prompt_tip": "You can use {{quote}} to insert a quote content template and {{question}} to insert a question (Role=user).\n\nThe following are the default values:\n\n{{default}}", - "quote_role_system_tip": "Please note that the {{question}} variable is removed from the \"Quote Template Prompt Words\"", - "quote_role_user_tip": "Please pay attention to adding the {{question}} variable in the \"Quote Template Prompt Word\"", - "raw_response": "Raw Response", - "reasoning_text": "Thinking text", + "question_classification": "Question classifier", + "question_optimization": "Question optimization", + "quote_content_placeholder": "Customize the structure of the referenced content to fit different scenarios. Use variables to configure the template.\n{{q}} - Main content\n{{a}} - Extra data\n{{source}} - Source name\n{{sourceId}} - Source ID\n{{index}} - Sequence number of reference", + "quote_content_tip": "Customize the structure of the referenced content to fit different scenarios. Use variables to configure the template.\n{{id}} - Unique reference ID\n{{q}} - Main content\n{{a}} - Extra data\n{{source}} - Source name\n{{sourceId}} - Source ID\n{{index}} - Sequence number of reference\nThese variables are all optional. The default values are as follows: {{default}}\n{{default}}", + "quote_num": "Reference", + "quote_prompt_tip": "Use {{quote}} to insert a reference template, and {{question}} to insert a question (Role = User).\nDefault values: \n{{default}}", + "quote_role_system_tip": "Please remove {{question}} from the prompt.", + "quote_role_user_tip": "Please add {{question}} to the prompt.", + "raw_response": "Original response", + "reasoning_text": "Reasoning process", "regex": "Regex", - "reply_text": "Reply Text", - "request_error": "request_error", - "response.Code log": "Code Log", - "response.Custom inputs": "Custom Inputs", - "response.Custom outputs": "Custom Outputs", - "response.Error": "Error", - "response.Read file result": "Read File Result", - "response.read files": "Read Files", - "select_an_application": "Select an Application", - "select_another_application_to_call": "You can choose another application to call", - "select_default_option": "Select the default value", - "special_array_format": "Special array format, returns an empty array when the search result is empty.", - "start_with": "Starts With", - "support_code_language": "Support import list: pandas,numpy", - "target_fields_description": "A target field consists of 'description' and 'key'. Multiple target fields can be extracted.", - "template.agent": "Agent", - "template.agent_intro": "Automatically select one or more functional blocks for calling through the AI model, or call plugins.", - "template.ai_chat": "AI Chat", - "template.ai_chat_intro": "AI Large Model Chat", - "template.dataset_search": "Dataset Search", - "template.dataset_search_intro": "Use 'semantic search' and 'full-text search' capabilities to find potentially relevant reference content from the 'Dataset'.", - "template.forbid_stream": "Forbid stream mode", - "template.forbid_stream_desc": "Forces the output mode of nested application streams to be disabled", + "reply_text": "Reply text", + "request_error": "Request error", + "response.Code log": "Log", + "response.Custom inputs": "Custom input", + "response.Custom outputs": "Custom output", + "response.Error": "Error details", + "response.Read file result": "Parsing result preview", + "response.read files": "Parsed document", + "select_an_application": "Select app", + "select_another_application_to_call": "You can select another app to call.", + "select_default_option": "Select", + "special_array_format": "Special array format. An empty array is returned when no results are found.", + "start_with": "starts with", + "support_code_language": "Libraries that can be imported in the code: pandas, numpy", + "target_fields_description": "A target field consists of the description and key. Multiple target fields can be extracted.", + "template.agent": "Tool call", + "template.agent_intro": "The model decides which tool to call.", + "template.ai_chat": "AI chat", + "template.ai_chat_intro": "Chat with an AI model.", + "template.dataset_search": "Knowledge base search", + "template.dataset_search_intro": "Uses semantic, full-text, and database search capabilities to search for reference materials related to the questions from the selected knowledge bases.", + "template.forbid_stream": "Disable output in streaming mode", + "template.forbid_stream_desc": "Force nested apps to run in non-streaming mode.", "template.plugin_output": "Plugin output", - "template.plugin_start": "Plugin start", + "template.plugin_start": "Plugin input", "template.system_config": "System", - "template.workflow_start": "Start", - "text_concatenation": "Text Editor", - "text_content_extraction": "Text Extract", - "text_to_extract": "Text to Extract", - "these_variables_will_be_input_parameters_for_code_execution": "These variables will be input parameters for code execution", - "to_add_node": "to add", - "to_connect_node": "to connect", - "tool.tool_result": "Tool operation results", - "tool_active_config": "Tool active", - "tool_active_config_type": "Tool activation: {{type}}", - "tool_call_termination": "Stop ToolCall", - "tool_custom_field": "Custom Tool", - "tool_field": " Tool Field Parameter Configuration", - "tool_input": "Tool Input", + "template.workflow_start": "Process startup", + "text_concatenation": "Text splicing", + "text_content_extraction": "Text extraction", + "text_to_extract": "Text for extraction", + "these_variables_will_be_input_parameters_for_code_execution": "These variables will be used as input parameters for code execution.", + "to_add_node": "add node", + "to_connect_node": "connect to node", + "tool.tool_result": "Tool running result", + "tool_active_config": "Activate tool", + "tool_active_config_type": "Tool activated: {{type}}", + "tool_call_termination": "Tool call termination", + "tool_custom_field": "Custom tool variable", + "tool_field": "Configure tool parameter", + "tool_input": "Tool parameter", "tool_params.enum_placeholder": "apple \npeach \nwatermelon", - "tool_params.enum_values": "Enum values", - "tool_params.enum_values_tip": "List the possible values for this field, one per line", + "tool_params.enum_values": "Enumeration values (optional)", + "tool_params.enum_values_tip": "Valid values for the field. One value per line.", "tool_params.params_description": "Description", - "tool_params.params_description_placeholder": "Name/Age/SQL statement..", + "tool_params.params_description_placeholder": "Name, age, SQL statement, etc.", "tool_params.params_name": "Name", "tool_params.params_name_placeholder": "name/age/sql", - "tool_params.tool_params_result": "Parameter configuration results", - "tool_raw_response_description": "The original response of the tool", - "trigger_after_application_completion": "Will be triggered after the application is fully completed", + "tool_params.tool_params_result": "Parameter configuration result", + "tool_raw_response_description": "Original response from tool", + "trigger_after_application_completion": "It will be triggered after the app is fully executed.", "unFoldAll": "Expand all", - "update_link_error": "Error updating link", - "update_specified_node_output_or_global_variable": "Can update the output value of a specified node or update global variables", + "update_link_error": "Error occurred while updating the link.", + "update_specified_node_output_or_global_variable": "Update output for a specified node or update global variables.", "use_user_id": "User ID", - "user_form_input_config": "Form configuration", - "user_form_input_description": "describe", - "user_form_input_name": "Name", - "user_question": "User Question", - "user_question_tool_desc": "User input questions (questions need to be improved)", + "user_form_input_config": "Template settings", + "user_form_input_description": "Description", + "user_form_input_name": "Title", + "user_question": "Question", + "user_question_tool_desc": "Question (optimization required)", "variable_description": "Variable description", - "variable_picker_tips": "Type node name or variable name to search", - "variable_update": "Variable Update", - "workflow.My edit": "My Edit", - "workflow.Switch_success": "Switch Successful", - "workflow.Team cloud": "Team Cloud", - "workflow.exit_tips": "Your changes have not been saved. 'Exit directly' will not save your edits." -} + "variable_picker_tips": "Enter a node name or variable name.", + "variable_update": "Variable update", + "workflow.My edit": "My edits", + "workflow.Switch_success": "Switched successfully.", + "workflow.Team cloud": "Historical versions", + "workflow.exit_tips": "Your changes are not saved and will be discarded if you exit directly." +} \ No newline at end of file diff --git a/packages/web/i18n/i18next.d.ts b/packages/web/i18n/i18next.d.ts index b831611dabdd..ded2595f4cb0 100644 --- a/packages/web/i18n/i18next.d.ts +++ b/packages/web/i18n/i18next.d.ts @@ -11,6 +11,7 @@ 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 evaluation from './zh-CN/evaluation.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'; @@ -20,11 +21,14 @@ 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 database_client from './zh-CN/database_client.json'; +import type admin from './zh-CN/admin.json'; import type { I18N_NAMESPACES } from './constants'; export interface I18nNamespaces { common: typeof common; dataset: typeof dataset; + evaluation: typeof evaluation; app: typeof app; file: typeof file; publish: typeof publish; @@ -45,6 +49,8 @@ export interface I18nNamespaces { account_model: typeof account_model; dashboard_mcp: typeof dashboard_mcp; dashboard_evaluation: typeof dashboard_evaluation; + database_client: typeof database_client; + admin: typeof admin; } export type I18nNsType = (keyof I18nNamespaces)[]; diff --git a/packages/web/i18n/zh-CN/account_bill.json b/packages/web/i18n/zh-CN/account_bill.json index f067e2ae3686..f9648dd6e217 100644 --- a/packages/web/i18n/zh-CN/account_bill.json +++ b/packages/web/i18n/zh-CN/account_bill.json @@ -36,6 +36,7 @@ "payment_method": "支付方式", "payway_coupon": "兑换码", "rerank": "结果重排", + "generate_sql": "生成 SQL", "save": "保存", "save_failed": "保存异常", "save_success": "保存成功", diff --git a/packages/web/i18n/zh-CN/account_info.json b/packages/web/i18n/zh-CN/account_info.json index cd20076cdcc6..64d2b357c12f 100644 --- a/packages/web/i18n/zh-CN/account_info.json +++ b/packages/web/i18n/zh-CN/account_info.json @@ -78,5 +78,7 @@ "user_team_team_name": "团队", "verification_code": "验证码", "you_can_convert": "您可以兑换", - "yuan": "元" + "yuan": "元", + "password_min_length": "密码至少 8 位", + "password_requirement": "至少包含两种组合:数字、字母、特殊字符" } diff --git a/packages/web/i18n/zh-CN/account_model.json b/packages/web/i18n/zh-CN/account_model.json index 6d97ac6ef845..74ddd1d34afd 100644 --- a/packages/web/i18n/zh-CN/account_model.json +++ b/packages/web/i18n/zh-CN/account_model.json @@ -3,8 +3,8 @@ "aipoint_usage": "积分消耗", "all": "全部", "api_key": "API 密钥", - "avg_response_time": "平均调用时长 (秒)", - "avg_ttfb": "平均首字时长 (秒)", + "avg_response_time": "平均调用时长(秒)", + "avg_ttfb": "平均首字时长(秒)", "azure": "微软 Azure", "base_url": "代理地址", "batch_size": "并发请求数", @@ -42,6 +42,7 @@ "duration": "耗时", "edit": "编辑", "edit_channel": "渠道配置", + "add_channel": "渠道配置", "enable_channel": "启用", "forbid_channel": "禁用", "input": "输入", @@ -53,9 +54,9 @@ "mapping": "模型映射", "mapping_tip": "需填写一个有效 Json。可在向实际地址发送请求时,对模型进行映射。例如:\n{\n \"gpt-4o\": \"gpt-4o-test\"\n}\n当 FastGPT 请求 gpt-4o 模型时,会向实际地址发送 gpt-4o-test 的模型,而不是 gpt-4o。", "maxToken_tip": "模型 max_tokens 参数", - "max_rpm": "最大RPM (每分钟请求数)", + "max_rpm": "最大RPM(每分钟请求数)", "max_temperature_tip": "模型 temperature 参数,不填则代表模型不支持 temperature 参数。", - "max_tpm": "最大TPM (每分钟Token数)", + "max_tpm": "最大TPM(每分钟Token数)", "model": "模型", "model_error_rate": "失败率", "model_error_request_times": "失败次数", @@ -89,5 +90,7 @@ "vlm_model": "图片理解模型", "vlm_model_tip": "用于知识库中对文档中的图片进行额外的索引生成", "volunme_of_failed_calls": "调用失败量", - "waiting_test": "等待测试" + "waiting_test": "等待测试", + "evaluation_model": "评测模型", + "evaluation_model_tip": "用于应用评测,及评测数据集中针对数据质量的评测。" } diff --git a/packages/web/i18n/zh-CN/account_team.json b/packages/web/i18n/zh-CN/account_team.json index 3710d27ee66f..c7976c32fb80 100644 --- a/packages/web/i18n/zh-CN/account_team.json +++ b/packages/web/i18n/zh-CN/account_team.json @@ -52,6 +52,10 @@ "create_dataset_folder": "创建知识库文件夹", "create_department": "创建子部门", "create_evaluation": "创建应用评测", + "create_evaluation_dataset_collection": "创建评测数据集", + "create_evaluation_dataset_data": "创建评测数据", + "create_evaluation_task": "创建评测任务", + "create_evaluation_metric": "创建评测维度", "create_group": "创建群组", "create_invitation_link": "创建邀请链接", "create_invoice": "开发票", @@ -75,6 +79,16 @@ "delete_dataset_collaborator": "知识库权限删除", "delete_department": "删除子部门", "delete_evaluation": "删除应用评测数据", + "delete_evaluation_dataset_collection": "删除评测数据集", + "delete_evaluation_dataset_data": "删除评测数据", + "delete_evaluation_dataset_task": "删除评测数据集任务", + "delete_evaluation_task": "删除评测任务", + "delete_evaluation_task_item": "删除评测任务项目", + "delete_evaluation_metric": "删除评测维度", + "delete_evaluation_task_data_item": "删除评测任务数据项", + "update_evaluation_task_data_item": "更新评测任务数据项", + "retry_evaluation_task_data_item": "重试评测任务数据项", + "export_evaluation_task_data_items": "导出评测任务数据项", "delete_from_org": "移出部门", "delete_from_team": "移出团队", "delete_group": "删除群组", @@ -89,6 +103,9 @@ "export_bill_records": "导出账单记录", "export_dataset": "导出知识库", "export_evaluation": "导出应用评测数据", + "export_evaluation_task_items": "导出评测任务项目", + "generate_evaluation_summary": "生成评估总结报告", + "update_evaluation_summary_config": "更新评估总结配置", "export_members": "导出成员", "forbid_hint": "停用后,该邀请链接将失效。 该操作不可撤销,是否确认停用?", "forbid_success": "停用成功", @@ -99,6 +116,7 @@ "has_forbidden": "已失效", "has_invited": "已邀请", "ignore": "忽略", + "import_evaluation_dataset_data": "导入评测数据", "inform_level_common": "一般", "inform_level_emergency": "紧急", "inform_level_important": "重要", @@ -172,6 +190,8 @@ "log_export_bill_records": "【{{name}}】导出了账单记录", "log_export_dataset": "【{{name}}】导出了名为【{{datasetName}}】的【{{datasetType}}】", "log_export_evaluation": "【{{name}}】导出了名为【{{appName}}】的【{{appType}}】的评测数据", + "log_generate_evaluation_summary": "【{{name}}】为评估任务【{{evalName}}】的指标【{{metricName}}】生成了总结报告", + "log_update_evaluation_summary_config": "【{{name}}】更新了评估任务【{{evalName}}】的总结配置", "log_join_team": "【{{name}}】通过邀请链接【{{link}}】加入团队", "log_kick_out_team": "【{{name}}】移除了成员【{{memberName}}】", "log_login": "【{{name}}】登录了系统", @@ -217,12 +237,18 @@ "permission_appCreate_tip": "可以在根目录创建应用,(文件夹下的创建权限由文件夹控制)", "permission_datasetCreate": "创建知识库", "permission_datasetCreate_Tip": "可以在根目录创建知识库,(文件夹下的创建权限由文件夹控制)", + "permission_evaluationCreate": "创建评估", + "permission_evaluationCreate_Tip": "可以创建评估任务、评估指标和评估数据集", "permission_manage": "管理员", "permission_manage_tip": "可以管理成员、创建群组、管理所有群组、为群组和成员分配权限", "please_bind_contact": "请绑定联系方式", "purchase_plan": "升级套餐", + "quality_assessment_evaluation_data": "质量评估评测数据", "recover_team_member": "成员恢复", "relocate_department": "部门移动", + "retry_evaluation_dataset_task": "重试评测数据集任务", + "retry_evaluation_task": "重试评测任务", + "retry_evaluation_task_item": "重试评测任务项目", "remark": "备注", "remove_tip": "确认将 {{username}} 移出团队?成员将被标记为“离开”,不删除操作数据,账号下资源自动转让给团队所有者。", "restore_tip": "确认将 {{username}} 加入团队吗?仅恢复该成员账号可用性及相关权限,无法恢复账号下资源。", @@ -237,6 +263,9 @@ "search_test": "搜索测试", "set_invoice_header": "设置发票抬头", "set_name_avatar": "团队头像 & 团队名", + "smart_generate_evaluation_data": "智能生成评测数据", + "start_evaluation_task": "启动评测任务", + "stop_evaluation_task": "停止评测任务", "sync_immediately": "立即同步", "sync_member_failed": "同步成员失败", "sync_member_success": "同步成员成功", @@ -261,11 +290,46 @@ "update_data": "更新数据", "update_dataset": "更新知识库", "update_dataset_collaborator": "知识库权限更改", + "update_evaluation_dataset_collection": "更新评测数据集", + "update_evaluation_dataset_data": "更新评测数据", + "update_evaluation_task": "更新评测任务", + "update_evaluation_task_item": "更新评测任务项目", + "update_evaluation_metric": "更新评测维度", + "debug_evaluation_metric": "调试评测维度", "update_publish_app": "应用更新", "used_times_limit": "有效人数", "user_name": "用户名", "user_team_invite_member": "邀请成员", "user_team_leave_team": "离开团队", "user_team_leave_team_failed": "离开团队失败", - "waiting": "待接受" + "waiting": "待接受", + "log_create_evaluation_dataset_collection": "【{{name}}】创建了名为【{{collectionName}}】的评测数据集", + "log_create_evaluation_dataset_data": "【{{name}}】在评测数据集【{{collectionName}}】中创建了评测数据", + "log_update_evaluation_dataset_collection": "【{{name}}】更新了名为【{{collectionName}}】的评测数据集", + "log_update_evaluation_dataset_data": "【{{name}}】更新了评测数据集【{{collectionName}}】中的评测数据", + "log_delete_evaluation_dataset_collection": "【{{name}}】删除了名为【{{collectionName}}】的评测数据集", + "log_delete_evaluation_dataset_data": "【{{name}}】删除了评测数据集【{{collectionName}}】中的评测数据", + "log_quality_assessment_evaluation_data": "【{{name}}】对评测数据集【{{collectionName}}】中的评测数据进行了质量评估", + "log_smart_generate_evaluation_data": "【{{name}}】在评测数据集【{{collectionName}}】中智能生成了评测数据", + "log_delete_evaluation_dataset_task": "【{{name}}】删除了评测数据集【{{collectionName}}】中的任务", + "log_retry_evaluation_dataset_task": "【{{name}}】重试了评测数据集【{{collectionName}}】中的任务", + "log_import_evaluation_dataset_data": "【{{name}}】向评测数据集【{{collectionName}}】导入了{{recordCount}}条评测数据", + "log_create_evaluation_task": "【{{name}}】创建了名为【{{taskName}}】的评测任务", + "log_update_evaluation_task": "【{{name}}】更新了名为【{{taskName}}】的评测任务", + "log_delete_evaluation_task": "【{{name}}】删除了名为【{{taskName}}】的评测任务", + "log_start_evaluation_task": "【{{name}}】启动了名为【{{taskName}}】的评测任务", + "log_stop_evaluation_task": "【{{name}}】停止了名为【{{taskName}}】的评测任务", + "log_retry_evaluation_task": "【{{name}}】重试了名为【{{taskName}}】的评测任务,重试了{{retryCount}}个项目", + "log_delete_evaluation_task_item": "【{{name}}】删除了评测任务【{{taskName}}】中的项目【{{itemId}}】", + "log_update_evaluation_task_item": "【{{name}}】更新了评测任务【{{taskName}}】中的项目【{{itemId}}】", + "log_retry_evaluation_task_item": "【{{name}}】重试了评测任务【{{taskName}}】中的项目【{{itemId}}】", + "log_create_evaluation_metric": "【{{name}}】创建了名为【{{metricName}}】的评测维度", + "log_delete_evaluation_metric": "【{{name}}】删除了名为【{{metricName}}】的评测维度", + "log_update_evaluation_metric": "【{{name}}】更新了名为【{{metricName}}】的评测维度", + "log_debug_evaluation_metric": "【{{name}}】调试了名为【{{metricName}}】的评测维度", + "log_export_evaluation_task_items": "【{{name}}】导出了评测任务【{{taskName}}】的{{itemCount}}个项目,格式为{{format}}", + "log_delete_evaluation_task_data_item": "【{{name}}】删除了评测任务【{{taskName}}】中的数据项【{{dataItemId}}】", + "log_update_evaluation_task_data_item": "【{{name}}】更新了评测任务【{{taskName}}】中的数据项【{{dataItemId}}】", + "log_retry_evaluation_task_data_item": "【{{name}}】重试了评测任务【{{taskName}}】中的数据项【{{dataItemId}}】", + "log_export_evaluation_task_data_items": "【{{name}}】导出了评测任务【{{taskName}}】的{{itemCount}}个数据项,格式为{{format}}" } diff --git a/packages/web/i18n/zh-CN/account_usage.json b/packages/web/i18n/zh-CN/account_usage.json index 7ebb628f12e3..f3b9d7d93719 100644 --- a/packages/web/i18n/zh-CN/account_usage.json +++ b/packages/web/i18n/zh-CN/account_usage.json @@ -1,7 +1,7 @@ { "ai_model": "AI 模型", "all": "所有", - "answer_accuracy": "评测-回答准确性", + "metrics_execute": "指标执行", "app_name": "应用名", "auto_index": "索引增强", "billing_module": "扣费模块", @@ -14,6 +14,11 @@ "duration_seconds": "时长(秒)", "embedding_index": "索引生成", "evaluation": "应用评测", + "evaluation_dataset_data_quality_assessment": "评测数据质量评估", + "evaluation_dataset_data_synthesis": "评测数据合成", + "evaluation_dataset_data_qa_synthesis": "评测数据问答合成", + "evaluation_quality_assessment": "评估质量评估", + "evaluation_debug_metric": "调试维度", "every_day": "天", "every_month": "月", "every_week": "每周", @@ -53,5 +58,6 @@ "total_usage": "总消耗", "usage_detail": "使用详情", "user_type": "类型", - "wecom": "企业微信" + "wecom": "企业微信", + "evaluation_summary_generation": "评估-总结生成" } diff --git a/packages/web/i18n/zh-CN/admin.json b/packages/web/i18n/zh-CN/admin.json new file mode 100644 index 000000000000..2c79a6947335 --- /dev/null +++ b/packages/web/i18n/zh-CN/admin.json @@ -0,0 +1,631 @@ +{ + "license_active_success": "激活成功", + "system_activation": "系统激活", + "system_activation_desc": "你需要使用 License 激活系统后才可继续使用。", + "domain_name": "当前域名为", + "input_license": "请输入 License", + "cancel": "取消", + "confirm": "确认", + "config_desc": "配置介绍", + "domain_invalid": "License 域名不合法", + "change_license": "变更授权", + "logout": "退出登录", + "expire_time": "过期时间", + "max_users": "最大用户数", + "max_apps": "最大应用数", + "max_datasets": "最大知识库数量", + "sso": "单点登录", + "pay": "支付系统", + "custom_templates": "自定义模板和系统工具", + "dataset_enhance": "知识库增强", + "unlimited": "不限制", + "data_dashboard": "数据面板", + "notification_management": "通知管理", + "log_management": "日志管理", + "user_management": "用户管理", + "user_info": "用户信息", + "team_management": "团队管理", + "plan_management": "套餐管理", + "payment_records": "支付记录", + "invoice_management": "开票管理", + "resource_management": "资源管理", + "app_management": "应用管理", + "dataset_management": "知识库管理", + "system_config": "系统配置", + "basic_config": "基础配置", + "feature_list": "功能清单", + "security_review": "安全审查", + "third_party_providers": "第三方提供商", + "user_config": "用户配置", + "plan_recharge": "套餐 & 充值", + "template_tools": "模板 & 工具", + "template_market": "模板市场", + "toolbox": "工具箱", + "audit_logs": "审计日志", + "first_page": "第一页", + "previous_page": "上一页", + "next_page": "下一页", + "last_page": "最后一页", + "page": "页", + "click_view_details": "点击查看详情", + "upload_image_failed": "上传图片失败", + "config_file": "配置文件", + "save": "保存", + "upload_profile_failed": "上传头像失败", + "associated_plugin_is_empty": "关联插件不能为空", + "config_success": "配置成功", + "confirm_delete_plugin": "确认删除该插件么?", + "delete_success": "删除成功", + "custom_plugin": "自定义插件", + "config": "配置", + "give_name": "取个名字", + "click_upload_avatar": "点击上传头像", + "app_name_empty": "应用名不能为空", + "description": "介绍", + "add_app_description": "为这个应用添加一个介绍", + "associated_plugins": "关联插件", + "search_plugin": "输入插件名或 appId 查找插件", + "attribute": "属性", + "author_name": "作者名称", + "default_system_name": "默认为系统名", + "is_enable": "是否启用", + "charge_token_fee": "是否收取 Token 费用", + "call_price": "调用价格(n积分/次)", + "instructions": "使用说明", + "use_markdown_syntax": "使用 markdown 语法", + "update": "更新", + "create_plugin": "新建插件", + "delete_confirm_message": "删除后,其下资源将同步删除且不可恢复。是否确认删除?", + "group_management": "分组管理", + "total_groups": "共 {localGroups.length} 个分组", + "add": "添加", + "add_type": "添加类型", + "avatar_select_error": "头像选择异常", + "rename": "重命名", + "add_group": "添加分组", + "avatar_name": "头像 & 名称", + "click_set_avatar": "点击设置头像", + "group_name_empty": "分组名称不能为空", + "official": "官方", + "configured": "已配置", + "not_configured": "未配置", + "system_key_price": "系统密钥价格(n积分/次)", + "plugin_config": "{{name}} 配置", + "enable_toolset": "是否启用工具集", + "config_system_key": "是否配置系统密钥", + "tool_list": "工具列表", + "tool_name": "工具名", + "key_price": "密钥价格", + "continue": "继续", + "user_deleted_message": "这人被删了", + "missing_field": "缺少字段", + "team_not_exist": "团队不存在", + "subscription_exists": "已存在相同类型的订阅", + "subscription_not_exist": "订阅不存在", + "user_exist": "用户已存在", + "account_logout": "账号已经注销了", + "user_not_found": "找不到 user", + "user_not_exist": "用户不存在", + "update_failed": "更新失败", + "send_notification_success": "发送通知成功", + "user_password_error": "用户或密码错误!", + "invoice_not_found": "找不到发票", + "invoice_issued": "发票已开具", + "invoice_completed_notice": "您申请的发票已完成,请注意查收", + "invoice_generation_completed": "开票完成", + "order_not_exist": "订单不存在", + "order_unpaid": "订单未支付", + "order_invoiced_no_refund": "订单已开票,无法直接退款", + "order_insufficient_amount": "订单金额不足", + "type_empty": "type 不能为空", + "quantity_must_be_positive": "数量必须大于0", + "team_not_found": "未找到团队", + "dataset_training_rebuilding": "数据集正在训练或者重建中,请稍后再试", + "database_rebuilding_index": "数据库重建索引", + "type_not_support": "暂不支持该类型消息", + "send_verification_code_success": "发送验证码成功", + "normal": "正常", + "leave": "离开", + "deactivated": "已停用", + "account": "账号", + "username": "用户名", + "contact": "联系方式", + "department": "部门", + "join_time": "加入时间", + "update_time": "更新时间", + "status": "状态", + "team_num_limit_error": "仅限1个团队", + "organization_name": "组织名", + "amount": "金额", + "yuan": "元", + "pending_invoice_count": "待开票数量", + "go_to_view": "前去查看", + "new_invoice_application": "有新的开票申请", + "order_type_error": "订单类型错误", + "missing_key_params_update_bill_failed": "缺少关键参数,更新账单失败,请联系管理员", + "plugin_service_link_not_configured": "未配置插件服务链接", + "audit_record_table": "审计记录表", + "operator": "操作人员", + "operation_type": "操作类型", + "operation_time": "操作时间", + "operation_content": "操作内容", + "no_audit_records": "暂无审计记录~", + "audit_details": "审计详情", + "details": "详情", + "close": "关闭", + "total_users": "总用户数", + "registered_users": "注册用户数", + "payment_amount": "付费金额", + "order_count": "订单数", + "all": "全部", + "success": "成功", + "paid_teams": "付费团队数", + "total_conversations": "总对话数", + "total_sessions": "总会话数", + "avg_conversations_per_session": "每个会话平均对话数", + "points_consumed": "积分消耗", + "user_total": "用户总数", + "dataset_total": "知识库总数", + "app_total": "应用总数", + "statistics_data": "统计数据", + "traffic": "流量", + "payment": "付费", + "active": "活跃", + "cost": "成本", + "last_7_days": "近7天", + "last_30_days": "近30天", + "last_90_days": "近90天", + "last_180_days": "近180天", + "confirm_modify_system_announcement": "确认修改系统公告?", + "confirm_send_system_notification": "确认发送系统通知?", + "modify_success": "修改成功", + "modify_failed": "修改失败", + "send_success": "发送成功", + "send_failed": "发送失败", + "system_announcement_config": "系统公告配置", + "system_announcement_description": "设置该内容,会在用户登录系统后,通过弹窗形式进行强提示。用户关闭后,下次不再提示。只能设置1个该类型通知。支持 markdown 格式。", + "send_system_notification": "发送系统通知", + "confirm_send": "确认发送", + "send_notification_description": "为所有用户发送一个通知,不同等级通知,会有不同提示。", + "message_level": "消息等级", + "level_normal": "一般(仅发站内信)", + "level_important": "重要(站内信+登录通知)", + "level_urgent": "紧急(站内信+登录通知+邮件/短信提醒)", + "level_normal_text": "一般", + "level_important_text": "重要", + "level_urgent_text": "紧急", + "notification_title": "通知标题", + "notification_content": "通知内容", + "log_record_table": "日志记录表", + "log_search_placeholder": "请想要查找的日志内容,回车搜索", + "time": "时间", + "log_content": "日志内容", + "no_log_records": "暂无Log记录~", + "log_level": "日志等级", + "log_detail": "日志详情", + "log_message": "日志消息", + "login_success": "登录成功!", + "admin_login": "管理员登录", + "login_username": "用户名", + "login_password": "密码", + "login_button": "登录", + "app_list": "应用列表", + "app_name": "应用名", + "creator": "创建者", + "redirect": "跳转", + "app_no_records": "无应用记录~", + "app_details": "应用详情", + "app_id": "应用id", + "app_creator_id": "创建者 ID", + "dataset_list": "知识库列表", + "dataset_name": "知识库名", + "dataset_data_size": "数据量", + "dataset_vector_count": "向量总数", + "dataset_favorite_count": "收藏数", + "new": "新增", + "name": "名称", + "enable": "启用", + "redirect_link": "跳转链接", + "operation": "操作", + "add_sidebar_item": "新增侧边项", + "sidebar_item_name": "侧边项名", + "sidebar_item_name_empty": "侧边项名不能为空", + "redirect_link_empty": "跳转链接不能为空", + "edit_plan": "编辑 {{label}} 套餐", + "plan_name": "套餐名称", + "custom_plan_name_desc": "自定义套餐名,可覆盖原套餐名", + "monthly_price": "每月价格", + "max_team_members": "最大团队成员", + "max_app_count": "最大APP数量", + "max_dataset_count": "最大知识库数量", + "history_retention_days": "历史记录保存多少天", + "max_dataset_index_count": "最大知识库索引数量", + "monthly_ai_points": "每月 AI 积分", + "training_priority_high": "训练优先级(高的优先)", + "allow_site_sync": "允许使用站点同步", + "allow_team_operation_logs": "允许团队操作日志", + "click_configure_plan": "点击配置套餐", + "copy_success": "复制成功", + "delete_confirm": "确认删除该变量?", + "delete": "删除", + "workflow_variable_custom": "自定义工作流变量", + "workflow_variable_custom_title": "自定义工作流变量", + "field_name": "变量名", + "usage_url": "使用量查询地址", + "note": "说明", + "import_config": "导入配置", + "import_success_save": "导入成功,请点击保存", + "import_check_format": "请检查配置文件格式", + "import": "导入", + "get_config_error": "获取配置出错", + "save_success": "保存成功", + "save_failed": "保存失败", + "frontend_display_config": "前端展示配置", + "personalization_config": "个性化配置", + "global_script": "全局Script脚本", + "system_params": "系统参数", + "pdf_parse_config": "PDF 解析配置", + "usage_limits": "使用限制", + "sidebar_config": "侧边栏配置", + "system_name": "系统名", + "custom_api_domain": "自定义api域名", + "custom_api_domain_desc": "可以设置一个额外的api地址,不使用主站的地址,需配置域名的cname和ssl证书。", + "custom_share_link_domain": "自定义分享链接域名", + "custom_share_link_domain_desc": "可以设置一个额外的分享链接地址,不使用主站的地址,需配置域名的cname和ssl证书。", + "openapi_prefix": "OpenAPI 前缀", + "contact_popup": "联系弹窗", + "contact_popup_desc": "使用 Markdown 进行配置,配置之后,在网页中\"联系我们\"相关的内容,会提示填写的内容。", + "custom_api_doc_url": "自定义 api 文档地址", + "custom_openapi_doc_url": "自定义 openapi 文档地址", + "doc_url_note": "文档地址(加一个 / 结尾,否则会携带子路径跳转)", + "contribute_plugin_doc_url": "贡献插件文档地址", + "contribute_template_doc_url": "贡献模板市场文档地址", + "global_script_desc": "自定义 Script 脚本,可以全局插入(可以做站点流量监控之类的)", + "mcp_forward_service_url": "MCP 转发服务地址", + "mcp_forward_service_desc": "需要部署一个 MCP 转发服务,用于将 FastGPT 应用以MCP协议暴露,例如:http://localhost:3005", + "oneapi_url": "oneAPI地址(会覆盖环境变量配置的)", + "oneapi_url_desc": "oneAPI地址,可以使用 oneapi 来实现多模型接入", + "input_oneapi_url": "请输入 oneAPI 地址", + "oneapi_key": "OneAPI 密钥(会覆盖环境变量配置的)", + "input_oneapi_key": "请输入 OneAPI 密钥", + "dataset_parse_max_process": "知识库解析最大处理进程", + "dataset_index_max_process": "知识库索引最大处理进程", + "file_understanding_max_process": "文件理解模型最大处理进程", + "image_understanding_max_process": "图片理解模型最大处理进程", + "hnsw_ef_search": "HNSW ef_search", + "hnsw_ef_search_desc": "HNSW 参数。越大召回率越高,性能越差,默认为 100,具体可见:https://github.com/pgvector/pgvector", + "hnsw_max_scan_tuples": "HNSW max_scan_tuples", + "hnsw_max_scan_tuples_desc": "迭代搜索最大数量,越大召回率越高,性能越差,默认为 100000,具体可见:https://github.com/pgvector/pgvector", + "token_calc_max_process": "token计算最大进程(通常多少并发设置多少)", + "custom_pdf_parse_url": "自定义 PDF 解析地址", + "custom_pdf_parse_key": "自定义 PDF 解析密钥", + "custom_pdf_parse_timeout": "自定义 PDF 解析超时时间(分钟)", + "doc2x_pdf_parse_key": "Doc2x pdf 解析密钥(比自定义 PDF 解析优先级低)", + "custom_pdf_parse_price": "自定义 PDF 解析价格(n 积分/页)", + "eval_config": "评测配置", + "eval_config_task_concurrency": "评测任务并发数", + "eval_config_task_concurrency_desc": "同时执行的评测任务数量", + "eval_config_case_concurrency": "评测用例执行并发数", + "eval_config_case_concurrency_desc": "单个评测任务中并发处理的用例数量", + "eval_config_case_max_retry": "评测用例最大重试次数", + "eval_config_case_max_retry_desc": "评测用例失败时的最大重试次数", + "eval_config_case_result_threshold": "评测判决默认阈值", + "eval_config_case_result_threshold_desc": "评测判决的默认阈值(0-1之间,决定评测结果的正负)", + "eval_config_summary_concurrency": "评测报告生成并发数", + "eval_config_data_quality_concurrency": "评测数据集-数据质量并发数", + "eval_config_dataset_synthesize_concurrency": "评测数据集-数据合成并发数", + "eval_config_smart_generate_concurrency": "评测数据集-数据智能生成并发数", + "eval_config_maxStalledCount": "评测-任务卡滞最大重试次数", + "max_upload_files_per_time": "单次最多上传多少个文件", + "max_upload_files_per_time_desc": "用户上传知识库时,每次上传最多选择多少个文件", + "max_upload_file_size": "上传文件最大大小(M)", + "max_upload_file_size_desc": "用户上传知识库时,每个文件最大是多少。放大的话,需要注意网关也要设置得够大。", + "export_interval_minutes": "导出间隔时长(分钟)", + "site_sync_interval_minutes": "站点同步使用间隔时长(分钟)", + "mobile_sidebar_location": "移动端的侧边栏显示在账号 - 个人信息里", + "basic_features": "基础功能", + "third_party_knowledge_base": "第三方知识库", + "third_party_publish_channels": "第三方发布渠道", + "feature_display_config": "功能展示配置", + "display_team_sharing": "展示团队分享", + "display_chat_blank_page": "展示聊天空白页(都关闭即可)", + "display_invite_friends_activity": "展示邀请好友活动", + "frontend_compliance_notice": "前端是否展示合规提示文案", + "feishu_knowledge_base": "飞书知识库", + "feishu_knowledge_base_desc": "关闭后,创建数据库时不再显示飞书数据库", + "yuque_knowledge_base": "语雀知识库", + "yuque_knowledge_base_desc": "关闭后,创建数据库时不再显示语雀数据库", + "feishu_publish_channel": "飞书发布渠道", + "feishu_publish_channel_desc": "关闭后,发布渠道中不再显示飞书发布渠道", + "dingtalk_publish_channel": "钉钉发布渠道", + "dingtalk_publish_channel_desc": "关闭后,发布渠道中不再显示钉钉发布渠道", + "wechat_publish_channel": "公众号发布渠道", + "wechat_publish_channel_desc": "关闭后,发布渠道中不再显示公众号发布渠道", + "content_security_review": "内容安全审查", + "baidu_security_id": "百度安全 id", + "baidu_security_secret": "百度安全 secret", + "custom_security_check_url": "自定义安全校验 URL", + "baidu_security_register_desc": "注册百度安全校验账号,并创建对应应用。提供应用的 id 和 secret", + "custom_security_check_desc": "如果您有自己的安全校验服务,可以填写该地址,并在安全设置中开启自定义安全校验", + "plan_free": "免费版", + "plan_trial": "体验版", + "plan_team": "团队版", + "plan_enterprise": "企业版", + "subscription_plan": "订阅套餐", + "standard_subscription_plan": "标准订阅套餐", + "custom_plan_description": "自定义套餐说明", + "dataset_storage_cost_desc": "知识库存储费用(xx元/1000条/月)", + "extra_ai_points_cost_desc": "额外AI积分费用(xx元/1000积分/月)", + "payment_method": "支付方式", + "wechat_payment_config": "微信支付配置", + "alipay_payment_config": "支付宝支付配置", + "corporate_payment_message": "对公支付消息提示", + "enable_subscription_plan": "是否启用订阅套餐", + "custom_plan_page_description": "如果填写了该地址,会覆盖系统上套餐页面,会跳转到这个自定义页面,你可以在自定义页面里定义收费规则", + "wechat_payment_materials": "微信支付相关材料", + "wechat_payment_registration_guide": "自行注册微信支付,目前需要wx扫码支付", + "unused_field_placeholder": "没用到,随便填个", + "certificate_management_guide": "点管理证书进去看到", + "wechat_key_extraction_guide": "按微信教程拿到这几个文件,txt打开key", + "alipay_payment_materials": "支付宝支付相关材料", + "alipay_application_guide": "自行注册支付宝应用,目前需要开通电脑网站支付", + "alipay_certificate_encryption_guide": "点接口加签方式后选择证书加密方式,具体操作参考", + "application_public_key_certificate": "应用公钥证书", + "private_key_document_reference": "参考上面私钥获取文档", + "alipay_root_certificate": "支付宝根证书", + "alipay_public_key_certificate": "支付宝公钥证书", + "alipay_dateway": "支付宝网关", + "alipay_gateway_sandbox_note": "支付宝网关,注意测试使用的沙箱环境是\nhttps://openapi-sandbox.dl.alipaydev.com/gateway.do\n,而生成环境是\nhttps://openapi.alipay.com/gateway.do\n", + "alipay_endpoint_sandbox_note": "支付宝端点,注意测试使用的沙箱环境是\nhttps://openapi-sandbox.dl.alipaydev.com\n,而生成环境是\nhttps://openapi.alipay.com\n", + "message_notification": "消息提示", + "markdown_format_support": "支持markdown格式", + "third_party_account_config": "第三方账号配置", + "allow_user_account_config": "允许用户配置账号", + "view_documentation": "查看文档", + "openai_oneapi_account": "OpenAI/OneAPI 账号", + "laf_account": "laf 账号", + "input_laf_address": "请输入 laf 地址", + "multi_team_mode": "多团队模式", + "single_team_mode": "单团队模式", + "sync_mode": "同步模式", + "notification_login_settings": "通知 & 登陆设置", + "team_mode_settings": "团队模式设置", + "custom_user_system_config": "自定义用户系统配置", + "email_notification_config": "邮箱通知配置(注册、套餐通知)", + "aliyun_sms_config": "阿里云短信配置", + "aliyun_sms_template_code": "阿里云短信模板CODE(SMS_xxx)", + "wechat_service_login": "微信服务号登陆", + "github_login_config": "GitHub 登录配置", + "google_login_config": "Google 登陆配置", + "microsoft_login_config": "微软登陆配置", + "quick_login": "快速登陆(不推荐)", + "login_notifications_config": "通知登陆 & 设置", + "user_service_root_address": "用户服务根地址(末尾不加/)", + "sso_usage_guide": "具体用法请看: [SSO & 外部成员同步](https://doc.fastgpt.io/docs/guide/admin/sso/)", + "sso_login_button_title": "SSO 登录按钮标题", + "config_sso_login_button_title": "配置 SSO 登录按钮的标题", + "sso_login_button_icon": "SSO 登录按钮的图标", + "config_sso_login_button_icon": "配置 SSO 登录按钮的图标", + "sso_auto_redirect": "SSO 自动跳转", + "sso_auto_redirect_desc": "开启后,用户进入登录页面,将会自动触发 SSO 登录,无需手动点击。", + "email_smtp_address": "邮箱服务SMTP地址", + "email_smtp_address_note": "不同厂商不一样\nQQ: smtp.qq.com\ngmail: smtp.gmail.com", + "email_smtp_username": "邮箱服务SMTP用户名", + "email_smtp_username_example": "qq 邮箱为例,对应 qq 号", + "email_password": "邮箱 Password", + "email_smtp_auth_code": "SMTP 授权码", + "enable_email_registration": "是否开启邮箱注册", + "aliyun_access_key_guide": "阿里云短信参数\nhttps://dysms.console.aliyun.com/overview\n申请对应的签名和短信模板,提供:\nACCESSKEYID\nACCESSSECRET\n签名名称\n模板CODE,SM开头的", + "aliyun_secret_key": "阿里云账号的secret key", + "sms_signature": "短信签名", + "registration_account": "注册账号", + "registration_account_desc": "填写后,将会开启手机号注册", + "reset_password": "重置密码", + "reset_password_desc": "填写后,将会开启手机号找回密码", + "bind_notification_phone": "绑定通知手机号", + "bind_notification_phone_desc": "填写后,将会允许手机号绑定通知方式", + "subscription_expiring_soon": "订阅套餐即将过期", + "subscription_expiring_soon_desc": "填写后,套餐即将过期,会发送一个短信", + "free_user_cleanup_warning": "免费版用户清理警告", + "wechat_service_appid": "服务号的 Appid。微信服务号的验证地址填写:商业版域名", + "wechat_service_secret": "服务号的 Secret", + "register_one": "注册一个", + "provide": "提供", + "domain": "域名", + "microsoft_app_client_id": "对应 Microsoft 应用的「应用程序(客户端) ID」", + "microsoft_tenant_id": "对应 Microsoft 应用的「租户 ID」, 若使用默认的 common 可不用填写", + "custom_button_name": "自定义按钮名", + "custom_button_name_desc": "自定义按钮的名称,若不填写则使用默认的 Microsoft 按钮", + "simple_app": "简易应用", + "workflow": "工作流", + "plugin": "插件", + "folder": "文件夹", + "http_plugin": "HTTP 插件", + "toolset": "工具集", + "tool": "工具", + "hidden": "隐藏", + "select_json_file": "请选择 JSON 文件", + "confirm_delete_template": "确认删除该模板么?", + "upload_config_first": "请先上传配置文件", + "app_type_not_recognized": "未识别到应用类型", + "config_json_format_error": "配置文件 JSON 格式错误", + "template_update_success": "模板更新成功", + "template_create_success": "模板创建成功", + "template_config": "模板配置", + "json_serialize_failed": "JSON 序列化失败", + "get_app_type_failed": "获取应用类型失败", + "file_overwrite_content": "文件将覆盖当前内容", + "config_file_label": "配置文件", + "official_config": "官方配置", + "upload_file": "上传文件", + "paste_config_or_drag_json": "粘贴配置或拖入 JSON 文件", + "paste_config": "粘贴配置", + "app_type": "应用类型", + "auto_recognize": "自动识别", + "app_attribute_not_recognized": "未识别到应用属性", + "text": "文本", + "link": "链接", + "input_link": "请输入链接", + "settings_successful": "设置成功", + "configure_quick_templates": "配置快捷模板", + "search_apps": "搜索应用", + "selected_count": "已选: {{count}} / 3", + "category_management": "分类管理", + "total_categories": "共 {{length}} 个分类", + "add_category": "添加分类", + "category_name": "分类名", + "category_name_empty": "分类名不能为空", + "template_list": "模板列表", + "quick_template": "快捷模板", + "add_template": "添加模板", + "recommended": "推荐", + "no_templates": "暂无模板", + "add_attribute_first": "请先添加属性", + "add_plugin": "添加插件", + "token_points": "Token 积分", + "token_fee_description": "开启该开关后,用户使用该插件,需要支付插件中Token的积分,并且同时会收取调用积分", + "call_points": "调用积分", + "system_key": "系统密钥", + "system_key_description": "对于需要密钥的工具,您可为其配置系统密钥,用户可通过支付积分的方式使用系统密钥。", + "no_plugins": "暂无插件", + "invoice_application": "开票申请", + "search_user_placeholder": "请输入用户名,回车搜索", + "submit_status": "提交状态", + "submit_complete_time": "提交时间/完成时间", + "invoice_title": "抬头", + "waiting_for_invoice": "等待开票", + "completed": "已完成", + "confirm_invoice": "确认开票", + "no_invoice_records": "无开票记录~", + "invoice_details": "发票详情", + "invoice_amount": "开票金额", + "organization": "组织名称", + "unified_credit_code": "统一信用代码", + "company_address": "公司地址", + "company_phone": "公司电话", + "bank_name": "开户银行", + "bank_account": "开户账号", + "need_special_invoice": "是否需要专票", + "yes": "是", + "no": "否", + "email_address": "邮箱地址", + "invoice_file": "发票文件", + "click_download": "点击下载", + "operation_success": "操作成功", + "operation_failed": "操作失败", + "upload_invoice_pdf": "请上传发票的PDF文件", + "select_invoice_file": "选择发票文件", + "confirm_submission": "确认提交", + "balance_recharge": "余额充值", + "plan_subscription": "套餐订阅", + "knowledge_base_expansion": "知识库扩容", + "ai_points_package": "AI积分套餐", + "monthly": "按月", + "yearly": "按年", + "free": "免费", + "trial": "体验", + "team": "团队", + "enterprise": "企业", + "custom": "自定义", + "wechat": "微信", + "balance": "余额", + "alipay": "支付宝", + "corporate": "对公", + "redeem_code": "兑换码", + "search_user": "请输入用户名搜索", + "team_id": "团队ID", + "recharge_member_name": "充值的成员名", + "unpaid": "未支付", + "no_bill_records": "无账单记录~", + "order_details": "订单详情", + "order_number": "订单号", + "generation_time": "生成时间", + "order_type": "订单类型", + "subscription_period": "订阅周期", + "subscription_package": "订阅套餐", + "months": "月数", + "extra_knowledge_base_capacity": "额外知识库容量", + "extra_ai_points": "额外AI积分", + "time_error": "开始时间不能大于结束时间", + "points_error": "剩余积分不能大于总积分", + "add_success": "添加成功", + "add_package": "添加套餐", + "package_type": "套餐类型", + "basic_package": "基础套餐", + "start_time": "开始时间", + "required": "*必填", + "end_time": "结束时间", + "package_level": "套餐等级", + "total_points": "总积分", + "remaining_points": "剩余积分", + "price_yuan_for_record_only": "价格(元)-仅用于记录", + "price": "价格", + "update_success": "更新成功", + "edit": "编辑", + "edit_package": "编辑套餐", + "value_override_description": "下面的值会覆盖套餐配置,不填则会用套餐的标准值", + "team_member_limit": "团队成员上限", + "app_limit": "应用上限", + "knowledge_base_limit": "知识库上限", + "team_name": "团队名", + "points": "积分", + "start_end_time": "起止时间", + "version": "版", + "extra_knowledge_base": "额外知识库", + "no_package_records": "无套餐记录~", + "role": "权限", + "team_details": "团队详情", + "chage_success": "变更成功", + "team_edit": "团队编辑", + "team_list": "团队列表", + "create_time": "创建时间", + "no_team_data": "无团队记录~", + "add_user": "添加用户", + "add_user_btn": "添加用户", + "password": "密码", + "password_requirements": "密码至少 8 位,且至少包含两种组合:数字、字母或特殊字符", + "account_deactivated": "账号已注销", + "edit_user": "编辑用户", + "user_status": "用户状态", + "confirm_deactivate_account": "确认注销该账号?会将该用户下关键资源删除,并修改其用户名成 xx-deleted", + "deactivate": "注销", + "no_user_records": "无用户记录~", + "user_details": "用户详情", + "system_incompatibility": "部分系统不兼容,导致页面崩溃。如果可以,请联系作者,反馈下具体操作和页面。大部分是 苹果 的 safari 浏览器导致,可以尝试更换 chrome 浏览器。", + "content_not_compliant": "您的内容不合规", + "baidu_content_security_check_exception": "百度内容安全校验异常", + "license_not_read": "未读取到 License", + "license_invalid": "License 不合法", + "license_expired": "License 已过期", + "license_content_error": "License 内容错误", + "system_not_activated": "系统未激活", + "exceed_max_users": "超过最大用户数", + "server_error": "服务器异常", + "request_error": "请求错误", + "unknown_error": "未知错误", + "token_expired_relogin": "token过期,重新登录", + "google_verification_result": "谷歌校验结果", + "abnormal_operation_environment": "您的操作环境存在异常,请刷新页面后重试或联系客服。", + "notify_expiring_packages": "通知即将过期的套餐", + "notify_free_users_cleanup": "通知免费版用户即将清理", + "bing_oauth_config_incomplete": "Bing OAuth配置不完整", + "request_limit_per_minute": "每分钟仅能请求次数", + "share_link_expired": "分享链接已过期", + "link_usage_limit_exceeded": "链接超出使用限制", + "authentication_failed": "身份校验失败", + "user_registration": "新用户注册", + "initial_password": "您的初始密码为", + "notification_too_frequent": "发送通知太频繁了", + "emergency_notification_requires_teamid": "紧急通知必须提供 teamId", + "send_sms_failed": "发送短信失败", + "fastgpt_user": "FastGPT用户", + "refund_failed": "退款失败", + "refund_request_failed": "退款请求失败", + "get_certificate_list_failed": "获取证书列表失败", + "platform_certificate_serial_mismatch": "平台证书序列号不相符", + "extra_knowledge_base_storage": "额外知识库存储", + "image_compression_error": "压缩图片异常", + "image_too_large": "图片太大了", + "password_min_length": "密码至少 8 位", + "password_requirement": "至少包含两种组合:数字、字母、特殊字符" +} diff --git a/packages/web/i18n/zh-CN/app.json b/packages/web/i18n/zh-CN/app.json index 6f5402a89b0c..5d280ddbe363 100644 --- a/packages/web/i18n/zh-CN/app.json +++ b/packages/web/i18n/zh-CN/app.json @@ -69,11 +69,11 @@ "cron.every_week": "每周执行", "cron.interval": "间隔执行", "dataset": "知识库", - "dataset_search_tool_description": "调用“语义检索”和“全文检索”能力,从“知识库”中查找可能与问题相关的参考内容。优先调用该工具来辅助回答用户的问题。", + "dataset_search_tool_description": "调用语义检索、全文检索、数据库检索能力,从“知识库”中查找可能与问题相关的参考内容", "day": "日", "deleted": "应用已删除", "document_quote": "文档引用", - "document_quote_tip": "通常用于接受用户上传的文档内容(这需要文档解析),也可以用于引用其他字符串数据。", + "document_quote_tip": "通常用于接受用户上传的文档内容(这需要文档解析),也可以用于引用其他字符串数据。", "document_upload": "文档上传", "edit_app": "应用详情", "edit_info": "编辑信息", @@ -108,7 +108,7 @@ "log_detail": "日志详情", "logs_app_data": "数据看板", "logs_app_result": "应用效果", - "logs_average_response_time": "平均运行时长(s)", + "logs_average_response_time": "平均运行时长(s)", "logs_average_response_time_description": "工作流总运行时间的平均值", "logs_bad_feedback": "点踩", "logs_chat_count": "会话次数", @@ -291,7 +291,7 @@ "transition_to_workflow_create_new_placeholder": "创建一个新的应用,而不是修改当前应用", "transition_to_workflow_create_new_tip": "转化成工作流后,将无法转化回简易模式,请确认!", "tts_ai_model": "使用语音合成模型", - "tts_browser": "浏览器自带(免费)", + "tts_browser": "浏览器自带(免费)", "tts_close": "关闭", "type.All": "全部", "type.Create http plugin tip": "通过 OpenAPI Schema 批量创建插件,兼容 GPTs 格式", @@ -355,5 +355,14 @@ "workflow.user_file_input": "文件链接", "workflow.user_file_input_desc": "用户上传的文档和图片链接", "workflow.user_select": "用户选择", - "workflow.user_select_tip": "该模块可配置多个选项,以供对话时选择。不同选项可导向不同工作流支线" + "workflow.user_select_tip": "该模块可配置多个选项,以供对话时选择。不同选项可导向不同工作流支线", + "files_cascader_no_knowledge_base": "不加入知识库", + "files_cascader_select_knowledge_base": "请选择知识库", + "files_cascader_select_first": "请先选择知识库", + "files_cascader_dataset_empty": "该知识库数据集为空", + "select_join_location": "选择加入位置", + "no_data_for_smart_generate": "该知识库中没有可用于智能生成的数据", + "team.menu.plugin": "插件", + "team.menu.app": "简易应用", + "team.menu.workflow": "工作流" } diff --git a/packages/web/i18n/zh-CN/chat.json b/packages/web/i18n/zh-CN/chat.json index d21a51db7539..fb957ea1de80 100644 --- a/packages/web/i18n/zh-CN/chat.json +++ b/packages/web/i18n/zh-CN/chat.json @@ -129,5 +129,11 @@ "upload": "上传", "variable_invisable_in_share": "自定义变量在免登录链接中不可见", "view_citations": "查看引用", - "web_site_sync": "Web站点同步" + "web_site_sync": "Web站点同步", + "embedding_model_error": "向量模型出错,请核查模型配置信息", + "language_model_error":"请求大模型出错,请检查模型配置是否正确", + "database_sql_query": "数据库检索的 SQL 语句", + "search_result": "检索结果", + "database_search_results": "数据库搜索结果", + "other_knowledge_base_search_results": "其他知识库搜索结果" } diff --git a/packages/web/i18n/zh-CN/common.json b/packages/web/i18n/zh-CN/common.json index 1b8789b1e56a..9371e69961e9 100644 --- a/packages/web/i18n/zh-CN/common.json +++ b/packages/web/i18n/zh-CN/common.json @@ -98,7 +98,7 @@ "add_success": "添加成功", "all_quotes": "全部引用", "all_result": "完整结果", - "app_evaluation": "应用评测(Beta)", + "app_evaluation": "应用评测(Beta)", "app_not_version": " 该应用未发布过,请先发布应用", "auth_config": "鉴权配置", "auth_type": "鉴权类型", @@ -164,6 +164,10 @@ "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.system_error.license_evaluation_task_amount_limit": "超出系统最大评估任务数量", + "code_error.system_error.license_eval_dataset_amount_limit": "超出系统最大评估数据集数量", + "code_error.system_error.license_eval_dataset_data_amount_limit": "超出系统最大评估数据集数据量", + "code_error.system_error.license_eval_metric_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": "不能删除默认群组", @@ -189,6 +193,10 @@ "code_error.team_error.user_not_active": "用户未接受或已离开团队", "code_error.team_error.website_sync_not_enough": "免费版无法使用Web站点同步~", "code_error.team_error.you_have_been_in_the_team": "你已经在该团队中", + "code_error.team_error.evaluation_task_amount_not_enough": "评估任务数量已达上限~", + "code_error.team_error.evaluation_dataset_amount_not_enough": "评测数据集数量已达上限~", + "code_error.team_error.evaluation_dataset_data_amount_not_enough": "评测数据条目数量已达上限~", + "code_error.team_error.evaluation_metric_amount_not_enough": "评测维度数量已达上限~", "code_error.token_error_code.403": "登录状态无效,请重新登录", "code_error.user_error.balance_not_enough": "账号余额不足~", "code_error.user_error.bin_visitor_guest": "您当前身份为游客,无权操作", @@ -382,6 +390,7 @@ "core.chat.logs.team": "团队空间对话", "core.chat.logs.test": "在线调试", "core.chat.logs.wecom": "企业微信", + "core.chat.logs.evaluation": "评估测试", "core.chat.markdown.Edit Question": "编辑问题", "core.chat.markdown.Quick Question": "点我立即提问", "core.chat.markdown.Send Question": "发送问题", @@ -406,13 +415,13 @@ "core.chat.response.module cq result": "分类结果", "core.chat.response.module extract description": "提取背景描述", "core.chat.response.module extract result": "提取结果", - "core.chat.response.module historyPreview": "记录预览(仅展示部分内容)", + "core.chat.response.module historyPreview": "记录预览(仅展示部分内容)", "core.chat.response.module http result": "响应体", "core.chat.response.module if else Result": "判断器结果", "core.chat.response.module limit": "单次搜索上限", "core.chat.response.module maxToken": "最大响应 tokens", "core.chat.response.module model": "模型", - "core.chat.response.module name": "模型名", + "core.chat.response.module name": "节点名", "core.chat.response.module query": "问题/检索词", "core.chat.response.module similarity": "相似度", "core.chat.response.module temperature": "温度", @@ -420,7 +429,7 @@ "core.chat.response.plugin output": "插件输出值", "core.chat.response.search using reRank": "结果重排", "core.chat.response.text output": "文本输出", - "core.chat.response.update_var_result": "变量更新结果(按顺序展示多个变量更新结果)", + "core.chat.response.update_var_result": "变量更新结果(按顺序展示多个变量更新结果)", "core.chat.response.user_select_result": "用户选择结果", "core.chat.retry": "重新生成", "core.chat.tts.Stop Speech": "停止", @@ -449,7 +458,7 @@ "core.dataset.collection.Start Sync Tip": "确认开始同步数据?将会删除旧数据后重新获取,请确认!", "core.dataset.collection.Sync": "同步数据", "core.dataset.collection.Sync Collection": "数据同步", - "core.dataset.collection.Website Empty Tip": "还没有关联网站", + "core.dataset.collection.Website Empty Tip": "还没有关联网站,", "core.dataset.collection.Website Link": "Web 站点地址", "core.dataset.collection.id": "集合 ID", "core.dataset.collection.metadata.Createtime": "创建时间", @@ -786,7 +795,7 @@ "dataset.data.Index Placeholder": "输入索引文本内容", "dataset.data.Input Success Tip": "导入数据成功", "dataset.data.Update Success Tip": "更新数据成功", - "dataset.data.edit.Index": "数据索引({{amount}})", + "dataset.data.edit.Index": "数据索引({{amount}})", "dataset.data.edit.divide_content": "分块内容", "dataset.data.input is empty": "数据内容不能为空 ", "dataset.dataset_name": "知识库名称", @@ -980,7 +989,7 @@ "permission.manager": "管理员", "permission.read": "读权限", "permission.write": "写权限", - "permission_other": "其他权限(多选)", + "permission_other": "其他权限(多选)", "please_input_name": "请输入名称", "plugin.App": "选择应用", "plugin.Currentapp": "当前应用", @@ -1340,5 +1349,26 @@ "zoomin_tip": "缩小 ctrl -", "zoomin_tip_mac": "缩小 ⌘ -", "zoomout_tip": "放大 ctrl +", - "zoomout_tip_mac": "放大 ⌘ +" -} + "zoomout_tip_mac": "放大 ⌘ +", + "no_database_connection": "还没有连接数据库,", + "click_config_database": "点击配置数据库", + "core.dataset.table": "数据表", + "core.dataset.search.mode.database": "数据库检索", + "core.dataset.search.mode.database desc": "使用向量检索查找数据库中可能相关的表和列", + "core.dataset.training.databaseSchema mode": "数据库结构", + "core.dataset.import.databaseSchema Tip": "对数据库中的表信息进行自动处理,使其更利于检索,以提高SQL生成的准确率", + "database_search": "数据库搜索", + "annotation_answer": "标注答案", + "core.dataset.search.Database search": "数据库检索", + "search_model": "检索模型", + "search_model_desc": "用于生成可在数据库中检索的SQL语句,并进行检索与汇总,生成可用于对话的文本。", + "search_model_tip": "使用非推理模型、参数量大的模型效果更佳。", + "other_knowledge_base": "其他知识库", + "table_not_exist": "不存在", + "core.app.workflow.search_knowledge.database": "数据库", + "semicolon": ";", + "database_sql_query": "数据库检索的 SQL 语句", + "search_result": "检索结果", + "comma": "、", + "set_avatar_in_edit_modal": "点击设置头像" +} \ No newline at end of file diff --git a/packages/web/i18n/zh-CN/dashboard_evaluation.json b/packages/web/i18n/zh-CN/dashboard_evaluation.json index abdc713033d8..1e72432e9070 100644 --- a/packages/web/i18n/zh-CN/dashboard_evaluation.json +++ b/packages/web/i18n/zh-CN/dashboard_evaluation.json @@ -28,7 +28,7 @@ "error": "异常", "error_tooltip": "有异常任务。 \n点击后,打开任务详情弹窗", "eval_file_check_error": "评测文件校验失败", - "evaluating": "评估中", + "evaluating": "评测中", "evaluation": "应用评测", "evaluation_created": "评测任务创建成功", "evaluation_export_title": "问题,标准答案,实际回答,状态,综合得分", @@ -49,5 +49,332 @@ "task_name": "任务名称", "team_has_running_evaluation": "当前团队已有正在运行的应用评测,请等待完成后再创建新的应用评测", "template_csv_file_select_tip": "仅支持严格按照模板填写的 {{fileType}} 文件", - "variables": "全局变量" + "variables": "全局变量", + "all_apps": "全部应用", + "search_evaluation_task": "搜索任务名或应用版本", + "create_new_task": "新建任务", + "task_name_column": "任务名", + "progress_column": "进度", + "evaluation_app_column": "评测应用", + "app_version_column": "应用版本", + "evaluation_result_column": "评测结果", + "start_finish_time_column": "开始时间/完成时间", + "executor_column": "执行人", + "waiting": "等待中", + "evaluating_status": "评测中", + "completed_status": "已完成", + "queuing_status": "排队中", + "running_status": "进行中", + "error_data_tooltip": "{{count}} 条数据执行异常,可点击查看详情", + "rename": "重命名", + "delete": "删除", + "confirm_delete_task": "确认删除该任务?", + "evaluation_tasks_tab": "评测任务", + "evaluation_tasks": "评测任务", + "evaluation_datasets_tab": "评测数据集", + "evaluation_dimensions_tab": "评测维度", + "create_new": "新建", + "retry_error_data": "重试异常数据", + "dataset_name_placeholder": "名称", + "create_new_dataset": "新建", + "smart_generation": "智能生成", + "file_import": "文件导入", + "confirm_delete_dataset": "确认删除该数据集?", + "error_details": "异常详情", + "status_queuing": "排队中", + "status_parsing": "文件解析中", + "status_generating": "数据生成中", + "status_generate_error": "生成异常", + "status_ready": "已就绪", + "status_parse_error": "解析异常", + "click_to_view_details": "点击查看详情", + "table_header_name": "名称", + "table_header_data_count": "数据量", + "table_header_time": "创建/更新时间", + "table_header_status": "状态", + "table_header_creator": "创建人", + "create_dimension": "新建", + "search_dimension": "搜索评测维度", + "delete_failed": "删除失败", + "delete_success": "删除成功", + "builtin": "内置", + "confirm_delete_dimension": "确认删除该维度?", + "dimension_name": "维度名", + "description": "介绍", + "create_update_time": "创建/更新时间", + "creator": "创建人", + "all": "全部", + "app": "应用", + "citation_template": "引用模板", + "correctness": "正确性", + "conciseness": "简洁性", + "harmfulness": "有害性", + "controversiality": "争议性", + "creativity": "创造性", + "criminality": "犯罪性", + "depth": "深度性", + "details": "细节性", + "dimension_name_label": "维度名", + "dimension_description_label": "介绍", + "prompt_label": "提示词", + "citation_template_button": "引用模板", + "test_run_title": "试运行", + "question_label": "问题", + "question_placeholder": "请输入问题内容", + "answer_label": "答案", + "reference_answer_label": "参考答案", + "reference_answer_placeholder": "请输入参考答案", + "actual_answer_label": "实际回答", + "actual_answer_placeholder": "请输入实际回答", + "run_result_label": "运行结果", + "start_run_button": "开始运行", + "running_text": "运行中", + "run_success": "运行成功", + "run_failed": "运行失败", + "not_run": "未运行", + "score_unit": "分", + "error_info_label": "报错信息:", + "no_feedback_text": "暂无反馈内容", + "dimension_create_back": "退出", + "dimension_create_test_run": "试运行", + "dimension_create_confirm": "确认", + "dimension_create_success": "创建成功", + "dimension_create_name_required": "请输入名称", + "dimension_create_prompt_required": "请输入提示词", + "dimension_get_data_failed": "获取维度数据失败", + "dimension_data_not_exist": "维度数据不存在", + "dimension_update_success": "更新成功", + "dimension_update_failed": "更新失败", + "dimension_name_required": "请输入名称", + "dimension_back": "退出", + "dimension_test_run": "试运行", + "dimension_save": "保存", + "file_import_back": "退出", + "file_import_name_label": "数据集名", + "file_import_name_placeholder": "请输入名称", + "file_import_select_file": "请选择文件", + "file_import_success": "文件导入成功", + "file_import_file_label": "文件", + "file_import_download_template": "点击下载文件模板", + "file_import_download_template_tip": "一行一个词条,第1列为【提问】,第2列为【答案】,第1行默认为表头,不会作为词条内容。例如:", + "file_import_auto_evaluation_label": "导入后自动进行数据质量评测", + "file_import_auto_evaluation_tip": "开启后将自动对导入的数据进行质量评测", + "file_import_evaluation_model_label": "质量评测模型", + "file_import_evaluation_model_placeholder": "请选择质量评测模型", + "file_import_confirm": "确认", + "manage_dimension": "管理维度", + "selected_count": "已选", + "dimension_config_tip": "维度配置说明", + "custom": "自定义", + "select_model_placeholder": "请选择模型", + "create_new_task_modal": "新建任务", + "task_name_input": "任务名", + "evaluation_app_select": "评测应用", + "evaluation_app_support_tip": "当前支持简易应用和工作流,暂不支持插件。", + "evaluation_app_select_placeholder": "请选择评测应用", + "evaluation_app_version_select": "评测应用版本", + "evaluation_app_version_select_placeholder": "请选择评测应用版本", + "evaluation_dataset_select": "评测数据集", + "evaluation_dataset_select_placeholder": "请选择评测数据集", + "create_import_dataset": "新建/导入", + "evaluation_dimensions_label": "评测维度", + "evaluation_dimensions_recommendation": "评测应用包含知识库搜索和AI对话环节,推荐使用 {{num}} 个维度进行评估", + "builtin_dimension": "内置", + "custom_dimension": "自定义", + "config_params": "配置参数", + "score_aggregation_method": "分数聚合方式", + "evaluation_dimensions": "评测维度", + "dimension": "维度", + "judgment_threshold": "判定阈值", + "judgment_threshold_tip": "用于判定测试数据在单个维度中表现是否符合预期,分数低于阈值时,将标记为不符合预期,可辅助识别存在问题的数据,分值为 1~100。\n评测后查看任务时可根据需要再次调整。", + "comprehensive_score_weight": "综合评分权重", + "comprehensive_score_weight_tip": "将按照指定权重计算测试数据全部维度的综合评分,可根据应用使用场景所关注的维度进行设置。\n评测后查看任务时可根据需要再次调整。", + "comprehensive_score_weight_sum": "综合评分权重和:", + "intelligent_generation_dataset": "智能生成数据集", + "dataset_name_input": "名称", + "dataset_name_input_placeholder": "请输入数据集名称", + "generation_basis": "生成依据", + "select_knowledge_base": "选择知识库", + "data_amount": "数据量", + "generation_model": "生成模型", + "generation_model_placeholder": "请选择生成模型", + "high_quality": "质量高", + "needs_improvement": "待优化", + "abnormal": "评测异常", + "not_evaluated": "未评测", + "detail_evaluating": "评测中", + "modify_result": "修改结果", + "restart_evaluation": "重新评测", + "evaluation_error_message": "评测过程中出现异常,请重新评测", + "high_quality_feedback": "该问题质量较高,表述清晰准确,符合标准要求。问题描述完整,答案准确且具有实用性。", + "needs_improvement_feedback": "该问题相对清晰,但可能需要进一步优化。建议增加更多上下文信息,使问题更加具体和明确,以便提供更准确的答案。", + "evaluation_service_error": "评测服务异常", + "edit_data": "编辑数据", + "enter_question": "请输入问题", + "question_required": "问题不能为空", + "reference_answer": "参考答案", + "enter_reference_answer": "请输入参考答案", + "reference_answer_required": "参考答案不能为空", + "quality_evaluation": "质量评测", + "quality_evaluation_btn_text": "质量评测", + "cancel": "取消", + "save": "仅保存", + "save_and_next": "保存并下一个", + "manually_calibrated": "已人工修改结果", + "modify_evaluation_result_title": "修改评测结果", + "evaluation_result_label": "评测结果", + "modify_reason_label": "修改理由", + "modify_reason_input_placeholder": "修改理由", + "no_data": "暂无数据", + "search": "搜索", + "settings": "设置", + "add_data": "追加数据", + "ai_generate": "智能生成", + "manual_add": "手动新增", + "no_answer": "暂无答案", + "confirm_delete_data": "确认删除该数据?", + "no_evaluation_result_click": "还没有测评结果,点击", + "start_evaluation_action": "开始测评", + "evaluation_dataset": "评测数据集", + "evaluation_status": "评测状态", + "manually_add_data_modal": "手动新增数据", + "question_input_label": "问题", + "reference_answer_input_label": "参考答案", + "auto_quality_eval_after_add": "数据质量评测", + "auto_quality_eval_add_tip": "开启后,将自动对数据的问答相关度、合理性等进行评测,给出数据质量评价及原因,可根据评价对数据进行优化。", + "quality_eval_model_label": "质量评测模型", + "select_quality_eval_model_placeholder": "请选择质量评测模型", + "confirm": "确认", + "confirm_quality_evaluation": "确认对全部数据({{total}})进行质量评测吗?", + "model_change_notice": "更改模型后将对后续评测的任务生效。", + "evaluation_model": "评测模型", + "select_evaluation_model": "请选择评测模型", + "evaluation_abnormal": "评测异常", + "error_message": "报错信息", + "file_parse_error": "文件解析异常", + "delete_file": "删除文件", + "reparse": "重新解析", + "data_generation_error": "条数据生成异常", + "source_knowledge_base": "依据知识库", + "source_chunk": "依据分块", + "operations": "操作", + "retry": "重试", + "retry_all": "全部重试", + "error_info": "异常信息", + "manual_add_data": "手动新增数据", + "max_3000_chars": "最多 3000 字", + "please_enter_question": "请输入问题", + "question_max_3000_chars": "问题不能超过3000字", + "please_enter_reference_answer": "请输入参考答案", + "reference_answer_max_3000_chars": "参考答案不能超过3000字", + "auto_quality_evaluation": "新增后自动进行数据质量评测", + "quality_evaluation_tip": "将对数据的问答相关度、合理性等进行评测,给出数据质量评价及原因,可根据评价对数据进行优化。", + "quality_evaluation_model": "质量评测模型", + "please_select_evaluation_model": "请选择质量评测模型", + "summary_pending": "待生成", + "summary_generating": "生成中", + "summary_done": "已完成", + "summary_failed": "生成失败", + "method_mean": "平均值", + "method_median": "中位数", + "prompt_cannot_be_empty": "提示词不允许为空", + "please_select_model": "请选择模型", + "run_failed_please_retry": "运行失败,请重试", + "intelligent_generate_data": "智能生成数据", + "evaluation_completed": "评估完成", + "generation_error": "生成异常", + "data_generating": "数据生成中", + "delete_dataset_error": "删除数据集异常", + "create_failed": "创建失败", + "no_dimension_data": "暂无维度数据", + "please_select_dimension_first": "请先选择该维度", + "model_evaluation_tip": "语言模型可判断实际回答和参考答案中的文本内容是否匹配;\n索引模型可将实际回答和参考答案转成向量,进一步评估语义相似性。", + "create_new_dimension": "新建维度", + "retry_success": "重试成功", + "data_generation_error_count": "{{count}}条数据生成异常", + "builtin_answer_correctness_name": "回答准确度", + "builtin_answer_correctness_desc": "衡量生成的回答与参考答案在事实上的一致性,评估其是否准确无误。", + "builtin_answer_similarity_name": "语义相似度", + "builtin_answer_similarity_desc": "评估生成回答与参考答案在语义上的匹配程度,判断其是否表达了相同的核心信息。", + "builtin_answer_relevancy_name": "回答相关度", + "builtin_answer_relevancy_desc": "衡量生成回答与提问之间的契合度,判断回答是否紧扣问题。", + "builtin_faithfulness_name": "回答忠诚度", + "builtin_faithfulness_desc": "评估生成回答是否忠实于提供的上下文信息,判断是否存在虚构或不实内容。", + "builtin_context_recall_name": "检索匹配度", + "builtin_context_recall_desc": "衡量检索系统是否能够获取回答所需的所有关键信息,评估其检索的完整性。", + "builtin_context_precision_name": "检索精确度", + "builtin_context_precision_desc": "衡量检索内容中是否优先返回高价值信息,反映排序质量与信息密度。", + "join_evaluation_dataset": "加入评测数据集", + "not_join_evaluation_dataset": "不加入评测数据集", + "create_new_dataset_btn_text": "新建数据集", + "please_select_evaluation_dataset": "请选择评测数据集", + "join_knowledge_base": "加入知识库", + "all_data_with_count": "全部数据({{num}})", + "question_data_with_count": "问题数据({{num}})", + "error_data_with_count": "异常数据({{num}})", + "export_data": "导出", + "retry_action": "重试", + "basic_info": "基本信息", + "application": "应用", + "version": "版本", + "evaluation_dataset_name": "评测数据集", + "start_time": "开始时间", + "end_time": "结束时间", + "executor_name": "执行人", + "app_with_search_and_chat_recommendation": "评测应用包含知识库搜索和AI对话环节,已推荐使用 3 个维度进行评估", + "app_with_chat_recommendation": "评测应用包含AI对话环节,已推荐使用 1 个维度进行评估", + "app_with_search_recommendation": "评测应用包含知识库搜索环节,已推荐使用 2 个维度进行评估", + "no_dimensions_added": "还没有添加评测维度,", + "click_to_add": "点击添加", + "meets_expectation": "符合预期!", + "below_expectation": "低于预期分数!", + "summary_generation_error": "总结内容生成异常,", + "error_message_prefix": "报错信息:", + "summary_pending_generation": "总结内容待生成", + "summary_generating_content": "总结内容生成中", + "data_with_count": "数据({{data}})", + "search_placeholder": "搜索", + "detail_title": "详情", + "modify_dataset_simultaneously": "同时修改评测数据集", + "retry_button": "重试", + "edit_action": "编辑", + "delete_action": "删除", + "confirm_delete_data_in_task": "确认在当前任务中删除该数据?", + "view_full_response": "查看完整响应", + "abnormal_status": "异常", + "question_field": "问题", + "reference_answer_field": "参考答案", + "actual_answer_field": "实际回答", + "no_answer_available": "暂无回答", + "comprehensive_score_title": "综合评分", + "dimension_score_title": "维度评分", + "error_data_calculation_notice": "{{count}} 条数据执行异常,仅使用执行成功的数据来计算分数。", + "regenerate_summary_content": "重新生成总结内容", + "processing_status": "处理中...", + "view_results_after_completion": "完成后可查看评测结果", + "all_data_execution_error": "全部数据执行异常,", + "check_error_details": "请查看异常数据中的详细原因,", + "click_to_retry": "点击重试", + "comprehensive_score_weight_description": "按照指定权重计算测试数据全部维度的综合评分,可根据应用使用场景所关注的维度进行设置。", + "no_data_available": "暂无数据", + "case_id_column": "评估项ID", + "question_column": "问题", + "comprehensive_score_column": "综合评分", + "error_info_column": "异常信息", + "request_failed": "请求失败", + "retry_request_submitted": "重试请求已提交", + "retry_failed": "重试失败", + "save_success": "保存成功", + "save_failed": "保存失败", + "summary_generation_request_submitted": "总结生成请求已提交", + "generate_summary_failed": "生成总结失败", + "export_failed": "导出失败", + "load_failed": "加载失败", + "export_success": "导出成功", + "no_dimension_data_cannot_generate_summary": "暂无维度数据,无法生成总结", + "evaluation_dimension_missing_model_config": "评测维度缺少模型配置", + "evaluation_dimension_missing_model_config_desc": "以下评测维度缺少模型配置,请先完善配置:{{names}}", + "example_highest_mountain_question": "世界最高峰是什么", + "example_highest_mountain_answer": "珠穆朗玛峰", + "data_updated_before_retest": "数据已更新,重新评测前将先自动保存" } diff --git a/packages/web/i18n/zh-CN/database_client.json b/packages/web/i18n/zh-CN/database_client.json new file mode 100644 index 000000000000..de9523346aac --- /dev/null +++ b/packages/web/i18n/zh-CN/database_client.json @@ -0,0 +1,24 @@ +{ + "client_destory_error": "数据库连接客户端销毁失败", + "client_not_found": "数据库连接客户端未正确初始化", + "not_support_databaseType": "不支持的数据库类型", + "not_implemented_databaseType": "未实现的数据库客户端", + "connection_failed": "数据库连接失败,请检查连接配置", + "authentication_failed": "数据库认证失败,请检查用户名和密码以及数据库权限是否正确开放", + "database_not_exist": "数据库不存在,请检查数据库名称是否正确", + "database_port_error": "数据库端口错误,请检查端口是否正确", + "connection_address_error": "连接地址错误,请检查地址是否正确", + "connection_timeout": "数据库连接超时,请检查网络是否通畅", + "connection_refused": "数据库连接被拒绝,请检查数据库是否允许外部连接", + "connection_check_error": "连接测试失败,请检查网络是否通畅", + "connection_lost": "数据库连接被中断,请检查网络是否通畅", + "host_error": "连接地址错误,请检查地址是否正确", + "invalid_table_name": "无效的表名", + "fetch_info_error": "获取数据库信息失败", + "database_config_not_found": "未配置数据库连接信息,请先配置", + "table_not_found": "获取表信息失败,表不存在", + "illeagal_table_info": "非法的表信息,请检查表信息是否正确", + "op_unknown_database_error": "尝试操作您的数据库时发生未知错误,请检查数据库连接是否正常", + "dative_service_error": "连接Dative服务时发生错误,请检查配置是否正确", + "tableNamesDuplicate": "存在多个重复表名" +} \ No newline at end of file diff --git a/packages/web/i18n/zh-CN/dataset.json b/packages/web/i18n/zh-CN/dataset.json index aa3cb957a8e0..1fe6194b1a90 100644 --- a/packages/web/i18n/zh-CN/dataset.json +++ b/packages/web/i18n/zh-CN/dataset.json @@ -69,7 +69,7 @@ "dataset.ReTrain": "重试", "dataset.Training Process": "训练状态", "dataset.Training_Count": "{{count}} 组训练中", - "dataset.Training_Errors": "异常 ({{count}})", + "dataset.Training_Errors": "异常({{count}})", "dataset.Training_QA": "{{count}} 组问答对训练中", "dataset.Training_Status": "训练状态", "dataset.Training_Waiting": "需等待 {{count}} 组数据", @@ -116,7 +116,7 @@ "import_select_file": "选择文件", "import_select_link": "输入链接", "index_prefix_title": "将标题加入索引", - "index_prefix_title_tips": "自动给索引所有索引加标题名", + "index_prefix_title_tips": "自动给所有索引加标题名", "index_size": "索引大小", "index_size_tips": "向量化时内容的长度,系统会自动按该大小对分块进行进一步的分割。", "input_required_field_to_select_baseurl": "请先输入必填信息", @@ -138,7 +138,7 @@ "noSelectedFolder": "没有选择文件夹", "noSelectedId": "没有选择 ID", "noValidId": "没有有效的 ID", - "open_auto_sync": "开启定时同步后,系统将会每天不定时尝试同步集合,集合同步期间,会出现无法搜索到该集合数据现象。", + "open_auto_sync": "开启自动同步后,系统将会每天不定时尝试同步集合,集合同步期间,会出现无法搜索到该集合数据现象。", "other_dataset": "第三方知识库", "paragraph_max_deep": "最大段落深度", "paragraph_split": "按段落分块", @@ -187,10 +187,10 @@ "split_sign_period": "句号", "split_sign_question": "问号", "split_sign_semicolon": "分号", - "start_sync_dataset_tip": "确实开始同步整个知识库?", + "start_sync_dataset_tip": "确定开始同步整个知识库?", "status_error": "运行异常", "sync_collection_failed": "同步集合错误,请检查是否能正常访问源文件", - "sync_schedule": "定时同步", + "sync_schedule": "自动同步", "sync_schedule_tip": "仅会同步已存在的集合。包括链接集合以及 API 知识库里所有集合。系统会每天进行轮询更新,无法确定具体的更新时间。", "tag.Add_new_tag": "新建标签", "tag.Edit_tag": "编辑标签", @@ -203,7 +203,7 @@ "tag.tags": "标签", "tag.total_tags": "共{{total}}个标签", "template_dataset": "模版导入", - "template_file_invalid": "模板文件格式不正确,应该是首列为 q,a,indexes 的 csv 文件", + "template_file_invalid": "模板文件格式不正确,应该是首行为 q,a,indexes 的 csv 文件", "template_mode": "模板导入", "the_knowledge_base_has_indexes_that_are_being_trained_or_being_rebuilt": "知识库有训练中或正在重建的索引", "total_num_files": "共 {{total}} 个文件", @@ -222,5 +222,125 @@ "website_info": "网站信息", "yuque_dataset": "语雀知识库", "yuque_dataset_config": "配置语雀知识库", - "yuque_dataset_desc": "可通过配置语雀文档权限,使用语雀文档构建知识库,文档不会进行二次存储" + "yuque_dataset_desc": "可通过配置语雀文档权限,使用语雀文档构建知识库,文档不会进行二次存储", + "enterprise_database": "数据库", + "enterprise_database_desc": "授权 MySQL 数据库后读取选定的数据表,检索时连接数据库获取相关信息", + "enterprise_database_embedding_model_tip": "索引模型可将数据库关键信息(截取部分数据的表名、表描述、列名、列描述)转成向量,用于进行语义检索。", + "database_structure_change_tip": "数据结构发生变化时,如数据表名称变更或其中的列名变更等,请手动刷新数据源。", + "refresh_data_source": "刷新数据源", + "search_name_or_description": "搜索名称或描述", + "connect_database": "连接数据库", + "data_config": "数据配置", + "database_type": "数据库类型", + "mysql_description": "支持 5.7 及其以上版本", + "database_host": "数据库地址", + "host_placeholder": "请输入IP地址或域名", + "host_tips": "请确保数据库所在与本平台连通,填入可访问的地址", + "host_required": "数据库地址不能为空", + "port": "端口号", + "port_required": "端口号不能为空", + "database_name": "数据库名称", + "database_username": "数据库用户名", + "database_password": "数据库密码", + "connection_pool_size": "连接池大小", + "connection_pool_required": "连接池大小不能为空", + "connection_pool_min_error": "连接池大小不能小于1", + "connection_pool_max_error": "连接池大小不能大于100", + "connect_next_step": "连接并下一步", + "database_config_title": "知识库将调用已选数据表中的数据进行索引", + "search_tables": "数据表名", + "table_selection_warning": "存在未选择内容", + "table_description": "数据表描述", + "default_table_description": "默认值是数据表表自带的描述", + "column_configuration": "数据列配置", + "search_columns": "列名", + "column_name": "列名", + "column_type": "类型", + "column_description": "描述", + "column_enabled": "启用", + "default_column_description": "默认值是数据表表自带的描述", + "confirm": "确认", + "edit_database_config_warning": "数据源已配置,发生 {{changedCount}} 个数据表存在列的更改,{{deletedCount}} 个数据表已不存在,请修正最新数据。", + "edit_database_warning": "修改数据库信息后,当前正在访问数据库的应用将断开重连,可能会导致正在运行的应用无法检索到结果。", + "database_config": "配置", + "refresh_success": "刷新成功", + "refresh_datasource": "刷新数据源", + "reconnect_success": "重新连接数据库,变更配置成功", + "auth_failed": "数据库连接失败,认证未通过,请检查用户名和密码。", + "connection_failed": "连接失败", + "reconnecting": "重新连接中", + "reconnect_success_detail": "已重新连接数据库,变更配置成功", + "table_changes_notice": "发现 {{changedCount}} 个数据表存在列的变更,{{deletedCount}} 个数据表已不存在,请核查最新数据。", + "reconnect_database": "重新连接数据库", + "column_desc_accuracy_tip": "为了提高问答准确率,请准确填写列描述,用来解释此列数据的含义和用途,大模型将会根据列描述选择对应的列数据进行检索和生成回答", + "default_table_desc_tip": "默认使用数据表中定义的描述。", + "column_enabled_tip": "启用后表示使用该列数据进行检索及回答", + "connecting": "正在连接", + "test_connectivity": "测试连通性", + "connect_and_next": "连接并下一步", + "connection_network_error": "连接失败,请检查网络连接", + "validate_ip_tip": "该输入项禁止使用本地回环地址,如:localhost、127.x.x.x、0.0.0.0", + "database": "数据库", + "search_model": "检索模型", + "search_model_desc": "用于生成可在数据库中检索的SQL语句,并进行检索与汇总,生成可用于对话的文本。", + "search_model_tip": "使用非推理模型、参数量大的模型效果更佳。", + "other_knowledge_base": "其他知识库", + "database_search": "数据库检索", + "description": "描述", + "remove": "移除", + "confirm_remove_database_table": "确认移除该数据表及其所有数据配置?", + "process.databaseSchema":"数据库结构索引生成", + "database_url": "数据库地址", + "database_url_placeholder": "例如: localhost 或 192.168.1.100", + "database_url_required": "数据库地址不能为空", + "database_port": "端口", + "database_username_placeholder": "数据库用户名", + "database_username_required": "用户名不能为空", + "database_password_placeholder": "数据库密码", + "database_password_required": "密码不能为空", + "database_max_connections": "最大连接数", + "database_required_fields": "请填写所有必填字段", + "save_database_config": "保存配置", + "confirm_update_database_config": "确认更新数据库配置?", + "confirm_create_database_config": "确认创建数据库配置?", + "database_config_updated": "数据库配置已更新", + "data_index_column_description": "SQL字段描述索引", + "data_index_column_value":"SQL字段示例值索引", + "error_create_datasetcollection": "创建数据集失败,请联系管理员", + "data_source_refreshed": "数据源已刷新,", + "found": "发现", + "tables_with_column_changes": "发现 {{modifiedTablesCount}} 个数据表存在列的变更,", + "tables_not_exist": "发现 {{delTablesCount}} 个数据表已不存在,", + "please_check_latest_data": "请核查最新数据。", + "has_unfilled_content": "存在未填写的内容", + "has_column_changes": "存在列的变更", + "connection_success": "连接成功", + "tables_added": "新增 {{addedTables}} 个数据表", + "tables_modified": "发现 {{modifiedTables}} 个数据表存在列的变更,", + "tables_deleted": "发现 {{deletedTables}} 个数据表已不存在,", + "please_verify_data": "请核查最新数据。", + "changes_detected": "发现", + "new_columns_added_disabled": "个新增列(默认未启用),", + "columns_no_longer_exist": "个列已不存在,", + "check_latest_data": "请核查最新数据。", + "config": "配置", + "refresh_failed": "刷新失败", + "unknown_error": "未知错误", + "no_data_available": "暂无数据", + "database_sql_query": "数据库检索的 SQL 语句", + "search_result": "检索结果", + "no_search_result": "暂无检索结果", + "database_address_required": "数据库地址不能为空", + "ip_format_invalid_range": "IP地址格式不正确,每段数值应在0-255之间", + "database_address_format_invalid": "数据库地址格式不正确,请输入有效的IP地址或域名", + "tables_modified_and_deleted": "发现 {{modifiedTables}} 个数据表存在列的变更,{{deletedTables}} 个数据表已不存在,", + "tables_no_longer_exist_comma": "个数据表已不存在,", + "no_data_in_database": "该数据库中没有数据", + "database_host_tooltip": "请输入可与本平台连通的正确地址", + "database_host_port_tip": "范围:{{min}}~{{max}}", + "connection_pool_size_tooltip": "应用在数据库进行检索时需与数据库建立连接,为避免影响数据库正常业务,将建立连接池,保障应用与数据库的连接数不超过连接池的大小。可根据数据库性能及业务并发访问量合理分配。", + "database_table_desc_tip": "为了提高问答准确率,请准确填写数据表描述,用来解释该表的含义和用途,大模型将会根据描述识别并调用该表。", + "database_tables": "数据表", + "remove_warning": "移除警告" + } diff --git a/packages/web/i18n/zh-CN/evaluation.json b/packages/web/i18n/zh-CN/evaluation.json new file mode 100644 index 000000000000..44321bb13850 --- /dev/null +++ b/packages/web/i18n/zh-CN/evaluation.json @@ -0,0 +1,130 @@ +{ + "dataset_collection_not_found": "评估数据集未找到", + "dataset_data_not_found": "评估数据集数据未找到", + "name_required": "名称必填", + "name_too_long": "名称过长", + "name_duplicate": "名称已存在", + "description_too_long": "描述过长", + "target_required": "评估目标必填", + "target_invalid_config": "评估目标配置无效", + "target_app_id_missing": "应用ID缺失", + "target_version_id_missing": "应用版本ID缺失", + "evaluators_required": "评估器必填", + "evaluator_invalid_config": "评估器配置无效", + "evaluator_invalid_score_scaling": "评估器分数缩放值无效,必须是大于0且小于等于10000的正数(支持小数,如0.01表示缩小100倍)", + "user_input_required": "用户输入必填", + "expected_output_required": "预期输出必填", + "invalid_format": "无效的格式", + "id_required": "评估ID不能为空", + "item_id_required": "评估项ID不能为空", + "data_item_id_required": "数据项ID不能为空", + "invalid_context": "无效的上下文", + "invalid_retrieval_context": "无效的检索上下文", + "insufficient_permission": "权限不足", + "app_not_found": "应用未找到", + "task_not_found": "评估任务未找到", + "item_not_found": "评估项未找到", + "invalid_status": "无效的状态", + "invalid_state_transition": "无效的状态转换,只有排队状态或手动停止的评估可以启动", + "only_running_can_stop": "只有运行中的评估可以停止", + "item_no_error_to_retry": "评估项无错误可重试", + "target_execution_error": "评估目标执行错误", + "dataset_load_failed": "数据集加载失败", + "target_config_invalid": "目标配置无效", + "evaluators_config_invalid": "评估器配置无效", + "unsupported_target_type": "不支持的目标类型", + "app_version_not_found": "应用版本未找到", + "duplicate_dataset_name": "数据集名称重复", + "no_data_in_collections": "集合中无数据", + "update_failed": "更新失败", + "task_system_error": "处理评估任务时发生系统错误", + "manually_stopped": "手动停止", + "evaluator_execution_errors": "评估器执行错误", + + "metric_not_found": "评估指标未找到", + "metric_un_auth": "无权操作该评估指标", + "metric_name_required": "指标名称不能为空", + "metric_name_too_long": "指标名称不能超过100个字符", + "metric_description_required": "指标描述不能为空", + "metric_description_too_long": "指标描述不能超过100个字符", + "metric_prompt_required": "指标提示词不能为空", + "metric_prompt_too_long": "指标提示词不能超过4000个字符", + "metric_type_required": "指标类型不能为空", + "metric_type_invalid": "指标类型无效", + "metric_name_invalid": "指标名称无效", + "metric_builtin_cannot_modify": "内置指标不能修改", + "metric_builtin_cannot_delete": "内置指标不能删除", + "metric_id_required": "指标ID不能为空", + + "eval_case_required": "评估用例不能为空", + "eval_case_user_input_required": "用户输入不能为空", + "eval_case_user_input_too_long": "用户输入不能超过1000个字符", + "eval_case_actual_output_required": "实际输出不能为空", + "eval_case_actual_output_too_long": "实际输出不能超过4000个字符", + "eval_case_expected_output_required": "期望输出不能为空", + "eval_case_expected_output_too_long": "期望输出不能超过4000个字符", + + "llm_config_required": "LLM配置不能为空", + "llm_model_name_required": "LLM模型名称不能为空", + + "debug_evaluation_failed": "评估调试失败", + + "evaluator_config_required": "评估器配置不能为空", + "evaluator_llm_config_missing": "评估器缺少LLM配置", + "evaluator_embedding_config_missing": "评估器缺少嵌入模型配置", + "evaluator_llm_model_not_found": "LLM模型不存在或获取失败", + "evaluator_embedding_model_not_found": "嵌入模型不存在或获取失败", + "evaluator_request_timeout": "评估器请求超时", + "evaluator_service_unavailable": "评估器服务不可用", + "evaluator_invalid_response": "评估器返回无效响应", + "evaluator_network_error": "评估器网络连接错误", + + "eval_id_required": "评估任务ID不能为空", + "summary_metrics_config_error": "指标配置错误", + "summary_threshold_value_required": "阈值不能为空", + "summary_weight_required": "权重不能为空", + "summary_weight_must_be_number": "权重必须为数字", + "summary_threshold_must_be_number": "阈值必须为数字", + "summary_calculate_type_required": "计算类型不能为空", + "summary_calculate_type_invalid": "计算类型无效", + "summary_no_valid_metrics_found": "没有找到有效的指标", + "summary_stream_response_not_supported": "不支持流式响应", + "summary_weight_sum_must_be_100": "所有指标的权重总和必须等于100", + "summary_model_invalid": "评估总结生成异常,请检查评估模型配置", + + "dataset_collection_id_required": "数据集合 ID 是必需的", + "dataset_collection_update_failed": "数据集合更新失败", + "dataset_model_not_found": "模型未找到", + "dataset_no_data": "数据集不包含数据", + "dataset_data_id_required": "数据集数据 ID 是必需的", + "data_quality_status_invalid": "数据质量状态无效", + "dataset_data_user_input_required": "用户输入是必需的", + "dataset_data_expected_output_required": "预期输出是必需的", + "dataset_data_actual_output_must_be_string": "实际输出必须是字符串", + "dataset_data_context_must_be_array_of_strings": "上下文必须是字符串数组", + "dataset_data_retrieval_context_must_be_array_of_strings": "检索上下文必须是字符串数组", + "dataset_data_enable_quality_eval_required": "启用质量评估标志是必需的", + "dataset_data_evaluation_model_required_for_quality": "启用质量评估时需要评估模型", + "dataset_data_metadata_must_be_object": "元数据必须是对象", + "dataset_data_list_error": "数据集数据列表错误", + "quality_assessment_failed": "质量评估失败", + "data_quality_job_active_cannot_set_high_quality": "质量评估任务进行中,无法设置为高质量状态", + "dataset_task_not_retryable": "任务不可重试", + "dataset_task_job_not_found": "未找到任务作业", + "dataset_task_job_mismatch": "任务作业不属于此集合", + "dataset_task_only_failed_can_delete": "只能删除失败的任务", + "dataset_task_operation_failed": "任务操作失败", + "dataset_task_delete_failed": "数据集任务删除失败", + "fetch_failed_tasks_error": "获取失败任务列表失败", + "file_required": "文件是必需的", + "file_must_be_csv": "文件必须是 CSV 文件", + "csv_invalid_structure": "CSV 文件结构无效", + "csv_parsing_error": "CSV 解析错误", + "csv_no_data_rows": "CSV 文件不包含数据行", + "count_must_be_greater_than_zero": "计数必须大于零", + "count_exceeds_available_data": "请求的计数超过可用数据", + "selected_datasets_contain_no_data": "所选数据集不包含数据", + "model_name_invalid": "评估模型名称必须是字符串", + "model_name_too_long": "评估模型名称不能超过 100 个字符", + "description_invalid_type": "描述必须是字符串" +} diff --git a/packages/web/i18n/zh-CN/file.json b/packages/web/i18n/zh-CN/file.json index bfd7df19c518..895d26025d77 100644 --- a/packages/web/i18n/zh-CN/file.json +++ b/packages/web/i18n/zh-CN/file.json @@ -5,7 +5,7 @@ "Failed_to_get_token": "获取令牌失败", "Image_ID_copied": "已复制ID", "Image_Preview": "图片预览", - "Image_dataset_requires_VLM_model_to_be_configured": "图片数据集需要配置图片理解模型(VLM)才能使用,请先在模型配置中添加支持图片理解的模型", + "Image_dataset_requires_VLM_model_to_be_configured": "图片数据集需要配置图片理解模型(VLM)才能使用,请先在模型配置中添加支持图片理解的模型", "Image_does_not_belong_to_current_team": "图片不属于当前团队", "Image_file_does_not_exist": "图片不存在", "Loading_image": "加载图片中...", @@ -15,6 +15,7 @@ "Please wait for all files to upload": "请等待所有文件上传完成", "bucket_chat": "对话文件", "bucket_file": "知识库文件", + "eval_file": "评测文件", "bucket_image": "图片", "click_to_view_raw_source": "点击查看来源", "common.Some images failed to process": "部分图片处理失败", @@ -35,7 +36,7 @@ "some_file_count_exceeds_limit": "超出 {{maxCount}} 个文件,已自动截取", "some_file_size_exceeds_limit": "部分文件超出 {{maxSize}},已被过滤", "support_file_type": "支持 {{fileType}} 类型文件", - "support_max_count": "最多支持 {{maxCount}} 个文件", + "support_max_count": "最多支持 {{maxCount}} 个文件,", "support_max_size": "单个文件最大 {{maxSize}}", "template_csv_file_select_tip": "仅支持严格按照模板填写的 {{fileType}} 文件", "template_strict_highlight": "严格按照模版", diff --git a/packages/web/i18n/zh-CN/publish.json b/packages/web/i18n/zh-CN/publish.json index fbcbe59fdd53..9be0ac788fbf 100644 --- a/packages/web/i18n/zh-CN/publish.json +++ b/packages/web/i18n/zh-CN/publish.json @@ -19,6 +19,7 @@ "official_account.desc": "通过 API 直接接入微信公众号", "official_account.edit_modal_title": "编辑微信公众号接入", "official_account.name": "微信公众号接入", + "official_account.wechat": "微信公众号", "official_account.params": "微信公众号参数", "private_config": "可见度配置", "publish_name": "名称", diff --git a/packages/web/i18n/zh-CN/workflow.json b/packages/web/i18n/zh-CN/workflow.json index 0493c0c6b0eb..8c396981b771 100644 --- a/packages/web/i18n/zh-CN/workflow.json +++ b/packages/web/i18n/zh-CN/workflow.json @@ -58,7 +58,7 @@ "execute_different_branches_based_on_conditions": "根据一定的条件,执行不同的分支。", "execution_error": "运行错误", "extraction_requirements_description": "提取要求描述", - "extraction_requirements_description_detail": "给AI一些对应的背景知识或要求描述,引导AI更好的完成任务。\\n该输入框可使用全局变量。", + "extraction_requirements_description_detail": "给AI一些对应的背景知识或要求描述,引导AI更好的完成任务。\n该输入框可使用全局变量。", "extraction_requirements_placeholder": "例如: 1. 当前时间为: {{cTime}}。你是一个实验室预约助手,你的任务是帮助用户预约实验室,从文本中获取对应的预约信息。\n2. 你是谷歌搜索助手,需要从文本中提取出合适的搜索词。", "feedback_text": "反馈的文本", "field_description": "字段描述", @@ -140,7 +140,7 @@ "please_enter_node_name": "请输入节点名称", "plugin.Instruction_Tip": "可以配置一段说明,以解释该插件的用途。每次使用插件前,会显示该段说明。支持标准 Markdown 语法。", "plugin.Instructions": "使用说明", - "plugin.global_file_input": "文件链接(弃用)", + "plugin.global_file_input": "文件链接(弃用)", "plugin_file_abandon_tip": "插件全局文件上传已弃用,请尽快调整。可以通过插件输入,添加图片类型输入来实现相关功能。", "plugin_input": "插件输入", "plugin_output_tool": "插件作为工具执行时,该字段是否作为工具响应结果", @@ -150,7 +150,7 @@ "quote_content_placeholder": "可以自定义引用内容的结构,以更好的适配不同场景。可以使用一些变量来进行模板配置\n{{q}} - 主要内容\n{{a}} - 辅助数据\n{{source}} - 来源名\n{{sourceId}} - 来源ID\n{{index}} - 第 n 个引用", "quote_content_tip": "可以自定义引用内容的结构,以更好的适配不同场景。可以使用一些变量来进行模板配置\n{{id}} - 引用数据唯一id\n{{q}} - 主要内容\n{{a}} - 辅助数据\n{{source}} - 来源名\n{{sourceId}} - 来源ID\n{{index}} - 第 n 个引用\n他们都是可选的,下面是默认值:\n{{default}}", "quote_num": "引用", - "quote_prompt_tip": "可以用 {{quote}} 来插入引用内容模板,使用 {{question}} 来插入问题(Role=user)。\n下面是默认值:\n{{default}}", + "quote_prompt_tip": "可以用 {{quote}} 来插入引用内容模板,使用 {{question}} 来插入问题(Role=user)。\n下面是默认值:\n{{default}}", "quote_role_system_tip": "请注意从“引用模板提示词”中移除 {{question}} 变量", "quote_role_user_tip": "请注意在“引用模板提示词”中添加 {{question}} 变量", "raw_response": "原始响应", @@ -176,7 +176,7 @@ "template.ai_chat": "AI 对话", "template.ai_chat_intro": "AI 大模型对话", "template.dataset_search": "知识库搜索", - "template.dataset_search_intro": "调用“语义检索”和“全文检索”能力,从“知识库”中查找可能与问题相关的参考内容", + "template.dataset_search_intro": "调用语义检索、全文检索、数据库检索能力,从“知识库”中查找可能与问题相关的参考内容", "template.forbid_stream": "禁用流输出", "template.forbid_stream_desc": "强制设置嵌套运行的应用,均以非流模式运行", "template.plugin_output": "插件输出", diff --git a/packages/web/i18n/zh-Hant/account_bill.json b/packages/web/i18n/zh-Hant/account_bill.json index aa3a92d61022..399c87368858 100644 --- a/packages/web/i18n/zh-Hant/account_bill.json +++ b/packages/web/i18n/zh-Hant/account_bill.json @@ -58,5 +58,6 @@ "unit_code_void": "統一信用代碼格式錯誤", "update": "更新", "yes": "是", - "yuan": "{{amount}}元" -} + "yuan": "{{amount}}元", + "generate_sql": "產生 SQL" +} \ No newline at end of file diff --git a/packages/web/i18n/zh-Hant/account_info.json b/packages/web/i18n/zh-Hant/account_info.json index 9c5560d48d91..a7bd846f9476 100644 --- a/packages/web/i18n/zh-Hant/account_info.json +++ b/packages/web/i18n/zh-Hant/account_info.json @@ -78,5 +78,7 @@ "user_team_team_name": "團隊", "verification_code": "驗證碼", "you_can_convert": "您可以兌換", - "yuan": "元" + "yuan": "元", + "password_min_length": "密碼至少 8 位", + "password_requirement": "至少包含兩種組合:數字、字母、特殊字符" } diff --git a/packages/web/i18n/zh-Hant/account_model.json b/packages/web/i18n/zh-Hant/account_model.json index 04047acbadb8..9f07f524976a 100644 --- a/packages/web/i18n/zh-Hant/account_model.json +++ b/packages/web/i18n/zh-Hant/account_model.json @@ -3,8 +3,8 @@ "aipoint_usage": "積分消耗", "all": "全部", "api_key": "API 金鑰", - "avg_response_time": "平均調用時長 (秒)", - "avg_ttfb": "平均首字時長 (秒)", + "avg_response_time": "平均調用時長(秒)", + "avg_ttfb": "平均首字時長(秒)", "azure": "Azure", "base_url": "代理地址", "batch_size": "並發請求數", @@ -42,6 +42,7 @@ "duration": "耗時", "edit": "編輯", "edit_channel": "管道設定", + "add_channel": "管道設定", "enable_channel": "啟用", "forbid_channel": "停用", "input": "輸入", @@ -53,9 +54,9 @@ "mapping": "模型對映", "mapping_tip": "需填寫一個有效 Json。\n可在向實際地址傳送請求時,對模型進行對映。\n例如:\n{\n \n \"gpt-4o\": \"gpt-4o-test\"\n\n}\n\n當 FastGPT 請求 gpt-4o 模型時,會向實際地址傳送 gpt-4o-test 的模型,而不是 gpt-4o。", "maxToken_tip": "模型 max_tokens 參數", - "max_rpm": "最大RPM (每分鐘請求數)", + "max_rpm": "最大RPM(每分鐘請求數)", "max_temperature_tip": "模型 temperature 參數,不填則代表模型不支援 temperature 參數。", - "max_tpm": "最大TPM (每分鐘Token數)", + "max_tpm": "最大TPM(每分鐘Token數)", "model": "模型", "model_error_rate": "失敗率", "model_error_request_times": "失敗次數", @@ -89,5 +90,7 @@ "vlm_model": "圖片理解模型", "vlm_model_tip": "用於知識庫中對文件中的圖片進行額外的索引生成", "volunme_of_failed_calls": "調用失敗量", - "waiting_test": "等待測試" + "waiting_test": "等待測試", + "evaluation_model": "評測模型", + "evaluation_model_tip": "用於應用評測,及評測數據集中針對數據質量的評測。" } diff --git a/packages/web/i18n/zh-Hant/account_team.json b/packages/web/i18n/zh-Hant/account_team.json index 02b1830953c9..314a35bd7fff 100644 --- a/packages/web/i18n/zh-Hant/account_team.json +++ b/packages/web/i18n/zh-Hant/account_team.json @@ -51,6 +51,10 @@ "create_dataset": "創建知識庫", "create_dataset_folder": "創建知識庫文件夾", "create_department": "創建子部門", + "create_evaluation_dataset_collection": "創建評測數據集", + "create_evaluation_dataset_data": "創建評測數據", + "create_evaluation_task": "創建評測任務", + "create_evaluation_metric": "創建評測維度", "create_group": "建立群組", "create_invitation_link": "建立邀請連結", "create_invoice": "開發票", @@ -74,6 +78,16 @@ "delete_dataset_collaborator": "知識庫權限刪除", "delete_department": "刪除子部門", "delete_evaluation": "刪除應用評測數據", + "delete_evaluation_dataset_collection": "刪除評測數據集", + "delete_evaluation_dataset_data": "刪除評測數據", + "delete_evaluation_dataset_task": "刪除評測數據集任務", + "delete_evaluation_task": "刪除評測任務", + "delete_evaluation_task_item": "刪除評測任務項目", + "delete_evaluation_metric": "刪除評測維度", + "delete_evaluation_task_data_item": "刪除評測任務數據項", + "update_evaluation_task_data_item": "更新評測任務數據項", + "retry_evaluation_task_data_item": "重試評測任務數據項", + "export_evaluation_task_data_items": "導出評測任務數據項", "delete_from_org": "移出部門", "delete_from_team": "移出團隊", "delete_group": "刪除群組", @@ -87,6 +101,9 @@ "export_app_chat_log": "導出應用聊天記錄", "export_bill_records": "導出賬單記錄", "export_dataset": "導出知識庫", + "export_evaluation_task_items": "導出評測任務項目", + "generate_evaluation_summary": "生成評估總結報告", + "update_evaluation_summary_config": "更新評估總結配置", "export_members": "導出成員", "forbid_hint": "停用後,該邀請連結將失效。該操作不可撤銷,是否確認停用?", "forbid_success": "停用成功", @@ -97,6 +114,7 @@ "has_forbidden": "已失效", "has_invited": "已邀請", "ignore": "忽略", + "import_evaluation_dataset_data": "匯入評測數據", "inform_level_common": "一般", "inform_level_emergency": "緊急", "inform_level_important": "重要", @@ -168,6 +186,8 @@ "log_export_app_chat_log": "【{{name}}】導出了名為【{{appName}}】的【{{appType}}】的聊天記錄", "log_export_bill_records": "【{{name}}】導出了賬單記錄", "log_export_dataset": "【{{name}}】導出了名為【{{datasetName}}】的【{{datasetType}}】", + "log_generate_evaluation_summary": "【{{name}}】為評估任務【{{evalName}}】的指標【{{metricName}}】生成了總結報告", + "log_update_evaluation_summary_config": "【{{name}}】更新了評估任務【{{evalName}}】的總結配置", "log_join_team": "【{{name}}】通過邀請鏈接【{{link}}】加入團隊", "log_kick_out_team": "{{name}} 移除了成員 {{memberName}}", "log_login": "【{{name}}】登錄了系統", @@ -217,9 +237,13 @@ "permission_manage_tip": "可以管理成員、建立群組、管理所有群組、為群組和成員分配權限", "please_bind_contact": "請綁定聯繫方式", "purchase_plan": "升級套餐", + "quality_assessment_evaluation_data": "品質評估評測數據", "recover_team_member": "成員恢復", "relocate_department": "部門移動", "remark": "備註", + "retry_evaluation_dataset_task": "重試評測數據集任務", + "retry_evaluation_task": "重試評測任務", + "retry_evaluation_task_item": "重試評測任務項目", "remove_tip": "確認將 {{username}} 移出團隊?", "restore_tip": "確認將 {{username}} 加入團隊嗎?\n僅恢復該成員賬號可用性及相關權限,無法恢復賬號下資源。", "restore_tip_title": "恢復確認", @@ -233,6 +257,7 @@ "search_test": "搜索測試", "set_invoice_header": "設置發票抬頭", "set_name_avatar": "團隊頭像", + "smart_generate_evaluation_data": "智慧生成評測數據", "sync_immediately": "立即同步", "sync_member_failed": "同步成員失敗", "sync_member_success": "同步成員成功", @@ -257,11 +282,54 @@ "update_data": "更新數據", "update_dataset": "更新知識庫", "update_dataset_collaborator": "知識庫權限更改", + "update_evaluation_dataset_collection": "更新評測數據集", + "update_evaluation_dataset_data": "更新評測數據", + "update_evaluation_task": "更新評測任務", + "update_evaluation_task_item": "更新評測任務項目", + "start_evaluation_task": "開始評測任務", + "stop_evaluation_task": "停止評測任務", + "update_evaluation_metric": "更新評測維度", + "debug_evaluation_metric": "調試評測維度", "update_publish_app": "應用更新", "used_times_limit": "有效人數", "user_name": "使用者名稱", "user_team_invite_member": "邀請成員", "user_team_leave_team": "離開團隊", "user_team_leave_team_failed": "離開團隊失敗", - "waiting": "待接受" -} + "waiting": "待接受", + "log_create_evaluation_dataset_collection": "【{{name}}】建立了名為【{{collectionName}}】的評測數據集", + "log_create_evaluation_dataset_data": "【{{name}}】在評測數據集【{{collectionName}}】中建立了評測數據", + "log_create_evaluation_task": "【{{name}}】創建了名為【{{taskName}}】的評測任務", + "log_update_evaluation_dataset_collection": "【{{name}}】更新了名為【{{collectionName}}】的評測數據集", + "log_update_evaluation_dataset_data": "【{{name}}】更新了評測數據集【{{collectionName}}】中的評測數據", + "log_update_evaluation_task": "【{{name}}】更新了名為【{{taskName}}】的評測任務", + "log_delete_evaluation_dataset_collection": "【{{name}}】刪除了名為【{{collectionName}}】的評測數據集", + "log_delete_evaluation_dataset_data": "【{{name}}】刪除了評測數據集【{{collectionName}}】中的評測數據", + "log_delete_evaluation_task": "【{{name}}】刪除了名為【{{taskName}}】的評測任務", + "log_start_evaluation_task": "【{{name}}】開始了名為【{{taskName}}】的評測任務", + "log_stop_evaluation_task": "【{{name}}】停止了名為【{{taskName}}】的評測任務", + "log_retry_evaluation_task": "【{{name}}】重試了名為【{{taskName}}】的評測任務失敗項目", + "log_delete_evaluation_task_item": "【{{name}}】刪除了評測任務【{{taskName}}】中的項目【{{itemId}}】", + "log_update_evaluation_task_item": "【{{name}}】更新了評測任務【{{taskName}}】中的項目【{{itemId}}】", + "log_retry_evaluation_task_item": "【{{name}}】重試了評測任務【{{taskName}}】中的項目【{{itemId}}】", + "log_export_evaluation_task_items": "【{{name}}】導出了評測任務【{{taskName}}】中的項目", + "log_delete_evaluation_task_data_item": "【{{name}}】刪除了評測任務【{{taskName}}】中的數據項【{{dataItemId}}】", + "log_update_evaluation_task_data_item": "【{{name}}】更新了評測任務【{{taskName}}】中的數據項【{{dataItemId}}】", + "log_retry_evaluation_task_data_item": "【{{name}}】重試了評測任務【{{taskName}}】中的數據項【{{dataItemId}}】", + "log_export_evaluation_task_data_items": "【{{name}}】導出了評測任務【{{taskName}}】的{{itemCount}}個數據項,格式為{{format}}", + "log_quality_assessment_evaluation_data": "【{{name}}】對評測數據集【{{collectionName}}】中的評測數據進行了品質評估", + "log_smart_generate_evaluation_data": "【{{name}}】在評測數據集【{{collectionName}}】中智慧生成了評測數據", + "log_delete_evaluation_dataset_task": "【{{name}}】刪除了評測數據集【{{collectionName}}】中的任務", + "log_retry_evaluation_dataset_task": "【{{name}}】重試了評測數據集【{{collectionName}}】中的任務", + "log_import_evaluation_dataset_data": "【{{name}}】向評測數據集【{{collectionName}}】匯入了{{recordCount}}條評測數據", + "log_create_evaluation_metric": "【{{name}}】創建了名為【{{metricName}}】的評測維度", + "log_delete_evaluation_metric": "【{{name}}】删除了名為【{{metricName}}】的評測維度", + "log_update_evaluation_metric": "【{{name}}】更新了名為【{{metricName}}】的評測維度", + "log_debug_evaluation_metric": "【{{name}}】調試了名為【{{metricName}}】的評測維度", + "permission_evaluationCreate_Tip": "可以創建評估任務、評估指標和評估數據集", + "create_evaluation": "創建應用評測", + "export_evaluation": "導出應用評測數據", + "log_create_evaluation": "【{{name}}】創建了名為【{{appName}}】的【{{appType}}】的批量評測", + "log_export_evaluation": "【{{name}}】導出了名為【{{appName}}】的【{{appType}}】的評測數據", + "permission_evaluationCreate": "創建評估" +} \ No newline at end of file diff --git a/packages/web/i18n/zh-Hant/account_usage.json b/packages/web/i18n/zh-Hant/account_usage.json index 799080d8f307..215757a53c8b 100644 --- a/packages/web/i18n/zh-Hant/account_usage.json +++ b/packages/web/i18n/zh-Hant/account_usage.json @@ -13,6 +13,11 @@ "duration_seconds": "時長(秒)", "embedding_index": "索引生成", "evaluation": "應用評測", + "evaluation_dataset_data_quality_assessment": "評測數據品質評估", + "evaluation_dataset_data_synthesis": "評測數據合成", + "evaluation_dataset_data_qa_synthesis": "評測數據問答合成", + "evaluation_quality_assessment": "評估品質評估", + "evaluation_debug_metric": "調試維度", "every_day": "天", "every_month": "月", "every_week": "每週", @@ -28,6 +33,7 @@ "llm_paragraph": "模型分段", "mcp": "MCP 調用", "member": "成員", + "metrics_execute": "指標執行", "member_name": "成員名", "module_name": "模組名", "month": "月", @@ -51,5 +57,7 @@ "total_usage": "總消耗", "usage_detail": "使用詳細資訊", "user_type": "類型", - "wecom": "企業微信" -} + "wecom": "企業微信", + "evaluation_summary_generation": "評估-總結生成", + "generate_answer": "產生應用回答" +} \ No newline at end of file diff --git a/packages/web/i18n/zh-Hant/admin.json b/packages/web/i18n/zh-Hant/admin.json new file mode 100644 index 000000000000..a78abef957f2 --- /dev/null +++ b/packages/web/i18n/zh-Hant/admin.json @@ -0,0 +1,631 @@ +{ + "license_active_success": "激活成功", + "system_activation": "系統激活", + "system_activation_desc": "你需要使用 License 激活系統後才可繼續使用。", + "domain_name": "當前域名為", + "input_license": "請輸入 License", + "cancel": "取消", + "confirm": "確認", + "config_desc": "配置介紹", + "domain_invalid": "License 域名不合法", + "change_license": "變更授權", + "logout": "退出登錄", + "expire_time": "過期時間", + "max_users": "最大用戶數", + "max_apps": "最大應用數", + "max_datasets": "最大知識庫數量", + "sso": "單點登錄", + "pay": "支付系統", + "custom_templates": "自定義模板和系統工具", + "dataset_enhance": "知識庫增強", + "unlimited": "不限制", + "data_dashboard": "數據面板", + "notification_management": "通知管理", + "log_management": "日誌管理", + "user_management": "用戶管理", + "user_info": "用戶信息", + "team_management": "團隊管理", + "plan_management": "套餐管理", + "payment_records": "支付記錄", + "invoice_management": "開票管理", + "resource_management": "資源管理", + "app_management": "應用管理", + "dataset_management": "知識庫管理", + "system_config": "系統配置", + "basic_config": "基礎配置", + "feature_list": "功能清單", + "security_review": "安全審查", + "third_party_providers": "第三方提供商", + "user_config": "用戶配置", + "plan_recharge": "套餐 & 充值", + "template_tools": "模板 & 工具", + "template_market": "模板市場", + "toolbox": "工具箱", + "audit_logs": "審計日誌", + "first_page": "頁首", + "previous_page": "上一頁", + "next_page": "下一頁", + "last_page": "最後一頁", + "page": "頁", + "click_view_details": "點擊查看詳情", + "upload_image_failed": "上傳圖片失敗", + "config_file": "配置文件", + "save": "保存", + "upload_profile_failed": "上傳頭像失敗", + "associated_plugin_is_empty": "關聯插件不能為空", + "config_success": "配置成功", + "confirm_delete_plugin": "確認刪除該插件嗎?", + "delete_success": "刪除成功", + "custom_plugin": "自定義插件", + "config": "配置", + "give_name": "取個名字", + "click_upload_avatar": "點擊上傳頭像", + "app_name_empty": "應用名不能為空", + "description": "介紹", + "add_app_description": "為這個應用添加一個介紹", + "associated_plugins": "關聯插件", + "search_plugin": "輸入插件名或 appId 查找插件", + "attribute": "屬性", + "author_name": "作者名", + "default_system_name": "默認為系統名", + "is_enable": "是否啟用", + "charge_token_fee": "是否收取 Token 費用", + "call_price": "調用價格(n積分/次)", + "instructions": "使用說明", + "use_markdown_syntax": "使用 markdown 語法", + "update": "更新", + "create_plugin": "新建插件", + "delete_confirm_message": "刪除後,其下資源將同步刪除且不可恢復。是否確認刪除?", + "group_management": "分組管理", + "total_groups": "共 {localGroups.length} 個分組", + "add": "添加", + "add_type": "添加類型", + "avatar_select_error": "頭像選擇異常", + "rename": "重命名", + "add_group": "添加分組", + "avatar_name": "頭像 & 名稱", + "click_set_avatar": "點擊設置頭像", + "group_name_empty": "分組名稱不能為空", + "official": "官方", + "configured": "已配置", + "not_configured": "未配置", + "system_key_price": "系統密鑰價格(n積分/次)", + "plugin_config": "{{name}} 配置", + "enable_toolset": "是否啟用工具集", + "config_system_key": "是否配置系統密鑰", + "tool_list": "工具列表", + "tool_name": "工具名", + "key_price": "密鑰價格", + "continue": "繼續", + "user_deleted_message": "這人被刪了", + "missing_field": "缺少字段", + "team_not_exist": "團隊不存在", + "subscription_exists": "已存在相同類型的訂閱", + "subscription_not_exist": "訂閱不存在", + "user_exist": "用戶已存在", + "account_logout": "賬號已經註銷了", + "user_not_found": "找不到用戶", + "user_not_exist": "用戶不存在", + "update_failed": "更新失敗", + "send_notification_success": "發送通知成功", + "user_password_error": "用戶或密碼錯誤!", + "invoice_not_found": "找不到發票", + "invoice_issued": "發票已開具", + "invoice_completed_notice": "您申請的發票已完成,請注意查收", + "invoice_generation_completed": "開票完成", + "order_not_exist": "訂單不存在", + "order_unpaid": "訂單未支付", + "order_invoiced_no_refund": "訂單已開票,無法直接退款", + "order_insufficient_amount": "訂單金額不足", + "type_empty": "類型不能為空", + "quantity_must_be_positive": "數量必須大於0", + "team_not_found": "未找到團隊", + "dataset_training_rebuilding": "數據集正在訓練或者重建中,請稍後再試", + "database_rebuilding_index": "數據庫重建索引", + "type_not_support": "暫不支持該類型消息", + "send_verification_code_success": "發送驗證碼成功", + "normal": "正常", + "leave": "離開", + "deactivated": "已停用", + "account": "賬號", + "username": "用戶名", + "contact": "聯繫方式", + "department": "部門", + "join_time": "加入時間", + "update_time": "更新時間", + "status": "狀態", + "team_num_limit_error": "僅限1個團隊", + "organization_name": "組織名", + "amount": "金額", + "yuan": "元", + "pending_invoice_count": "待開票數量", + "go_to_view": "前去查看", + "new_invoice_application": "有新的開票申請", + "order_type_error": "訂單類型錯誤", + "missing_key_params_update_bill_failed": "缺少關鍵參數,更新帳單失敗,請聯繫管理員", + "plugin_service_link_not_configured": "未配置插件服務鏈接", + "audit_record_table": "審計記錄表", + "operator": "操作人員", + "operation_type": "操作類型", + "operation_time": "操作時間", + "operation_content": "操作內容", + "no_audit_records": "暫無審計記錄~", + "audit_details": "審計詳情", + "details": "詳情", + "close": "關閉", + "total_users": "總用戶數", + "registered_users": "註冊用戶數", + "payment_amount": "支付金額", + "order_count": "訂單數", + "all": "全部", + "success": "成功", + "paid_teams": "付費團隊數", + "total_conversations": "總對話數", + "total_sessions": "總會話數", + "avg_conversations_per_session": "每個會話平均對話數", + "points_consumed": "積分消耗", + "user_total": "用戶總數", + "dataset_total": "知識庫總數", + "app_total": "應用總數", + "statistics_data": "統計數據", + "traffic": "流量", + "payment": "支付", + "active": "活躍", + "cost": "成本", + "last_7_days": "近7天", + "last_30_days": "近30天", + "last_90_days": "近90天", + "last_180_days": "近180天", + "confirm_modify_system_announcement": "確認修改系統公告?", + "confirm_send_system_notification": "確認發送系統通知?", + "modify_success": "修改成功", + "modify_failed": "修改失敗", + "send_success": "發送成功", + "send_failed": "發送失敗", + "system_announcement_config": "系統公告配置", + "system_announcement_description": "設置該內容,會在用戶登錄系統後,通過彈窗形式進行強提示。用戶關閉後,下次不再提示。只能設置1個該類型通知。支持 markdown 格式。", + "send_system_notification": "發送系統通知", + "confirm_send": "確認發送", + "send_notification_description": "為所有用戶發送一個通知,不同等級通知,會有不同提示。", + "message_level": "消息等級", + "level_normal": "一般(僅發站內信)", + "level_important": "重要(站內信+登錄通知)", + "level_urgent": "緊急(站內信+登錄通知+郵件/短信提醒)", + "level_normal_text": "一般", + "level_important_text": "重要", + "level_urgent_text": "緊急", + "notification_title": "通知標題", + "notification_content": "通知內容", + "log_record_table": "日誌記錄表", + "log_search_placeholder": "請想要查找的日誌內容,回車搜索", + "time": "時間", + "log_content": "日誌內容", + "no_log_records": "暫無Log記錄~", + "log_level": "日誌等級", + "log_detail": "日誌詳情", + "log_message": "日誌消息", + "login_success": "登錄成功!", + "admin_login": "管理員登錄", + "login_username": "用戶名", + "login_password": "密碼", + "login_button": "登錄", + "app_list": "應用列表", + "app_name": "應用名", + "creator": "創建者", + "redirect": "跳轉", + "app_no_records": "無應用記錄~", + "app_details": "應用詳情", + "app_id": "應用id", + "app_creator_id": "創建者 ID", + "dataset_list": "知識庫列表", + "dataset_name": "知識庫名", + "dataset_data_size": "數據量", + "dataset_vector_count": "向量總數", + "dataset_favorite_count": "收藏數", + "new": "新增", + "name": "名稱", + "enable": "啟用", + "redirect_link": "跳轉鏈接", + "operation": "操作", + "add_sidebar_item": "新增側邊項", + "sidebar_item_name": "側邊項名", + "sidebar_item_name_empty": "側邊項名不能為空", + "redirect_link_empty": "跳轉鏈接不能為空", + "edit_plan": "編輯 {{label}} 套餐", + "plan_name": "套餐名稱", + "custom_plan_name_desc": "自定義套餐名,可覆蓋原套餐名", + "monthly_price": "每月價格", + "max_team_members": "最大團隊成員", + "max_app_count": "最大APP數量", + "max_dataset_count": "最大知識庫數量", + "history_retention_days": "歷史記錄保存多少天", + "max_dataset_index_count": "最大知識庫索引數量", + "monthly_ai_points": "每月 AI 積分", + "training_priority_high": "訓練優先級(高的優先)", + "allow_site_sync": "允許使用站點同步", + "allow_team_operation_logs": "允許團隊操作日誌", + "click_configure_plan": "點擊配置套餐", + "copy_success": "複製成功", + "delete_confirm": "確認刪除該變量?", + "delete": "刪除", + "workflow_variable_custom": "自定義工作流變量", + "workflow_variable_custom_title": "自定義工作流變量", + "field_name": "變量名", + "usage_url": "使用量查詢地址", + "note": "說明", + "import_config": "導入配置", + "import_success_save": "導入成功,請點擊保存", + "import_check_format": "請檢查配置文件格式", + "import": "導入", + "get_config_error": "獲取配置出錯", + "save_success": "保存成功", + "save_failed": "保存失敗", + "frontend_display_config": "前端展示配置", + "personalization_config": "個性化配置", + "global_script": "全局Script腳本", + "system_params": "系統參數", + "pdf_parse_config": "PDF 解析配置", + "usage_limits": "使用限制", + "sidebar_config": "側邊欄配置", + "system_name": "系統名", + "custom_api_domain": "自定義api域名", + "custom_api_domain_desc": "可以設置一個額外的api地址,不使用主站的地址,需配置域名的cname和ssl證書。", + "custom_share_link_domain": "自定義分享鏈接域名", + "custom_share_link_domain_desc": "可以設置一個額外的分享鏈接地址,不使用主站的地址,需配置域名的cname和ssl證書。", + "openapi_prefix": "OpenAPI 前綴", + "contact_popup": "聯繫彈窗", + "contact_popup_desc": "使用 Markdown 進行配置,配置之後,在網頁中\"聯繫我們\"相關的內容,會提示填寫的內容。", + "custom_api_doc_url": "自定義 api 文檔地址", + "custom_openapi_doc_url": "自定義 openapi 文檔地址", + "doc_url_note": "文檔地址(加一個 / 結尾,否則會攜帶子路徑跳轉)", + "contribute_plugin_doc_url": "貢獻插件文檔地址", + "contribute_template_doc_url": "貢獻模板市場文檔地址", + "global_script_desc": "自定義 Script 腳本,可以全局插入(可以做站點流量監控之類的)", + "mcp_forward_service_url": "MCP 轉發服務地址", + "mcp_forward_service_desc": "需要部署一個 MCP 轉發服務,用於將 FastGPT 應用以MCP協議暴露,例如:http://localhost:3005", + "oneapi_url": "oneAPI地址(會覆蓋環境變量配置的)", + "oneapi_url_desc": "oneAPI地址,可以使用 oneapi 來實現多模型接入", + "input_oneapi_url": "請輸入 oneAPI 地址", + "oneapi_key": "OneAPI 密鑰(會覆蓋環境變量配置的)", + "input_oneapi_key": "請輸入 OneAPI 密鑰", + "dataset_parse_max_process": "知識庫解析最大處理進程", + "dataset_index_max_process": "知識庫索引最大處理進程", + "file_understanding_max_process": "文件理解模型最大處理進程", + "image_understanding_max_process": "圖片理解模型最大處理進程", + "hnsw_ef_search": "HNSW ef_search", + "hnsw_ef_search_desc": "HNSW 參數。越大召回率越高,性能越差,默認為 100,具體可見:https://github.com/pgvector/pgvector", + "hnsw_max_scan_tuples": "HNSW max_scan_tuples", + "hnsw_max_scan_tuples_desc": "迭代搜索最大數量,越大召回率越高,性能越差,默認為 100000,具體可見:https://github.com/pgvector/pgvector", + "token_calc_max_process": "token計算最大進程(通常多少並發設置多少)", + "custom_pdf_parse_url": "自定義 PDF 解析地址", + "custom_pdf_parse_key": "自定義 PDF 解析密鑰", + "custom_pdf_parse_timeout": "自定義 PDF 解析逾時時間(分鐘)", + "doc2x_pdf_parse_key": "Doc2x pdf 解析密鑰(比自定義 PDF 解析優先級低)", + "custom_pdf_parse_price": "自定義 PDF 解析價格(n 積分/頁)", + "eval_config": "評測配置", + "eval_config_task_concurrency": "評測任務並發數", + "eval_config_task_concurrency_desc": "同時執行的評測任務數量", + "eval_config_case_concurrency": "評測用例執行並發數", + "eval_config_case_concurrency_desc": "單個評測任務中並發處理的用例數量", + "eval_config_case_max_retry": "評測用例最大重試次數", + "eval_config_case_max_retry_desc": "評測用例失敗時的最大重試次數", + "eval_config_case_result_threshold": "評測判決默認閾值", + "eval_config_case_result_threshold_desc": "評測判決的默認闾值(0-1之間,決定評測結果的正負)", + "eval_config_summary_concurrency": "評測報告生成並發數", + "eval_config_data_quality_concurrency": "評測數據集-數據質量評測並發數", + "eval_config_dataset_synthesize_concurrency": "評測數據集-數據合成並發數", + "eval_config_smart_generate_concurrency": "評測數據集-智能生成並發數", + "eval_config_maxStalledCount": "評測-任務卡滯最大重試次數", + "max_upload_files_per_time": "單次最多上傳多少個文件", + "max_upload_files_per_time_desc": "用戶上傳知識庫時,每次上傳最多選擇多少個文件", + "max_upload_file_size": "上傳文件最大大小(M)", + "max_upload_file_size_desc": "用戶上傳知識庫時,每個文件最大是多少。放大的話,需要注意網關也要設置得夠大。", + "export_interval_minutes": "導出間隔時長(分鐘)", + "site_sync_interval_minutes": "站點同步使用間隔時長(分鐘)", + "mobile_sidebar_location": "移動端的側邊欄顯示在賬號 - 個人信息里", + "basic_features": "基礎功能", + "third_party_knowledge_base": "第三方知識庫", + "third_party_publish_channels": "第三方發布渠道", + "feature_display_config": "功能展示配置", + "display_team_sharing": "展示團隊分享", + "display_chat_blank_page": "展示聊天空白頁(都關閉即可)", + "display_invite_friends_activity": "展示邀請好友活動", + "frontend_compliance_notice": "前端是否展示合規提示文案", + "feishu_knowledge_base": "飛書知識庫", + "feishu_knowledge_base_desc": "關閉後,創建數據庫時不再顯示飛書數據庫", + "yuque_knowledge_base": "語雀知識庫", + "yuque_knowledge_base_desc": "關閉後,創建數據庫時不再顯示語雀數據庫", + "feishu_publish_channel": "飛書發布渠道", + "feishu_publish_channel_desc": "關閉後,發布渠道中不再顯示飛書發布渠道", + "dingtalk_publish_channel": "釘釘發布渠道", + "dingtalk_publish_channel_desc": "關閉後,發布渠道中不再顯示釘釘發布渠道", + "wechat_publish_channel": "公眾號發布渠道", + "wechat_publish_channel_desc": "關閉後,發布渠道中不再顯示公眾號發布渠道", + "content_security_review": "內容安全審查", + "baidu_security_id": "百度安全 id", + "baidu_security_secret": "百度安全 secret", + "custom_security_check_url": "自定義安全校驗 URL", + "baidu_security_register_desc": "註冊百度安全校驗賬號,並創建對應應用。提供應用的 id 和 secret", + "custom_security_check_desc": "如果您有自己的安全校驗服務,可以填寫該地址,並在安全設置中開啟自定義安全校驗", + "plan_free": "免費版", + "plan_trial": "體驗版", + "plan_team": "團隊版", + "plan_enterprise": "企業版", + "subscription_plan": "訂閱套餐", + "standard_subscription_plan": "標準訂閱套餐", + "custom_plan_description": "自定義套餐說明", + "dataset_storage_cost_desc": "知識庫存儲費用(xx元/1000條/月)", + "extra_ai_points_cost_desc": "額外AI積分費用(xx元/1000積分/月)", + "payment_method": "支付方式", + "wechat_payment_config": "微信支付配置", + "alipay_payment_config": "支付寶支付配置", + "corporate_payment_message": "對公支付消息提示", + "enable_subscription_plan": "是否啟用訂閱套餐", + "custom_plan_page_description": "如果填寫了該地址,會覆蓋系統上套餐頁面,會跳轉到這個自定義頁面,你可以在自定義頁面裡定義收費規則", + "wechat_payment_materials": "微信支付相關材料", + "wechat_payment_registration_guide": "自行註冊微信支付,目前需要wx掃碼支付", + "unused_field_placeholder": "沒用到,隨便填個", + "certificate_management_guide": "點管理證書進去看到", + "wechat_key_extraction_guide": "按微信教程拿到這幾個文件,txt打開key", + "alipay_payment_materials": "支付寶支付相關材料", + "alipay_application_guide": "自行註冊支付寶應用,目前需要開通電腦網站支付", + "alipay_certificate_encryption_guide": "點接口加簽方式後選擇證書加密方式,具體操作參考", + "application_public_key_certificate": "應用公鑰證書", + "private_key_document_reference": "參考上面私鑰獲取文檔", + "alipay_root_certificate": "支付寶根證書", + "alipay_public_key_certificate": "支付寶公鑰證書", + "alipay_dateway": "支付寶網關", + "alipay_gateway_sandbox_note": "支付寶網關,注意測試使用的沙箱環境是\nhttps://openapi-sandbox.dl.alipaydev.com/gateway.do\n,而生成環境是\nhttps://openapi.alipay.com/gateway.do\n", + "alipay_endpoint_sandbox_note": "支付寶端點,注意測試使用的沙箱環境是\nhttps://openapi-sandbox.dl.alipaydev.com\n,而生成環境是\nhttps://openapi.alipay.com\n", + "message_notification": "消息提示", + "markdown_format_support": "支持markdown格式", + "third_party_account_config": "第三方賬號配置", + "allow_user_account_config": "允許用戶配置賬號", + "view_documentation": "查看文檔", + "openai_oneapi_account": "OpenAI/OneAPI 賬號", + "laf_account": "laf 賬號", + "input_laf_address": "請輸入 laf 地址", + "multi_team_mode": "多團隊模式", + "single_team_mode": "單團隊模式", + "sync_mode": "同步模式", + "notification_login_settings": "通知 & 登錄設置", + "team_mode_settings": "團隊模式設置", + "custom_user_system_config": "自定義用戶系統配置", + "email_notification_config": "郵箱通知配置(註冊、套餐通知)", + "aliyun_sms_config": "阿里雲短信配置", + "aliyun_sms_template_code": "阿里雲短信模板CODE(SMS_xxx)", + "wechat_service_login": "微信服務號登錄", + "github_login_config": "GitHub 登錄配置", + "google_login_config": "Google 登錄配置", + "microsoft_login_config": "微軟登錄配置", + "quick_login": "快速登錄(不推薦)", + "login_notifications_config": "通知登錄 & 設置", + "user_service_root_address": "用戶服務根地址(末尾不加/)", + "sso_usage_guide": "具體用法請看: [SSO & 外部成員同步](https://doc.fastgpt.io/docs/guide/admin/sso/)", + "sso_login_button_title": "SSO 登錄按鈕標題", + "config_sso_login_button_title": "配置 SSO 登錄按鈕的標題", + "sso_login_button_icon": "SSO 登錄按鈕的圖標", + "config_sso_login_button_icon": "配置 SSO 登錄按鈕的圖標", + "sso_auto_redirect": "SSO 自動跳轉", + "sso_auto_redirect_desc": "開啟後,用戶進入登錄頁面,將會自動觸發 SSO 登錄,無需手動點擊。", + "email_smtp_address": "郵箱服務SMTP地址", + "email_smtp_address_note": "不同廠商不一樣\nQQ: smtp.qq.com\ngmail: smtp.gmail.com", + "email_smtp_username": "郵箱服務SMTP用戶名", + "email_smtp_username_example": "qq 郵箱為例,對應 qq 號", + "email_password": "郵箱 密碼", + "email_smtp_auth_code": "SMTP 授權碼", + "enable_email_registration": "是否開啟郵箱註冊", + "aliyun_access_key_guide": "阿里雲短信參數\nhttps://dysms.console.aliyun.com/overview\n申請對應的簽名和短信模板,提供:\nACCESSKEYID\nACCESSSECRET\n簽名名稱\n模板CODE,SM開頭的", + "aliyun_secret_key": "阿里雲賬號的secret key", + "sms_signature": "短信簽名", + "registration_account": "註冊賬號", + "registration_account_desc": "填寫後,將會開啟手機號註冊", + "reset_password": "重置密碼", + "reset_password_desc": "填寫後,將會開啟手機號找回密碼", + "bind_notification_phone": "綁定通知手機號", + "bind_notification_phone_desc": "填寫後,將會允許手機號綁定通知方式", + "subscription_expiring_soon": "訂閱套餐即將過期", + "subscription_expiring_soon_desc": "填寫後,套餐即將過期,會發送一個短信", + "free_user_cleanup_warning": "免費版用戶清理警告", + "wechat_service_appid": "服務號的 Appid。微信服務號的驗證地址填寫:商業版域名//api/support/user/account/login/wx/callback", + "wechat_service_secret": "服務號的 Secret", + "register_one": "註冊一個", + "provide": "提供", + "domain": "域名", + "microsoft_app_client_id": "對應 Microsoft 應用的「應用程序(客戶端) ID」", + "microsoft_tenant_id": "對應 Microsoft 應用的「租戶 ID」, 若使用默認的 common 可不用填寫", + "custom_button_name": "自定義按鈕名", + "custom_button_name_desc": "自定義按鈕的名稱,若不填寫則使用默認的 Microsoft 按鈕", + "simple_app": "簡易應用", + "workflow": "工作流", + "plugin": "插件", + "folder": "文件夾", + "http_plugin": "HTTP 插件", + "toolset": "工具集", + "tool": "工具", + "hidden": "隱藏", + "select_json_file": "請選擇 JSON 文件", + "confirm_delete_template": "確認刪除該模板嗎?", + "upload_config_first": "請先上傳配置文件", + "app_type_not_recognized": "未識別到應用類型", + "config_json_format_error": "配置文件 JSON 格式錯誤", + "template_update_success": "模板更新成功", + "template_create_success": "模板創建成功", + "template_config": "模板配置", + "json_serialize_failed": "JSON 序列化失敗", + "get_app_type_failed": "獲取應用類型失敗", + "file_overwrite_content": "文件將覆蓋當前內容", + "config_file_label": "配置文件", + "official_config": "官方配置", + "upload_file": "上傳文件", + "paste_config_or_drag_json": "粘貼配置或拖入 JSON 文件", + "paste_config": "粘貼配置", + "app_type": "應用類型", + "auto_recognize": "自動識別", + "app_attribute_not_recognized": "未識別到應用屬性", + "text": "文本", + "link": "鏈接", + "input_link": "請輸入鏈接", + "settings_successful": "設置成功", + "configure_quick_templates": "配置快捷模板", + "search_apps": "搜索應用", + "selected_count": "已選: {{count}} / 3", + "category_management": "分類管理", + "total_categories": "共 {{length}} 個分類", + "add_category": "添加分類", + "category_name": "分類名", + "category_name_empty": "分類名不能為空", + "template_list": "模板列表", + "quick_template": "快捷模板", + "add_template": "添加模板", + "recommended": "推薦", + "no_templates": "暫無模板", + "add_attribute_first": "請先添加屬性", + "add_plugin": "添加插件", + "token_points": "Token 積分", + "token_fee_description": "開啟該開關後,用戶使用該插件,需要支付插件中Token的積分,並且同時會收取調用積分", + "call_points": "調用積分", + "system_key": "系統密鑰", + "system_key_description": "對於需要密鑰的工具,您可為其配置系統密鑰,用戶可透過支付積分的方式使用系統密鑰。", + "no_plugins": "暫無插件", + "invoice_application": "開票申請", + "search_user_placeholder": "請輸入用戶名,回車搜索", + "submit_status": "提交狀態", + "submit_complete_time": "提交時間/完成時間", + "invoice_title": "抬頭", + "waiting_for_invoice": "等待開票", + "completed": "已完成", + "confirm_invoice": "確認開票", + "no_invoice_records": "無開票記錄~", + "invoice_details": "發票詳情", + "invoice_amount": "開票金額", + "organization": "組織名稱", + "unified_credit_code": "統一信用代碼", + "company_address": "公司地址", + "company_phone": "公司電話", + "bank_name": "開戶銀行", + "bank_account": "開戶賬號", + "need_special_invoice": "是否需要專票", + "yes": "是", + "no": "否", + "email_address": "郵箱地址", + "invoice_file": "發票文件", + "click_download": "點擊下載", + "operation_success": "操作成功", + "operation_failed": "操作失敗", + "upload_invoice_pdf": "請上傳發票的PDF文件", + "select_invoice_file": "選擇發票文件", + "confirm_submission": "確認提交", + "balance_recharge": "餘額充值", + "plan_subscription": "套餐訂閱", + "knowledge_base_expansion": "知識庫擴容", + "ai_points_package": "AI積分套餐", + "monthly": "按月", + "yearly": "按年", + "free": "免費", + "trial": "體驗", + "team": "團隊", + "enterprise": "企業", + "custom": "自定義", + "wechat": "微信", + "balance": "餘額", + "alipay": "支付寶", + "corporate": "對公", + "redeem_code": "兌換碼", + "search_user": "請輸入用戶名搜索", + "team_id": "團隊ID", + "recharge_member_name": "充值的成員名", + "unpaid": "未支付", + "no_bill_records": "無賬單記錄~", + "order_details": "訂單詳情", + "order_number": "訂單號", + "generation_time": "生成時間", + "order_type": "訂單類型", + "subscription_period": "訂閱周期", + "subscription_package": "訂閱套餐", + "months": "月數", + "extra_knowledge_base_capacity": "額外知識庫容量", + "extra_ai_points": "額外AI積分", + "time_error": "開始時間不能大於結束時間", + "points_error": "剩餘積分不能大於總積分", + "add_success": "添加成功", + "add_package": "添加套餐", + "package_type": "套餐類型", + "basic_package": "基礎套餐", + "start_time": "開始時間", + "required": "*必填", + "end_time": "結束時間", + "package_level": "套餐級別", + "total_points": "總積分", + "remaining_points": "剩餘積分", + "price_yuan_for_record_only": "價格(元)-僅用於記錄", + "price": "價格", + "update_success": "更新成功", + "edit": "編輯", + "edit_package": "編輯套餐", + "value_override_description": "下面的值會覆蓋套餐配置,不填則會用套餐的標準值", + "team_member_limit": "團隊成員上限", + "app_limit": "應用上限", + "knowledge_base_limit": "知識庫上限", + "team_name": "團隊名", + "points": "積分", + "start_end_time": "起止時間", + "version": "版", + "extra_knowledge_base": "額外知識庫", + "no_package_records": "無套餐記錄~", + "role": "權限", + "team_details": "團隊詳情", + "chage_success": "變更成功", + "team_edit": "團隊編輯", + "team_list": "團隊列表", + "create_time": "創建時間", + "no_team_data": "無團隊記錄~", + "add_user": "添加用戶", + "add_user_btn": "添加用戶", + "password": "密碼", + "password_requirements": "密碼至少 8 位,且至少包含兩種組合:數字、字母或特殊字符", + "account_deactivated": "賬號已註銷", + "edit_user": "編輯用戶", + "user_status": "用戶狀態", + "confirm_deactivate_account": "確認註銷該賬號?會將該用戶下關鍵資源刪除,並修改其用戶名成 xx-deleted", + "deactivate": "註銷", + "no_user_records": "無用戶記錄~", + "user_details": "用戶詳情", + "system_incompatibility": "部分系統不兼容,導致頁面崩潰。如果可以,請聯繫作者,反饋下具體操作和頁面。大部分是 蘋果 的 safari 瀏覽器導致,可以嘗試更換 chrome 瀏覽器。", + "content_not_compliant": "您的內容不合規", + "baidu_content_security_check_exception": "百度內容安全校驗異常", + "license_not_read": "未讀取到 License", + "license_invalid": "License 不合法", + "license_expired": "License 已過期", + "license_content_error": "License 內容錯誤", + "system_not_activated": "系統未激活", + "exceed_max_users": "超過最大用戶數", + "server_error": "服務器異常", + "request_error": "請求錯誤", + "unknown_error": "未知錯誤", + "token_expired_relogin": "token過期,重新登錄", + "google_verification_result": "谷歌校驗結果", + "abnormal_operation_environment": "您的操作環境存在異常,請刷新頁面後重試或聯繫客服。", + "notify_expiring_packages": "通知即將過期的套餐", + "notify_free_users_cleanup": "通知免費版用戶即將清理", + "bing_oauth_config_incomplete": "Bing OAuth配置不完整", + "request_limit_per_minute": "每分鐘僅能請求次數", + "share_link_expired": "分享鏈接已過期", + "link_usage_limit_exceeded": "鏈接超出使用限制", + "authentication_failed": "身份校驗失敗", + "user_registration": "新用戶註冊", + "initial_password": "您的初始密碼為", + "notification_too_frequent": "發送通知太頻繁了", + "emergency_notification_requires_teamid": "緊急通知必須提供 teamId", + "send_sms_failed": "發送短信失敗", + "fastgpt_user": "FastGPT用戶", + "refund_failed": "退款失敗", + "refund_request_failed": "退款請求失敗", + "get_certificate_list_failed": "獲取證書列表失敗", + "platform_certificate_serial_mismatch": "平台證書序列號不相符", + "extra_knowledge_base_storage": "額外知識庫存儲", + "image_compression_error": "壓縮圖片異常", + "image_too_large": "圖片太大了", + "password_min_length": "密碼至少 8 位", + "password_requirement": "至少包含兩種組合:數字、字母、特殊字符" +} diff --git a/packages/web/i18n/zh-Hant/app.json b/packages/web/i18n/zh-Hant/app.json index 57e267dbcac5..1eb37293abc3 100644 --- a/packages/web/i18n/zh-Hant/app.json +++ b/packages/web/i18n/zh-Hant/app.json @@ -69,7 +69,7 @@ "cron.every_week": "每週執行", "cron.interval": "間隔執行", "dataset": "知識庫", - "dataset_search_tool_description": "呼叫「語意搜尋」和「全文搜尋」功能,從「知識庫」中尋找可能與問題相關的參考內容。優先呼叫這個工具來協助回答使用者的問題。", + "dataset_search_tool_description": "調用語義檢索、全文檢索、數據庫檢索能力,從“知識庫”中查找可能與問題相關的參考內容。", "day": "日", "deleted": "應用已刪除", "document_quote": "文件引用", @@ -282,7 +282,7 @@ "transition_to_workflow_create_new_placeholder": "建立新的應用程式,而不是修改目前應用程式", "transition_to_workflow_create_new_tip": "轉換成工作流程後,將無法轉換回簡易模式,請確認!", "tts_ai_model": "使用語音合成模型", - "tts_browser": "瀏覽器自帶 (免費)", + "tts_browser": "瀏覽器自帶(免費)", "tts_close": "關閉", "type.All": "全部", "type.Create http plugin tip": "透過 OpenAPI Schema 批次建立外掛,相容 GPTs 格式", @@ -346,5 +346,23 @@ "workflow.user_file_input": "檔案連結", "workflow.user_file_input_desc": "使用者上傳的檔案和圖片連結", "workflow.user_select": "使用者選擇", - "workflow.user_select_tip": "這個模組可以設定多個選項,供對話時選擇。不同選項可以導向不同的工作流程支線" -} + "workflow.user_select_tip": "這個模組可以設定多個選項,供對話時選擇。不同選項可以導向不同的工作流程支線", + "files_cascader_no_knowledge_base": "不加入知識庫", + "files_cascader_select_knowledge_base": "請選擇知識庫", + "files_cascader_select_first": "請先選擇知識庫", + "files_cascader_dataset_empty": "該知識庫資料集為空", + "select_join_location": "選擇加入位置", + "no_data_for_smart_generate": "該知識庫中沒有可用於智能生成的數據", + "logs_bad_feedback": "點踩", + "logs_source_count": "渠道用戶", + "logs_timespan_day": "按日", + "logs_timespan_month": "按月", + "logs_timespan_quarter": "按季", + "logs_timespan_week": "按週", + "logs_total_avg_duration": "平均時長", + "logs_total_feedback": "共 {{goodFeedBack}} 讚 | 共 {{badFeedBack}} 踩", + "logs_user_callback": "用戶回饋", + "team.menu.plugin": "外掛", + "team.menu.app": "簡易應用程式", + "team.menu.workflow": "工作流程" +} \ No newline at end of file diff --git a/packages/web/i18n/zh-Hant/chat.json b/packages/web/i18n/zh-Hant/chat.json index 2f2443e463f5..aed3ad37a22a 100644 --- a/packages/web/i18n/zh-Hant/chat.json +++ b/packages/web/i18n/zh-Hant/chat.json @@ -126,5 +126,14 @@ "upload": "上傳", "variable_invisable_in_share": "自定義變數在免登入連結中不可見", "view_citations": "檢視引用", - "web_site_sync": "網站同步" -} + "web_site_sync": "網站同步", + "embedding_model_error": "向量模型出錯,請核對模型配置資訊", + "language_model_error": "請求大模型出錯,請檢查模型配置是否正確", + "setting.copyright.save_success": "Logo 儲存成功", + "setting.home.available_tools": "可用工具", + "setting.share": "分享", + "database_sql_query": "資料庫檢索的 SQL 語句", + "search_result": "檢索結果", + "database_search_results": "數據庫搜索結果", + "other_knowledge_base_search_results": "其他知識庫搜索結果" +} \ No newline at end of file diff --git a/packages/web/i18n/zh-Hant/common.json b/packages/web/i18n/zh-Hant/common.json index 6b22437a4ff2..6bfa75031752 100644 --- a/packages/web/i18n/zh-Hant/common.json +++ b/packages/web/i18n/zh-Hant/common.json @@ -98,7 +98,7 @@ "add_success": "新增成功", "all_quotes": "全部引用", "all_result": "完整結果", - "app_evaluation": "應用評測(Beta)", + "app_evaluation": "應用評測(Beta)", "app_not_version": "該應用未發布過,請先發布應用", "auth_config": "鑑權配置", "auth_type": "鑑權類型", @@ -146,7 +146,6 @@ "code_error.error_code.502": "閘道錯誤", "code_error.error_code.503": "伺服器過載或維護中", "code_error.error_code.504": "閘道逾時", - "code_error.error_code[429]": "請求過於頻繁", "code_error.error_message.403": "憑證錯誤", "code_error.error_message.510": "帳戶餘額不足", "code_error.error_message.511": "無權操作此模型", @@ -164,6 +163,10 @@ "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.system_error.license_evaluation_task_amount_limit": "超出系統最大評估任務數量", + "code_error.system_error.license_eval_dataset_amount_limit": "超出系統最大評估數據集數量", + "code_error.system_error.license_eval_dataset_data_amount_limit": "超出系統最大評估數據集資料量", + "code_error.system_error.license_eval_metric_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": "無法刪除預設群組", @@ -189,6 +192,10 @@ "code_error.team_error.user_not_active": "使用者未接受或已離開團隊", "code_error.team_error.website_sync_not_enough": "免費版無法使用 Web 站點同步~", "code_error.team_error.you_have_been_in_the_team": "你已經在該團隊中", + "code_error.team_error.evaluation_task_amount_not_enough": "評估任務數量已達上限~", + "code_error.team_error.evaluation_dataset_amount_not_enough": "評測資料集數量已達上限~", + "code_error.team_error.evaluation_dataset_data_amount_not_enough": "評測資料條目數量已達上限~", + "code_error.team_error.evaluation_metric_amount_not_enough": "評測維度數量已達上限~", "code_error.token_error_code.403": "登入狀態無效,請重新登入", "code_error.user_error.balance_not_enough": "帳戶餘額不足", "code_error.user_error.bin_visitor_guest": "您目前身份為訪客,無權操作", @@ -412,7 +419,7 @@ "core.chat.response.module limit": "單次搜尋上限", "core.chat.response.module maxToken": "最大回應 Token 數", "core.chat.response.module model": "模型", - "core.chat.response.module name": "模型名稱", + "core.chat.response.module name": "節點名稱", "core.chat.response.module query": "問題/搜尋詞", "core.chat.response.module similarity": "相似度", "core.chat.response.module temperature": "溫度", @@ -448,7 +455,7 @@ "core.dataset.collection.Start Sync Tip": "確認開始同步資料?將會刪除舊資料後重新取得,請確認!", "core.dataset.collection.Sync": "同步資料", "core.dataset.collection.Sync Collection": "資料同步", - "core.dataset.collection.Website Empty Tip": "還沒有關聯網站", + "core.dataset.collection.Website Empty Tip": "還沒有關聯網站,", "core.dataset.collection.Website Link": "網站網址", "core.dataset.collection.id": "集合 ID", "core.dataset.collection.metadata.Createtime": "建立時間", @@ -799,7 +806,6 @@ "dataset_text_model_tip": "用於知識庫預處理階段的文字處理,例如自動補充索引、問答對提取。", "date_12_months": "12個月", "date_1_month": "1個月", - "date_3 months": "3個月", "date_6_months": "6個月", "deep_rag_search": "深度搜尋", "delete_api": "確認刪除此 API 金鑰?\n刪除後該金鑰將立即失效,對應的對話記錄不會被刪除,請確認!", @@ -979,7 +985,7 @@ "permission.manager": "管理員", "permission.read": "讀取權限", "permission.write": "寫入權限", - "permission_other": "其他權限(多選)", + "permission_other": "其他權限(多選)", "please_input_name": "請輸入名稱", "plugin.App": "選擇應用程式", "plugin.Currentapp": "目前應用程式", @@ -1338,5 +1344,33 @@ "zoomin_tip": "縮小 ctrl -", "zoomin_tip_mac": "縮小 ⌘ -", "zoomout_tip": "放大 ctrl +", - "zoomout_tip_mac": "放大 ⌘ +" -} + "zoomout_tip_mac": "放大 ⌘ +", + "no_database_connection": "還沒有連接數據庫,", + "click_config_database": "點擊配置數據庫", + "annotation_answer": "標註答案", + "core.dataset.search.Database search": "資料庫檢索", + "search_model": "檢索模型", + "search_model_desc": "用於產生可在資料庫中檢索的SQL語句,並進行檢索與匯總,產生可用於對話的文字。", + "search_model_tip": "使用非推理模型、參數量大的模型效果更佳。", + "other_knowledge_base": "其他知識庫", + "table_not_exist": "不存在", + "core.app.workflow.search_knowledge.database": "資料庫", + "code_error.error_code.429": "請求過於頻繁", + "core.chat.retry": "重新產生", + "date_3_months": "3個月", + "database_search": "資料庫搜尋", + "core.chat.logs.evaluation": "評估測試", + "support.wallet.subscription.eval_items_count": "單次評測資料條數: {{count}} 條", + "core.dataset.table": "資料表", + "core.dataset.search.mode.database": "資料庫檢索", + "core.dataset.search.mode.database desc": "使用向量檢索查找資料庫中可能相關的表和列", + "core.dataset.training.databaseSchema mode": "資料庫結構", + "core.dataset.import.databaseSchema Tip": "對資料庫中的表資訊進行自動處理,使其更利於檢索,以提高SQL產生的準確率", + "semicolon": ";", + "database_sql_query": "資料庫檢索的 SQL 語句", + "search_result": "檢索結果", + "database_search_results": "數據庫搜索結果", + "other_knowledge_base_search_results": "其他知識庫搜索結果", + "comma": "、", + "set_avatar_in_edit_modal": "點擊設置頭像" +} \ No newline at end of file diff --git a/packages/web/i18n/zh-Hant/dashboard_evaluation.json b/packages/web/i18n/zh-Hant/dashboard_evaluation.json index 508142b00833..031520907630 100644 --- a/packages/web/i18n/zh-Hant/dashboard_evaluation.json +++ b/packages/web/i18n/zh-Hant/dashboard_evaluation.json @@ -24,7 +24,7 @@ "data_list": "數據列表", "error": "異常", "error_tooltip": "有異常任務。 \n\n點擊後,打開任務詳情彈窗", - "evaluating": "評估中", + "evaluating": "評測中", "evaluation": "應用評測", "evaluation_export_title": "問題,標準答案,實際回答,狀態,綜合得分", "evaluation_file_max_size": "{{count}} 條數據", @@ -42,5 +42,339 @@ "task_detail": "任務詳情", "team_has_running_evaluation": "當前團隊已有正在運行的應用評測,請等待完成後再創建新的應用評測", "template_csv_file_select_tip": "僅支持嚴格按照模板格式的 {{fileType}} 文件", - "variables": "全局變量" + "variables": "全局變量", + "all_apps": "全部應用", + "search_evaluation_task": "搜索任務名或應用版本", + "create_new_task": "新建任務", + "task_name_column": "任務名", + "progress_column": "進度", + "evaluation_app_column": "評測應用", + "app_version_column": "應用版本", + "evaluation_result_column": "評測結果", + "start_finish_time_column": "開始時間/完成時間", + "executor_column": "執行人", + "waiting": "等待中", + "evaluating_status": "評測中", + "completed_status": "已完成", + "queuing_status": "排隊中", + "running_status": "進行中", + "error_data_tooltip": "{{count}} 條數據執行異常,可點擊查看詳情", + "rename": "重命名", + "delete": "刪除", + "confirm_delete_task": "確認刪除該任務?", + "evaluation_tasks_tab": "評測任務", + "evaluation_tasks": "評測任務", + "evaluation_datasets_tab": "評測數據集", + "evaluation_dimensions_tab": "評測維度", + "create_new": "新建", + "retry_error_data": "重試異常數據", + "dataset_name_placeholder": "名稱", + "create_new_dataset": "新建", + "smart_generation": "智能生成", + "file_import": "文件導入", + "confirm_delete_dataset": "確認刪除該數據集?", + "error_details": "異常詳情", + "status_queuing": "排隊中", + "status_parsing": "文件解析中", + "status_generating": "數據生成中", + "status_generate_error": "生成異常", + "status_ready": "已就緒", + "status_parse_error": "解析異常", + "click_to_view_details": "點擊查看詳情", + "table_header_name": "名稱", + "table_header_data_count": "數據量", + "table_header_time": "創建/更新時間", + "table_header_status": "狀態", + "table_header_creator": "創建人", + "create_dimension": "新建", + "search_dimension": "搜索評測維度", + "delete_failed": "刪除失敗", + "delete_success": "刪除成功", + "builtin": "內建", + "confirm_delete_dimension": "確認刪除該維度?", + "dimension_name": "維度名", + "description": "介紹", + "create_update_time": "創建/更新時間", + "creator": "創建人", + "all": "全部", + "app": "應用", + "citation_template": "引用模板", + "correctness": "正確性", + "conciseness": "簡潔性", + "harmfulness": "有害性", + "controversiality": "爭議性", + "creativity": "創造性", + "criminality": "犯罪性", + "depth": "深度性", + "details": "細節性", + "dimension_name_label": "維度名", + "dimension_description_label": "介紹", + "prompt_label": "提示詞", + "citation_template_button": "引用模板", + "test_run_title": "試運行", + "question_label": "問題", + "question_placeholder": "請輸入問題內容", + "answer_label": "答案", + "reference_answer_label": "參考答案", + "reference_answer_placeholder": "請輸入參考答案", + "actual_answer_label": "實際回答", + "actual_answer_placeholder": "請輸入實際回答", + "run_result_label": "運行結果", + "start_run_button": "開始運行", + "running_text": "運行中", + "run_success": "運行成功", + "run_failed": "運行失敗", + "not_run": "未運行", + "score_unit": "分", + "error_info_label": "報錯信息:", + "no_feedback_text": "暫無反饋內容", + "dimension_create_back": "退出", + "dimension_create_test_run": "試運行", + "dimension_create_confirm": "確認", + "dimension_create_success": "創建成功", + "dimension_create_name_required": "請輸入名稱", + "dimension_create_prompt_required": "請輸入提示詞", + "dimension_get_data_failed": "獲取維度數據失敗", + "dimension_data_not_exist": "維度數據不存在", + "dimension_update_success": "更新成功", + "dimension_update_failed": "更新失敗", + "dimension_name_required": "請輸入名稱", + "dimension_back": "退出", + "dimension_test_run": "試運行", + "dimension_save": "保存", + "file_import_back": "退出", + "file_import_name_label": "數據集名", + "file_import_name_placeholder": "請輸入名稱", + "file_import_select_file": "請選擇文件", + "file_import_success": "文件導入成功", + "file_import_file_label": "文件", + "file_import_download_template": "點擊下載文件模板", + "file_import_download_template_tip": "一行一個詞條,第1列為【提問】,第2列為【答案】,第1行默認為表頭,不會作為詞條內容。例如:", + "file_import_auto_evaluation_label": "導入後自動進行數據質量評測", + "file_import_auto_evaluation_tip": "開啟後將自動對導入的數據進行質量評測", + "file_import_evaluation_model_label": "質量評測模型", + "file_import_evaluation_model_placeholder": "請選擇質量評測模型", + "file_import_confirm": "確認", + "manage_dimension": "管理維度", + "selected_count": "已選", + "dimension_config_tip": "維度配置說明", + "custom": "自定義", + "select_model_placeholder": "請選擇模型", + "create_new_task_modal": "新建任務", + "task_name_input": "任務名", + "evaluation_app_select": "評測應用", + "evaluation_app_support_tip": "當前支持簡易應用和工作流,暫不支持插件。", + "evaluation_app_select_placeholder": "請選擇評測應用", + "evaluation_app_version_select": "評測應用版本", + "evaluation_app_version_select_placeholder": "請選擇評測應用版本", + "evaluation_dataset_select": "評測數據集", + "evaluation_dataset_select_placeholder": "請選擇評測數據集", + "create_import_dataset": "新建/導入", + "evaluation_dimensions_label": "評測維度", + "evaluation_dimensions_recommendation": "評測應用包含知識庫搜索和AI對話環節,推薦使用 {{num}} 個維度進行評估", + "builtin_dimension": "內建", + "custom_dimension": "自定義", + "config_params": "配置參數", + "score_aggregation_method": "分數聚合方式", + "evaluation_dimensions": "評測維度", + "dimension": "維度", + "judgment_threshold": "判定閾值", + "judgment_threshold_tip": "用於判定測試數據在單個維度中表現是否符合預期,分數低於閾值時,將標記為不符合預期,可輔助識別存在問題的數據,分值為 1~100。\n評測後查看任務時可根據需要再次調整。", + "comprehensive_score_weight": "綜合評分權重", + "comprehensive_score_weight_tip": "將按照指定權重計算測試數據全部維度的綜合評分,可根據應用使用場景所關注的維度進行設置。\n評測後查看任務時可根據需要再次調整。", + "comprehensive_score_weight_sum": "綜合評分權重和:", + "intelligent_generation_dataset": "智能生成數據集", + "dataset_name_input": "名稱", + "dataset_name_input_placeholder": "請輸入數據集名稱", + "generation_basis": "生成依據", + "select_knowledge_base": "選擇知識庫", + "data_amount": "數據量", + "generation_model": "生成模型", + "generation_model_placeholder": "請選擇生成模型", + "high_quality": "品質高", + "needs_improvement": "待優化", + "detail_evaluating": "測評中", + "abnormal": "評測異常", + "not_evaluated": "未評測", + "modify_result": "修改結果", + "restart_evaluation": "重新測評", + "evaluation_error_message": "測評過程中出現異常,請重新測評", + "high_quality_feedback": "該問題質量較高,表述清晰準確,符合標準要求。問題描述完整,答案準確且具有實用性。", + "needs_improvement_feedback": "該問題相對清晰,但可能需要進一步優化。建議增加更多上下文信息,使問題更加具體和明確,以便提供更準確的答案。", + "evaluation_service_error": "測評服務異常", + "edit_data": "編輯數據", + "enter_question": "請輸入問題", + "question_required": "問題不能為空", + "reference_answer": "參考答案", + "enter_reference_answer": "請輸入參考答案", + "reference_answer_required": "參考答案不能為空", + "quality_evaluation": "質量測評", + "quality_evaluation_btn_text": "質量測評", + "cancel": "取消", + "save": "僅保存", + "save_and_next": "保存並下一個", + "manually_calibrated": "已人工修改結果", + "modify_evaluation_result_title": "修改評測結果", + "evaluation_result_label": "評測結果", + "modify_reason_label": "修改理由", + "modify_reason_input_placeholder": "修改理由", + "no_data": "暫無數據", + "search": "搜索", + "settings": "設置", + "add_data": "追加數據", + "ai_generate": "智能生成", + "manual_add": "手動新增", + "no_answer": "暫無答案", + "confirm_delete_data": "確認刪除該數據?", + "no_evaluation_result_click": "還沒有測評結果,點擊", + "start_evaluation_action": "開始測評", + "evaluation_dataset": "評測數據集", + "evaluation_status": "評測狀態", + "manually_add_data_modal": "手動新增數據", + "question_input_label": "問題", + "reference_answer_input_label": "參考答案", + "auto_quality_eval_after_add": "數據質量評測", + "auto_quality_eval_add_tip": "開啟後,將自動對數據的問答相關度、合理性等進行評測,給出數據質量評價及原因,可根據評價對數據進行優化。", + "quality_eval_model_label": "質量評測模型", + "select_quality_eval_model_placeholder": "請選擇質量評測模型", + "confirm": "確認", + "confirm_quality_evaluation": "確認對全部數據({{total}}進行質量評測嗎", + "model_change_notice": "更改模型後將對後續評測的任務生效。", + "evaluation_model": "評測模型", + "select_evaluation_model": "請選擇評測模型", + "evaluation_abnormal": "評測異常", + "error_message": "錯誤信息", + "file_parse_error": "文件解析異常", + "delete_file": "刪除文件", + "reparse": "重新解析", + "data_generation_error": "條數據生成異常", + "source_knowledge_base": "依據知識庫", + "source_chunk": "依據分塊", + "operations": "操作", + "retry": "重試", + "retry_all": "全部重試", + "error_info": "異常信息", + "manual_add_data": "手動新增數據", + "max_3000_chars": "最多 3000 字", + "please_enter_question": "請輸入問題", + "question_max_3000_chars": "問題不能超過3000字", + "please_enter_reference_answer": "請輸入參考答案", + "reference_answer_max_3000_chars": "參考答案不能超過3000字", + "auto_quality_evaluation": "新增後自動進行數據質量評測", + "quality_evaluation_tip": "將對數據的問答相關度、合理性等進行評測,給出數據質量評價及原因,可根據評價對數據進行優化。", + "quality_evaluation_model": "質量評測模型", + "please_select_evaluation_model": "請選擇質量評測模型", + "summary_pending": "待生成", + "summary_generating": "生成中", + "summary_done": "已完成", + "summary_failed": "生成失敗", + "method_mean": "平均值", + "method_median": "中位數", + "prompt_cannot_be_empty": "提示詞不允許為空", + "please_select_model": "請選擇模型", + "run_failed_please_retry": "運行失敗,請重試", + "intelligent_generate_data": "智能生成數據", + "evaluation_completed": "評估完成", + "generation_error": "生成異常", + "data_generating": "數據生成中", + "delete_dataset_error": "刪除數據集異常", + "create_failed": "創建失敗", + "no_dimension_data": "暫無維度數據", + "please_select_dimension_first": "請先選擇該維度", + "model_evaluation_tip": "語言模型可判斷實際回答和參考答案中的文本內容是否匹配;\n索引模型可將實際回答和參考答案轉成向量,進一步評估語義相似性。", + "create_new_dimension": "新建維度", + "retry_success": "重試成功", + "data_generation_error_count": "{{count}}條數據生成異常", + "builtin_answer_correctness_name": "回答準確度", + "builtin_answer_correctness_desc": "衡量生成的回答與參考答案在事實上的一致性,評估其是否準確無誤。", + "builtin_answer_similarity_name": "語義相似度", + "builtin_answer_similarity_desc": "評估生成回答與參考答案在語義上的匹配程度,判斷其是否表達了相同的核心信息。", + "builtin_answer_relevancy_name": "回答相關度", + "builtin_answer_relevancy_desc": "衡量生成回答與提問之間的契合度,判斷回答是否緊扣問題。", + "builtin_faithfulness_name": "回答忠誠度", + "builtin_faithfulness_desc": "評估生成回答是否忠實於提供的上下文信息,判斷是否存在虛構或不實內容。", + "builtin_context_recall_name": "檢索匹配度", + "builtin_context_recall_desc": "衡量檢索系統是否能夠獲取回答所需的所有關鍵信息,評估其檢索的完整性。", + "builtin_context_precision_name": "檢索精確度", + "builtin_context_precision_desc": "衡量檢索內容中是否優先返回高價值信息,反映排序質量與信息密度。", + "join_evaluation_dataset": "加入評測數據集", + "not_join_evaluation_dataset": "不加入評測數據集", + "create_new_dataset_btn_text": "新建數據集", + "please_select_evaluation_dataset": "請選擇評測數據集", + "join_knowledge_base": "加入知識庫", + "all_data_with_count": "全部數據({{num}})", + "question_data_with_count": "問題數據({{num}})", + "error_data_with_count": "異常數據({{num}})", + "export_data": "導出", + "retry_action": "重試", + "basic_info": "基本資訊", + "application": "應用", + "version": "版本", + "evaluation_dataset_name": "評測數據集", + "start_time": "開始時間", + "end_time": "結束時間", + "executor_name": "執行人", + "app_with_search_and_chat_recommendation": "評測應用包含知識庫搜索和AI對話環節,已推薦使用 3 個維度進行評估", + "app_with_chat_recommendation": "評測應用包含AI對話環節,已推薦使用 1 個維度進行評估", + "app_with_search_recommendation": "評測應用包含知識庫搜索環節,已推薦使用 2 個維度進行評估", + "no_dimensions_added": "還沒有添加評測維度,", + "click_to_add": "點擊添加", + "meets_expectation": "符合預期!", + "below_expectation": "低於預期分數!", + "summary_generation_error": "總結內容生成異常,", + "error_message_prefix": "報錯信息:", + "summary_pending_generation": "總結內容待生成", + "summary_generating_content": "總結內容生成中", + "data_with_count": "數據({{data}})", + "search_placeholder": "搜索", + "detail_title": "詳情", + "modify_dataset_simultaneously": "同時修改評測數據集", + "retry_button": "重試", + "edit_action": "編輯", + "delete_action": "刪除", + "confirm_delete_data_in_task": "確認在當前任務中刪除該數據?", + "view_full_response": "查看完整響應", + "abnormal_status": "異常", + "question_field": "問題", + "reference_answer_field": "參考答案", + "actual_answer_field": "實際回答", + "no_answer_available": "暫無回答", + "comprehensive_score_title": "綜合評分", + "dimension_score_title": "維度評分", + "error_data_calculation_notice": "{{count}} 條數據執行異常,僅使用執行成功的數據來計算分數。", + "regenerate_summary_content": "重新生成總結內容", + "processing_status": "處理中...", + "view_results_after_completion": "完成後可查看評測結果", + "all_data_execution_error": "全部數據執行異常,", + "check_error_details": "請查看異常數據中的詳細原因,", + "click_to_retry": "點擊重試", + "comprehensive_score_weight_description": "按照指定權重計算測試數據全部維度的綜合評分,可根據應用使用場景所關注的維度進行設置。", + "no_data_available": "暫無數據", + "case_id_column": "評估項ID", + "question_column": "問題", + "comprehensive_score_column": "綜合評分", + "error_info_column": "異常信息", + "request_failed": "請求失敗", + "retry_request_submitted": "重試請求已提交", + "retry_failed": "重試失敗", + "save_success": "保存成功", + "save_failed": "保存失敗", + "summary_generation_request_submitted": "總結生成請求已提交", + "generate_summary_failed": "生成總結失敗", + "export_failed": "導出失敗", + "load_failed": "加載失敗", + "export_success": "導出成功", + "no_dimension_data_cannot_generate_summary": "暫無維度數據,無法生成總結", + "detail": "詳情", + "eval_file_check_error": "評測文件校驗失敗", + "stauts": "狀態", + "task_name": "任務名稱", + "Task_name": "任務名", + "click_to_download_template": "點擊下載該應用的 CSV 模板", + "evaluation_created": "評測任務創建成功", + "evaluation_dimension_missing_model_config": "評測維度缺少模型配置", + "evaluation_dimension_missing_model_config_desc": "以下評測維度缺少模型配置,請先完善配置:{{names}}", + "example_highest_mountain_question": "世界最高峰是什麼", + "example_highest_mountain_answer": "珠穆朗瑪峰", + "data_updated_before_retest": "數據已更新,重新評測前將先自動保存" } diff --git a/packages/web/i18n/zh-Hant/database_client.json b/packages/web/i18n/zh-Hant/database_client.json new file mode 100644 index 000000000000..9f7c03f3f5f5 --- /dev/null +++ b/packages/web/i18n/zh-Hant/database_client.json @@ -0,0 +1,24 @@ +{ + "client_destory_error": "資料庫連線客戶端銷毀失敗", + "client_not_found": "資料庫連線客戶端未正確初始化", + "not_support_databaseType": "不支援的資料庫類型", + "not_implemented_databaseType": "未實作的資料庫客戶端", + "connection_failed": "資料庫連線失敗,請檢查連線配置", + "authentication_failed": "資料庫認證失敗,請檢查使用者名稱和密碼以及資料庫權限是否正確開放", + "database_not_exist": "資料庫不存在,請檢查資料庫名稱是否正確", + "database_port_error": "資料庫連接埠錯誤,請檢查連接埠是否正確", + "connection_address_error": "連接位址錯誤,請檢查位址是否正確", + "connection_timeout": "資料庫連線逾時,請檢查網路是否通暢", + "connection_refused": "資料庫連線被拒絕,請檢查資料庫是否允許外部連線", + "connection_check_error": "連線測試失敗,請檢查網路是否通暢", + "connection_lost": "資料庫連線中斷,請檢查網路是否通暢", + "host_error": "連線位址錯誤,請檢查位址是否正確", + "invalid_table_name": "無效的表名", + "fetch_info_error": "取得資料庫資訊失敗", + "database_config_not_found": "未配置資料庫連線訊息,請先設定", + "table_not_found": "取得表格資訊失敗,表格不存在", + "illeagal_table_info": "非法的表格訊息,請檢查表格資訊是否正確", + "op_unknown_database_error": "嘗試操作您的資料庫時發生未知錯誤,請檢查資料庫連線是否正常", + "dative_service_error": "連線Dative服務時發生錯誤,請檢查設定是否正確", + "tableNamesDuplicate": "存在多個重複表名" +} \ No newline at end of file diff --git a/packages/web/i18n/zh-Hant/dataset.json b/packages/web/i18n/zh-Hant/dataset.json index 0b53de95fda5..c3e86653422a 100644 --- a/packages/web/i18n/zh-Hant/dataset.json +++ b/packages/web/i18n/zh-Hant/dataset.json @@ -138,7 +138,7 @@ "noSelectedFolder": "沒有選擇文件夾", "noSelectedId": "沒有選擇 ID", "noValidId": "沒有有效的 ID", - "open_auto_sync": "開啟定時同步後,系統將每天不定時嘗試同步集合,集合同步期間,會出現無法搜尋到該集合資料現象。", + "open_auto_sync": "開啟自動同步後,系統將每天不定時嘗試同步集合,集合同步期間,會出現無法搜尋到該集合資料現象。", "other_dataset": "第三方知識庫", "paragraph_max_deep": "最大段落深度", "paragraph_split": "按段落分塊", @@ -190,7 +190,7 @@ "start_sync_dataset_tip": "確實開始同步整個知識庫?", "status_error": "執行異常", "sync_collection_failed": "同步集合錯誤,請檢查是否能正常存取來原始檔", - "sync_schedule": "定時同步", + "sync_schedule": "自動同步", "sync_schedule_tip": "只會同步已存在的集合。\n包括連結集合以及 API 知識庫裡所有集合。\n系統會每天進行輪詢更新,無法確定特定的更新時間。", "tag.Add_new_tag": "新增標籤", "tag.Edit_tag": "編輯標籤", @@ -222,5 +222,124 @@ "website_info": "網站資訊", "yuque_dataset": "語雀知識庫", "yuque_dataset_config": "設定語雀知識庫", - "yuque_dataset_desc": "可透過設定語雀文件權限,使用語雀文件建構知識庫,文件不會進行二次儲存" + "yuque_dataset_desc": "可透過設定語雀文件權限,使用語雀文件建構知識庫,文件不會進行二次儲存", + "enterprise_database": "資料庫", + "enterprise_database_desc": "授權 MySQL 資料庫後讀取選定的資料表,檢索時連接資料庫獲取相關資訊", + "enterprise_database_embedding_model_tip": "索引模型可將資料庫關鍵資訊(截取部分資料的表名、表描述、列名、列描述)轉成向量,用於進行語義檢索。", + "database_structure_change_tip": "資料結構發生變化時,如資料表名稱變更或其中的列名變更等,請手動重新整理資料來源。", + "refresh_data_source": "重新整理資料來源", + "search_name_or_description": "搜尋名稱或描述", + "connect_database": "連接資料庫", + "data_config": "資料配置", + "database_type": "資料庫類型", + "mysql_description": "支援 5.7 及其以上版本", + "database_host": "資料庫位址", + "host_placeholder": "請輸入IP位址或域名", + "host_tips": "請確保資料庫所在與本平台連通,填入可存取的位址", + "host_required": "資料庫位址不能為空", + "port": "連接埠號", + "port_required": "連接埠號不能為空", + "database_name": "資料庫名稱", + "database_username": "資料庫使用者名稱", + "database_password": "資料庫密碼", + "connection_pool_size": "連接池大小", + "connection_pool_required": "連接池大小不能為空", + "connection_pool_min_error": "連接池大小不能小於1", + "connection_pool_max_error": "連接池大小不能大於100", + "connect_next_step": "連接並下一步", + "database_config_title": "知識庫將呼叫已選資料表中的資料進行索引", + "search_tables": "資料表名", + "table_selection_warning": "存在未選擇內容", + "table_description": "資料表描述", + "default_table_description": "預設值是資料表表自帶的描述", + "column_configuration": "資料列配置", + "search_columns": "列名", + "column_name": "列名", + "column_type": "類型", + "column_description": "描述", + "column_enabled": "啟用", + "default_column_description": "預設值是資料表表自帶的描述", + "confirm": "確認", + "edit_database_config_warning": "資料來源已配置,發生 {{changedCount}} 個資料表存在列的變更,{{deletedCount}} 個資料表已不存在,請修正最新資料。", + "edit_database_warning": "修改資料庫資訊後,當前正在存取資料庫的應用將斷開重連,可能會導致正在執行的應用無法檢索到結果。", + "database_config": "配置", + "refresh_success": "重新整理成功", + "refresh_datasource": "重新整理資料來源", + "reconnect_success": "重新連接資料庫,變更配置成功", + "auth_failed": "資料庫連接失敗,認證未通過,請檢查使用者名稱和密碼。", + "connection_failed": "連接失敗", + "reconnecting": "重新連接中", + "reconnect_success_detail": "已重新連接資料庫,變更配置成功", + "table_changes_notice": "發現 {{changedCount}} 個資料表存在列的變更,{{deletedCount}} 個資料表已不存在,請核查最新資料。", + "reconnect_database": "重新連接資料庫", + "column_desc_accuracy_tip": "為了提高問答準確率,請準確填寫列描述,用來解釋此列資料的含義和用途,大模型將會根據列描述選擇對應的列資料進行檢索和生成回答", + "default_table_desc_tip": "預設使用資料表中定義的描述。", + "column_enabled_tip": "啟用後表示使用該列資料進行檢索及回答", + "connecting": "正在連接", + "test_connectivity": "測試連通性", + "connect_and_next": "連接並下一步", + "connection_network_error": "連接失敗,請檢查網路連接", + "validate_ip_tip": "該輸入項禁止使用本地回環位址,如:localhost、127.x.x.x、0.0.0.0", + "database": "資料庫", + "search_model": "檢索模型", + "search_model_desc": "用於生成可在資料庫中檢索的SQL語句,並進行檢索與匯總,生成可用於對話的文字。", + "search_model_tip": "使用非推理模型、參數量大的模型效果更佳。", + "other_knowledge_base": "其他知識庫", + "database_search": "資料庫檢索", + "description": "描述", + "remove": "移除", + "confirm_remove_database_table": "確認移除該數據表及其所有數據配置?", + "data_source_refreshed": "資料來源已刷新,", + "found": "發現", + "tables_with_column_changes": "發現 {{modifiedTablesCount}} 個資料表存在欄位的變更,", + "tables_not_exist": "發現 {{delTablesCount}} 個資料表已不存在,", + "please_check_latest_data": "請核查最新資料。", + "has_unfilled_content": "存在未填寫的內容", + "has_column_changes": "存在欄位的變更", + "connection_success": "連接成功", + "tables_added": "新增 {{addedTables}} 個數據表", + "tables_modified": "發現 {{modifiedTables}} 個數據表存在列的變更,", + "tables_deleted": "發現 {{deletedTables}} 個數據表已不存在,", + "please_verify_data": "請核查最新數據。", + "changes_detected": "發現", + "new_columns_added_disabled": "個新增列(默認未啟用),", + "columns_no_longer_exist": "個列已不存在,", + "check_latest_data": "請核查最新數據。", + "config": "配置", + "refresh_failed": "刷新失敗", + "unknown_error": "未知錯誤", + "no_data_available": "暫無數據", + "database_sql_query": "資料庫檢索的 SQL 語句", + "search_result": "檢索結果", + "no_search_result": "暫無檢索結果", + "database_address_required": "資料庫地址不能為空", + "ip_format_invalid_range": "IP地址格式不正確,每段數值應在0-255之間", + "database_address_format_invalid": "資料庫地址格式不正確,請輸入有效的IP地址或域名", + "data_index_column_value":"SQL欄位範例值索引", + "error_create_datasetcollection": "建立資料集失敗,請聯絡管理員", + "tables_modified_and_deleted": "發現 {{modifiedTables}} 個數據表存在列的變更,{{deletedTables}} 個數據表已不存在,", + "tables_no_longer_exist_comma": "個數據表已不存在,", + "process.databaseSchema": "資料庫結構索引產生", + "database_url": "資料庫地址", + "database_url_placeholder": "例如: localhost 或 192.168.1.100", + "database_url_required": "資料庫地址不能為空", + "database_port": "連接埠", + "database_username_placeholder": "資料庫使用者名稱", + "database_username_required": "使用者名稱不能為空", + "database_password_placeholder": "資料庫密碼", + "database_password_required": "密碼不能為空", + "database_max_connections": "最大連接數", + "database_required_fields": "請填寫所有必填欄位", + "save_database_config": "儲存配置", + "confirm_update_database_config": "確認更新資料庫配置?", + "confirm_create_database_config": "確認創建資料庫配置?", + "database_config_updated": "資料庫配置已更新", + "data_index_column_description": "SQL欄位描述索引", + "no_data_in_database": "該數據庫中沒有數據", + "database_host_tooltip": "請輸入可與本平台連通的正確地址。", + "database_host_port_tip": "範圍:{{min}}~{{max}}", + "connection_pool_size_tooltip": "應用在資料庫進行檢索時需與資料庫建立連接,為避免影響資料庫正常業務,將建立連接池,保障應用與資料庫的連接數不超過連接池的大小。可根據資料庫性能及業務並發訪問量合理分配。", + "database_table_desc_tip": "為了提高問答準確率,請準確填寫數據表描述,用來解釋該表的含義和用途,大模型將會根據描述識別並調用該表。", + "database_tables": "數據表", + "remove_warning": "移除警告" } diff --git a/packages/web/i18n/zh-Hant/evaluation.json b/packages/web/i18n/zh-Hant/evaluation.json new file mode 100644 index 000000000000..d2e9870cb7e8 --- /dev/null +++ b/packages/web/i18n/zh-Hant/evaluation.json @@ -0,0 +1,130 @@ +{ + "dataset_collection_not_found": "評估數據集未找到", + "dataset_data_not_found": "評估數據集數據未找到", + "name_required": "名稱必填", + "name_too_long": "名稱過長", + "name_duplicate": "名稱已存在", + "description_too_long": "描述過長", + "target_required": "評估目標必填", + "target_invalid_config": "評估目標配置無效", + "target_app_id_missing": "應用ID缺失", + "target_version_id_missing": "應用版本ID缺失", + "evaluators_required": "評估器必填", + "evaluator_invalid_config": "評估器配置無效", + "evaluator_invalid_score_scaling": "評估器分數縮放值無效,必須是大於0且小於等於10000的正數(支持小數,如0.01表示縮小100倍)", + "user_input_required": "用戶輸入必填", + "expected_output_required": "預期輸出必填", + "invalid_format": "無效的格式", + "id_required": "評估ID不能為空", + "item_id_required": "評估項ID不能為空", + "data_item_id_required": "數據項ID不能為空", + "invalid_context": "無效的上下文", + "invalid_retrieval_context": "無效的檢索上下文", + "insufficient_permission": "權限不足", + "app_not_found": "應用未找到", + "task_not_found": "評估任務未找到", + "item_not_found": "評估項未找到", + "invalid_status": "無效的狀態", + "invalid_state_transition": "無效的狀態轉換, 只有排隊狀態或手動停止的評估可以啟動", + "only_running_can_stop": "只有運行中的評估可以停止", + "item_no_error_to_retry": "評估項無錯誤可重試", + "target_execution_error": "評估目標執行錯誤", + "dataset_load_failed": "數據集加載失敗", + "target_config_invalid": "目標配置無效", + "evaluators_config_invalid": "評估器配置無效", + "unsupported_target_type": "不支持的目標類型", + "app_version_not_found": "應用版本未找到", + "duplicate_dataset_name": "數據集名稱重複", + "no_data_in_collections": "集合中無數據", + "update_failed": "更新失敗", + "task_system_error": "處理評估任務時發生系統錯誤", + "manually_stopped": "手動停止", + "evaluator_execution_errors": "評估器執行錯誤", + + "metric_not_found": "評估指標未找到", + "metric_un_auth": "無權操作該評估指標", + "metric_name_required": "指標名稱不能為空", + "metric_name_too_long": "指標名稱不能超過100個字符", + "metric_description_required": "指標描述不能為空", + "metric_description_too_long": "指標描述不能超過100個字符", + "metric_prompt_required": "指標提示詞不能為空", + "metric_prompt_too_long": "指標提示詞不能超過4000個字符", + "metric_type_required": "指標類型不能為空", + "metric_type_invalid": "指標類型無效", + "metric_name_invalid": "指標名稱無效", + "metric_builtin_cannot_modify": "內建指標不能修改", + "metric_builtin_cannot_delete": "內建指標不能刪除", + "metric_id_required": "指標ID不能為空", + + "eval_case_required": "評估用例不能為空", + "eval_case_user_input_required": "用戶輸入不能為空", + "eval_case_user_input_too_long": "用戶輸入不能超過1000個字符", + "eval_case_actual_output_required": "實際輸出不能為空", + "eval_case_actual_output_too_long": "實際輸出不能超過4000個字符", + "eval_case_expected_output_required": "期望輸出不能為空", + "eval_case_expected_output_too_long": "期望輸出不能超過4000個字符", + + "llm_config_required": "LLM配置不能為空", + "llm_model_name_required": "LLM模型名稱不能為空", + + "debug_evaluation_failed": "評估調試失敗", + + "evaluator_config_required": "評估器配置不能為空", + "evaluator_llm_config_missing": "評估器缺少LLM配置", + "evaluator_embedding_config_missing": "評估器缺少嵌入模型配置", + "evaluator_llm_model_not_found": "LLM模型不存在或獲取失敗", + "evaluator_embedding_model_not_found": "嵌入模型不存在或獲取失敗", + "evaluator_request_timeout": "評估器請求超時", + "evaluator_service_unavailable": "評估器服務不可用", + "evaluator_invalid_response": "評估器返回無效響應", + "evaluator_network_error": "評估器網絡連接錯誤", + + "eval_id_required": "評估任務ID不能為空", + "summary_metrics_config_error": "指標配置錯誤", + "summary_threshold_value_required": "閾值不能為空", + "summary_weight_required": "權重不能為空", + "summary_weight_must_be_number": "權重必須為數字", + "summary_threshold_must_be_number": "閾值必須為數字", + "summary_calculate_type_required": "計算類型不能為空", + "summary_calculate_type_invalid": "計算類型無效", + "summary_no_valid_metrics_found": "沒有找到有效的指標", + "summary_stream_response_not_supported": "不支持流式響應", + "summary_weight_sum_must_be_100": "所有指標的權重總和必須等於100", + "summary_model_invalid": "評估總結生成失敗,請檢查評估模型配置", + + "dataset_collection_id_required": "數據集合 ID 是必需的", + "dataset_collection_update_failed": "數據集合更新失敗", + "dataset_model_not_found": "模型未找到", + "dataset_no_data": "數據集不包含數據", + "dataset_data_id_required": "數據集數據 ID 是必需的", + "data_quality_status_invalid": "數據質量狀態無效", + "dataset_data_user_input_required": "用戶輸入是必需的", + "dataset_data_expected_output_required": "預期輸出是必需的", + "dataset_data_actual_output_must_be_string": "實際輸出必須是字符串", + "dataset_data_context_must_be_array_of_strings": "上下文必須是字符串數組", + "dataset_data_retrieval_context_must_be_array_of_strings": "檢索上下文必須是字符串數組", + "dataset_data_enable_quality_eval_required": "啟用質量評估標誌是必需的", + "dataset_data_evaluation_model_required_for_quality": "啟用質量評估時需要評估模型", + "dataset_data_metadata_must_be_object": "元數據必須是對象", + "dataset_data_list_error": "數據集數據列表錯誤", + "quality_assessment_failed": "質量評估失敗", + "data_quality_job_active_cannot_set_high_quality": "質量評估任務進行中,無法設置為高質量狀態", + "dataset_task_not_retryable": "任務不可重試", + "dataset_task_job_not_found": "未找到任務作業", + "dataset_task_job_mismatch": "任務作業不屬於此集合", + "dataset_task_only_failed_can_delete": "只能刪除失敗的任務", + "dataset_task_operation_failed": "任務操作失敗", + "dataset_task_delete_failed": "數據集任務刪除失敗", + "fetch_failed_tasks_error": "獲取失敗任務列表失敗", + "file_required": "文件是必需的", + "file_must_be_csv": "文件必須是 CSV 文件", + "csv_invalid_structure": "CSV 文件結構無效", + "csv_parsing_error": "CSV 解析錯誤", + "csv_no_data_rows": "CSV 文件不包含數據行", + "count_must_be_greater_than_zero": "計數必須大於零", + "count_exceeds_available_data": "請求的計數超過可用數據", + "selected_datasets_contain_no_data": "所選數據集不包含數據", + "model_name_invalid": "評估模型名稱必須是字串", + "model_name_too_long": "評估模型名稱不能超過 100 個字元", + "description_invalid_type": "描述必須是字串" +} diff --git a/packages/web/i18n/zh-Hant/file.json b/packages/web/i18n/zh-Hant/file.json index 8445cf42e5de..b4d4546227a7 100644 --- a/packages/web/i18n/zh-Hant/file.json +++ b/packages/web/i18n/zh-Hant/file.json @@ -5,7 +5,7 @@ "Failed_to_get_token": "獲取令牌失敗", "Image_ID_copied": "已復制ID", "Image_Preview": "圖片預覽", - "Image_dataset_requires_VLM_model_to_be_configured": "圖片數據集需要配置圖片理解模型(VLM)才能使用,請先在模型配置中添加支持圖片理解的模型", + "Image_dataset_requires_VLM_model_to_be_configured": "圖片數據集需要配置圖片理解模型(VLM)才能使用,請先在模型配置中添加支持圖片理解的模型", "Image_does_not_belong_to_current_team": "圖片不屬於當前團隊", "Image_file_does_not_exist": "圖片不存在", "Loading_image": "加載圖片中...", @@ -15,6 +15,7 @@ "Please wait for all files to upload": "請等待所有文件上傳完成", "bucket_chat": "對話檔案", "bucket_file": "知識庫檔案", + "eval_file": "評測檔案", "bucket_image": "圖片", "click_to_view_raw_source": "點選檢視原始來源", "common.Some images failed to process": "部分圖片處理失敗", @@ -35,7 +36,7 @@ "some_file_count_exceeds_limit": "已超過 {{maxCount}} 個檔案上限,系統已自動截斷", "some_file_size_exceeds_limit": "部分檔案超過 {{maxSize}} 大小限制,已自動過濾", "support_file_type": "支援 {{fileType}} 格式的檔案", - "support_max_count": "最多可支援 {{maxCount}} 個檔案", + "support_max_count": "最多可支援 {{maxCount}} 個檔案,", "support_max_size": "單一檔案大小上限為 {{maxSize}}", "template_csv_file_select_tip": "僅支持嚴格按照模板格式的 {{fileType}} 文件", "template_strict_highlight": "嚴格按照模版", diff --git a/packages/web/i18n/zh-Hant/publish.json b/packages/web/i18n/zh-Hant/publish.json index e19c34c9fa6b..487a5bc1c284 100644 --- a/packages/web/i18n/zh-Hant/publish.json +++ b/packages/web/i18n/zh-Hant/publish.json @@ -19,6 +19,7 @@ "official_account.desc": "透過 API 直接連結微信公眾號", "official_account.edit_modal_title": "編輯微信公眾號整合", "official_account.name": "微信公眾號整合", + "official_account.wechat": "微信公眾號", "official_account.params": "微信公眾號參數", "private_config": "顯示設定", "publish_name": "名稱", diff --git a/packages/web/i18n/zh-Hant/workflow.json b/packages/web/i18n/zh-Hant/workflow.json index 3bbfa9d654ef..fceb16981d0d 100644 --- a/packages/web/i18n/zh-Hant/workflow.json +++ b/packages/web/i18n/zh-Hant/workflow.json @@ -58,7 +58,7 @@ "execute_different_branches_based_on_conditions": "根據條件執行不同的分支。", "execution_error": "執行錯誤", "extraction_requirements_description": "擷取需求描述", - "extraction_requirements_description_detail": "提供 AI 相對應的背景知識或需求描述,引導 AI 更好地完成任務。\\n這個輸入框可以使用全域變數。", + "extraction_requirements_description_detail": "提供 AI 相對應的背景知識或需求描述,引導 AI 更好地完成任務。\n這個輸入框可以使用全域變數。", "extraction_requirements_placeholder": "例如:1. 目前時間為:{{cTime}}。\n你是實驗室預約助手,你的任務是幫助使用者預約實驗室,從文字中取得對應的預約資訊。\n\n2. 你是 Google 搜尋助手,需要從文字中提取出合適的搜尋字詞。", "feedback_text": "回饋文字", "field_description": "欄位描述", @@ -176,7 +176,7 @@ "template.ai_chat": "AI 對話", "template.ai_chat_intro": "AI 大型語言模型對話", "template.dataset_search": "知識庫搜尋", - "template.dataset_search_intro": "使用「語意搜尋」和「全文搜尋」功能,從「知識庫」中尋找可能與問題相關的參考內容", + "template.dataset_search_intro": "調用語義檢索、全文檢索、數據庫檢索能力,從“知識庫”中查找可能與問題相關的參考內容。", "template.forbid_stream": "停用串流輸出", "template.forbid_stream_desc": "強制設定巢狀執行的應用程式以非串流模式執行", "template.plugin_output": "外掛程式輸出", diff --git a/packages/web/package.json b/packages/web/package.json index 4be2dbf6bf4f..b1b07b8b5853 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,6 +1,10 @@ { "name": "@fastgpt/web", "version": "1.0.0", + "scripts": { + "check-i18n": "node ../../scripts/i18n/checkI18nCompleteness.js", + "fix-i18n": "node ../../scripts/i18n/checkI18nCompleteness.js --fix" + }, "dependencies": { "@chakra-ui/anatomy": "2.2.1", "@chakra-ui/icons": "2.1.1", diff --git a/packages/web/styles/theme.ts b/packages/web/styles/theme.ts index 73e134b57198..026c43cd88a8 100644 --- a/packages/web/styles/theme.ts +++ b/packages/web/styles/theme.ts @@ -354,6 +354,18 @@ const Input: ComponentStyleConfig = { _disabled: { color: 'myGray.400', bg: 'myWhite.300' + }, + _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' + } } } } @@ -363,7 +375,6 @@ const Input: ComponentStyleConfig = { variant: 'outline' } }; - const NumberInput = numInputMultiStyle({ sizes: { sm: defineStyle({ @@ -629,6 +640,9 @@ const Table = tableMultiStyle({ borderRightRadius: 'md' } } + }, + th: { + textTransform: 'none' } }, tbody: { diff --git a/packages/web/support/user/audit/constants.ts b/packages/web/support/user/audit/constants.ts index 210f9819a0a7..389760aa7b32 100644 --- a/packages/web/support/user/audit/constants.ts +++ b/packages/web/support/user/audit/constants.ts @@ -442,6 +442,258 @@ export const auditLogMap = { datasetType: string; } }, + //Evaluation Dataset + [AuditEventEnum.CREATE_EVALUATION_DATASET_COLLECTION]: { + content: i18nT('account_team:log_create_evaluation_dataset_collection'), + typeLabel: i18nT('account_team:create_evaluation_dataset_collection'), + params: {} as { + name?: string; + collectionName: string; + } + }, + [AuditEventEnum.UPDATE_EVALUATION_DATASET_COLLECTION]: { + content: i18nT('account_team:log_update_evaluation_dataset_collection'), + typeLabel: i18nT('account_team:update_evaluation_dataset_collection'), + params: {} as { + name?: string; + collectionName: string; + } + }, + [AuditEventEnum.DELETE_EVALUATION_DATASET_COLLECTION]: { + content: i18nT('account_team:log_delete_evaluation_dataset_collection'), + typeLabel: i18nT('account_team:delete_evaluation_dataset_collection'), + params: {} as { + name?: string; + collectionName: string; + } + }, + [AuditEventEnum.CREATE_EVALUATION_DATASET_DATA]: { + content: i18nT('account_team:log_create_evaluation_dataset_data'), + typeLabel: i18nT('account_team:create_evaluation_dataset_data'), + params: {} as { + name?: string; + collectionName: string; + } + }, + [AuditEventEnum.UPDATE_EVALUATION_DATASET_DATA]: { + content: i18nT('account_team:log_update_evaluation_dataset_data'), + typeLabel: i18nT('account_team:update_evaluation_dataset_data'), + params: {} as { + name?: string; + collectionName: string; + } + }, + [AuditEventEnum.DELETE_EVALUATION_DATASET_DATA]: { + content: i18nT('account_team:log_delete_evaluation_dataset_data'), + typeLabel: i18nT('account_team:delete_evaluation_dataset_data'), + params: {} as { + name?: string; + collectionName: string; + } + }, + [AuditEventEnum.QUALITY_ASSESSMENT_EVALUATION_DATA]: { + content: i18nT('account_team:log_quality_assessment_evaluation_data'), + typeLabel: i18nT('account_team:quality_assessment_evaluation_data'), + params: {} as { + name?: string; + collectionName: string; + } + }, + [AuditEventEnum.SMART_GENERATE_EVALUATION_DATA]: { + content: i18nT('account_team:log_smart_generate_evaluation_data'), + typeLabel: i18nT('account_team:smart_generate_evaluation_data'), + params: {} as { + name?: string; + collectionName: string; + } + }, + [AuditEventEnum.DELETE_EVALUATION_DATASET_TASK]: { + content: i18nT('account_team:log_delete_evaluation_dataset_task'), + typeLabel: i18nT('account_team:delete_evaluation_dataset_task'), + params: {} as { + name?: string; + collectionName: string; + } + }, + [AuditEventEnum.RETRY_EVALUATION_DATASET_TASK]: { + content: i18nT('account_team:log_retry_evaluation_dataset_task'), + typeLabel: i18nT('account_team:retry_evaluation_dataset_task'), + params: {} as { + name?: string; + collectionName: string; + } + }, + [AuditEventEnum.IMPORT_EVALUATION_DATASET_DATA]: { + content: i18nT('account_team:log_import_evaluation_dataset_data'), + typeLabel: i18nT('account_team:import_evaluation_dataset_data'), + params: {} as { + name?: string; + collectionName: string; + recordCount?: number; + } + }, + //Evaluation Task + [AuditEventEnum.CREATE_EVALUATION_TASK]: { + content: i18nT('account_team:log_create_evaluation_task'), + typeLabel: i18nT('account_team:create_evaluation_task'), + params: {} as { + name?: string; + taskName: string; + evalDatasetCollectionId: string; + targetType: string; + evaluatorCount: number; + } + }, + [AuditEventEnum.UPDATE_EVALUATION_TASK]: { + content: i18nT('account_team:log_update_evaluation_task'), + typeLabel: i18nT('account_team:update_evaluation_task'), + params: {} as { + name?: string; + taskName: string; + } + }, + [AuditEventEnum.DELETE_EVALUATION_TASK]: { + content: i18nT('account_team:log_delete_evaluation_task'), + typeLabel: i18nT('account_team:delete_evaluation_task'), + params: {} as { + name?: string; + taskName: string; + } + }, + [AuditEventEnum.START_EVALUATION_TASK]: { + content: i18nT('account_team:log_start_evaluation_task'), + typeLabel: i18nT('account_team:start_evaluation_task'), + params: {} as { + name?: string; + taskName: string; + } + }, + [AuditEventEnum.STOP_EVALUATION_TASK]: { + content: i18nT('account_team:log_stop_evaluation_task'), + typeLabel: i18nT('account_team:stop_evaluation_task'), + params: {} as { + name?: string; + taskName: string; + } + }, + [AuditEventEnum.RETRY_EVALUATION_TASK]: { + content: i18nT('account_team:log_retry_evaluation_task'), + typeLabel: i18nT('account_team:retry_evaluation_task'), + params: {} as { + name?: string; + taskName: string; + retryCount: number; + } + }, + //Evaluation Task Item + [AuditEventEnum.DELETE_EVALUATION_TASK_ITEM]: { + content: i18nT('account_team:log_delete_evaluation_task_item'), + typeLabel: i18nT('account_team:delete_evaluation_task_item'), + params: {} as { + name?: string; + taskName: string; + itemId: string; + } + }, + [AuditEventEnum.UPDATE_EVALUATION_TASK_ITEM]: { + content: i18nT('account_team:log_update_evaluation_task_item'), + typeLabel: i18nT('account_team:update_evaluation_task_item'), + params: {} as { + name?: string; + taskName: string; + itemId: string; + } + }, + [AuditEventEnum.RETRY_EVALUATION_TASK_ITEM]: { + content: i18nT('account_team:log_retry_evaluation_task_item'), + typeLabel: i18nT('account_team:retry_evaluation_task_item'), + params: {} as { + name?: string; + taskName: string; + itemId: string; + } + }, + [AuditEventEnum.EXPORT_EVALUATION_TASK_ITEMS]: { + content: i18nT('account_team:log_export_evaluation_task_items'), + typeLabel: i18nT('account_team:export_evaluation_task_items'), + params: {} as { + name?: string; + taskName: string; + format: string; + itemCount: number; + } + }, + //Evaluation Metric + [AuditEventEnum.CREATE_EVALUATION_METRIC]: { + content: i18nT('account_team:log_create_evaluation_metric'), + typeLabel: i18nT('account_team:create_evaluation_metric'), + params: {} as { + name?: string; + metricName: string; + } + }, + [AuditEventEnum.DELETE_EVALUATION_METRIC]: { + content: i18nT('account_team:log_delete_evaluation_metric'), + typeLabel: i18nT('account_team:delete_evaluation_metric'), + params: {} as { + name?: string; + metricName: string; + } + }, + [AuditEventEnum.UPDATE_EVALUATION_METRIC]: { + content: i18nT('account_team:log_update_evaluation_metric'), + typeLabel: i18nT('account_team:update_evaluation_metric'), + params: {} as { + name?: string; + metricName: string; + } + }, + [AuditEventEnum.DEBUG_EVALUATION_METRIC]: { + content: i18nT('account_team:log_debug_evaluation_metric'), + typeLabel: i18nT('account_team:debug_evaluation_metric'), + params: {} as { + name?: string; + metricName: string; + } + }, + //Evaluation Task DataItem Aggregation + [AuditEventEnum.DELETE_EVALUATION_TASK_DATA_ITEM]: { + content: i18nT('account_team:log_delete_evaluation_task_data_item'), + typeLabel: i18nT('account_team:delete_evaluation_task_data_item'), + params: {} as { + name?: string; + taskName: string; + dataItemId: string; + } + }, + [AuditEventEnum.UPDATE_EVALUATION_TASK_DATA_ITEM]: { + content: i18nT('account_team:log_update_evaluation_task_data_item'), + typeLabel: i18nT('account_team:update_evaluation_task_data_item'), + params: {} as { + name?: string; + taskName: string; + dataItemId: string; + } + }, + [AuditEventEnum.RETRY_EVALUATION_TASK_DATA_ITEM]: { + content: i18nT('account_team:log_retry_evaluation_task_data_item'), + typeLabel: i18nT('account_team:retry_evaluation_task_data_item'), + params: {} as { + name?: string; + taskName: string; + dataItemId: string; + } + }, + [AuditEventEnum.EXPORT_EVALUATION_TASK_DATA_ITEMS]: { + content: i18nT('account_team:log_export_evaluation_task_data_items'), + typeLabel: i18nT('account_team:export_evaluation_task_data_items'), + params: {} as { + name?: string; + taskName: string; + format: string; + itemCount: number; + } + }, //SearchTest [AuditEventEnum.SEARCH_TEST]: { content: i18nT('account_team:log_search_test'), @@ -498,5 +750,15 @@ export const auditLogMap = { content: i18nT('account_team:log_delete_api_key'), typeLabel: i18nT('account_team:delete_api_key'), params: {} as { name?: string; keyName: string } + }, + [AuditEventEnum.GENERATE_EVALUATION_SUMMARY]: { + content: i18nT('account_team:log_generate_evaluation_summary'), + typeLabel: i18nT('account_team:generate_evaluation_summary'), + params: {} as { name?: string; evalName: string; metricName: string } + }, + [AuditEventEnum.UPDATE_EVALUATION_SUMMARY_CONFIG]: { + content: i18nT('account_team:log_update_evaluation_summary_config'), + typeLabel: i18nT('account_team:update_evaluation_summary_config'), + params: {} as { name?: string; evalName: string } } } as const; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1126459e88f1..4412f14cf6b0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,7 +46,7 @@ importers: version: 10.1.4(socks@2.8.4) next-i18next: specifier: 15.4.2 - version: 15.4.2(i18next@23.16.8)(next@14.2.28(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + version: 15.4.2(i18next@23.16.8)(next@14.2.28(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) prettier: specifier: 3.2.4 version: 3.2.4 @@ -171,6 +171,9 @@ importers: cheerio: specifier: 1.0.0-rc.12 version: 1.0.0-rc.12 + chinese-conv: + specifier: ^3.2.2 + version: 3.2.2 cookie: specifier: ^0.7.1 version: 0.7.2 @@ -195,6 +198,9 @@ importers: form-data: specifier: ^4.0.4 version: 4.0.4 + franc: + specifier: ^6.2.0 + version: 6.2.0 iconv-lite: specifier: ^0.6.3 version: 0.6.3 @@ -226,14 +232,14 @@ importers: specifier: 2.0.2 version: 2.0.2 mysql2: - specifier: ^3.11.3 - version: 3.13.0 + specifier: ^3.14.3 + version: 3.14.5 next: specifier: 14.2.28 - version: 14.2.28(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1) + version: 14.2.28(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1) nextjs-cors: specifier: ^2.2.0 - version: 2.2.0(next@14.2.28(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1)) + version: 2.2.0(next@14.2.28(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1)) node-cron: specifier: ^3.0.3 version: 3.0.3 @@ -255,6 +261,9 @@ importers: pino-opentelemetry-transport: specifier: ^1.0.1 version: 1.0.1(@opentelemetry/api@1.9.0)(pino@9.7.0) + reflect-metadata: + specifier: ^0.2.2 + version: 0.2.2 request-ip: specifier: ^3.3.0 version: 3.3.0 @@ -267,6 +276,9 @@ importers: turndown: specifier: ^7.1.2 version: 7.2.0 + typeorm: + specifier: ^0.3.24 + version: 0.3.26(babel-plugin-macros@3.1.0)(ioredis@5.6.0)(mongodb@6.14.2(socks@2.8.4))(mysql2@3.14.5)(pg@8.14.0)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@20.17.24)(typescript@5.8.2)) winston: specifier: ^3.17.0 version: 3.17.0 @@ -324,7 +336,7 @@ importers: version: 2.1.1(@chakra-ui/system@2.6.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1))(react@18.3.1) '@chakra-ui/next-js': specifier: 2.4.2 - version: 2.4.2(@chakra-ui/react@2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.28(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react@18.3.1) + version: 2.4.2(@chakra-ui/react@2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.28(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react@18.3.1) '@chakra-ui/react': specifier: 2.10.7 version: 2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -387,7 +399,7 @@ importers: version: 4.17.21 next-i18next: specifier: 15.4.2 - version: 15.4.2(i18next@23.16.8)(next@14.2.28(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + version: 15.4.2(i18next@23.16.8)(next@14.2.28(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) papaparse: specifier: ^5.4.1 version: 5.4.1 @@ -593,6 +605,9 @@ importers: rehype-katex: specifier: ^7.0.0 version: 7.0.1 + rehype-raw: + specifier: ^7.0.0 + version: 7.0.0 remark-breaks: specifier: ^4.0.0 version: 4.0.0 @@ -3153,6 +3168,9 @@ packages: '@sinonjs/fake-timers@10.3.0': resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@sqltools/formatter@1.2.5': + resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==} + '@svgr/babel-plugin-add-jsx-attribute@6.5.1': resolution: {integrity: sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ==} engines: {node: '>=10'} @@ -3984,10 +4002,18 @@ packages: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} + ansis@3.17.0: + resolution: {integrity: sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==} + engines: {node: '>=14'} + anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + app-root-path@3.1.0: + resolution: {integrity: sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==} + engines: {node: '>= 6.0.0'} + append-field@1.0.0: resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} @@ -4378,6 +4404,9 @@ packages: resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==} engines: {node: '>= 6'} + chinese-conv@3.2.2: + resolution: {integrity: sha512-tv/he3PX7Uo+ySYVdKy3t/vhx7zdZoshS2HGLWOqotfVaR5hK5f6rsJeU2vAXeCRgzRxpW52LcN3CtinVDPEeQ==} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -4481,6 +4510,9 @@ packages: collapse-white-space@1.0.6: resolution: {integrity: sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==} + collapse-white-space@2.1.0: + resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} + collect-v8-coverage@1.0.2: resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} @@ -4949,6 +4981,14 @@ packages: babel-plugin-macros: optional: true + dedent@1.7.0: + resolution: {integrity: sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + deep-eql@4.1.4: resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} engines: {node: '>=6'} @@ -5690,6 +5730,9 @@ packages: framesync@6.1.2: resolution: {integrity: sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==} + franc@6.2.0: + resolution: {integrity: sha512-rcAewP7PSHvjq7Kgd7dhj82zE071kX5B4W1M4ewYMf/P+i6YsDQmj62Xz3VQm9zyUzUXwhIde/wHLGCMrM+yGg==} + fresh@0.5.2: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} @@ -6011,6 +6054,10 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} + iconv-lite@0.7.0: + resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} + engines: {node: '>=0.10.0'} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -7385,10 +7432,13 @@ packages: resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - mysql2@3.13.0: - resolution: {integrity: sha512-M6DIQjTqKeqXH5HLbLMxwcK5XfXHw30u5ap6EZmu7QVmcF/gnh2wS/EOiQ4MTbXz/vQeoXrmycPlVRM00WSslg==} + mysql2@3.14.5: + resolution: {integrity: sha512-40hDf8LPUsuuJ2hFq+UgOuPwt2IFLIRDvMv6ez9hKbXeYuZPxDDwiJW7KdknvOsQqKznaKczOT1kELgFkhDvFg==} engines: {node: '>= 8.0'} + n-gram@2.0.2: + resolution: {integrity: sha512-S24aGsn+HLBxUGVAUFOwGpKs7LBcG4RudKU//eWzt/mQ97/NMKQxDWHyHx63UNWk/OOdihgmzoETn1tf5nQDzQ==} + named-placeholders@1.1.3: resolution: {integrity: sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==} engines: {node: '>=12.0.0'} @@ -8337,6 +8387,9 @@ packages: rehype-katex@7.0.1: resolution: {integrity: sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==} + rehype-raw@7.0.0: + resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==, tarball: http://npm.uedc.sangfor.com.cn:80/rehype-raw/-/rehype-raw-7.0.0.tgz} + remark-breaks@4.0.0: resolution: {integrity: sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==} @@ -8604,6 +8657,11 @@ packages: setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + sha.js@2.4.12: + resolution: {integrity: sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==} + engines: {node: '>= 0.10'} + hasBin: true + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -8720,6 +8778,10 @@ packages: sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + sql-highlight@6.1.0: + resolution: {integrity: sha512-ed7OK4e9ywpE7pgRMkMQmZDPKSVdm0oX5IEtZiKnFucSF0zu6c80GZBe38UqHuVhTWJ9xsKgSMjCG2bml86KvA==} + engines: {node: '>=14'} + sqlstring@2.3.3: resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} engines: {node: '>= 0.6'} @@ -9047,6 +9109,10 @@ packages: to-buffer@1.1.1: resolution: {integrity: sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==} + to-buffer@1.2.1: + resolution: {integrity: sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==} + engines: {node: '>= 0.4'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -9077,6 +9143,9 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true + trigram-utils@2.0.1: + resolution: {integrity: sha512-nfWIXHEaB+HdyslAfMxSqWKDdmqY9I32jS7GnqpdWQnLH89r6A5sdk3fDVYqGAZ0CrT8ovAFSAo6HRiWcWNIGQ==} + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -9240,6 +9309,62 @@ packages: typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + typeorm@0.3.26: + resolution: {integrity: sha512-o2RrBNn3lczx1qv4j+JliVMmtkPSqEGpG0UuZkt9tCfWkoXKu8MZnjvp2GjWPll1SehwemQw6xrbVRhmOglj8Q==} + engines: {node: '>=16.13.0'} + hasBin: true + peerDependencies: + '@google-cloud/spanner': ^5.18.0 || ^6.0.0 || ^7.0.0 + '@sap/hana-client': ^2.14.22 + better-sqlite3: ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 + ioredis: ^5.0.4 + mongodb: ^5.8.0 || ^6.0.0 + mssql: ^9.1.1 || ^10.0.1 || ^11.0.1 + mysql2: ^2.2.5 || ^3.0.1 + oracledb: ^6.3.0 + pg: ^8.5.1 + pg-native: ^3.0.0 + pg-query-stream: ^4.0.0 + redis: ^3.1.1 || ^4.0.0 || ^5.0.14 + reflect-metadata: ^0.1.14 || ^0.2.0 + sql.js: ^1.4.0 + sqlite3: ^5.0.3 + ts-node: ^10.7.0 + typeorm-aurora-data-api-driver: ^2.0.0 || ^3.0.0 + peerDependenciesMeta: + '@google-cloud/spanner': + optional: true + '@sap/hana-client': + optional: true + better-sqlite3: + optional: true + ioredis: + optional: true + mongodb: + optional: true + mssql: + optional: true + mysql2: + optional: true + oracledb: + optional: true + pg: + optional: true + pg-native: + optional: true + pg-query-stream: + optional: true + redis: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + ts-node: + optional: true + typeorm-aurora-data-api-driver: + optional: true + typescript@5.7.2: resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} engines: {node: '>=14.17'} @@ -9451,6 +9576,10 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + hasBin: true + uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true @@ -9970,7 +10099,7 @@ snapshots: '@babel/traverse': 7.26.10 '@babel/types': 7.26.10 convert-source-map: 2.0.0 - debug: 4.4.0 + debug: 4.4.1 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -10797,6 +10926,14 @@ snapshots: next: 14.2.28(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1) react: 18.3.1 + '@chakra-ui/next-js@2.4.2(@chakra-ui/react@2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.28(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react@18.3.1)': + dependencies: + '@chakra-ui/react': 2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@emotion/cache': 11.14.0 + '@emotion/react': 11.11.1(@types/react@18.3.1)(react@18.3.1) + next: 14.2.28(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1) + react: 18.3.1 + '@chakra-ui/object-utils@2.1.0': {} '@chakra-ui/react-use-safe-layout-effect@2.1.0(react@18.3.1)': @@ -11197,7 +11334,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.4.0 + debug: 4.4.1 espree: 9.6.1 globals: 13.24.0 ignore: 5.3.2 @@ -11293,7 +11430,7 @@ snapshots: '@humanwhocodes/config-array@0.11.14': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.0 + debug: 4.4.1 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -11301,7 +11438,7 @@ snapshots: '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.0 + debug: 4.4.1 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -12466,6 +12603,8 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 + '@sqltools/formatter@1.2.5': {} + '@svgr/babel-plugin-add-jsx-attribute@6.5.1(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 @@ -13533,11 +13672,15 @@ snapshots: ansi-styles@6.2.1: {} + ansis@3.17.0: {} + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 picomatch: 2.3.1 + app-root-path@3.1.0: {} + append-field@1.0.0: {} arg@4.1.3: {} @@ -13808,7 +13951,7 @@ snapshots: dependencies: bytes: 3.1.2 content-type: 1.0.5 - debug: 4.4.0 + debug: 4.4.1 http-errors: 2.0.0 iconv-lite: 0.6.3 on-finished: 2.4.1 @@ -14045,6 +14188,8 @@ snapshots: parse5: 7.2.1 parse5-htmlparser2-tree-adapter: 7.1.0 + chinese-conv@3.2.2: {} + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -14145,6 +14290,8 @@ snapshots: collapse-white-space@1.0.6: {} + collapse-white-space@2.1.0: {} + collect-v8-coverage@1.0.2: {} color-convert@1.9.3: @@ -14643,6 +14790,10 @@ snapshots: optionalDependencies: babel-plugin-macros: 3.1.0 + dedent@1.7.0(babel-plugin-macros@3.1.0): + optionalDependencies: + babel-plugin-macros: 3.1.0 + deep-eql@4.1.4: dependencies: type-detect: 4.1.0 @@ -15102,7 +15253,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.0)(eslint@8.56.0): + eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.0(eslint-plugin-import@2.31.0)(eslint@8.56.0))(eslint@8.56.0): dependencies: debug: 3.2.7 optionalDependencies: @@ -15113,7 +15264,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.0)(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.0(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: @@ -15135,7 +15286,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.56.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.0)(eslint@8.56.0) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.0(eslint-plugin-import@2.31.0)(eslint@8.56.0))(eslint@8.56.0) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -15164,7 +15315,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.0)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.0(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -15516,7 +15667,7 @@ snapshots: content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 - debug: 4.4.0 + debug: 4.4.1 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -15697,7 +15848,7 @@ snapshots: finalhandler@2.1.0: dependencies: - debug: 4.4.0 + debug: 4.4.1 encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 @@ -15821,6 +15972,10 @@ snapshots: dependencies: tslib: 2.4.0 + franc@6.2.0: + dependencies: + trigram-utils: 2.0.1 + fresh@0.5.2: {} fresh@2.0.0: {} @@ -16171,7 +16326,7 @@ snapshots: https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.3 - debug: 4.4.0 + debug: 4.4.1 transitivePeerDependencies: - supports-color @@ -16203,6 +16358,10 @@ snapshots: dependencies: safer-buffer: 2.1.2 + iconv-lite@0.7.0: + dependencies: + safer-buffer: 2.1.2 + ieee754@1.2.1: {} ignore@5.3.2: {} @@ -16571,7 +16730,7 @@ snapshots: istanbul-lib-source-maps@5.0.6: dependencies: '@jridgewell/trace-mapping': 0.3.25 - debug: 4.4.0 + debug: 4.4.1 istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color @@ -18084,7 +18243,7 @@ snapshots: mquery@5.0.0: dependencies: - debug: 4.4.0 + debug: 4.4.1 transitivePeerDependencies: - supports-color @@ -18126,18 +18285,20 @@ snapshots: mute-stream@1.0.0: {} - mysql2@3.13.0: + mysql2@3.14.5: dependencies: aws-ssl-profiles: 1.1.2 denque: 2.1.0 generate-function: 2.3.1 - iconv-lite: 0.6.3 + iconv-lite: 0.7.0 long: 5.3.1 lru.min: 1.1.2 named-placeholders: 1.1.3 seq-queue: 0.0.5 sqlstring: 2.3.3 + n-gram@2.0.2: {} + named-placeholders@1.1.3: dependencies: lru-cache: 7.18.3 @@ -18160,7 +18321,7 @@ snapshots: new-find-package-json@2.0.0: dependencies: - debug: 4.4.0 + debug: 4.4.1 transitivePeerDependencies: - supports-color @@ -18176,6 +18337,18 @@ snapshots: react: 18.3.1 react-i18next: 14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next-i18next@15.4.2(i18next@23.16.8)(next@14.2.28(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.10 + '@types/hoist-non-react-statics': 3.3.6 + core-js: 3.41.0 + hoist-non-react-statics: 3.3.2 + i18next: 23.16.8 + i18next-fs-backend: 2.6.0 + next: 14.2.28(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1) + react: 18.3.1 + react-i18next: 14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next@14.2.28(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1): dependencies: '@next/env': 14.2.28 @@ -18203,10 +18376,64 @@ snapshots: - '@babel/core' - babel-plugin-macros - nextjs-cors@2.2.0(next@14.2.28(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1)): + next@14.2.28(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1): + dependencies: + '@next/env': 14.2.28 + '@swc/helpers': 0.5.5 + busboy: 1.6.0 + caniuse-lite: 1.0.30001704 + graceful-fs: 4.2.11 + postcss: 8.4.31 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + styled-jsx: 5.1.1(babel-plugin-macros@3.1.0)(react@18.3.1) + optionalDependencies: + '@next/swc-darwin-arm64': 14.2.28 + '@next/swc-darwin-x64': 14.2.28 + '@next/swc-linux-arm64-gnu': 14.2.28 + '@next/swc-linux-arm64-musl': 14.2.28 + '@next/swc-linux-x64-gnu': 14.2.28 + '@next/swc-linux-x64-musl': 14.2.28 + '@next/swc-win32-arm64-msvc': 14.2.28 + '@next/swc-win32-ia32-msvc': 14.2.28 + '@next/swc-win32-x64-msvc': 14.2.28 + '@opentelemetry/api': 1.9.0 + sass: 1.85.1 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + next@14.2.28(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1): + dependencies: + '@next/env': 14.2.28 + '@swc/helpers': 0.5.5 + busboy: 1.6.0 + caniuse-lite: 1.0.30001704 + graceful-fs: 4.2.11 + postcss: 8.4.31 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + styled-jsx: 5.1.1(@babel/core@7.26.10)(react@18.3.1) + optionalDependencies: + '@next/swc-darwin-arm64': 14.2.28 + '@next/swc-darwin-x64': 14.2.28 + '@next/swc-linux-arm64-gnu': 14.2.28 + '@next/swc-linux-arm64-musl': 14.2.28 + '@next/swc-linux-x64-gnu': 14.2.28 + '@next/swc-linux-x64-musl': 14.2.28 + '@next/swc-win32-arm64-msvc': 14.2.28 + '@next/swc-win32-ia32-msvc': 14.2.28 + '@next/swc-win32-x64-msvc': 14.2.28 + '@opentelemetry/api': 1.9.0 + sass: 1.85.1 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + nextjs-cors@2.2.0(next@14.2.28(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1)): dependencies: cors: 2.8.5 - next: 14.2.28(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1) + next: 14.2.28(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1) node-abi@3.74.0: dependencies: @@ -19202,6 +19429,12 @@ snapshots: unist-util-visit-parents: 6.0.1 vfile: 6.0.3 + rehype-raw@7.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-raw: 9.1.0 + vfile: 6.0.3 + remark-breaks@4.0.0: dependencies: '@types/mdast': 4.0.4 @@ -19375,7 +19608,7 @@ snapshots: router@2.2.0: dependencies: - debug: 4.4.0 + debug: 4.4.1 depd: 2.0.0 is-promise: 4.0.0 parseurl: 1.3.3 @@ -19499,7 +19732,7 @@ snapshots: send@1.2.0: dependencies: - debug: 4.4.0 + debug: 4.4.1 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -19567,6 +19800,12 @@ snapshots: setprototypeof@1.2.0: {} + sha.js@2.4.12: + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.1 + to-buffer: 1.2.1 + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -19683,6 +19922,8 @@ snapshots: sprintf-js@1.1.3: {} + sql-highlight@6.1.0: {} + sqlstring@2.3.3: {} ssri@10.0.6: @@ -19860,6 +20101,13 @@ snapshots: optionalDependencies: '@babel/core': 7.26.10 + styled-jsx@5.1.1(babel-plugin-macros@3.1.0)(react@18.3.1): + dependencies: + client-only: 0.0.1 + react: 18.3.1 + optionalDependencies: + babel-plugin-macros: 3.1.0 + stylis@4.2.0: {} stylis@4.3.6: {} @@ -20033,6 +20281,12 @@ snapshots: to-buffer@1.1.1: {} + to-buffer@1.2.1: + dependencies: + isarray: 2.0.5 + safe-buffer: 5.2.1 + typed-array-buffer: 1.0.3 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -20056,6 +20310,11 @@ snapshots: tree-kill@1.2.2: {} + trigram-utils@2.0.1: + dependencies: + collapse-white-space: 2.1.0 + n-gram: 2.0.2 + trim-lines@3.0.1: {} trim-trailing-lines@1.1.4: {} @@ -20223,6 +20482,33 @@ snapshots: typedarray@0.0.6: {} + typeorm@0.3.26(babel-plugin-macros@3.1.0)(ioredis@5.6.0)(mongodb@6.14.2(socks@2.8.4))(mysql2@3.14.5)(pg@8.14.0)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@20.17.24)(typescript@5.8.2)): + dependencies: + '@sqltools/formatter': 1.2.5 + ansis: 3.17.0 + app-root-path: 3.1.0 + buffer: 6.0.3 + dayjs: 1.11.13 + debug: 4.4.1 + dedent: 1.7.0(babel-plugin-macros@3.1.0) + dotenv: 16.5.0 + glob: 10.4.5 + reflect-metadata: 0.2.2 + sha.js: 2.4.12 + sql-highlight: 6.1.0 + tslib: 2.8.1 + uuid: 11.1.0 + yargs: 17.7.2 + optionalDependencies: + ioredis: 5.6.0 + mongodb: 6.14.2(socks@2.8.4) + mysql2: 3.14.5 + pg: 8.14.0 + ts-node: 10.9.2(@types/node@20.17.24)(typescript@5.8.2) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + typescript@5.7.2: {} typescript@5.8.2: {} @@ -20439,6 +20725,8 @@ snapshots: utils-merge@1.0.1: {} + uuid@11.1.0: {} + uuid@8.3.2: {} uuid@9.0.1: {} @@ -20509,7 +20797,7 @@ snapshots: vite-node@1.6.1(@types/node@20.17.24)(sass@1.85.1)(terser@5.39.0): dependencies: cac: 6.7.14 - debug: 4.4.0 + debug: 4.4.1 pathe: 1.1.2 picocolors: 1.1.1 vite: 5.4.14(@types/node@20.17.24)(sass@1.85.1)(terser@5.39.0) @@ -20527,7 +20815,7 @@ snapshots: vite-node@3.1.1(@types/node@20.17.24)(sass@1.85.1)(terser@5.39.0): dependencies: cac: 6.7.14 - debug: 4.4.0 + debug: 4.4.1 es-module-lexer: 1.6.0 pathe: 2.0.3 vite: 6.2.2(@types/node@20.17.24)(sass@1.85.1)(terser@5.39.0) @@ -20576,7 +20864,7 @@ snapshots: '@vitest/utils': 1.6.1 acorn-walk: 8.3.4 chai: 4.5.0 - debug: 4.4.0 + debug: 4.4.1 execa: 8.0.1 local-pkg: 0.5.1 magic-string: 0.30.17 diff --git a/projects/app/.env.template b/projects/app/.env.template index 79aef450f9eb..df7bf4b194e6 100644 --- a/projects/app/.env.template +++ b/projects/app/.env.template @@ -27,6 +27,8 @@ PLUGIN_BASE_URL= PLUGIN_TOKEN= # code sandbox url SANDBOX_URL=http://localhost:3001 +# diting url +DITING_BASE_URL=http://xxx:3000 # ai proxy api AIPROXY_API_ENDPOINT=https://xxx.come AIPROXY_API_TOKEN=xxxxx @@ -98,5 +100,4 @@ CONFIG_JSON_PATH= # Signoz SIGNOZ_BASE_URL= SIGNOZ_SERVICE_NAME= -SIGNOZ_STORE_LEVEL=warn - +SIGNOZ_STORE_LEVEL=warn \ No newline at end of file diff --git a/projects/app/data/config.json b/projects/app/data/config.json index 1d0303370ce9..90e81d6fb8ad 100644 --- a/projects/app/data/config.json +++ b/projects/app/data/config.json @@ -16,6 +16,17 @@ "key": "", // 自定义 PDF 解析服务密钥 "doc2xKey": "", // doc2x 服务密钥 "price": 0 // PDF 解析服务价格 + }, + "evalConfig": { + "taskConcurrency": 3, + "caseConcurrency": 10, + "caseMaxRetry": 3, + "caseResultThreshold": 0.8, + "summaryConcurrency": 1, + "dataQualityConcurrency": 2, + "datasetDataSynthesizeConcurrency": 5, + "datasetSmartGenerateConcurrency": 2, + "maxStalledCount": 3 } } } diff --git a/projects/app/data/metric.json b/projects/app/data/metric.json new file mode 100644 index 000000000000..ccc8ce811bd2 --- /dev/null +++ b/projects/app/data/metric.json @@ -0,0 +1,51 @@ +{ + "builtinMetrics": [ + { + "name": "answer_correctness", + "description": "Evaluates the factual consistency between the generated answer and the expected answer, evaluating whether it is accurate and error-free.", + "userInputRequired": true, + "actualOutputRequired": true, + "expectedOutputRequired": true, + "embeddingRequired": true, + "llmRequired": true + }, + { + "name": "answer_similarity", + "description": "Evaluates the semantic alignment between the generated answer and the expected answer, determining whether they convey the same core information.", + "actualOutputRequired": true, + "expectedOutputRequired": true, + "embeddingRequired": true + }, + { + "name": "answer_relevancy", + "description": "Evaluates how well the generated answer aligns with the question, judging whether the response directly addresses the query.", + "userInputRequired": true, + "actualOutputRequired": true, + "llmRequired": true + }, + { + "name": "context_precision", + "description": "Evaluates whether high-value information is prioritized in the retrieved content, reflecting the quality of ranking and information density.", + "userInputRequired": true, + "expectedOutputRequired": true, + "retrievalContextRequired": true, + "llmRequired": true + }, + { + "name": "context_recall", + "description": "Evaluates whether the retrieval system successfully retrieves all key information necessary for formulating the answer, assessing the completeness of retrieval.", + "userInputRequired": true, + "expectedOutputRequired": true, + "retrievalContextRequired": true, + "llmRequired": true + }, + { + "name": "faithfulness", + "description": "Evaluates whether the generated answer remains faithful to the provided context, determining whether it contains fabricated or inaccurate content.", + "userInputRequired": true, + "actualOutputRequired": true, + "retrievalContextRequired": true, + "llmRequired": true + } + ] +} diff --git a/projects/app/next.config.js b/projects/app/next.config.js index 39ea4e1c7978..14603c655a16 100644 --- a/projects/app/next.config.js +++ b/projects/app/next.config.js @@ -103,8 +103,8 @@ const nextConfig = { return config; }, // 需要转译的包 - transpilePackages: ['@modelcontextprotocol/sdk', 'ahooks'], - experimental: { + transpilePackages: ['@modelcontextprotocol/sdk', 'ahooks', 'chinese-conv'], + experimental: { // 优化 Server Components 的构建和运行,避免不必要的客户端打包。 serverComponentsExternalPackages: [ 'mongoose', diff --git a/projects/app/package.json b/projects/app/package.json index a42828458458..1dc165293718 100644 --- a/projects/app/package.json +++ b/projects/app/package.json @@ -59,6 +59,7 @@ "recharts": "^2.15.0", "rehype-external-links": "^3.0.0", "rehype-katex": "^7.0.0", + "rehype-raw": "^7.0.0", "remark-breaks": "^4.0.0", "remark-gfm": "^4.0.0", "remark-math": "^6.0.0", diff --git a/projects/app/public/imgs/avatar/summaryAvatar.svg b/projects/app/public/imgs/avatar/summaryAvatar.svg new file mode 100644 index 000000000000..fb361f4c7245 --- /dev/null +++ b/projects/app/public/imgs/avatar/summaryAvatar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/projects/app/src/components/Layout/navbar.tsx b/projects/app/src/components/Layout/navbar.tsx index 05aa996346e9..ebaa32c0ef9e 100644 --- a/projects/app/src/components/Layout/navbar.tsx +++ b/projects/app/src/components/Layout/navbar.tsx @@ -63,7 +63,11 @@ const Navbar = ({ unread }: { unread: number }) => { '/dashboard/[pluginGroupId]', '/dashboard/mcpServer', '/dashboard/evaluation', - '/dashboard/evaluation/create' + '/dashboard/evaluation/task/detail', + '/dashboard/evaluation/dataset/fileImport', + '/dashboard/evaluation/dataset/detail', + '/dashboard/evaluation/dimension/create', + '/dashboard/evaluation/dimension/edit' ] }, { diff --git a/projects/app/src/components/Layout/navbarPhone.tsx b/projects/app/src/components/Layout/navbarPhone.tsx index 4bfa65fda0cf..1d2f97da34a0 100644 --- a/projects/app/src/components/Layout/navbarPhone.tsx +++ b/projects/app/src/components/Layout/navbarPhone.tsx @@ -33,7 +33,11 @@ const NavbarPhone = ({ unread }: { unread: number }) => { '/dashboard/[pluginGroupId]', '/dashboard/mcpServer', '/dashboard/evaluation', - '/dashboard/evaluation/create' + '/dashboard/evaluation/task/detail', + '/dashboard/evaluation/dataset/fileImport', + '/dashboard/evaluation/dataset/detail', + '/dashboard/evaluation/dimension/create', + '/dashboard/evaluation/dimension/edit' ], unread: 0 }, diff --git a/projects/app/src/components/Markdown/index.tsx b/projects/app/src/components/Markdown/index.tsx index 5445a8168c42..cf438f8f5e94 100644 --- a/projects/app/src/components/Markdown/index.tsx +++ b/projects/app/src/components/Markdown/index.tsx @@ -6,6 +6,7 @@ import RemarkBreaks from 'remark-breaks'; // Line break import RehypeKatex from 'rehype-katex'; // Math render import RemarkGfm from 'remark-gfm'; // Special markdown syntax import RehypeExternalLinks from 'rehype-external-links'; +import RehypeRaw from 'rehype-raw'; // Support raw HTML import styles from './index.module.scss'; import dynamic from 'next/dynamic'; @@ -84,7 +85,7 @@ const MarkdownRender = ({ ${showAnimation ? `${formatSource ? styles.waitingAnimation : styles.animation}` : ''} `} remarkPlugins={[RemarkMath, [RemarkGfm, { singleTilde: false }], RemarkBreaks]} - rehypePlugins={[RehypeKatex, [RehypeExternalLinks, { target: '_blank' }]]} + rehypePlugins={[RehypeKatex, [RehypeExternalLinks, { target: '_blank' }], RehypeRaw]} components={components} urlTransform={urlTransform} > diff --git a/projects/app/src/components/Select/AppSelect.tsx b/projects/app/src/components/Select/AppSelect.tsx index 1f075e7d369e..985a1d55ad2b 100644 --- a/projects/app/src/components/Select/AppSelect.tsx +++ b/projects/app/src/components/Select/AppSelect.tsx @@ -12,7 +12,19 @@ import { getMyApps } from '@/web/core/app/api'; import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; import Avatar from '@fastgpt/web/components/common/Avatar'; -const AppSelect = ({ value, onSelect }: { value: string; onSelect: (id: string) => void }) => { +const AppSelect = ({ + value, + onSelect, + placeholder, + h, + bg +}: { + value: string; + onSelect: (id: string) => void; + placeholder?: string; + h?: string | number; + bg?: string; +}) => { const [currentApp, setCurrentApp] = useState(null); const { t } = useTranslation(); @@ -43,15 +55,15 @@ const AppSelect = ({ value, onSelect }: { value: string; onSelect: (id: string) Trigger={ } diff --git a/projects/app/src/components/common/folder/SelectOneResource.tsx b/projects/app/src/components/common/folder/SelectOneResource.tsx index 5d8043c328aa..76e6efa54b22 100644 --- a/projects/app/src/components/common/folder/SelectOneResource.tsx +++ b/projects/app/src/components/common/folder/SelectOneResource.tsx @@ -16,6 +16,7 @@ import { useTranslation } from 'next-i18next'; type ResourceItemType = GetResourceListItemResponse & { open: boolean; children?: ResourceItemType[]; + showAvatar?: boolean; }; const rootId = 'root'; @@ -24,28 +25,33 @@ const SelectOneResource = ({ server, value, onSelect, - maxH = ['80vh', '600px'] + maxH = ['80vh', '600px'], + showRoot = true }: { server: (e: GetResourceFolderListProps) => Promise; value?: ParentIdType; onSelect: (e?: ResourceItemType) => any; maxH?: BoxProps['maxH']; + showRoot?: boolean; }) => { const { t } = useTranslation(); const [dataList, setDataList] = useState([]); const [requestingIdList, setRequestingIdList] = useState([]); const concatRoot = useMemo(() => { - const root: ResourceItemType = { - id: rootId, - open: true, - avatar: FolderImgUrl, - name: t('common:root_folder'), - isFolder: true, - children: dataList - }; - return [root]; - }, [dataList, t]); + if (showRoot) { + const root: ResourceItemType = { + id: rootId, + open: true, + avatar: FolderImgUrl, + name: t('common:root_folder'), + isFolder: true, + children: dataList + }; + return [root]; + } + return dataList; + }, [dataList, t, showRoot]); const { runAsync: requestServer } = useRequest2((e: GetResourceFolderListProps) => { if (requestingIdList.includes(e.parentId)) return Promise.reject(null); @@ -78,7 +84,7 @@ const SelectOneResource = ({ alignItems={'center'} cursor={'pointer'} py={1} - pl={index === 0 ? '0.5rem' : `${1.75 * (index - 1) + 0.5}rem`} + pl={index === 0 && showRoot ? '0.5rem' : `${1.75 * (index - 1) + 0.5}rem`} pr={2} borderRadius={'md'} _hover={{ @@ -110,7 +116,7 @@ const SelectOneResource = ({ } })} > - {index !== 0 && ( + {(index !== 0 || !showRoot) && ( )} - + {item.showAvatar !== false && ( + + )} {item.name} diff --git a/projects/app/src/components/core/ai/ModelTable/index.tsx b/projects/app/src/components/core/ai/ModelTable/index.tsx index b687e5a521d4..202bee27c5fc 100644 --- a/projects/app/src/components/core/ai/ModelTable/index.tsx +++ b/projects/app/src/components/core/ai/ModelTable/index.tsx @@ -65,18 +65,18 @@ const ModelTable = () => { typeof item.inputPrice === 'number' ? ( - {`${t('common:Input')}:`} + {`${t('common:Input')}: `} {item.inputPrice || 0} - {`${t('common:support.wallet.subscription.point')} / 1K Tokens`} + {`${t('common:support.wallet.subscription.point')}/1K tokens`} - {`${t('common:Output')}:`} + {`${t('common:Output')}: `} {item.outputPrice || 0} - {`${t('common:support.wallet.subscription.point')} / 1K Tokens`} + {`${t('common:support.wallet.subscription.point')}/1K tokens`} ) : ( @@ -84,7 +84,7 @@ const ModelTable = () => { {item.charsPointsPrice || 0} - {`${t('common:support.wallet.subscription.point')} / 1K Tokens`} + {`${t('common:support.wallet.subscription.point')}/1K tokens`} ), tagColor: 'blue' @@ -95,10 +95,10 @@ const ModelTable = () => { priceLabel: ( {`${t('common:Input')}: `} - + {item.charsPointsPrice || 0} - {` ${t('common:support.wallet.subscription.point')} / 1K Tokens`} + {` ${t('common:support.wallet.subscription.point')}/1K tokens`} ), tagColor: 'yellow' @@ -111,7 +111,7 @@ const ModelTable = () => { {item.charsPointsPrice || 0} - {` ${t('common:support.wallet.subscription.point')} / 1K ${t('common:unit.character')}`} + {` ${t('common:support.wallet.subscription.point')}/1K ${t('common:unit.character')}`} ), tagColor: 'green' @@ -124,7 +124,7 @@ const ModelTable = () => { {item.charsPointsPrice} - {` ${t('common:support.wallet.subscription.point')} / 60${t('common:unit.seconds')}`} + {` ${t('common:support.wallet.subscription.point')}/60${t('common:unit.seconds')}`} ), tagColor: 'purple' @@ -135,10 +135,10 @@ const ModelTable = () => { priceLabel: item.charsPointsPrice ? ( {`${t('common:Input')}: `} - + {item.charsPointsPrice} - {` ${t('common:support.wallet.subscription.point')} / 1K Tokens`} + {` ${t('common:support.wallet.subscription.point')}/1K tokens`} ) : ( '-' diff --git a/projects/app/src/components/core/app/DatasetParamsModal.tsx b/projects/app/src/components/core/app/DatasetParamsModal.tsx index fca020b4541b..97c7d2ef0aa4 100644 --- a/projects/app/src/components/core/app/DatasetParamsModal.tsx +++ b/projects/app/src/components/core/app/DatasetParamsModal.tsx @@ -10,7 +10,8 @@ import { Slider, SliderTrack, SliderFilledTrack, - SliderThumb + SliderThumb, + Divider } from '@chakra-ui/react'; import { useForm } from 'react-hook-form'; import MyModal from '@fastgpt/web/components/common/MyModal'; @@ -49,12 +50,17 @@ const DatasetParamsModal = ({ datasetSearchExtensionModel, datasetSearchExtensionBg, maxTokens, + hasDatabaseKnowledge = false, + hasOtherKnowledge = true, + generateSqlModel = '', onClose, onSuccess }: AppDatasetSearchParamsType & { maxTokens?: number; // limit max tokens onClose: () => void; onSuccess: (e: AppDatasetSearchParamsType) => void; + hasDatabaseKnowledge?: boolean; + hasOtherKnowledge?: boolean; }) => { const { t } = useTranslation(); const { teamPlanStatus } = useUserStore(); @@ -85,7 +91,8 @@ const DatasetParamsModal = ({ similarity, datasetSearchUsingExtensionQuery, datasetSearchExtensionModel: datasetSearchExtensionModel || defaultModels.llm?.model, - datasetSearchExtensionBg + datasetSearchExtensionBg, + generateSqlModel: hasDatabaseKnowledge ? generateSqlModel || defaultModels.llm?.model : '' } }); @@ -102,6 +109,7 @@ const DatasetParamsModal = ({ const usingReRankWatch = watch('usingReRank'); const reRankModelWatch = watch('rerankModel'); const rerankWeightWatch = watch('rerankWeight'); + const generateSqlModelWatch = watch('generateSqlModel'); const showSimilarity = useMemo(() => { if (similarity === undefined) return false; @@ -134,6 +142,66 @@ const DatasetParamsModal = ({ return Math.ceil(maxTokens / 80 / 100) * 100; }, [maxTokens]); + const showModelTitle = useMemo( + () => hasDatabaseKnowledge && hasOtherKnowledge, + [hasDatabaseKnowledge, hasOtherKnowledge] + ); + + const renderHeader = useMemo(() => { + return ( + <> + {showModelTitle && ( + + + + {t('common:core.app.workflow.search_knowledge.database')} + + + )} + {hasDatabaseKnowledge && ( + + + {t('common:search_model')} + + {t('common:search_model_desc')} +
+ {t('common:search_model_tip')} + + } + /> +
+ + { + setValue('generateSqlModel', e); + }} + /> + +
+ )} + {showModelTitle && ( + <> + + + + + {t('common:other_knowledge_base')} + + + + )} + + ); + }, [chatModelSelectList, showModelTitle, t]); + return ( - - width={'100%'} - mb={3} - list={[ - { - icon: 'common/setting', - label: t('common:core.dataset.search.search mode'), - value: SearchSettingTabEnum.searchMode - }, - { - icon: 'core/dataset/searchfilter', - label: t('common:core.dataset.search.Filter'), - value: SearchSettingTabEnum.limit - }, - { - label: t('common:core.module.template.Query extension'), - value: SearchSettingTabEnum.queryExtension, - icon: 'core/dataset/questionExtension' - } - ]} - inlineStyles={{ - borderBottomColor: 'myGray.200', - borderBottom: '1px solid' - }} - value={currentTabType} - onChange={setCurrentTabType} - /> - {currentTabType === SearchSettingTabEnum.searchMode && ( - - - py={2.5} - gridGap={4} + {renderHeader} + {hasOtherKnowledge && ( + <> + + width={'100%'} + mb={3} list={[ { - title: t('common:core.dataset.search.mode.embedding'), - desc: t('common:core.dataset.search.mode.embedding desc'), - value: DatasetSearchModeEnum.embedding + icon: 'common/setting', + label: t('common:core.dataset.search.search mode'), + value: SearchSettingTabEnum.searchMode }, { - title: t('common:core.dataset.search.mode.fullTextRecall'), - desc: t('common:core.dataset.search.mode.fullTextRecall desc'), - value: DatasetSearchModeEnum.fullTextRecall + icon: 'core/dataset/searchfilter', + label: t('common:core.dataset.search.Filter'), + value: SearchSettingTabEnum.limit }, { - title: t('common:core.dataset.search.mode.mixedRecall'), - desc: t('common:core.dataset.search.mode.mixedRecall desc'), - value: DatasetSearchModeEnum.mixedRecall, - children: searchModeWatch === DatasetSearchModeEnum.mixedRecall && ( - - - - - {t('common:core.dataset.search.mode.embedding')} - - - {embeddingWeightWatch} - - - - - {t('common:core.dataset.search.score.fullText')} - - - {fullTextWeightWatch} - - - - { - setValue('embeddingWeight', Number(e.toFixed(2))); - }} - > - - - - - - - - - ) + label: t('common:core.module.template.Query extension'), + value: SearchSettingTabEnum.queryExtension, + icon: 'core/dataset/questionExtension' } ]} - value={searchModeWatch} - onChange={(e) => { - setValue('searchMode', e); + inlineStyles={{ + borderBottomColor: 'myGray.200', + borderBottom: '1px solid' }} + value={currentTabType} + onChange={setCurrentTabType} /> - {/* Rerank */} - <> - - - {t('common:core.dataset.search.ReRank')} - - - {!showReRank ? ( - - {t('common:core.ai.Not deploy rerank model')} - - ) : ( - - )} - - {usingReRankWatch && ( + {currentTabType === SearchSettingTabEnum.searchMode && ( + + + py={2.5} + gridGap={4} + list={[ + { + title: t('common:core.dataset.search.mode.embedding'), + desc: t('common:core.dataset.search.mode.embedding desc'), + value: DatasetSearchModeEnum.embedding + }, + { + title: t('common:core.dataset.search.mode.fullTextRecall'), + desc: t('common:core.dataset.search.mode.fullTextRecall desc'), + value: DatasetSearchModeEnum.fullTextRecall + }, + { + title: t('common:core.dataset.search.mode.mixedRecall'), + desc: t('common:core.dataset.search.mode.mixedRecall desc'), + value: DatasetSearchModeEnum.mixedRecall, + children: searchModeWatch === DatasetSearchModeEnum.mixedRecall && ( + + + + + {t('common:core.dataset.search.mode.embedding')} + + + {embeddingWeightWatch} + + + + + {t('common:core.dataset.search.score.fullText')} + + + {fullTextWeightWatch} + + + + { + setValue('embeddingWeight', Number(e.toFixed(2))); + }} + > + + + + + + + + + ) + } + ]} + value={searchModeWatch} + onChange={(e) => { + setValue('searchMode', e); + }} + /> + {/* Rerank */} <> - - - {t('common:rerank_weight')} - + + + {t('common:core.dataset.search.ReRank')} + + + {!showReRank ? ( + + {t('common:core.ai.Not deploy rerank model')} + + ) : ( + + )} + + {usingReRankWatch && ( + <> + + + {t('common:rerank_weight')} + + + { + setValue( + NodeInputKeyEnum.datasetSearchRerankWeight, + Number(val.toFixed(2)) + ); + }} + /> + + + + + {t('common:model.type.reRank')} + + + { + setValue(NodeInputKeyEnum.datasetSearchRerankModel, val); + }} + /> + + + + )} + + + )} + {currentTabType === SearchSettingTabEnum.limit && ( + + {limit !== undefined && ( + + + {t('common:max_quote_tokens')} + + + {maxTokens ? ( + { + setValue(NodeInputKeyEnum.datasetMaxTokens, val); + setRefresh(!refresh); + }} + /> + ) : ( + + )} + + + )} + + + {t('common:min_similarity')} + + + + {showSimilarity ? ( { - setValue( - NodeInputKeyEnum.datasetSearchRerankWeight, - Number(val.toFixed(2)) - ); - }} - /> - - - - - {t('common:model.type.reRank')} - - - { - setValue(NodeInputKeyEnum.datasetSearchRerankModel, val); + setValue(NodeInputKeyEnum.datasetSimilarity, val); + setRefresh(!refresh); }} /> - - - - )} - - - )} - {currentTabType === SearchSettingTabEnum.limit && ( - - {limit !== undefined && ( - - - {t('common:max_quote_tokens')} - - - - {maxTokens ? ( - { - setValue(NodeInputKeyEnum.datasetMaxTokens, val); - setRefresh(!refresh); - }} - /> - ) : ( - - )} + ) : ( + + {t('common:core.dataset.search.No support similarity')} + + )} + )} - - - {t('common:min_similarity')} - - - - {showSimilarity ? ( - { - setValue(NodeInputKeyEnum.datasetSimilarity, val); - setRefresh(!refresh); - }} - /> - ) : ( - - {t('common:core.dataset.search.No support similarity')} - + {currentTabType === SearchSettingTabEnum.queryExtension && ( + + + {t('common:core.dataset.Query extension intro')} + + + + {t('common:core.dataset.search.Using query extension')} + + + + {datasetSearchUsingCfrForm === true && ( + <> + + + {t('common:core.ai.Model')} + + + { + setValue('datasetSearchExtensionModel', val); + }} + /> + + + + + + {t('common:core.app.edit.Query extension background prompt')} + + + + + + + + )} - - - )} - {currentTabType === SearchSettingTabEnum.queryExtension && ( - - - {t('common:core.dataset.Query extension intro')} - - - - {t('common:core.dataset.search.Using query extension')} - - - - {datasetSearchUsingCfrForm === true && ( - <> - - {t('common:core.ai.Model')} - - { - setValue('datasetSearchExtensionModel', val); - }} - /> - - - - - - {t('common:core.app.edit.Query extension background prompt')} - - - - - - - - )} - + )} diff --git a/projects/app/src/components/core/app/DatasetSelectModal.tsx b/projects/app/src/components/core/app/DatasetSelectModal.tsx index 377ef7b15e2b..6785e9b6c8a8 100644 --- a/projects/app/src/components/core/app/DatasetSelectModal.tsx +++ b/projects/app/src/components/core/app/DatasetSelectModal.tsx @@ -26,18 +26,23 @@ import SearchInput from '@fastgpt/web/components/common/Input/SearchInput'; import { useDatasetSelect } from '@/components/core/dataset/SelectModal'; import FolderPath from '@/components/common/folder/Path'; import EmptyTip from '@fastgpt/web/components/common/EmptyTip'; +import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; // Dataset selection modal component export const DatasetSelectModal = ({ isOpen, defaultSelectedDatasets = [], onChange, - onClose + onClose, + scene = '', + formatResData }: { isOpen: boolean; defaultSelectedDatasets: SelectedDatasetType; + scene?: string; onChange: (e: SelectedDatasetType) => void; onClose: () => void; + formatResData?: (datasetList: DatasetListItemType[]) => DatasetListItemType[]; }) => { // Translation function const { t } = useTranslation(); @@ -46,8 +51,13 @@ export const DatasetSelectModal = ({ useState(defaultSelectedDatasets); const { toast } = useToast(); + const isSmartGenerateScene = scene === 'smartGenerate'; + // Use server-side search, following the logic of the dataset list page - const { paths, setParentId, searchKey, setSearchKey, datasets, isFetching } = useDatasetSelect(); + const { paths, setParentId, searchKey, setSearchKey, datasets, isFetching } = useDatasetSelect( + scene, + formatResData + ); // The vector model of the first selected dataset const activeVectorModel = selectedDatasets[0]?.vectorModel?.model; @@ -60,9 +70,19 @@ export const DatasetSelectModal = ({ [selectedDatasets] ); + const isEmptyDatabase = (item: DatasetListItemType) => isSmartGenerateScene && !item.dataCount; + // Check if a dataset is disabled (vector model mismatch) const isDatasetDisabled = (item: DatasetListItemType) => { - return !!activeVectorModel && activeVectorModel !== item.vectorModel.model; + return isSmartGenerateScene + ? isEmptyDatabase(item) + : !!activeVectorModel && activeVectorModel !== item.vectorModel.model; + }; + + const getDisableTip = (item: DatasetListItemType) => { + return isSmartGenerateScene && isDatasetDisabled(item) + ? t('app:no_data_for_smart_generate') + : ''; }; // Cache compatible datasets by vector model to avoid repeated filtering @@ -96,10 +116,13 @@ export const DatasetSelectModal = ({ const onSelect = (item: DatasetListItemType, checked: boolean) => { if (checked) { if (isDatasetDisabled(item)) { - return toast({ - status: 'warning', - title: t('common:dataset.Select Dataset Tips') - }); + return ( + !isSmartGenerateScene && + toast({ + status: 'warning', + title: t('common:dataset.Select Dataset Tips') + }) + ); } setSelectedDatasets((prev) => [ ...prev, @@ -107,7 +130,9 @@ export const DatasetSelectModal = ({ datasetId: item._id, avatar: item.avatar, name: item.name, - vectorModel: item.vectorModel + vectorModel: item.vectorModel, + datasetType: item.type, + dataCount: item.dataCount } ]); } else { @@ -242,16 +267,18 @@ export const DatasetSelectModal = ({ onClick={(e) => e.stopPropagation()} // Prevent parent click when clicking checkbox > {item.type !== DatasetTypeEnum.folder && ( - { - const checked = e.target.checked; - onSelect(item, checked); - }} - colorScheme="blue" - size="sm" - /> + + { + const checked = e.target.checked; + onSelect(item, checked); + }} + colorScheme="blue" + size="sm" + /> + )} @@ -300,7 +327,9 @@ export const DatasetSelectModal = ({ datasetId: item._id, avatar: item.avatar, name: item.name, - vectorModel: item.vectorModel + vectorModel: item.vectorModel, + datasetType: item.type, + dataCount: item.dataCount }) ); setSelectedDatasets((prev) => [...prev, ...newSelections]); @@ -326,7 +355,7 @@ export const DatasetSelectModal = ({ {/* Selected count display */} - {t('app:Selected')}: {selectedDatasets.length} {t('app:dataset')} + {t('app:Selected')}: {selectedDatasets.length} {/* Selected dataset list */} @@ -372,19 +401,21 @@ export const DatasetSelectModal = ({ - - - {t('common:dataset.Select Dataset Tips')} - + {!isSmartGenerateScene && ( + + + {t('common:dataset.Select Dataset Tips')} + + )} + } + menuList={[ + { + children: [ + { + label: ( + + + {t('dashboard_evaluation:smart_generation')} + + ), + onClick: () => handleCreateDataset('smart') + }, + { + label: ( + + + {t('dashboard_evaluation:file_import')} + + ), + onClick: () => handleCreateDataset('import') + } + ] + } + ]} + /> + + + + + {/* 智能生成数据集弹窗 */} + {isIntelligentModalOpen && ( + + )} + + ); +}; + +export default React.memo(EvaluationDatasetSelector); diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/FilesCascader.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/FilesCascader.tsx new file mode 100644 index 000000000000..494b4c2d85d6 --- /dev/null +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/FilesCascader.tsx @@ -0,0 +1,527 @@ +import React, { useState, useMemo, useCallback, useRef } from 'react'; +import { + Box, + Flex, + Text, + Button, + useDisclosure, + Menu, + MenuButton, + MenuList, + HStack, + VStack +} from '@chakra-ui/react'; +import { useTranslation } from 'next-i18next'; +import Avatar from '@fastgpt/web/components/common/Avatar'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants'; +import { getCollectionIcon } from '@fastgpt/global/core/dataset/utils'; +import { getDatasets } from '@/web/core/dataset/api'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import { getDatasetCollections } from '@/web/core/dataset/api'; +import MyBox from '@fastgpt/web/components/common/MyBox'; +import type { GetResourceListItemResponse } from '@fastgpt/global/common/parentFolder/type'; +import { useMemoizedFn } from 'ahooks'; + +export interface FileSelection { + datasetId?: string; + datasetName?: string; + datasetAvatar?: string; + collectionId?: string; + collectionName?: string; + collectionType?: string; + noKnowledgeBase?: boolean; +} + +interface FilesCascaderProps { + value?: FileSelection; + onChange?: (selection: FileSelection) => void; + placeholder?: string; + width?: string | string[]; + isDisabled?: boolean; +} + +/** + * 知识库数据项类型 + */ +type DatasetItemType = GetResourceListItemResponse & { + open: boolean; + children?: DatasetItemType[]; + type?: DatasetTypeEnum; + vectorModel?: { name: string }; +}; + +const FilesCascader: React.FC = ({ + value, + onChange, + placeholder, + width = '100%', + isDisabled = false +}) => { + const { t } = useTranslation(); + const ButtonRef = useRef(null); + const { isOpen, onOpen, onClose } = useDisclosure(); + + // 状态管理:选中的知识库ID、知识库列表、正在请求的ID列表 + const [selectedDatasetId, setSelectedDatasetId] = useState(''); + const [datasetList, setDatasetList] = useState([]); + const [requestingIdList, setRequestingIdList] = useState([]); + + /** + * 获取知识库列表 + * @param parentId 父级ID + * @returns 知识库列表数据 + */ + const getDatasetList = useCallback(async ({ parentId }: { parentId: string | null }) => { + return getDatasets({ parentId, searchKey: '' }).then((res) => + res.map((item) => ({ + id: item._id, + name: item.name, + avatar: item.avatar, + isFolder: item.type === DatasetTypeEnum.folder, + type: item.type as DatasetTypeEnum, + vectorModel: item.vectorModel, + open: false + })) + ); + }, []); + + /** + * 请求服务器获取数据 + */ + const { runAsync: requestServer } = useRequest2((e: { parentId: string | null }) => { + if (requestingIdList.includes(e.parentId || '')) return Promise.reject(null); + + setRequestingIdList((state) => [...state, e.parentId || '']); + return getDatasetList(e).finally(() => + setRequestingIdList((state) => state.filter((id) => id !== (e.parentId || ''))) + ); + }, {}); + + /** + * 初始化加载知识库列表 + */ + const { loading } = useRequest2(() => requestServer({ parentId: null }), { + manual: false, + onSuccess: (data) => { + setDatasetList(data); + } + }); + + /** + * 获取选中知识库下的文件集合 + */ + const { + data: collections = [], + runAsync: loadCollections, + loading: loadingCollections + } = useRequest2( + (datasetId: string) => + getDatasetCollections({ + datasetId: datasetId, + parentId: '', + selectFolder: false, + simple: true, + pageNum: 1, + pageSize: 50 + }).then((res) => res.list || []), + { + manual: true + } + ); + + /** + * 格式化集合数据,添加图标 + */ + const formatCollections = useMemo( + () => + collections.map((collection) => { + const icon = getCollectionIcon({ type: collection.type, name: collection.name }); + return { + ...collection, + icon + }; + }), + [collections] + ); + + /** + * 处理知识库选择 + * @param dataset 选中的知识库 + */ + const handleDatasetSelect = useCallback( + async (dataset: DatasetItemType) => { + if (dataset.isFolder) { + // 如果是文件夹,展开或收起 + if (!dataset.children) { + const data = await requestServer({ parentId: dataset.id }); + dataset.children = data; + } + dataset.open = !dataset.open; + setDatasetList([...datasetList]); + } else { + // 如果是知识库,设置为选中状态并加载文件 + setSelectedDatasetId(dataset.id); + const tempSelectionData = { + datasetId: dataset.id, + datasetName: dataset.name, + datasetAvatar: dataset.avatar, + collectionId: undefined, + collectionName: undefined, + collectionType: undefined, + noKnowledgeBase: false + }; + // 立即更新选择器的显示 + onChange?.(tempSelectionData); + await loadCollections(dataset.id); + } + }, + [datasetList, requestServer, loadCollections, onChange] + ); + + /** + * 处理文件选择 + * @param collection 选中的文件集合 + */ + const handleCollectionSelect = useCallback( + (collection: any) => { + // 在知识库列表中查找知识库名称和头像 + const findDatasetInfo = ( + list: DatasetItemType[], + id: string + ): { name?: string; avatar?: string } | undefined => { + for (const item of list) { + if (item.id === id) return { name: item.name, avatar: item.avatar }; + if (item.children) { + const found = findDatasetInfo(item.children, id); + if (found) return found; + } + } + return undefined; + }; + + const datasetInfo = findDatasetInfo(datasetList, selectedDatasetId); + const newSelection = { + datasetId: selectedDatasetId, + datasetName: datasetInfo?.name, + datasetAvatar: datasetInfo?.avatar, + collectionId: collection._id, + collectionName: collection.name, + collectionType: collection.type, + noKnowledgeBase: false + }; + onChange?.(newSelection); + onClose(); + }, + [selectedDatasetId, datasetList, onChange, onClose] + ); + + /** + * 处理"不加入知识库"选择 + */ + const handleNoKnowledgeBase = useCallback(() => { + setSelectedDatasetId(''); + const newSelection = { + datasetId: undefined, + datasetName: undefined, + collectionId: undefined, + collectionName: undefined, + collectionType: undefined, + noKnowledgeBase: true + }; + onChange?.(newSelection); + onClose(); + }, [onChange, onClose]); + + /** + * 获取知识库头像 + */ + const getDatasetAvatar = useCallback( + (datasetId: string): string | undefined => { + const findDatasetAvatar = (list: DatasetItemType[], id: string): string | undefined => { + for (const item of list) { + if (item.id === id) return item.avatar; + if (item.children) { + const found = findDatasetAvatar(item.children, id); + if (found) return found; + } + } + return undefined; + }; + return findDatasetAvatar(datasetList, datasetId); + }, + [datasetList] + ); + + /** + * 渲染显示内容 + */ + const renderDisplayContent = useMemo(() => { + if (value?.noKnowledgeBase) { + return ( + + {t('app:files_cascader_no_knowledge_base')} + + ); + } + + if (value?.datasetName && value?.collectionName) { + // 显示完整路径:知识库图标 + 知识库名称 + 箭头 + 文件图标 + 文件名称 + const datasetAvatar = + value.datasetAvatar || (value.datasetId ? getDatasetAvatar(value.datasetId) : undefined); + const collectionIcon = getCollectionIcon({ + type: value.collectionType as any, + name: value.collectionName + }); + + return ( + + {datasetAvatar && ( + + )} + + {value.datasetName} + + + {collectionIcon && ( + + )} + + {value.collectionName} + + + ); + } + + if (value?.datasetName) { + const datasetAvatar = + value.datasetAvatar || (value.datasetId ? getDatasetAvatar(value.datasetId) : undefined); + return ( + + {datasetAvatar && ( + + )} + + {value.datasetName} + + + ); + } + + return ( + + {placeholder || t('app:files_cascader_select_knowledge_base')} + + ); + }, [value, placeholder, t, getDatasetAvatar]); + + /** + * 渲染知识库树形结构 + */ + const renderDatasetTree = useMemoizedFn( + ({ list, index = 0 }: { list: DatasetItemType[]; index?: number }) => { + return ( + <> + {list.map((item) => ( + + handleDatasetSelect(item)} + > + {item.isFolder && ( + + + + )} + + + {item.name} + + + {item.children && item.open && ( + {renderDatasetTree({ list: item.children, index: index + 1 })} + )} + + ))} + + ); + } + ); + + return ( + + + } + variant={'whitePrimaryOutline'} + size={'md'} + fontSize={'sm'} + textAlign={'left'} + 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' + }} + bg={isOpen ? '#fff' : '#fff'} + color={isOpen ? 'primary.700' : 'myGray.700'} + borderColor={isOpen ? 'primary.300' : 'myGray.200'} + boxShadow={isOpen ? '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)' : 'none'} + _hover={{ borderColor: 'primary.300' }} + > + + + {loading && } + {renderDisplayContent} + + + + + { + const w = ButtonRef.current?.clientWidth; + if (w) { + return `${Math.max(w, 500)}px !important`; + } + return '500px !important'; + })()} + px={'0'} + py={'0'} + border={'1px solid #fff'} + boxShadow={ + '0px 2px 4px rgba(161, 167, 179, 0.25), 0px 0px 1px rgba(121, 141, 159, 0.25);' + } + zIndex={99} + h={'300px'} + overflow={'hidden'} + > + + {/* 左侧知识库列表 */} + + + {/* 不加入知识库选项 */} + {!loading && ( + + + {t('app:files_cascader_no_knowledge_base')} + + + )} + + {/* 知识库列表 */} + {datasetList.length !== 0 && {renderDatasetTree({ list: datasetList })}} + + + + {/* 右侧文件列表 */} + + + {!selectedDatasetId || value?.noKnowledgeBase ? ( + + + {t('app:files_cascader_select_first')} + + + ) : formatCollections.length === 0 && !loadingCollections ? ( + + + {t('app:files_cascader_dataset_empty')} + + + ) : ( + formatCollections.map((collection) => ( + handleCollectionSelect(collection)} + mb={0.5} + > + + + + {collection.name} + + + + )) + )} + + + + + + + ); +}; + +export default React.memo(FilesCascader); diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/QuoteList.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/QuoteList.tsx index 4026f860809d..798fd102c861 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/QuoteList.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/QuoteList.tsx @@ -9,13 +9,18 @@ import { ChatItemContext } from '@/web/core/chat/context/chatItemContext'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { useChatStore } from '@/web/core/chat/context/useChatStore'; import { getQuoteDataList } from '@/web/core/chat/api'; +import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants'; const QuoteList = React.memo(function QuoteList({ chatItemDataId = '', - rawSearch = [] + rawSearch = [], + applicationId, + chatId }: { chatItemDataId?: string; rawSearch: SearchDataResponseItemType[]; + applicationId?: string; + chatId?: string; }) { const theme = useTheme(); const { appId, outLinkAuthData } = useChatStore(); @@ -23,7 +28,7 @@ const QuoteList = React.memo(function QuoteList({ const RawSourceBoxProps = useContextSelector(ChatBoxContext, (v) => ({ chatItemDataId, appId: v.appId, - chatId: v.chatId, + chatId: chatId || v.chatId, // 优先使用外部传入的chatId ...(v.outLinkAuthData || {}) })); const showRawSource = useContextSelector(ChatItemContext, (v) => v.isShowReadRawSource); @@ -32,14 +37,24 @@ const QuoteList = React.memo(function QuoteList({ (v) => v.showRouteToDatasetDetail ); + const datasetDataIdList = useMemo( + () => rawSearch.map((item) => item.id).filter((v) => !v.includes('sql')), + [rawSearch] + ); + const collectionIdList = useMemo( + () => + [...new Set(rawSearch.map((item) => item.collectionId))].filter((v) => !v.includes('sql')), + [rawSearch] + ); + const { data: quoteList } = useRequest2( async () => !!chatItemDataId ? await getQuoteDataList({ - datasetDataIdList: rawSearch.map((item) => item.id), - collectionIdList: [...new Set(rawSearch.map((item) => item.collectionId))], + datasetDataIdList, + collectionIdList, chatItemDataId, - appId, + appId: applicationId || appId, chatId: RawSourceBoxProps.chatId, ...outLinkAuthData }) diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ResponseTags.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ResponseTags.tsx index df5f58fa1fcb..2d4d73faa3d5 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ResponseTags.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ResponseTags.tsx @@ -20,6 +20,7 @@ export type CitationRenderItem = { key: string; displayText: string; icon?: string; + disabled?: boolean; onClick: () => any; }; @@ -92,10 +93,14 @@ const ResponseTags = ({ type: 'dataset' as const, key: item.collectionId, displayText: item.sourceName, + disabled: item.sourceId?.startsWith('sql'), icon: item.imageId ? 'core/dataset/imageFill' : getSourceNameIcon({ sourceId: item.sourceId, sourceName: item.sourceName }), onClick: () => { + if (item.sourceId?.startsWith('sql')) { + return; + } onOpenCiteModal({ collectionId: item.collectionId, sourceId: item.sourceId, @@ -169,7 +174,10 @@ const ResponseTags = ({ > {citationRenderList.map((item, index) => { return ( - + { e.stopPropagation(); item.onClick?.(); diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/SelectMarkCollection.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/SelectMarkCollection.tsx index 507d2d3c90cc..67f5dc3b4887 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/SelectMarkCollection.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/SelectMarkCollection.tsx @@ -1,21 +1,27 @@ -import React from 'react'; -import { ModalBody, useTheme, ModalFooter, Button, Box, Card, Flex, Grid } from '@chakra-ui/react'; +import React, { useState, useMemo, useCallback, useEffect } from 'react'; +import { ModalBody, ModalFooter, Button, VStack, FormControl, FormLabel } from '@chakra-ui/react'; import { useTranslation } from 'next-i18next'; -import Avatar from '@fastgpt/web/components/common/Avatar'; -import MyIcon from '@fastgpt/web/components/common/Icon'; -import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants'; -import DatasetSelectModal, { useDatasetSelect } from '@/components/core/dataset/SelectModal'; +import { useRouter } from 'next/router'; +import MyModal from '@fastgpt/web/components/common/MyModal'; import dynamic from 'next/dynamic'; import { type AdminFbkType } from '@fastgpt/global/core/chat/type.d'; -import SelectCollections from '@/web/core/dataset/components/SelectCollections'; -import EmptyTip from '@fastgpt/web/components/common/EmptyTip'; +import FilesCascader from './FilesCascader'; +import type { FileSelection } from './FilesCascader'; +import EvaluationDatasetSelector from './EvaluationDatasetSelector'; +import { getEvaluationList } from '@/web/core/evaluation/task'; +import { useUserStore } from '@/web/support/user/useUserStore'; const InputDataModal = dynamic(() => import('@/pageComponents/dataset/detail/InputDataModal')); export type AdminMarkType = { feedbackDataId?: string; datasetId?: string; + datasetName?: string; + datasetAvatar?: string; collectionId?: string; + collectionName?: string; + collectionType?: string; + noKnowledgeBase?: boolean; q: string; a?: string; }; @@ -32,109 +38,134 @@ const SelectMarkCollection = ({ onSuccess: (adminFeedback: AdminFbkType) => void; }) => { const { t } = useTranslation(); - const theme = useTheme(); - const { paths, setParentId, datasets, isFetching } = useDatasetSelect(); + const router = useRouter(); + const { userInfo } = useUserStore(); + + // 从路由查询参数获取appId + const appId = useMemo(() => { + return router.query.appId as string; + }, [router.query.appId]); + + // 级联选择器的值 + const cascaderValue: FileSelection = useMemo(() => { + return { + datasetId: adminMarkData.datasetId, + datasetName: adminMarkData.datasetName, + datasetAvatar: adminMarkData.datasetAvatar, + collectionId: adminMarkData.collectionId, + collectionName: adminMarkData.collectionName, + collectionType: adminMarkData.collectionType, + noKnowledgeBase: adminMarkData.noKnowledgeBase + }; + }, [adminMarkData]); + + // 处理级联选择器变化 + const handleCascaderChange = useCallback( + (selection: FileSelection) => { + setAdminMarkData({ + ...adminMarkData, + ...selection + }); + }, + [adminMarkData, setAdminMarkData] + ); + + // 评测数据集选择相关 + const [selectedEvaluationDataset, setSelectedEvaluationDataset] = useState(''); + + // 处理评测数据集选择变化 + const handleEvaluationDatasetChange = useCallback((datasetId: string) => { + setSelectedEvaluationDataset(datasetId); + }, []); + + // 处理确认按钮点击 + const handleConfirm = useCallback(() => { + if ( + (adminMarkData.datasetId && adminMarkData.collectionId) || + (selectedEvaluationDataset && selectedEvaluationDataset !== 'null') + ) { + // 打开输入数据模态框 + setShowInputDataModal(true); + } + }, [adminMarkData.datasetId, adminMarkData.collectionId, selectedEvaluationDataset]); + + // 控制是否显示输入数据模态框 + const [showInputDataModal, setShowInputDataModal] = useState(false); + + // 检查是否有评测权限 + const hasEvaluationPermission = useMemo(() => { + return userInfo?.team?.permission.hasEvaluationCreatePer; + }, [userInfo]); + + // 检查是否可以选择(需要同时选择了数据集和集合,且不是"不加入知识库") + const canConfirm = + (adminMarkData.datasetId && adminMarkData.collectionId && !adminMarkData.noKnowledgeBase) || + (hasEvaluationPermission && selectedEvaluationDataset !== 'null' && selectedEvaluationDataset); + + useEffect(() => { + // 只有在有评测权限时才获取评测列表 + if (hasEvaluationPermission) { + getEvaluationList({ + pageNum: 1, + pageSize: 1, + appId: appId + }).then((res) => { + const item = res?.list?.[0]; + if (item) { + setSelectedEvaluationDataset(item.evalDatasetCollectionId); + } + }); + } + }, [appId, hasEvaluationPermission]); return ( <> {/* select dataset */} - {!adminMarkData.datasetId && ( - - - - {datasets.map((item) => - (() => { - return ( - { - if (item.type === DatasetTypeEnum.folder) { - setParentId(item._id); - } else { - setAdminMarkData({ ...adminMarkData, datasetId: item._id }); - } - }} - > - - - {item.name} - - - - {item.vectorModel.name} - - - ); - })() - )} - - {datasets.length === 0 && } - - - )} - - {/* select collection */} - {adminMarkData.datasetId && ( - { - setAdminMarkData({ - ...adminMarkData, - collectionId: collectionIds[0] - }); - }} - CustomFooter={ - - - - } - /> - )} + + + + {hasEvaluationPermission && ( + + )} + + + {t('dashboard_evaluation:join_knowledge_base')} + + + + + + + + + + {/* input data */} - {adminMarkData.datasetId && adminMarkData.collectionId && ( + {showInputDataModal && ( { - setAdminMarkData({ - ...adminMarkData, - collectionId: undefined - }); + setShowInputDataModal(false); }} - collectionId={adminMarkData.collectionId} + collectionId={adminMarkData.collectionId || ''} + evaluationDatasetId={ + selectedEvaluationDataset === 'null' ? '' : selectedEvaluationDataset + } dataId={adminMarkData.feedbackDataId} defaultValue={{ q: adminMarkData.q, diff --git a/projects/app/src/components/core/chat/components/WholeResponseModal.tsx b/projects/app/src/components/core/chat/components/WholeResponseModal.tsx index 985a8762e509..e5b938265269 100644 --- a/projects/app/src/components/core/chat/components/WholeResponseModal.tsx +++ b/projects/app/src/components/core/chat/components/WholeResponseModal.tsx @@ -6,7 +6,10 @@ import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/cons import MyModal from '@fastgpt/web/components/common/MyModal'; import Markdown from '@/components/Markdown'; import QuoteList from '../ChatContainer/ChatBox/components/QuoteList'; -import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constants'; +import { + DatasetSearchModeMap, + DatasetSearchModeEnum +} from '@fastgpt/global/core/dataset/constants'; import { formatNumber } from '@fastgpt/global/common/math/tools'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import Avatar from '@fastgpt/web/components/common/Avatar'; @@ -18,6 +21,8 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { getFileIcon } from '@fastgpt/global/common/file/icon'; import EmptyTip from '@fastgpt/web/components/common/EmptyTip'; import { completionFinishReasonMap } from '@fastgpt/global/core/ai/constants'; +import { isEmpty } from 'lodash'; +import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants'; type sideTabItemType = { moduleLogo?: string; @@ -34,15 +39,18 @@ export const WholeResponseContent = ({ activeModule, hideTabs, dataId, - chatTime + chatTime, + appId, + chatId }: { activeModule: ChatHistoryItemResType; hideTabs?: boolean; dataId?: string; chatTime?: Date; + appId?: string; + chatId?: string; }) => { const { t } = useTranslation(); - // Auto scroll to top const ContentRef = useRef(null); useEffect(() => { @@ -121,6 +129,40 @@ export const WholeResponseContent = ({ [RowRender, t] ); + const searchModeDisplay = useMemo(() => { + if (activeModule.searchMode === DatasetSearchModeEnum.database) { + return t(DatasetSearchModeMap[DatasetSearchModeEnum.database]?.title); + } + + if (!isEmpty(activeModule.sqlResult)) { + const model = activeModule.searchMode as any; + // @ts-ignore + const textList = [ + t(DatasetSearchModeMap[DatasetSearchModeEnum.database]?.title), + // @ts-ignore + t(DatasetSearchModeMap[model]?.title) + ].filter((v) => v); + return textList.join(t('common:semicolon')); + } + // @ts-ignore + return t(DatasetSearchModeMap[activeModule.searchMode]?.title); + }, [activeModule.searchMode, activeModule.sqlResult, t]); + + const otherKnowledgeBaseDataList = useMemo( + () => (activeModule?.quoteList || []).filter((item) => !item.id.startsWith('sql')), + [activeModule.quoteList] + ); + const hasOtherKnowledgeBase = useMemo( + () => otherKnowledgeBaseDataList.length > 0, + [otherKnowledgeBaseDataList] + ); + + const databaseDataList = useMemo( + () => (activeModule?.quoteList || []).filter((item) => item.id.startsWith('sql')), + [activeModule.quoteList] + ); + const hasDatabase = useMemo(() => databaseDataList.length > 0, [databaseDataList]); + return activeModule ? ( - - {/* @ts-ignore */} - {t(DatasetSearchModeMap[activeModule.searchMode]?.title)} - + {searchModeDisplay} {activeModule.embeddingWeight && ( <>{`(${t('chat:response_hybrid_weight', { emb: activeModule.embeddingWeight, @@ -303,10 +342,42 @@ export const WholeResponseContent = ({ /> {activeModule.quoteList && activeModule.quoteList.length > 0 && ( - } - /> + <> + {hasDatabase && ( + + } + /> + )} + {hasOtherKnowledgeBase && ( + + } + /> + )} + )} {/* dataset concat */} @@ -609,13 +680,17 @@ export const ResponseBox = React.memo(function ResponseBox({ dataId, chatTime, hideTabs = false, - useMobile = false + useMobile = false, + appId, + chatId }: { response: ChatHistoryItemResType[]; dataId?: string; chatTime: Date; hideTabs?: boolean; useMobile?: boolean; + appId?: string; + chatId?: string; }) { const { t } = useTranslation(); const { isPc } = useSystem(); @@ -741,6 +816,8 @@ export const ResponseBox = React.memo(function ResponseBox({ activeModule={activeModule} hideTabs={hideTabs} chatTime={chatTime} + appId={appId} + chatId={chatId} /> @@ -806,6 +883,8 @@ export const ResponseBox = React.memo(function ResponseBox({ activeModule={activeModule} hideTabs={hideTabs} chatTime={chatTime} + appId={appId} + chatId={chatId} /> diff --git a/projects/app/src/components/core/dataset/QuoteItem.tsx b/projects/app/src/components/core/dataset/QuoteItem.tsx index bbf79e9d2b52..f0bc515b71e1 100644 --- a/projects/app/src/components/core/dataset/QuoteItem.tsx +++ b/projects/app/src/components/core/dataset/QuoteItem.tsx @@ -1,5 +1,5 @@ import React, { useMemo, useState } from 'react'; -import { Box, Flex, Link, Progress } from '@chakra-ui/react'; +import { Box, Flex, Link, Progress, Text } from '@chakra-ui/react'; import RawSourceBox from '@/components/core/dataset/RawSourceBox'; import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type.d'; import NextLink from 'next/link'; @@ -8,11 +8,7 @@ import { useTranslation } from 'next-i18next'; import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; import dynamic from 'next/dynamic'; import MyBox from '@fastgpt/web/components/common/MyBox'; -import { - DatasetCollectionTypeEnum, - SearchScoreTypeEnum, - SearchScoreTypeMap -} from '@fastgpt/global/core/dataset/constants'; +import { SearchScoreTypeEnum, SearchScoreTypeMap } from '@fastgpt/global/core/dataset/constants'; import type { readCollectionSourceBody } from '@/pages/api/core/dataset/collection/read'; import Markdown from '@/components/Markdown'; @@ -107,6 +103,34 @@ const QuoteItem = ({ const score = useMemo(() => { return formatScore(quoteItem.score); }, [quoteItem.score]); + const isDatabaseAnswer = useMemo(() => quoteItem.id.startsWith('sql'), [quoteItem.id]); + const datasetDetailUrl = useMemo( + () => + isDatabaseAnswer + ? `/dataset/detail?datasetId=${quoteItem.datasetId}` + : `/dataset/detail?datasetId=${quoteItem.datasetId}¤tTab=dataCard&collectionId=${quoteItem.collectionId}`, + [isDatabaseAnswer, quoteItem.datasetId, quoteItem.collectionId] + ); + + const databaseContent = useMemo( + () => ( + + + + {t('chat:database_sql_query')} + + {quoteItem.a || '-'} + + + + {t('chat:search_result')} + + {quoteItem.q || '-'} + + + ), + [t, quoteItem.q, quoteItem.a] + ); return ( <> @@ -121,71 +145,86 @@ const QuoteItem = ({ display={'flex'} flexDirection={'column'} > - - {score?.primaryScore && ( - - - #{score.primaryScore.index + 1} - - - {t(SearchScoreTypeMap[score.primaryScore.type]?.label as any)} - {SearchScoreTypeMap[score.primaryScore.type]?.showScore - ? ` ${score.primaryScore.value?.toFixed(4)}` - : ''} - - - - )} - {score.secondaryScore.map((item, i) => ( - - - + {score?.primaryScore || score.secondaryScore.length > 0 ? ( + + {score?.primaryScore && ( + + + #{score.primaryScore.index + 1} - #{item.index + 1} - - - {t(SearchScoreTypeMap[item.type]?.label as any)}: {item.value.toFixed(4)} + borderRightColor={'primary.700'} + borderRightWidth={'1px'} + h={'14px'} + mx={2} + /> + + {t(SearchScoreTypeMap[score.primaryScore.type]?.label as any)} + {SearchScoreTypeMap[score.primaryScore.type]?.showScore + ? ` ${score.primaryScore.value?.toFixed(4)}` + : ''} - - {SearchScoreTypeMap[item.type]?.showScore && ( - - )} + + )} + {score.secondaryScore.map((item, i) => ( + + + + + #{item.index + 1} + + + {t(SearchScoreTypeMap[item.type]?.label as any)}: {item.value.toFixed(4)} + + + + {SearchScoreTypeMap[item.type]?.showScore && ( + + )} + - - - ))} - + + ))} + + ) : ( + '' + )} - - + {isDatabaseAnswer ? ( + databaseContent + ) : ( + <> + + + + )} - + {!isDatabaseAnswer && ( + + )} - {quoteItem.id && canEditData && ( + {quoteItem.id && !isDatabaseAnswer && canEditData && ( {t('common:to_dataset')} diff --git a/projects/app/src/components/core/dataset/SearchParamsTip.tsx b/projects/app/src/components/core/dataset/SearchParamsTip.tsx index e231ca963469..7270b7def352 100644 --- a/projects/app/src/components/core/dataset/SearchParamsTip.tsx +++ b/projects/app/src/components/core/dataset/SearchParamsTip.tsx @@ -16,7 +16,10 @@ const SearchParamsTip = ({ responseEmptyText, usingReRank = false, datasetSearchUsingExtensionQuery, - queryExtensionModel + queryExtensionModel, + hasDatabaseKnowledge = false, + hasOtherKnowledge = true, + generateSqlModel }: { searchMode: `${DatasetSearchModeEnum}`; similarity?: number; @@ -25,6 +28,9 @@ const SearchParamsTip = ({ usingReRank?: boolean; datasetSearchUsingExtensionQuery?: boolean; queryExtensionModel?: string; + hasDatabaseKnowledge?: boolean; + hasOtherKnowledge?: boolean; + generateSqlModel?: string; }) => { const { t } = useTranslation(); const { reRankModelList, llmModelList } = useSystemStore(); @@ -39,6 +45,11 @@ const SearchParamsTip = ({ [datasetSearchUsingExtensionQuery, queryExtensionModel, llmModelList] ); + const onlyDatabase = useMemo( + () => hasDatabaseKnowledge && !hasOtherKnowledge, + [hasDatabaseKnowledge, hasOtherKnowledge] + ); + return ( - {t('common:core.dataset.search.search mode')} - {t('common:max_quote_tokens')} - {t('common:min_similarity')} - {hasReRankModel && {t('common:core.dataset.search.ReRank')}} - {t('common:core.module.template.Query extension')} - {hasEmptyResponseMode && ( - {t('common:core.dataset.search.Empty result response')} + {!onlyDatabase && ( + <> + {t('common:core.dataset.search.search mode')} + {t('common:max_quote_tokens')} + {t('common:min_similarity')} + {hasReRankModel && ( + {t('common:core.dataset.search.ReRank')} + )} + {t('common:core.module.template.Query extension')} + {hasEmptyResponseMode && ( + {t('common:core.dataset.search.Empty result response')} + )} + + )} + {hasDatabaseKnowledge && ( + {t('common:core.dataset.search.Database search')} )} - - - - {t(DatasetSearchModeMap[searchMode]?.title as any)} - - - - {limit} - - - {hasSimilarityMode ? similarity : t('common:core.dataset.search.Nonsupport')} - - {hasReRankModel && ( + {!onlyDatabase && ( + <> + + + + {t(DatasetSearchModeMap[searchMode]?.title as any)} + + + + {limit} + + + {hasSimilarityMode ? similarity : t('common:core.dataset.search.Nonsupport')} + + {hasReRankModel && ( + + {usingReRank ? '✅' : '❌'} + + )} + + {extensionModelName ? extensionModelName : '❌'} + + {hasEmptyResponseMode && {responseEmptyText !== '' ? '✅' : '❌'}} + + )} + {hasDatabaseKnowledge && ( - {usingReRank ? '✅' : '❌'} + {getWebLLMModel(generateSqlModel)?.name || '-'} )} - - {extensionModelName ? extensionModelName : '❌'} - - {hasEmptyResponseMode && {responseEmptyText !== '' ? '✅' : '❌'}} diff --git a/projects/app/src/components/core/dataset/SelectModal.tsx b/projects/app/src/components/core/dataset/SelectModal.tsx index d83a60ddb3c0..208542e75df8 100644 --- a/projects/app/src/components/core/dataset/SelectModal.tsx +++ b/projects/app/src/components/core/dataset/SelectModal.tsx @@ -7,6 +7,7 @@ import { Box } from '@chakra-ui/react'; import FolderPath from '@/components/common/folder/Path'; import MyBox from '@fastgpt/web/components/common/MyBox'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import type { DatasetListItemType } from '@fastgpt/global/core/dataset/type.d'; type PathItemType = { parentId: string; @@ -68,7 +69,10 @@ const DatasetSelectContainer = ({ ); }; -export function useDatasetSelect() { +export function useDatasetSelect( + scene?: string | undefined, + formatResData = (datasetList: DatasetListItemType[]) => datasetList +) { const [parentId, setParentId] = useState(''); const [searchKey, setSearchKey] = useState(''); @@ -81,14 +85,14 @@ export function useDatasetSelect() { } = useRequest2( async () => { const result = await Promise.all([ - getDatasets({ parentId, searchKey }), + getDatasets({ parentId, searchKey, ...(scene ? { scene } : {}) }), // Only get paths when not searching searchKey.trim() ? Promise.resolve([]) : getDatasetPaths({ sourceId: parentId, type: 'current' }) ]); return { - datasets: result[0], + datasets: formatResData(result[0]), paths: result[1] }; }, diff --git a/projects/app/src/components/support/apikey/Table.tsx b/projects/app/src/components/support/apikey/Table.tsx index 0f9a59bdb148..0391ec1d4c01 100644 --- a/projects/app/src/components/support/apikey/Table.tsx +++ b/projects/app/src/components/support/apikey/Table.tsx @@ -151,7 +151,7 @@ const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => { {t('common:Name')} - Api Key + API key {t('common:support.outlink.Usage points')} {feConfigs?.isPlus && ( <> diff --git a/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx b/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx index 3755d237e10e..4720e85b51d4 100644 --- a/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx +++ b/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx @@ -103,8 +103,8 @@ function MemberModal({ const [selectedRole, setSelectedRole] = useState(roleList?.read?.value); const roleLabel = useMemo(() => { if (selectedRole === undefined) return ''; - return getRoleLabelList(selectedRole!).join('、'); - }, [getRoleLabelList, selectedRole]); + return getRoleLabelList(selectedRole!).join(t('common:comma')); + }, [getRoleLabelList, selectedRole, t]); const onUpdateCollaborators = useContextSelector( CollaboratorContext, diff --git a/projects/app/src/global/core/dataset/api.d.ts b/projects/app/src/global/core/dataset/api.d.ts index fe1cc1c5bd65..93b9581defdf 100644 --- a/projects/app/src/global/core/dataset/api.d.ts +++ b/projects/app/src/global/core/dataset/api.d.ts @@ -84,4 +84,11 @@ export type SearchTestResponse = { queryExtensionModel?: string; }; +export type DatabaseSearchTestResponse = { + answer: string; + sql_result: string; + duration: string; + limit: number; + searchMode: `${DatasetSearchModeEnum}`; +}; /* =========== training =========== */ diff --git a/projects/app/src/global/core/dataset/type.d.ts b/projects/app/src/global/core/dataset/type.d.ts index ddca53cb0d11..2ad6f20985c3 100644 --- a/projects/app/src/global/core/dataset/type.d.ts +++ b/projects/app/src/global/core/dataset/type.d.ts @@ -7,6 +7,7 @@ import type { DatasetPermission } from '@fastgpt/global/support/permission/datas /* ================= collection ===================== */ export type DatasetCollectionsListItemType = { + tableSchemaDescription: DatasetCollectionSchemaType['tableSchema']['description']; _id: string; parentId?: DatasetCollectionSchemaType['parentId']; tmbId: DatasetCollectionSchemaType['tmbId']; @@ -27,6 +28,10 @@ export type DatasetCollectionsListItemType = { dataAmount: number; trainingAmount: number; hasError?: boolean; + + // For database type datasets, include table schema description + tableSchemaDescription?: string; + tableSchema?: DatasetCollectionSchemaType['tableSchema']; }; /* ================= data ===================== */ diff --git a/projects/app/src/instrumentation.ts b/projects/app/src/instrumentation.ts index ebaf45cbade3..f439a179d759 100644 --- a/projects/app/src/instrumentation.ts +++ b/projects/app/src/instrumentation.ts @@ -19,6 +19,7 @@ export async function register() { { startTrainingQueue }, { preLoadWorker }, { loadSystemModels }, + { loadSystemBuiltinMetrics }, { connectSignoz } ] = await Promise.all([ import('@fastgpt/service/common/mongo/init'), @@ -32,6 +33,7 @@ export async function register() { import('@/service/core/dataset/training/utils'), import('@fastgpt/service/worker/preload'), import('@fastgpt/service/core/ai/config/utils'), + import('@fastgpt/service/core/evaluation/metric/provider'), import('@fastgpt/service/common/otel/trace/register') ]); @@ -46,8 +48,14 @@ export async function register() { await connectMongo(connectionMongo, MONGO_URL); connectMongo(connectionLogMongo, MONGO_LOG_URL); - //init system config;init vector database;init root user - await Promise.all([getInitConfig(), initVectorStore(), initRootUser(), loadSystemModels()]); + //init system config;init vector database;init root user;init models;init builtin metrics + await Promise.all([ + getInitConfig(), + initVectorStore(), + initRootUser(), + loadSystemModels(), + loadSystemBuiltinMetrics() + ]); try { await preLoadWorker(); @@ -60,6 +68,11 @@ export async function register() { initAppTemplateTypes(); // getSystemPlugins(true); startMongoWatch(); + + // 动态导入评估模块并初始化(确保在系统配置加载后) + const { initEvaluationWorkers } = await import('@fastgpt/service/core/evaluation'); + initEvaluationWorkers(); + startCron(); startTrainingQueue(true); diff --git a/projects/app/src/pageComponents/account/AccountContainer.tsx b/projects/app/src/pageComponents/account/AccountContainer.tsx index 7a0f71a9ff5e..915d01cf28d3 100644 --- a/projects/app/src/pageComponents/account/AccountContainer.tsx +++ b/projects/app/src/pageComponents/account/AccountContainer.tsx @@ -43,84 +43,87 @@ const AccountContainer = ({ return router.pathname.split('/').pop() as TabEnum; }, [router.pathname]); - const tabList = useRef([ - { - icon: 'support/user/userLight', - label: t('account:personal_information'), - value: TabEnum.info - }, - ...(feConfigs?.isPlus - ? [ - { - icon: 'support/user/usersLight', - label: t('account:team'), - value: TabEnum.team - }, - { - icon: 'support/usage/usageRecordLight', - label: t('account:usage_records'), - value: TabEnum.usage - } - ] - : []), - ...(feConfigs?.show_pay && userInfo?.team?.permission.hasManagePer - ? [ - { - icon: 'support/bill/payRecordLight', - label: t('account:bills_and_invoices'), - value: TabEnum.bill - } - ] - : []), - { - icon: 'common/thirdParty', - label: t('account:third_party'), - value: TabEnum.thirdParty - }, - { - icon: 'common/model', - label: t('account:model_provider'), - value: TabEnum.model - }, - ...(feConfigs?.show_promotion && userInfo?.team?.permission.isOwner - ? [ - { - icon: 'support/account/promotionLight', - label: t('account:promotion_records'), - value: TabEnum.promotion - } - ] - : []), - ...(userInfo?.team?.permission.hasApikeyCreatePer - ? [ - { - icon: 'key', - label: t('account:api_key'), - value: TabEnum.apikey - } - ] - : []), + const tabList = useMemo( + () => [ + { + icon: 'support/user/userLight', + label: t('account:personal_information'), + value: TabEnum.info + }, + ...(feConfigs?.isPlus + ? [ + { + icon: 'support/user/usersLight', + label: t('account:team'), + value: TabEnum.team + }, + { + icon: 'support/usage/usageRecordLight', + label: t('account:usage_records'), + value: TabEnum.usage + } + ] + : []), + ...(feConfigs?.show_pay && userInfo?.team?.permission.hasManagePer + ? [ + { + icon: 'support/bill/payRecordLight', + label: t('account:bills_and_invoices'), + value: TabEnum.bill + } + ] + : []), + { + icon: 'common/thirdParty', + label: t('account:third_party'), + value: TabEnum.thirdParty + }, + { + icon: 'common/model', + label: t('account:model_provider'), + value: TabEnum.model + }, + ...(feConfigs?.show_promotion && userInfo?.team?.permission.isOwner + ? [ + { + icon: 'support/account/promotionLight', + label: t('account:promotion_records'), + value: TabEnum.promotion + } + ] + : []), + ...(userInfo?.team?.permission.hasApikeyCreatePer + ? [ + { + icon: 'key', + label: t('account:api_key'), + value: TabEnum.apikey + } + ] + : []), - ...(feConfigs.isPlus - ? [ - { - icon: 'support/user/informLight', - label: t('account:notifications'), - value: TabEnum.inform - } - ] - : []), - { - icon: 'common/settingLight', - label: t('common:Setting'), - value: TabEnum.setting - }, - { - icon: 'support/account/loginoutLight', - label: t('account:logout'), - value: TabEnum.loginout - } - ]); + ...(feConfigs.isPlus + ? [ + { + icon: 'support/user/informLight', + label: t('account:notifications'), + value: TabEnum.inform + } + ] + : []), + { + icon: 'common/settingLight', + label: t('common:Setting'), + value: TabEnum.setting + }, + { + icon: 'support/account/loginoutLight', + label: t('account:logout'), + value: TabEnum.loginout + } + ], + [t, feConfigs, userInfo] + ); const { openConfirm, ConfirmModal } = useConfirm({ content: t('account:confirm_logout') @@ -156,7 +159,7 @@ const AccountContainer = ({ mx={'auto'} mt={2} w={'100%'} - list={tabList.current} + list={tabList} value={currentTab} onChange={setCurrentTab} /> @@ -173,7 +176,7 @@ const AccountContainer = ({ m={'auto'} w={'100%'} size={isPc ? 'md' : 'sm'} - list={tabList.current.map((item) => ({ + list={tabList.map((item) => ({ value: item.value, label: item.label }))} diff --git a/projects/app/src/pageComponents/account/info/UpdatePswModal.tsx b/projects/app/src/pageComponents/account/info/UpdatePswModal.tsx index c49bdf60a970..cf736d307b5b 100644 --- a/projects/app/src/pageComponents/account/info/UpdatePswModal.tsx +++ b/projects/app/src/pageComponents/account/info/UpdatePswModal.tsx @@ -1,5 +1,14 @@ import React from 'react'; -import { ModalBody, Box, Flex, Input, ModalFooter, Button } from '@chakra-ui/react'; +import { + ModalBody, + Box, + Flex, + Input, + ModalFooter, + Button, + UnorderedList, + ListItem +} from '@chakra-ui/react'; import MyModal from '@fastgpt/web/components/common/MyModal'; import { useTranslation } from 'next-i18next'; import { useForm } from 'react-hook-form'; @@ -7,6 +16,7 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { updatePasswordByOld } from '@/web/support/user/api'; import { useToast } from '@fastgpt/web/hooks/useToast'; import { checkPasswordRule } from '@fastgpt/global/common/string/password'; +import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; type FormType = { oldPsw: string; @@ -15,10 +25,18 @@ type FormType = { }; const UpdatePswModal = ({ onClose }: { onClose: () => void }) => { - const { t } = useTranslation(); + const { t, i18n } = useTranslation(); const { toast } = useToast(); - const { register, handleSubmit, getValues } = useForm({ + // 根据语言设置不同的标签宽度 + const labelWidth = i18n.language === 'en' ? '120px' : '70px'; + + const { + register, + handleSubmit, + getValues, + formState: { errors } + } = useForm({ defaultValues: { oldPsw: '', newPsw: '', @@ -45,48 +63,57 @@ const UpdatePswModal = ({ onClose }: { onClose: () => void }) => { }); } }; - return ( - + {t('account_info:old_password') + ':'} - + {t('account_info:new_password') + ':'} - { - if (!checkPasswordRule(val)) { - return t('login:password_tip'); + + {t('account_info:password_min_length')} + {t('account_info:password_requirement')} + + } + > + { + if (!checkPasswordRule(val)) { + return t('login:password_tip'); + } + return true; } - return true; - } - })} - > + })} + > + - + {t('account_info:confirm_password') + ':'} (getValues('newPsw') === val ? true : t('user:password.not_match')) @@ -98,7 +125,7 @@ const UpdatePswModal = ({ onClose }: { onClose: () => void }) => { - diff --git a/projects/app/src/pageComponents/account/model/AddModelBox.tsx b/projects/app/src/pageComponents/account/model/AddModelBox.tsx index 8edc66f10b8d..851a78dc801d 100644 --- a/projects/app/src/pageComponents/account/model/AddModelBox.tsx +++ b/projects/app/src/pageComponents/account/model/AddModelBox.tsx @@ -683,16 +683,14 @@ export const ModelEditModal = ({ - {feConfigs?.isPlus && ( - - {t('account_model:use_in_eval')} - - - - - - - )} + + {t('account_model:use_in_eval')} + + + + + + diff --git a/projects/app/src/pageComponents/account/model/Channel/EditChannelModal.tsx b/projects/app/src/pageComponents/account/model/Channel/EditChannelModal.tsx index 7fcf28ac63cc..576570e2193b 100644 --- a/projects/app/src/pageComponents/account/model/Channel/EditChannelModal.tsx +++ b/projects/app/src/pageComponents/account/model/Channel/EditChannelModal.tsx @@ -170,7 +170,7 @@ const EditChannelModal = ({ void > {/* 第一行 */} - RequestID + Request ID {detailData?.request_id} diff --git a/projects/app/src/pageComponents/account/model/ModelConfigTable.tsx b/projects/app/src/pageComponents/account/model/ModelConfigTable.tsx index 1ae132ab7e92..8227dcef7632 100644 --- a/projects/app/src/pageComponents/account/model/ModelConfigTable.tsx +++ b/projects/app/src/pageComponents/account/model/ModelConfigTable.tsx @@ -110,18 +110,18 @@ const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => { typeof item.inputPrice === 'number' ? ( - {`${t('common:Input')}:`} + {`${t('common:Input')}: `} {item.inputPrice || 0} - {`${t('common:support.wallet.subscription.point')} / 1K Tokens`} + {`${t('common:support.wallet.subscription.point')}/1K tokens`} - {`${t('common:Output')}:`} + {`${t('common:Output')}: `} {item.outputPrice || 0} - {`${t('common:support.wallet.subscription.point')} / 1K Tokens`} + {`${t('common:support.wallet.subscription.point')}/1K tokens`} ) : ( @@ -129,7 +129,7 @@ const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => { {item.charsPointsPrice || 0} - {`${t('common:support.wallet.subscription.point')} / 1K Tokens`} + {`${t('common:support.wallet.subscription.point')}/1K tokens`} ), tagColor: 'blue' @@ -142,10 +142,10 @@ const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => { priceLabel: ( {`${t('common:Input')}: `} - + {item.charsPointsPrice || 0} - {` ${t('common:support.wallet.subscription.point')} / 1K Tokens`} + {` ${t('common:support.wallet.subscription.point')}/1K tokens`} ), tagColor: 'yellow' @@ -160,7 +160,7 @@ const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => { {item.charsPointsPrice || 0} - {` ${t('common:support.wallet.subscription.point')} / 1K ${t('common:unit.character')}`} + {` ${t('common:support.wallet.subscription.point')}/1K ${t('common:unit.character')}`} ), tagColor: 'green' @@ -175,7 +175,7 @@ const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => { {item.charsPointsPrice || 0} - {` ${t('common:support.wallet.subscription.point')} / 60${t('common:unit.seconds')}`} + {` ${t('common:support.wallet.subscription.point')}/60${t('common:unit.seconds')}`} ), tagColor: 'purple' @@ -188,10 +188,10 @@ const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => { priceLabel: item.charsPointsPrice ? ( {`${t('common:Input')}: `} - + {item.charsPointsPrice} - {` ${t('common:support.wallet.subscription.point')} / 1K Tokens`} + {` ${t('common:support.wallet.subscription.point')}/1K tokens`} ) : ( '-' @@ -735,6 +735,30 @@ const DefaultModelModal = ({ /> + + + {t('account_model:evaluation_model')} + + + + item.useInEvaluation) + .map((item) => ({ + value: item.model, + label: item.name + }))} + onChange={(e) => { + setDefaultData((state) => ({ + ...state, + evaluation: llmModelList.find((item) => item.model === e) + })); + }} + /> + + diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeUserSelect.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeUserSelect.tsx index e3fca1c1a407..be7847697427 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeUserSelect.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeUserSelect.tsx @@ -177,7 +177,7 @@ const OptionItem = ({ /> - {t('common:option') + (index + 1)} + {t('common:option') + ' ' + (index + 1)} diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SelectDataset.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SelectDataset.tsx index e6671953172d..aa4697d162cf 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SelectDataset.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SelectDataset.tsx @@ -11,6 +11,7 @@ import { useContextSelector } from 'use-context-selector'; import { WorkflowContext } from '@/pageComponents/app/detail/WorkflowComponents/context'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; +import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants'; const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal')); @@ -100,9 +101,45 @@ export const SelectDatasetRender = React.memo(function SelectDatasetRender({ datasetId: item.datasetId, vectorModel: item.vectorModel, name: item.name, - avatar: item.avatar + avatar: item.avatar, + datasetType: item.datasetType }))} onChange={(e) => { + const searchModeInfo = inputs.find( + (v) => v.key === NodeInputKeyEnum.datasetSearchMode + ); + if (searchModeInfo) { + const hasDatabaseKnowledge = e.some( + (v) => v.datasetType === DatasetTypeEnum.database + ); + + const hasOtherKnowledge = e.some((v) => v.datasetType !== DatasetTypeEnum.database); + let value = searchModeInfo.value; + + // 如果当前是database模式 + if (value === DatasetSearchModeEnum.database) { + // 如果存在其他知识类型,则改为embedding模式 + if (hasOtherKnowledge) { + value = DatasetSearchModeEnum.embedding; + } + } + // 如果当前不是database模式 + else { + // 如果存在数据库知识且不存在其他知识类型,则设置为database模式 + if (hasDatabaseKnowledge && !hasOtherKnowledge) { + value = DatasetSearchModeEnum.database; + } + } + onChangeNode({ + nodeId, + key: searchModeInfo.key, + type: 'updateInput', + value: { + ...searchModeInfo, + value: e.length === 0 ? DatasetSearchModeEnum.embedding : value + } + }); + } onChangeNode({ nodeId, key: item.key, @@ -126,7 +163,8 @@ export const SelectDatasetRender = React.memo(function SelectDatasetRender({ onCloseDatasetSelect, onOpenDatasetSelect, selectedDatasets, - t + t, + inputs ]); return Render; diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SelectDatasetParams.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SelectDatasetParams.tsx index 4fa622577ce1..1b6ff9219603 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SelectDatasetParams.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SelectDatasetParams.tsx @@ -13,6 +13,7 @@ import { useContextSelector } from 'use-context-selector'; import { WorkflowContext } from '@/pageComponents/app/detail/WorkflowComponents/context'; import { getWebLLMModel } from '@/web/common/system/utils'; import { type AppDatasetSearchParamsType } from '@fastgpt/global/core/app/type'; +import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants'; const SelectDatasetParam = ({ inputs = [], nodeId }: RenderInputProps) => { const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); @@ -31,9 +32,45 @@ const SelectDatasetParam = ({ inputs = [], nodeId }: RenderInputProps) => { rerankWeight: 0.6, datasetSearchUsingExtensionQuery: true, datasetSearchExtensionModel: defaultModels.llm?.model, - datasetSearchExtensionBg: '' + datasetSearchExtensionBg: '', + generateSqlModel: defaultModels.llm?.model }); + const knowledgeTypeConfig = useMemo(() => { + const datasetList = (nodeList.find((node) => node.nodeId === nodeId)?.inputs || []).filter( + (input) => input.key === NodeInputKeyEnum.datasetSelectList + ); + const knowledgeInfoList = datasetList + .map((dataset) => dataset.value) + .flat() + .filter((v) => v); + + // 引用变量场景展示全部 + if (datasetList.some((v) => v.selectedTypeIndex == 1)) { + setData((e) => ({ + ...e, + searchMode: + e.searchMode === DatasetSearchModeEnum.database + ? DatasetSearchModeEnum.embedding + : e.searchMode + })); + return { + hasDatabaseKnowledge: true, + hasOtherKnowledge: true + }; + } + + return { + hasDatabaseKnowledge: knowledgeInfoList.some( + (item) => item.datasetType === DatasetTypeEnum.database + ), + // 没选择知识库时展示通用知识库配置 + hasOtherKnowledge: + knowledgeInfoList.some((item) => item.datasetType !== DatasetTypeEnum.database) || + knowledgeInfoList.length === 0 + }; + }, [nodeList, nodeId]); + const tokenLimit = useMemo(() => { let maxTokens = 0; @@ -86,13 +123,15 @@ const SelectDatasetParam = ({ inputs = [], nodeId }: RenderInputProps) => { searchMode={data.searchMode} similarity={data.similarity} limit={data.limit} + generateSqlModel={data.generateSqlModel} usingReRank={data.usingReRank} datasetSearchUsingExtensionQuery={data.datasetSearchUsingExtensionQuery} queryExtensionModel={data.datasetSearchExtensionModel} + {...knowledgeTypeConfig} /> ); - }, [data, onOpen, t]); + }, [data, onOpen, t, knowledgeTypeConfig]); return ( <> @@ -100,6 +139,7 @@ const SelectDatasetParam = ({ inputs = [], nodeId }: RenderInputProps) => { {isOpen && ( { diff --git a/projects/app/src/pageComponents/app/evaluation/DetailModal.tsx b/projects/app/src/pageComponents/app/evaluation/DetailModal.tsx deleted file mode 100644 index ccb91da7ef1d..000000000000 --- a/projects/app/src/pageComponents/app/evaluation/DetailModal.tsx +++ /dev/null @@ -1,582 +0,0 @@ -import { useSystemStore } from '@/web/common/system/useSystemStore'; -import { - Box, - Button, - Flex, - IconButton, - ModalBody, - Textarea, - Accordion, - AccordionItem, - AccordionButton, - AccordionPanel, - AccordionIcon, - Input -} from '@chakra-ui/react'; -import { getModelFromList } from '@fastgpt/global/core/ai/model'; -import Avatar from '@fastgpt/web/components/common/Avatar'; -import MyModal from '@fastgpt/web/components/common/MyModal'; -import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; -import { useTranslation } from 'next-i18next'; -import { useEffect, useMemo, useState } from 'react'; -import MyIcon from '@fastgpt/web/components/common/Icon'; -import { - deleteEvalItem, - getEvalItemsList, - retryEvalItem, - updateEvalItem -} from '@/web/core/app/api/evaluation'; -import { usePagination } from '@fastgpt/web/hooks/usePagination'; -import { downloadFetch } from '@/web/common/system/utils'; -import PopoverConfirm from '@fastgpt/web/components/common/MyPopover/PopoverConfirm'; -import { type TFunction } from 'i18next'; -import EmptyTip from '@fastgpt/web/components/common/EmptyTip'; -import { useForm } from 'react-hook-form'; -import { - EvaluationStatusMap, - EvaluationStatusEnum -} from '@fastgpt/global/core/app/evaluation/constants'; -import type { evaluationType, listEvalItemsItem } from '@fastgpt/global/core/app/evaluation/type'; -import type { updateEvalItemBody } from '@fastgpt/global/core/app/evaluation/api'; -import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; - -const formatEvaluationStatus = (item: { status: number; errorMessage?: string }, t: TFunction) => { - if (item.errorMessage) { - return ( - - {t('dashboard_evaluation:error')} - - ); - } - - const statusConfig = { - [EvaluationStatusEnum.queuing]: { - color: 'myGray.500', - key: t('dashboard_evaluation:queuing') - }, - [EvaluationStatusEnum.evaluating]: { - color: 'primary.600', - key: t('dashboard_evaluation:evaluating') - }, - [EvaluationStatusEnum.completed]: { - color: 'green.600', - key: t('dashboard_evaluation:completed') - } - }; - - const config = statusConfig[item.status as keyof typeof statusConfig] || null; - if (!config) return null; - - return ( - - {config.key} - - ); -}; - -const EvaluationDetailModal = ({ - evalDetail, - onClose, - fetchEvalList -}: { - evalDetail: evaluationType; - onClose: () => void; - fetchEvalList: () => void; -}) => { - const { t } = useTranslation(); - const [selectedIndex, setSelectedIndex] = useState(0); - const [editing, setEditing] = useState(false); - const [pollingInterval, setPollingInterval] = useState(10000); - - const { llmModelList } = useSystemStore(); - const modelData = useMemo( - () => getModelFromList(llmModelList, evalDetail.evalModel), - [evalDetail.evalModel, llmModelList] - ); - - const { - data: evalItemsList, - Pagination, - pageSize, - total, - getData: fetchData - } = usePagination(getEvalItemsList, { - defaultPageSize: 20, - params: { - evalId: evalDetail._id - }, - pollingInterval - }); - - useEffect(() => { - const hasRunningOrErrorTasks = evalItemsList.some((item) => { - return ( - item.status === EvaluationStatusEnum.evaluating || - item.status === EvaluationStatusEnum.queuing || - !!item.errorMessage - ); - }); - setPollingInterval(hasRunningOrErrorTasks ? 10000 : 0); - }, [evalItemsList]); - - const evalItem = evalItemsList[selectedIndex]; - - const statusMap = useMemo( - () => - Object.fromEntries( - Object.entries(EvaluationStatusMap).map(([key, config]) => [ - key, - { label: t(config.name as any) } - ]) - ), - [t] - ); - - const { runAsync: exportEval, loading: isDownloading } = useRequest2(async () => { - await downloadFetch({ - url: `/api/proApi/core/app/evaluation/exportItems?evalId=${evalDetail._id}`, - filename: `${evalDetail.name}.csv`, - body: { - title: t('dashboard_evaluation:evaluation_export_title'), - statusMap - } - }); - }); - - const { runAsync: delEvalItem, loading: isLoadingDelete } = useRequest2(deleteEvalItem, { - onSuccess: () => { - fetchData(); - fetchEvalList(); - } - }); - - const { runAsync: rerunItem, loading: isLoadingRerun } = useRequest2(retryEvalItem, { - onSuccess: () => { - fetchData(); - fetchEvalList(); - } - }); - - const { runAsync: updateItem, loading: isLoadingUpdate } = useRequest2( - async (data: updateEvalItemBody) => { - await updateEvalItem({ ...data, evalItemId: evalItem.evalItemId }); - }, - { - onSuccess: () => { - fetchData(); - fetchEvalList(); - } - } - ); - - const { register, handleSubmit } = useForm(); - - return ( - <> - - - {/* Summary */} - - - - {t('dashboard_evaluation:task_name')} - - - {evalDetail?.name} - - - - - {t('dashboard_evaluation:Evaluation_model')} - - - - - {modelData?.name} - - - - - - {t('dashboard_evaluation:Evaluation_app')} - - - - - {evalDetail?.appName} - - - - - - {t('dashboard_evaluation:Progress')} - - - {evalDetail?.completedCount} - {`/${evalDetail?.totalCount}`} - {evalDetail?.errorMessage && ( - - - - {t('dashboard_evaluation:paused')} - - - - - )} - - - - - {t('dashboard_evaluation:Overall_score')} - - - {typeof evalDetail.score === 'number' ? (evalDetail.score * 100).toFixed(2) : '-'} - - - - - - - - - - {`${t('dashboard_evaluation:data_list')}: ${evalDetail?.totalCount}`} - - - - - - - - - {t('dashboard_evaluation:detail')} - - - {evalItem && ( - - {editing ? ( - - ) : ( - <> - {(evalItem.status === EvaluationStatusEnum.queuing || - !!evalItem.errorMessage) && ( - } - onClick={() => { - setEditing(true); - }} - /> - )} - {!!evalItem.errorMessage && ( - } - isLoading={isLoadingRerun} - onClick={() => { - rerunItem({ - evalItemId: evalItem.evalItemId - }); - }} - /> - )} - {(evalItem.status === EvaluationStatusEnum.queuing || - !!evalItem.errorMessage) && ( - } - isLoading={isLoadingDelete} - /> - } - type="delete" - content={t('dashboard_evaluation:comfirm_delete_item')} - onConfirm={() => delEvalItem({ evalItemId: evalItem.evalItemId })} - /> - )} - - )} - - )} - - - - - - - - {t('dashboard_evaluation:question')} - - - {t('dashboard_evaluation:stauts')} - - - {t('dashboard_evaluation:Overall_score')} - - - - - {evalItemsList.map((item: listEvalItemsItem, index: number) => { - const formattedStatus = formatEvaluationStatus(item, t); - - return ( - { - setSelectedIndex(index); - setEditing(false); - }} - > - - - - {index < 9 ? `0${index + 1}` : index + 1} - - - {item.question} - - - - - {formattedStatus} - - - {typeof item.score === 'number' ? (item.score * 100).toFixed(2) : '-'} - - - ); - })} - - - {total >= pageSize && ( - - - - )} - - - {evalItem ? ( - - {!editing && evalItem?.errorMessage && ( - - {evalItem?.errorMessage} - - )} - {Object.keys(evalItem?.globalVariables || {}).length > 0 && ( - - - - - - {t('dashboard_evaluation:variables')} - - - - - {Object.entries(evalItem?.globalVariables || {}).map( - ([key, value], index, arr) => ( - - - {key} - - - {editing ? ( - - ) : ( - - {value} - - )} - - - ) - )} - - - - - )} - - {t('dashboard_evaluation:question')} - {editing ? ( -