chore: replace all cn comments of fe to en version by volc api (#320)
This commit is contained in:
@@ -4,15 +4,15 @@ const path = require('path');
|
||||
|
||||
const config = [
|
||||
{
|
||||
idlRoot: '../../../../opencoze', // idl 根目录
|
||||
idlRoot: '../../../../opencoze', // IDL root directory
|
||||
entries: {
|
||||
passport: './idl/passport/passport.thrift', // 入口服务名称及路径
|
||||
passport: './idl/passport/passport.thrift', // Entry service name and path
|
||||
explore:
|
||||
'./idl/flow/marketplace/flow_marketplace_product/public_api.thrift',
|
||||
},
|
||||
commonCodePath: path.resolve(__dirname, './src/api/config.ts'), // 自定义配置文件
|
||||
output: './src', // 产物所在位置
|
||||
plugins: [], // 自定义插件
|
||||
commonCodePath: path.resolve(__dirname, './src/api/config.ts'), // custom profile
|
||||
output: './src', // Product location
|
||||
plugins: [], // custom plugin
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -22,36 +22,36 @@ export enum ProductEntityType {
|
||||
/** Workflow = 3 , */
|
||||
SocialScene = 4,
|
||||
Project = 6,
|
||||
/** 历史工作流,后续不会再有(废弃) */
|
||||
/** History workflow, no more in the future (abandoned) */
|
||||
WorkflowTemplate = 13,
|
||||
/** 历史图像流模板,后续不会再有(废弃) */
|
||||
/** Historical image stream template, no more in the future (obsolete) */
|
||||
ImageflowTemplate = 15,
|
||||
/** 模板通用标识,仅用于绑定模板相关的配置,不绑定商品 */
|
||||
/** Template universal identification, only used to bind template-related configurations, not bind products */
|
||||
TemplateCommon = 20,
|
||||
/** Bot 模板 */
|
||||
/** Bot template */
|
||||
BotTemplate = 21,
|
||||
/** 工作流模板 */
|
||||
/** workflow template */
|
||||
WorkflowTemplateV2 = 23,
|
||||
/** 图像流模板(该类型已下线,合并入 workflow,但历史数据会保留,前端视作 workflow 展示) */
|
||||
/** Image stream template (this type has been offline and merged into workflow, but historical data will be preserved, and the front end will be treated as workflow display) */
|
||||
ImageflowTemplateV2 = 25,
|
||||
/** 项目模板 */
|
||||
/** project template */
|
||||
ProjectTemplate = 26,
|
||||
/** coze token 类商品,理论上只会有一个 */
|
||||
/** Coze token products, theoretically there will only be one */
|
||||
CozeToken = 50,
|
||||
/** 订阅 credit 的流量包,理论上只会有一个 */
|
||||
/** Subscribe to the traffic package of credit, theoretically there will only be one */
|
||||
MsgCredit = 55,
|
||||
/** 消息订阅类商品,理论上只有一个 */
|
||||
/** There is only one subscription product in theory */
|
||||
SubsMsgCredit = 60,
|
||||
Common = 99,
|
||||
/** 专题(兼容之前的设计) */
|
||||
/** Special Topics (Compatible with previous designs) */
|
||||
Topic = 101,
|
||||
}
|
||||
export enum SortType {
|
||||
Heat = 1,
|
||||
Newest = 2,
|
||||
/** 收藏时间 */
|
||||
/** collection time */
|
||||
FavoriteTime = 3,
|
||||
/** 相关性,只用于搜索场景 */
|
||||
/** Correlation, only for search scenarios */
|
||||
Relative = 4,
|
||||
}
|
||||
export enum ProductPublishMode {
|
||||
@@ -59,9 +59,9 @@ export enum ProductPublishMode {
|
||||
ClosedSource = 2,
|
||||
}
|
||||
export enum ProductListSource {
|
||||
/** 推荐列表页 */
|
||||
/** recommended list page */
|
||||
Recommend = 1,
|
||||
/** 个性化推荐 */
|
||||
/** personalized recommendation */
|
||||
CustomizedRecommend = 2,
|
||||
}
|
||||
export enum PluginType {
|
||||
@@ -77,7 +77,7 @@ export interface CommercialSetting {
|
||||
commercial_type: ProductPaidType
|
||||
}
|
||||
export enum ProductStatus {
|
||||
/** 从未上架 */
|
||||
/** It never hit the shelves. */
|
||||
NeverListed = 0,
|
||||
Listed = 1,
|
||||
Unlisted = 2,
|
||||
@@ -103,20 +103,20 @@ export interface ImageInfo {
|
||||
url: string,
|
||||
}
|
||||
export enum ProductDraftStatus {
|
||||
/** 默认 */
|
||||
/** default */
|
||||
Default = 0,
|
||||
/** 审核中 */
|
||||
/** Under review. */
|
||||
Pending = 1,
|
||||
/** 审核通过 */
|
||||
/** approved */
|
||||
Approved = 2,
|
||||
/** 审核不通过 */
|
||||
/** The review failed. */
|
||||
Rejected = 3,
|
||||
/** 已废弃 */
|
||||
/** Abandoned */
|
||||
Abandoned = 4,
|
||||
}
|
||||
export type AuditStatus = ProductDraftStatus;
|
||||
export interface OpeningDialog {
|
||||
/** Bot开场白 */
|
||||
/** Bot opening line */
|
||||
content: string
|
||||
}
|
||||
export enum InputType {
|
||||
@@ -131,27 +131,27 @@ export enum PluginParamTypeFormat {
|
||||
ImageUrl = 1,
|
||||
}
|
||||
export enum WorkflowNodeType {
|
||||
/** 开始 */
|
||||
/** start */
|
||||
Start = 1,
|
||||
/** 结束 */
|
||||
/** end */
|
||||
End = 2,
|
||||
/** 大模型 */
|
||||
/** Large model */
|
||||
LLM = 3,
|
||||
/** 插件 */
|
||||
/** plugin */
|
||||
Api = 4,
|
||||
/** 代码 */
|
||||
/** code */
|
||||
Code = 5,
|
||||
/** 知识库 */
|
||||
/** Knowledge Base */
|
||||
Dataset = 6,
|
||||
/** 选择器 */
|
||||
/** selector */
|
||||
If = 8,
|
||||
/** 工作流 */
|
||||
/** Workflow */
|
||||
SubWorkflow = 9,
|
||||
/** 变量 */
|
||||
/** variable */
|
||||
Variable = 11,
|
||||
/** 数据库 */
|
||||
/** database */
|
||||
Database = 12,
|
||||
/** 消息 */
|
||||
/** message */
|
||||
Message = 13,
|
||||
}
|
||||
export enum SocialSceneRoleType {
|
||||
@@ -161,11 +161,11 @@ export enum SocialSceneRoleType {
|
||||
}
|
||||
export enum UIPreviewType {
|
||||
/**
|
||||
* UI 预览类型,定义对齐 UI Builder,目前用于 Project
|
||||
* 网页端
|
||||
* UI preview type, defining alignment UI Builder, currently used in Project
|
||||
* web page
|
||||
*/
|
||||
Web = 1,
|
||||
/** 移动端 */
|
||||
/** mobile end */
|
||||
Client = 2,
|
||||
}
|
||||
export interface ChargeSKUExtra {
|
||||
@@ -173,7 +173,7 @@ export interface ChargeSKUExtra {
|
||||
is_self_define: boolean,
|
||||
}
|
||||
export enum FavoriteListSource {
|
||||
/** 用户自己创建的 */
|
||||
/** Created by users themselves */
|
||||
CreatedByMe = 1,
|
||||
}
|
||||
export interface FavoriteEntity {
|
||||
@@ -182,13 +182,13 @@ export interface FavoriteEntity {
|
||||
name: string,
|
||||
icon_url: string,
|
||||
description: string,
|
||||
/** 废弃,使用UserInfo */
|
||||
/** Abandoned, using UserInfo */
|
||||
seller: SellerInfo,
|
||||
/** 用于跳转到Bot编辑页 */
|
||||
/** Use to jump to the bot edit page */
|
||||
space_id: string,
|
||||
/** 用户是否有该实体所在Space的权限 */
|
||||
/** Does the user have permissions to the space where the entity is located? */
|
||||
has_space_permission: boolean,
|
||||
/** 收藏时间 */
|
||||
/** collection time */
|
||||
favorite_at: string,
|
||||
product_extra?: FavoriteProductExtra,
|
||||
user_info: UserInfo,
|
||||
|
||||
@@ -17,20 +17,20 @@
|
||||
import * as base from './../../base';
|
||||
export { base };
|
||||
export interface Price {
|
||||
/** 金额 */
|
||||
/** amount */
|
||||
amount: string,
|
||||
/** 币种,如USD、CNY */
|
||||
/** Currencies such as USD and CNY */
|
||||
currency: string,
|
||||
/** 小数位数 */
|
||||
/** decimal places */
|
||||
decimal_num: number,
|
||||
}
|
||||
export enum FollowType {
|
||||
/** 无关系 */
|
||||
/** It doesn't matter. */
|
||||
Unknown = 0,
|
||||
/** 关注 */
|
||||
/** follow */
|
||||
Followee = 1,
|
||||
/** 粉丝 */
|
||||
/** fan */
|
||||
Follower = 2,
|
||||
/** 互相关注 */
|
||||
/** Follow each other */
|
||||
MutualFollow = 3,
|
||||
}
|
||||
@@ -19,7 +19,7 @@ export interface AppUserInfo {
|
||||
user_unique_name: string
|
||||
}
|
||||
export interface User {
|
||||
/** 与原接口字段名对齐 */
|
||||
/** Align with the original interface field name */
|
||||
user_id_str: string,
|
||||
name: string,
|
||||
user_unique_name: string,
|
||||
@@ -92,7 +92,7 @@ export interface UserUpdateProfileResponse {
|
||||
code: number,
|
||||
msg: string,
|
||||
}
|
||||
/** 邮箱密码注册 */
|
||||
/** Email password registration */
|
||||
export const PassportWebEmailRegisterV2Post = /*#__PURE__*/createAPI<PassportWebEmailRegisterV2PostRequest, PassportWebEmailRegisterV2PostResponse>({
|
||||
"url": "/api/passport/web/email/register/v2/",
|
||||
"method": "POST",
|
||||
@@ -105,7 +105,7 @@ export const PassportWebEmailRegisterV2Post = /*#__PURE__*/createAPI<PassportWeb
|
||||
"schemaRoot": "api://schemas/idl_passport_passport",
|
||||
"service": "passport"
|
||||
});
|
||||
/** 退出登录 */
|
||||
/** log out */
|
||||
export const PassportWebLogoutGet = /*#__PURE__*/createAPI<PassportWebLogoutGetRequest, PassportWebLogoutGetResponse>({
|
||||
"url": "/api/passport/web/logout/",
|
||||
"method": "GET",
|
||||
@@ -116,7 +116,7 @@ export const PassportWebLogoutGet = /*#__PURE__*/createAPI<PassportWebLogoutGetR
|
||||
"schemaRoot": "api://schemas/idl_passport_passport",
|
||||
"service": "passport"
|
||||
});
|
||||
/** 邮箱帐密登录 */
|
||||
/** Email account password login */
|
||||
export const PassportWebEmailLoginPost = /*#__PURE__*/createAPI<PassportWebEmailLoginPostRequest, PassportWebEmailLoginPostResponse>({
|
||||
"url": "/api/passport/web/email/login/",
|
||||
"method": "POST",
|
||||
@@ -129,7 +129,7 @@ export const PassportWebEmailLoginPost = /*#__PURE__*/createAPI<PassportWebEmail
|
||||
"schemaRoot": "api://schemas/idl_passport_passport",
|
||||
"service": "passport"
|
||||
});
|
||||
/** 通过邮箱重置密码 */
|
||||
/** Reset password via email */
|
||||
export const PassportWebEmailPasswordResetGet = /*#__PURE__*/createAPI<PassportWebEmailPasswordResetGetRequest, PassportWebEmailPasswordResetGetResponse>({
|
||||
"url": "/api/passport/web/email/password/reset/",
|
||||
"method": "GET",
|
||||
@@ -142,7 +142,7 @@ export const PassportWebEmailPasswordResetGet = /*#__PURE__*/createAPI<PassportW
|
||||
"schemaRoot": "api://schemas/idl_passport_passport",
|
||||
"service": "passport"
|
||||
});
|
||||
/** 账号信息 */
|
||||
/** account information */
|
||||
export const PassportAccountInfoV2 = /*#__PURE__*/createAPI<PassportAccountInfoV2Request, PassportAccountInfoV2Response>({
|
||||
"url": "/api/passport/account/info/v2/",
|
||||
"method": "POST",
|
||||
|
||||
@@ -18,7 +18,7 @@ import { vi, describe, it, expect, beforeAll, beforeEach } from 'vitest';
|
||||
import { Toast } from '@coze-arch/bot-semi';
|
||||
import { axiosInstance, isApiError, ApiError } from '@coze-arch/bot-http';
|
||||
|
||||
// 导入 axios 配置以触发 Toast 配置
|
||||
// Import axios configuration to trigger Toast configuration
|
||||
import '../src/axios';
|
||||
|
||||
vi.mock('@coze-arch/bot-semi', () => ({
|
||||
@@ -28,9 +28,9 @@ vi.mock('@coze-arch/bot-semi', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
// 模拟 isApiError 函数
|
||||
// emulating the isApiError function
|
||||
vi.mock('@coze-arch/bot-http', () => {
|
||||
// 保存原始的 axiosInstance.interceptors.response.use 方法
|
||||
// Save the original axiosInstant.interceptors.response.use method
|
||||
const originalUse = vi.fn();
|
||||
|
||||
return {
|
||||
@@ -60,13 +60,13 @@ describe('axios configuration', () => {
|
||||
let onRejected: Function;
|
||||
|
||||
beforeAll(async () => {
|
||||
// 导入 axios 配置以触发 Toast 配置和拦截器注册
|
||||
// Import axios configuration to trigger Toast configuration and blocker registration
|
||||
await import('../src/axios');
|
||||
|
||||
// 验证 Toast.config 被调用
|
||||
// Verify that Toast.config is called
|
||||
expect(Toast.config).toHaveBeenCalledWith({ top: 80 });
|
||||
|
||||
// 获取注册的拦截器函数
|
||||
// Get the registered interceptor function
|
||||
const useArgs = (axiosInstance.interceptors.response.use as any).mock
|
||||
.calls[0];
|
||||
onFulfilled = useArgs[0];
|
||||
@@ -88,15 +88,15 @@ describe('axios configuration', () => {
|
||||
});
|
||||
|
||||
it('should show error toast when API error occurs', () => {
|
||||
// 创建一个 API 错误
|
||||
// Create an API error
|
||||
const apiError = new (ApiError as any)('500', 'API Error');
|
||||
|
||||
// 模拟 isApiError 返回 true
|
||||
// isApiError returns true
|
||||
(isApiError as any).mockReturnValue(true);
|
||||
|
||||
try {
|
||||
onRejected(apiError);
|
||||
// 如果没有抛出错误,测试应该失败
|
||||
// If no errors are thrown, the test should fail
|
||||
expect(true).toBe(false);
|
||||
} catch (error) {
|
||||
expect(isApiError).toHaveBeenCalledWith(apiError);
|
||||
@@ -109,16 +109,16 @@ describe('axios configuration', () => {
|
||||
});
|
||||
|
||||
it('should not show error toast when __disableErrorToast is true', () => {
|
||||
// 创建一个 API 错误,并设置 __disableErrorToast 为 true
|
||||
// Create an API error and set __disableErrorToast to true
|
||||
const apiError = new (ApiError as any)('401', 'Unauthorized');
|
||||
apiError.config.__disableErrorToast = true;
|
||||
|
||||
// 模拟 isApiError 返回 true
|
||||
// isApiError returns true
|
||||
(isApiError as any).mockReturnValue(true);
|
||||
|
||||
try {
|
||||
onRejected(apiError);
|
||||
// 如果没有抛出错误,测试应该失败
|
||||
// If no errors are thrown, the test should fail
|
||||
expect(true).toBe(false);
|
||||
} catch (error) {
|
||||
expect(isApiError).toHaveBeenCalledWith(apiError);
|
||||
@@ -128,15 +128,15 @@ describe('axios configuration', () => {
|
||||
});
|
||||
|
||||
it('should not show error toast when error has no message', () => {
|
||||
// 创建一个没有消息的 API 错误
|
||||
// Create an API error with no message
|
||||
const apiError = new (ApiError as any)('403', undefined);
|
||||
|
||||
// 模拟 isApiError 返回 true
|
||||
// isApiError returns true
|
||||
(isApiError as any).mockReturnValue(true);
|
||||
|
||||
try {
|
||||
onRejected(apiError);
|
||||
// 如果没有抛出错误,测试应该失败
|
||||
// If no errors are thrown, the test should fail
|
||||
expect(true).toBe(false);
|
||||
} catch (error) {
|
||||
expect(isApiError).toHaveBeenCalledWith(apiError);
|
||||
@@ -146,16 +146,16 @@ describe('axios configuration', () => {
|
||||
});
|
||||
|
||||
it('should not show error toast when isApiError returns false', () => {
|
||||
// 创建一个普通错误
|
||||
// Create a normal error
|
||||
const regularError = new Error('Regular Error');
|
||||
(regularError as any).msg = 'Error message';
|
||||
|
||||
// 模拟 isApiError 返回 false
|
||||
// isApiError returned false
|
||||
(isApiError as any).mockReturnValue(false);
|
||||
|
||||
try {
|
||||
onRejected(regularError);
|
||||
// 如果没有抛出错误,测试应该失败
|
||||
// If no errors are thrown, the test should fail
|
||||
expect(true).toBe(false);
|
||||
} catch (error) {
|
||||
expect(isApiError).toHaveBeenCalledWith(regularError);
|
||||
@@ -165,10 +165,10 @@ describe('axios configuration', () => {
|
||||
});
|
||||
|
||||
it('should handle null or undefined error', () => {
|
||||
// 测试 null 错误
|
||||
// Test null error
|
||||
try {
|
||||
onRejected(null);
|
||||
// 如果没有抛出错误,测试应该失败
|
||||
// If no errors are thrown, the test should fail
|
||||
expect(true).toBe(false);
|
||||
} catch (error) {
|
||||
expect(isApiError).toHaveBeenCalledWith(null);
|
||||
@@ -178,10 +178,10 @@ describe('axios configuration', () => {
|
||||
|
||||
vi.clearAllMocks();
|
||||
|
||||
// 测试 undefined 错误
|
||||
// Test undefined error
|
||||
try {
|
||||
onRejected(undefined);
|
||||
// 如果没有抛出错误,测试应该失败
|
||||
// If no errors are thrown, the test should fail
|
||||
expect(true).toBe(false);
|
||||
} catch (error) {
|
||||
expect(isApiError).toHaveBeenCalledWith(undefined);
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
import { vi } from 'vitest';
|
||||
|
||||
import { basicApi } from '../src/basic-api';
|
||||
@@ -52,7 +52,7 @@ describe('basic-api', () => {
|
||||
headers: { 'Another-Header': 'another-value' },
|
||||
};
|
||||
|
||||
// @ts-expect-error - 我们知道这是一个有效的方法调用
|
||||
// @ts-expect-error - we know this is a valid method call
|
||||
await basicApi.request(mockParams, mockConfig);
|
||||
|
||||
expect(axiosInstance.request).toHaveBeenCalledWith({
|
||||
@@ -76,7 +76,7 @@ describe('basic-api', () => {
|
||||
headers: { 'Custom-Header': 'value' },
|
||||
};
|
||||
|
||||
// @ts-expect-error - 我们知道这是一个有效的方法调用
|
||||
// @ts-expect-error - we know this is a valid method call
|
||||
await basicApi.request(mockParams);
|
||||
|
||||
expect(axiosInstance.request).toHaveBeenCalledWith({
|
||||
@@ -97,7 +97,7 @@ describe('basic-api', () => {
|
||||
method: 'GET',
|
||||
};
|
||||
|
||||
// @ts-expect-error - 我们知道这是一个有效的方法调用
|
||||
// @ts-expect-error - we know this is a valid method call
|
||||
await basicApi.request(mockParams);
|
||||
|
||||
expect(axiosInstance.request).toHaveBeenCalledWith({
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
type AxiosRequestConfig,
|
||||
} from '@coze-arch/bot-http';
|
||||
|
||||
// Toast展示位置离top 80px
|
||||
// Toast display 80px from the top
|
||||
Toast.config({
|
||||
top: 80,
|
||||
});
|
||||
@@ -32,7 +32,7 @@ interface CustomAxiosConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* 业务自定义 axios 配置
|
||||
* Business custom axios configuration
|
||||
* @param __disableErrorToast default: false
|
||||
*/
|
||||
export type BotAPIRequestConfig = AxiosRequestConfig & CustomAxiosConfig;
|
||||
@@ -40,7 +40,7 @@ export type BotAPIRequestConfig = AxiosRequestConfig & CustomAxiosConfig;
|
||||
axiosInstance.interceptors.response.use(
|
||||
response => response.data,
|
||||
error => {
|
||||
// 业务逻辑
|
||||
// business logic
|
||||
if (
|
||||
isApiError(error) &&
|
||||
error.msg &&
|
||||
|
||||
@@ -39,7 +39,7 @@ const updateDTS = ({
|
||||
envVarName,
|
||||
outputFileName,
|
||||
}: TUpdateDTSParams) => {
|
||||
// 初始化一个 ts-morph 项目
|
||||
// Initialize a ts-morph project
|
||||
const project = new Project({
|
||||
compilerOptions: {
|
||||
incremental: true,
|
||||
@@ -49,20 +49,20 @@ const updateDTS = ({
|
||||
noEmitOnError: true,
|
||||
},
|
||||
});
|
||||
// 添加想要解析的文件
|
||||
// Add the file you want to parse
|
||||
const file = project.addSourceFileAtPath(inputFileName);
|
||||
|
||||
// 获取你想要解析的变量
|
||||
// Get the variable you want to parse
|
||||
const envs = file.getVariableDeclarationOrThrow(envVarName);
|
||||
// 获取 envs 变量的初始值
|
||||
// Get the initial value of the envs variable
|
||||
const initializer = envs.getInitializerIfKindOrThrow(
|
||||
SyntaxKind.ObjectLiteralExpression,
|
||||
);
|
||||
// 获取 envs 对象的属性
|
||||
// Get the properties of the envs object
|
||||
const properties = initializer.getProperties();
|
||||
|
||||
const baseDir = path.resolve(__dirname, '../');
|
||||
// 创建一个新的文件,用来保存生成的类型定义
|
||||
// Create a new file to hold the generated type definition
|
||||
const typeDefs = project.createSourceFile(
|
||||
outputFileName,
|
||||
`/*
|
||||
@@ -82,7 +82,7 @@ const updateDTS = ({
|
||||
*/
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// 基于${path.relative(baseDir, inputFileName)}自动生成,请勿手动修改`,
|
||||
// Automatically generated based on ${path.relative(baseDir, inputFileName)}, do not modify manually `,
|
||||
{
|
||||
overwrite: true,
|
||||
},
|
||||
@@ -97,7 +97,7 @@ const updateDTS = ({
|
||||
});
|
||||
};
|
||||
|
||||
// 遍历每一个属性
|
||||
// Iterate through each attribute
|
||||
properties.forEach(property => {
|
||||
if (
|
||||
property instanceof PropertyAssignment ||
|
||||
@@ -109,9 +109,9 @@ const updateDTS = ({
|
||||
const type = expression.getType();
|
||||
|
||||
if (type.isObject()) {
|
||||
// 如果类型是一个对象类型,获取其属性
|
||||
// If the type is an object type, obtain its properties
|
||||
const spreadProperties = type.getProperties();
|
||||
// 遍历属性
|
||||
// traversal properties
|
||||
for (const spreadProperty of spreadProperties) {
|
||||
const declaration = spreadProperty.getDeclarations()?.[0];
|
||||
if (declaration) {
|
||||
@@ -129,7 +129,7 @@ const updateDTS = ({
|
||||
}
|
||||
}
|
||||
});
|
||||
// 保存文件
|
||||
// Save file
|
||||
typeDefs.addVariableStatements(
|
||||
declarations
|
||||
.sort((a, b) => (a.name > b.name ? 1 : -1))
|
||||
|
||||
@@ -29,21 +29,21 @@ const processEnvs = {
|
||||
REGION: (process.env.REGION || 'cn') as 'cn' | 'sg' | 'va',
|
||||
NODE_ENV: process.env.NODE_ENV as 'production' | 'development' | 'test',
|
||||
CDN_PATH_PREFIX: (process.env.CDN_PATH_PREFIX ?? '/') as string,
|
||||
// vmok 生产者使用,用来将生产者的 sourcemap 文件上传至对应的消费者版本下面
|
||||
// Used by vmok producers to upload the producer's sourcemap file to the corresponding consumer version
|
||||
CONSUMER_BUILD_VERSION: (process.env.CONSUMER_BUILD_VERSION ?? '') as string,
|
||||
};
|
||||
|
||||
const IS_OVERSEA = Boolean(process.env.REGION) && process.env.REGION !== 'cn';
|
||||
const IS_CN_REGION = process.env.REGION === 'cn';
|
||||
const IS_VA_REGION = process.env.REGION === 'va';
|
||||
const IS_RELEASE_VERSION = processEnvs.CUSTOM_VERSION === 'release'; // 为 ture 表示对外版本
|
||||
const IS_RELEASE_VERSION = processEnvs.CUSTOM_VERSION === 'release'; // Show external version for ture
|
||||
const IS_OVERSEA_RELEASE = IS_OVERSEA && IS_RELEASE_VERSION;
|
||||
const IS_PROD =
|
||||
processEnvs.BUILD_TYPE === 'online' ||
|
||||
process.env.CUSTOM_BUILD_TYPE === 'online'; // 是否是线上
|
||||
process.env.CUSTOM_BUILD_TYPE === 'online'; // Is it online?
|
||||
const IS_BOE = processEnvs.BUILD_TYPE === 'offline';
|
||||
const IS_DEV_MODE = processEnvs.NODE_ENV === 'development'; // 本地开发
|
||||
const IS_BOT_OP = false; // 是否是 bot 运营平台,默认都是 false,从运营平台构建会设置成 true
|
||||
const IS_DEV_MODE = processEnvs.NODE_ENV === 'development'; // local development
|
||||
const IS_BOT_OP = false; // Whether it is a bot operation platform, the default is false, and the build from the operation platform will be set to true.
|
||||
const IS_OPEN_SOURCE = (process.env.IS_OPEN_SOURCE ?? 'false') === 'true';
|
||||
|
||||
const judgements = {
|
||||
@@ -74,14 +74,14 @@ const getCDN = () => {
|
||||
}
|
||||
}
|
||||
if (IS_RELEASE_VERSION && processEnvs.BUILD_TYPE === 'online') {
|
||||
// 海外正式版使用独立业务线
|
||||
// Overseas official version uses independent business lines
|
||||
return getOuterCDN();
|
||||
} else {
|
||||
return getInnerCDN();
|
||||
}
|
||||
};
|
||||
|
||||
/** 对应CDN资源上传平台上的CDN地址 */
|
||||
/** Corresponding to the CDN address on the CDN resource upload platform */
|
||||
const getUploadCDN = () => {
|
||||
const uploadCDNPrefixes = {
|
||||
UPLOAD_CDN_CN: '',
|
||||
@@ -101,7 +101,7 @@ export const base = {
|
||||
...processEnvs,
|
||||
...judgements,
|
||||
CDN: getCDN(),
|
||||
// release 环境下外部静态域名
|
||||
// External static domain name in the release environment
|
||||
OUTER_CDN: getOuterCDN(),
|
||||
...getUploadCDN(),
|
||||
};
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable max-lines -- 待拆分 */
|
||||
|
||||
/* eslint-disable max-lines -- to be split */
|
||||
|
||||
import { extractEnvValue } from './utils/config-helper';
|
||||
import { volcanoConfigs } from './configs/volcano';
|
||||
@@ -98,9 +98,9 @@ const SEC_SDK_ASSERT_URL = extractEnvValue<string | null>({
|
||||
release: null,
|
||||
},
|
||||
sg: {
|
||||
// 非 release 环境使用默认内网公用 SCM, tos 无上传权限,也无脱敏需要,直接使用风控提供的 tos 地址
|
||||
// The non-release environment uses the default intranet public SCM, tos has no upload permission and no desensitization need. Directly use the tos address provided by risk control
|
||||
inhouse: '',
|
||||
// release 上传至独立 SCM CDN
|
||||
// Release and upload to the independent SCM CDN
|
||||
release: '',
|
||||
},
|
||||
va: {
|
||||
@@ -239,7 +239,7 @@ const BYTE_UPLOADER_REGION = extractEnvValue<
|
||||
| 'gcp'
|
||||
>({
|
||||
cn: {
|
||||
// TODO 确认下这里
|
||||
// TODO confirm here.
|
||||
boe: 'boe',
|
||||
inhouse: 'cn-north-1',
|
||||
release: 'cn-north-1',
|
||||
@@ -255,7 +255,7 @@ const BYTE_UPLOADER_REGION = extractEnvValue<
|
||||
|
||||
const IMAGE_FALLBACK_HOST = extractEnvValue<string>({
|
||||
cn: {
|
||||
// TODO 确认下这里
|
||||
// TODO confirm here.
|
||||
boe: '',
|
||||
inhouse: '',
|
||||
release: '',
|
||||
@@ -299,7 +299,7 @@ const GOOGLE_PLATFORM_ID = extractEnvValue<null | number>({
|
||||
},
|
||||
});
|
||||
|
||||
// 曾经计划 facebook 登录 然后需求变动后没有了
|
||||
// I used to plan to log in to Facebook, but after the requirements changed, it was gone.
|
||||
const FACEBOOK_APP_ID = extractEnvValue<null | string>({
|
||||
cn: {
|
||||
boe: null,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import { base } from './base';
|
||||
const { IS_RELEASE_VERSION, IS_OVERSEA, IS_BOE } = base;
|
||||
export const features = {
|
||||
// 与志强&产品沟通后,下掉boe环境的sso
|
||||
// After communicating with Zhiqiang & products, remove the sso of boe environment.
|
||||
FEATURE_ENABLE_SSO: !IS_RELEASE_VERSION && !IS_BOE,
|
||||
FEATURE_ENABLE_APP_GUIDE: !IS_RELEASE_VERSION || IS_OVERSEA,
|
||||
FEATURE_ENABLE_FEEDBACK_MAILTO: IS_RELEASE_VERSION,
|
||||
@@ -27,47 +27,47 @@ export const features = {
|
||||
// FEATURE_ENABLE_RUYI_CARD: false,
|
||||
FEATURE_ENABLE_VARIABLE: false,
|
||||
/**
|
||||
* 是否开启新的注销流程,目前只有cn开启
|
||||
* Whether to start a new cancellation process? Currently only cn is open.
|
||||
*/
|
||||
FEATURE_ENABLE_NEW_DELETE_ACCOUNT: !IS_OVERSEA,
|
||||
FEATURE_AWEME_LOGIN: !IS_OVERSEA,
|
||||
FEATURE_GOOGLE_LOGIN: IS_OVERSEA,
|
||||
|
||||
/**
|
||||
* @description 只在boe环境和inhouse-cn环境支持 workflow code 节点编辑 python 代码
|
||||
* @Description Only supports workflow code node editing python code in boe environment and inhouse-cn environment
|
||||
*/
|
||||
FEATURE_ENABLE_CODE_PYTHON: !IS_OVERSEA && !IS_RELEASE_VERSION,
|
||||
|
||||
/**
|
||||
* 暂时隐藏banner,后续可能用于运营位置
|
||||
* Temporarily hide the banner, it may be used later to operate the location
|
||||
*/
|
||||
FEATURE_ENABLE_BANNER: false,
|
||||
|
||||
/**
|
||||
* Database tooltip示例区分图海外和国内
|
||||
* Database tooltip example distinguishes between overseas and domestic
|
||||
*/
|
||||
FEATURE_ENABLE_DATABASE_TABLE: !IS_OVERSEA,
|
||||
|
||||
/**
|
||||
* bot市场中国区入口
|
||||
* Bot Market China Entrance
|
||||
*/
|
||||
FEATURE_ENABLE_BOT_STORE: true,
|
||||
/**
|
||||
* workflow llm 计费只在海外或者 in-house 显示
|
||||
* Workflow llm billing is only displayed overseas or in-house.
|
||||
*/
|
||||
FEATURE_ENABLE_WORKFLOW_LLM_PAYMENT: IS_OVERSEA || !IS_RELEASE_VERSION,
|
||||
|
||||
/**
|
||||
* 豆包 cici 特殊需求,只在inhouse上线
|
||||
* Bean bag cici special needs, only online in inhouse
|
||||
*/
|
||||
FEATURE_ENABLE_QUERY_ENTRY: !IS_RELEASE_VERSION,
|
||||
/**
|
||||
* coze接入审核增加,用于发布机审弹窗提前、版本历史Publish类审核结果展示。目前仅CN生效。
|
||||
* Coze access audit has been added, which is used for the advance of the publishing machine audit pop-up window and the display of the version history Publishing audit results. Currently only CN is effective.
|
||||
*/
|
||||
FEATURE_ENABLE_TCS: !IS_OVERSEA,
|
||||
|
||||
/**
|
||||
* Tea 上报数据增加 UG 线索回传参数,仅 cn release 需要
|
||||
* Add UG clue return parameters to the data reported by Tea, which is only required for cn release.
|
||||
*
|
||||
*/
|
||||
FEATURE_ENABLE_TEA_UG: IS_RELEASE_VERSION && !IS_OVERSEA,
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// 基于src/index.ts自动生成,请勿手动修改
|
||||
// Automatically generated based on src/index.ts, do not modify manually
|
||||
declare const APP_ID: number;
|
||||
declare const APP_KEY: string;
|
||||
declare const AWEME_ORIGIN: string;
|
||||
@@ -108,3 +108,4 @@ declare const VOLCANO_PLATFORM_APP_KEY: string | null;
|
||||
declare const VOLCANO_PLATFORM_ID: number | null;
|
||||
declare const VOLC_PRIVATE_POLICY: string;
|
||||
declare const VOLC_TERMS_OF_SERVICE: string;
|
||||
|
||||
@@ -17,26 +17,26 @@
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
/**
|
||||
* 获取当前 git 分支名称
|
||||
* @returns 当前分支名称,如果不在 git 仓库中或发生错误则返回 undefined
|
||||
* Get the current git branch name
|
||||
* @Returns the current branch name, or undefined if not in the git repository or an error occurs
|
||||
*/
|
||||
export function getCurrentBranch(): string | undefined {
|
||||
try {
|
||||
// 使用 git rev-parse 获取当前分支名
|
||||
// --abbrev-ref 参数返回分支名而不是 commit hash
|
||||
// HEAD 表示当前位置
|
||||
// Use git rev-parse to get the current branch name
|
||||
// --Abbrev-ref parameter returns branch name instead of commit hash
|
||||
// HEAD represents the current location
|
||||
const branch = execSync('git rev-parse --abbrev-ref HEAD', {
|
||||
encoding: 'utf-8',
|
||||
}).trim();
|
||||
|
||||
// 如果在 detached HEAD 状态,返回 undefined
|
||||
// If in the detached HEAD state, return undefined
|
||||
if (branch === 'HEAD') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return branch;
|
||||
} catch (error) {
|
||||
// 如果执行出错(比如不在 git 仓库中),返回 undefined
|
||||
// If there is an execution error (e.g. not in the git repository), return undefined.
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
18
frontend/packages/arch/bot-env/src/global.d.ts
vendored
18
frontend/packages/arch/bot-env/src/global.d.ts
vendored
@@ -27,22 +27,22 @@ type TUploaderRegion =
|
||||
|
||||
interface Window {
|
||||
gfdatav1?: {
|
||||
// 部署区域
|
||||
// deployment area
|
||||
region?: string;
|
||||
// SCM 版本
|
||||
// SCM version
|
||||
ver?: number | string;
|
||||
// 当前环境, 取值为 boe 或 prod
|
||||
// Current environment, the value is boe or prod
|
||||
env?: 'boe' | 'prod';
|
||||
// 环境标识,如 prod 或 ppe_*
|
||||
// Environmental identification, such as prod or ppe_ *
|
||||
envName?: string;
|
||||
// 当前的小流量频道 ID,0 表示全流量
|
||||
// The current small traffic channel ID, 0 represents full traffic
|
||||
canary?: 0;
|
||||
extra?: {
|
||||
/**
|
||||
* @description goofy 团队不建议依赖该字段,能不用则不用
|
||||
* 1 表示小流量
|
||||
* 3 表示灰度
|
||||
* null 表示全流量
|
||||
* @Description The goofy team does not recommend relying on this field. If you can't use it, don't use it.
|
||||
* 1 means small traffic.
|
||||
* 3 means grey release
|
||||
* Null means full traffic
|
||||
*/
|
||||
canaryType?: 1 | 3 | null;
|
||||
};
|
||||
|
||||
@@ -118,14 +118,14 @@ describe('bot-error-certain-error', () => {
|
||||
sendCertainError(new Error(), handle);
|
||||
expect(handle).toHaveBeenCalled();
|
||||
});
|
||||
// notInstanceError json stringify 失败的单测
|
||||
// notInstanceError json stringified single test
|
||||
errorFuncList.forEach(item => {
|
||||
const handle = vi.fn();
|
||||
if (item.name !== 'notInstanceError') {
|
||||
return;
|
||||
}
|
||||
(item.func as Mock).mockReturnValue(true);
|
||||
// JSON stringify 会报错的 case
|
||||
// JSON stringify will report an error case
|
||||
const b = { a: {} };
|
||||
const a = { b: {}, name: 'notInstanceError' };
|
||||
b.a = a;
|
||||
|
||||
@@ -68,18 +68,18 @@ const handleCertainError: (error: Error) => void = error => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 上报到自定义错误
|
||||
// Report a custom error
|
||||
if (errorName === 'CustomError') {
|
||||
const { eventName, msg } = error as CustomError;
|
||||
// 补充统一上报custom error event_name 用于监控
|
||||
// Supplement unified reporting custom error event_name for monitoring
|
||||
loggerWithScope.persist.error({
|
||||
eventName: ReportEventNames.CustomErrorReport,
|
||||
message: msg,
|
||||
error,
|
||||
meta: {
|
||||
name: error.name,
|
||||
originEventName: eventName, // 原始originEventName
|
||||
originErrorMessage: msg, // 原始 error msg
|
||||
originEventName: eventName, // originEventName
|
||||
originErrorMessage: msg, // Original error msg
|
||||
},
|
||||
});
|
||||
loggerWithScope.persist.error({
|
||||
@@ -93,12 +93,12 @@ const handleCertainError: (error: Error) => void = error => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 滤除已经上报到自定义事件
|
||||
// Filter out custom events that have been reported
|
||||
if (errorName === 'ApiError' || errorName === 'AxiosError') {
|
||||
return;
|
||||
}
|
||||
|
||||
// ChunkLoad 失败, 不上报,在slardar 静态资源异常统计
|
||||
// ChunkLoad failed, not reported, static resource exception statistics in slardar
|
||||
if (errorName === 'ChunkLoadError') {
|
||||
reporter.info({
|
||||
message: 'chunkLoadError',
|
||||
@@ -112,7 +112,7 @@ const handleCertainError: (error: Error) => void = error => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 不继承 Error 的错误,目前 case(semi 表单校验 )
|
||||
// Error that does not inherit Error, current case (semi form validation)
|
||||
if (errorName === 'notInstanceError') {
|
||||
let errorInfo;
|
||||
try {
|
||||
|
||||
@@ -16,23 +16,23 @@
|
||||
|
||||
export enum ReportEventNames {
|
||||
/**
|
||||
* 通用异常错误
|
||||
* generic exception error
|
||||
*/
|
||||
ChunkLoadError = 'chunk_load_error', // webpack chunk load 失败
|
||||
Unhandledrejection = 'unhandledrejection', // 异步错误兜底
|
||||
GlobalErrorBoundary = 'global_error_boundary', // 全局的errorBoundary 错误
|
||||
ChunkLoadError = 'chunk_load_error', // Webpack chunk load failed
|
||||
Unhandledrejection = 'unhandledrejection', // Asynchronous Error Bottom Line
|
||||
GlobalErrorBoundary = 'global_error_boundary', // Global errorBoundary error
|
||||
NotInstanceError = 'notInstanceError',
|
||||
CustomErrorReport = 'custom_error_report', // 统一上报的custom error
|
||||
CustomErrorReport = 'custom_error_report', // Uniformly reported customs errors
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已经明确的错误
|
||||
* Get the error that has been identified
|
||||
*
|
||||
* 1、CustomError: 业务方 throw new CustomError(ReportEventNames.xxx, 'xxx')
|
||||
* 2、AxiosError: 状态码非 2xx;
|
||||
* 3、ApiError: 状态码 2xx & 业务code !== 0
|
||||
* 4、ChunkLoadError: webpack chunk load 失败
|
||||
* 5、notInstanceError,不继承 Error 的错误,目前 case(semi 表单校验 )
|
||||
* 1. CustomError: The business party throws new CustomError (ReportEventNames.xxx, 'xxx')
|
||||
* 2. AxiosError: The status code is not 2xx;
|
||||
* 3, ApiError: status code 2xx & business code! == 0
|
||||
* 4. ChunkLoadError: webpack chunk load failed
|
||||
* 5. notInstanceError, error that does not inherit Error, the current case (semi form verification)
|
||||
*/
|
||||
export type CertainErrorName =
|
||||
| 'CustomError'
|
||||
|
||||
@@ -30,7 +30,7 @@ export class CustomError extends Error {
|
||||
this.ext = ext;
|
||||
}
|
||||
}
|
||||
// sladar beforeSend捕获到的错误需要通过.name判断错误类型
|
||||
// sladar beforeSend The captured error needs to determine the error type by .name.
|
||||
export const isCustomError = (error: unknown): error is CustomError =>
|
||||
error instanceof CustomError ||
|
||||
(error as CustomError)?.name === 'CustomError';
|
||||
|
||||
@@ -61,7 +61,7 @@ export const useErrorCatch = (slardarInstance: SlardarInstance) => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 3. 拦截 slardar 上报
|
||||
// 3. Interception of slardar reports
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const beforeSlardarSend = (e: any) => {
|
||||
|
||||
@@ -31,7 +31,7 @@ const loggerWithScope = logger.createLoggerWith({
|
||||
export const useRouteErrorCatch = (error: unknown) => {
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
// 处理不是error实例的情况
|
||||
// Handling cases that are not instances of error
|
||||
const realError =
|
||||
error instanceof Error
|
||||
? error
|
||||
@@ -39,7 +39,7 @@ export const useRouteErrorCatch = (error: unknown) => {
|
||||
ReportEventNames.GlobalErrorBoundary,
|
||||
`global error route catch error infos:${String(error)}`,
|
||||
);
|
||||
// 过滤 其他error
|
||||
// Filtering, other errors
|
||||
sendCertainError(realError, () => {
|
||||
loggerWithScope.persist.error({
|
||||
eventName: ReportEventNames.GlobalErrorBoundary,
|
||||
|
||||
@@ -131,7 +131,7 @@ describe('pullFeatureFlags', () => {
|
||||
});
|
||||
|
||||
it('should access values from global context firstly', async () => {
|
||||
// 从localStorage & global context 都取到值的情况下,优先使用 context 值
|
||||
// When getting the value from both localStorage & global context, the context value is preferred
|
||||
readFromCache.mockImplementation(async () => {
|
||||
await $wait(100);
|
||||
return { foo: true };
|
||||
|
||||
@@ -30,15 +30,15 @@ import { PACKAGE_NAMESPACE } from './constant';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
|
||||
const DEFAULT_POLLING_INTERVAL = 5 * ONE_SEC;
|
||||
// 设置 17 作为时间分片大小
|
||||
// Set 17 as the time sharding size
|
||||
const TIME_PIECE = 17;
|
||||
|
||||
interface PullFeatureFlagsParams {
|
||||
// 取值超时时间
|
||||
// value timeout
|
||||
timeout: number;
|
||||
// 严格模式下,不会插入兜底逻辑,且取不到数值时直接报错
|
||||
// In strict mode, no fallback logic will be inserted, and an error will be reported directly when the value cannot be obtained
|
||||
strict: boolean;
|
||||
// 轮训间隔,生产环境默认 60 秒;开发 & 测试环境默认 10 秒
|
||||
// Rotation interval, production environment default 60 seconds; development & testing environment default 10 seconds
|
||||
pollingInterval: number;
|
||||
fetchFeatureGating: FetchFeatureGatingFunction;
|
||||
}
|
||||
@@ -66,12 +66,12 @@ const runPipeline = async (
|
||||
}
|
||||
|
||||
const { timeout: to, strict } = context;
|
||||
// 超时时间不应该小于 1s
|
||||
// The timeout should not be less than 1s.
|
||||
const timeout = Math.max(to, ONE_SEC);
|
||||
const works: (() => Promise<WorkResult | undefined>)[] = [];
|
||||
const waitTimeout = wait.bind(null, timeout + ONE_SEC);
|
||||
|
||||
// 从线上环境取值
|
||||
// Take value from the online environment
|
||||
works.push(async () => {
|
||||
try {
|
||||
const values = await context.fetchFeatureGating();
|
||||
@@ -81,7 +81,7 @@ const runPipeline = async (
|
||||
}
|
||||
await waitTimeout();
|
||||
} catch (e) {
|
||||
// TODO: 这里加埋点,上报接口异常
|
||||
// TODO: Add event tracking here to report interface abnormalities
|
||||
logger.persist.error({
|
||||
namespace: PACKAGE_NAMESPACE,
|
||||
message: 'Fetch fg by "fetchFeatureGating" failure',
|
||||
@@ -91,8 +91,8 @@ const runPipeline = async (
|
||||
}
|
||||
});
|
||||
|
||||
// 从浏览器全局对象取值
|
||||
// 这里需要判断一下,只有浏览器环境才执行
|
||||
// Get value from browser global object
|
||||
// It needs to be judged here, only the browser environment will execute it.
|
||||
works.push(async () => {
|
||||
try {
|
||||
const values = await readFgPromiseFromContext();
|
||||
@@ -104,10 +104,10 @@ const runPipeline = async (
|
||||
namespace: PACKAGE_NAMESPACE,
|
||||
message: "Can't not read fg from global context",
|
||||
});
|
||||
// 强制等等超时,以免整个 works resolve 到错误的值
|
||||
// Force and so on to time out, lest the entire works resolve to the wrong value
|
||||
await waitTimeout();
|
||||
} catch (e) {
|
||||
// TODO: 这里加埋点,上报接口异常
|
||||
// TODO: Add event tracking here to report interface abnormalities
|
||||
logger.persist.error({
|
||||
namespace: PACKAGE_NAMESPACE,
|
||||
message: 'Fetch fg from context failure',
|
||||
@@ -117,18 +117,18 @@ const runPipeline = async (
|
||||
}
|
||||
});
|
||||
|
||||
// 从缓存中取值
|
||||
// fetch value from cache
|
||||
works.push(async () => {
|
||||
try {
|
||||
const values = await readFromCache();
|
||||
if (values) {
|
||||
// 等待 xx ms 后再读 persist,以确保优先从 context 取值
|
||||
// Wait for xx ms before reading persist to ensure that values are retrieved from context first
|
||||
await wait(timeout - TIME_PIECE);
|
||||
return { values, source: 'persist' };
|
||||
}
|
||||
await waitTimeout();
|
||||
} catch (e) {
|
||||
// TODO: 这里加埋点,上报接口异常
|
||||
// TODO: Add event tracking here to report interface abnormalities
|
||||
logger.persist.error({
|
||||
namespace: PACKAGE_NAMESPACE,
|
||||
message: 'Fetch fg from persist cache failure',
|
||||
@@ -138,7 +138,7 @@ const runPipeline = async (
|
||||
}
|
||||
});
|
||||
|
||||
// 兜底,超时取不到值返回默认值,也就是全部都是 false
|
||||
// Bottom line, the value cannot be obtained after timeout, and the default value is returned, that is, all are false.
|
||||
works.push(async () => {
|
||||
await wait(timeout + TIME_PIECE);
|
||||
if (strict) {
|
||||
@@ -147,7 +147,7 @@ const runPipeline = async (
|
||||
return { values: {} as unknown as FEATURE_FLAGS, source: 'bailout' };
|
||||
});
|
||||
|
||||
// 这里不可能返回 undefined,所以做一次强制转换
|
||||
// It is impossible to return undefined here, so do a cast
|
||||
const res = (await Promise.race(
|
||||
works.map(work => work()),
|
||||
)) as unknown as WorkResult;
|
||||
@@ -169,7 +169,7 @@ const normalize = (
|
||||
const normalizeContext = Object.assign(
|
||||
DEFAULT_CONTEXT,
|
||||
Object.keys(ctx)
|
||||
// 只取不为 undefined 的东西
|
||||
// Only take things that are not undefined
|
||||
.filter(k => typeof ctx[k] !== 'undefined')
|
||||
.reduce((acc, k) => ({ ...acc, [k]: ctx[k] }), {}),
|
||||
);
|
||||
@@ -186,7 +186,7 @@ const pullFeatureFlags = async (context?: Partial<PullFeatureFlagsParams>) => {
|
||||
tracer.trace('start');
|
||||
const start = performance.now();
|
||||
const retry = async () => {
|
||||
// 出现错误时,自动重试
|
||||
// When an error occurs, automatically retry
|
||||
await wait(pollingInterval);
|
||||
await pullFeatureFlags(context);
|
||||
};
|
||||
@@ -194,7 +194,7 @@ const pullFeatureFlags = async (context?: Partial<PullFeatureFlagsParams>) => {
|
||||
const res = await runPipeline(normalizeContext);
|
||||
|
||||
const { values, source } = res;
|
||||
// TODO: 这里应该上报数量,后续 logger 提供相关能力后要改一下
|
||||
// TODO: The quantity should be reported here, and it should be changed after the subsequent logger provides relevant capabilities.
|
||||
logger.persist.success({
|
||||
namespace: PACKAGE_NAMESPACE,
|
||||
message: `Load FG from ${source} start at ${start}ms and spend ${
|
||||
|
||||
@@ -19,11 +19,11 @@ import { type FEATURE_FLAGS as ORIGIN_FEATURE_FLAGS } from './feature-flags';
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
type FEATURE_FLAGS = ORIGIN_FEATURE_FLAGS & {
|
||||
/**
|
||||
* 返回所有可用 key 列表
|
||||
* Returns a list of all available keys
|
||||
*/
|
||||
keys: string[];
|
||||
/**
|
||||
* FG 是否已经完成初始化
|
||||
* Has FG completed initialization?
|
||||
*/
|
||||
isInited: boolean;
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ import { getFlags } from './get-flags';
|
||||
|
||||
export const useFlags = (): [FEATURE_FLAGS] => {
|
||||
const plainFlags = getFlags();
|
||||
// 监听 fg store 事件,触发 react 组件响应变化
|
||||
// Listens to the fg store event and triggers the react component to respond to changes
|
||||
const [, setTick] = useState<number>(0);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -26,7 +26,7 @@ const isFlagsShapeObj = (obj: unknown) => {
|
||||
if (typeof obj === 'object') {
|
||||
const shape = obj as FEATURE_FLAGS;
|
||||
return (
|
||||
// 如果包含任意属性值不是 boolean,则认为不是 flags 对象
|
||||
// If any property value is not a boolean, it is not considered a flags object
|
||||
Object.keys(shape).some(r => typeof shape[r] !== 'boolean') === false
|
||||
);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ class FeatureFlagStorage extends EventEmitter {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 从 remote 取值
|
||||
// Get value from remote
|
||||
if (Reflect.has(cache, name)) {
|
||||
return Reflect.get(cache, name);
|
||||
}
|
||||
|
||||
@@ -20,26 +20,26 @@ export const isEqual = (
|
||||
obj1: Record<string, boolean> | undefined,
|
||||
obj2: Record<string, boolean> | undefined,
|
||||
) => {
|
||||
// 有任意一个不是对象时,则直接返回 false
|
||||
// If any one is not an object, return false directly.
|
||||
if (!isObject(obj1) || !isObject(obj2)) {
|
||||
return false;
|
||||
}
|
||||
const o1 = obj1 as Record<string, boolean>;
|
||||
const o2 = obj2 as Record<string, boolean>;
|
||||
|
||||
// 检查两个对象有相同的键数,如果数量不同,则一定不相等
|
||||
// Check that two objects have the same number of keys. If the numbers are different, they must not be equal
|
||||
if (Object.keys(o1).length !== Object.keys(o2).length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果键数相同,然后我们检查每个键的值
|
||||
// If the number of keys is the same, then we check the value of each key
|
||||
for (const key in o1) {
|
||||
// 如果键不存在于第二个对象,或者值不同,返回false
|
||||
// If the key does not exist in the second object, or the values are different, return false.
|
||||
if (!(key in o2) || o1[key] !== o2[key]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果所有键都存在于两个对象,并且所有的值都相同,返回 true
|
||||
// Returns true if all keys exist in both objects and all values are the same.
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
declare const ENABLE_COVERAGE: boolean;
|
||||
interface Window {
|
||||
/**
|
||||
* tea 实例
|
||||
* Tea example
|
||||
*/
|
||||
Tea?: any;
|
||||
}
|
||||
|
||||
@@ -16,5 +16,5 @@
|
||||
|
||||
import { useIsResponsiveByRouteConfig } from '@coze-arch/bot-hooks-base';
|
||||
|
||||
/** @deprecated - 通过路由配置维护 */
|
||||
/** @deprecated - maintenance via routing configuration */
|
||||
export const useIsResponsive = () => useIsResponsiveByRouteConfig();
|
||||
|
||||
@@ -69,12 +69,12 @@ describe('useLineClamp', () => {
|
||||
|
||||
const { result } = renderHook(() => useLineClamp());
|
||||
|
||||
// 使用 vi.spyOn 来模拟 contentRef.current
|
||||
// Use vi.spyOn to simulate contentRef.current
|
||||
vi.spyOn(result.current.contentRef, 'current', 'get').mockReturnValue(
|
||||
mockDiv,
|
||||
);
|
||||
|
||||
// 使用 act 包装异步操作
|
||||
// Wrap asynchronous operations with act
|
||||
act(() => {
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
});
|
||||
@@ -85,10 +85,10 @@ describe('useLineClamp', () => {
|
||||
it('should handle null contentRef', () => {
|
||||
const { result } = renderHook(() => useLineClamp());
|
||||
|
||||
// 使用 vi.spyOn 来模拟 contentRef.current 为 null
|
||||
// Use vi.spyOn to simulate contentRef. current is null
|
||||
vi.spyOn(result.current.contentRef, 'current', 'get').mockReturnValue(null);
|
||||
|
||||
// 使用 act 包装异步操作
|
||||
// Wrap asynchronous operations with act
|
||||
act(() => {
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* @description `LayoutContext`用于跨组件传递布局相关信息
|
||||
* @Description 'LayoutContext' is used to pass layout-related information across components
|
||||
* @since 2024.03.05
|
||||
*/
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
declare const ENABLE_COVERAGE: boolean;
|
||||
interface Window {
|
||||
/**
|
||||
* tea 实例
|
||||
* Tea example
|
||||
*/
|
||||
Tea?: any;
|
||||
}
|
||||
|
||||
@@ -16,14 +16,14 @@
|
||||
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
/** 清空认证数据的路由参数 */
|
||||
/** Clear the routing parameters of the authentication data */
|
||||
export const resetAuthLoginDataFromRoute = () => {
|
||||
window.history.replaceState({}, '');
|
||||
};
|
||||
export function useResetLocationState() {
|
||||
const location = useLocation();
|
||||
return () => {
|
||||
// 清空location的state
|
||||
// Clear the state of the location
|
||||
location.state = {};
|
||||
resetAuthLoginDataFromRoute();
|
||||
};
|
||||
|
||||
@@ -24,9 +24,9 @@ export interface ComponentStateUpdateFunc<State extends Obj> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 对 state 一层封装,用途:
|
||||
* 1. 默认增量更新
|
||||
* 2. 支持重置
|
||||
* One-layer encapsulation of state, use:
|
||||
* 1. Default incremental update
|
||||
* 2. Support reset
|
||||
*
|
||||
* @example
|
||||
* const { state, resetState, setState } = useComponentState({ a: 1, b: 2 });
|
||||
|
||||
@@ -28,11 +28,11 @@ import {
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
/**
|
||||
* 用于校验当前模块默认展开收起状态
|
||||
* @deprecated 改属性已经废弃不维护,请更换@coze-agent-ide/tool中的useToolContentBlockDefaultExpand
|
||||
* @param blockKey 主键
|
||||
* @param configured 是否有配置内容
|
||||
* @param when 是否校验
|
||||
* Used to verify the default expanded and stowed state of the current module
|
||||
* @Deprecated change attribute is deprecated and not maintained, please replace the useToolContentBlockDefaultExpand in @code-agent-ide/tool
|
||||
* @param blockKey primary key
|
||||
* @param configured whether there is configuration content
|
||||
* Check when @param
|
||||
*
|
||||
*/
|
||||
const useDefaultExPandCheck = (
|
||||
@@ -52,18 +52,18 @@ const useDefaultExPandCheck = (
|
||||
})),
|
||||
);
|
||||
return useMemo(() => {
|
||||
// 不做校验
|
||||
// No verification
|
||||
if (!$when) {
|
||||
return undefined;
|
||||
// 状态机未就绪
|
||||
// Finite-state machine not ready
|
||||
} else if (!init || size(botSkillBlockCollapsibleState) === 0) {
|
||||
return undefined;
|
||||
/**
|
||||
* @description 仅在满足以下条件时用户行为记录才能生效
|
||||
* @Description A user behavior record is only valid if the following conditions are met
|
||||
*
|
||||
* 1. 用户有编辑权限
|
||||
* 2. 不能是历史预览环境
|
||||
* 3. 必须已配置
|
||||
* 1. Users have editing rights
|
||||
* 2. Cannot be a historical preview environment
|
||||
* 3. Must be configured
|
||||
*/
|
||||
} else if (editable && !isReadonly && configured) {
|
||||
const transformerBlockKey = skillKeyToApiStatusKeyTransformer(blockKey);
|
||||
|
||||
@@ -24,38 +24,38 @@ import { getFileListByDragOrPaste } from './helper/get-file-list-by-drag';
|
||||
export interface UseDragAndPasteUploadParam {
|
||||
ref: RefObject<HTMLDivElement>;
|
||||
/**
|
||||
* 触发上传的回调
|
||||
* Callback that triggers upload
|
||||
*/
|
||||
onUpload: (fileList: File[]) => void;
|
||||
/**
|
||||
* 是否禁用拖拽上传
|
||||
* Whether to disable drag-and-drop uploads
|
||||
*/
|
||||
disableDrag: boolean;
|
||||
/**
|
||||
* 是否禁用粘贴上传
|
||||
* Whether to disable paste uploads
|
||||
*/
|
||||
disablePaste: boolean;
|
||||
/**
|
||||
* 最大上传的文件数量
|
||||
* Maximum number of uploaded files
|
||||
*/
|
||||
fileLimit: number;
|
||||
/**
|
||||
* 文件大小, eg: 10MB = 10 * 1024 * 1024
|
||||
* File size, eg: 10MB = 10 * 1024 * 1024
|
||||
*/
|
||||
maxFileSize: number;
|
||||
invalidSizeMessage: string | undefined;
|
||||
invalidFormatMessage: string | undefined;
|
||||
fileExceedsMessage: string | undefined;
|
||||
/**
|
||||
* 文件格式是否合法
|
||||
* Is the file format legal?
|
||||
*/
|
||||
isFileFormatValid: (file: File) => boolean;
|
||||
/**
|
||||
* @returns 已存在文件的数量
|
||||
* @Returns the number of existing files
|
||||
*/
|
||||
getExistingFileCount: () => number;
|
||||
/**
|
||||
* 用户离开拖拽区域时, state 变化的延迟
|
||||
* Delay in state change when the user leaves the drag area
|
||||
* @default 100
|
||||
*/
|
||||
closeDelay: number | undefined;
|
||||
@@ -79,9 +79,9 @@ export const useDragAndPasteUpload = ({
|
||||
const [isDragOver, setIsDragOver] = useState(false);
|
||||
|
||||
/**
|
||||
* drag 时, 指针从 parent dom 进入到 child dom 时会快速连续触发 onDragEnter onDragLeave 导致状态流转错误
|
||||
* 在 onLeave 时给状态流转加上延时能够避免流转问题
|
||||
* 触发 dragEnter dragLeave 时, event.target 不一定指向 parent dom, 所以也无法通过 target 来判断
|
||||
* When dragging, the pointer from the parent dom to the child dom will fire onDragEnter onDragLeave in quick succession, resulting in a state flow error
|
||||
* Adding a delay to the state flow on onLeave avoids the flow problem
|
||||
* When dragEnter dragLeave is triggered, event.target does not necessarily point to parent dom, so it cannot be judged by target
|
||||
*/
|
||||
const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
@@ -147,10 +147,10 @@ export const useDragAndPasteUpload = ({
|
||||
const onDragOver = (e: HTMLElementEventMap['dragover']) => {
|
||||
/**
|
||||
* {@link https://segmentfault.com/q/1010000011746669}
|
||||
* 原理:
|
||||
* 这里阻止的默认行为是开启可编辑模式,具体就是document.designMode属性,
|
||||
* 该属性默认是off关闭的,当开启之后就可以对网页进行编辑
|
||||
* 开启的方式就是document.designMode = "on"; 开启之后就不用在监听dragover事件中阻止默认了
|
||||
* Principle:
|
||||
* The default behavior blocked here is to enable editable mode, specifically the document.designMode property,
|
||||
* This property is turned off by default, and when turned on, you can edit the webpage.
|
||||
* The way to open it is document.designMode = "on"; after opening it, there is no need to block the default in the monitor dragover event.
|
||||
*/
|
||||
e.preventDefault();
|
||||
clearTimer();
|
||||
|
||||
@@ -22,20 +22,20 @@ import { useInViewport } from 'ahooks';
|
||||
import { type EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
|
||||
|
||||
export interface UseExposureParams {
|
||||
/** 曝光元素 */
|
||||
/** Exposure element */
|
||||
target: BasicTarget;
|
||||
/** Intersection observer参数 */
|
||||
/** Intersection observer parameters */
|
||||
options?: Options;
|
||||
/** 上报事件名称 */
|
||||
/** event name reported */
|
||||
eventName?: EVENT_NAMES;
|
||||
/** 上报参数 */
|
||||
/** reporting parameters */
|
||||
reportParams?: Record<string, unknown>;
|
||||
/** 是否进行上报 默认为true */
|
||||
/** Whether to report, the default is true */
|
||||
needReport?: boolean;
|
||||
isReportOnce?: boolean;
|
||||
}
|
||||
|
||||
/** 曝光埋点上报 */
|
||||
/** Exposure event tracking report */
|
||||
export const useExposure = ({
|
||||
target,
|
||||
options,
|
||||
@@ -49,7 +49,7 @@ export const useExposure = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (isReportOnce && refHasReport.current) {
|
||||
//已上报过数据,就直接返回
|
||||
//If the data has been reported, please return directly.
|
||||
return;
|
||||
}
|
||||
if (needReport && isInView) {
|
||||
|
||||
@@ -17,6 +17,6 @@
|
||||
import { userStoreService } from '@coze-studio/user-store';
|
||||
|
||||
/**
|
||||
* 判断当前用户是否处于登陆状态
|
||||
* Determine whether the current user is logged in
|
||||
*/
|
||||
export const useLoggedIn = () => userStoreService.useIsLogined();
|
||||
|
||||
@@ -22,9 +22,9 @@ export interface PageStateUpdateFunc<State extends object = object> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 对state一层封装,包含更新state、重置state
|
||||
* A layer of encapsulation of state, including updating state and resetting state
|
||||
*
|
||||
* @deprecated 请使用 bot-hooks 的 useComponentStates
|
||||
* @deprecated Please use the useComponentStates of bot-hooks
|
||||
*/
|
||||
export function usePageState<State extends object = object>(
|
||||
initState: State,
|
||||
@@ -52,7 +52,7 @@ export function usePageState<State extends object = object>(
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
// 自动重置状态
|
||||
// Automatic reset status
|
||||
if (destroyRef.current) {
|
||||
resetState();
|
||||
}
|
||||
|
||||
@@ -21,78 +21,78 @@ import { type ScreenRange } from '@coze-arch/responsive-kit';
|
||||
|
||||
export interface TRouteConfigGlobal {
|
||||
/**
|
||||
* 展示小助手
|
||||
* display assistant
|
||||
* @default true
|
||||
* @import 开源版不支持该字段
|
||||
* @Import open source version does not support this field
|
||||
*/
|
||||
showAssistant?: boolean;
|
||||
/**
|
||||
* 展示小助手引导提示
|
||||
* Show assistant guide prompt
|
||||
* @default false
|
||||
* @import 开源版不支持该字段
|
||||
* @Import open source version does not support this field
|
||||
*/
|
||||
showAssistantGuideTip?: boolean;
|
||||
/**
|
||||
* 当企业ID发生变化时的回调函数。
|
||||
* @import 开源版不支持该字段
|
||||
* @param enterpriseId - 变化后的企业ID。
|
||||
* @param params - 包含导航函数和当前路径名的对象。
|
||||
* Callback function when the enterprise ID changes.
|
||||
* @Import open source version does not support this field
|
||||
* @Param enterpriseId - Changed enterprise ID.
|
||||
* @Param params - An object containing the navigation function and the current pathname.
|
||||
*/
|
||||
onEnterpriseChange?: (
|
||||
enterpriseId: string,
|
||||
params: {
|
||||
navigate: NavigateFunction; // 导航函数,用于路由跳转。
|
||||
pathname: string; // 当前路径名,用于构建新的路径。
|
||||
navigate: NavigateFunction; // Navigation function for routing jumps.
|
||||
pathname: string; // The current path name is used to build a new path.
|
||||
},
|
||||
) => void;
|
||||
/**
|
||||
* 是否展示侧边栏
|
||||
* Whether to display the sidebar
|
||||
* @default false
|
||||
*/
|
||||
hasSider?: boolean;
|
||||
/**
|
||||
* 展示移动端不适配提示文案
|
||||
* Display mobile end does not fit prompt copy
|
||||
* @default false
|
||||
*/
|
||||
showMobileTips?: boolean;
|
||||
/**
|
||||
* 是否需要身份验证
|
||||
* Is authentication required?
|
||||
* @default false
|
||||
*/
|
||||
requireAuth?: boolean;
|
||||
/**
|
||||
* 登录失效时的回退地址
|
||||
* The fallback address when the login fails
|
||||
* @default /sign
|
||||
*/
|
||||
loginFallbackPath?: string;
|
||||
/**
|
||||
* @deprecated
|
||||
* 是否允许身份验证为可选
|
||||
* Whether to allow authentication is optional
|
||||
* @default false
|
||||
*/
|
||||
requireAuthOptional?: boolean;
|
||||
/**
|
||||
* 设置为 true 时自动应用缺省值 { rangeMax: ScreenRange.LG, include: false } 对应之前绝大多数支持响应式路由的配置
|
||||
* The default value {rangeMax: ScreenRange. LG, include: false} is automatically applied when set to true for most previous configurations that support responsive routing
|
||||
* @default false
|
||||
*/
|
||||
responsive?: { rangeMax: ScreenRange; include?: boolean } | true;
|
||||
/**
|
||||
* 子菜单组件
|
||||
* submenu component
|
||||
* @default undefined
|
||||
*/
|
||||
subMenu?: FC<Record<string, never>>;
|
||||
/**
|
||||
* 一级导航菜单项 key
|
||||
* Primary navigation menu item key
|
||||
* @default undefined
|
||||
*/
|
||||
menuKey?: string;
|
||||
/**
|
||||
* 二级导航菜单项 key
|
||||
* Secondary navigation menu item key
|
||||
* @default undefined
|
||||
*/
|
||||
subMenuKey?: string;
|
||||
/**
|
||||
* 控制是否根据 query 中的 page_mode 字段判断页面模式: 默认侧边导航模式 or 全屏popover模式
|
||||
* Controls whether page mode is determined based on page_mode fields in the query: default side navigation mode or full screen popover mode
|
||||
* @default false
|
||||
*/
|
||||
pageModeByQuery?: boolean;
|
||||
@@ -102,7 +102,7 @@ export const useRouteConfig = <
|
||||
TConfig extends TRouteConfigGlobal = TRouteConfigGlobal,
|
||||
>(
|
||||
defaults?: TConfig,
|
||||
// 强制所有字段可能为空
|
||||
// Force all fields to be empty
|
||||
): Partial<TConfig> => {
|
||||
const matches = useMatches();
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ describe('page jump', () => {
|
||||
const spaceID = '234';
|
||||
const workflowID = '345';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention -- 这是组件
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention -- this is the component
|
||||
const MockWorkflowPage = () => {
|
||||
const jumpResponse = usePageJumpResponse(PageType.WORKFLOW);
|
||||
const [cleared, setCleared] = useState(false);
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
declare const ENABLE_COVERAGE: boolean;
|
||||
interface Window {
|
||||
/**
|
||||
* tea 实例
|
||||
* Tea example
|
||||
*/
|
||||
Tea?: any;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
import {
|
||||
type WorkflowMode,
|
||||
type WorkFlowListStatus,
|
||||
@@ -22,40 +22,40 @@ import {
|
||||
import type { PageJumpExecFunc } from '.';
|
||||
|
||||
/**
|
||||
* workflow 弹窗打开模式,默认不传,或仅添加一次
|
||||
* Workflow pop-up window opening mode, not passed by default, or only added once
|
||||
*/
|
||||
export enum OpenModeType {
|
||||
OnlyOnceAdd = 'only_once_add',
|
||||
}
|
||||
/**
|
||||
* 记录 workflow 弹窗的选中状态
|
||||
* Record the selected status of the workflow pop-up window
|
||||
*/
|
||||
export interface WorkflowModalState {
|
||||
/**
|
||||
* @deprecated 是否已发布的状态
|
||||
* @deprecated whether the published status
|
||||
*/
|
||||
status?: WorkFlowListStatus;
|
||||
/**
|
||||
* @deprecated 左边菜单栏选中的类型,注意这个 type 是前端翻译过的,与接口请求参数里的 type 不是同一个
|
||||
* @Deprecated The type selected in the menu bar on the left. Note that this type is translated by the front end and is not the same as the type in the interface request parameter.
|
||||
*/
|
||||
type?: number | string;
|
||||
/**
|
||||
* 弹窗状态 JSON 字符串
|
||||
* Popup status JSON string
|
||||
*/
|
||||
statusStr?: string;
|
||||
}
|
||||
|
||||
// #region ---------------------- step 1. 声明场景枚举,若涉及新页面则也声明一下页面枚举 ----------------------
|
||||
// (添加了页面或场景枚举后,整个文件会有多处 ts 报错,这是预期内的。根据 step 指引一步一步完善配置即可)
|
||||
// #Region ---------------------- Step 1. Declare the scene enumeration, if it involves a new page, also declare the page enumeration ----------------------
|
||||
// (After adding the page or scene enumeration, there will be multiple ts errors in the entire file, which is expected. Just follow the step guidelines to improve the configuration step by step)
|
||||
|
||||
/**
|
||||
* 目标页面枚举,用于聚合【场景(scene)】,便于根据页面对当前场景做 narrowing
|
||||
* The target page enumeration is used to aggregate [scene (scene) ], which is convenient for narrowing the current scene according to the page
|
||||
*
|
||||
* e.g. 从 A 页跳转到 B 页,只需要定义 B 的页面枚举
|
||||
* To jump from page A to page B, just define the page enumeration of B.
|
||||
*
|
||||
*
|
||||
* - Q: 为什么不使用默认的自增 enum 数值,每个页面手写两遍名字好麻烦
|
||||
* - A: 便于调试时能直接看出含义,不用查代码对照,下同
|
||||
* - Q: Why not use the default self-increasing enum value, it's troublesome to handwrite the name twice for each page
|
||||
* - A: It is easy to see the meaning directly when debugging, and there is no need to check the code for comparison. The following is the same
|
||||
*/
|
||||
export const enum PageType {
|
||||
BOT = 'bot',
|
||||
@@ -66,70 +66,70 @@ export const enum PageType {
|
||||
DOUYIN_BOT = 'douyin_bot',
|
||||
}
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention -- 有必要 disable,该场景需要不同的 enum 命名规范 */
|
||||
/* eslint-disable @typescript-eslint/naming-convention -- it is necessary to disable, this scenario requires a different enum naming convention */
|
||||
/**
|
||||
* 每种跳转场景的唯一枚举
|
||||
* Unique enumeration for each jump scenario
|
||||
*
|
||||
* e.g. 比如 bot 编辑页创建 workflow 是一种场景,查看 workflow 是一种场景
|
||||
* E.g. For example, bot editing page creation workflow is a scene, viewing workflow is a scene
|
||||
*
|
||||
* 枚举定义规范:
|
||||
* 1. 最常见的场景:两个页面简单跳转可以按 `{源页面}__TO__{目标页面}` 的格式命名。
|
||||
* (注意 TO 前后各有两个下划线,以便区分页面名为多个单词的场景,比如 BOT_LIST__TO__BOT_DETAIL,后文不再赘述)
|
||||
* 2. 从源页面可能存在多种跳转到目标页面的场景,则可以按 `{源页面}__{行为}__{目标页面}` 格式命名。
|
||||
* 3. 如果目标页面逻辑很简单,又有多处跳转来源,则可以按 `TO__{目标页面}` 格式命名。
|
||||
* 4. 对于「跳转到目标页再返回」的特化逻辑,可以给已有的场景命名添加 `__{后缀}`,比如 BOT__CREATE__WORKFLOW__BACK
|
||||
* Enumeration definition specification:
|
||||
* 1. The most common scenario: two pages simple jump can be named according to the format of "{source page} __TO__ {target page}".
|
||||
* (Note that there are two underscores before and after TO to distinguish scenes with multiple words on the page, such as BOT_LIST__TO__BOT_DETAIL, which will not be repeated later)
|
||||
* 2. There may be multiple scenarios for jumping to the target page from the source page, so you can name it in the format of '{source page} __ {behavior} __ {target page}'.
|
||||
* 3. If the target page logic is very simple and there are multiple jump sources, you can name it in the format of TO__ {target page}.
|
||||
* 4. For the specialization logic of "jump to the target page and then return", you can add "__ {suffix}" to the existing scene name, such as BOT__CREATE__WORKFLOW__BACK
|
||||
*
|
||||
* - Q: 我觉得一个目标页面无脑使用 `TO__{目标页面}` 的格式就没问题啊,业务逻辑、来源判断什么我都可以在 参数(param) 和 响应值(response) 中完成
|
||||
* - A: 的确,一个目标页面只声明一种场景就可以打遍天下,这里只是提供了逐级细化拆分场景的范式。
|
||||
* 从 `TO__{目标页面}` 到 `{源页面}__TO__{目标页面}` 再到 `{源页面}__{行为}__{目标页面}` 场景是越来越细分的,业务方可以自行决定如何使用
|
||||
* - Q: I think it's no problem for a target page to use the format of 'TO__ {target page} 'without thinking. I can complete everything in the parameters (param) and response value (response) of the business logic and source judgment
|
||||
* - A: Indeed, a target page can go all over the world by declaring only one scene, and here is just a paradigm for refining and splitting scenes step by step.
|
||||
* From "TO__ {target page}" to "{source page} __TO__ {target page}" and then to "{source page} __ {behavior} __ {target page}" scenarios are increasingly segmented, business parties can decide how to use
|
||||
*/
|
||||
export const enum SceneType {
|
||||
/** bot 详情页查看 workflow */
|
||||
/** Bot details page View workflow */
|
||||
BOT__VIEW__WORKFLOW = 'botViewWorkflow',
|
||||
/** bot 详情页查看 workflow,或新建 workflow 但未发布,点击返回 */
|
||||
/** View the workflow on the bot details page, or create a new workflow but not published, click Return */
|
||||
WORKFLOW__BACK__BOT = 'workflowBackBot',
|
||||
/** bot 详情页创建 workflow,在 workflow 发布后返回 */
|
||||
/** The bot details page creates a workflow and returns it after the workflow is published */
|
||||
WORKFLOW_PUBLISHED__BACK__BOT = 'workflowPublishedBackBot',
|
||||
|
||||
/** 抖音 bot 详情查看 workflow */
|
||||
/** Douyin bot details view workflow */
|
||||
DOUYIN_BOT__VIEW__WORKFLOW = 'douyinBotViewWorkflow',
|
||||
|
||||
/** 抖音 bot 详情页返回 */
|
||||
/** Douyin bot details page Back */
|
||||
WORKFLOW__BACK__DOUYIN_BOT = 'workflowBackDouyinBot',
|
||||
|
||||
/** 抖音 bot 详情页发布后返回 */
|
||||
/** Douyin bot details page back after release */
|
||||
WORKFLOW_PUBLISHED__BACK__DOUYIN_BOT = 'workflowPulishedBackDouyinBot',
|
||||
|
||||
/** bot 详情页进入 mock data 页面 */
|
||||
/** Bot details page Enter the mock data page */
|
||||
BOT__TO__PLUGIN_MOCK_DATA = 'botToPluginMockData',
|
||||
/** workflow 详情页进入 mock data 页面 */
|
||||
/** Workflow details page Enter the mock data page */
|
||||
WORKFLOW__TO__PLUGIN_MOCK_DATA = 'workflowToPluginMockData',
|
||||
/** mock set 页进入 mock data 页面 */
|
||||
/** Mock set page Enter the mock data page */
|
||||
PLUGIN_MOCK_SET__TO__PLUGIN_MOCK_DATA = 'pluginMockSetToPluginMockData',
|
||||
/** bot 详情页进入 knowledge 页面 */
|
||||
/** Bot details page Enter the knowledge page */
|
||||
BOT__VIEW__KNOWLEDGE = 'botViewKnowledge',
|
||||
/** knowledge 页面点击退出返回 bot 详情页(未点击添加) */
|
||||
/** Knowledge page Click Exit to return to bot details page (not clicked Add) */
|
||||
KNOWLEDGE__BACK__BOT = 'knowledgeBackBot',
|
||||
/** knowledge 页面点击返回 bot 详情页,并添加到 bot */
|
||||
/** Knowledge page Click to return to bot details page and add to bot */
|
||||
KNOWLEDGE__ADD_TO__BOT = 'knowledgeAddToBot',
|
||||
/** bot 列表页进入bot 详情页,并查看发布结果 */
|
||||
/** Bot List Page Go to the bot details page and view the release results */
|
||||
BOT_LIST__VIEW_PUBLISH_RESULT_IN__BOT_DETAIL = 'botListViewPublishResultInBotDetail',
|
||||
/** bot 列表页进入bot 详情页,并查看发布结果 */
|
||||
/** Bot List Page Go to the bot details page and view the release results */
|
||||
BOT_LIST__VIEW_PUBLISH_RESULT_IN__DOUYIN_DETAIL = 'botListViewPublishResultInDouyinDetail',
|
||||
/** social scene 页面查看 workflow */
|
||||
/** Social scene page View workflow */
|
||||
SOCIAL_SCENE__VIEW__WORKFLOW = 'socialSceneViewWorkflow',
|
||||
/** social scene 详情页查看 workflow,或新建 workflow 但未发布,点击返回 */
|
||||
/** View the workflow on the social scene details page, or create a new workflow but not published, click Return */
|
||||
WORKFLOW__BACK__SOCIAL_SCENE = 'workflowBackSocialScene',
|
||||
/** social scene 详情页创建或查看 workflow,在 workflow 发布后返回 */
|
||||
/** Create or view a workflow on the social scene details page, and return after the workflow is published */
|
||||
WORKFLOW_PUBLISHED__BACK__SOCIAL_SCENE = 'workflowPublishedBackSocialScene',
|
||||
}
|
||||
/* eslint-enable @typescript-eslint/naming-convention -- 恢复 enum 命名规范 */
|
||||
/* eslint-enable @typescript-eslint/naming-convention -- restore enum naming convention */
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region ---------------------- step 2. 将声明的场景枚举绑定至页面 ----------------------
|
||||
// #Region ---------------------- Step 2. Bind the declared scene enumeration to the page ----------------------
|
||||
|
||||
/** 绑定某一页面可能包含的场景 */
|
||||
/** Bind the scenes that a page may contain */
|
||||
export const PAGE_SCENE_MAP = {
|
||||
[PageType.WORKFLOW]: [
|
||||
SceneType.BOT__VIEW__WORKFLOW,
|
||||
@@ -162,29 +162,29 @@ export const PAGE_SCENE_MAP = {
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region ---------------------- step 3. 声明新场景的参数类型 ----------------------
|
||||
// 【参数(param)】表示从 A 页面跳转 B 页面时,需要 A 页面填写的数据。这份数据会作为 route state 传递
|
||||
// (B 页面会取得处理后的数据,称为响应值)
|
||||
// #Region ---------------------- Step 3. Declare the parameter types for the new scene ----------------------
|
||||
// [Parameter (param) ] represents the data that needs to be filled in on page A when jumping from page A to page B. This data will be passed as route state
|
||||
// (Page B will retrieve the processed data, which is called the response value.)
|
||||
|
||||
interface BotViewWorkflow {
|
||||
spaceID: string;
|
||||
workflowID: string;
|
||||
botID?: string;
|
||||
workflowModalState?: WorkflowModalState;
|
||||
/** multi 模式下会有此项 */
|
||||
/** This will be available in multi mode */
|
||||
agentID?: string;
|
||||
/** @deprecated workflow弹窗打开模式,默认和仅可添加一次 */
|
||||
/** @Deprecated workflow pop-up open mode, default and can only be added once */
|
||||
workflowOpenMode?: OpenModeType;
|
||||
flowMode?: WorkflowMode;
|
||||
/** 是否在新窗口打开 */
|
||||
/** Whether to open in a new window */
|
||||
newWindow?: boolean;
|
||||
/** 可选的工作流节点 ID */
|
||||
/** Optional workflow node ID */
|
||||
workflowNodeID?: string;
|
||||
/** 可选的工作流版本 */
|
||||
/** Optional workflow version */
|
||||
workflowVersion?: string;
|
||||
/** 可选的执行 ID */
|
||||
/** Optional Execution ID */
|
||||
executeID?: string;
|
||||
/** 可选的子流程执行 ID */
|
||||
/** Optional subprocess execution ID */
|
||||
subExecuteID?: string;
|
||||
}
|
||||
|
||||
@@ -192,9 +192,9 @@ interface WorkflowBackBot {
|
||||
spaceID: string;
|
||||
botID: string;
|
||||
workflowModalState?: WorkflowModalState;
|
||||
/** multi 模式下会有此项 */
|
||||
/** This will be available in multi mode */
|
||||
agentID?: string;
|
||||
/** workflow弹窗打开模式,默认和仅可添加一次 */
|
||||
/** Workflow pop-up open mode, default and can only be added once */
|
||||
workflowOpenMode?: OpenModeType;
|
||||
flowMode?: WorkflowMode;
|
||||
}
|
||||
@@ -204,14 +204,14 @@ interface WorkflowPulishedBackBot {
|
||||
botID: string;
|
||||
workflowID: string;
|
||||
pluginID: string;
|
||||
/** multi 模式下会有此项 */
|
||||
/** This will be available in multi mode */
|
||||
agentID?: string;
|
||||
/** workflow弹窗打开模式,默认和仅可添加一次 */
|
||||
/** Workflow pop-up open mode, default and can only be added once */
|
||||
workflowOpenMode?: OpenModeType;
|
||||
flowMode?: WorkflowMode;
|
||||
}
|
||||
|
||||
/** 绑定场景的参数类型 */
|
||||
/** Parameter types for the binding scenario */
|
||||
export type SceneParamTypeMap<T extends SceneType> = {
|
||||
[SceneType.BOT__VIEW__WORKFLOW]: BotViewWorkflow;
|
||||
[SceneType.DOUYIN_BOT__VIEW__WORKFLOW]: BotViewWorkflow;
|
||||
@@ -288,7 +288,7 @@ export type SceneParamTypeMap<T extends SceneType> = {
|
||||
sceneID: string;
|
||||
workflowModalState?: WorkflowModalState;
|
||||
flowMode?: WorkflowMode;
|
||||
/** 是否在新窗口打开 */
|
||||
/** Whether to open in a new window */
|
||||
newWindow?: boolean;
|
||||
};
|
||||
[SceneType.WORKFLOW__BACK__SOCIAL_SCENE]: {
|
||||
@@ -308,17 +308,17 @@ export type SceneParamTypeMap<T extends SceneType> = {
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region ---------------------- step 4. 配置新场景的响应值 ----------------------
|
||||
// 【响应值(response)】表示从 A 页面跳转 B 页面时,B 页面获取的数据
|
||||
// Q: 为什么 B 页面不能直接拿到 参数(param),还得转换一下?
|
||||
// A: 1. route state 无法传递 不能 stringify 的参数,比如函数;
|
||||
// 2. 静态配置(response)和动态配置(param)分离,简化业务调用。
|
||||
// #Region ---------------------- Step 4. Configure the response value of the new scene ----------------------
|
||||
// [Response value (response) ] represents the data obtained by page B when jumping from page A to page B
|
||||
// Q: Why can't the B page directly get the parameters (param), and it has to be converted?
|
||||
// A: 1. route state cannot be passed, cannot be stringified parameters, such as functions;
|
||||
// 2. Static configuration (response) and dynamic configuration (param) are separated to simplify business calls.
|
||||
|
||||
// 若未来这部分配置不断膨胀导致文件过长,则可以考虑进一步拆分文件
|
||||
// If this part of the configuration continues to expand in the future and the file is too long, you can consider further splitting the file
|
||||
|
||||
/** 绑定场景的响应值 */
|
||||
/** Bind the response value of the scene */
|
||||
export const SCENE_RESPONSE_MAP = {
|
||||
// 临时修正类型推导问题,待有场景需要第二个参数 jump 时删掉此处 _
|
||||
// Temporarily fixed type derivation problem, delete here when the second parameter jump is required in a scene _
|
||||
[SceneType.BOT__VIEW__WORKFLOW]: (params, _) => {
|
||||
let url = `/work_flow?space_id=${params.spaceID}&workflow_id=${params.workflowID}`;
|
||||
|
||||
@@ -468,16 +468,16 @@ export const SCENE_RESPONSE_MAP = {
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region ---------------------- 业务方无需关注的部分 ----------------------
|
||||
// #Region ---------------------- part that business parties don't need to pay attention to ----------------------
|
||||
|
||||
/**
|
||||
* 辅助类型
|
||||
* auxiliary type
|
||||
*
|
||||
* 该类型实现以下几件事:
|
||||
* 1. 检查 SceneType 是否被遍历,有遗漏则会报错
|
||||
* 2. 为回调方法注入参数类型
|
||||
* 3. 约束响应值必须包含某些字段(比如 url),否则报错
|
||||
* 4. 正确推导响应值的具体类型,便于后续使用
|
||||
* This type implements the following things:
|
||||
* 1. Check whether SceneType is traversed. If there is any omission, an error will be reported.
|
||||
* 2. Inject parameter types for callback methods
|
||||
* 3. Constraint The response value must contain certain fields (such as url), otherwise an error will be reported
|
||||
* 4. Correctly derive the specific type of the response value for subsequent use
|
||||
*/
|
||||
type SceneResponseConstraint = {
|
||||
[K in SceneType]: (
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
import { isNil } from 'lodash-es';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
export { PageType, SceneType };
|
||||
|
||||
/**
|
||||
* 页面跳转 hook
|
||||
* Page redirect hook
|
||||
*
|
||||
* @example
|
||||
* const pageJump = usePageJump();
|
||||
@@ -41,8 +41,8 @@ export function usePageJumpService(): {
|
||||
const navigate = useNavigate();
|
||||
return {
|
||||
jump: <T extends SceneType>(sceneType: T, param?: SceneParamTypeMap<T>) => {
|
||||
// eslint-disable-next-line max-len -- eslint 注释格式限制,不得不超出 max-len
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-empty-function -- 1.内部类型难以推导,不影响外侧类型约束和推导 2.只是获取 url,不会使用第二个参数,空函数仅用于解决类型错误,不影响使用,更不影响调用侧类型约束和推导
|
||||
// eslint-disable-next-line max-len -- eslint comment format limit, have to exceed max-len
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-empty-function -- 1. The internal type is difficult to derive and does not affect the outer type constraint and derivation 2. Just get the url, do not use the second parameter, the empty function is only used to solve the type error, does not affect the use, and does not affect the call side type constraint and derivation
|
||||
const { url } = SCENE_RESPONSE_MAP[sceneType](param as any, () => {});
|
||||
|
||||
if (!url) {
|
||||
@@ -61,19 +61,19 @@ export function usePageJumpService(): {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前页面的响应值
|
||||
* Get the response value of the current page
|
||||
*
|
||||
* 如果当前页面可能有多种场景,那么返回值将是这些场景响应值的 union,需要在业务代码中根据 `scene` 来做 type narrowing
|
||||
* If the current page may have multiple scenes, then the return value will be the union of the response values of these scenes. You need to do type narrowing according to the'scene 'in the business code.
|
||||
*
|
||||
* 当没接收到场景值 或接收到的场景值与页面不匹配时,返回 null
|
||||
* Returns null when no scene value is received, or if the received scene value does not match the page
|
||||
*
|
||||
* 注意:即使页面刷新后也会保留该响应值,若不希望刷新后也保留,需要调用 clearScene() 方法
|
||||
* Note: The response value will be retained even after the page is refreshed. If you don't want it to be retained after the refresh, you need to call the clearScene () method
|
||||
*
|
||||
* @example
|
||||
* const routeResponse = usePageResponse(PageType.WORKFLOW);
|
||||
* // 此时只知道是 workflow 页面,但场景可能是 查看或创建
|
||||
* //At this time, only the workflow page is known, but the scene may be, view or createw page is known, but the scene may be viewed or created
|
||||
* if (routeResponse.scene === SceneType.BOT_CREATE_WORKFLOW) {
|
||||
* // 此时 routeResponse 能被推导为 BOT_CREATE_WORKFLOW 场景的响应值
|
||||
* //At this point routeResponse can be derived as the response value of the BOT_CREATE_WORKFLOW scene response value of the BOT_CREATE_WORKFLOW scene
|
||||
* }
|
||||
*/
|
||||
export function usePageJumpResponse<P extends PageType>(
|
||||
@@ -92,7 +92,7 @@ export function usePageJumpResponse<P extends PageType>(
|
||||
}
|
||||
|
||||
if (!(validScenes as SceneType[]).includes(param?.scene)) {
|
||||
// route state 传来的场景枚举值,并不存在于调用方声明的页面中
|
||||
// The scene enumeration value from route state does not exist in the page declared by the caller
|
||||
console.error(
|
||||
"got wrong route state: this page doesn't have the scene passed by route param",
|
||||
);
|
||||
@@ -100,58 +100,58 @@ export function usePageJumpResponse<P extends PageType>(
|
||||
}
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- 内部类型难以推导,不影响调用侧类型推导
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- internal types are difficult to derive, does not affect call-side type derivation
|
||||
...SCENE_RESPONSE_MAP[param.scene](param as any, jump),
|
||||
scene: param.scene,
|
||||
clearScene: (forceRerender = false) => {
|
||||
if (forceRerender) {
|
||||
// 清除 history.state 之后 rerender,useLocation 依然能取到清除前的值,应该是 react-router-dom 做了缓存
|
||||
// 搜索发现做一次 replace navigate 可解,且测试发现并不会导致组件重新挂载,只会 rerender
|
||||
// After clearing history.state, rerender, useLocation can still get the value before clearing, it should be cached by react-router-dom
|
||||
// Search discovery can be solved by doing a replace navigate, and test discovery does not cause the component to be remounted, only rerendered
|
||||
navigate(location.pathname, { replace: true });
|
||||
return;
|
||||
}
|
||||
history.replaceState({}, '');
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- 内部类型难以推导,不影响调用侧类型推导
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- internal types are difficult to derive, does not affect call-side type derivation
|
||||
} as any;
|
||||
}
|
||||
|
||||
/**
|
||||
* usePageJumpResponse().jump 的类型
|
||||
* usePageJumpResponse ().jump type
|
||||
*
|
||||
* 因为要复用,所以单独声明一下
|
||||
* Because it needs to be reused, it is declared separately.
|
||||
*/
|
||||
export interface PageJumpExecFunc {
|
||||
/**
|
||||
* @param sceneType 场景
|
||||
* @param param 用户输入场景后,能推导出对应的 param 类型作为约束,若该场景无参数,则可不传 param
|
||||
* @param sceneType
|
||||
* @Param param After the user enters the scene, the corresponding param type can be derived as a constraint. If the scene has no parameters, no param can be passed.
|
||||
*/
|
||||
<T extends SceneWithNoParam>(sceneType: T): void;
|
||||
// eslint-disable-next-line @typescript-eslint/unified-signatures -- 报错有问题,不应该合并声明
|
||||
// eslint-disable-next-line @typescript-eslint/unified-signatures -- There is a problem with the error, the declaration should not be merged
|
||||
<T extends SceneType>(sceneType: T, param: SceneParamTypeMap<T>): void;
|
||||
}
|
||||
|
||||
/** 返回 P 页面下可能的场景 */
|
||||
/** Return to the possible scenarios under the P page */
|
||||
type PageSceneUnion<P extends PageType> = (typeof PAGE_SCENE_MAP)[P][number];
|
||||
/**
|
||||
* 获取场景的响应值类型
|
||||
* Get the response value type of the scene
|
||||
*
|
||||
* 利用 distributive condition 特性,将返回的类型拆分为 union
|
||||
* 以便业务中利用 discriminated union 特性通过判断 scene 来实现 type narrowing
|
||||
* Use the distributive condition property to split the returned type into union.
|
||||
* In order to use the discriminated union feature in the business to realize type narrowing by judging the scene
|
||||
*/
|
||||
export type SceneResponseType<T extends SceneType> = T extends SceneType
|
||||
? Omit<ReturnType<(typeof SCENE_RESPONSE_MAP)[T]>, 'url'> & {
|
||||
scene: T;
|
||||
/**
|
||||
* 清除当前页面绑定的一切跳转数据
|
||||
* @param forceRefresh 是否即时清空。
|
||||
* 默认需要刷新才能清空(由于 react-router-dom 的原因,即使 rerender 也会获取到清空前的响应值);
|
||||
* 传 true 时会调用一次 replace navigate,触发 rerender 并且不会再获取到响应值(不会触发组件 unmount)
|
||||
* Clear all jump data bound to the current page
|
||||
* @Param forceRefresh is emptied instantly.
|
||||
* By default, it needs to be refreshed to clear (due to react-router-dom, even rerender will get the response value before clearing);
|
||||
* When passing true, replace navigate will be called once, triggering rerender and no response value will be obtained (no component unmount will be triggered).
|
||||
*/
|
||||
clearScene: (forceRefresh?: boolean) => void;
|
||||
}
|
||||
: never;
|
||||
/** 筛选出没有参数的场景 */
|
||||
/** Filter out scenes without parameters */
|
||||
type SceneWithNoParam = SceneType extends infer P
|
||||
? P extends SceneType
|
||||
? SceneParamTypeMap<P> extends undefined
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import { AxiosError, type AxiosResponse } from 'axios';
|
||||
import { logger } from '@coze-arch/logger';
|
||||
|
||||
// 上报事件枚举
|
||||
// Enumeration of reported events
|
||||
export enum ReportEventNames {
|
||||
NetworkError = 'flow-infra-network-error',
|
||||
ApiError = 'flow-infra-api-error',
|
||||
@@ -52,7 +52,7 @@ export class ApiError extends AxiosError {
|
||||
export const isApiError = (error: unknown): error is ApiError =>
|
||||
error instanceof ApiError;
|
||||
|
||||
// 上报http错误,apiError&axiosError
|
||||
// Report http errors, apiError & axiosError
|
||||
export const reportHttpError = (
|
||||
eventName: ReportEventNames,
|
||||
error: AxiosError,
|
||||
|
||||
@@ -55,7 +55,7 @@ axiosInstance.interceptors.response.use(
|
||||
});
|
||||
const { data = {} } = response;
|
||||
|
||||
// 新增接口返回message字段
|
||||
// Added interface return message field
|
||||
const { code, msg, message } = data;
|
||||
|
||||
if (code !== 0) {
|
||||
@@ -107,7 +107,7 @@ axiosInstance.interceptors.response.use(
|
||||
if (isAxiosError(error)) {
|
||||
reportHttpError(ReportEventNames.NetworkError, error);
|
||||
if (error.response?.status === HTTP_STATUS_COE_UNAUTHORIZED) {
|
||||
// 401 身份过期&没有身份
|
||||
// 401 Identity Expired & No Identity
|
||||
if (typeof error.response.data === 'object') {
|
||||
const unauthorizedData = error.response.data as UnauthorizedResponse;
|
||||
const redirectUri = unauthorizedData?.data?.redirect_uri;
|
||||
@@ -141,10 +141,10 @@ axiosInstance.interceptors.request.use(config => {
|
||||
['post', 'get'].includes(config.method?.toLowerCase() ?? '') &&
|
||||
!getHeader('content-type')
|
||||
) {
|
||||
// 新的 csrf 防护需要 post/get 请求全部带上这个 header
|
||||
// The new CSRF protection requires all post/get requests to have this header.
|
||||
setHeader('content-type', 'application/json');
|
||||
if (!config.data) {
|
||||
// axios 会自动在 data 为空时清除 content-type,所以需要设置一个空对象
|
||||
// Axios will automatically clear the content-type when the data is empty, so you need to set an empty object
|
||||
config.data = {};
|
||||
}
|
||||
}
|
||||
@@ -153,15 +153,15 @@ axiosInstance.interceptors.request.use(config => {
|
||||
|
||||
type AddRequestInterceptorShape = typeof axiosInstance.interceptors.request.use;
|
||||
/**
|
||||
* 添加全局 axios 的 interceptor 处理器,方便在上层扩展 axios 行为。
|
||||
* 请注意,该接口会影响所有 bot-http 下的请求,请注意保证行为的稳定性
|
||||
* Add an interceptor handler for global axios to easily extend axios behavior on top.
|
||||
* Please note that this interface will affect all requests under bot-http. Please ensure the stability of the behavior
|
||||
*/
|
||||
export const addGlobalRequestInterceptor: AddRequestInterceptorShape = (
|
||||
onFulfilled,
|
||||
onRejected?,
|
||||
) => {
|
||||
// PS: 这里不期望直接暴露 axios 实例到上层,因为不知道会被怎么修改使用
|
||||
// 因此,这里需要暴露若干方法,将行为与副作用限制在可控范围内
|
||||
// PS: It is not expected to directly expose the axios instance to the upper layer, because it is not known how it will be modified and used
|
||||
// Therefore, several methods need to be exposed to keep behavior and side effects under control
|
||||
const id = axiosInstance.interceptors.request.use(onFulfilled, onRejected);
|
||||
return id;
|
||||
};
|
||||
@@ -169,7 +169,7 @@ export const addGlobalRequestInterceptor: AddRequestInterceptorShape = (
|
||||
type RemoveRequestInterceptorShape =
|
||||
typeof axiosInstance.interceptors.request.eject;
|
||||
/**
|
||||
* 删除全局 axios 的 interceptor 处理器,其中,id 参数为调用 addGlobalRequestInterceptor 返回的值
|
||||
* Removes the interceptor handler of the global axios where the id parameter is the value returned by the calling addGlobalRequestInterceptor
|
||||
*/
|
||||
export const removeGlobalRequestInterceptor: RemoveRequestInterceptorShape = (
|
||||
id: number,
|
||||
|
||||
@@ -16,17 +16,17 @@
|
||||
|
||||
import { GlobalEventBus } from '@coze-arch/web-context';
|
||||
|
||||
// api 请求有关事件
|
||||
// API request related events
|
||||
export enum APIErrorEvent {
|
||||
// 无登录状态
|
||||
// No login status
|
||||
UNAUTHORIZED = 'unauthorized',
|
||||
// 登录了 没权限
|
||||
// Logged in, no permission.
|
||||
NOACCESS = 'noAccess',
|
||||
// 风控拦截
|
||||
// Risk control interception
|
||||
SHARK_BLOCK = 'sharkBlocked',
|
||||
// 国家限制
|
||||
// State restrictions
|
||||
COUNTRY_RESTRICTED = 'countryRestricted',
|
||||
// COZE TOKEN 不足
|
||||
// Insufficient COZE TOKEN
|
||||
COZE_TOKEN_INSUFFICIENT = 'cozeTokenInsufficient',
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
interface Window {
|
||||
/**
|
||||
* tea 实例
|
||||
* Tea example
|
||||
*/
|
||||
Tea?: any;
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ export type SpaceRequest<T> = Omit<T, 'space_id'>;
|
||||
|
||||
type D = DeveloperApiService<BotAPIRequestConfig>;
|
||||
|
||||
// 这些是暴露出来需要被调用的函数列表
|
||||
// 新增函数请在该列表后面追加即可
|
||||
// This is the exposed list of functions that need to be called
|
||||
// To add new functions, please add them after the list.
|
||||
type ExportFunctions =
|
||||
| 'GetPlaygroundPluginList'
|
||||
| 'GetDraftBotList'
|
||||
@@ -85,7 +85,7 @@ type ExportFunctions =
|
||||
|
||||
type ExportService = {
|
||||
[K in ExportFunctions]: (
|
||||
// 这里主要是为了 omit 掉 space_id 这个参数,而做的二次封装
|
||||
// Here is mainly to omit the space_id this parameter, and do the secondary encapsulation
|
||||
params: SpaceRequest<Parameters<D[K]>[0]>,
|
||||
options?: Parameters<D[K]>[1],
|
||||
) => ReturnType<D[K]>;
|
||||
|
||||
@@ -60,7 +60,7 @@ type ExportSpaceService = {
|
||||
|
||||
const getSpaceId = () => useSpaceStore.getState().getSpaceId();
|
||||
|
||||
// 需要注入store space id的api
|
||||
// API that needs to store space id
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export const SpaceApiV2 = new Proxy(Object.create(null), {
|
||||
get(_, funcName: ApiType) {
|
||||
|
||||
@@ -26,10 +26,10 @@ import {
|
||||
|
||||
import { useAuthStore } from '../../src/auth';
|
||||
|
||||
// 模拟全局变量
|
||||
// simulated global variable
|
||||
vi.stubGlobal('IS_DEV_MODE', false);
|
||||
|
||||
// 模拟依赖
|
||||
// simulated dependency
|
||||
vi.mock('@coze-arch/bot-api', () => ({
|
||||
PlaygroundApi: {
|
||||
DraftBotCollaboration: vi.fn().mockResolvedValue({
|
||||
@@ -75,7 +75,7 @@ vi.mock('@coze-arch/bot-api', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
// 模拟 logger
|
||||
// Analog logger
|
||||
vi.mock('@coze-arch/logger', () => ({
|
||||
reporter: {
|
||||
error: vi.fn(),
|
||||
@@ -85,7 +85,7 @@ vi.mock('@coze-arch/logger', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
// 模拟 CustomError
|
||||
// Simulate CustomError
|
||||
vi.mock('@coze-arch/bot-error', () => ({
|
||||
CustomError: vi.fn(),
|
||||
}));
|
||||
@@ -107,7 +107,7 @@ describe('auth', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
// 重置 store 状态
|
||||
// Reset store state
|
||||
act(() => {
|
||||
useAuthStore.setState({
|
||||
collaboratorsMap: {
|
||||
@@ -115,12 +115,12 @@ describe('auth', () => {
|
||||
[ResourceType.Workflow]: {},
|
||||
[ResourceType.Intelligence]: {},
|
||||
} as any,
|
||||
// 确保 getCachedCollaborators 方法返回一个空数组
|
||||
// Ensure getCachedCollaborators method returns an empty array
|
||||
getCachedCollaborators: vi.fn().mockReturnValue([]),
|
||||
});
|
||||
});
|
||||
|
||||
// 模拟 API 响应
|
||||
// Simulate API response
|
||||
(PlaygroundApi.DraftBotCollaboration as any).mockResolvedValue({
|
||||
data: mockCreators,
|
||||
});
|
||||
@@ -149,7 +149,7 @@ describe('auth', () => {
|
||||
it('当缓存中有协作者时应该返回缓存的协作者', () => {
|
||||
const { result } = renderHook(() => useAuthStore());
|
||||
|
||||
// 先设置缓存
|
||||
// Set up the cache first
|
||||
act(() => {
|
||||
useAuthStore.setState({
|
||||
collaboratorsMap: {
|
||||
@@ -267,7 +267,7 @@ describe('auth', () => {
|
||||
it('当资源类型为 Bot 时应该调用 patPermissionApi.AddCollaborator', async () => {
|
||||
const { result } = renderHook(() => useAuthStore());
|
||||
|
||||
// 确保 getCachedCollaborators 返回一个空数组
|
||||
// Make sure getCachedCollaborators return an empty array
|
||||
vi.spyOn(result.current, 'getCachedCollaborators').mockReturnValue([]);
|
||||
|
||||
(patPermissionApi.AddCollaborator as any).mockResolvedValue({});
|
||||
@@ -380,7 +380,7 @@ describe('auth', () => {
|
||||
it('当资源类型为 Bot 时应该调用 patPermissionApi.AddCollaborator', async () => {
|
||||
const { result } = renderHook(() => useAuthStore());
|
||||
|
||||
// 确保 getCachedCollaborators 返回一个空数组
|
||||
// Make sure getCachedCollaborators return an empty array
|
||||
vi.spyOn(result.current, 'getCachedCollaborators').mockReturnValue([]);
|
||||
|
||||
const mockUsers = [
|
||||
|
||||
@@ -21,11 +21,11 @@ import { workflowApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { useSpaceGrayStore, TccKey } from '../../src/space-gray';
|
||||
|
||||
// 模拟全局变量
|
||||
// simulated global variable
|
||||
vi.stubGlobal('IS_DEV_MODE', false);
|
||||
vi.stubGlobal('IS_BOT_OP', false);
|
||||
|
||||
// 模拟依赖
|
||||
// simulated dependency
|
||||
vi.mock('@coze-arch/logger', () => ({
|
||||
reporter: {
|
||||
error: vi.fn(),
|
||||
@@ -51,7 +51,7 @@ describe('space-gray', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
// 重置 store 状态
|
||||
// Reset store state
|
||||
act(() => {
|
||||
useSpaceGrayStore.setState({
|
||||
spaceId: '',
|
||||
@@ -59,7 +59,7 @@ describe('space-gray', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// 模拟 API 响应
|
||||
// Simulate API response
|
||||
(workflowApi.GetWorkflowGrayFeature as any).mockResolvedValue({
|
||||
data: mockFeatureItems,
|
||||
});
|
||||
@@ -104,7 +104,7 @@ describe('space-gray', () => {
|
||||
});
|
||||
|
||||
it('当 IS_BOT_OP 为 true 时应该调用 OPGetWorkflowGrayFeature API', async () => {
|
||||
// 设置 IS_BOT_OP 为 true
|
||||
// Set IS_BOT_OP to true
|
||||
vi.stubGlobal('IS_BOT_OP', true);
|
||||
|
||||
const { result } = renderHook(() => useSpaceGrayStore());
|
||||
@@ -120,22 +120,22 @@ describe('space-gray', () => {
|
||||
expect(result.current.spaceId).toBe(mockSpaceId);
|
||||
expect(result.current.grayFeatureItems).toEqual(mockFeatureItems);
|
||||
|
||||
// 恢复 IS_BOT_OP 为 false
|
||||
// Restore IS_BOT_OP to false
|
||||
vi.stubGlobal('IS_BOT_OP', false);
|
||||
});
|
||||
|
||||
it('当 spaceId 与缓存的相同时不应该调用 API', async () => {
|
||||
const { result } = renderHook(() => useSpaceGrayStore());
|
||||
|
||||
// 先加载一次
|
||||
// Load once first
|
||||
await act(async () => {
|
||||
await result.current.load(mockSpaceId);
|
||||
});
|
||||
|
||||
// 清除之前的调用记录
|
||||
// Clear previous call history
|
||||
vi.clearAllMocks();
|
||||
|
||||
// 再次加载相同的 spaceId
|
||||
// Load the same spaceId again.
|
||||
await act(async () => {
|
||||
await result.current.load(mockSpaceId);
|
||||
});
|
||||
@@ -167,7 +167,7 @@ describe('space-gray', () => {
|
||||
it('当特性在灰度列表中且 in_gray 为 true 时应该返回 true', async () => {
|
||||
const { result } = renderHook(() => useSpaceGrayStore());
|
||||
|
||||
// 先加载灰度特性
|
||||
// Load grey release feature first
|
||||
await act(async () => {
|
||||
await result.current.load(mockSpaceId);
|
||||
});
|
||||
@@ -193,7 +193,7 @@ describe('space-gray', () => {
|
||||
|
||||
const { result } = renderHook(() => useSpaceGrayStore());
|
||||
|
||||
// 先加载灰度特性
|
||||
// Load grey release feature first
|
||||
await act(async () => {
|
||||
await result.current.load(mockSpaceId);
|
||||
});
|
||||
@@ -208,12 +208,12 @@ describe('space-gray', () => {
|
||||
it('当特性不在灰度列表中时应该返回 false', async () => {
|
||||
const { result } = renderHook(() => useSpaceGrayStore());
|
||||
|
||||
// 先加载灰度特性
|
||||
// Load grey release feature first
|
||||
await act(async () => {
|
||||
await result.current.load(mockSpaceId);
|
||||
});
|
||||
|
||||
// 使用一个不存在的 key
|
||||
// Use a non-existent key
|
||||
const isHit = result.current.isHitSpaceGray('NonExistentKey' as TccKey);
|
||||
|
||||
expect(isHit).toBe(false);
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
|
||||
// 导入被测试的模块
|
||||
// Import the tested module
|
||||
import localForage from 'localforage';
|
||||
|
||||
import { getStorage, clearStorage } from '../../src/utils/get-storage';
|
||||
|
||||
@@ -36,10 +36,10 @@ import {
|
||||
} from '@coze-arch/bot-api';
|
||||
|
||||
interface AuthStoreState {
|
||||
/* 两层map
|
||||
/* Two layer map
|
||||
{
|
||||
资源类型: {
|
||||
资源ID: 协作者
|
||||
Resource Type: {
|
||||
Resource ID: Collaborator
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -78,15 +78,15 @@ interface AuthStoreAction {
|
||||
resource: ResourceIdentifier;
|
||||
users: Creator[];
|
||||
options?: BotAPIRequestConfig;
|
||||
// 第三个参数是error code
|
||||
// The third argument is the error code
|
||||
roles?: Array<CollaboratorType>;
|
||||
}) => Promise<[Creator[], Creator[], number]>;
|
||||
// permission 服务新增的批量添加接口
|
||||
// New batch addition interface for permission service
|
||||
batchAddCollaboratorsServer: (params: {
|
||||
resource: ResourceIdentifier;
|
||||
users: Creator[];
|
||||
options?: BotAPIRequestConfig;
|
||||
// 第三个参数是error code
|
||||
// The third argument is the error code
|
||||
roles?: Array<CollaboratorType>;
|
||||
}) => Promise<boolean>;
|
||||
}
|
||||
@@ -224,7 +224,7 @@ export const useAuthStore = create<AuthStoreState & AuthStoreAction>()(
|
||||
},
|
||||
}));
|
||||
},
|
||||
// 暂时由前端批量处理
|
||||
// Temporarily batch processed by the front end
|
||||
batchRemoveCollaborators: async (resource, userIds, options) => {
|
||||
const resultArr = await Promise.all(
|
||||
userIds.map(
|
||||
@@ -343,14 +343,14 @@ export const useAuthStore = create<AuthStoreState & AuthStoreAction>()(
|
||||
),
|
||||
),
|
||||
);
|
||||
// 目前的批量实现需要对单个添加的接口的code进行排序,拿到最高优先级的message来透出
|
||||
// Current batch implementations need to sort the code of individual added interfaces to get the highest priority message to reveal
|
||||
let errorCode = 0;
|
||||
const [addedUsers, failedUsers] = resultArr.reduce<
|
||||
[Creator[], Creator[]]
|
||||
>(
|
||||
([r, f], finish, index) => {
|
||||
const user = users[index];
|
||||
// 这么写是为了ts能正确类型推导。ts@5.0.4
|
||||
// This is written so that ts can derive the correct type. ts@5.0.4
|
||||
if (finish.result === true) {
|
||||
return [[...r, user], f];
|
||||
}
|
||||
@@ -360,12 +360,12 @@ export const useAuthStore = create<AuthStoreState & AuthStoreAction>()(
|
||||
message?: string;
|
||||
msg?: string;
|
||||
};
|
||||
// 比较code
|
||||
// Comparison code
|
||||
if (Number(error.code) > errorCode) {
|
||||
errorCode = Number(error.code);
|
||||
}
|
||||
}
|
||||
// 错误时,需要比较code然后复制message
|
||||
// Error, you need to compare the code and then copy the message
|
||||
return [r, [...f, user]];
|
||||
},
|
||||
[[], []],
|
||||
|
||||
@@ -33,7 +33,7 @@ declare namespace DataItem {
|
||||
interface UserInfo {
|
||||
app_id: number;
|
||||
/**
|
||||
* @deprecated 会因为溢出丢失精度,使用 user_id_str
|
||||
* @Deprecated will lose precision due to overflow, use user_id_str
|
||||
*/
|
||||
user_id: number;
|
||||
user_id_str: string;
|
||||
@@ -109,7 +109,7 @@ declare namespace DataItem {
|
||||
name?: string;
|
||||
[key?: string]: unknown;
|
||||
}; // Record<string, unknown>;
|
||||
// int值。1审核中,2审核通过,3审核不通过
|
||||
// int value. 1 During the review, 2 passed the review, and 3 failed the review.
|
||||
audit_status: number;
|
||||
details: Record<string, unknown>;
|
||||
is_auditing: boolean;
|
||||
@@ -119,7 +119,7 @@ declare namespace DataItem {
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送验证码的返回数据结构
|
||||
* The return data structure for sending the verification code
|
||||
*/
|
||||
interface SendCodeData {
|
||||
mobile: string;
|
||||
|
||||
@@ -15,17 +15,17 @@
|
||||
*/
|
||||
|
||||
export {
|
||||
/** @deprecated 该使用方式已废弃,后续请使用@coze-arch/foundation-sdk导出的方法*/
|
||||
/** @Deprecated This usage method is deprecated, please use the method exported by @code-arch/foundation-sdk in the future*/
|
||||
useSpaceStore,
|
||||
/** @deprecated 该使用方式已废弃,后续请使用@coze-arch/foundation-sdk导出的方法*/
|
||||
/** @Deprecated This usage method is deprecated, please use the method exported by @code-arch/foundation-sdk in the future*/
|
||||
useSpace,
|
||||
/** @deprecated 该使用方式已废弃,后续请使用@coze-arch/foundation-sdk导出的方法*/
|
||||
/** @Deprecated This usage method is deprecated, please use the method exported by @code-arch/foundation-sdk in the future*/
|
||||
useSpaceList,
|
||||
} from '@coze-foundation/space-store';
|
||||
|
||||
export { useAuthStore } from './auth';
|
||||
|
||||
/** @deprecated - 持久化方案有问题,废弃 */
|
||||
/** @Deprecated - problem with persistence scheme, deprecated */
|
||||
export { clearStorage } from './utils/get-storage';
|
||||
|
||||
export { useSpaceGrayStore, TccKey } from './space-gray';
|
||||
|
||||
@@ -58,7 +58,7 @@ const fetchTccConfig = async spaceId => {
|
||||
}
|
||||
};
|
||||
|
||||
/* 通过 tcc 动态配置的 space 粒度的灰度 */
|
||||
/* Dynamically configured grey release of space granularity via tcc */
|
||||
export const useSpaceGrayStore = create<TccStore & TccAction>()(
|
||||
devtools(
|
||||
(set, get) => ({
|
||||
|
||||
@@ -26,7 +26,7 @@ const instance = localForage.createInstance({
|
||||
const throttleTime = 1000;
|
||||
|
||||
/**
|
||||
* 获取store数据持久化引擎
|
||||
* Get stored data persistence engine
|
||||
*/
|
||||
export const getStorage = (): StateStorage => {
|
||||
const persistStorage: StateStorage = {
|
||||
@@ -42,5 +42,5 @@ export const getStorage = (): StateStorage => {
|
||||
return persistStorage;
|
||||
};
|
||||
|
||||
/** @deprecated - 持久化方案有问题,废弃 */
|
||||
/** @Deprecated - problem with persistence scheme, deprecated */
|
||||
export const clearStorage = instance.clear;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
import { vi, beforeEach, describe, it, expect } from 'vitest';
|
||||
import { logger } from '@coze-arch/logger';
|
||||
|
||||
@@ -36,7 +36,6 @@ vi.mock('@coze-arch/logger', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
describe('bot-tea', () => {
|
||||
const mockLocation = 'https://example.com/test';
|
||||
const mockSessionStorage = {
|
||||
@@ -101,7 +100,7 @@ describe('bot-tea', () => {
|
||||
const mockParams = { foo: 'bar' };
|
||||
|
||||
it('should send event with UG params when FEATURE_ENABLE_TEA_UG is true', () => {
|
||||
// @ts-expect-error - 模拟全局变量
|
||||
// @ts-expect-error - simulate global variables
|
||||
window.FEATURE_ENABLE_TEA_UG = true;
|
||||
const savedUrl = 'https://example.com/saved';
|
||||
mockSessionStorage.getItem.mockReturnValue(savedUrl);
|
||||
@@ -125,7 +124,7 @@ describe('bot-tea', () => {
|
||||
});
|
||||
|
||||
it('should send event without UG params when FEATURE_ENABLE_TEA_UG is false', () => {
|
||||
// @ts-expect-error - 模拟全局变量
|
||||
// @ts-expect-error - simulate global variables
|
||||
window.FEATURE_ENABLE_TEA_UG = false;
|
||||
|
||||
sendTeaEvent(mockEvent, mockParams);
|
||||
@@ -140,7 +139,7 @@ describe('bot-tea', () => {
|
||||
});
|
||||
|
||||
it('should handle undefined params', () => {
|
||||
// @ts-expect-error - 模拟全局变量
|
||||
// @ts-expect-error - simulate global variables
|
||||
window.FEATURE_ENABLE_TEA_UG = false;
|
||||
|
||||
sendTeaEvent(mockEvent);
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
import TeaNew, {
|
||||
type EVENT_NAMES,
|
||||
type UserGrowthEventParams,
|
||||
@@ -50,9 +50,9 @@ export {
|
||||
export const LANDING_PAGE_URL_KEY = 'coze_landing_page_url';
|
||||
|
||||
/**
|
||||
* UG 期望上报的 LandingPageUrl 是“网民最初点击到的页面完整 URL”,
|
||||
* 即使打开了新的页面,也应该上报第一次打开的落地页 URL
|
||||
*
|
||||
* The LandingPageUrl that UG expects to report is "the full URL of the page that netizens initially clicked on."
|
||||
* Even if you open a new page, you should report the URL of the landing page you opened for the first time.
|
||||
*
|
||||
*/
|
||||
export const initBotLandingPageUrl = () => {
|
||||
const saved = window.sessionStorage.getItem(LANDING_PAGE_URL_KEY);
|
||||
@@ -74,14 +74,14 @@ export const sendTeaEvent = <TEventName extends EVENT_NAMES>(
|
||||
if (FEATURE_ENABLE_TEA_UG) {
|
||||
const ugParams: UserGrowthEventParams = {
|
||||
LandingPageUrl: getBotLandingPageUrl(),
|
||||
// 与 UG 约定的 AppId,固定值
|
||||
// AppId agreed with UG, fixed value
|
||||
AppId: 510023,
|
||||
EventName: event,
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers -- 秒时间戳
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers -- timestamp
|
||||
EventTs: Math.floor(Date.now() / 1000),
|
||||
growth_deepevent: '4',
|
||||
};
|
||||
// @ts-expect-error -- UG 额外参数
|
||||
// @ts-expect-error -- UG extra parameters
|
||||
params = { ...ugParams, ...(rawParams ?? {}) };
|
||||
}
|
||||
logger.info({
|
||||
|
||||
@@ -13,20 +13,20 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/** bot详情页来源:目前只有bot和explore列表 */
|
||||
|
||||
/** Bot details page source: currently only bot and explore list */
|
||||
export enum BotPageFromEnum {
|
||||
Bot = 'bot', //bot列表
|
||||
Explore = 'explore', //explore列表
|
||||
Bot = 'bot', //bot list
|
||||
Explore = 'explore', //Explore List
|
||||
Store = 'store',
|
||||
Template = 'template',
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- 不得不 any
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- had to any
|
||||
export type Obj = Record<string, any>;
|
||||
|
||||
/**
|
||||
* 展示完整类型
|
||||
* Show the full type
|
||||
*
|
||||
* @example
|
||||
* type Intersection = { a: string } & { b: number };
|
||||
@@ -38,7 +38,7 @@ export type Expand<T extends Obj> = T extends infer U
|
||||
: never;
|
||||
|
||||
/**
|
||||
* 只对特定字段做 required,常用于修正服务端类型声明错误
|
||||
* Required only for specific fields, often used to correct server level type declaration errors
|
||||
*
|
||||
* @example
|
||||
* interface Agent {
|
||||
|
||||
@@ -33,7 +33,7 @@ declare namespace DataItem {
|
||||
interface UserInfo {
|
||||
app_id: number;
|
||||
/**
|
||||
* @deprecated 会因为溢出丢失精度,使用 user_id_str
|
||||
* @Deprecated will lose precision due to overflow, use user_id_str
|
||||
*/
|
||||
user_id: number;
|
||||
user_id_str: string;
|
||||
@@ -109,7 +109,7 @@ declare namespace DataItem {
|
||||
name?: string;
|
||||
[key: string]: unknown;
|
||||
}; // Record<string, unknown>;
|
||||
// int值。1审核中,2审核通过,3审核不通过
|
||||
// int value. 1 During the review, 2 passed the review, and 3 failed the review.
|
||||
audit_status: 1 | 2 | 3;
|
||||
details: Record<string, unknown>;
|
||||
is_auditing: boolean;
|
||||
@@ -119,7 +119,7 @@ declare namespace DataItem {
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送验证码的返回数据结构
|
||||
* The return data structure for sending the verification code
|
||||
*/
|
||||
interface SendCodeData {
|
||||
mobile: string;
|
||||
|
||||
@@ -28,7 +28,7 @@ export interface DynamicParams extends Record<string, string | undefined> {
|
||||
mock_set_id?: string;
|
||||
conversation_id: string;
|
||||
commit_version?: string;
|
||||
/** 社会场景 */
|
||||
/** social scene */
|
||||
scene_id?: string;
|
||||
post_id?: string;
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ declare type MicroComponentsMapItem = {
|
||||
|
||||
interface Window {
|
||||
/**
|
||||
* IDE plugin iframe 中挂载的用于卸载的方法
|
||||
* IDE plugin iframe mount method for unmounting
|
||||
*/
|
||||
editorDispose?: any;
|
||||
MonacoEnvironment?: any;
|
||||
|
||||
@@ -56,13 +56,13 @@ describe('Date', () => {
|
||||
expect(getCurrentTZ().utcOffset()).toBe(60 * 8);
|
||||
});
|
||||
it('#formatDate', () => {
|
||||
// 使用固定的时间戳,但验证格式而不是具体的时区值
|
||||
// Uses a fixed timestamp, but verifies the format rather than a specific timezone value
|
||||
const timestamp = 1718782764;
|
||||
const date = formatDate(timestamp);
|
||||
// 验证格式是否正确:YYYY/MM/DD HH:mm:ss
|
||||
// Verify that the format is correct: YYYY/MM/DD HH: mm: ss
|
||||
expect(date).toMatch(/^\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}$/);
|
||||
|
||||
// 验证时间戳转换的一致性:格式化后再解析应该得到相同的dayjs对象的日期部分
|
||||
// Verify consistency of timestamp conversions: Formatting and parsing should result in the same date portion of the dayjs object
|
||||
const formattedDayjs = dayjs(date, 'YYYY/MM/DD HH:mm:ss');
|
||||
const originalDayjs = dayjs.unix(timestamp);
|
||||
expect(formattedDayjs.unix()).toBe(originalDayjs.unix());
|
||||
|
||||
@@ -15,13 +15,13 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
表格单元格宽度适配
|
||||
当 width 小于 WidthThresholds.Small 时应该返回 ColumnSize.Default。
|
||||
当 width 大于等于 WidthThresholds.Small 但小于 WidthThresholds.Medium 时应该返回 ColumnSize.Small。
|
||||
当 width 大于等于 WidthThresholds.Medium 但小于 WidthThresholds.Large 时应该返回 ColumnSize.Medium。
|
||||
当 width 大于等于 WidthThresholds.Large 时应该返回 ColumnSize.Large。
|
||||
当 minWidth 为 'auto' 时应该返回 'auto'。
|
||||
当 minWidth 是一个指定数字时,应该返回 minWidth 和 columnWidth 中较大的一个。*/
|
||||
Table cell width adaptation
|
||||
ColumnSize. Default should be returned when width is less than WidthThresholds. Small.
|
||||
ColumnSize. Small should be returned when width is greater than or equal to WidthThresholds. Small but less than WidthThresholds. Medium.
|
||||
ColumnSize. Medium should be returned when width is greater than or equal to WidthThresholds. Medium but less than WidthThresholds. Large.
|
||||
ColumnSize. Large should be returned when width is greater than or equal to WidthThresholds. Large.
|
||||
'Auto 'should be returned when minWidth is'auto'.
|
||||
When minWidth is a specified number, the larger of minWidth and columnWidth should be returned.*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
|
||||
@@ -25,12 +25,12 @@ export const ArrayUtil = {
|
||||
mapAndFilter,
|
||||
};
|
||||
|
||||
// region array2Map 重载声明
|
||||
// 和 OptionUtil.array2Map 虽然相似,但在用法和类型约束上还是很不一样的
|
||||
// Region array2Map Overload Statement
|
||||
// Although similar to OptionUtil.array2Map, it is still very different in terms of usage and type constraints
|
||||
/**
|
||||
* 将列表转化为 map
|
||||
* Convert a list to a map
|
||||
* @param items
|
||||
* @param key 指定 item[key] 作为 map 的键
|
||||
* @Param key Specifies item [key] as the key of the map
|
||||
* @example
|
||||
* const items = [{name: 'a', id: 1}];
|
||||
* array2Map(items, 'id');
|
||||
@@ -41,10 +41,10 @@ function array2Map<T extends Obj, K extends keyof T>(
|
||||
key: K,
|
||||
): Record<T[K], T>;
|
||||
/**
|
||||
* 将列表转化为 map
|
||||
* Convert a list to a map
|
||||
* @param items
|
||||
* @param key 指定 item[key] 作为 map 的键
|
||||
* @param value 指定 item[value] 作为 map 的值
|
||||
* @Param key Specifies item [key] as the key of the map
|
||||
* @Param value Specifies item [value] as the value of the map
|
||||
* @example
|
||||
* const items = [{name: 'a', id: 1}];
|
||||
* array2Map(items, 'id', 'name');
|
||||
@@ -56,10 +56,10 @@ function array2Map<T extends Obj, K extends keyof T, V extends keyof T>(
|
||||
value: V,
|
||||
): Record<T[K], T[V]>;
|
||||
/**
|
||||
* 将列表转化为 map
|
||||
* Convert a list to a map
|
||||
* @param items
|
||||
* @param key 指定 item[key] 作为 map 的键
|
||||
* @param value 获取值
|
||||
* @Param key Specifies item [key] as the key of the map
|
||||
* @param value get value
|
||||
* @example
|
||||
* const items = [{name: 'a', id: 1}];
|
||||
* array2Map(items, 'id', (item) => `${item.id}-${item.name}`);
|
||||
@@ -72,7 +72,7 @@ function array2Map<T extends Obj, K extends keyof T, V>(
|
||||
): Record<T[K], V>;
|
||||
// endregion
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/** 将列表转化为 map */
|
||||
/** Convert a list to a map */
|
||||
function array2Map<T extends Obj, K extends keyof T>(
|
||||
items: T[],
|
||||
key: K,
|
||||
@@ -111,10 +111,10 @@ function mapAndFilter<I = Obj, T = Obj>(
|
||||
const realValue = map ? map(currentValue) : currentValue;
|
||||
const filtered = filter ? filter(currentValue) : true;
|
||||
if (!filtered) {
|
||||
// 如果filtered是false,表示此项需要跳过
|
||||
// If filtered is false, this item needs to be skipped
|
||||
return previousValue;
|
||||
}
|
||||
// 如果filtered是true,表示需要加上此项
|
||||
// If filtered is true, it means that this item needs to be added
|
||||
return [...previousValue, realValue] as Array<I>;
|
||||
}, [] as Array<I>);
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ export const formatDate = (v: number, template = 'YYYY/MM/DD HH:mm:ss') =>
|
||||
|
||||
export const CHINESE_TIMEZONE = 'Asia/Shanghai';
|
||||
|
||||
// 根据地区判断 海外返回UTC时间,国内返回北京时间
|
||||
// According to the regional judgment, return to UTC time overseas, and return to Beijing time domestically.
|
||||
export const getCurrentTZ = (param?: ConfigType): Dayjs => {
|
||||
if (IS_OVERSEA) {
|
||||
return dayjs(param).utc(true);
|
||||
@@ -56,18 +56,18 @@ export const getCurrentTZ = (param?: ConfigType): Dayjs => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取dayjs add后的时间戳
|
||||
* Get timestamp after dayjs add
|
||||
*/
|
||||
export const getTimestampByAdd = (value: number, unit?: ManipulateType) =>
|
||||
dayjs().add(value, unit).unix();
|
||||
|
||||
/**
|
||||
* 获取当前的时间戳
|
||||
* Get the current timestamp
|
||||
*/
|
||||
export const getCurrentTimestamp = () => dayjs().unix();
|
||||
|
||||
/**
|
||||
* 获取当前时间到次日UTC0点的时间间隔,精确到分钟
|
||||
* Gets the time interval between the current time and UTC0 the next day, accurate to the minute
|
||||
* e.g. 12h 30m
|
||||
*/
|
||||
export const getRemainTime = () => {
|
||||
@@ -81,43 +81,43 @@ export const getRemainTime = () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* fork 自 packages/community/pages/src/bot/utils/index.ts
|
||||
* 将11位的时间戳按以下格式显示
|
||||
* 1. 不足一分钟, 显示”Just now“
|
||||
* 2. 不足1小时, 显示”{n}min ago“,例如 3min ago
|
||||
* 3. 不足1天,显示”{n}h ago",例如 3h ago
|
||||
* 4. 不足1个月,显示"{n}d ago", 例如 3d ago
|
||||
* 5. 超过1个月,显示“{MM}/{DD}/{yyyy}”,例如12/1/2024,中文是2024 年 12 月 1 日
|
||||
* Fork from packages/community/pages/src/bot/utils/index.ts
|
||||
* Display the 11-digit timestamp in the following format
|
||||
* 1. Less than a minute, showing "Just now"
|
||||
* 2. Less than 1 hour, showing "{n} min ago", such as 3min ago
|
||||
* 3. Less than 1 day, display "{n} h ago", such as 3h ago
|
||||
* 4. Less than 1 month, display "{n} d ago", such as 3d ago
|
||||
* 5. More than 1 month, display "{MM}/{DD}/{yyyy}", for example 12/1/2024, Chinese is December 1, 2024
|
||||
*
|
||||
*/
|
||||
export const formatTimestamp = (timestampMs: number) => {
|
||||
/** 秒级时间戳 */
|
||||
/** Second timestamp */
|
||||
const timestampSecond = Math.floor(timestampMs / 1000);
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const diff = now - timestampSecond;
|
||||
|
||||
// 将时间差转换成分钟、小时和天数
|
||||
// Convert time differences to minutes, hours, and days
|
||||
const minutes = Math.floor(diff / 60);
|
||||
const hours = Math.floor(diff / 3600);
|
||||
const days = Math.floor(diff / 86400);
|
||||
|
||||
// 不足一分钟,显示“Just now”
|
||||
// Less than a minute, showing "Just now"
|
||||
if (minutes < 1) {
|
||||
return I18n.t('community_time_just_now');
|
||||
}
|
||||
// 不足一小时,显示“{n}min ago”
|
||||
// Less than an hour, showing "{n} min ago"
|
||||
else if (hours < 1) {
|
||||
return I18n.t('community_time_min', { n: minutes });
|
||||
}
|
||||
// 不足一天,显示“{n}h ago”
|
||||
// Less than a day, showing "{n} h ago"
|
||||
else if (days < 1) {
|
||||
return I18n.t('community_time_hour', { n: hours });
|
||||
}
|
||||
// 不足一个月,显示“{n}d ago”
|
||||
// Less than a month, showing "{n} d ago"
|
||||
else if (days < 30) {
|
||||
return I18n.t('community_time_day', { n: days });
|
||||
}
|
||||
// 超过一个月,显示“{MM}/{DD}/{yyyy}”
|
||||
// More than a month, showing "{MM}/{DD}/{yyyy}"
|
||||
else {
|
||||
const dayObj = dayjs(timestampSecond * 1000);
|
||||
return I18n.t('community_time_date', {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* 获取最近一个可滚动元素
|
||||
* Get the latest scrollable element
|
||||
*/
|
||||
export function closestScrollableElement(element: HTMLElement) {
|
||||
const htmlElement = document.documentElement;
|
||||
@@ -47,7 +47,7 @@ export function closestScrollableElement(element: HTMLElement) {
|
||||
return htmlElement;
|
||||
}
|
||||
|
||||
// 解决浏览器拦截window.open行为,接口catch则跳错误兜底页
|
||||
// Solve browser interception window.open behavior, interface catch jump error default page
|
||||
export const openNewWindow = async (
|
||||
callbackUrl: () => Promise<string> | string,
|
||||
defaultUrl?: string,
|
||||
|
||||
@@ -30,9 +30,9 @@ export class BufferedEventEmitter<T extends EventEmitter.ValidEventTypes> {
|
||||
buffer: EventWithData<T>[] = [];
|
||||
|
||||
/**
|
||||
* 触发事件
|
||||
* @param event 事件名称
|
||||
* @param args 参数
|
||||
* trigger event
|
||||
* @param event name
|
||||
* @param args parameter
|
||||
*/
|
||||
emit<P extends EventEmitter.EventNames<T>>(
|
||||
event: P,
|
||||
@@ -49,9 +49,9 @@ export class BufferedEventEmitter<T extends EventEmitter.ValidEventTypes> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅事件
|
||||
* @param event 事件名称
|
||||
* @param fn 事件回调
|
||||
* subscribe to events
|
||||
* @param event name
|
||||
* @param fn event callback
|
||||
*/
|
||||
on<P extends EventEmitter.EventNames<T>>(
|
||||
event: P,
|
||||
@@ -61,9 +61,9 @@ export class BufferedEventEmitter<T extends EventEmitter.ValidEventTypes> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消订阅事件
|
||||
* @param event 事件名称
|
||||
* @param fn 事件回调
|
||||
* unsubscribe from the event
|
||||
* @param event name
|
||||
* @param fn event callback
|
||||
*/
|
||||
off<P extends EventEmitter.EventNames<T>>(
|
||||
event: P,
|
||||
@@ -73,7 +73,7 @@ export class BufferedEventEmitter<T extends EventEmitter.ValidEventTypes> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 开启缓存事件订阅器,开启时会将关闭时收到的事件对应的回调按顺序逐一触发
|
||||
* Turn on the cached event subscriber, and when turned on, the callbacks corresponding to the events received when closed will be fired one by one in sequence
|
||||
*/
|
||||
start() {
|
||||
this.started = true;
|
||||
@@ -83,14 +83,14 @@ export class BufferedEventEmitter<T extends EventEmitter.ValidEventTypes> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭缓存事件订阅器,在关闭时收到的事件会被缓存并延迟到下次开启时触发
|
||||
* Close the cached event subscriber. Events received during shutdown will be cached and delayed until the next time it is turned on
|
||||
*/
|
||||
stop() {
|
||||
this.started = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除缓存事件订阅器缓存的事件,使得在重新开启(start)时不会触发在关闭(stop)时收到的事件对应的回调
|
||||
* Clears the cached events of the event subscriber so that the callback corresponding to the event received at stop is not triggered when starting again
|
||||
*/
|
||||
clear() {
|
||||
this.buffer = [];
|
||||
@@ -105,7 +105,7 @@ const initEventEmitter = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 模块折叠 有关事件
|
||||
// Module folding, related events
|
||||
export enum OpenBlockEvent {
|
||||
DATA_MEMORY_BLOCK_OPEN = 'dataMemoryBlockOpen',
|
||||
TABLE_MEMORY_BLOCK_OPEN = 'tableMemoryBlockOpen',
|
||||
@@ -122,7 +122,7 @@ export enum OpenBlockEvent {
|
||||
BACKGROUND_IMAGE_BLOCK = 'BackgroundImageOpen',
|
||||
}
|
||||
|
||||
// 模块弹窗 有关事件
|
||||
// Module pop-ups, related events
|
||||
export enum OpenModalEvent {
|
||||
PLUGIN_API_MODAL_OPEN = 'pluginApiModalOpen',
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
import { isObject } from 'lodash-es';
|
||||
|
||||
/**
|
||||
* @param inputError 传啥都行,一般是 catch (e) 那个 e
|
||||
* @param reason 解释
|
||||
* @param inputError can pass anything, usually catch (e) that e.
|
||||
* @param reason explanation
|
||||
*/
|
||||
export const getReportError = (
|
||||
inputError: unknown,
|
||||
|
||||
@@ -66,7 +66,7 @@ export {
|
||||
draftEventEmitter,
|
||||
} from './event-handler';
|
||||
export { setMobileBody, setPCBody } from './viewport';
|
||||
/** 获取设备信息 */
|
||||
/** Get device information */
|
||||
export {
|
||||
getIsIPhoneOrIPad,
|
||||
getIsIPad,
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
import { reporter } from '@coze-arch/logger';
|
||||
import { CustomError } from '@coze-arch/bot-error';
|
||||
|
||||
// 这段代码是从 apps/bot/src/store/socket/utils.ts 复制出来的,后续也可以考虑统一
|
||||
// This code is copied from apps/bot/src/store/socket/utils.ts, and you can also consider unification in the future.
|
||||
const hasSuggestion = (ext?: unknown) =>
|
||||
isObject(ext) && 'has_suggest' in ext && ext.has_suggest === '1';
|
||||
|
||||
@@ -113,7 +113,7 @@ export class MessageReportEvent {
|
||||
|
||||
private _receiveTotalMessagesEvent = {
|
||||
start: () => {
|
||||
// 打断了
|
||||
// interrupted
|
||||
this._receiveTotalMessagesReportEvent =
|
||||
this._createReceiveTotalMessagesEvent();
|
||||
},
|
||||
@@ -219,7 +219,7 @@ export class MessageReportEvent {
|
||||
return;
|
||||
}
|
||||
if (!message.content) {
|
||||
// 回复消息为空的错误事件上报
|
||||
// Error event reporting with empty reply message
|
||||
reporter.errorEvent({
|
||||
eventName: ReportEventNames.emptyReceiveMessage,
|
||||
error: new CustomError(
|
||||
|
||||
@@ -38,7 +38,7 @@ const THOUSAND = 1e3;
|
||||
const MILLION = 1e6;
|
||||
const BILLION = 1e9;
|
||||
const TRILLION = 1e12;
|
||||
//将数字转换成K、M等单位
|
||||
//Convert numbers into K, M, and other units
|
||||
export const formatNumber = (num: number) => {
|
||||
const absNum = Math.abs(num);
|
||||
if (absNum >= TRILLION) {
|
||||
@@ -56,7 +56,7 @@ export const formatNumber = (num: number) => {
|
||||
return num;
|
||||
};
|
||||
|
||||
// 将数字转换成百分数, 向上取整
|
||||
// Convert a number to a percentage, round it up
|
||||
export const formatPercent = (num?: number): string => {
|
||||
if (num === undefined || num === null) {
|
||||
return 'NaN%';
|
||||
@@ -65,17 +65,17 @@ export const formatPercent = (num?: number): string => {
|
||||
|
||||
let formatted = percentage.toFixed(1);
|
||||
|
||||
// 如果小数点后一位是0,则移除小数点和0
|
||||
// If the decimal place is 0, remove the decimal point and 0.
|
||||
if (formatted.endsWith('.0')) {
|
||||
formatted = formatted.slice(0, -2);
|
||||
}
|
||||
|
||||
// 添加百分号并返回结果
|
||||
// Add a percent sign and return the result
|
||||
return `${formatted}%`;
|
||||
};
|
||||
|
||||
// 格式化时间, 毫秒, 保留一位小数点
|
||||
// 比如6.7s, 3.2min, 100ms, 1.3h
|
||||
// Format time, milliseconds, one decimal place reserved
|
||||
// For example, 6.7s, 3.2min, 100ms, 1.3h
|
||||
export const formatTime = (ms: number) => {
|
||||
const absMs = Math.abs(ms);
|
||||
|
||||
@@ -103,7 +103,7 @@ export const getEllipsisCount = (num: number, max: number): string =>
|
||||
num > max ? `${max}+` : `${num}`;
|
||||
|
||||
/**
|
||||
* @deprecated 不知道这个函数是干啥的。。。
|
||||
* @Deprecated doesn't know what this function does...
|
||||
*/
|
||||
export const exhaustiveCheck = (_v: never) => {
|
||||
// empty
|
||||
|
||||
@@ -20,8 +20,8 @@ const browser = Browser.getParser(window.navigator.userAgent);
|
||||
|
||||
let getIsMobileCache: boolean | undefined;
|
||||
/**
|
||||
* 是否是移动设备
|
||||
* 注:ipad 不是移动设备
|
||||
* Is it a mobile device?
|
||||
* Note: iPad is not a mobile device
|
||||
*/
|
||||
const isMobile = () => browser.getPlatformType(true).includes('mobile');
|
||||
|
||||
@@ -34,7 +34,7 @@ export const getIsMobile = () => {
|
||||
|
||||
let getIsIPhoneOrIPadCache: boolean | undefined;
|
||||
/**
|
||||
* gpt-4 提供的代码
|
||||
* Code provided by gpt-4
|
||||
*/
|
||||
export const getIsIPhoneOrIPad = () => {
|
||||
if (typeof getIsIPhoneOrIPadCache === 'undefined') {
|
||||
@@ -52,7 +52,7 @@ export const getIsIPhoneOrIPad = () => {
|
||||
|
||||
let getIsIPadCache: boolean | undefined;
|
||||
/**
|
||||
* gpt-4 提供的代码
|
||||
* Code provided by gpt-4
|
||||
*/
|
||||
export const getIsIPad = () => {
|
||||
if (typeof getIsIPadCache === 'undefined') {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import EventEmitter from 'eventemitter3';
|
||||
|
||||
interface BusinessData<T> {
|
||||
code: number; // 0: 成功, 其他: 错误码,业务tidying
|
||||
code: number; // 0: success, others: error codes, business tidying
|
||||
data?: T;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
@@ -55,11 +55,11 @@ export const responsiveTableColumn = (
|
||||
return 'auto';
|
||||
}
|
||||
|
||||
// 查找第一个符合条件的项
|
||||
// Find the first eligible item
|
||||
const range =
|
||||
colWidthRanges.find(colWidth => width >= colWidth.threshold) ||
|
||||
defaultRange;
|
||||
|
||||
// 返回 minWidth 或找到的 columnWidth,取决于哪个更大
|
||||
// Return minWidth or found columnWidth, depending on which is larger
|
||||
return Math.max(minWidth, range.columnWidth);
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ import { REPORT_EVENTS } from '@coze-arch/report-events';
|
||||
import { logger, reporter } from '@coze-arch/logger';
|
||||
|
||||
/**
|
||||
* @deprecated 这其实是 unsafe 的,请换用 typeSafeJSONParse
|
||||
* @Deprecated This is actually unsafe, please use typeSafeJSONParse instead
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const safeJSONParse: (v: any, emptyValue?: any) => any = (
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
import { type SkillKeyEnum } from '@coze-agent-ide/tool-config';
|
||||
|
||||
/**
|
||||
* `能力模块主键` 转 `接口定义的属性名` 函数
|
||||
* ⚠️ 命名需参看 @/services/auto-generate/developer_api/namespaces/developer_api > TabDisplayItems
|
||||
* 'Capability module primary key 'to'interface-defined attribute name' function
|
||||
* ⚠️ For naming, see @/services/auto-generate/developer_api/namespaces/developer_api > TabDisplayItems
|
||||
*/
|
||||
export const skillKeyToApiStatusKeyTransformer = ($key: SkillKeyEnum) =>
|
||||
`${$key}_tab_status`;
|
||||
|
||||
@@ -39,8 +39,8 @@ const removeAllListeners = (instance: UploaderInstance) => {
|
||||
export interface FileItem {
|
||||
file: File;
|
||||
/**
|
||||
* 非图片的文件 type 为 object
|
||||
* 这里显得很奇怪, 是为了对齐 @byted/uploader 的设计
|
||||
* Non-image file type is object
|
||||
* This seems strange, to align the design of @byted/uploader
|
||||
*/
|
||||
fileType: 'image' | 'object';
|
||||
}
|
||||
@@ -61,12 +61,12 @@ export interface UploadFileV2Param {
|
||||
}
|
||||
|
||||
/**
|
||||
* 改良版本的上传方法
|
||||
* 1. 能够支持打断, 清除副作用
|
||||
* 2. 更完善的回调函数
|
||||
* 3. 支持一次上传多文件
|
||||
* Improved version upload method
|
||||
* 1. Able to support interruption and clear side effects
|
||||
* 2. Better callback function
|
||||
* 3. Support uploading multiple files at one time
|
||||
*/
|
||||
// eslint-disable-next-line max-lines-per-function -- 内部的方法分了模块但是都依赖同一个 context 作打断无法拆出去
|
||||
// eslint-disable-next-line max-lines-per-function -- the internal methods are divided into modules, but they all rely on the same context for interruption and cannot be removed
|
||||
export function uploadFileV2({
|
||||
fileItemList,
|
||||
userId,
|
||||
@@ -125,13 +125,13 @@ export function uploadFileV2({
|
||||
{
|
||||
schema,
|
||||
useFileExtension: true,
|
||||
// 解决报错问题:
|
||||
// Solve the error problem:
|
||||
userId,
|
||||
appId: APP_ID,
|
||||
// cp-disable-next-line
|
||||
imageHost: `https://${upload_host}`, //imageX上传必填
|
||||
imageHost: `https://${upload_host}`, //imageX upload required
|
||||
imageConfig: {
|
||||
serviceId: service_id || '', // 在视频云中申请的服务id
|
||||
serviceId: service_id || '', // The service id applied for in the video cloud.
|
||||
},
|
||||
objectConfig: {
|
||||
serviceId: service_id || '',
|
||||
@@ -155,7 +155,7 @@ export function uploadFileV2({
|
||||
AccessKeyId: auth?.access_key_id || '',
|
||||
SecretAccessKey: auth?.secret_access_key || '',
|
||||
},
|
||||
type: fileType, // 上传文件类型,三个可选值:video(视频或者音频,默认值),image(图片),object(普通文件)
|
||||
type: fileType, // Upload file type, three optional values: video (video or audio, default), image (picture), object (normal file)
|
||||
});
|
||||
return { file, fileType, fileKey };
|
||||
});
|
||||
@@ -170,7 +170,7 @@ export function uploadFileV2({
|
||||
|
||||
list.push(inform as any);
|
||||
if (list.length === fileAndKeyList.length) {
|
||||
// 按顺序赋值
|
||||
// Assignment in order
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
list = fileAndKeyList.map(({ fileKey }) =>
|
||||
list.find(v => v.key === fileKey),
|
||||
|
||||
@@ -106,13 +106,13 @@ export function upLoadFile({
|
||||
getUploader,
|
||||
getUploadAuthToken,
|
||||
}: {
|
||||
/** 业务, 不同业务对应不同的 ImageX 服务 */
|
||||
/** Business, different businesses correspond to different ImageX services */
|
||||
biz?: 'bot' | 'workflow' | string;
|
||||
file: File;
|
||||
fileType?: 'image' | 'object';
|
||||
getProgress?: (progress: number) => void;
|
||||
getUploader?: (uploader: BytedUploader) => void;
|
||||
// 业务方自己获取upload token
|
||||
// The business party obtains the upload token by itself
|
||||
getUploadAuthToken?: () => Promise<developer_api.GetUploadAuthTokenResponse>;
|
||||
}) {
|
||||
const config = bizConfig[biz];
|
||||
@@ -163,9 +163,9 @@ export function upLoadFile({
|
||||
userId: userStoreService.getUserInfo()?.user_id_str || '',
|
||||
appId: APP_ID,
|
||||
// cp-disable-next-line
|
||||
imageHost: `https://${uploadHost}`, //imageX上传必填
|
||||
imageHost: `https://${uploadHost}`, //imageX upload required
|
||||
imageConfig: {
|
||||
serviceId: serviceId || '', // 在视频云中申请的服务id
|
||||
serviceId: serviceId || '', // The service id applied for in the video cloud.
|
||||
},
|
||||
objectConfig: {
|
||||
serviceId: serviceId || '',
|
||||
@@ -196,7 +196,7 @@ export function upLoadFile({
|
||||
const fileKey = bytedUploader.addFile({
|
||||
file,
|
||||
stsToken,
|
||||
type: fileType, // 上传文件类型,三个可选值:video(视频或者音频,默认值),image(图片),object(普通文件)
|
||||
type: fileType, // Upload file type, three optional values: video (video or audio, default), image (picture), object (normal file)
|
||||
});
|
||||
bytedUploader.start(fileKey);
|
||||
} catch (e) {
|
||||
|
||||
@@ -32,7 +32,7 @@ import {
|
||||
} from './utils';
|
||||
import { type FetchSteamConfig } from './type';
|
||||
|
||||
/** 发起流式消息拉取的请求 */
|
||||
/** Initiate a request for a streaming message pull */
|
||||
export async function fetchStream<Message = ParseEvent, DataClump = unknown>(
|
||||
requestInfo: RequestInfo,
|
||||
{
|
||||
@@ -79,11 +79,11 @@ export async function fetchStream<Message = ParseEvent, DataClump = unknown>(
|
||||
let betweenChunkTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
/**
|
||||
* clear 时机
|
||||
* 所有异常退出
|
||||
* create 函数 return
|
||||
* readStream 结束
|
||||
* abortSignal 触发
|
||||
* Clear time
|
||||
* All abnormal exits
|
||||
* Create function return
|
||||
* readStream ends
|
||||
* abortSignal trigger
|
||||
*/
|
||||
const clearTotalFetchTimer = () => {
|
||||
if (!totalFetchTimer) {
|
||||
@@ -94,8 +94,8 @@ export async function fetchStream<Message = ParseEvent, DataClump = unknown>(
|
||||
};
|
||||
|
||||
/**
|
||||
* set 时机
|
||||
* fetch 之间 set 一次, 只此一次
|
||||
* Set the timing
|
||||
* Fetch set once, only once
|
||||
*/
|
||||
const setTotalFetchTimer = () => {
|
||||
if (totalFetchTimeout && onTotalFetchTimeout) {
|
||||
@@ -107,11 +107,11 @@ export async function fetchStream<Message = ParseEvent, DataClump = unknown>(
|
||||
};
|
||||
|
||||
/**
|
||||
* clear 时机
|
||||
* readStream 异常退出
|
||||
* readStream 结束
|
||||
* 收到了新 chunk
|
||||
* abortSignal 触发
|
||||
* Clear time
|
||||
* readStream exits abnormally
|
||||
* readStream ends
|
||||
* Got a new chunk
|
||||
* abortSignal trigger
|
||||
*/
|
||||
const clearBetweenChunkTimer = () => {
|
||||
if (!betweenChunkTimer) {
|
||||
@@ -122,9 +122,9 @@ export async function fetchStream<Message = ParseEvent, DataClump = unknown>(
|
||||
};
|
||||
|
||||
/**
|
||||
* set 时机
|
||||
* readStream 之前 set 一次
|
||||
* 每次收到 chunk 并执行了 clearBetweenChunkTimer 时 set 一次
|
||||
* Set the timing
|
||||
* readStream is set once before
|
||||
* Set every time a chunk is received and clearBetweenChunkTimer executed
|
||||
*/
|
||||
const setBetweenChunkTimer = () => {
|
||||
if (betweenChunkTimeout && onBetweenChunkTimeout) {
|
||||
@@ -136,7 +136,7 @@ export async function fetchStream<Message = ParseEvent, DataClump = unknown>(
|
||||
};
|
||||
|
||||
signal?.addEventListener('abort', () => {
|
||||
// 此处 abort 后下方 readableStream 与 writableStream 都会停止
|
||||
// After aborting here, both the readableStream and writableStream below will stop.
|
||||
clearTotalFetchTimer();
|
||||
clearBetweenChunkTimer();
|
||||
resolve();
|
||||
@@ -160,13 +160,13 @@ export async function fetchStream<Message = ParseEvent, DataClump = unknown>(
|
||||
return response;
|
||||
} catch (error) {
|
||||
/**
|
||||
* 这里会被 catch 的错误
|
||||
* fetch 服务端返回异常
|
||||
* js error,例如被 onStart 抛出的
|
||||
* fetch 过程中 signal 被 abort
|
||||
* Mistakes that will be caught here
|
||||
* Fetch server level returned exception
|
||||
* Js error, such as thrown by onStart
|
||||
* The signal was aborted during fetching
|
||||
*/
|
||||
|
||||
// 被 abort 不认为是异常,不调用 onError
|
||||
// Being aborted is not considered an exception, and onError is not called.
|
||||
if (isAbortError(error)) {
|
||||
return;
|
||||
}
|
||||
@@ -214,11 +214,11 @@ export async function fetchStream<Message = ParseEvent, DataClump = unknown>(
|
||||
//
|
||||
validateChunk(decodedChunk);
|
||||
|
||||
// 上方 start 会在 TransformStream 被构建的同时执行,所以此处执行时能取到 parser
|
||||
// The above start will be executed at the same time as the TransformStream is built, so the parser can be fetched when executed here.
|
||||
parser.feed(decodedChunk);
|
||||
} catch (chunkError) {
|
||||
// 处理 validateChunk 抛出的业务错误
|
||||
// 服务端不会流式返回业务错误,错误结构:{ msg: 'xxx', code: 123456 }
|
||||
// Handling business errors thrown by validateChunk
|
||||
// The server level does not stream back business errors. Error structure: {msg: 'xxx', code: 123456}
|
||||
controller.error(chunkError);
|
||||
}
|
||||
},
|
||||
@@ -226,14 +226,14 @@ export async function fetchStream<Message = ParseEvent, DataClump = unknown>(
|
||||
|
||||
const streamWriter = new WritableStream<Message>({
|
||||
async write(chunk, controller) {
|
||||
// 写消息异步化 避免回调中的错误 panic 管道流
|
||||
// Write messages asynchronously to avoid false panic pipeline flow in callbacks
|
||||
await Promise.resolve();
|
||||
const param = { message: chunk, dataClump };
|
||||
const validateResult = validateMessage?.(param);
|
||||
|
||||
if (validateResult && validateResult.status === 'error') {
|
||||
/**
|
||||
* 会中断 WritableStream, 即使还有数据也会被中断, 不会再写了
|
||||
* WritableStream will be interrupted, even if there is still data, it will not be written again
|
||||
*/
|
||||
throw validateResult.error;
|
||||
}
|
||||
@@ -262,14 +262,14 @@ export async function fetchStream<Message = ParseEvent, DataClump = unknown>(
|
||||
resolve();
|
||||
} catch (streamError) {
|
||||
/**
|
||||
* 这里会被 catch 的错误
|
||||
* 流式返回中服务端异常
|
||||
* Mistakes that will be caught here
|
||||
* Exception at server level in streaming return
|
||||
* js error
|
||||
* 流式返回过程中被 signal 被 abort
|
||||
* 上方 onParseErrorFn 被调用
|
||||
* The signal was aborted during streaming return
|
||||
* The above onParseErrorFn is called
|
||||
*/
|
||||
|
||||
// 被 abort 不认为是异常,不调用 onError
|
||||
// Being aborted is not considered an exception, and onError is not called.
|
||||
if (isAbortError(streamError)) {
|
||||
return;
|
||||
}
|
||||
@@ -287,7 +287,7 @@ export async function fetchStream<Message = ParseEvent, DataClump = unknown>(
|
||||
async function create(): Promise<void> {
|
||||
const response = await fetchAndVerifyResponse();
|
||||
const body = response?.body;
|
||||
// response 不合法与没有 body 的错误在上方 onStart 中处理过
|
||||
// The response invalid and no body errors are handled in onStart above
|
||||
if (!body) {
|
||||
clearTotalFetchTimer();
|
||||
return;
|
||||
|
||||
@@ -40,54 +40,54 @@ export type ValidateResult =
|
||||
};
|
||||
|
||||
/**
|
||||
* {@link RequestInfo} 与 {@link RequestInit} 是 Fetch 原有参数类型
|
||||
* {@link RequestInfo} and {@link RequestInit} are the original parameter types of Fetch
|
||||
*/
|
||||
|
||||
export interface FetchSteamConfig<Message = ParseEvent, DataClump = unknown>
|
||||
extends RequestInit {
|
||||
/**
|
||||
* 当开始 fetch时调用
|
||||
* Called when fetch starts
|
||||
*/
|
||||
onFetchStart?: (params?: DataClump) => void;
|
||||
|
||||
/**
|
||||
* 当 fetch 返回 response 时调用此方法。使用这个方法来验证 Response 是否符合预期,当不符合预期时抛出错误
|
||||
* 无论是否提供此方法,会自动校验 Response.ok 标志位与 Response.body 是否存在
|
||||
* Call this method when fetch returns a response. Use this method to verify that the Response meets expectations, and throw an error when it does not
|
||||
* Whether or not this method is provided, the existence of the Response.ok flag and Response.body is automatically verified
|
||||
*/
|
||||
onStart?: (response: Response) => Promise<void>;
|
||||
|
||||
/**
|
||||
* 当 fetch 成功返回 response 并且 onStart 成功后触发此回调
|
||||
* This callback is triggered when fetch successfully returns a response and onStart succeeds
|
||||
*/
|
||||
onFetchSuccess?: (params?: DataClump) => void;
|
||||
|
||||
/**
|
||||
* 开始读取 ReadableStream 时触发此回调。onFetchSuccess 后紧接着会触发这个回调
|
||||
* This callback is triggered when you start reading ReadableStream. onFetchSuccess is followed by this callback
|
||||
*/
|
||||
onStartReadStream?: (params?: DataClump) => void;
|
||||
|
||||
/**
|
||||
* 流式过程中解析服务端返回的 chunk 数据,返回值符合 {@link Message} 类型时,预期将在后续 {@link onMessage} 方法中响应
|
||||
* 可在解析过程中进行中断或抛出错误,抛出错误同时会中断整个流式解析
|
||||
* 如果不提供则由 onMessage 直接响应 chunk 数据
|
||||
* The chunk data returned by the server level is parsed during streaming, and when the return value conforms to the type {@link Message}, it is expected to respond in subsequent {@link onMessage} methods
|
||||
* You can interrupt or throw an error during parsing, and throwing an error will also interrupt the entire stream parsing
|
||||
* If not provided, onMessage directly responds to chunk data
|
||||
*/
|
||||
streamParser?: (
|
||||
parseEvent: ParseEvent,
|
||||
method: {
|
||||
/**
|
||||
* 中止当前流式读取行为
|
||||
* Abort current streaming read behavior
|
||||
*/
|
||||
terminate: () => void;
|
||||
/**
|
||||
* @deprecated
|
||||
* 抛出错误,同时中止当前流式读取行为,如果流中还有正常数据未被读取,也会被一起终止掉
|
||||
* Throw an error and abort the current stream reading behavior. If there is still normal data in the stream that has not been read, it will also be terminated together.
|
||||
*/
|
||||
onParseError: (error: FetchStreamErrorInfo) => void;
|
||||
},
|
||||
) => Message | undefined;
|
||||
|
||||
/**
|
||||
* 在 onMessage 回调之前执行。对业务错误的处理和抛出推荐在这个回调处理
|
||||
* Execute before the onMessage callback. Handling of business errors and throwing recommendations are handled in this callback
|
||||
*/
|
||||
validateMessage?: (params: {
|
||||
message: Message;
|
||||
@@ -95,51 +95,51 @@ export interface FetchSteamConfig<Message = ParseEvent, DataClump = unknown>
|
||||
}) => ValidateResult;
|
||||
|
||||
/**
|
||||
* 接收到服务端 Chunk 数据并经过 parse(如果有)后,如果过程中无异常则调用此方法
|
||||
* After receiving the server level Chunk data and parsing (if any), call this method if there are no exceptions in the process
|
||||
*/
|
||||
onMessage?: (params: { message: Message; dataClump?: DataClump }) => void;
|
||||
|
||||
/**
|
||||
* 当 fetchStream resolve 时调用此方法
|
||||
* Call this method when fetchStream resolves
|
||||
*/
|
||||
onAllSuccess?: (params?: DataClump) => void;
|
||||
|
||||
/**
|
||||
* fetchStream 整个过程中出现任意错误会调用此方法,包括 fetch / 流式处理 chunk / response 非法等
|
||||
* 不会自动重试
|
||||
* This method will be called if any errors occur during the fetchStream process, including fetch/streaming chunk/response illegal, etc
|
||||
* Does not automatically retry
|
||||
*/
|
||||
onError?: (params: {
|
||||
fetchStreamError: FetchStreamError;
|
||||
dataClump?: DataClump;
|
||||
}) => void;
|
||||
|
||||
/** Fetch 方法,默认为 window.fetch */
|
||||
/** Fetch method, the default is window.fetch */
|
||||
fetch?: typeof fetch;
|
||||
|
||||
/**
|
||||
* {@link https://book-refactoring2.ifmicro.com/docs/ch3.html#310-%E6%95%B0%E6%8D%AE%E6%B3%A5%E5%9B%A2%EF%BC%88data-clumps%EF%BC%89}
|
||||
* 如果你想为每个 fetchStream 维护一些业务数据、状态,推荐在此处传入抽象后的数据实例。它们会在每个回调函数中出现
|
||||
* If you want to maintain some business data and state for each fetchStream, it is recommended to pass in the abstracted data instances here. They will appear in each callback function
|
||||
*/
|
||||
dataClump?: DataClump;
|
||||
|
||||
/**
|
||||
* fetch stream 整个过程的超时时长, 单位: ms。缺省或者传入 0 代表不开启定时器
|
||||
* The timeout of the entire process of fetch stream, in: ms. Default or pass in 0 to indicate that the timer is not turned on
|
||||
*/
|
||||
totalFetchTimeout?: number;
|
||||
|
||||
/**
|
||||
* 当设置了 totalFetchTimeout, 并且到期时触发此回调。除此外不会有其余副作用,例如:abort 请求。请调用方根据需要自行处理
|
||||
* This callback is triggered when totalFetchTimeout is set and expires. There will be no other side effects, such as abort requests. Please handle it yourself as needed
|
||||
*/
|
||||
onTotalFetchTimeout?: (params?: DataClump) => void;
|
||||
|
||||
/**
|
||||
* chunk 之间超时时长, 处理 stream 过程中, 从收到上一个 chunk 开始计时, 收到下一个 chunk 时清除定时并重新计时
|
||||
* 缺省或者传入 0 代表不开启定时器, 单位: ms
|
||||
* Timeout time between chunks. During the processing of the stream, the timing starts from the receipt of the previous chunk. When the next chunk is received, the timing is cleared and re-timed.
|
||||
* Default or incoming 0 means the timer is not turned on, unit: ms
|
||||
*/
|
||||
betweenChunkTimeout?: number;
|
||||
|
||||
/**
|
||||
* 当设置了 chunkTimeout,并且定时器到期时触发此回调。除此外不会有其余副作用,例如:abort 请求。请调用方根据需要自行处理
|
||||
* This callback is triggered when chunkTimeout is set and the timer expires. In addition, there will be no other side effects, such as: abort request. Please handle it yourself as needed
|
||||
*/
|
||||
onBetweenChunkTimeout?: (params?: DataClump) => void;
|
||||
}
|
||||
|
||||
@@ -36,11 +36,11 @@ export function validateChunk(decodedChunk: string): void {
|
||||
let json: unknown;
|
||||
try {
|
||||
json = JSON.parse(decodedChunk);
|
||||
// eslint-disable-next-line @coze-arch/no-empty-catch, @coze-arch/use-error-in-catch -- 设计如此
|
||||
// eslint-disable-next-line @coze-arch/no-empty-catch, @coze-arch/use-error-in-catch -- designed like this
|
||||
} catch {
|
||||
/**
|
||||
* 此处捕获 JSON.parse 错误不做任何处理
|
||||
* 正常流式返回 json 解析失败才是正常的
|
||||
* Catch JSON.parse errors here without any processing
|
||||
* It is normal for normal streaming to return json parsing failure.
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
@@ -42,20 +42,20 @@ import type {
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
* 获取当前主题
|
||||
* Get the current topic
|
||||
*/
|
||||
export declare function useCurrentTheme(): ThemeType;
|
||||
|
||||
//------- Passport
|
||||
/**
|
||||
* 退出登录
|
||||
* log out
|
||||
*/
|
||||
export declare function logoutOnly(): Promise<void>;
|
||||
|
||||
/**
|
||||
* 上传用户头像
|
||||
* @param file - 头像文件
|
||||
* @returns web_uri - 头像文件对应的 url
|
||||
* Upload user avatar
|
||||
* @param file - avatar file
|
||||
* @Returns web_uri - URL for avatar file
|
||||
*/
|
||||
export declare function uploadAvatar(file: File): Promise<{ web_uri: string }>;
|
||||
//-------
|
||||
@@ -63,87 +63,87 @@ export declare function uploadAvatar(file: File): Promise<{ web_uri: string }>;
|
||||
//------- User
|
||||
|
||||
/**
|
||||
* 刷新用户信息
|
||||
* Refresh user information
|
||||
*/
|
||||
export declare function refreshUserInfo(): Promise<void>;
|
||||
|
||||
/**
|
||||
* 获取登录状态
|
||||
* @returns LoginStatus - 检查中/已登录/未登录
|
||||
* Get login status
|
||||
* @returns LoginStatus - checking/logged in/not logged in
|
||||
*/
|
||||
export declare function getLoginStatus(): LoginStatus;
|
||||
|
||||
/**
|
||||
* 获取登录校验是否完成
|
||||
* @returns boolean 登录校验是否完成
|
||||
* Get whether the login verification is complete
|
||||
* @Returns boolean login verification completed
|
||||
*/
|
||||
export declare function getIsSettled(): boolean;
|
||||
|
||||
/**
|
||||
* 获取是否登录,如需要获取真实登录状态,请配合 getIsSettled/useIsSettled 使用
|
||||
* @deprecated - 推荐使用 getLoginStatus
|
||||
* @returns boolean 是否登录
|
||||
* Get whether to log in. If you need to get the real login status, please use it with getIsSettled/useIsSettled
|
||||
* @deprecated - recommended getLoginStatus
|
||||
* @returns boolean whether to log in
|
||||
*/
|
||||
export declare function getIsLogined(): boolean;
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @returns UserInfo | null - 用户信息
|
||||
* Acquire user information
|
||||
* @returns UserInfo | null - user information
|
||||
*/
|
||||
export declare function getUserInfo(): UserInfo | null;
|
||||
|
||||
/**
|
||||
* 获取用户三方授权信息
|
||||
* Obtain user tripartite authorization information
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
export declare function getUserAuthInfos(): Promise<void>;
|
||||
|
||||
/**
|
||||
* 响应式获取登录状态
|
||||
* @returns LoginStatus - 检查中/已登录/未登录
|
||||
* Responsive access to login status
|
||||
* @returns LoginStatus - checking/logged in/not logged in
|
||||
*/
|
||||
export declare function useLoginStatus(): LoginStatus;
|
||||
|
||||
/**
|
||||
* 响应式获取登录校验是否完成
|
||||
* @returns boolean 登录校验是否完成
|
||||
* Responsive Get Login Verification Completed
|
||||
* @Returns boolean login verification completed
|
||||
*/
|
||||
export declare function useIsSettled(): boolean;
|
||||
|
||||
/**
|
||||
* 响应式获取是否登录,如需要获取真实登录状态,请配合 getIsSettled/useIsSettled 使用
|
||||
* @deprecated - 推荐使用 useLoginStatus
|
||||
* @returns boolean 是否登录
|
||||
* Responsive to get whether to log in. If you need to get the real login status, please use it with getIsSettled/useIsSettled
|
||||
* @deprecated - useLoginStatus
|
||||
* @returns boolean whether to log in
|
||||
*/
|
||||
export declare function useIsLogined(): boolean;
|
||||
|
||||
/**
|
||||
* 响应式获取用户信息
|
||||
* @returns UserInfo | null - 用户信息
|
||||
* Responsive acquisition of user information
|
||||
* @returns UserInfo | null - user information
|
||||
*/
|
||||
export declare function useUserInfo(): UserInfo | null;
|
||||
|
||||
/**
|
||||
* 响应式获取用户三方授权信息
|
||||
* Responsive acquisition of user tripartite authorization information
|
||||
* @returns UserAuthInfo[]
|
||||
*/
|
||||
export declare function useUserAuthInfo(): UserAuthInfo[];
|
||||
|
||||
/**
|
||||
* 响应式获取用户用户标签
|
||||
* Responsive acquisition of user tags
|
||||
* @returns UserLabel
|
||||
*/
|
||||
export declare function useUserLabel(): UserLabel | null;
|
||||
|
||||
/**
|
||||
* 订阅 UserAuthInfo 变化
|
||||
* @param callback - 回调函数
|
||||
* Subscribe to UserAuthInfo Changes
|
||||
* @param callback - callback function
|
||||
*/
|
||||
export declare function subscribeUserAuthInfos(
|
||||
callback: (state: UserAuthInfo[], prev: UserAuthInfo[]) => void,
|
||||
): () => void;
|
||||
|
||||
//------- layout组件
|
||||
//------- layout component
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export declare const SideSheetMenu: FC;
|
||||
@@ -163,6 +163,6 @@ export interface MenuItem {
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据spaceId获取space信息
|
||||
* Get space information based on spaceId
|
||||
*/
|
||||
export declare function useSpace(spaceId: string): BotSpace | undefined;
|
||||
|
||||
@@ -18,18 +18,18 @@ export interface BackButtonProps {
|
||||
onClickBack: () => void;
|
||||
}
|
||||
|
||||
/** 导航栏自定义按钮属性 */
|
||||
/** Navigation bar custom button properties */
|
||||
export interface NavBtnProps {
|
||||
// 必填,Nav.Item导航组件唯一key,路由匹配时高亮
|
||||
// Required, Nav. Item navigation component unique key, highlighted when the route matches
|
||||
navKey: string;
|
||||
//按钮图标
|
||||
//button icon
|
||||
icon?: React.ReactNode;
|
||||
// 按钮名称
|
||||
// button name
|
||||
label: string | React.ReactNode;
|
||||
// 后缀节点
|
||||
// Suffix Node
|
||||
suffix?: string | React.ReactNode;
|
||||
// 仅在左侧导航栏默认模式中展示
|
||||
// Show only in the default mode of the left navigation bar
|
||||
onlyShowInDefault?: boolean;
|
||||
// 按钮点击回调
|
||||
// Button click callback
|
||||
onClick: (e: React.MouseEvent) => void;
|
||||
}
|
||||
|
||||
@@ -15,34 +15,34 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* 代表OAuth2回调的access_token的用途,目前有:
|
||||
* 1.login:登陆时进行用户中台三方登陆(auth/login)
|
||||
* 2. delete_account:删除账号时获取ticket(auth/authorize)
|
||||
* 3. oauth: 发布三方平台是时获取用户授权
|
||||
* The uses of the access_token representing the OAuth2 callback are currently:
|
||||
* 1. Login: When logging in, perform user mid-platform three-party login (auth/login)
|
||||
* 2. delete_account: Get ticket when deleting account (auth/authorized)
|
||||
* 3.Oauth: It is time to obtain user authorization when releasing the three-party platform
|
||||
*/
|
||||
export type OAuth2StateType = 'login' | 'delete_account' | 'oauth';
|
||||
|
||||
export interface OAuth2RedirectConfig {
|
||||
/**
|
||||
* 最终的OAuth2鉴权信息将作为路由参数跳转,这个参数指定目标路由地址,注意在目标路由上使用
|
||||
* useAuthLoginDataRouteFromOAuth2来提取路由参数,并转换成用户中台三方登陆服务(authLogin)的参数;
|
||||
* 默认值为当前路径名称,即不传navigatePath参数时,当前路由一定要注册useAuthLoginDataRouteFromOAuth2才有效
|
||||
* The final OAuth2 authentication information will be redirected as a route parameter, which specifies the target route address. Be careful to use it on the target route
|
||||
* useAuthLoginDataRouteFromOAuth2 to extract the routing parameters and convert them into the parameters of the user's mid-platform three-party login service (authLogin);
|
||||
* The default value is the current path name, that is, when the navigatePath parameter is not passed, the current route must be registered useAuthLoginDataRouteFromOAuth2 to be valid
|
||||
*/
|
||||
navigatePath?: string;
|
||||
/**
|
||||
* OAuth2回调后拿到的鉴权信息的使用场景,用于在一些路由组件中区分,不符合对应场景的不能用于消费
|
||||
* The usage scenario of the authentication information obtained after the OAuth2 callback is used to distinguish among some routing components. Those that do not meet the corresponding scenario cannot be used for consumption
|
||||
*/
|
||||
type: OAuth2StateType;
|
||||
/**
|
||||
* 传递给OAuth2服务器的state字段,会在回调时传回,用于恢复网页状态
|
||||
* The state field passed to the OAuth2 server is returned during the callback to restore the state of the webpage
|
||||
*/
|
||||
|
||||
extra?: {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
origin?: string;
|
||||
[x: string]: string; // 用于安全监测
|
||||
[x: string]: string; // For safety monitoring
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
encrypt_state?: string; //加密state,bind_type 为 4时使用
|
||||
encrypt_state?: string; //Encrypted state, used when bind_type 4
|
||||
};
|
||||
scope?: string;
|
||||
optionalScope?: string;
|
||||
|
||||
@@ -32,7 +32,7 @@ export interface UserConnectItem {
|
||||
export interface UserInfo {
|
||||
app_id: number;
|
||||
/**
|
||||
* @deprecated 会因为溢出丢失精度,使用 user_id_str
|
||||
* @Deprecated will lose precision due to overflow, use user_id_str
|
||||
*/
|
||||
user_id: number;
|
||||
user_id_str: string;
|
||||
@@ -102,14 +102,14 @@ export interface UserInfo {
|
||||
};
|
||||
need_check_bind_status: boolean;
|
||||
bui_audit_info?: {
|
||||
// TODO 这里的类型定义需要再明确一点更好
|
||||
// The type definition here in TODO needs to be more clear and better
|
||||
audit_info: {
|
||||
user_unique_name?: string;
|
||||
avatar_url?: string;
|
||||
name?: string;
|
||||
[key: string]: unknown;
|
||||
}; // Record<string, unknown>;
|
||||
// int值。1审核中,2审核通过,3审核不通过
|
||||
// int value. 1 During the review, 2 passed the review, and 3 failed the review.
|
||||
audit_status: 1 | 2 | 3;
|
||||
details: Record<string, unknown>;
|
||||
is_auditing: boolean;
|
||||
|
||||
@@ -18,7 +18,7 @@ import { act, renderHook, type RenderHookResult } from '@testing-library/react-h
|
||||
import { useState } from 'react';
|
||||
import usePersistCallback from '..';
|
||||
|
||||
// 函数变化,但是地址不变
|
||||
// The function changes, but the address remains the same
|
||||
|
||||
const TestHooks = () => {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
@@ -18,7 +18,7 @@ import { useState, useRef, type Dispatch, type SetStateAction, useCallback } fro
|
||||
|
||||
const isFunction = (val: any): val is Function => typeof val === 'function';
|
||||
|
||||
// 获取新的状态值,兼容传值和传函数情况
|
||||
// Get a new state value, compatible with passing values and functions
|
||||
function getStateVal<T>(preState: T, initVal?: SetStateAction<T>): T | undefined {
|
||||
if (isFunction(initVal)) {
|
||||
return initVal(preState);
|
||||
|
||||
@@ -49,7 +49,7 @@ interface AutoMergeUrlParamsOptions {
|
||||
}
|
||||
|
||||
interface IOptions {
|
||||
omitKeys?: string[]; // 在 url 中不展示的字段但是还是会传到最后的返回的 value 中
|
||||
omitKeys?: string[]; // Fields that are not displayed in the url but will still be passed to the final returned value
|
||||
autoFormat?: boolean;
|
||||
autoMergeUrlParamsOptions?: AutoMergeUrlParamsOptions;
|
||||
autoMergeUrlParams?: boolean;
|
||||
@@ -94,7 +94,7 @@ function formatValueFn<T>(obj: T, autoFormat: boolean): KeysObj<T> {
|
||||
}
|
||||
}
|
||||
|
||||
// 第一次初始化是 url merge defaultValue ,然后后续 setValue 用 value merge url
|
||||
// The first initialization is url merge defaultValue, then subsequent setValue merges url with value
|
||||
// eslint-disable-next-line max-params
|
||||
function getMergeValue<T>(
|
||||
value: T,
|
||||
@@ -131,7 +131,7 @@ function getMergeValue<T>(
|
||||
return mergeValue as T;
|
||||
}
|
||||
|
||||
// 初始化 initValue 其中的 value 值可能会有 number 类型, 会被在 url 转成 Object 全部转换成 string, 需自行处理下
|
||||
// Initialize initValue The value value in it may have a number type, which will be converted to Object in the url and all converted to string. You need to deal with it yourself.
|
||||
// The value in the initialization initValue may be a number,
|
||||
// which will be converted into an Object in the url and all converted into a string, which needs to be processed manually.
|
||||
function useUrlParams<T>(
|
||||
|
||||
@@ -13,31 +13,31 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
import { i18nContext } from '../../src/i18n-provider/context';
|
||||
|
||||
describe('i18n-provider/context', () => {
|
||||
it('should create a context with default values', () => {
|
||||
// 验证 i18nContext 是否被正确创建
|
||||
// Verify that i18nContext was created correctly
|
||||
expect(i18nContext).toBeDefined();
|
||||
|
||||
// 获取默认值 - 使用类型断言访问内部属性
|
||||
// @ts-expect-error - 访问内部属性
|
||||
// Get Default Values - Use Type Assertions to Access Internal Properties
|
||||
// @ts-expect-error - access internal properties
|
||||
const defaultValue = i18nContext._currentValue;
|
||||
|
||||
// 验证默认值中的 i18n 对象是否存在
|
||||
// Verify that the i18n object in the default value exists
|
||||
expect(defaultValue.i18n).toBeDefined();
|
||||
|
||||
// 验证 t 函数是否存在
|
||||
// Verify that the t function exists
|
||||
expect(defaultValue.i18n.t).toBeDefined();
|
||||
expect(typeof defaultValue.i18n.t).toBe('function');
|
||||
|
||||
// 验证 t 函数的行为
|
||||
// Verify the behavior of the t function
|
||||
expect(defaultValue.i18n.t('test-key')).toBe('test-key');
|
||||
|
||||
// 验证 i18nContext 是一个对象
|
||||
// Verify that i18nContext is an object
|
||||
expect(typeof i18nContext).toBe('object');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -40,7 +40,7 @@ describe('I18nProvider', () => {
|
||||
const provider = new I18nProvider({ children });
|
||||
const result = provider.render().props.children;
|
||||
|
||||
// 验证渲染结果
|
||||
// Validate the render result
|
||||
expect(result).toBeDefined();
|
||||
expect(result.props).toBeDefined();
|
||||
expect(result.props.value).toBeDefined();
|
||||
@@ -48,7 +48,7 @@ describe('I18nProvider', () => {
|
||||
expect(typeof result.props.value.i18n.t).toBe('function');
|
||||
expect(result.props.children).toBe(children);
|
||||
|
||||
// 验证默认的 t 函数行为
|
||||
// Verify the default t function behavior
|
||||
const defaultT = result.props.value.i18n.t;
|
||||
expect(defaultT('test-key')).toBe('test-key');
|
||||
});
|
||||
@@ -69,14 +69,14 @@ describe('I18nProvider', () => {
|
||||
const provider = new I18nProvider({ children, i18n: mockI18n as any });
|
||||
const result = provider.render().props.children;
|
||||
|
||||
// 验证渲染结果
|
||||
// Validate the render result
|
||||
expect(result).toBeDefined();
|
||||
expect(result.props).toBeDefined();
|
||||
expect(result.props.value).toBeDefined();
|
||||
expect(result.props.value.i18n).toBe(mockI18n);
|
||||
expect(result.props.children).toBe(children);
|
||||
|
||||
// 验证使用了提供的 i18n
|
||||
// Verify that the provided i18n is used.
|
||||
const key = 'test-key';
|
||||
result.props.value.i18n.t(key);
|
||||
expect(mockI18n.t).toHaveBeenCalledWith(key);
|
||||
|
||||
@@ -18,7 +18,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
|
||||
import { I18n, initI18nInstance } from '../../src/raw';
|
||||
|
||||
// 模拟本地化资源
|
||||
// Simulate localized resources
|
||||
vi.mock('../../src/resource.ts', () => ({
|
||||
default: {
|
||||
en: { i18n: { test: 'Test' } },
|
||||
|
||||
@@ -39,9 +39,9 @@ type I18nOptions<K extends LocaleData> = K extends keyof I18nOptionsMap
|
||||
? I18nOptionsMap[K]
|
||||
: never;
|
||||
|
||||
// 这里导出的 const I18n = new FlowIntl() 与 '@edenx/plugin-starling-intl/runtime' 中的 I18n 功能等价
|
||||
// 其实就是对 '@edenx/plugin-starling-intl/runtime' 中的 I18n 进行了一层封装,目的是为了后续进一步灵活的定义I18n.t() 的参数类型。
|
||||
// 这里的 I18n.t() 的参数类型是通过泛型 LocaleData 来定义的,而 '@edenx/plugin-starling-intl/runtime' 中的 I18n.t() 的参数类型是通过泛型 string 来定义的。
|
||||
// The exported const I18n = new FlowIntl () is functionally equivalent to I18n in '@edenx/plugin-starling-intl/runtime'
|
||||
// In fact, it is a layer of encapsulation for I18n in '@edenx/plugin-starling-intl/runtime', in order to further flexibly define the parameter type of I18n.t () in the future.
|
||||
// The parameter types of I18n.t () here are defined by the generic LocaleData, while the parameter types of I18n.t () in '@edenx/plugin-starling-intl/runtime' are defined by the generic string.
|
||||
class FlowIntl {
|
||||
plugins: any[] = [];
|
||||
public i18nInstance: I18nCore;
|
||||
@@ -89,9 +89,9 @@ class FlowIntl {
|
||||
|
||||
t<K extends I18nKeysNoOptionsType>(
|
||||
keys: K,
|
||||
// 这里如果用 never 的话,导致存量代码第二个参数是 `{}` 的时候会报错,所以这里用 Record<string, unknown> 代替
|
||||
// 后续的做法是:用 sg 把存量的代码都修复了之后,这里再改成 never 类型,从而保证未来新增的代码,都是有类型检查的。
|
||||
// 记得改动的时候 #87 行也要一起修改
|
||||
// If you use never here, an error will be reported when the second parameter of the stock code is' {} ', so use Record < string, unknown > instead
|
||||
// The follow-up approach is to use sg to fix all the existing code, and then change it to the never type here, so as to ensure that future new code is type-checked.
|
||||
// Remember to modify line #87 together when changing.
|
||||
options?: Record<string, unknown>,
|
||||
fallbackText?: string,
|
||||
): string;
|
||||
|
||||
@@ -27,8 +27,8 @@ export interface IntlConstructorOptions {
|
||||
}
|
||||
let intlInstance: any = null;
|
||||
/**
|
||||
* I18n实例
|
||||
* 自定义配置
|
||||
* I18n example
|
||||
* custom configuration
|
||||
*/
|
||||
class Intl {
|
||||
plugins: any[];
|
||||
@@ -38,7 +38,7 @@ class Intl {
|
||||
this.i18nInstance = opts?.i18nInstance ?? new I18next();
|
||||
}
|
||||
/**
|
||||
* i18n 没有定义类型,这里声明 any
|
||||
* I18n does not define a type, declare any here
|
||||
*/
|
||||
use(plugin: any) {
|
||||
if (!this.plugins.includes(plugin)) {
|
||||
@@ -119,7 +119,7 @@ class Intl {
|
||||
if (!that.i18nInstance || !that.i18nInstance.init) {
|
||||
return fallbackText ?? (Array.isArray(keys) ? keys[0] : keys);
|
||||
}
|
||||
// 有人给 key 传空字符串?
|
||||
// Someone passed an empty string to the key?
|
||||
if (!keys || (typeof keys === 'string' && !keys.trim())) {
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -55,15 +55,15 @@ export function formatLang(lng, plugins) {
|
||||
|
||||
const defaultFallbackLanguage = 'zh-CN';
|
||||
const defaultConfig = {
|
||||
lng: defaultFallbackLanguage, // 如果使用了 Language Detector,i18next 底层 lng 的权重是大于插件的
|
||||
lng: defaultFallbackLanguage, // If Language Detector is used, the weight of the underlying lng of i18next is greater than that of the plug-in.
|
||||
fallbackLng: ['en-US'],
|
||||
inContext: true,
|
||||
};
|
||||
// 默认开启ICU插值解析
|
||||
// Default enable ICU interpolation parsing
|
||||
|
||||
/**
|
||||
* I18n内核
|
||||
* 安全校验
|
||||
* I18n kernel
|
||||
* security check
|
||||
*/
|
||||
export default class I18next {
|
||||
instance: i18n;
|
||||
@@ -90,7 +90,7 @@ export default class I18next {
|
||||
}
|
||||
|
||||
_handleConfigs(config?: InitOptions) {
|
||||
this.userLng = config?.lng || null; // 用户自己设定的 lng
|
||||
this.userLng = config?.lng || null; // Lng set by the user.
|
||||
|
||||
this.config = Object.assign({}, defaultConfig, config || {});
|
||||
}
|
||||
@@ -138,10 +138,10 @@ export default class I18next {
|
||||
},
|
||||
},
|
||||
(err, t) => {
|
||||
// 初始化好了
|
||||
// Initialized
|
||||
|
||||
try {
|
||||
// 把等待添加的东西都加进去
|
||||
// Add everything waiting to be added
|
||||
for (const item of this._waitingToAddResourceBundle) {
|
||||
this.instance.addResourceBundle(...item);
|
||||
}
|
||||
@@ -184,7 +184,7 @@ export default class I18next {
|
||||
overwrite,
|
||||
);
|
||||
}
|
||||
// 还没初始化好
|
||||
// It hasn't been initialized yet.
|
||||
this._waitingToAddResourceBundle.push([
|
||||
lng,
|
||||
ns,
|
||||
@@ -244,7 +244,7 @@ export default class I18next {
|
||||
.join('')
|
||||
: Array(keys.length).fill(' ');
|
||||
|
||||
// fixed: 去除默认lngs,有lngs i18next就会忽略lng
|
||||
// Fixed: Remove the default lngs, if there is lngs i18next, the lng will be ignored.
|
||||
const opt: Record<string, any> = Object.assign(
|
||||
{ keySeparator: separatorMock, nsSeparator: separatorMock },
|
||||
options,
|
||||
|
||||
@@ -22,18 +22,18 @@
|
||||
import { type InitOptions } from 'i18next';
|
||||
|
||||
/**
|
||||
* 初始化 Intl 实例配置参数
|
||||
* Initialize Intl instance configuration parameters
|
||||
*/
|
||||
export interface IIntlInitOptions
|
||||
extends Omit<InitOptions, 'missingInterpolationHandler'> {
|
||||
/**
|
||||
* t 方法是否开启第三个参数兜底
|
||||
* Whether the t method turns on the third parameter to cover the bottom
|
||||
* @default true
|
||||
*/
|
||||
thirdParamFallback?: boolean;
|
||||
|
||||
/**
|
||||
* 忽略所有控制台输出,不建议设置为 true
|
||||
* Ignore all console output, do not recommend setting to true
|
||||
* @default false
|
||||
*/
|
||||
ignoreWarning?: boolean;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// 临时文件,后续删除
|
||||
// Temporary files, later deleted
|
||||
|
||||
export { SuggestReplyMode } from './auto-generated/developer_api/namespaces/developer_api';
|
||||
export { TaskType as CopyTaskType } from './auto-generated/intelligence_api/namespaces/method_struct';
|
||||
|
||||
@@ -18,12 +18,12 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
|
||||
import { RemoteWebWorker, register } from '../src/index';
|
||||
|
||||
// 获取模拟函数
|
||||
// Get the simulation function
|
||||
const mockCreateObjectURL = vi.mocked(URL.createObjectURL);
|
||||
|
||||
describe('RemoteWebWorker', () => {
|
||||
beforeEach(() => {
|
||||
// 清除模拟调用记录
|
||||
// Clear simulated call record
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
@@ -63,10 +63,10 @@ describe('RemoteWebWorker', () => {
|
||||
|
||||
expect(mockCreateObjectURL).toHaveBeenCalledTimes(1);
|
||||
|
||||
// 验证创建的 Blob 内容
|
||||
// Verify the content of the created blob
|
||||
const blobArg = mockCreateObjectURL.mock.calls[0][0];
|
||||
expect(blobArg).toBeInstanceOf(Blob);
|
||||
// 由于 Blob 的内容无法直接访问,我们只能验证它被创建了
|
||||
// Since the content of the blob cannot be accessed directly, we can only verify that it was created
|
||||
});
|
||||
|
||||
it('应该处理非字符串 URL', () => {
|
||||
@@ -84,14 +84,14 @@ describe('RemoteWebWorker', () => {
|
||||
|
||||
describe('register', () => {
|
||||
it('应该将全局 Worker 替换为 RemoteWebWorker', () => {
|
||||
// 创建一个模拟的全局对象
|
||||
// Create a simulated global object
|
||||
const mockGlobal = {
|
||||
worker() {
|
||||
/* 空函数 */
|
||||
/* empty function */
|
||||
},
|
||||
};
|
||||
|
||||
// 将 worker 属性重命名为 Worker,以便测试 register 函数
|
||||
// Rename the worker property to Worker to test the register function
|
||||
Object.defineProperty(mockGlobal, 'Worker', {
|
||||
get() {
|
||||
return this.worker;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user