chore: replace all cn comments of fe to en version by volc api (#320)

This commit is contained in:
tecvan
2025-07-31 10:32:15 +08:00
committed by GitHub
parent 716ec0cba8
commit 71f6245a01
2960 changed files with 15545 additions and 15545 deletions

View File

@@ -55,13 +55,13 @@ describe('useInitProjectRole', () => {
console.log('result', result.current);
console.log('mockIsReady', mockIsReady);
// 验证是否调用了 setRoles setIsReady
// Verify that setRoles and setIsReady are called
expect(mockSetRoles).toHaveBeenCalledWith(projectId, [
ProjectRoleType.Owner,
]);
expect(mockSetIsReady).toHaveBeenCalledWith(projectId, true);
// 验证返回值
// Validate the return value
expect(result.current).toBe(true);
});
@@ -82,7 +82,7 @@ describe('useInitProjectRole', () => {
]);
expect(mockSetIsReady).toHaveBeenCalledWith(projectId1, true);
// 重新渲染,使用新的 projectId
// Render again, using the new projectId
rerender({ spaceId: testSpaceId, projectId: projectId2 });
expect(mockSetRoles).toHaveBeenCalledWith(projectId2, [

View File

@@ -49,11 +49,11 @@ describe('useInitSpaceRole', () => {
const spaceId = 'space-1';
const { result } = renderHook(() => useInitSpaceRole(spaceId));
// 验证是否调用了 setRoles setIsReady
// Verify that setRoles and setIsReady are called
expect(mockSetRoles).toHaveBeenCalledWith(spaceId, [SpaceRoleType.Owner]);
expect(mockSetIsReady).toHaveBeenCalledWith(spaceId, true);
// 验证返回值
// Validate the return value
expect(result.current).toBe(true);
});
@@ -68,7 +68,7 @@ describe('useInitSpaceRole', () => {
expect(mockSetRoles).toHaveBeenCalledWith(spaceId1, [SpaceRoleType.Owner]);
expect(mockSetIsReady).toHaveBeenCalledWith(spaceId1, true);
// 重新渲染,使用新的 spaceId
// Render again, using the new spaceId.
rerender({ id: spaceId2 });
expect(mockSetRoles).toHaveBeenCalledWith(spaceId2, [SpaceRoleType.Owner]);

View File

@@ -33,5 +33,5 @@ export function useInitProjectRole(spaceId: string, projectId: string) {
setIsReady(projectId, true);
}, [projectId]);
return isReady; // 是否初始化完成。
return isReady; // Whether the initialization is complete.
}

View File

@@ -15,7 +15,7 @@
*/
/**
* @file 开源版暂时不提供权限控制功能,本文件中导出的方法用于未来拓展使用。
* The @file open-source version does not provide permission control functions for the time being. The methods exported in this file are for future expansion.
*/
import { useEffect } from 'react';

View File

@@ -32,37 +32,37 @@ describe('Project Calc Permission', () => {
spaceType: SpaceType.Personal,
};
// 个人空间应该有查看权限
// Personal space should have viewing permission
expect(calcPermission(EProjectPermission.View, params)).toBe(true);
// 个人空间应该有编辑信息权限
// Personal space should have permission to edit information
expect(calcPermission(EProjectPermission.EDIT_INFO, params)).toBe(true);
// 个人空间应该有删除权限
// Personal space should have deletion permission
expect(calcPermission(EProjectPermission.DELETE, params)).toBe(true);
// 个人空间应该有发布权限
// Personal space should have publishing permission
expect(calcPermission(EProjectPermission.PUBLISH, params)).toBe(true);
// 个人空间应该有创建资源权限
// Personal space should have permission to create resources
expect(calcPermission(EProjectPermission.CREATE_RESOURCE, params)).toBe(
true,
);
// 个人空间应该有复制资源权限
// Personal space should have permission to copy resources
expect(calcPermission(EProjectPermission.COPY_RESOURCE, params)).toBe(
true,
);
// 个人空间应该有复制项目权限
// Personal space should have permission to copy items
expect(calcPermission(EProjectPermission.COPY, params)).toBe(true);
// 个人空间应该有测试运行插件权限
// Personal space should have test run plug-in permissions
expect(calcPermission(EProjectPermission.TEST_RUN_PLUGIN, params)).toBe(
true,
);
// 个人空间应该有测试运行工作流权限
// Personal space should have test run workflow permissions
expect(calcPermission(EProjectPermission.TEST_RUN_WORKFLOW, params)).toBe(
true,
);
@@ -75,12 +75,12 @@ describe('Project Calc Permission', () => {
spaceType: SpaceType.Personal,
};
// 个人空间不应该有添加协作者权限
// Personal Spaces should not have Add Collaborators permissions
expect(calcPermission(EProjectPermission.ADD_COLLABORATOR, params)).toBe(
false,
);
// 个人空间不应该有删除协作者权限
// Personal space should not have permission to delete collaborators
expect(
calcPermission(EProjectPermission.DELETE_COLLABORATOR, params),
).toBe(false);
@@ -95,47 +95,47 @@ describe('Project Calc Permission', () => {
spaceType: SpaceType.Team,
};
// 项目所有者应该有查看权限
// The project owner should have viewing rights
expect(calcPermission(EProjectPermission.View, params)).toBe(true);
// 项目所有者应该有编辑信息权限
// The project owner should have permission to edit the information
expect(calcPermission(EProjectPermission.EDIT_INFO, params)).toBe(true);
// 项目所有者应该有删除权限
// The project owner should have delete permissions
expect(calcPermission(EProjectPermission.DELETE, params)).toBe(true);
// 项目所有者应该有发布权限
// The project owner should have publishing rights
expect(calcPermission(EProjectPermission.PUBLISH, params)).toBe(true);
// 项目所有者应该有创建资源权限
// The project owner should have the Create Resource permission
expect(calcPermission(EProjectPermission.CREATE_RESOURCE, params)).toBe(
true,
);
// 项目所有者应该有复制资源权限
// The project owner should have permission to copy the resource
expect(calcPermission(EProjectPermission.COPY_RESOURCE, params)).toBe(
true,
);
// 项目所有者应该有复制项目权限
// The project owner should have permission to copy the project
expect(calcPermission(EProjectPermission.COPY, params)).toBe(true);
// 项目所有者应该有测试运行插件权限
// The project owner should have test run plug-in permissions
expect(calcPermission(EProjectPermission.TEST_RUN_PLUGIN, params)).toBe(
true,
);
// 项目所有者应该有测试运行工作流权限
// The project owner should have test run workflow permissions
expect(calcPermission(EProjectPermission.TEST_RUN_WORKFLOW, params)).toBe(
true,
);
// 项目所有者应该有添加协作者权限
// The project owner should have permission to add collaborators
expect(calcPermission(EProjectPermission.ADD_COLLABORATOR, params)).toBe(
true,
);
// 项目所有者应该有删除协作者权限
// The project owner should have the Delete Collaborator permission
expect(
calcPermission(EProjectPermission.DELETE_COLLABORATOR, params),
).toBe(true);
@@ -148,47 +148,47 @@ describe('Project Calc Permission', () => {
spaceType: SpaceType.Team,
};
// 项目编辑者应该有查看权限
// Project editors should have viewing rights
expect(calcPermission(EProjectPermission.View, params)).toBe(true);
// 项目编辑者应该有编辑信息权限
// Project editors should have permission to edit information
expect(calcPermission(EProjectPermission.EDIT_INFO, params)).toBe(true);
// 项目编辑者应该有创建资源权限
// Project editors should have the Create Resource permission
expect(calcPermission(EProjectPermission.CREATE_RESOURCE, params)).toBe(
true,
);
// 项目编辑者应该有复制资源权限
// Project editors should have permission to copy resources
expect(calcPermission(EProjectPermission.COPY_RESOURCE, params)).toBe(
true,
);
// 项目编辑者应该有复制项目权限
// The project editor should have permission to copy the project
expect(calcPermission(EProjectPermission.COPY, params)).toBe(true);
// 项目编辑者应该有测试运行插件权限
// Project editors should have test run plug-in permissions
expect(calcPermission(EProjectPermission.TEST_RUN_PLUGIN, params)).toBe(
true,
);
// 项目编辑者应该有测试运行工作流权限
// The project editor should have test run workflow permissions
expect(calcPermission(EProjectPermission.TEST_RUN_WORKFLOW, params)).toBe(
true,
);
// 项目编辑者应该有添加协作者权限
// Project editors should have Add Collaborators permission
expect(calcPermission(EProjectPermission.ADD_COLLABORATOR, params)).toBe(
true,
);
// 项目编辑者不应该有删除权限
// Project editors should not have delete permissions
expect(calcPermission(EProjectPermission.DELETE, params)).toBe(false);
// 项目编辑者不应该有发布权限
// Project editors should not have permission to publish
expect(calcPermission(EProjectPermission.PUBLISH, params)).toBe(false);
// 项目编辑者不应该有删除协作者权限
// Project editors should not have permission to delete collaborators
expect(
calcPermission(EProjectPermission.DELETE_COLLABORATOR, params),
).toBe(false);
@@ -203,47 +203,47 @@ describe('Project Calc Permission', () => {
spaceType: SpaceType.Team,
};
// 空间成员应该有查看权限
// Space members should have viewing rights
expect(calcPermission(EProjectPermission.View, params)).toBe(true);
// 空间成员应该有复制项目权限
// Space members should have permission to copy items
expect(calcPermission(EProjectPermission.COPY, params)).toBe(true);
// 空间成员应该有测试运行工作流权限
// Space members should have test run workflow permissions
expect(calcPermission(EProjectPermission.TEST_RUN_WORKFLOW, params)).toBe(
true,
);
// 空间成员不应该有编辑信息权限
// Space members should not have permission to edit information
expect(calcPermission(EProjectPermission.EDIT_INFO, params)).toBe(false);
// 空间成员不应该有删除权限
// Space members should not have delete permissions
expect(calcPermission(EProjectPermission.DELETE, params)).toBe(false);
// 空间成员不应该有发布权限
// Space members should not have publishing privileges
expect(calcPermission(EProjectPermission.PUBLISH, params)).toBe(false);
// 空间成员不应该有创建资源权限
// Space members should not have permission to create resources
expect(calcPermission(EProjectPermission.CREATE_RESOURCE, params)).toBe(
false,
);
// 空间成员不应该有复制资源权限
// Space members should not have permission to copy resources
expect(calcPermission(EProjectPermission.COPY_RESOURCE, params)).toBe(
false,
);
// 空间成员不应该有测试运行插件权限
// Space members should not have test run plug-in permissions
expect(calcPermission(EProjectPermission.TEST_RUN_PLUGIN, params)).toBe(
false,
);
// 空间成员不应该有添加协作者权限
// Space members should not have Add Collaborator permissions
expect(calcPermission(EProjectPermission.ADD_COLLABORATOR, params)).toBe(
false,
);
// 空间成员不应该有删除协作者权限
// Space members should not have permission to delete collaborators
expect(
calcPermission(EProjectPermission.DELETE_COLLABORATOR, params),
).toBe(false);
@@ -256,13 +256,13 @@ describe('Project Calc Permission', () => {
spaceType: SpaceType.Team,
};
// 空间所有者应该有查看权限
// Space owners should have viewing rights
expect(calcPermission(EProjectPermission.View, params)).toBe(true);
// 空间所有者应该有复制项目权限
// The space owner should have permission to copy items
expect(calcPermission(EProjectPermission.COPY, params)).toBe(true);
// 空间所有者应该有测试运行工作流权限
// Space owners should have test run workflow permissions
expect(calcPermission(EProjectPermission.TEST_RUN_WORKFLOW, params)).toBe(
true,
);
@@ -275,13 +275,13 @@ describe('Project Calc Permission', () => {
spaceType: SpaceType.Team,
};
// 空间管理员应该有查看权限
// The space administrator should have viewing rights
expect(calcPermission(EProjectPermission.View, params)).toBe(true);
// 空间管理员应该有复制项目权限
// The space administrator should have permission to copy items
expect(calcPermission(EProjectPermission.COPY, params)).toBe(true);
// 空间管理员应该有测试运行工作流权限
// The space administrator should have test run workflow permissions
expect(calcPermission(EProjectPermission.TEST_RUN_WORKFLOW, params)).toBe(
true,
);
@@ -294,7 +294,7 @@ describe('Project Calc Permission', () => {
spaceType: SpaceType.Team,
};
// 默认角色不应该有任何权限
// The default role should not have any permissions
expect(calcPermission(EProjectPermission.View, params)).toBe(false);
expect(calcPermission(EProjectPermission.EDIT_INFO, params)).toBe(false);
expect(calcPermission(EProjectPermission.DELETE, params)).toBe(false);
@@ -329,7 +329,7 @@ describe('Project Calc Permission', () => {
spaceType: SpaceType.Team,
};
// 应该有项目编辑者的所有权限
// Should have all the permissions of the project editor
expect(calcPermission(EProjectPermission.View, params)).toBe(true);
expect(calcPermission(EProjectPermission.EDIT_INFO, params)).toBe(true);
expect(calcPermission(EProjectPermission.CREATE_RESOURCE, params)).toBe(
@@ -349,7 +349,7 @@ describe('Project Calc Permission', () => {
true,
);
// 不应该有项目编辑者没有的权限
// There should be no permissions that the project editor does not have
expect(calcPermission(EProjectPermission.DELETE, params)).toBe(false);
expect(calcPermission(EProjectPermission.PUBLISH, params)).toBe(false);
expect(
@@ -364,7 +364,7 @@ describe('Project Calc Permission', () => {
spaceType: SpaceType.Team,
};
// 没有角色不应该有任何权限
// No role should have no permissions
expect(calcPermission(EProjectPermission.View, params)).toBe(false);
expect(calcPermission(EProjectPermission.EDIT_INFO, params)).toBe(false);
expect(calcPermission(EProjectPermission.DELETE, params)).toBe(false);

View File

@@ -24,28 +24,28 @@ import {
describe('Project Constants', () => {
describe('ProjectRoleType', () => {
it('应该定义所有必要的角色类型', () => {
// 验证所有角色类型都已定义
// Verify that all role types are defined
expect(ProjectRoleType.Owner).toBeDefined();
expect(ProjectRoleType.Editor).toBeDefined();
// 验证角色类型的值
// Validate the value of the role type
expect(ProjectRoleType.Owner).toBe('owner');
expect(ProjectRoleType.Editor).toBe('editor');
});
it('应该包含正确数量的角色类型', () => {
// 验证角色类型的数量
// Number of validation role types
const roleTypeCount = Object.keys(ProjectRoleType).filter(key =>
isNaN(Number(key)),
).length;
expect(roleTypeCount).toBe(2); // Owner Editor
expect(roleTypeCount).toBe(2); // Owner and Editor
});
});
describe('EProjectPermission', () => {
it('应该定义所有必要的权限点', () => {
// 验证所有权限点都已定义
// Verify that all permission spots are defined
expect(EProjectPermission.View).toBeDefined();
expect(EProjectPermission.EDIT_INFO).toBeDefined();
expect(EProjectPermission.DELETE).toBeDefined();
@@ -60,17 +60,17 @@ describe('Project Constants', () => {
});
it('应该为每个权限点分配唯一的值', () => {
// 创建一个集合来存储所有权限点的值
// Create a collection to store the values of all permission spots
const permissionValues = new Set();
// 获取所有权限点的值
// Get values for all permission spots
Object.values(EProjectPermission)
.filter(value => typeof value === 'number')
.forEach(value => {
permissionValues.add(value);
});
// 验证权限点的数量与唯一值的数量相同
// The number of validation permission spots is the same as the number of unique values
const numericKeys = Object.keys(EProjectPermission).filter(
key => !isNaN(Number(key)),
).length;
@@ -79,12 +79,12 @@ describe('Project Constants', () => {
});
it('应该包含正确数量的权限点', () => {
// 验证权限点的数量
// Number of permission spots verified
const permissionCount = Object.keys(EProjectPermission).filter(key =>
isNaN(Number(key)),
).length;
expect(permissionCount).toBe(12); // 11个权限点
expect(permissionCount).toBe(12); // 11 permission spots
});
});
});

View File

@@ -143,20 +143,20 @@ describe('Project Auth Store', () => {
const projectId = 'test-project-1';
const roles = [ProjectRoleType.Owner];
// 设置初始数据
// Set initial data
await act(() => {
result.current.setRoles(projectId, roles);
result.current.setIsReady(projectId, true);
});
// 验证数据已设置
// Verify that the data is set
expect(result.current.roles[projectId]).toEqual(roles);
expect(result.current.isReady[projectId]).toBe(true);
// 销毁数据
// Destroy data
result.current.destory(projectId);
// 验证数据已清除
// Verify that the data has been cleared
expect(result.current.roles[projectId]).toEqual([]);
expect(result.current.isReady[projectId]).toBe(false);
});
@@ -171,16 +171,16 @@ describe('Project Auth Store', () => {
const roles1 = [ProjectRoleType.Owner];
const roles2 = [ProjectRoleType.Editor];
// 设置初始数据
// Set initial data
result.current.setRoles(projectId1, roles1);
result.current.setRoles(projectId2, roles2);
result.current.setIsReady(projectId1, true);
result.current.setIsReady(projectId2, true);
// 销毁项目1的数据
// Destruction of data for item 1
result.current.destory(projectId1);
// 验证项目1的数据已清除项目2的数据保持不变
// Verify that the data for item 1 has been cleared and that the data for item 2 remains unchanged
expect(result.current.roles[projectId1]).toEqual([]);
expect(result.current.isReady[projectId1]).toBe(false);
expect(result.current.roles[projectId2]).toEqual(roles2);

View File

@@ -17,15 +17,15 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { renderHook } from '@testing-library/react-hooks';
// 模拟 React 的 useEffect
// The useEffect of React
const cleanupFns = new Map();
vi.mock('react', () => ({
useEffect: vi.fn((fn, deps) => {
// 执行 effect 函数并获取清理函数
// Execute the effect function and get the cleanup function
const cleanup = fn();
// 存储清理函数,以便在 unmount 时调用
// Store the cleanup function to call when unmounted
cleanupFns.set(fn, cleanup);
// 返回清理函数
// Return cleanup function
return cleanup;
}),
}));
@@ -33,7 +33,7 @@ vi.mock('react', () => ({
import { useDestoryProject } from '../../src/project/use-destory-project';
import { useProjectAuthStore } from '../../src/project/store';
// 模拟 useProjectAuthStore
// emulation useProjectAuthStore
vi.mock('../../src/project/store', () => {
const destorySpy = vi.fn();
return {
@@ -41,19 +41,19 @@ vi.mock('../../src/project/store', () => {
};
});
// 创建一个包装函数,确保在 unmount 时调用清理函数
// Create a wrapper function to ensure that the cleanup function is called when unmounted
function renderHookWithCleanup(callback, options = {}) {
const result = renderHook(callback, options);
const originalUnmount = result.unmount;
result.unmount = () => {
// 调用所有清理函数
// Call all cleanup functions
cleanupFns.forEach(cleanup => {
if (typeof cleanup === 'function') {
cleanup();
}
});
// 调用原始的 unmount
// Call the original unmount
originalUnmount();
};
@@ -70,21 +70,21 @@ describe('useDestoryProject', () => {
const projectId = 'test-project-id';
const destorySpy = vi.fn();
// 模拟 useProjectAuthStore 返回 destorySpy
// Emulate useProjectAuthStore returns destorySpy
(useProjectAuthStore as any).mockReturnValue(destorySpy);
// 渲染 hook
// Render hook
const { unmount } = renderHookWithCleanup(() =>
useDestoryProject(projectId),
);
// 验证初始状态下 destory 未被调用
// Verify that destory is not called in the initial state
expect(destorySpy).not.toHaveBeenCalled();
// 卸载组件
// uninstall components
unmount();
// 验证 destory 被调用,且参数正确
// Verify that destory is called and the parameters are correct
expect(destorySpy).toHaveBeenCalledTimes(1);
expect(destorySpy).toHaveBeenCalledWith(projectId);
});
@@ -93,18 +93,18 @@ describe('useDestoryProject', () => {
const projectId1 = 'test-project-id-1';
const destorySpy = vi.fn();
// 模拟 useProjectAuthStore 返回 destorySpy
// Emulate useProjectAuthStore returns destorySpy
(useProjectAuthStore as any).mockReturnValue(destorySpy);
// 渲染 hook
// Render hook
const { unmount } = renderHookWithCleanup(() =>
useDestoryProject(projectId1),
);
// 卸载组件
// uninstall components
unmount();
// 验证 destory 被调用,且参数为 projectId1
// Verify that destory is called with the parameter projectId1.
expect(destorySpy).toHaveBeenCalledTimes(1);
expect(destorySpy).toHaveBeenCalledWith(projectId1);
});
@@ -113,22 +113,22 @@ describe('useDestoryProject', () => {
const projectId2 = 'test-project-id-2';
const destorySpy = vi.fn();
// 清除之前的所有模拟和清理函数
// Clear all previous simulation and cleanup functions
vi.clearAllMocks();
cleanupFns.clear();
// 模拟 useProjectAuthStore 返回 destorySpy
// Emulate useProjectAuthStore returns destorySpy
(useProjectAuthStore as any).mockReturnValue(destorySpy);
// 渲染 hook
// Render hook
const { unmount } = renderHookWithCleanup(() =>
useDestoryProject(projectId2),
);
// 卸载组件
// uninstall components
unmount();
// 验证 destory 被调用,且参数为 projectId2
// Verify that destory is called with the parameter projectId2.
expect(destorySpy).toHaveBeenCalledTimes(1);
expect(destorySpy).toHaveBeenCalledWith(projectId2);
});

View File

@@ -29,7 +29,7 @@ import {
} from '../../src/project/constants';
import { calcPermission } from '../../src/project/calc-permission';
// 模拟依赖
// simulated dependency
vi.mock('@coze-arch/foundation-sdk', () => ({
useSpace: vi.fn(),
}));
@@ -54,94 +54,94 @@ describe('useProjectAuth', () => {
beforeEach(() => {
vi.clearAllMocks();
// 模拟 useSpace 返回空间信息
// Simulating useSpace returns spatial information
(useSpace as any).mockReturnValue({
space_type: SpaceType.Team,
});
// 模拟 useSpaceRole 返回空间角色
// Simulate useSpaceRole Return Space Role
(useSpaceRole as any).mockReturnValue([SpaceRoleType.Member]);
// 模拟 useProjectRole 返回项目角色
// Simulate useProjectRole Return project role
(useProjectRole as any).mockReturnValue([ProjectRoleType.Editor]);
// 模拟 calcPermission 返回权限结果
// Simulate calcPermission return permission result
(calcPermission as any).mockReturnValue(true);
});
it('应该调用 calcPermission 并返回正确的权限结果', () => {
// 渲染 hook
// Render hook
const { result } = renderHook(() =>
useProjectAuth(permissionKey, projectId, spaceId),
);
// 验证 useSpace 被调用
// Verify that useSpace is called
expect(useSpace).toHaveBeenCalledWith(spaceId);
// 验证 useSpaceRole 被调用
// Verify useSpaceRole is called
expect(useSpaceRole).toHaveBeenCalledWith(spaceId);
// 验证 useProjectRole 被调用
// Verify useProjectRole is called
expect(useProjectRole).toHaveBeenCalledWith(projectId);
// 验证 calcPermission 被调用,且参数正确
// Verify that calcPermission is called and the parameters are correct
expect(calcPermission).toHaveBeenCalledWith(permissionKey, {
projectRoles: [ProjectRoleType.Editor],
spaceRoles: [SpaceRoleType.Member],
spaceType: SpaceType.Team,
});
// 验证返回值
// Validate the return value
expect(result.current).toBe(true);
});
it('应该在 calcPermission 返回 false 时返回 false', () => {
// 模拟 calcPermission 返回 false
// simulated calcPermission returns false
(calcPermission as any).mockReturnValue(false);
// 渲染 hook
// Render hook
const { result } = renderHook(() =>
useProjectAuth(permissionKey, projectId, spaceId),
);
// 验证返回值
// Validate the return value
expect(result.current).toBe(false);
});
it('应该在空间类型不存在时抛出错误', () => {
// 模拟 useSpace 返回没有 space_type 的对象
// Mock useSpace returns objects without space_type
(useSpace as any).mockReturnValue({});
// 使用 vi.spyOn 监听 console.error 以防止测试输出错误信息
// Use vi.spyOn to listen to console.error to prevent test output error messages
vi.spyOn(console, 'error').mockImplementation(() => {
// 空实现,防止错误输出
// Empty implementation to prevent error output
});
// 验证抛出错误
// validation throws error
expect(() => {
const { result } = renderHook(() =>
useProjectAuth(permissionKey, projectId, spaceId),
);
// 强制访问 result.current 触发错误
// Force access result.current trigger error
console.log(result.current);
}).toThrow('useSpaceAuth must be used after space list has been pulled.');
});
it('应该在空间为 null 时抛出错误', () => {
// 模拟 useSpace 返回 null
// Simulate useSpace returns null
(useSpace as any).mockReturnValue(null);
// 使用 vi.spyOn 监听 console.error 以防止测试输出错误信息
// Use vi.spyOn to listen to console.error to prevent test output error messages
vi.spyOn(console, 'error').mockImplementation(() => {
// 空实现,防止错误输出
// Empty implementation to prevent error output
});
// 验证抛出错误
// validation throws error
expect(() => {
const { result } = renderHook(() =>
useProjectAuth(permissionKey, projectId, spaceId),
);
// 强制访问 result.current 触发错误
// Force access result.current trigger error
console.log(result.current);
}).toThrow('useSpaceAuth must be used after space list has been pulled.');
});

View File

@@ -21,7 +21,7 @@ import { useProjectRole } from '../../src/project/use-project-role';
import { useProjectAuthStore } from '../../src/project/store';
import { ProjectRoleType } from '../../src/project/constants';
// 模拟依赖
// simulated dependency
vi.mock('../../src/project/store', () => ({
useProjectAuthStore: vi.fn(),
}));
@@ -36,38 +36,38 @@ describe('useProjectRole', () => {
it('应该返回正确的项目角色', () => {
const expectedRoles = [ProjectRoleType.Owner];
// 模拟 useProjectAuthStore 返回项目角色和 ready 状态
// Mock useProjectAuthStore returns project role and ready state
(useProjectAuthStore as any).mockReturnValue({
isReady: true,
role: expectedRoles,
});
// 渲染 hook
// Render hook
const { result } = renderHook(() => useProjectRole(projectId));
// 验证 useProjectAuthStore 被调用
// Verify useProjectAuthStore is called
expect(useProjectAuthStore).toHaveBeenCalled();
// 验证返回值
// Validate the return value
expect(result.current).toEqual(expectedRoles);
});
it('应该在项目未准备好时抛出错误', () => {
// 模拟 useProjectAuthStore 返回未准备好的状态
// Simulate useProjectAuthStore returns an unprepared state
(useProjectAuthStore as any).mockReturnValue({
isReady: false,
role: [],
});
// 使用 vi.spyOn 监听 console.error 以防止测试输出错误信息
// Use vi.spyOn to listen to console.error to prevent test output error messages
vi.spyOn(console, 'error').mockImplementation(() => {
// 空实现,防止错误输出
// Empty implementation to prevent error output
});
// 验证抛出错误
// validation throws error
expect(() => {
const { result } = renderHook(() => useProjectRole(projectId));
// 强制访问 result.current 触发错误
// Force access result.current trigger error
console.log(result.current);
}).toThrow(
'useProjectAuth must be used after useInitProjectRole has been completed.',
@@ -75,32 +75,32 @@ describe('useProjectRole', () => {
});
it('应该在角色为 undefined 时返回空数组', () => {
// 模拟 useProjectAuthStore 返回 undefined 角色
// Emulate useProjectAuthStore returns undefined role
(useProjectAuthStore as any).mockReturnValue({
isReady: true,
role: undefined,
});
// 渲染 hook
// Render hook
const { result } = renderHook(() => useProjectRole(projectId));
// 验证返回值为空数组
// Verify that the return value is an empty array
expect(result.current).toEqual([]);
});
it('应该处理多种角色类型', () => {
const expectedRoles = [ProjectRoleType.Owner, ProjectRoleType.Editor];
// 模拟 useProjectAuthStore 返回多种角色
// Emulate useProjectAuthStore returns multiple roles
(useProjectAuthStore as any).mockReturnValue({
isReady: true,
role: expectedRoles,
});
// 渲染 hook
// Render hook
const { result } = renderHook(() => useProjectRole(projectId));
// 验证返回值
// Validate the return value
expect(result.current).toEqual(expectedRoles);
});
});

View File

@@ -23,146 +23,146 @@ import { calcPermission } from '../../src/space/calc-permission';
describe('Space Calc Permission', () => {
describe('calcPermission', () => {
it('应该为 Owner 角色返回正确的权限', () => {
// Owner 应该有更新空间的权限
// Owner should have permission to update the space
expect(
calcPermission(ESpacePermisson.UpdateSpace, [SpaceRoleType.Owner]),
).toBe(true);
// Owner 应该有删除空间的权限
// Owner should have permission to delete space
expect(
calcPermission(ESpacePermisson.DeleteSpace, [SpaceRoleType.Owner]),
).toBe(true);
// Owner 应该有添加成员的权限
// Owner should have permission to add members
expect(
calcPermission(ESpacePermisson.AddBotSpaceMember, [
SpaceRoleType.Owner,
]),
).toBe(true);
// Owner 应该有移除成员的权限
// Owner should have permission to remove members
expect(
calcPermission(ESpacePermisson.RemoveSpaceMember, [
SpaceRoleType.Owner,
]),
).toBe(true);
// Owner 应该有转移所有权的权限
// Owner should have the right to transfer ownership
expect(
calcPermission(ESpacePermisson.TransferSpace, [SpaceRoleType.Owner]),
).toBe(true);
// Owner 应该有更新成员的权限
// Owner should have permission to update members
expect(
calcPermission(ESpacePermisson.UpdateSpaceMember, [
SpaceRoleType.Owner,
]),
).toBe(true);
// Owner 应该有管理 API 的权限
// Owner should have permission to manage the API
expect(calcPermission(ESpacePermisson.API, [SpaceRoleType.Owner])).toBe(
true,
);
});
it('应该为 Admin 角色返回正确的权限', () => {
// Admin 应该有添加成员的权限
// Admin should have permission to add members
expect(
calcPermission(ESpacePermisson.AddBotSpaceMember, [
SpaceRoleType.Admin,
]),
).toBe(true);
// Admin 应该有移除成员的权限
// Admin should have permission to remove members
expect(
calcPermission(ESpacePermisson.RemoveSpaceMember, [
SpaceRoleType.Admin,
]),
).toBe(true);
// Admin 应该有退出空间的权限
// Admin should have permission to exit the space
expect(
calcPermission(ESpacePermisson.ExitSpace, [SpaceRoleType.Admin]),
).toBe(true);
// Admin 应该有更新成员的权限
// Admin should have permission to update members
expect(
calcPermission(ESpacePermisson.UpdateSpaceMember, [
SpaceRoleType.Admin,
]),
).toBe(true);
// Admin 不应该有更新空间的权限
// Admin should not have permission to update the space
expect(
calcPermission(ESpacePermisson.UpdateSpace, [SpaceRoleType.Admin]),
).toBe(false);
// Admin 不应该有删除空间的权限
// Admin should not have permission to delete space
expect(
calcPermission(ESpacePermisson.DeleteSpace, [SpaceRoleType.Admin]),
).toBe(false);
// Admin 不应该有转移所有权的权限
// Admin should not have permission to transfer ownership
expect(
calcPermission(ESpacePermisson.TransferSpace, [SpaceRoleType.Admin]),
).toBe(false);
// Admin 不应该有管理 API 的权限
// Admin should not have permission to manage APIs
expect(calcPermission(ESpacePermisson.API, [SpaceRoleType.Admin])).toBe(
false,
);
});
it('应该为 Member 角色返回正确的权限', () => {
// Member 应该有退出空间的权限
// Members should have permission to exit the space
expect(
calcPermission(ESpacePermisson.ExitSpace, [SpaceRoleType.Member]),
).toBe(true);
// Member 不应该有更新空间的权限
// Members should not have permission to update space
expect(
calcPermission(ESpacePermisson.UpdateSpace, [SpaceRoleType.Member]),
).toBe(false);
// Member 不应该有删除空间的权限
// Members should not have permission to delete space
expect(
calcPermission(ESpacePermisson.DeleteSpace, [SpaceRoleType.Member]),
).toBe(false);
// Member 不应该有添加成员的权限
// Members should not have permission to add members
expect(
calcPermission(ESpacePermisson.AddBotSpaceMember, [
SpaceRoleType.Member,
]),
).toBe(false);
// Member 不应该有移除成员的权限
// Members should not have permission to remove members
expect(
calcPermission(ESpacePermisson.RemoveSpaceMember, [
SpaceRoleType.Member,
]),
).toBe(false);
// Member 不应该有转移所有权的权限
// Members should not have permission to transfer ownership
expect(
calcPermission(ESpacePermisson.TransferSpace, [SpaceRoleType.Member]),
).toBe(false);
// Member 不应该有更新成员的权限
// Members should not have permission to update members
expect(
calcPermission(ESpacePermisson.UpdateSpaceMember, [
SpaceRoleType.Member,
]),
).toBe(false);
// Member 不应该有管理 API 的权限
// Members should not have permission to manage APIs
expect(calcPermission(ESpacePermisson.API, [SpaceRoleType.Member])).toBe(
false,
);
});
it('应该为 Default 角色返回正确的权限', () => {
// Default 不应该有任何权限
// Default should not have any permissions
expect(
calcPermission(ESpacePermisson.UpdateSpace, [SpaceRoleType.Default]),
).toBe(false);
@@ -196,7 +196,7 @@ describe('Space Calc Permission', () => {
});
it('应该处理多个角色的情况', () => {
// 当用户同时拥有 Member Admin 角色时,应该有两个角色的所有权限
// When a user has both the Member and Admin roles, they should have all the permissions of both roles
expect(
calcPermission(ESpacePermisson.ExitSpace, [
SpaceRoleType.Member,
@@ -211,7 +211,7 @@ describe('Space Calc Permission', () => {
]),
).toBe(true);
// 即使其中一个角色没有权限,只要有一个角色有权限,就应该返回 true
// Even if one of the roles has no permissions, it should return true as long as one of the roles has permissions.
expect(
calcPermission(ESpacePermisson.UpdateSpace, [
SpaceRoleType.Member,
@@ -221,13 +221,13 @@ describe('Space Calc Permission', () => {
});
it('应该处理空角色数组', () => {
// 当没有角色时,应该返回 false
// When there is no role, it should return false.
expect(calcPermission(ESpacePermisson.UpdateSpace, [])).toBe(false);
expect(calcPermission(ESpacePermisson.ExitSpace, [])).toBe(false);
});
it('应该处理未知角色', () => {
// 当角色未知时,应该返回 false
// When the character is unknown, it should return false.
expect(
calcPermission(ESpacePermisson.UpdateSpace, [
'UnknownRole' as unknown as SpaceRoleType,

View File

@@ -22,7 +22,7 @@ import { ESpacePermisson } from '../../src/space/constants';
describe('Space Constants', () => {
describe('ESpacePermisson', () => {
it('应该定义所有必要的权限点', () => {
// 验证所有权限点都已定义
// Verify that all permission spots are defined
expect(ESpacePermisson.UpdateSpace).toBeDefined();
expect(ESpacePermisson.DeleteSpace).toBeDefined();
expect(ESpacePermisson.AddBotSpaceMember).toBeDefined();
@@ -34,17 +34,17 @@ describe('Space Constants', () => {
});
it('应该为每个权限点分配唯一的值', () => {
// 创建一个集合来存储所有权限点的值
// Create a collection to store the values of all permission spots
const permissionValues = new Set();
// 获取所有权限点的值
// Get values for all permission spots
Object.values(ESpacePermisson)
.filter(value => typeof value === 'number')
.forEach(value => {
permissionValues.add(value);
});
// 验证权限点的数量与唯一值的数量相同
// The number of validation permission spots is the same as the number of unique values
const numericKeys = Object.keys(ESpacePermisson).filter(
key => !isNaN(Number(key)),
).length;
@@ -55,10 +55,10 @@ describe('Space Constants', () => {
describe('SpaceRoleType', () => {
it('应该正确导出 SpaceRoleType', () => {
// 验证 SpaceRoleType 已正确导出
// Verify that SpaceRoleType has been exported correctly
expect(SpaceRoleType).toBeDefined();
// 验证 SpaceRoleType 包含必要的角色
// Verify that SpaceRoleType contains the necessary roles
expect(SpaceRoleType.Owner).toBeDefined();
expect(SpaceRoleType.Admin).toBeDefined();
expect(SpaceRoleType.Member).toBeDefined();

View File

@@ -18,18 +18,18 @@ import { describe, it, expect, beforeEach, vi } from 'vitest';
import { renderHook, act } from '@testing-library/react-hooks';
import { SpaceRoleType } from '@coze-arch/idl/developer_api';
// 模拟全局变量
// simulated global variable
vi.stubGlobal('IS_DEV_MODE', true);
describe('Space Auth Store', () => {
beforeEach(() => {
// 重置模块缓存,确保每个测试都使用新的 store 实例
// Reset the module cache to ensure that each test uses a new store instance
vi.resetModules();
});
describe('setRoles', () => {
it('应该正确设置空间角色', async () => {
// 动态导入 store 模块,确保每次测试都获取新的实例
// Dynamically import the store module to ensure that each test gets a new instance
const { useSpaceAuthStore } = await vi.importActual(
'../../src/space/store',
);
@@ -137,22 +137,22 @@ describe('Space Auth Store', () => {
const { result } = renderHook(() => useSpaceAuthStore());
const roles = [SpaceRoleType.Owner];
// 设置初始数据
// Set initial data
await act(() => {
result.current.setRoles('space1', roles);
result.current.setIsReady('space1', true);
});
// 验证数据已设置
// Verify that the data is set
expect(result.current.roles.space1).toEqual(roles);
expect(result.current.isReady.space1).toBe(true);
// 销毁数据
// Destroy data
await act(() => {
result.current.destory('space1');
});
// 验证数据已清除
// Verify that the data has been cleared
expect(result.current.roles.space1).toEqual([]);
expect(result.current.isReady.space1).toBeUndefined();
});
@@ -163,7 +163,7 @@ describe('Space Auth Store', () => {
);
const { result } = renderHook(() => useSpaceAuthStore());
// 设置两个空间的数据
// Set data for two spaces
await act(() => {
result.current.setRoles('space1', [SpaceRoleType.Owner]);
result.current.setIsReady('space1', true);
@@ -171,16 +171,16 @@ describe('Space Auth Store', () => {
result.current.setIsReady('space2', true);
});
// 只销毁 space1 的数据
// Only destroy space1 data
await act(() => {
result.current.destory('space1');
});
// 验证 space1 的数据已清除
// Verify that Space1's data has been cleared
expect(result.current.roles.space1).toEqual([]);
expect(result.current.isReady.space1).toBeUndefined();
// 验证 space2 的数据保持不变
// Verify that Space2's data remains unchanged
expect(result.current.roles.space2).toEqual([SpaceRoleType.Member]);
expect(result.current.isReady.space2).toBe(true);
});
@@ -193,14 +193,14 @@ describe('Space Auth Store', () => {
);
const { result } = renderHook(() => useSpaceAuthStore());
// 重置 store 确保测试环境干净
// Reset store to ensure testing environment is clean
await act(() => {
Object.keys(result.current.roles).forEach(spaceId => {
result.current.destory(spaceId);
});
});
// 验证初始状态
// Verify the initial state
expect(result.current.roles).toEqual({});
expect(result.current.isReady).toEqual({});
});

View File

@@ -17,38 +17,38 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { renderHook } from '@testing-library/react-hooks';
// 模拟 React 的 useEffect
// The useEffect of React
const cleanupFns = new Map();
vi.mock('react', () => ({
useEffect: vi.fn((fn, deps) => {
// 执行 effect 函数并获取清理函数
// Execute the effect function and get the cleanup function
const cleanup = fn();
// 存储清理函数,以便在 unmount 时调用
// Store the cleanup function to call when unmounted
cleanupFns.set(fn, cleanup);
// 返回清理函数
// Return cleanup function
return cleanup;
}),
}));
// 模拟 store
// Simulated store
const mockDestory = vi.fn();
vi.mock('../../src/space/store', () => ({
useSpaceAuthStore: vi.fn(selector => selector({ destory: mockDestory })),
}));
// 创建一个包装函数,确保在 unmount 时调用清理函数
// Create a wrapper function to ensure that the cleanup function is called when unmounted
function renderHookWithCleanup(callback, options = {}) {
const result = renderHook(callback, options);
const originalUnmount = result.unmount;
result.unmount = () => {
// 调用所有清理函数
// Call all cleanup functions
cleanupFns.forEach(cleanup => {
if (typeof cleanup === 'function') {
cleanup();
}
});
// 调用原始的 unmount
// Call the original unmount
originalUnmount();
};
@@ -66,16 +66,16 @@ describe('useDestorySpace', () => {
it('应该在组件卸载时调用 destory 方法', () => {
const spaceId = 'test-space-id';
// 渲染 hook
// Render hook
const { unmount } = renderHookWithCleanup(() => useDestorySpace(spaceId));
// 初始时不应调用 destory
// Destory should not be called initially
expect(mockDestory).not.toHaveBeenCalled();
// 模拟组件卸载
// Simulate component uninstall
unmount();
// 卸载时应调用 destory 并传入正确的 spaceId
// When uninstalling, call destory and pass in the correct spaceId.
expect(mockDestory).toHaveBeenCalledTimes(1);
expect(mockDestory).toHaveBeenCalledWith(spaceId);
});
@@ -84,25 +84,25 @@ describe('useDestorySpace', () => {
const spaceId1 = 'space-id-1';
const spaceId2 = 'space-id-2';
// 渲染第一个 hook 实例
// Render the first hook instance
const { unmount: unmount1 } = renderHookWithCleanup(() =>
useDestorySpace(spaceId1),
);
// 渲染第二个 hook 实例
// Render the second hook instance
const { unmount: unmount2 } = renderHookWithCleanup(() =>
useDestorySpace(spaceId2),
);
// 卸载第一个实例
// Uninstall the first instance
unmount1();
expect(mockDestory).toHaveBeenCalledWith(spaceId1);
// 卸载第二个实例
// Uninstall the second instance
unmount2();
expect(mockDestory).toHaveBeenCalledWith(spaceId2);
// 总共应调用两次
// It should be called twice in total.
expect(mockDestory).toHaveBeenCalledTimes(4);
});
});

View File

@@ -20,12 +20,12 @@ import { SpaceRoleType } from '@coze-arch/idl/developer_api';
import { ESpacePermisson } from '../../src/space/constants';
// 模拟 useSpaceRole
// Simulation useSpaceRole
vi.mock('../../src/space/use-space-role', () => ({
useSpaceRole: vi.fn(),
}));
// 模拟 calcPermission
// simulated calcPermission
vi.mock('../../src/space/calc-permission', () => ({
calcPermission: vi.fn(),
}));
@@ -40,26 +40,26 @@ describe('useSpaceAuth', () => {
const permissionKey = ESpacePermisson.UpdateSpace;
const mockRoles = [SpaceRoleType.Owner];
// 模拟 useSpaceRole 返回角色
// Simulate useSpaceRole return role
(useSpaceRole as unknown as ReturnType<typeof vi.fn>).mockReturnValue(
mockRoles,
);
// 模拟 calcPermission 返回权限结果
// Simulate calcPermission return permission result
(calcPermission as unknown as ReturnType<typeof vi.fn>).mockReturnValue(
true,
);
// 渲染 hook
// Render hook
const { result } = renderHook(() => useSpaceAuth(permissionKey, spaceId));
// 验证 useSpaceRole 被调用,并传入正确的 spaceId
// Verify that useSpaceRole is called, passing in the correct spaceId.
expect(useSpaceRole).toHaveBeenCalledWith(spaceId);
// 验证 calcPermission 被调用,并传入正确的参数
// Verify that calcPermission is called, passing in the correct parameters
expect(calcPermission).toHaveBeenCalledWith(permissionKey, mockRoles);
// 验证返回值与 calcPermission 的返回值一致
// Verify that the return value is consistent with the return value of calcPermission
expect(result.current).toBe(true);
});
@@ -68,20 +68,20 @@ describe('useSpaceAuth', () => {
const permissionKey = ESpacePermisson.UpdateSpace;
const mockRoles = [SpaceRoleType.Member];
// 模拟 useSpaceRole 返回角色
// Simulate useSpaceRole return role
(useSpaceRole as unknown as ReturnType<typeof vi.fn>).mockReturnValue(
mockRoles,
);
// 模拟 calcPermission 返回权限结果
// Simulate calcPermission return permission result
(calcPermission as unknown as ReturnType<typeof vi.fn>).mockReturnValue(
false,
);
// 渲染 hook
// Render hook
const { result } = renderHook(() => useSpaceAuth(permissionKey, spaceId));
// 验证返回值与 calcPermission 的返回值一致
// Verify that the return value is consistent with the return value of calcPermission
expect(result.current).toBe(false);
});
@@ -90,23 +90,23 @@ describe('useSpaceAuth', () => {
const permissionKey = ESpacePermisson.UpdateSpace;
const mockRoles: SpaceRoleType[] = [];
// 模拟 useSpaceRole 返回空角色数组
// Simulate useSpaceRole returns an empty character array
(useSpaceRole as unknown as ReturnType<typeof vi.fn>).mockReturnValue(
mockRoles,
);
// 模拟 calcPermission 返回权限结果
// Simulate calcPermission return permission result
(calcPermission as unknown as ReturnType<typeof vi.fn>).mockReturnValue(
false,
);
// 渲染 hook
// Render hook
const { result } = renderHook(() => useSpaceAuth(permissionKey, spaceId));
// 验证 calcPermission 被调用,并传入正确的参数
// Verify that calcPermission is called, passing in the correct parameters
expect(calcPermission).toHaveBeenCalledWith(permissionKey, mockRoles);
// 验证返回值与 calcPermission 的返回值一致
// Verify that the return value is consistent with the return value of calcPermission
expect(result.current).toBe(false);
});
});

View File

@@ -20,23 +20,23 @@ import { SpaceRoleType } from '@coze-arch/idl/developer_api';
import { useSpaceAuthStore } from '../../src/space/store';
// 模拟 zustand
// Analog zustand
vi.mock('zustand/react/shallow', () => ({
useShallow: fn => fn,
}));
// 模拟 foundation-sdk
// Simulation foundation-sdk
const mockUseSpace = vi.fn();
vi.mock('@coze-arch/foundation-sdk', () => ({
useSpace: (...args) => mockUseSpace(...args),
}));
// 模拟 store
// Simulated store
vi.mock('../../src/space/store', () => ({
useSpaceAuthStore: vi.fn(),
}));
// 导入实际模块,确保在模拟之后导入
// Import the actual module, make sure to import it after simulation
import { useSpaceRole } from '../../src/space/use-space-role';
describe('useSpaceRole', () => {
@@ -49,40 +49,40 @@ describe('useSpaceRole', () => {
const mockSpace = { id: spaceId, name: 'Test Space' };
const mockRoles = [SpaceRoleType.Owner];
// 模拟 useSpace 返回 space 对象
// Simulate useSpace Return space object
mockUseSpace.mockReturnValue(mockSpace);
// 模拟 useSpaceAuthStore 返回 isReady role
// Emulate useSpaceAuthStore returns isReady and role
(useSpaceAuthStore as unknown as ReturnType<typeof vi.fn>).mockReturnValue({
isReady: true,
role: mockRoles,
});
// 渲染 hook
// Render hook
const { result } = renderHook(() => useSpaceRole(spaceId));
// 验证 useSpace 被调用,并传入正确的 spaceId
// Verify that useSpace is called, passing in the correct spaceId.
expect(mockUseSpace).toHaveBeenCalledWith(spaceId);
// 验证 useSpaceAuthStore 被调用,并传入正确的选择器
// Verify that useSpaceAuthStore is called, passing in the correct selector
expect(useSpaceAuthStore).toHaveBeenCalled();
// 验证返回值与预期一致
// Verify that the return value is as expected
expect(result.current).toEqual(mockRoles);
});
it('应该在 space 不存在时抛出错误', () => {
const spaceId = 'test-space-id';
// 模拟 useSpace 返回 null
// Simulate useSpace returns null
mockUseSpace.mockReturnValue(null);
// 使用 vi.spyOn 监听 console.error 以防止测试输出错误信息
// Use vi.spyOn to listen to console.error to prevent test output error messages
vi.spyOn(console, 'error').mockImplementation(() => {
// 空实现,防止错误输出
// Empty implementation to prevent error output
});
// 验证渲染 hook 时抛出错误
// Error thrown while validating render hook
expect(() => useSpaceRole(spaceId)).toThrow(
'useSpaceAuth must be used after space list has been pulled.',
);
@@ -92,21 +92,21 @@ describe('useSpaceRole', () => {
const spaceId = 'test-space-id';
const mockSpace = { id: spaceId, name: 'Test Space' };
// 模拟 useSpace 返回 space 对象
// Simulate useSpace Return space object
mockUseSpace.mockReturnValue(mockSpace);
// 模拟 useSpaceAuthStore 返回 isReady false
// Emulate useSpaceAuthStore returns isReady to false
(useSpaceAuthStore as unknown as ReturnType<typeof vi.fn>).mockReturnValue({
isReady: false,
role: null,
});
// 使用 vi.spyOn 监听 console.error 以防止测试输出错误信息
// Use vi.spyOn to listen to console.error to prevent test output error messages
vi.spyOn(console, 'error').mockImplementation(() => {
// 空实现,防止错误输出
// Empty implementation to prevent error output
});
// 验证渲染 hook 时抛出错误
// Error thrown while validating render hook
expect(() => useSpaceRole(spaceId)).toThrow(
'useSpaceAuth must be used after useInitSpaceRole has been completed.',
);
@@ -116,21 +116,21 @@ describe('useSpaceRole', () => {
const spaceId = 'test-space-id';
const mockSpace = { id: spaceId, name: 'Test Space' };
// 模拟 useSpace 返回 space 对象
// Simulate useSpace Return space object
mockUseSpace.mockReturnValue(mockSpace);
// 模拟 useSpaceAuthStore 返回 isReady true,但 role null
// Emulate useSpaceAuthStore returns isReady as true, but role as null
(useSpaceAuthStore as unknown as ReturnType<typeof vi.fn>).mockReturnValue({
isReady: true,
role: null,
});
// 使用 vi.spyOn 监听 console.error 以防止测试输出错误信息
// Use vi.spyOn to listen to console.error to prevent test output error messages
vi.spyOn(console, 'error').mockImplementation(() => {
// 空实现,防止错误输出
// Empty implementation to prevent error output
});
// 验证渲染 hook 时抛出错误
// Error thrown while validating render hook
expect(() => useSpaceRole(spaceId)).toThrow(
`Can not get space role of space: ${spaceId}`,
);

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
// TODO: 替换成Project接口导出的idl
// TODO: replace with idl exported by Project interface
export enum ProjectRoleType {
Owner = 'owner',
Editor = 'editor',
@@ -22,51 +22,51 @@ export enum ProjectRoleType {
export enum EProjectPermission {
/**
* 访问/查看project
* Visit/view projects
*/
View,
/**
* 编辑project基础信息
* Edit project basic information
*/
EDIT_INFO,
/**
* 删除project
* Delete project
*/
DELETE,
/**
* 发布project
* Publish project
*/
PUBLISH,
/**
* 创建project内资源
* Create project resources
*/
CREATE_RESOURCE,
/**
* project内复制资源
* Copy resources within the project
*/
COPY_RESOURCE,
/**
* 复制project/创建副本
* Copy project/create copy
*/
COPY,
/**
* 试运行plugin
* Practice running plugins
*/
TEST_RUN_PLUGIN,
/**
* 试运行workflow
* Practice running workflow
*/
TEST_RUN_WORKFLOW,
/**
* 添加project协作者
* Add project collaborators
*/
ADD_COLLABORATOR,
/**
* 删除project协作者
* Delete project collaborator
*/
DELETE_COLLABORATOR,
/**
* 回滚 APP 版本
* Roll back the APP version
*/
ROLLBACK,
}

View File

@@ -20,26 +20,26 @@ import { create } from 'zustand';
import { type ProjectRoleType } from './constants';
interface ProjectAuthStoreState {
// 每一个Project的角色数据
// Role Data for Each Project
roles: {
[projectId: string]: ProjectRoleType[];
};
// 每一个Project的角色数据的初始化状态是否完成初始化。
// The initialization status of each Project's role data, and whether the initialization has been completed.
isReady: {
[projectId: string]: boolean;
};
}
interface SpaceAuthStoreAction {
// 设置projectId对应的Project的角色
// Set the role of the Project corresponding to the projectId
setRoles: (projectId: string, role: ProjectRoleType[]) => void;
// 设置projectId对应的Project的数据是否ready
// Set whether the data of the Project corresponding to the projectId is ready
setIsReady: (projectId: string, isReady: boolean) => void;
// 回收Project数据
// Recovering Project Data
destory: (projectId) => void;
}
/**
* ProjectAuthStore设计成支持多Project切换维护多个Project的数据防止因为Project切换时序导致的bug。
* ProjectAuthStore is designed to support multi-project switching, maintain data of multiple projects, and prevent bugs caused by project switching timing.
*/
export const useProjectAuthStore = create<
ProjectAuthStoreState & SpaceAuthStoreAction

View File

@@ -23,7 +23,7 @@ export function useDestoryProject(projectId: string) {
return useEffect(
() => () => {
// 空间组件销毁时清空对应space数据
// When the space component is destroyed, empty the corresponding space data
destorySpace(projectId);
},
[],

View File

@@ -26,7 +26,7 @@ export function useProjectAuth(
projectId: string,
spaceId: string,
) {
// 获取space类型信息
// Get space type information
const space = useSpace(spaceId);
if (!space?.space_type) {
@@ -35,13 +35,13 @@ export function useProjectAuth(
);
}
// 获取space role信息
// Get space role information
const spaceRoles = useSpaceRole(spaceId);
// 获取project role信息
// Get project role information
const projectRoles = useProjectRole(projectId);
// 计算权限点
// Calculate permission spot
return calcPermission(key, {
projectRoles,
spaceRoles,

View File

@@ -15,44 +15,44 @@
*/
/**
* 空间相关的权限点枚举
* Spatially dependent permission spot enumeration
*/
export enum ESpacePermisson {
/**
* 更新空间
* update space
*/
UpdateSpace,
/**
* 删除空间
* delete space
*/
DeleteSpace,
/**
* 添加成员
* Add member
*/
AddBotSpaceMember,
/**
* 移除空间成员
* Remove space member
*/
RemoveSpaceMember,
/**
* 退出空间
* exit space
*/
ExitSpace,
/**
* 转移owner权限
* Transfer owner permissions
*/
TransferSpace,
/**
* 更新成员
* update member
*/
UpdateSpaceMember,
/**
* 管理API-KEY
* Manage API-KEY
*/
API,
}
/**
* 空间角色枚举
* Spatial Role Enumeration
*/
export { SpaceRoleType } from '@coze-arch/idl/developer_api';

View File

@@ -19,26 +19,26 @@ import { create } from 'zustand';
import { type SpaceRoleType } from '@coze-arch/idl/developer_api';
interface SpaceAuthStoreState {
// 每一个空间的角色数据
// Role data for each space
roles: {
[spaceId: string]: SpaceRoleType[];
};
// 每一个空间的角色数据的初始化状态,是否完成初始化。
// The initialization status of the character data in each space, and whether the initialization is completed.
isReady: {
[spaceId: string]: boolean;
};
}
interface SpaceAuthStoreAction {
// 设置spaceId对应的空间的角色
// Set the role of the space corresponding to the spaceId
setRoles: (spaceId: string, roles: SpaceRoleType[]) => void;
// 设置spaceId对应的空间的数据是否ready
// Set whether the data of the space corresponding to the spaceId is ready
setIsReady: (spaceId: string, isReady: boolean) => void;
// 回收空间数据
// Recovering spatial data
destory: (spaceId) => void;
}
/**
* SpaceAuthStore设计成支持多空间切换维护多个空间的数据位置因为空间切换时序导致的bug。
* SpaceAuthStore is designed to support multi-space switching and maintain data in multiple spaces. The location is due to bugs caused by the timing of space switching.
*/
export const useSpaceAuthStore = create<
SpaceAuthStoreState & SpaceAuthStoreAction

View File

@@ -23,7 +23,7 @@ export function useDestorySpace(spaceId: string) {
return useEffect(
() => () => {
// 空间组件销毁时清空对应space数据
// When the space component is destroyed, empty the corresponding space data
destorySpace(spaceId);
},
[],

View File

@@ -19,8 +19,8 @@ import { type ESpacePermisson } from './constants';
import { calcPermission } from './calc-permission';
export function useSpaceAuth(key: ESpacePermisson, spaceId: string) {
// 获取space role信息
// Get space role information
const role = useSpaceRole(spaceId);
// 计算权限点
// Calculate permission spot
return calcPermission(key, role);
}

View File

@@ -20,7 +20,7 @@ import { useSpace } from '@coze-arch/foundation-sdk';
import { useSpaceAuthStore } from './store';
export function useSpaceRole(spaceId: string) {
// 获取space信息,已有hook
// Get space information, there are hooks.
const space = useSpace(spaceId);
if (!space) {

View File

@@ -20,9 +20,9 @@ export default defineConfig({
dirname: __dirname,
preset: 'web',
test: {
// 全局测试超时时间(毫秒)
testTimeout: 10000, // 10
// Hook 超时时间(毫秒)
hookTimeout: 10000, // 10
// Global test timeout (milliseconds)
testTimeout: 10000, // 10 seconds
// Hook timeout in milliseconds
hookTimeout: 10000, // 10 seconds
},
});

View File

@@ -52,17 +52,17 @@ export default function Coachmark({
const initVisible = async (cid: string) => {
const coachMarkStorage =
await localStorageService.getValueSync(COACHMARK_KEY);
// readStep 代表已读的step index
// readStep represents the read step index
const readStep = (
typeSafeJSONParse(coachMarkStorage) as Record<string, number> | undefined
)?.[cid];
// 如果没有读过或者读过的step index 小于当前项的index则展示。
// Displays if it has not been read, or if the read step index is less than the index of the current item.
const shouldShow = readStep === undefined || itemIndex > readStep;
setVisible(shouldShow);
};
// 设置已读的step index
// Set the read step index
const setCoachmarkReadStep = useCallback(
(step: number) => {
const coachmarkStorage =
@@ -73,7 +73,7 @@ export default function Coachmark({
number | undefined
>;
// 如果没有读过或者要设置的index大于已读的step index 才设置,否则忽略。
// If it has not been read, or the index to be set is greater than the read step index, otherwise it is ignored.
if (
coachmarkValue[caseId] === undefined ||
step > Number(coachmarkValue[caseId])
@@ -99,7 +99,7 @@ export default function Coachmark({
)
) {
const nextIndex = index + (action === ACTIONS.PREV ? -1 : 1);
// 设置已经读过的step index
// Set the step index that has been read
setCoachmarkReadStep(index);
setStepIndex(nextIndex);
}
@@ -150,7 +150,7 @@ export default function Coachmark({
padding: 0,
},
buttonBack: {
display: 'none', // 隐藏返回按钮
display: 'none', // Hide back button
},
}}
floaterProps={{

View File

@@ -1,4 +1,4 @@
/** 基本组件样式 */
/** basic component style */
.dot {
width: 4px;
height: 4px;
@@ -26,7 +26,7 @@
}
}
/** 业务组件样式 */
/** Business Component Style */
.half-top-root {
flex: 1;
position: relative;

View File

@@ -184,7 +184,7 @@ export default function LevelLine({
style,
multiInfo = { multiline: false },
}: LevelLineProps) {
// getLineShowResult 返回数据,暂时没涉及到 root 画线
// getLineShowResult returns data, no root drawing is involved for the time being
const lineShowResult = getLineShowResult({ level, data });
const showMap: Record<LineShowResult, React.ReactNode> = {
[LineShowResult.HalfTopRoot]: (
@@ -203,7 +203,7 @@ export default function LevelLine({
[LineShowResult.FullRootWithChildren]: (
<FullRootWithChildren className={className} style={style} />
),
// output tree 中,暂时没涉及到 root 画线
// In the output tree, there is no root drawing involved for the time being
[LineShowResult.HalfTopChild]: (
<HalfTopChild
className={classNames(

View File

@@ -56,7 +56,7 @@ export default function ParamDescription({
)}
value={data.description}
ellipsis={true}
// 好像不生效
// It doesn't seem to work.
disabled={disabled}
handleBlur={() => {
setInputFocus(false);

View File

@@ -48,7 +48,7 @@ export default function ParamOperator({
}: ParamOperatorProps) {
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
const isLimited = level >= 3;
// 是否展示新增子项的按钮
// Whether to display the button for adding a child item
const needRenderAppendChild =
ObjectLikeTypes.includes(data.type) && !isLimited;
const computedOperatorStyle = (): React.CSSProperties => {

View File

@@ -34,7 +34,7 @@ interface ParamTypeProps {
level: number;
onSelectChange?: SelectProps['onChange'];
disabled?: boolean;
// 不支持使用的类型
// Types not supported
disabledTypes?: ParamTypeAlias[];
}

View File

@@ -54,8 +54,8 @@ export type WorkflowSLTextAreaProps = ComponentProps<typeof TextArea> & {
};
/**
* @component TextArea Workflow 场景下的二次封装;
* focus(inputting) 的时候提供多行滚动输入能力blur 的时候提供 ellipsis tooltip 提示能力
* @Component TextArea secondary encapsulation in Workflow scenarios;
* When focusing (inputting), it provides multi-line scrolling input capability, and when blur, it provides ellipsis and tooltip prompt capability.
*/
export default function WorkflowSLTextArea(props: WorkflowSLTextAreaProps) {
const { ellipsis = true } = props;
@@ -84,7 +84,7 @@ export default function WorkflowSLTextArea(props: WorkflowSLTextAreaProps) {
$state.inputOnFocus = false;
props?.handleBlur?.($state.value || '');
props?.onBlur?.(e);
// 失焦的时候,滚动到最顶端
// When out of focus, scroll to the top
if (textAreaRef?.current) {
textAreaRef.current.scrollTop = 0;
}
@@ -95,7 +95,7 @@ export default function WorkflowSLTextArea(props: WorkflowSLTextAreaProps) {
props?.handleChange?.(v);
};
// 输入法输入结束
// Input method input end
const onCompositionEnd = (e: React.CompositionEvent<HTMLTextAreaElement>) => {
const target = e.target as HTMLTextAreaElement;
@@ -126,7 +126,7 @@ export default function WorkflowSLTextArea(props: WorkflowSLTextAreaProps) {
$state.value = props.value;
}, [props.value]);
/** 是否处于失焦缩略状态 */
/** Is it in an out-of-focus thumbnail state? */
const ellipsisWithBlur = useMemo(
() => !$state.inputOnFocus && hasEllipsis,
[hasEllipsis, $state.inputOnFocus],

View File

@@ -36,10 +36,10 @@ import styles from './index.module.less';
export interface CustomTreeNodeProps extends RenderFullLabelProps {
onChange: (mode: ChangeMode, param: TreeNodeCustomData) => void;
// Description 组件变换为多行时,其下面第一个 child 需被记录
// When the Description component is transformed into multiple rows, the first child below it needs to be recorded
onActiveMultiInfoChange?: (info: ActiveMultiInfo) => void;
activeMultiInfo?: ActiveMultiInfo;
// 不支持使用的类型
// Types not supported
disabledTypes?: ParamTypeAlias[];
}
@@ -59,7 +59,7 @@ export default function CustomTreeNode(props: CustomTreeNodeProps) {
} = props;
const { allowValueEmpty, readonly, hasObjectLike, withDescription } =
useConfig();
// 当前值
// current value
const value = data as TreeNodeCustomData;
const isTopLevel = level === 0;
const isOnlyOneData = value.isSingle && isTopLevel;
@@ -71,15 +71,15 @@ export default function CustomTreeNode(props: CustomTreeNodeProps) {
const disableDelete = Boolean(
!allowValueEmpty && isOnlyOneData && isTopLevel,
);
// 删除时
// When deleting
const onDelete = () => {
onChange(ChangeMode.Delete, value);
};
// 新增子项时
// When adding a child
const onAppend = () => {
onChange(ChangeMode.Append, value);
};
// 类型切换时
// When switching types
const onSelectChange = (
val?: string | number | Array<unknown> | Record<string, unknown>,
) => {
@@ -90,7 +90,7 @@ export default function CustomTreeNode(props: CustomTreeNodeProps) {
if (isNumber(val)) {
const isObjectLike = ObjectLikeTypes.includes(val);
if (!isObjectLike) {
// 如果不是类Object判断是否有children如果有删除掉
// If it is not a class Object, determine whether there are children. If so, delete it
if (value.children && value.children.length > 0) {
delete value.children;
}
@@ -99,14 +99,14 @@ export default function CustomTreeNode(props: CustomTreeNodeProps) {
onChange(ChangeMode.Update, { ...value, type: val });
}
// 更新type
// Update type
};
// 更新
// update
const onNameChange = (name: string) => {
onChange(ChangeMode.Update, { ...value, name });
};
// 更新
// update
const onDescriptionChange = useCallback(
(description: string) => {
onChange(ChangeMode.Update, { ...value, description });
@@ -115,7 +115,7 @@ export default function CustomTreeNode(props: CustomTreeNodeProps) {
);
/**
* Description 组件单行 / 多行变换时,其下面第一个 child 的竖线需要缩短 / 延长
* Description When the component converts single/multiple rows, the vertical line of the first child below it needs to be shortened/lengthened
*/
const onDescriptionLineChange = useCallback(
(type: DescriptionLine) => {
@@ -140,7 +140,7 @@ export default function CustomTreeNode(props: CustomTreeNodeProps) {
if (readonly) {
return (
// 提高class的css 权重
// Increase the CSS weight of the class
<div
className={classNames(
styles['readonly-icon-container'],
@@ -170,7 +170,7 @@ export default function CustomTreeNode(props: CustomTreeNodeProps) {
})}
ref={treeNodeRef}
>
{/* 每增加一级多15长度 */}
{/* 15 more lengths for each additional level */}
<div
style={{ width: IndentationWidth }}
className={styles['level-icon']}
@@ -196,7 +196,7 @@ export default function CustomTreeNode(props: CustomTreeNodeProps) {
level={level}
disabledTypes={disabledTypes}
/>
{/* LLM 节点输出才有 description */}
{/* The LLM node output has a description. */}
{withDescription ? (
<ParamDescription
data={value}

View File

@@ -29,22 +29,22 @@ export type TreeNodeCustomData = TreeNodeData &
| 'quotedValue'
| 'fieldRandomKey'
> & {
// 行唯一值
// row unique value
key: string;
// Formfield
// Formed fields
field?: string;
// 是否是第一项
// Is it the first item?
isFirst?: boolean;
// 是否是最后一项
// Is it the last item?
isLast?: boolean;
// 是否只有该项一条数据
// Is there only one item of data?
isSingle?: boolean;
// 该项的嵌套层级从0开始
// The nesting level of the item, starting at 0
level?: number;
// 辅助线展示的字段
// Fields displayed by the auxiliary line
helpLineShow?: Array<boolean>;
children?: Array<TreeNodeCustomData>;
// 变量描述,用于作为隐藏的引导
// Variable descriptions, used as hidden bootstraps
description?: string;
};
@@ -52,24 +52,24 @@ export interface CustomTreeNodeFuncRef {
data: TreeNodeCustomData;
level: number;
readonly: boolean;
// 通用change方法
// General change method
onChange: (mode: ChangeMode, param: TreeNodeCustomData) => void;
// 定制的类型改变的change方法主要用于自定义render使用
// 添加子项
// Customized type change method, mainly used for custom rendering
// Add child item
onAppend: () => void;
// 删除该项
// Delete this item
onDelete: () => void;
// 删除该项下面的所有子项
// Delete all children under this item
onDeleteChildren: () => void;
// 类型改变时内部的调用方法主要用于从类Object类型转为其他类型时需要删除所有子项
// The internal call method when the type changes, mainly used to delete all children when converting from the class Object type to other types
onSelectChange: (
val?: string | number | Array<unknown> | Record<string, unknown>,
) => void;
}
export interface ActiveMultiInfo {
// 当前行是否处于多行状态,多行状态竖线需要延长
// Whether the current line is in a multi-line state, and the vertical line in the multi-line state needs to be extended
activeMultiKey: string;
// 当前行paramName数据是否出现错误信息
// Is there an error message for the current row paramName data?
withNameError?: boolean;
}

View File

@@ -67,7 +67,7 @@ export default function Header() {
</span>
</div>
{/* description 目前只在 LLM output 中存在 */}
{/* Description currently only exists in LLM output */}
{withDescription ? (
<div className={styles.description}>
<span className={styles.text}>

View File

@@ -15,12 +15,12 @@
*/
/* eslint-disable @typescript-eslint/naming-convention */
// 类型选择控件基础宽度
// Type selection control base width
export const OperatorTypeBaseWidth = 155;
// 61 = 删除按钮 + 添加按钮 的上层容器宽度
// 61 = Remove button + Add button, upper container width
export const OperatorLargeSize = 61;
// 31 = 删除按钮 的上层容器宽度
// 31 = Remove button, upper container width
export const OperatorSmallSize = 31;
// 8 = 删除按钮与变量类型中间的 margin 距离
// 8 = Remove the margin distance between the button and the variable type
export const SpacingSize = 8;

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable @coze-arch/max-line-per-function */
import React, { type PropsWithChildren, useState } from 'react';
@@ -56,26 +56,26 @@ export function Parameters(props: PropsWithChildren<ParametersProps>) {
allowValueEmpty = true,
onChange,
} = props;
// 监听该值的变化
// Monitor for changes in this value
const isValueEmpty = !value || value.length === 0;
const { data: formattedTreeData, hasObjectLike } = formatTreeData(
cloneDeep(value) as TreeNodeCustomData[],
);
/**
* 表示当前哪一行的父亲节点的 description 处于多行状态(LLM节点)
* 用于渲染树形竖线,处于多行文本的下一行竖线应该延长
* param name 有错误信息,竖线从错误信息下方延展,长度有所变化
* The description of the parent node of which row is currently in a multi-row state (LLM node)
* For rendering tree vertical lines, the next vertical line in multiple lines of text should be extended
* If the param name has an error message, the vertical bar extends below the error message and the length changes
*/
const [activeMultiInfo, setActiveMultiInfo] = useState<ActiveMultiInfo>({
activeMultiKey: '',
});
// 该组件的 change 方法
// How to change this component
const onValueChange = (freshValue?: Array<TreeNodeCustomData>) => {
if (onChange) {
freshValue = (freshValue || []).concat([]);
// 清理掉无用字段
// Clean up useless fields
traverse<TreeNodeCustomData>(freshValue, node => {
const { key, name, type, description, children } = node;
// eslint-disable-next-line guard-for-in
@@ -95,9 +95,9 @@ export function Parameters(props: PropsWithChildren<ParametersProps>) {
}
};
// 树节点的 change 方法
// Tree node change method
const onTreeNodeChange = (mode: ChangeMode, param: TreeNodeCustomData) => {
// 先clone一份因为Tree内部会对treeData执行isEqual克隆一份一定是false
// Clone one first, because the Tree will execute isEqual on treeData, cloning one must be false.
const cloneDeepTreeData = cloneDeep(
formattedTreeData,
) as Array<TreeNodeCustomData>;
@@ -108,13 +108,13 @@ export function Parameters(props: PropsWithChildren<ParametersProps>) {
if (findResult) {
switch (mode) {
case ChangeMode.Append: {
// 新增不可以用 parentData 做标准,要在当前 data 下新增
// You can't use parentData as a standard for adding, you need to add it under the current data.
const { data } = findResult;
const currentChildren = data.children || [];
// @ts-expect-error 有些值不需要此时指定,因为在 rerender 的时候会执行 format
// @ts-expect-error Some values do not need to be specified at this time because format is executed during rerender
data.children = currentChildren.concat({
...getDefaultAppendValue(),
// 增加 field
// Add field
field: `${data.field}.children[${currentChildren.length}]`,
});
onValueChange(cloneDeepTreeData);

View File

@@ -19,10 +19,10 @@ export enum ParamTypeAlias {
Integer,
Boolean,
Number,
/** 理论上没有 List 了,此项仅作兼容 */
/** Theoretically there is no List, this item is only for compatibility */
List = 5,
Object = 6,
// 上面是 api 中定义的 InputType。下面是整合后的。从 99 开始,避免和后端定义撞车
// The above is the InputType defined in the api. The following is the integrated one. Start from 99 to avoid collisions with the backend definition.
ArrayString = 99,
ArrayInteger,
ArrayBoolean,
@@ -51,20 +51,20 @@ export enum ParamValueType {
export interface RecursedParamDefinition {
name?: string;
/** Tree 组件要求每一个节点都有 key而 key 不适合用名称(前后缀)等任何方式赋值,最终确定由接口转换层一次性提供随机 key */
/** The Tree component requires each node to have a key, and the key is not suitable for assignment in any way such as name (before and after). Finally, the interface conversion layer provides a random key at one time. */
fieldRandomKey?: string;
desc?: string;
required?: boolean;
type: ParamTypeAlias;
children?: RecursedParamDefinition[];
// region 参数值定义
// 输入参数的值可以来自上游变量引用,也可以是用户输入的定值(复杂类型则只允许引用)
// 如果是定值,传 fixedValue
// 如果是引用,传 quotedValue
// Region parameter value definition
// The value of the input parameter can come from an upstream variable reference, or it can be a fixed value entered by the user (for complex types, only references are allowed).
// If it is a fixed value, pass the fixedValue.
// If it is a reference, pass the quotedValue.
isQuote?: ParamValueType;
/** 参数定值 */
/** parameter setting */
fixedValue?: string;
/** 参数引用 */
/** parameter reference */
quotedValue?: [nodeId: string, ...path: string[]]; // string[]
// endregion
}
@@ -89,9 +89,9 @@ export interface ParametersProps {
className?: string;
style?: React.CSSProperties;
withDescription?: boolean;
// 不支持使用的类型
// Types not supported
disabledTypes?: ParamTypeAlias[];
errors?: ParametersError[];
// 支持空值 & 空数组
// Support null value & empty array
allowValueEmpty?: boolean;
}

View File

@@ -17,9 +17,9 @@
import { isFunction } from 'lodash-es';
/**
* 将 { value: label } 形式的结构体转成Select需要的options Array<{ label, value }>
* computedValue将value值转化一次作为options的value
* passItem判断当前value值是否需要跳过遍历
* Convert a structure of the form {value: label} into the options Array < {label, value} > required by Select
* computedValue: Convert the value value once as the value of options
* passItem: Determine whether the current value needs to skip the traversal
*/
export default function convertMaptoOptions<Value = number>(
map: Record<string, unknown>,
@@ -27,9 +27,9 @@ export default function convertMaptoOptions<Value = number>(
computedValue?: (val: unknown) => Value;
passItem?: (val: unknown) => boolean;
/**
* 由于 i18n 的实现方式问题,写成常量的文案需要惰性加载
* 因此涉及到 i18n 的 { value: label } 结构一律需要写成 { value: () => label }
* 该属性启用时,会额外进行一次惰性加载
* Due to the implementation of i18n, the copy written as a constant needs to be loaded lazily
* Therefore, the {value: label} structure involving i18n needs to be written as {value : () => label}
* When this property is enabled, an additional lazy load is performed
* @default false
* @link
*/

View File

@@ -32,7 +32,7 @@ interface ChildrenFindResult {
export type FindDataResult = RootFindResult | ChildrenFindResult | null;
/**
* 根据target数组找到key在该项的值和位置主要是获取位置方便操作parent的children
* According to the target array, find the value and position of the key in the item, mainly to obtain the position, which is convenient for operating the children of the parent.
*/
export function findCustomTreeNodeDataResult(
target: Array<TreeNodeCustomData>,
@@ -40,7 +40,7 @@ export function findCustomTreeNodeDataResult(
): FindDataResult {
const dataInRoot = target.find(item => item.field === findField);
if (dataInRoot) {
// 如果是根节点
// If it is the root node
return {
isRoot: true,
parentData: null,
@@ -87,7 +87,7 @@ export function formatTreeData(data: Array<TreeNodeCustomData>) {
function resolveActionParamList(
list: Array<TreeNodeCustomData>,
field: string,
// 主要是用来辅助展示线的判断的
// It is mainly used to assist the judgment of the display line.
{
parentData,
level,
@@ -99,16 +99,16 @@ export function formatTreeData(data: Array<TreeNodeCustomData>) {
list?.forEach((item, index) => {
const keyField = field ? `${field}.${index}` : `${index}`;
hasObjectLike = hasObjectLike || ObjectLikeTypes.includes(item.type);
// 赋值children
// Assignment children
item.key = item.key ?? item.fieldRandomKey ?? nanoid();
item.field = keyField;
item.isFirst = index === 0;
item.isLast = index === list.length - 1;
item.isSingle = item.isFirst && item.isLast;
item.level = level;
// 第一级不展示辅助线,需要判断level
// 也就是第二级level = 1只需要自身的层级线
// 在第三级level = 2之后需要辅助线展示上一级的辅助线
// The first level does not show the auxiliary line, you need to judge the level
// That is, the second level (level = 1) only needs its own level line
// After the third level (level = 2), the guide line is required to show the guide line of the previous level
item.helpLineShow =
parentData && level >= MAX_LINE_LEVEL
? (parentData.helpLineShow || []).concat(!parentData.isLast)
@@ -165,7 +165,7 @@ export function getLineShowResult({
item ? LineShowResult.HelpLineBlock : LineShowResult.EmptyBlock,
) || [];
const isRoot = isRootWithoutChildren || isRootWithChildren;
// 根节点不需要展示线,只有非根节点才需要辅助线
// The root node does not need a display line, only non-root nodes need auxiliary lines.
if (!isRoot) {
if (isChildWithChildren) {
if (data.isLast) {

View File

@@ -55,7 +55,7 @@ interface AutoGenerateProps {
};
showAiAvatar: boolean;
/**
* 最多允许多少个候选
* How many candidates are allowed at most?
* @default 5
*/
maxCandidateCount?: number;
@@ -66,7 +66,7 @@ interface PictureItem {
uid: string;
}
// 自动生成头像错误码
// Automatically generate avatar error codes
enum ErrorCode {
OVER_QUOTA_PER_DAY = 700012034,
CONTENT_NOT_LEGAL = 700012050,
@@ -177,7 +177,7 @@ export const AutoGenerate = (props: AutoGenerateProps) => {
});
const codeNumber = Number((error as { code: number })?.code);
if (codeNumber === ErrorCode.OVER_QUOTA_PER_DAY) {
// 超过单日次数上限
// Exceeding the maximum number of times a day
setTotalCount(MAX_TOTAL_COUNT);
Toast.error({
content: I18n.t('bot_edit_profile_pircture_autogen_quota_tooltip'),
@@ -213,7 +213,7 @@ export const AutoGenerate = (props: AutoGenerateProps) => {
};
useEffect(() => {
// 获取当日总生成次数
// Get the total number of spawns for the day
DeveloperApi.GetGenerateIconInfo()
.then(({ data }) => {
setTotalCount(Number(data?.current_day_count));
@@ -274,7 +274,7 @@ export const AutoGenerate = (props: AutoGenerateProps) => {
[s['loading-hover']]: loadingHover && !picture.url,
})}
>
{/* 二次hover展示取消 */}
{/* Secondary hover display cancelled */}
{hoverCount.current > 1 && loadingHover && !picture.url ? (
<div
className={s.mask}
@@ -287,7 +287,7 @@ export const AutoGenerate = (props: AutoGenerateProps) => {
</div>
) : null}
{/* 选中图片蒙版 */}
{/* Select image mask */}
{checkedId === idx && (
<div className={s.mask}>
<IconCozCheckMark className="text-[16]" />

View File

@@ -65,12 +65,12 @@ interface PackageUploadProps {
triggerClassName?: string;
maskIcon?: React.ReactNode;
/**
* 编辑遮罩的展示模式
* - full-center(默认): 整体覆盖黑色透明遮罩, Icon 居中展示. hover 展示
* - right-bottom: 右下角遮罩, 长期展示
* Edit the display mode of the mask
* - full-center (default): overall cover black transparent mask, Icon centered show.hover display
* - right-bottom: lower right masking, long display
*/
maskMode?: 'full-center' | 'right-bottom';
/** 编辑遮罩的 className */
/** Edit the className of the mask */
editMaskClassName?: string;
/** max size */
maxSize?: number;
@@ -81,7 +81,7 @@ interface PackageUploadProps {
contentNotLegalText?: string;
};
/**
* 自动生成的最大候选数量
* Maximum number of candidates automatically generated
* @default 5
*/
maxCandidateCount?: number;
@@ -91,14 +91,14 @@ interface PackageUploadProps {
onGenerateStaticImageClick?: React.MouseEventHandler<HTMLButtonElement>;
onGenerateGifClick?: React.MouseEventHandler<HTMLButtonElement>;
onSizeError?: () => void;
// 自定义自定生成图片逻辑
// Custom custom generated image logic
renderAutoGenerate?: (params: RenderAutoGenerateParams) => React.ReactNode;
testId?: string;
}
// eslint-disable-next-line @coze-arch/max-line-per-function
const _PictureUpload = (props: PackageUploadProps) => {
// 业务
// business
const {
onChange,
value,
@@ -225,7 +225,7 @@ const _PictureUpload = (props: PackageUploadProps) => {
return;
}
Toast.error({
// starling 切换
// Starling toggle
content: I18n.t(
'dataset_upload_image_warning',
{},

View File

@@ -54,11 +54,11 @@ function customUploadRequest(
try {
const { fileInstance } = file;
// 业务
// business
if (fileInstance) {
const extension = getFileExtension(file.name);
// 业务
// business
(async () => {
try {
const base64 = await getBase64(fileInstance);
@@ -72,7 +72,7 @@ function customUploadRequest(
onSuccess?.(result.data);
afterUploadCustom?.();
} catch (error) {
// 如参数校验失败情况会走到catch
// If parameter validation fails, it will go to catch.
afterUploadCustom?.();
}
})();

View File

@@ -64,10 +64,10 @@ export const IntelligenceList: React.FC<IntelligenceListProps> = ({
return (
<div className="relative h-full">
{/* 上遮罩 */}
{/* upper mask */}
<div className="sticky top-0 left-0 right-0 h-[20px] bg-gradient-to-b from-[rgba(255,255,255,1)] to-transparent pointer-events-none z-10" />
{/* 列表内容 */}
{/* list content */}
<div className="styled-scrollbar">
{data.list.map(intelligence => (
<IntelligenceItem
@@ -90,7 +90,7 @@ export const IntelligenceList: React.FC<IntelligenceListProps> = ({
) : null}
</div>
{/* 下遮罩 */}
{/* lower mask */}
<div className="sticky bottom-0 left-0 right-0 h-[20px] bg-gradient-to-t from-[rgba(255,255,255,1)] to-transparent pointer-events-none z-10" />
</div>
);

View File

@@ -50,7 +50,7 @@ export const intelligenceSearchService = {
},
});
const intelligenceList = resp?.data?.intelligences ?? [];
// 只保留single mode bot
// Keep only single mode bots
const singleModeBotList = intelligenceList.filter(
intelligence => intelligence.other_info?.bot_mode === BotMode.SingleMode,
);

View File

@@ -68,7 +68,7 @@ export const UpdateUserAvatar = forwardRef(
const { fileInstance } = file;
if (fileInstance) {
// 业务
// business
const resp = await uploadAvatar(fileInstance);
onChange?.(resp.web_uri);
onUpdateSuccess?.(resp.web_uri);

View File

@@ -23,7 +23,7 @@ interface SimpleParamTypeAlias {
children?: SimpleParamTypeAlias[];
}
// 使用 zod 创建校验规则,只校验 name description
// Use zod to create validation rules, only name and description are validated
const createParameterValueSchema = (): ZodSchema<SimpleParamTypeAlias> =>
z.lazy(() =>
z.object({
@@ -34,7 +34,7 @@ const createParameterValueSchema = (): ZodSchema<SimpleParamTypeAlias> =>
const parametersValueSchema = z.array(createParameterValueSchema());
// 定义 validValue 函数,使用 zod 进行校验
// Define the validValue function and use zod for verification
export default function validValue(
values: ParameterValue[],
): ParametersError[] | undefined {

View File

@@ -20,7 +20,7 @@ import classNames from 'classnames';
import s from './index.module.less';
// TODO 后续迭代扩展时props可细化
// Props can be refined when subsequent iterations of TODO are expanded
interface ActionBarHoverContainerProps {
style?: React.CSSProperties;
}

View File

@@ -47,16 +47,16 @@ export const CopyTextMessage: React.FC<
const [isCopySuccessful, setIsCopySuccessful] = useState<boolean>(false);
const trigger = useTooltipTrigger('hover');
// 单位s
// Unit s
const COUNT_DOWN_TIME = 3;
// 单位s转化为ms的倍数
// The unit's is converted to a multiple of ms
const TIMES = 1000;
const handleCopy = () => {
const resp = copy(content);
if (resp) {
// 复制成功
// Copy successful
setIsCopySuccessful(true);
setTimeout(() => setIsCopySuccessful(false), COUNT_DOWN_TIME * TIMES);
Toast.success({
@@ -68,7 +68,7 @@ export const CopyTextMessage: React.FC<
eventName: ReportEventNames.CopyTextMessage,
});
} else {
// 复制失败
// Copy failed
Toast.warning({
content: I18n.t('copy_failed'),
showClose: false,

View File

@@ -58,7 +58,7 @@ export const DeleteMessage: React.FC<PropsWithChildren<DeleteMessageProps>> = ({
/>
}
onClick={() => {
// 通过 groupId 索引即可
// Just index through groupId.
deleteMessageGroup(groupId);
}}
color="secondary"

View File

@@ -57,7 +57,7 @@ export interface FrownUponUIProps extends FrownUponProps {
isMobile: boolean;
}
// 点踩按钮
// Click the button
export const FrownUpon: React.FC<PropsWithChildren<FrownUponProps>> = ({
onClick,
isFrownUponPanelVisible,
@@ -81,7 +81,7 @@ export const FrownUpon: React.FC<PropsWithChildren<FrownUponProps>> = ({
: [MessageFeedbackDetailType.UnlikeDefault],
},
}).then(() => {
// 接口调用后再切换展示状态
// Switch the display state after the interface is called.
onClick?.();
});
};
@@ -108,7 +108,7 @@ export const FrownUponUI: React.FC<FrownUponUIProps> = ({
}) => {
const toolTipWrapperRef = useRef<HTMLDivElement>(null);
const isHovering = useHover(toolTipWrapperRef);
// 解决点踩填写原因面板展开收起过程中点踩按钮的tooltip展示错乱问题
// Solve the problem that the tooltip display of the click button is disordered during the process of clicking and filling in the reason panel.
useDispatchMouseLeave(toolTipWrapperRef, isFrownUponPanelVisible);
return (
<div style={{ position: 'relative' }} ref={toolTipWrapperRef}>
@@ -172,7 +172,7 @@ export interface FrownUponPanelUIProps {
wrapReasons: boolean | undefined;
style?: CSSProperties;
}
// 点踩填写原因面板
// Click to fill in the reason panel
export const FrownUponPanel: React.FC<
PropsWithChildren<FrownUponPanelProps>
> = ({ containerStyle, onCancel, onSubmit, wrapReasons }) => {
@@ -191,7 +191,7 @@ export const FrownUponPanel: React.FC<
: undefined,
},
}).then(() => {
// 接口调用后再切换展示状态
// Switch the display state after the interface is called.
onSubmit?.();
});
};

View File

@@ -43,7 +43,7 @@ export const MoreOperations: React.FC<MoreOperationsProps> = ({
disabled={isDeleteMessageLock}
icon={<IconCozTrashCan className="coz-fg-hglt-red" />}
onClick={() => {
// 通过 groupId 索引即可
// Just index through groupId.
deleteMessageGroup(groupId);
}}
type="danger"

View File

@@ -61,13 +61,13 @@ export const QuoteMessage: React.FC<
};
/**
* 哥哥们改动这里要小心一点喔,QuoteMessageImpl的前置依赖项是 message-grab
* Brothers, be careful with changes here. The pre-dependency of QuoteMessageImpl is message-grab.
*/
export const QuoteMessageImpl: React.FC<
PropsWithChildren<QuoteMessageProps>
> = ({ className, ...props }) => {
// INFO: 这里使用 as 是因为明确的知道 父组件提前尝试取 plugin 并且提前拦截的情况
// 后续如果有改动,请务必注意这里
// INFO: As is used here because it is clear that the parent component tries to fetch the plugin in advance and intercepts it in advance
// If there are any changes in the future, please be sure to pay attention here.
const plugin = useQuotePlugin() as WriteableChatAreaPlugin<
GrabPluginBizContext,
unknown

View File

@@ -65,7 +65,7 @@ export const ThumbsUp: React.FC<ThumbsUpProps> = ({
: MessageFeedbackType.Like,
},
}).then(() => {
// 接口调用后再切换展示状态
// Switch the display state after the interface is called.
onClick?.();
});
};

View File

@@ -17,10 +17,10 @@
import { type RefObject, useEffect } from 'react';
/**
* 点击赞、踩按钮,可以关闭打开原因填写面板
* 填写面板关闭的时候, 会造成一次 Reflow。此时赞、踩按钮的位置会发生变化 鼠标已经不在按钮上,但是对应按钮元素不会处罚 mouseleave 事件
* 由于不触发 mouseleave 造成按钮上的 tooltip 不消失、错位等问题
* 所以需要在面板 visible 变化时 patch 一个 mouseleave 事件
* Click the like and step on the button to close the reason for opening and fill in the panel.
* When the fill panel is closed, a Reflow will be caused. At this time, the position of the like and step buttons will change, and the mouse is no longer on the button, but the corresponding button element will not penalize the mouseleave event.
* Because the mouseleave is not triggered, the tooltip on the button does not disappear, misplaced, etc.
* So you need to patch a mouseleave event when the panel changes visible
*/
export const useDispatchMouseLeave = (
ref: RefObject<HTMLDivElement>,

View File

@@ -30,7 +30,7 @@ import { ReportEventNames } from '../report-events';
import { useReportMessageFeedbackFn } from '../context/report-message-feedback';
/**
* @description 消息点赞/点踩
* @description Message Like/Click
*/
export const useReportMessageFeedback = () => {
const { reporter } = useChatArea();
@@ -64,19 +64,19 @@ export const useReportMessageFeedback = () => {
};
/**
* @description 获取 点赞按钮组件/点踩按钮组件/点踩原因填写面板组件 props
* @description Get, like button component/click button component/click button reason Fill in the panel component props
*/
export const useReportMessageFeedbackHelpers = () => {
// 点赞成功标识
// Like success logo
const [isThumbsUpSuccessful, { toggle: toogleIsThumbsUpSuccessful }] =
useToggle<boolean>(false);
// 点踩成功标识
// Click on the success sign
const [isFrownUponSuccessful, { toggle: toogleIsFrownUponSuccessful }] =
useToggle<boolean>(false);
// 点踩原因填写面板展示
// Click on the reason to fill in the panel display
const [
isFrownUponPanelVisible,
{
@@ -85,20 +85,20 @@ export const useReportMessageFeedbackHelpers = () => {
},
] = useToggle<boolean>(false);
// 点赞按钮组件onClick事件
// Like button component onClick event
const thumbsUpOnClick = () => {
toogleIsThumbsUpSuccessful();
// 点赞/点踩互斥
// Like/click on mutual exclusion
if (!isThumbsUpSuccessful && isFrownUponSuccessful) {
toogleIsFrownUponSuccessful();
setIsFrownUponPanelVisibleFalse();
}
};
// 点踩按钮组件onClick事件
// Click button component onClick event
const frownUponOnClick = () => {
toogleIsFrownUponSuccessful();
// 点赞/点踩互斥
// Like/click on mutual exclusion
if (!isFrownUponSuccessful && isThumbsUpSuccessful) {
toogleIsThumbsUpSuccessful();
}
@@ -110,15 +110,15 @@ export const useReportMessageFeedbackHelpers = () => {
}
};
// 点踩原因填写面板组件onCancel事件
// Click on the reason to fill in the panel component onCancel event
const frownUponPanelonCancel = () => {
setIsFrownUponPanelVisibleFalse();
};
// 点踩原因填写面板组件onSubmit事件
// Click on the reason to fill in the panel component onSubmit event
const frownUponPanelonSubmit = () => {
setIsFrownUponPanelVisibleFalse();
// 点赞/点踩互斥
// Like/click on mutual exclusion
if (isThumbsUpSuccessful) {
toogleIsThumbsUpSuccessful();
}

View File

@@ -15,10 +15,10 @@
*/
export enum ReportEventNames {
/** 原名: chat_area_tts_voice_ws */
/** Original name: chat_area_tts_voice_ws */
TtsVoiceWs = 'chat_answer_action_start_TTS',
/** 原名: chat_area_report_message */
/** Original name: chat_area_report_message */
ReportMessage = 'chat_answer_action_message_feedback',
/** 原名: chat_area_copy_text_message */
/** Original name: chat_area_copy_text_message */
CopyTextMessage = 'chat_answer_action_copy_text_message',
}

View File

@@ -30,13 +30,13 @@ export const getShowFeedback = ({
>;
latestSectionId: string;
}): boolean => {
// 是否是推送的消息
// Is it a pushed message?
const isPushedMessage = getIsPushedMessage(message);
if (isPushedMessage) {
return false;
}
// 来自最后一个消息组的 final answer
// The final answer from the last message group
return (
meta.isGroupLastAnswerMessage &&
meta.isFromLatestGroup &&

View File

@@ -28,12 +28,12 @@ export const getShowRegenerate = ({
meta: Pick<MessageMeta, 'isFromLatestGroup' | 'sectionId'>;
latestSectionId: string;
}): boolean => {
// 是否是推送的消息
// Is it a pushed message?
const isPushedMessage = getIsPushedMessage(message);
if (isPushedMessage) {
return false;
}
// 来自最后一个消息组
// From the last message group
return getIsLastGroup({ meta, latestSectionId });
};

View File

@@ -39,7 +39,7 @@ export const BizMessageInnerAddonBottom: FC<IProps> = memo(
return () => {
ref.current = p.message.reasoning_content;
};
// content 用来触发 reasoning rerender
// Content used to trigger reasoning rerender
}, [p.message.reasoning_content, p.message.content]);
return p.message.role === 'assistant' && p.message.reasoning_content ? (

View File

@@ -19,16 +19,16 @@ import { type PluginRegistryEntry } from '@coze-common/chat-area';
import { type PluginBizContext } from './types/biz-context';
import { BizPlugin } from './plugin';
// eslint-disable-next-line @typescript-eslint/naming-convention -- 插件命名大写开头符合预期
// eslint-disable-next-line @typescript-eslint/naming-convention -- Plugin names start with uppercase as expected
export const ReasoningPluginRegistry: PluginRegistryEntry<PluginBizContext> = {
/**
* 贯穿插件生命周期、组件的上下文
* Context of components throughout the plug-in lifecycle
*/
createPluginBizContext() {
return {};
},
/**
* 插件本体
* plug-in ontology
*/
Plugin: BizPlugin,
};

View File

@@ -28,19 +28,19 @@ import { BizMessageInnerAddonBottom } from './custom-components/message-inner-ad
export class BizPlugin extends ReadonlyChatAreaPlugin<PluginBizContext> {
/**
* 插件类型
* PluginMode.Readonly = 只读模式
* PluginMode.Writeable = 可写模式
* plugin type
* PluginMode. Readonly = read-only mode
* PluginMode. Writeable = Writable Mode
*/
public pluginMode = PluginMode.Readonly;
/**
* 插件名称
* 请点 PluginName 里面去定义
* plugin name
* Please click PluginName to define it.
*/
public pluginName = PluginName.Demo;
/**
* 生命周期服务
* lifecycle services
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public lifeCycleServices: any = createReadonlyLifeCycleServices(
@@ -49,7 +49,7 @@ export class BizPlugin extends ReadonlyChatAreaPlugin<PluginBizContext> {
);
/**
* 自定义组件
* custom component
*/
public customComponents = createCustomComponents({
TextMessageInnerTopSlot: BizMessageInnerAddonBottom,

View File

@@ -161,8 +161,8 @@ const testUserMessage: TextMessage = {
plugin: '',
execute_display_name: '',
},
/** 正常、打断状态 拉消息列表时使用chat运行时没有这个字段 */
/** 打断位置 */
/** Normal, interrupted state, used when pulling the message list, this field is not available when chat is running. */
/** interrupt position */
broken_pos: 9999999,
sender_id: '',
mention_list: [],
@@ -225,8 +225,8 @@ const randomTestMessageList: Message<ContentType>[] = Mock.mock({
tool_name: '@string',
plugin: '@string',
},
/** 正常、打断状态 拉消息列表时使用chat运行时没有这个字段 */
/** 打断位置 */
/** Normal, interrupted state, used when pulling the message list, this field is not available when chat is running. */
/** interrupt position */
broken_pos: 9999999,
sender_id: '77777',
mention_list: [],

View File

@@ -42,7 +42,7 @@ vi.mock('@coze-common/chat-core', () => ({
},
}));
// 固定一下参数
// Fix the parameters
vi.mock('../../../src/constants/message', () => ({
MIN_MESSAGE_INDEX_DIFF_TO_ABORT_CURRENT: 10,
}));

View File

@@ -19,21 +19,21 @@ import { findRespondRecord, type Responding } from '../../src/store/waiting';
vi.mock('@coze-common/chat-core', () => ({
ContentType: vi.fn(),
VerboseMsgType: {
/** 跳转节点 */
/** jump node */
JUMP_TO: 'multi_agents_jump_to_agent',
/** 回溯节点 */
/** backtracking node */
BACK_WORD: 'multi_agents_backwards',
/** 长期记忆节点 */
/** long-term memory node */
LONG_TERM_MEMORY: 'time_capsule_recall',
/** finish answer*/
GENERATE_ANSWER_FINISH: 'generate_answer_finish',
/** 流式插件调用状态 */
/** Streaming plugin call status */
STREAM_PLUGIN_FINISH: 'stream_plugin_finish',
/** 知识库召回 */
/** knowledge base recall */
KNOWLEDGE_RECALL: 'knowledge_recall',
/** 中断消息:目前只用于地理位置授权 */
/** Interrupt message: Currently only used for geolocation authorization */
INTERRUPT: 'interrupt',
/** hooks调用 */
/** Hooks call */
HOOK_CALL: 'hook_call',
},
Scene: {

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { proxyFreeze } from '../../src/utils/proxy-freeze';
vi.stubGlobal('IS_DEV_MODE', true);
@@ -21,7 +21,7 @@ vi.stubGlobal('IS_DEV_MODE', true);
describe('proxyFreeze', () => {
it('should return the same object for non-objects', () => {
const nonObject = 42;
// @ts-expect-error -- 测试使用
// @ts-expect-error -- test use
expect(proxyFreeze(nonObject)).toBe(nonObject);
});

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ContentType, type Message } from '@coze-common/chat-core';
import {
@@ -28,21 +28,21 @@ import {
vi.mock('@coze-common/chat-core', () => ({
ContentType: vi.fn(),
VerboseMsgType: {
/** 跳转节点 */
/** jump node */
JUMP_TO: 'multi_agents_jump_to_agent',
/** 回溯节点 */
/** backtracking node */
BACK_WORD: 'multi_agents_backwards',
/** 长期记忆节点 */
/** long-term memory node */
LONG_TERM_MEMORY: 'time_capsule_recall',
/** finish answer*/
GENERATE_ANSWER_FINISH: 'generate_answer_finish',
/** 流式插件调用状态 */
/** Streaming plugin call status */
STREAM_PLUGIN_FINISH: 'stream_plugin_finish',
/** 知识库召回 */
/** knowledge base recall */
KNOWLEDGE_RECALL: 'knowledge_recall',
/** 中断消息:目前只用于地理位置授权 */
/** Interrupt message: Currently only used for geolocation authorization */
INTERRUPT: 'interrupt',
/** hooks调用 */
/** Hooks call */
HOOK_CALL: 'hook_call',
},
Scene: {
@@ -159,7 +159,7 @@ describe('normal text message', () => {
startSending(sentMessage);
// 检测 Sending 是否存在
// Check if Sending exists
const { sending } = useWaitingStore.getState();
@@ -171,11 +171,11 @@ describe('normal text message', () => {
startSending(sentMessage);
// 检测 Sending 是否存在
// Check if Sending exists
const { sending } = useWaitingStore.getState();
expect(sending).toStrictEqual(sentMessage);
// 清除 Sending
// Clear Sending
clearSending();
const { sending: afterClearSending } = useWaitingStore.getState();
@@ -198,7 +198,7 @@ describe('normal text message', () => {
});
it('update responding is correct', () => {
// 检测 responding 是否存在
// Detection of responding presence
const { updateResponding } = useWaitingStore.getState();
updateResponding(llmMessage);
@@ -225,7 +225,7 @@ describe('normal text message', () => {
is_finish: true,
};
// @ts-expect-error -- 单测
// @ts-expect-error -- single test
updateResponding(allFinishedMessage);
const { responding } = useWaitingStore.getState();
@@ -241,7 +241,7 @@ describe('normal text message', () => {
type: 'tool_response',
};
// @ts-expect-error -- 单测
// @ts-expect-error -- single test
updateResponding(toolResponseMessage);
const { responding } = useWaitingStore.getState();
@@ -254,7 +254,7 @@ describe('normal text message', () => {
updateResponding(llmMessage);
// 修改 reply_id 造成冲突假象
// Modifying reply_id creates the illusion of conflict
const modifiedMessage = {
...llmMessage,
reply_id: '嘤嘤嘤',
@@ -281,7 +281,7 @@ describe('normal text message', () => {
}),
};
// @ts-expect-error -- 测试
// @ts-expect-error -- test
updateResponding(verboseMessage);
const finishedMessage = {
@@ -292,7 +292,7 @@ describe('normal text message', () => {
}),
};
// @ts-expect-error -- 测试
// @ts-expect-error -- test
updateResponding(finishedMessage);
@@ -329,7 +329,7 @@ describe('normal text message', () => {
type: 'function_call',
};
// @ts-expect-error -- 测试
// @ts-expect-error -- test
updateResponding(functionCallMessage);
const respondingMessage = {
@@ -338,7 +338,7 @@ describe('normal text message', () => {
index: 2,
};
// @ts-expect-error -- 测试
// @ts-expect-error -- test
updateResponding(respondingMessage);
const { responding } = useWaitingStore.getState();
@@ -410,7 +410,7 @@ describe('normal text message', () => {
type: 'function_call',
};
// @ts-expect-error -- 测试
// @ts-expect-error -- test
updateResponding(functionCallMessage);
const respondingMessage = {
@@ -419,14 +419,14 @@ describe('normal text message', () => {
index: -1,
};
// @ts-expect-error -- 测试
// @ts-expect-error -- test
updateResponding(respondingMessage);
const { responding } = useWaitingStore.getState();
expect(responding).toStrictEqual({
replyId: llmMessage.reply_id,
// @ts-expect-error -- 测试
// @ts-expect-error -- test
response: [getResponse(functionCallMessage)],
});
});
@@ -439,7 +439,7 @@ describe('normal text message', () => {
type: 'function_call',
};
// @ts-expect-error -- 测试
// @ts-expect-error -- test
updateResponding(functionCallMessage);
const respondingMessage = {
@@ -448,14 +448,14 @@ describe('normal text message', () => {
index: 'hhhh',
};
// @ts-expect-error -- 测试
// @ts-expect-error -- test
updateResponding(respondingMessage);
const { responding } = useWaitingStore.getState();
expect(responding).toStrictEqual({
replyId: llmMessage.reply_id,
// @ts-expect-error -- 测试
// @ts-expect-error -- test
response: [getResponse(functionCallMessage)],
});
});
@@ -468,7 +468,7 @@ describe('normal text message', () => {
type: 'function_call',
};
// @ts-expect-error -- 测试
// @ts-expect-error -- test
updateResponding(functionCallMessage);
const functionCallMessage2 = {
@@ -481,7 +481,7 @@ describe('normal text message', () => {
type: 'function_call',
};
// @ts-expect-error -- 测试
// @ts-expect-error -- test
updateResponding(functionCallMessage2);
const { responding } = useWaitingStore.getState();
@@ -489,9 +489,9 @@ describe('normal text message', () => {
expect(responding).toStrictEqual({
replyId: llmMessage.reply_id,
response: [
// @ts-expect-error -- 测试
// @ts-expect-error -- test
getResponse(functionCallMessage),
// @ts-expect-error -- 测试
// @ts-expect-error -- test
getResponse(functionCallMessage2),
],
});

View File

@@ -166,7 +166,7 @@ const ChatAreaMain: FC<ChatAreaMainProps> = ({
>
<div className={styles['header-node']}>{headerNode}</div>
{customMessageListFloatSlotList.map(
// eslint-disable-next-line @typescript-eslint/naming-convention -- 符合预期
// eslint-disable-next-line @typescript-eslint/naming-convention -- as expected
({ pluginName, Component }) => (
<PluginScopeContextProvider
pluginName={pluginName}
@@ -197,7 +197,7 @@ const ChatAreaMain: FC<ChatAreaMainProps> = ({
<DragUploadArea />
</div>
{customComponentList.map(
// eslint-disable-next-line @typescript-eslint/naming-convention -- 符合预期
// eslint-disable-next-line @typescript-eslint/naming-convention -- as expected
({ pluginName, Component }, index) => (
<PluginScopeContextProvider pluginName={pluginName}>
<Component key={`${index}ScrollViewBottom`} />

View File

@@ -101,7 +101,7 @@ type OverrideProps = Omit<
export interface ChatInputProps<T extends OverrideProps> {
/**
* 传递给 Component 的 props类型为 T
* Props passed to Component of type T.
*/
componentProps?: T;
getChatInputController?: (controller: {
@@ -204,7 +204,7 @@ export const ChatInput: <T extends OverrideProps>(
sendTextMessage(payload, 'inputAndSend');
};
// TODO: 再封装一个 hook @gaoyuanhan
// TODO: encapsulate another hook @gaoyuanhan
const handleSendMultimodalMessage = (inputPayload: SendMessagePayload) => {
const fileDataList = useBatchFileUploadStore.getState().getFileDataList();
const strategy = getSendMultimodalMessageStrategy(
@@ -351,7 +351,7 @@ export const ChatInput: <T extends OverrideProps>(
{!!InputAddonTop && <InputAddonTop />}
{enableMultimodalUpload ? <BatchUploadFileList /> : null}
{customInputAddonTopList.map(
/* eslint-disable-next-line @typescript-eslint/naming-convention -- 符合预期的命名 */
/* eslint-disable-next-line @typescript-eslint/naming-convention -- matches the expected naming */
({ pluginName, Component }, index) => (
<PluginScopeContextProvider
pluginName={pluginName}

View File

@@ -88,7 +88,7 @@ export const BuildInContentBox: FC<ContentBoxProps> = props => {
return (
<>
{
// eslint-disable-next-line @typescript-eslint/naming-convention -- 符合预期的命名
// eslint-disable-next-line @typescript-eslint/naming-convention -- matches the expected naming
customContentBoxList.map(({ pluginName, Component }) => (
<PluginScopeContextProvider pluginName={pluginName}>
<Component
@@ -126,7 +126,7 @@ export const BuildInContentBox: FC<ContentBoxProps> = props => {
multimodalTextContentAddonTop={
<>
{customTextMessageInnerTopSlotList.map(
// eslint-disable-next-line @typescript-eslint/naming-convention -- 符合预期的命名
// eslint-disable-next-line @typescript-eslint/naming-convention -- matches the expected naming
({ pluginName, Component }, index) => (
<PluginScopeContextProvider
pluginName={pluginName}

View File

@@ -50,7 +50,7 @@ export const ContextDivider = ({ text }: ContextDividerProps) => {
<div
className={classNames(
styles['divider-line'],
// ui 要求分割线颜色特别处理 不使用 token
// UI requires special handling of divider color, no token is used
styles['coz-divider-line-style'],
{
'!coz-bg-images-secondary': showBackground,

View File

@@ -131,7 +131,7 @@ const getHeaderConfig = (
} = headerProps;
if (isTopLevelOfTheNestedPanel) {
const topLevelConfig = getTopLevelOfTheNestedPanelHeaderConfig(headerProps);
// 没有匹配上就用对应对话最后一个 function call unit 来渲染
// If there is no match, use the last function call unit of the corresponding dialogue to render
if (topLevelConfig) {
return topLevelConfig;
}
@@ -148,7 +148,7 @@ const getHeaderConfig = (
}
const { apiResponse, llmOutput, isFinish } = messageUnit;
// 流式插件、异步插件比较特殊,只有收到结束消息才算结束
// Streaming plugins and asynchronous plugins are special, and the end is only counted when the end message is received.
const hasResponse = apiResponse && isFinish;
const functionCallIconAndName = getFunctionCallMessageIconAndNameOptimization(
{
@@ -159,9 +159,9 @@ const getHeaderConfig = (
},
);
// response为空
// Response is empty
if (!hasResponse) {
// 历史消息对话
// Chat history
if (!isMessageFromOngoingChat) {
return {
icon: functionCallIconAndName.icon,
@@ -169,7 +169,7 @@ const getHeaderConfig = (
status: 'interrupt',
};
}
// 当前进行中的对话
// Current conversation in progress
return {
icon: <IconCozLoading className="animate-spin" />,
title: functionCallIconAndName.title,
@@ -177,7 +177,7 @@ const getHeaderConfig = (
};
}
// 正常返回逻辑
// normal return logic
return {
icon: functionCallIconAndName.icon,
title: functionCallIconAndName.title,

View File

@@ -175,7 +175,7 @@ const getVerboseContent = (llmContent: string) => {
}
switch (msg_type) {
// 回溯节点文案
// backtracking node copy
case VerboseMsgType.BACK_WORD: {
const startMode = I18n.t(
'agentflow_transfer_ conversation_settings_backtrack_start',
@@ -187,24 +187,24 @@ const getVerboseContent = (llmContent: string) => {
contentData?.restart ? startMode : previousMode
}`;
}
// 跳转节点文案
// jump node copy
case VerboseMsgType.JUMP_TO: {
return `${I18n.t('agentflow_jump_running_process_trigger_condition')}${
contentData?.condition ?? ''
}`;
}
// 长期记忆节点文案
// long-term memory node copy
case VerboseMsgType.LONG_TERM_MEMORY: {
return contentData?.wraped_text ?? '';
}
//默认直接展示json
//Default direct display json
default: {
return llmContent;
}
}
};
// hook_call类型
// hook_call type
const renderHooksMessage = (messageUnit: FunctionCallMessageUnit) => {
const reportError = (error: Error) => {
reporter.error({

View File

@@ -46,11 +46,11 @@ import { KNOWLEDGE_OPEN_SEARCH_ERROR } from './knowledge-recall';
import s from './index.module.less';
// 失败状态
// failure state
const FAILED = '1';
/**
* coze home插件展示,这里保留为兜底逻辑
* Coze home plugin display, keep it as fallback logic here
*/
const specialPluginNameMap = {
'ts-bot_creator-bot_creator': {
@@ -94,7 +94,7 @@ export const HeaderTitleText: React.FC<
</>
);
// Bot调试区调用插件兜底逻辑(原逻辑)
// Bot debugging area calls plugin fallback logic (original logic)
const getFunctionCallMessageIconAndName: (props: {
content: string;
ext: MessageExt;
@@ -134,7 +134,7 @@ const getFunctionCallMessageIconAndName: (props: {
};
}
// coze home相关提示
// Coze home related tips
const specialPluginNameText = getPluginNameText(name);
const prefix = isLoading ? I18n.t('Using') : I18n.t('Used');
@@ -157,7 +157,7 @@ const getFunctionCallMessageIconAndName: (props: {
};
};
// Bot调试区调用插件提示优化
// Bot debugging area call plug-in prompt optimization
export const getFunctionCallMessageIconAndNameOptimization: (props: {
content: string;
ext: MessageExt;
@@ -172,7 +172,7 @@ export const getFunctionCallMessageIconAndNameOptimization: (props: {
ext?.execute_display_name || '',
null,
);
// 只有等于1的时候是失败0或者空字符串的时候都是成功
// It only fails when it is equal to 1, and succeeds when it is 0 or empty string.
const message = resExt
? resExt.plugin_status === FAILED
? executeDisplayName?.value?.name_execute_failed
@@ -180,8 +180,8 @@ export const getFunctionCallMessageIconAndNameOptimization: (props: {
: executeDisplayName?.value?.name_executing;
if (!message) {
// 兜底走原逻辑
// TODO: 兜底逻辑处理和@徐雯沟通完后续放到服务端处理 --@李慧文
// Go through the original logic
// TODO: fallback logic processing After communicating with @Xu Wen, it will be processed at the server level -- @Li Huiwen
return getFunctionCallMessageIconAndName({
content,
ext,
@@ -194,7 +194,7 @@ export const getFunctionCallMessageIconAndNameOptimization: (props: {
title: <HeaderTitleText>{message}</HeaderTitleText>,
};
} catch {
// 兜底走原逻辑
// Go through the original logic
return getFunctionCallMessageIconAndName({
content,
ext,
@@ -336,7 +336,7 @@ export const getVerboseMessageHeaderConfig = ({
const contentData = safeJSONParse(content.data);
if (isVerboseContentData(contentData)) {
/** 长期记忆 */
/** long-term memory */
if (content?.msg_type === VerboseMsgType.LONG_TERM_MEMORY) {
if (
isLatestFunctionCallOfRelatedChat &&
@@ -359,7 +359,7 @@ export const getVerboseMessageHeaderConfig = ({
};
}
/** 跳转、回溯,无loading */
/** Jump, backtrack, no loading */
return {
icon: <IconCozJump />,
title: (
@@ -374,7 +374,7 @@ export const getVerboseMessageHeaderConfig = ({
}
}
/** 兜底 */
/** bottom line */
return {
icon: <IconCozJump />,
title: '',

View File

@@ -103,7 +103,7 @@
width: 100%;
min-height: 32px;
/** 高度限制 */
/** height limit */
max-height: 272px;
padding: 6px 12px;

View File

@@ -102,7 +102,7 @@ export const FunctionCallMessagesCollapse: React.FC<
const content = safeJSONParse(cur.llmOutput.content);
if (isVerboseContent(content)) {
/** 跳转和长期记忆分别统计耗时 */
/** Time-consuming for jump and long-term memory statistics */
if (content.msg_type === VerboseMsgType.LONG_TERM_MEMORY) {
prev.longTerm += time;
} else {

View File

@@ -23,7 +23,7 @@ import { type KnowledgeRecallSlice } from '../../../../store/types';
const getRecallEmptyText = () => I18n.t('recall_knowledge_no_related_slices');
// 云搜索鉴权失败的错误代码
// BigInt with failed cloud search authentication
export const KNOWLEDGE_OPEN_SEARCH_ERROR = 708882003;
const getMessageWithStatusCode = (statusCode?: number) => {

View File

@@ -81,10 +81,10 @@ const FunctionCallMessageBoxImpl: ComponentTypesMap['functionCallMessageBox'] =
return !getIsVisibleMessageMeta(meta, configs);
});
// footer位置的answer actions所需要props通过useMessageBoxContext透传
// Answer actions for footer location, required props pass through useMessageBoxContext
const ActionBarFooter = MessageBoxActionBarFooter;
// hover才展示的answer actions所需要props通过useMessageBoxContext透传
// Hover just show the answer actions, the required props pass through useMessageBoxContext
const ActionBarHoverContent = MessageBoxActionBarHoverContent;
if (!messageUnitList.length || isInvisible) {
@@ -133,7 +133,7 @@ export const FunctionCallMessageBox: React.FC<{
Boolean(state.responding?.replyId === messageGroup.groupId),
);
// 收到final answer
// Received final answer
const isRelatedChatComplete = useIsGroupAnswerFinish(messageGroup);
const isFakeInterruptAnswer = useIsGroupFakeInterruptAnswer(messageGroup);

View File

@@ -53,7 +53,7 @@ export const LoadMore = ({
const spinRef = useRef<HTMLSpanElement>(null);
const [inViewport] = useInViewport(() => spinRef.current);
// 防止连续触发两次请求loading 变化早于 IconSpin 组件显隐变化)
// Prevent two consecutive requests from being triggered (loading changes earlier than explicit changes in the IconSpin component)
const deferredLoading = useDeferredValue(loading);
useEffect(() => {

View File

@@ -45,7 +45,7 @@ export const Actions = ({ message }: { message: Message }) => {
},
},
];
// TODO: Trigger类型适配
// TODO: Trigger type adaptation
return (
<div className={styles.actions}>
{menuConfigs.map((prop, idx) => (

View File

@@ -42,9 +42,9 @@ import { RevealTrigger } from './reveal-trigger';
import styles from './index.module.less';
// TODO: 在这里区分 用户的消息和模型的消息组件
// TODO: Here, distinguish between the user's message and the model's message component
// eslint-disable-next-line @coze-arch/max-line-per-function -- TODO 后期拆分一下
// eslint-disable-next-line @coze-arch/max-line-per-function -- TODO will split it later.
export const MessageBox: React.FC = memo(() => {
const { configs, reporter } = useChatAreaContext();
@@ -113,10 +113,10 @@ export const MessageBox: React.FC = memo(() => {
const ReceiveMessageBox = receiveMessageBox ?? BuildInReceiveMessageBox;
const SendMessageBox = sendMessageBox ?? BuildInSendMessageBox;
// footer位置的answer actions所需要props通过useMessageBoxContext透传
// Answer actions for footer location, required props pass through useMessageBoxContext
const ActionBarFooter = MessageBoxActionBarFooter;
// hover才展示的answer actions所需要props通过useMessageBoxContext透传
// Hover just show the answer actions, the required props pass through useMessageBoxContext
const ActionBarHoverContent = MessageBoxActionBarHoverContent;
const MessageBoxUI = isSendMessage ? SendMessageBox : ReceiveMessageBox;
@@ -124,25 +124,25 @@ export const MessageBox: React.FC = memo(() => {
const { imageAutoSizeContainerWidth, enableImageAutoSize } =
useUIKitMessageImageAutoSizeConfig();
// 老机制的自定义ContentBox
// Custom ContentBox for the old mechanism
const UsedContentBox = ContentBox ?? BuildInContentBox;
// Render调用生命周期用于判断是否需要使用业务组件进行渲染 - 与插件化做结合
// The Render call life cycle is used to determine whether business components need to be used for rendering - in combination with plug-ins
const { MessageBox: DynamicCustomMessageBox } =
lifeCycleService.render.onMessageBoxRender({ ctx: { message, meta } }) ??
{};
const staticCustomMessageBoxConfig =
usePluginCustomComponents('MessageBox').at(0); // 谁先谁来 只选一个
usePluginCustomComponents('MessageBox').at(0); // Whoever comes first, choose only one.
// 插件化机制提供的自定义MessageBox
// Custom MessageBox provided by plug-in mechanism
const StaticCustomMessageBox = staticCustomMessageBoxConfig?.Component;
// 使用的自定义MessageBox (如果有,否则返回undefined
// The custom MessageBox used (if any, otherwise return undefined)
const UsedCustomMessageBox =
DynamicCustomMessageBox ?? StaticCustomMessageBox;
// 最终使用的MessageBox
// The final used MessageBox
const UsedMessageBox = UsedCustomMessageBox ?? MessageBoxUI;
const reportError = (error: unknown) => {
@@ -217,7 +217,7 @@ export const MessageBox: React.FC = memo(() => {
{message.content_type === ContentType.Text && (
<>
{customTextMessageInnerTopComponentList?.map(
// eslint-disable-next-line @typescript-eslint/naming-convention -- 命名符合预期
// eslint-disable-next-line @typescript-eslint/naming-convention -- naming as expected
({ pluginName, Component }, index) => (
<PluginScopeContextProvider
pluginName={pluginName}
@@ -229,7 +229,7 @@ export const MessageBox: React.FC = memo(() => {
)}
</>
)}
{/* 这里是内部实现机制不准备告诉外面所以只有不存在自定义组件的时候才渲染children */}
{/* This is the internal implementation mechanism, and we are not going to tell the outside, so we only render children when there is no custom component. */}
{UsedCustomMessageBox ? null : (
<UsedContentBox
isContentLoading={isContentLoading}
@@ -247,7 +247,7 @@ export const MessageBox: React.FC = memo(() => {
)}
<div className={styles['footer-slot-style']}>
{customMessageInnerBottomComponentList?.map(
// eslint-disable-next-line @typescript-eslint/naming-convention -- 符合预期的命名
// eslint-disable-next-line @typescript-eslint/naming-convention -- matches the expected naming
({ pluginName, Component }) => (
<PluginScopeContextProvider
pluginName={pluginName}

View File

@@ -63,7 +63,7 @@ export const MessageGroupList = forwardRef<
const messageGroupIdList = useMessagesStore(
useShallow(state =>
// TODO: 这里需要考虑怎么业务接入进来,暂时先按照调试区做吧
// TODO: You need to consider how to access the business here. For the time being, let's follow the debugging area.
state.messageGroupList.map(group => group.groupId),
),
);

View File

@@ -58,7 +58,7 @@ export const MessageGroupBody: ComponentTypesMap['messageGroupBody'] = memo(
);
})}
{Boolean(functionCallMessageIdList.length) && (
// 看起来 functioncall 消息的 answer action 挑战了 MessageBoxProvider 的设计
// It seems that the functioncall answer action challenges the design of the MessageBoxProvider
<MessageBoxProvider
groupId={groupId}
messageUniqKey={functionCallMessageIdList.at(0) ?? ''}
@@ -67,7 +67,7 @@ export const MessageGroupBody: ComponentTypesMap['messageGroupBody'] = memo(
isFirstUserOrFinalAnswerMessage={false}
isLastUserOrFinalAnswerMessage={false}
>
{/* function call运行过程 */}
{/* Function call */}
<FunctionCallMessageBox
messageGroup={messageGroup}
getBotInfo={getBotInfo}

View File

@@ -125,7 +125,7 @@ export const MessageGroupWrapper: React.FC<
return findMessageById(state.metaList, userMessageId);
}, isEqual);
// TODO: 目前服务端不支持打断本地消息。不能删除正在发送中的消息。需要标志这个状态
// TODO: Current server level does not support interrupting local messages. Sending messages cannot be deleted. This status needs to be flagged
const isSendingMessage = Boolean(userMessageMeta?.isSending);
const deleteMessageGroup = useDeleteMessageGroup();

View File

@@ -135,7 +135,7 @@ export const OnboardingMessage: FC<IProps> = props => {
<CustomeUIKitMessageBox
messageType="receive"
messageId={null}
// fixme 这里没有 sender id
// Fixme no sender id here.
senderInfo={{ url: avatar, nickname: name, id: '' }}
showUserInfo={true}
theme={getOnboardingMessageBoxTheme({

View File

@@ -36,7 +36,7 @@ const getMixContent = (list: MixMessageContent['item_list']) => {
if (item.type === ContentType.Image) {
return `[${I18n.t('editor_toolbar_image')}]`;
} else if (item.type === ContentType.File) {
// TODO: jq - 如果后期支持多个的时候 可能会有问题
// TODO: jq - If multiple are supported later, there may be problems.
return item?.file?.file_name ? `[${item?.file?.file_name}]` : '';
} else if (item.type === ContentType.Text) {
return item.text;
@@ -46,12 +46,12 @@ const getMixContent = (list: MixMessageContent['item_list']) => {
return info?.join(' ');
};
// 仅对 message_type === plugin_async 的 answer 进行使用引用样式
// Use reference styles only for answers message_type === plugin_async
export const PluginAsyncQuote: FC<PluginAsyncQuoteProps> = props => {
const { message } = props;
const replyMessage = message?.reply_message;
// 只有回复的消息是文本的才添加引用
// Add citations only if the reply message is text.
if (
!(
message?.source === messageSource.AsyncResult &&
@@ -62,7 +62,7 @@ export const PluginAsyncQuote: FC<PluginAsyncQuoteProps> = props => {
return null;
}
// 引用的原始消息是 图片 或者文件 采用固定形式
// The original message quoted is a picture, or file, in a fixed form
const isImage = replyMessage?.content_type === ContentType.Image;
const isFile = replyMessage?.content_type === ContentType.File;
const isMix = replyMessage?.content_type === ContentType.Mix;

View File

@@ -39,7 +39,7 @@ export const Preview: FC<{ layout?: Layout }> = ({ layout }) => {
};
return (
<ImagePreview
// image preview 的默认 z index 比 toast 要高,调小一些
// The default z index for image preview is higher than toast and smaller
zIndex={1009}
previewCls={layout === Layout.MOBILE ? s['image-preview-mobile'] : ''}
src={previewURL}

View File

@@ -49,12 +49,12 @@ export const SendStatus: FC<SendStatusProps> = props => {
return (
<div
className={classNames(styles['message-right'], {
// 适配移动端 解决移动端loading遮盖问题
// Adapt mobile end to solve the problem of mobile end loading and covering
[styles['message-right-mobile'] as string]: layout === Layout.MOBILE,
[styles['message-right-pc'] as string]: layout === Layout.PC,
})}
>
{/* 消息发送状态 */}
{/* message sending status */}
{meta.isSending ? (
<IconSpin className={classNames(styles['icon-sending'])} spin />
) : null}

View File

@@ -34,7 +34,7 @@ import s from './index.module.less';
export const SuggestionInChat = () => {
const { useMessagesStore, useSuggestionsStore } = useChatAreaStoreSet();
const { enableMention } = usePreference();
// fixme 之前直接取最后一条消息进行处理不可靠,修改后仍存在问题,考虑 suggestion 存储时存入 sender_id
// Before fixme, it is unreliable to directly take the last message for processing, and there are still problems after modification. Consider the suggestion stored in the sender_id
const latestGroup = useMessagesStore(state => state.messageGroupList.at(0));
const senderId = useMessagesStore(
state =>
@@ -42,8 +42,8 @@ export const SuggestionInChat = () => {
latestGroup?.memberSet.llmAnswerMessageIdList.includes(msg.message_id),
)?.sender_id,
);
// 注意 notice manual trigger 类型的消息groupId 是由 message_id 拼接获得。
// 所以一定无法基于 replyId 反向索引
// Note that for notice or manual trigger type messages, groupId is obtained by message_id stitching.
// So it must not be possible to reverse index based on replyId
const replyId = latestGroup?.groupId;
const { latestSectionHasMessage } = useMessagesOverview();
const suggestionBatch = useSuggestionsStore(state =>
@@ -63,7 +63,7 @@ export const SuggestionInChat = () => {
<div
className={classNames(s['suggestion-fail-wrap'], {
[s['suggestion-fail-wrap-selectable'] as string]: selectable,
// 适配移动端 解决 suggestion error 边距问题
// Adapt mobile end to solve suggestion error margin problem
[s['suggestion-fail-wrap-mobile'] as string]:
layout === Layout.MOBILE,
[s['suggestion-fail-wrap-pc'] as string]: layout === Layout.PC,
@@ -125,7 +125,7 @@ export const Suggestions = ({
suggestions: string[];
isInNewConversation?: boolean;
/**
* 上层的 SuggestionInChat enableMention false 时不会传值
* SuggestionInChat does not pass a value when enableMention false
*/
senderId: string | undefined;
suggestionsShowMode?: SuggestedQuestionsShowMode;
@@ -147,7 +147,7 @@ export const Suggestions = ({
<div
data-testid="chat-area.suggestion-list"
className={classNames(s.suggestions, {
// 适配移动端 解决 suggestion 边距问题
// Adapt mobile end to solve the suggestion margin problem
[s['suggestion-with-selectable-in-new-conversation'] as string]:
selectable && isInNewConversation,
[s['suggestions-mobile'] as string]: layout === Layout.MOBILE,

View File

@@ -47,7 +47,7 @@ export interface MessageBoxProps {
isMessageGroupFirstMessage?: boolean;
isMessageGroupLastMessage?: boolean;
renderFooter?: (refreshContainerWidth: () => void) => React.ReactNode;
/** 鼠标悬浮时展示的组件 */
/** Components displayed while the mouse is hovering */
hoverContent?: React.ReactNode;
children?: React.ReactNode;
readonly?: boolean;
@@ -55,15 +55,15 @@ export interface MessageBoxProps {
layout: Layout;
showBackground: boolean;
/**
* 右上角插槽
* Upper right slot
*/
topRightSlot?: React.ReactNode;
/*
* 开启图片自适应大小
* Turn on the picture auto-resizing.
*/
enableImageAutoSize?: boolean;
/**
* 图片自适应大小容器宽度
* Image auto-resizing container width
*/
imageAutoSizeContainerWidth?: number;
eventCallbacks?: IEventCallbacks;
@@ -84,11 +84,11 @@ export interface ContentBoxProps {
layout: Layout;
showBackground: boolean;
/**
* 开启图片自适应大小
* Turn on the picture auto-resizing.
*/
enableImageAutoSize?: boolean;
/**
* 图片自适应大小容器宽度
* Image auto-resizing container width
*/
isCardDisabled?: boolean;
isContentLoading?: boolean;
@@ -122,22 +122,22 @@ export interface ComponentTypesMap {
functionCallMessageBox: ComponentType<{
functionCallMessageList: Message[];
/**
* message 对应的一轮对话是否结束 没被打断 final answer 有返回完毕不管 suggest
* Whether the conversation corresponding to the message is over, not interrupted, and the final answer is returned, regardless of whether it is suggested
*/
isRelatedChatComplete: boolean;
/**
* message 对应的一轮对话是否为假意打断
* Whether the conversation corresponding to the message is a false interruption
*/
isFakeInterruptAnswer: boolean;
/**
* 消息是否来自正在进行的对话根据responding.replyId判断
* Whether the message is from an ongoing conversation, as determined by respond.replyId
*/
isMessageFromOngoingChat: boolean;
getBotInfo: GetBotInfo;
}>;
messageActionBarFooter: ComponentType<{ refreshContainerWidth: () => void }>;
messageActionBarHoverContent: ComponentType;
// TODO: 组件要细化到 message_type 渲染
// TODO: Components to be refined to message_type rendering
receiveMessageBox: ComponentType<ReceiveMessageBoxProps>;
receiveMessageBoxTopRightSlot: ComponentType;
sendMessageBox: ComponentType<SendMessageBoxProps>;
@@ -164,15 +164,15 @@ export interface ComponentTypesMap {
}>;
clearContextIcon: ComponentType;
/**
* 输入框整体顶部附加物
* Text box overall top add-on
*/
inputAboveOutside: ComponentType;
/**
* 输入框内部上方附加物
* Add-on inside text box
*/
inputAddonTop: ComponentType;
/**
* 输入框内部右侧插槽
* Text box inside right slot
*/
inputRightActions?: ComponentType;
chatInputTooltip?: ComponentType;

View File

@@ -16,8 +16,8 @@
import { AgentType } from '@coze-arch/bot-api/developer_api';
// TODO: 这里为啥没i18n需要做
// 节点类型名称映射关系
// TODO: Why is there no i18n to do here?
// Node type name mapping relationship
export const agentTypeNameMap: { [key in AgentType]: string | undefined } = {
[AgentType.LLM_Agent]: 'Agent',
[AgentType.Bot_Agent]: 'Bot',

View File

@@ -29,7 +29,7 @@ export const MARK_MESSAGE_READ_DEBOUNCE_MAX_WAIT = 3000;
export const LOAD_SILENTLY_MAX_NEW_ADDED_COUNT = 6;
// 5s 内调用 get_message_list 超过 3 次,则对请求排队,排队间隔1s
// If the get_message_list is called more than 3 times in 5s, the request is queued, and the queuing interval is 1s.
export const LOAD_MORE_CALL_GET_HISTORY_LIST_TIME_WINDOW = 5000;
export const LOAD_MORE_CALL_GET_HISTORY_LIST_LIMIT = 3;
export const LOAD_MORE_CALL_GET_HISTORY_LIST_EXCEED_RATE_DELAY = 1000;
@@ -38,9 +38,9 @@ export const CURSOR_TO_LOAD_LATEST_MESSAGE = '0';
export const CURSOR_TO_LOAD_LAST_READ_MESSAGE = '-1';
export const LOAD_EAGERLY_LOAD_MESSAGE_COUNT = 20;
/** 并没有做多页同步加载的机制,因此丢弃策略数量与 eagerly 最大加载数量对齐 */
/** There is no mechanism to do multi-page simultaneous loading, so the number of discarded policies is aligned with the eagerly maximum number of loads */
export const MIN_MESSAGE_INDEX_DIFF_TO_ABORT_CURRENT =
LOAD_EAGERLY_LOAD_MESSAGE_COUNT - 1;
/** 服务端没有 reply_id 时可能给这种值 */
/** This value may be given when the server level has no reply_id */
export const SERVER_MESSAGE_REPLY_ID_PLACEHOLDER_VALUES = ['0', '-1'];

View File

@@ -16,7 +16,7 @@
export const SCROLL_VIEW_BOTTOM_DISTANCE_TO_SHOW_NEWEST_TIP = 600;
/**
* 向下加载更多时,模拟 overflow anchor 效果,但是同时稍微移动视图内容,把新增内容稍微多展示一下
* When downloading more, simulate the overflow anchor effect, but at the same time move the view content slightly to show the new content a little more
*/
export const LOAD_NEXT_ANCHOR_ADDITIONAL_MOVE_DISTANCE = 50;
export const LOAD_NEXT_LOCK_DELAY = 30;

View File

@@ -19,7 +19,7 @@ import { createContext } from 'react';
import { type MarkReadService } from '../../service/mark-read';
/**
* 不需要放到最外层 Provider 里的 service 实例提供 Context
* Context provided by a service instance that does not need to be placed in the outermost provider
*/
export interface AfterInitService {

View File

@@ -49,19 +49,19 @@ export interface MessageCallbackParams {
export type SendMessageCallback = (
params: MessageCallbackParams,
from: SendMessageFrom,
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type -- 为什么上层在使用的时候被推断为 void
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type -- why is the upper layer inferred as void when used
) => MessageCallbackParams | void;
export type SendMessageFailedCallback = (
params: MessageCallbackParams,
from: SendMessageFrom,
error: unknown,
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type -- 为什么上层在使用的时候被推断为 void
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type -- why is the upper layer inferred as void when used
) => MessageCallbackParams | void;
export type MessageCallback = (
params: MessageCallbackParams,
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type -- 为什么上层在使用的时候被推断为 void
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type -- why is the upper layer inferred as void when used
) => MessageCallbackParams | void;
export interface SelectionChangeParams {
@@ -85,15 +85,15 @@ export type OnboardingSelectChangeCallback = (
isAlreadyHasSelect: boolean,
) => void;
/**
* 由 ChatArea 向外部发送的事件
* 外部响应
* Events sent externally by ChatArea
* external response
*/
export interface ChatAreaLifeCycleEventMap {
onInitSuccess: () => void;
onInitError: () => void;
onDestroy: () => void;
/**
* @param params 被 freeze
* @param params frozen
*/
onBeforeMessageSend: SendMessageCallback;
onMessageSendFail: SendMessageFailedCallback;
@@ -119,7 +119,7 @@ export interface ChatAreaLifeCycleEventMap {
}) => void;
onAfterStopResponding: OnAfterStopRespondingCallback;
/**
* @deprecated 临时使用,后面考虑切换实现
* @Deprecated temporary use, consider switching implementation later
*/
onParseReceiveMessageBoxTheme?: OnParseReceiveMessageBoxTheme;
}

View File

@@ -27,7 +27,7 @@ export const generateChatCoreBiz = (
return 'coze_home';
case Scene.Playground:
return 'bot_editor';
// 现在没有 bot store 场景
// There is no bot store now
default:
return 'third_part';
}

View File

@@ -43,7 +43,7 @@ import {
import { NullableChatAreaContext } from './context';
/**
* requestToInit 变化可能导致重新初始化(extendDataLifecycle会影响)
* requestToInit changes may cause reinitialization (extendDataLifecycle affects)
*/
export const ChatAreaProviderNew = forwardRef<
ChatAreaProviderMethod,
@@ -120,7 +120,7 @@ export const ChatAreaProviderNew = forwardRef<
[],
);
/* TODO: 拆分一下 context */
/* TODO: Split the context */
return (
<NullableChatAreaContext.Provider
value={{

View File

@@ -21,7 +21,7 @@ import {
type ChatAreaProviderProps,
} from './type';
/**
* 代码 1 周后删除,暂时保留以防万一
* Delete the code after 1 week and keep it temporarily just in case.
*/
import { ChatAreaProviderNew } from './provider-new';

View File

@@ -75,18 +75,18 @@ export interface MixInitResponse {
botVersion?: string;
botInfoMap?: SenderInfoMap;
userInfoMap?: UserInfoMap;
/** hasMore指导前继数据nextHasMore指导后继数据 */
/** hasMore guides predecessor data, nextHasMore guides successor data */
next_has_more?: boolean;
/** cursor指导向前翻页nextCursor指导向后翻页 */
/** Cursor guides page forward, nextCursor guides page backward */
next_cursor: string | undefined;
/** 当前读取到的message_index */
/** Currently read message_index */
read_message_index?: string;
backgroundInfo?: BackgroundImageInfo;
}
/**
* 目前都是来自 verbose 的子类型;
* 影响深远,慎重调整
* Currently all are subtypes from verbose;
* Far-reaching impact, carefully adjusted
*/
export enum IgnoreMessageType {
Knowledge,
@@ -102,11 +102,11 @@ export const allIgnorableMessageTypes = [
IgnoreMessageType.Backwards,
];
// TODO: 感觉preference需要赶紧与configs合并否则初始化的地方拿不到数据provider外)
// TODO: I feel that preference needs to be merged with configs quickly, otherwise the data cannot be obtained at the initialization place (outside the provider)
export interface ChatAreaConfigs {
ignoreMessageConfigList: IgnoreMessageType[];
showFunctionCallDetail: boolean;
// 是否group用户消息合并头像
// Whether to group user messages (merge avatars)
groupUserMessage: boolean;
uploadPlugin: typeof UploadPlugin;
}
@@ -119,7 +119,7 @@ export type ExtendDataLifecycle = 'disable' | 'full-site';
export interface ChatAreaProviderProps
extends Partial<ProviderPassThroughPreference> {
// botId presetBot 必须提供其一, 加了运行时检查
// botId presetBot must provide one, plus runtime check
botId?: string;
spaceId?: string;
presetBot?: PresetBot;
@@ -129,7 +129,7 @@ export interface ChatAreaProviderProps
botVersion?: string;
requestToInit: () => Promise<MixInitResponse>;
/**
* @deprecated 废弃了,请使用插件化方案
* @Deprecated deprecated, please use plugin scheme
*/
eventCallback?: ChatAreaEventCallback;
/**
@@ -140,20 +140,20 @@ export interface ChatAreaProviderProps
*/
createChatCoreOverrideConfig?: CreateChatCoreOverrideConfig;
/**
* @deprecated 不好。后续新增配置参考 ProviderPassThroughPreference
* @Deprecated is not good. Subsequent new configuration reference ProviderPassThroughPreference
*/
configs?: Partial<ChatAreaConfigs>;
/** 是否延长数据生命周期,以超出 provider 自身限制 */
/** Whether to extend the data lifecycle beyond the provider's own limitations */
extendDataLifecycle?: ExtendDataLifecycle;
enableChatCoreDebug?: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
pluginRegistryList?: RegisterPlugin<any>[];
/**
* @deprecated 后续下线该参数,不应该随意使用
* @Deprecated This parameter should not be used at will
*/
enableInitServiceRefactor?: boolean;
/**
* 自定义停止回复按钮的等待状态
* Customize the wait state of the stop reply button
*/
stopRespondOverrideWaiting?: boolean;
}
@@ -211,15 +211,15 @@ export interface ChatAreaContext
export interface ChatAreaProviderMethod {
resetStateFullSite: () => void;
/** !!!给 coze home 开的后门,不要用!!! */
/** !!! The back door to the coze home, don't use it!!! */
updateSenderInfo: UpdateBotInfoByImmer;
/**
* !!!又加了一个后门我快不行了
* !!! Added another back door and I'm dying
*/
updateWaitingSenderId: (id: WaitingSenderId) => void;
// bot store加的后门
// Backdoor to the bot store
/**
* @deprecated 废弃,后续禁止使用
* @deprecated, subsequent use is prohibited
*/
refreshMessageList: () => void;
}

Some files were not shown because too many files have changed in this diff Show More