chore: replace all cn comments of fe to en version by volc api (#320)
This commit is contained in:
@@ -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, [
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -33,5 +33,5 @@ export function useInitProjectRole(spaceId: string, projectId: string) {
|
||||
setIsReady(projectId, true);
|
||||
}, [projectId]);
|
||||
|
||||
return isReady; // 是否初始化完成。
|
||||
return isReady; // Whether the initialization is complete.
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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.');
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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({});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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}`,
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
[],
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
[],
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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={{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -56,7 +56,7 @@ export default function ParamDescription({
|
||||
)}
|
||||
value={data.description}
|
||||
ellipsis={true}
|
||||
// 好像不生效
|
||||
// It doesn't seem to work.
|
||||
disabled={disabled}
|
||||
handleBlur={() => {
|
||||
setInputFocus(false);
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -34,7 +34,7 @@ interface ParamTypeProps {
|
||||
level: number;
|
||||
onSelectChange?: SelectProps['onChange'];
|
||||
disabled?: boolean;
|
||||
// 不支持使用的类型
|
||||
// Types not supported
|
||||
disabledTypes?: ParamTypeAlias[];
|
||||
}
|
||||
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -29,22 +29,22 @@ export type TreeNodeCustomData = TreeNodeData &
|
||||
| 'quotedValue'
|
||||
| 'fieldRandomKey'
|
||||
> & {
|
||||
// 行唯一值
|
||||
// row unique value
|
||||
key: string;
|
||||
// Form的field
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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]" />
|
||||
|
||||
@@ -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',
|
||||
{},
|
||||
|
||||
@@ -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?.();
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -58,7 +58,7 @@ export const DeleteMessage: React.FC<PropsWithChildren<DeleteMessageProps>> = ({
|
||||
/>
|
||||
}
|
||||
onClick={() => {
|
||||
// 通过 groupId 索引即可
|
||||
// Just index through groupId.
|
||||
deleteMessageGroup(groupId);
|
||||
}}
|
||||
color="secondary"
|
||||
|
||||
@@ -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?.();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -65,7 +65,7 @@ export const ThumbsUp: React.FC<ThumbsUpProps> = ({
|
||||
: MessageFeedbackType.Like,
|
||||
},
|
||||
}).then(() => {
|
||||
// 接口调用后再切换展示状态
|
||||
// Switch the display state after the interface is called.
|
||||
onClick?.();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
@@ -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 &&
|
||||
|
||||
@@ -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 });
|
||||
};
|
||||
|
||||
@@ -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 ? (
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: [],
|
||||
|
||||
@@ -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,
|
||||
}));
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -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`} />
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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: '',
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
width: 100%;
|
||||
min-height: 32px;
|
||||
|
||||
/** 高度限制 */
|
||||
/** height limit */
|
||||
max-height: 272px;
|
||||
padding: 6px 12px;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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'];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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={{
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user