diff --git a/.DS_Store b/.DS_Store index b9ff8c5..f166eaf 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/build/assets/icon1-22423762.png b/build/assets/icon1-22423762.png deleted file mode 100644 index a1d3787..0000000 Binary files a/build/assets/icon1-22423762.png and /dev/null differ diff --git a/build/assets/icon1-active-e898a63f.png b/build/assets/icon1-active-e898a63f.png deleted file mode 100644 index f519402..0000000 Binary files a/build/assets/icon1-active-e898a63f.png and /dev/null differ diff --git a/build/assets/icon2-0a5dbb41.png b/build/assets/icon2-0a5dbb41.png deleted file mode 100644 index 758b09c..0000000 Binary files a/build/assets/icon2-0a5dbb41.png and /dev/null differ diff --git a/build/assets/icon2-active-eae32509.png b/build/assets/icon2-active-eae32509.png deleted file mode 100644 index fa7f990..0000000 Binary files a/build/assets/icon2-active-eae32509.png and /dev/null differ diff --git a/build/assets/icon3-active-56cabe9a.png b/build/assets/icon3-active-56cabe9a.png deleted file mode 100644 index d8a44e0..0000000 Binary files a/build/assets/icon3-active-56cabe9a.png and /dev/null differ diff --git a/build/assets/icon4-active-78d00c9a.png b/build/assets/icon4-active-78d00c9a.png deleted file mode 100644 index 678fb02..0000000 Binary files a/build/assets/icon4-active-78d00c9a.png and /dev/null differ diff --git a/build/assets/icon5-187cba94.png b/build/assets/icon5-187cba94.png deleted file mode 100644 index f9f1e92..0000000 Binary files a/build/assets/icon5-187cba94.png and /dev/null differ diff --git a/build/assets/icon5-active-52a6d7fe.png b/build/assets/icon5-active-52a6d7fe.png deleted file mode 100644 index d175730..0000000 Binary files a/build/assets/icon5-active-52a6d7fe.png and /dev/null differ diff --git a/build/index.html b/build/index.html index bc9eb26..1846e8f 100644 --- a/build/index.html +++ b/build/index.html @@ -1,25 +1 @@ - - - - - - - - - - NPCs - - - - - - - - - - -
- - - - \ No newline at end of file +NPCs
\ No newline at end of file diff --git a/build/locales/en/bs.json b/build/locales/en/bs.json index 125bf29..56ad2eb 100644 --- a/build/locales/en/bs.json +++ b/build/locales/en/bs.json @@ -21,25 +21,34 @@ "pleaseEnterCaptcha": "Please enter captcha" }, "menu": { - "app": "App", - "skills": "Skills", + "app": "Chat", + "user": "User", + "github": "GitHub", + "bookopen": "Document", + "skills": "Build", "knowledge": "Knowledge", + "evaluation": "Evaluation", "models": "Models", "system": "System", + "log": "Logs", "themeSwitch": "Theme Switch", "document": "Documentation", "logout": "Logout", "logoutDescription": "Log out", + "logoutContent": "Are you sure to log out", "forBestExperience": "For the best experience, please access this website on a PC", - "onlineDocumentation": "Online Documentation" + "onlineDocumentation": "Online Documentation", + "changePwd": "Password" }, "system": { "userManagement": "User Management", "roleManagement": "Role Management", + "userGroupsM": "UserGroup Management", "systemConfiguration": "System Configuration", "username": "Username", "confirmDisable": "Confirm disabling this user?", - "roleSelect": "Select Role", + "roleSelect": "Select Roles", + "userGroupsSel": "Select UserGroups", "roleList": "Role List", "confirmText": "Are you sure you want to delete", "roleName": "Role Name", @@ -52,8 +61,41 @@ "roleNamePrompt": "Role name cannot exceed 50 characters", "roleNameRequired": "Role name is required", "roleNameExists": "Role name already exists", + "groupNameExists": "UserGroup name already exists", + "groupNamePrompt": "UserGroup name cannot exceed 30 characters", + "groupNameRequired": "UserGroup name is required", "parameterConfig": "Parameter Configuration", - "language": "Language" + "language": "Language", + "assistantAuthorization": "Assistant Authorization", + "assistantName": "Assistant Name", + "userList": "User List", + "userGroupList": "UserGroup List", + "userGroup": "userGroup", + "role": "Role", + "searchUserGroups": "Search user groups", + "searchRoles": "Search roles", + "reset": "Reset", + "confirm": "Confirm", + "userGroupName": "Enter UserGroup Name", + "groupName": "UserGroup Name", + "admins": "Admin", + "flowControl": "Overall UserGroup Flow Control", + "AssistantFlowCtrl": "Assistant Flow Control", + "SkillFlowCtrl": "Skill Flow Control", + "createdBy": "CreatedBy", + "flowCtrlStrategy": "Flow control strategy", + "limit": "Limit", + "unlimited": "Unlimited", + "iconHover": "Simultaneously constrained by the overall traffic control strategy of the user group", + "maximum": "Up to", + "perMinute": "simultaneous online sessions", + "changeTime": "Modification Time", + "deleteGroup": "After deletion 【{{name}}】 will no longer exist, Do you want to delete it?", + "currentGroup": "Current UserGroup", + "defaultGroup": "DefaultGroup", + "resetPwd": "ResetPassword", + "selectGroup": "Please select a user group", + "selectRole": "Please select a role" }, "skills": { "manageTemplate": "Manage Skill Templates", @@ -303,7 +345,40 @@ "knowledgeImg": "Knowledge Base Avatar", "indexModel": "Index model", "dataUp": "Single data upper limit", - "introduce": "Introduce" + "introduce": "Introduce", + "fileUploadResult": "Out of the {{total}} files uploaded, {{failed}} failed to upload.", + "modalTitle": "File Duplicate Prompt", + "modalMessage": "The following files already exist in the knowledge base. Continuing the upload will overwrite the original files and processing strategy. Do you want to proceed with overwrite?", + "keepOriginal": "Keep Original Files", + "override": "Override", + "toolName": "ToolName" + }, + "evaluation": { + "id": "任务ID", + "filename": "测试文件名称", + "skillAssistant": "技能助手", + "status": "状态", + "score": "评测分数", + "createDate": "创建日期", + "download": "下载", + "confirmDeleteEvaluation": "确认删除该评测任务?", + "createTitle": "新建任务", + "selectLabel": "选择要评测的技能或者助手:", + "selectPlaceholder": "请选择", + "dataLabel": "测试集数据:", + "fileExpandName": "支持扩展名:", + "downloadTemplate": "下载模板文件", + "promptLabel": "评测指令文本:", + "enterExecType": "请选择要评测的技能或助手", + "enterUniqueId": "请选择技能或助手ID", + "enterVersion": "请选择技能的版本", + "enterFile": "请选择测试集数据", + "enterPrompt": "评测指令不能为空", + "fileSizeLimit": "文件大小限制在10M以内", + "evaluationCollection": "评测集合", + "tooltip": "该指令文本用于指导大模型对 ground truth 和 answer 提取要点,如无特别需求请勿修改", + "create": "创建", + "cancel": "取消" }, "code": { "editPythonCodeDescription": "Edit your Python code here. This code snippet accepts module imports and a function definition. Make sure your function returns a string.", @@ -576,6 +651,41 @@ "result": "Test Result", "outResultPlaceholder": "Click the button to output the result" }, + "resetPassword": { + "slogen": "Securely Reset Your Password", + "currentPassword": "Current Password", + "newPassword": "New Password", + "confirmNewPassword": "Confirm New Password", + "pleaseEnterCurrentPassword": "Please enter your current password.", + "pleaseEnterNewPassword": "Please enter your new password.", + "pleaseEnterConfirmPassword": "Please confirm your new password.", + "newPasswordTooShort": "New password must be at least 8 characters.", + "passwordMismatch": "The new passwords do not match.", + "resetButton": "Change Password", + "passwordResetSuccess": "Your password has been successfully reset.", + "adminResetSuccess": "Password has been successfully reset", + "resetFailed": "Pwd Reset Failed", + "notEmpty": "The new password cannot be empty" + }, + "log": { + "auditManagement": "Audit Management", + "searchButton": "Search", + "resetButton": "Reset", + "auditId": "Audit ID", + "username": "Username", + "operationTime": "Operation Time", + "systemModule": "System Module", + "operationAction": "Operation Action", + "objectType": "Operation Object Type", + "operationObject": "Operation Object", + "ipAddress": "IP Address", + "remark": "Remark", + "selectUser": "Select User", + "selectUserGroup": "Select User Group", + "startDate": "Start Date", + "endDate": "End Date", + "actionBehavior": "Action Behavior" + }, "agents": { "AgentInitializer":{ "display_name": "AgentInitializer", diff --git a/build/locales/zh/bs.json b/build/locales/zh/bs.json index b2ff614..00a08d8 100644 --- a/build/locales/zh/bs.json +++ b/build/locales/zh/bs.json @@ -14,32 +14,42 @@ "pleaseEnterAccount": "请填写账号", "pleaseEnterPassword": "请填写密码", "accountTooShort": "账号过短", - "passwordTooShort": "请填写密码,至少6位", + "passwordTooShort": "请填写密码,至少7位", "passwordError": "密码必须包含字母、数字!", "passwordMismatch": "两次密码不一致", "registrationSuccess": "注册成功,请输入密码进行登录" }, "menu": { - "app": "聊天", + "user": "用户", + "bookopen": "帮助文档", + "github": "GitHub", + "app": "会 话", "skills": "NPC", "knowledge": "知识库", - "models": "模型", - "system": "账号", + "evaluation": "评 测", + "models": "模 型", + "system": "账 号", + "log": "审 计", "themeSwitch": "主题切换", "document": "文档", "logout": "退出", "logoutDescription": "退出登录", + "logoutContent": "确认退出登录吗", "forBestExperience": "为了您的良好体验,请在 PC 端访问该网站", - "onlineDocumentation": "在线文档" + "onlineDocumentation": "在线文档", + "changePwd": "修改密码" }, "system": { - "userManagement": "用户管理", + "userManagement": "用户管理", + "userGroupsM": "用户组管理", "roleManagement": "角色管理", "systemConfiguration": "系统配置", "username": "用户名", "confirmDisable": "确认禁用该用户?", "roleSelect": "角色选择", + "userGroupsSel": "用户组选择", "roleList": "角色列表", + "userGroupList": "用户组列表", "confirmText": "是否删除", "roleName": "角色名称", "skillAuthorization": "能力授权", @@ -49,10 +59,42 @@ "usePermission": "使用权限", "managePermission": "管理权限", "roleNamePrompt": "角色名称不能超过50字符", - "roleNameRequired": "角色名称不能为空", + "roleNameRequired": "角色名称不可为空", + "groupNameExists": "用户组名称不可重复", + "groupNamePrompt": "用户组名称不能超过30字符", + "groupNameRequired": "用户组名称不可为空", "roleNameExists": "角色名称已存在", "parameterConfig": "参数配置", - "language": "语言" + "language": "语言", + "assistantAuthorization": "NPC授权", + "assistantName": "NPC名称", + "userList": "用户列表", + "userGroup": "用户组", + "role": "角色", + "searchUserGroups": "搜索用户组", + "searchRoles": "搜索角色", + "reset": "重置", + "confirm": "确认", + "userGroupName": "输入用户组名称", + "groupName": "用户组名称", + "admins": "管理员", + "flowControl": "用户组整体流量控制", + "AssistantFlowCtrl": "NPC流量控制", + "SkillFlowCtrl": "能力流量控制", + "createdBy": "创建人", + "flowCtrlStrategy": "流量控制策略", + "limit": "有限制", + "unlimited": "无限制", + "iconHover": "同时受用户组整体流量控制策略约束", + "maximum": "最多", + "perMinute": "个同时在线会话", + "changeTime": "修改时间", + "deleteGroup": "删除后 【{{name}}】 将不再存在,是否删除?", + "currentGroup": "当前用户组", + "defaultGroup": "默认用户组", + "resetPwd": "重置密码", + "selectGroup": "请选择用户组", + "selectRole": "请选择角色" }, "skills": { "manageTemplate": "管理能力模板", @@ -295,7 +337,41 @@ "knowledgeImg": "知识库头像", "indexModel": "索引模型", "dataUp": "单条数据上限", - "introduce": "介绍" + "introduce": "介绍", + "fileUploadResult": "共上传 {{total}} 份文件,有 {{failed}} 份文件上传失败", + "modalTitle": "文件重复提示", + "modalMessage": "以下文件在知识库中已存在,继续上传将会覆盖原有文件以及处理策略,是否覆盖?", + "keepOriginal": "不覆盖,保留原文件", + "override": "覆盖", + "toolName": "工具名称" + }, + "evaluation": { + "id": "任务ID", + "filename": "测试文件名称", + "skillAssistant": "能力NPC", + "status": "状态", + "score": "评测分数", + "createDate": "创建日期", + "download": "下载", + "confirmDeleteEvaluation": "确认删除该评测任务?", + "createTitle": "新建任务", + "selectLabel": "选择要评测的能力或者NPC:", + "selectPlaceholder": "请选择", + "selectInputPlaceholder": "请根据名称进行搜索", + "dataLabel": "测试集数据:", + "fileExpandName": "支持扩展名:", + "downloadTemplate": "下载模板文件", + "promptLabel": "评测指令文本:", + "enterExecType": "请选择要评测的能力或NPC", + "enterUniqueId": "请选择能力或NPCID", + "enterVersion": "请选择能力的版本", + "enterFile": "请选择测试集数据", + "enterPrompt": "评测指令不能为空", + "fileSizeLimit": "文件大小限制在10M以内", + "evaluationCollection": "评测集合", + "tooltip": "该指令文本用于指导大模型对 ground truth 和 answer 提取要点,如无特别需求请勿修改", + "create": "创建", + "cancel": "取消" }, "code": { "editPythonCodeDescription": "编辑你的 Python 代码此代码片段接受模块导入和一个函数定义。确保您的函数返回一个字符串。", @@ -568,6 +644,41 @@ "result": "测试结果", "outResultPlaceholder": "点击按钮,输出结果" }, + "resetPassword": { + "slogen": "安全地重置您的密码", + "currentPassword": "当前密码", + "newPassword": "新密码", + "confirmNewPassword": "确认新密码", + "pleaseEnterCurrentPassword": "请输入当前密码。", + "pleaseEnterNewPassword": "请输入新密码。", + "pleaseEnterConfirmPassword": "请确认新密码。", + "newPasswordTooShort": "新密码必须至少 7 个字符。", + "passwordMismatch": "新密码不匹配。", + "resetButton": "修改密码", + "passwordResetSuccess": "您的密码已成功修改", + "adminResetSuccess": "密码已重置", + "resetFailed": "密码重置失败", + "notEmpty": "新密码不能为空" + }, + "log": { + "auditManagement": "审计管理", + "searchButton": "查询", + "resetButton": "重置", + "auditId": "审计ID", + "username": "用户名", + "operationTime": "操作时间", + "systemModule": "系统模块", + "operationAction": "操作行为", + "objectType": "操作对象类型", + "operationObject": "操作对象", + "ipAddress": "IP地址", + "remark": "备注", + "selectUser": "选择用户", + "selectUserGroup": "选择用户组", + "startDate": "开始日期", + "endDate": "结束日期", + "actionBehavior": "操作行为" + }, "agents": { "AgentInitializer": { "display_name": "AgentInitializer", diff --git a/package-lock.json b/package-lock.json index edfd2da..1e836b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,7 +61,9 @@ "react": "^18.2.0", "react-ace": "^10.1.0", "react-beautiful-dnd": "^13.1.1", + "react-color": "^2.19.3", "react-cookie": "^4.1.1", + "react-day-picker": "^8.10.1", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", "react-error-boundary": "^4.0.11", @@ -115,6 +117,7 @@ "tailwindcss": "^3.3.3", "typescript": "^5.2.2", "vite": "^4.5.2", + "vite-plugin-html": "^3.2.2", "vite-plugin-static-copy": "^0.17.0" }, "engines": { @@ -1283,6 +1286,14 @@ "react": ">= 16" } }, + "node_modules/@icons/material": { + "version": "0.2.4", + "resolved": "https://registry.npmmirror.com/@icons/material/-/material-0.2.4.tgz", + "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1401,6 +1412,16 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmmirror.com/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "devOptional": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", @@ -4950,6 +4971,12 @@ "node": ">=8" } }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmmirror.com/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true + }, "node_modules/async-validator": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", @@ -5284,6 +5311,12 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -5432,6 +5465,16 @@ "node": ">=6" } }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, "node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -5615,6 +5658,27 @@ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmmirror.com/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "dev": true, + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/clean-css/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/cli-cursor": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", @@ -5975,6 +6039,12 @@ "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmmirror.com/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -6014,7 +6084,22 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "optional": true + "devOptional": true + }, + "node_modules/connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/consola": { + "version": "2.15.3", + "resolved": "https://registry.npmmirror.com/consola/-/consola-2.15.3.tgz", + "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==", + "dev": true }, "node_modules/console-control-strings": { "version": "1.1.0", @@ -6115,6 +6200,22 @@ "tiny-invariant": "^1.0.6" } }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/css-selector-tokenizer": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz", @@ -6124,6 +6225,18 @@ "fastparse": "^1.1.2" } }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", @@ -6401,6 +6514,16 @@ "node": ">=12" } }, + "node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/dayjs": { "version": "1.11.10", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", @@ -6702,6 +6825,32 @@ "csstype": "^3.0.2" } }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, "node_modules/domexception": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", @@ -6714,11 +6863,40 @@ "node": ">=12" } }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmmirror.com/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, "node_modules/dompurify": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.1.tgz", "integrity": "sha512-tVP8C/GJwnABOn/7cx/ymx/hXpmBfWIPihC1aOEvS8GbMqy3pgeYtJk1HXN3CO7tu+8bpY18f6isjR5Cymj0TQ==" }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmmirror.com/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dot-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", @@ -6728,6 +6906,27 @@ "tslib": "^2.0.3" } }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "8.0.3", + "resolved": "https://registry.npmmirror.com/dotenv-expand/-/dotenv-expand-8.0.3.tgz", + "integrity": "sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", @@ -6778,6 +6977,21 @@ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmmirror.com/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.750", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.750.tgz", @@ -7177,6 +7391,27 @@ "url": "https://github.com/sindresorhus/file-type?sponsor=1" } }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/filename-reserved-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-3.0.0.tgz", @@ -8168,6 +8403,15 @@ "node": ">=0.4" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, "node_modules/highlight.js": { "version": "10.7.3", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", @@ -8200,6 +8444,36 @@ "node": ">=12" } }, + "node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dev": true, + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-minifier-terser/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmmirror.com/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, "node_modules/html-parse-stringify": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", @@ -8909,6 +9183,46 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmmirror.com/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jake/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/jake/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/jest-diff": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", @@ -9432,6 +9746,11 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/material-colors": { + "version": "1.2.6", + "resolved": "https://registry.npmmirror.com/material-colors/-/material-colors-1.2.6.tgz", + "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==" + }, "node_modules/mathjax-full": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/mathjax-full/-/mathjax-full-3.2.2.tgz", @@ -11789,6 +12108,16 @@ } } }, + "node_modules/node-html-parser": { + "version": "5.4.2", + "resolved": "https://registry.npmmirror.com/node-html-parser/-/node-html-parser-5.4.2.tgz", + "integrity": "sha512-RaBPP3+51hPne/OolXxcz89iYvQvKOydaqoePpOgXcrOKZhjVIzmpKZz+Hd/RBO2/zN2q6CNJhQzucVz+u3Jyw==", + "dev": true, + "dependencies": { + "css-select": "^4.2.1", + "he": "1.2.0" + } + }, "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -11862,6 +12191,18 @@ "set-blocking": "^2.0.0" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/nwsapi": { "version": "2.2.9", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.9.tgz", @@ -12080,6 +12421,16 @@ "node": ">=4" } }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -12147,6 +12498,16 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", @@ -12215,6 +12576,12 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-0.2.0.tgz", + "integrity": "sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==", + "dev": true + }, "node_modules/pdfjs-dist": { "version": "3.10.111", "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.10.111.tgz", @@ -13432,6 +13799,23 @@ "react-dom": "^16.8.5 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-color": { + "version": "2.19.3", + "resolved": "https://registry.npmmirror.com/react-color/-/react-color-2.19.3.tgz", + "integrity": "sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==", + "dependencies": { + "@icons/material": "^0.2.4", + "lodash": "^4.17.15", + "lodash-es": "^4.17.15", + "material-colors": "^1.2.1", + "prop-types": "^15.5.10", + "reactcss": "^1.2.0", + "tinycolor2": "^1.4.1" + }, + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-colorful": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz", @@ -13454,6 +13838,19 @@ "react": ">= 16.3.0" } }, + "node_modules/react-day-picker": { + "version": "8.10.1", + "resolved": "https://registry.npmmirror.com/react-day-picker/-/react-day-picker-8.10.1.tgz", + "integrity": "sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==", + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/gpbl" + }, + "peerDependencies": { + "date-fns": "^2.28.0 || ^3.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -13859,6 +14256,14 @@ "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/reactcss": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/reactcss/-/reactcss-1.2.3.tgz", + "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==", + "dependencies": { + "lodash": "^4.0.1" + } + }, "node_modules/reactflow": { "version": "11.11.2", "resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.11.2.tgz", @@ -14149,6 +14554,15 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmmirror.com/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/remark-breaks": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/remark-breaks/-/remark-breaks-4.0.0.tgz", @@ -15308,6 +15722,31 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "devOptional": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "devOptional": true + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "devOptional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/space-separated-tokens": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", @@ -15791,6 +16230,30 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "optional": true }, + "node_modules/terser": { + "version": "5.31.3", + "resolved": "https://registry.npmmirror.com/terser/-/terser-5.31.3.tgz", + "integrity": "sha512-pAfYn3NIZLyZpa83ZKigvj6Rn9c/vd5KfYGX7cN1mnzqgDcxWvrU5ZtAfIKhEXz9nRecw4z3LXkjaq96/qZqAA==", + "devOptional": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "devOptional": true + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -15845,6 +16308,11 @@ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==" + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -16487,6 +16955,56 @@ } } }, + "node_modules/vite-plugin-html": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/vite-plugin-html/-/vite-plugin-html-3.2.2.tgz", + "integrity": "sha512-vb9C9kcdzcIo/Oc3CLZVS03dL5pDlOFuhGlZYDCJ840BhWl/0nGeZWf3Qy7NlOayscY4Cm/QRgULCQkEZige5Q==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^4.2.0", + "colorette": "^2.0.16", + "connect-history-api-fallback": "^1.6.0", + "consola": "^2.15.3", + "dotenv": "^16.0.0", + "dotenv-expand": "^8.0.2", + "ejs": "^3.1.6", + "fast-glob": "^3.2.11", + "fs-extra": "^10.0.1", + "html-minifier-terser": "^6.1.0", + "node-html-parser": "^5.3.3", + "pathe": "^0.2.0" + }, + "peerDependencies": { + "vite": ">=2.0.0" + } + }, + "node_modules/vite-plugin-html/node_modules/@rollup/pluginutils": { + "version": "4.2.1", + "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", + "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", + "dev": true, + "dependencies": { + "estree-walker": "^2.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/vite-plugin-html/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/vite-plugin-static-copy": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-0.17.1.tgz", diff --git a/package.json b/package.json index c3aa430..511c5fe 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,9 @@ "react": "^18.2.0", "react-ace": "^10.1.0", "react-beautiful-dnd": "^13.1.1", + "react-color": "^2.19.3", "react-cookie": "^4.1.1", + "react-day-picker": "^8.10.1", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", "react-error-boundary": "^4.0.11", @@ -136,6 +138,7 @@ "tailwindcss": "^3.3.3", "typescript": "^5.2.2", "vite": "^4.5.2", + "vite-plugin-html": "^3.2.2", "vite-plugin-static-copy": "^0.17.0" }, "engines": { diff --git a/public/locales/en/bs.json b/public/locales/en/bs.json index 125bf29..56ad2eb 100644 --- a/public/locales/en/bs.json +++ b/public/locales/en/bs.json @@ -21,25 +21,34 @@ "pleaseEnterCaptcha": "Please enter captcha" }, "menu": { - "app": "App", - "skills": "Skills", + "app": "Chat", + "user": "User", + "github": "GitHub", + "bookopen": "Document", + "skills": "Build", "knowledge": "Knowledge", + "evaluation": "Evaluation", "models": "Models", "system": "System", + "log": "Logs", "themeSwitch": "Theme Switch", "document": "Documentation", "logout": "Logout", "logoutDescription": "Log out", + "logoutContent": "Are you sure to log out", "forBestExperience": "For the best experience, please access this website on a PC", - "onlineDocumentation": "Online Documentation" + "onlineDocumentation": "Online Documentation", + "changePwd": "Password" }, "system": { "userManagement": "User Management", "roleManagement": "Role Management", + "userGroupsM": "UserGroup Management", "systemConfiguration": "System Configuration", "username": "Username", "confirmDisable": "Confirm disabling this user?", - "roleSelect": "Select Role", + "roleSelect": "Select Roles", + "userGroupsSel": "Select UserGroups", "roleList": "Role List", "confirmText": "Are you sure you want to delete", "roleName": "Role Name", @@ -52,8 +61,41 @@ "roleNamePrompt": "Role name cannot exceed 50 characters", "roleNameRequired": "Role name is required", "roleNameExists": "Role name already exists", + "groupNameExists": "UserGroup name already exists", + "groupNamePrompt": "UserGroup name cannot exceed 30 characters", + "groupNameRequired": "UserGroup name is required", "parameterConfig": "Parameter Configuration", - "language": "Language" + "language": "Language", + "assistantAuthorization": "Assistant Authorization", + "assistantName": "Assistant Name", + "userList": "User List", + "userGroupList": "UserGroup List", + "userGroup": "userGroup", + "role": "Role", + "searchUserGroups": "Search user groups", + "searchRoles": "Search roles", + "reset": "Reset", + "confirm": "Confirm", + "userGroupName": "Enter UserGroup Name", + "groupName": "UserGroup Name", + "admins": "Admin", + "flowControl": "Overall UserGroup Flow Control", + "AssistantFlowCtrl": "Assistant Flow Control", + "SkillFlowCtrl": "Skill Flow Control", + "createdBy": "CreatedBy", + "flowCtrlStrategy": "Flow control strategy", + "limit": "Limit", + "unlimited": "Unlimited", + "iconHover": "Simultaneously constrained by the overall traffic control strategy of the user group", + "maximum": "Up to", + "perMinute": "simultaneous online sessions", + "changeTime": "Modification Time", + "deleteGroup": "After deletion 【{{name}}】 will no longer exist, Do you want to delete it?", + "currentGroup": "Current UserGroup", + "defaultGroup": "DefaultGroup", + "resetPwd": "ResetPassword", + "selectGroup": "Please select a user group", + "selectRole": "Please select a role" }, "skills": { "manageTemplate": "Manage Skill Templates", @@ -303,7 +345,40 @@ "knowledgeImg": "Knowledge Base Avatar", "indexModel": "Index model", "dataUp": "Single data upper limit", - "introduce": "Introduce" + "introduce": "Introduce", + "fileUploadResult": "Out of the {{total}} files uploaded, {{failed}} failed to upload.", + "modalTitle": "File Duplicate Prompt", + "modalMessage": "The following files already exist in the knowledge base. Continuing the upload will overwrite the original files and processing strategy. Do you want to proceed with overwrite?", + "keepOriginal": "Keep Original Files", + "override": "Override", + "toolName": "ToolName" + }, + "evaluation": { + "id": "任务ID", + "filename": "测试文件名称", + "skillAssistant": "技能助手", + "status": "状态", + "score": "评测分数", + "createDate": "创建日期", + "download": "下载", + "confirmDeleteEvaluation": "确认删除该评测任务?", + "createTitle": "新建任务", + "selectLabel": "选择要评测的技能或者助手:", + "selectPlaceholder": "请选择", + "dataLabel": "测试集数据:", + "fileExpandName": "支持扩展名:", + "downloadTemplate": "下载模板文件", + "promptLabel": "评测指令文本:", + "enterExecType": "请选择要评测的技能或助手", + "enterUniqueId": "请选择技能或助手ID", + "enterVersion": "请选择技能的版本", + "enterFile": "请选择测试集数据", + "enterPrompt": "评测指令不能为空", + "fileSizeLimit": "文件大小限制在10M以内", + "evaluationCollection": "评测集合", + "tooltip": "该指令文本用于指导大模型对 ground truth 和 answer 提取要点,如无特别需求请勿修改", + "create": "创建", + "cancel": "取消" }, "code": { "editPythonCodeDescription": "Edit your Python code here. This code snippet accepts module imports and a function definition. Make sure your function returns a string.", @@ -576,6 +651,41 @@ "result": "Test Result", "outResultPlaceholder": "Click the button to output the result" }, + "resetPassword": { + "slogen": "Securely Reset Your Password", + "currentPassword": "Current Password", + "newPassword": "New Password", + "confirmNewPassword": "Confirm New Password", + "pleaseEnterCurrentPassword": "Please enter your current password.", + "pleaseEnterNewPassword": "Please enter your new password.", + "pleaseEnterConfirmPassword": "Please confirm your new password.", + "newPasswordTooShort": "New password must be at least 8 characters.", + "passwordMismatch": "The new passwords do not match.", + "resetButton": "Change Password", + "passwordResetSuccess": "Your password has been successfully reset.", + "adminResetSuccess": "Password has been successfully reset", + "resetFailed": "Pwd Reset Failed", + "notEmpty": "The new password cannot be empty" + }, + "log": { + "auditManagement": "Audit Management", + "searchButton": "Search", + "resetButton": "Reset", + "auditId": "Audit ID", + "username": "Username", + "operationTime": "Operation Time", + "systemModule": "System Module", + "operationAction": "Operation Action", + "objectType": "Operation Object Type", + "operationObject": "Operation Object", + "ipAddress": "IP Address", + "remark": "Remark", + "selectUser": "Select User", + "selectUserGroup": "Select User Group", + "startDate": "Start Date", + "endDate": "End Date", + "actionBehavior": "Action Behavior" + }, "agents": { "AgentInitializer":{ "display_name": "AgentInitializer", diff --git a/public/locales/zh/bs.json b/public/locales/zh/bs.json index b2ff614..00a08d8 100644 --- a/public/locales/zh/bs.json +++ b/public/locales/zh/bs.json @@ -14,32 +14,42 @@ "pleaseEnterAccount": "请填写账号", "pleaseEnterPassword": "请填写密码", "accountTooShort": "账号过短", - "passwordTooShort": "请填写密码,至少6位", + "passwordTooShort": "请填写密码,至少7位", "passwordError": "密码必须包含字母、数字!", "passwordMismatch": "两次密码不一致", "registrationSuccess": "注册成功,请输入密码进行登录" }, "menu": { - "app": "聊天", + "user": "用户", + "bookopen": "帮助文档", + "github": "GitHub", + "app": "会 话", "skills": "NPC", "knowledge": "知识库", - "models": "模型", - "system": "账号", + "evaluation": "评 测", + "models": "模 型", + "system": "账 号", + "log": "审 计", "themeSwitch": "主题切换", "document": "文档", "logout": "退出", "logoutDescription": "退出登录", + "logoutContent": "确认退出登录吗", "forBestExperience": "为了您的良好体验,请在 PC 端访问该网站", - "onlineDocumentation": "在线文档" + "onlineDocumentation": "在线文档", + "changePwd": "修改密码" }, "system": { - "userManagement": "用户管理", + "userManagement": "用户管理", + "userGroupsM": "用户组管理", "roleManagement": "角色管理", "systemConfiguration": "系统配置", "username": "用户名", "confirmDisable": "确认禁用该用户?", "roleSelect": "角色选择", + "userGroupsSel": "用户组选择", "roleList": "角色列表", + "userGroupList": "用户组列表", "confirmText": "是否删除", "roleName": "角色名称", "skillAuthorization": "能力授权", @@ -49,10 +59,42 @@ "usePermission": "使用权限", "managePermission": "管理权限", "roleNamePrompt": "角色名称不能超过50字符", - "roleNameRequired": "角色名称不能为空", + "roleNameRequired": "角色名称不可为空", + "groupNameExists": "用户组名称不可重复", + "groupNamePrompt": "用户组名称不能超过30字符", + "groupNameRequired": "用户组名称不可为空", "roleNameExists": "角色名称已存在", "parameterConfig": "参数配置", - "language": "语言" + "language": "语言", + "assistantAuthorization": "NPC授权", + "assistantName": "NPC名称", + "userList": "用户列表", + "userGroup": "用户组", + "role": "角色", + "searchUserGroups": "搜索用户组", + "searchRoles": "搜索角色", + "reset": "重置", + "confirm": "确认", + "userGroupName": "输入用户组名称", + "groupName": "用户组名称", + "admins": "管理员", + "flowControl": "用户组整体流量控制", + "AssistantFlowCtrl": "NPC流量控制", + "SkillFlowCtrl": "能力流量控制", + "createdBy": "创建人", + "flowCtrlStrategy": "流量控制策略", + "limit": "有限制", + "unlimited": "无限制", + "iconHover": "同时受用户组整体流量控制策略约束", + "maximum": "最多", + "perMinute": "个同时在线会话", + "changeTime": "修改时间", + "deleteGroup": "删除后 【{{name}}】 将不再存在,是否删除?", + "currentGroup": "当前用户组", + "defaultGroup": "默认用户组", + "resetPwd": "重置密码", + "selectGroup": "请选择用户组", + "selectRole": "请选择角色" }, "skills": { "manageTemplate": "管理能力模板", @@ -295,7 +337,41 @@ "knowledgeImg": "知识库头像", "indexModel": "索引模型", "dataUp": "单条数据上限", - "introduce": "介绍" + "introduce": "介绍", + "fileUploadResult": "共上传 {{total}} 份文件,有 {{failed}} 份文件上传失败", + "modalTitle": "文件重复提示", + "modalMessage": "以下文件在知识库中已存在,继续上传将会覆盖原有文件以及处理策略,是否覆盖?", + "keepOriginal": "不覆盖,保留原文件", + "override": "覆盖", + "toolName": "工具名称" + }, + "evaluation": { + "id": "任务ID", + "filename": "测试文件名称", + "skillAssistant": "能力NPC", + "status": "状态", + "score": "评测分数", + "createDate": "创建日期", + "download": "下载", + "confirmDeleteEvaluation": "确认删除该评测任务?", + "createTitle": "新建任务", + "selectLabel": "选择要评测的能力或者NPC:", + "selectPlaceholder": "请选择", + "selectInputPlaceholder": "请根据名称进行搜索", + "dataLabel": "测试集数据:", + "fileExpandName": "支持扩展名:", + "downloadTemplate": "下载模板文件", + "promptLabel": "评测指令文本:", + "enterExecType": "请选择要评测的能力或NPC", + "enterUniqueId": "请选择能力或NPCID", + "enterVersion": "请选择能力的版本", + "enterFile": "请选择测试集数据", + "enterPrompt": "评测指令不能为空", + "fileSizeLimit": "文件大小限制在10M以内", + "evaluationCollection": "评测集合", + "tooltip": "该指令文本用于指导大模型对 ground truth 和 answer 提取要点,如无特别需求请勿修改", + "create": "创建", + "cancel": "取消" }, "code": { "editPythonCodeDescription": "编辑你的 Python 代码此代码片段接受模块导入和一个函数定义。确保您的函数返回一个字符串。", @@ -568,6 +644,41 @@ "result": "测试结果", "outResultPlaceholder": "点击按钮,输出结果" }, + "resetPassword": { + "slogen": "安全地重置您的密码", + "currentPassword": "当前密码", + "newPassword": "新密码", + "confirmNewPassword": "确认新密码", + "pleaseEnterCurrentPassword": "请输入当前密码。", + "pleaseEnterNewPassword": "请输入新密码。", + "pleaseEnterConfirmPassword": "请确认新密码。", + "newPasswordTooShort": "新密码必须至少 7 个字符。", + "passwordMismatch": "新密码不匹配。", + "resetButton": "修改密码", + "passwordResetSuccess": "您的密码已成功修改", + "adminResetSuccess": "密码已重置", + "resetFailed": "密码重置失败", + "notEmpty": "新密码不能为空" + }, + "log": { + "auditManagement": "审计管理", + "searchButton": "查询", + "resetButton": "重置", + "auditId": "审计ID", + "username": "用户名", + "operationTime": "操作时间", + "systemModule": "系统模块", + "operationAction": "操作行为", + "objectType": "操作对象类型", + "operationObject": "操作对象", + "ipAddress": "IP地址", + "remark": "备注", + "selectUser": "选择用户", + "selectUserGroup": "选择用户组", + "startDate": "开始日期", + "endDate": "结束日期", + "actionBehavior": "操作行为" + }, "agents": { "AgentInitializer": { "display_name": "AgentInitializer", diff --git a/src/.DS_Store b/src/.DS_Store index 907cc45..c128842 100644 Binary files a/src/.DS_Store and b/src/.DS_Store differ diff --git a/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index 03d4bee..90b67bd 100644 --- a/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ b/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -34,7 +34,7 @@ import { nodeIconsLucide, nodeIMgsLucide } from "../../../../utils"; -import { undoRedoContext } from "../../../../contexts/undoRedoContext"; +import KnowledgeSelect from "@/components/bs-comp/selectComponent/knowledge"; export default function ParameterComponent({ left, @@ -61,7 +61,7 @@ export default function ParameterComponent({ const updateNodeInternals = useUpdateNodeInternals(); const [position, setPosition] = useState(0); const { closePopUp } = useContext(PopUpContext); - const { setTabsState, flow, setFlow } = useContext(TabsContext); + const { setTabsState, flow, version } = useContext(TabsContext); const groupedEdge = useRef(null); // 用yu过滤菜单的数据 @@ -91,7 +91,7 @@ export default function ParameterComponent({ }, [id, data, reactFlowInstance]) // milvus 组件,知识库不为空是 embbeding取消必填限制 useEffect(() => { - const {embedding, index_name, collection_name, connection_args} = data.node.template + const { embedding, index_name, collection_name, connection_args } = data.node.template if ((index_name || collection_name) && embedding) { const hidden = disabled ? false : !!(collection_name || index_name).value data.node.template.embedding.required = !hidden @@ -101,7 +101,7 @@ export default function ParameterComponent({ } }, [data, disabled]) const handleRemoveMilvusEmbeddingEdge = (nodeId) => { - const edges = reactFlowInstance.getEdges().filter(edge => edge.targetHandle.indexOf('Embeddings|embedding|'+nodeId) === -1) + const edges = reactFlowInstance.getEdges().filter(edge => edge.targetHandle.indexOf('Embeddings|embedding|' + nodeId) === -1) reactFlowInstance.setEdges(edges) } const [myData, setMyData] = useState(useContext(typesContext).data); @@ -219,7 +219,6 @@ export default function ParameterComponent({ }} /> */} - {getNodeNames()[item.family] ?? "Other"}{" "} @@ -268,34 +267,6 @@ export default function ParameterComponent({ } }, [tooltipTitle]); - - // 记录快照 - const {takeSnapshot} = useContext(undoRedoContext); - const { types, deleteNode } = useContext(typesContext); -// const onNodeDragStart: NodeDragHandler = useCallback(() => { -// // 👇 make dragging a node undoable -// takeSnapshot(); -// // 👉 you can place your event handlers here -// }, [takeSnapshot]); - const onMouseDownColor = useCallback(() => { - // console.log(nodeColorsP) - // console.log(data,color,nodeColorsP,nodeColorsP[types[data.type]]); - // const type = types[data.type]; - // Object.keys(nodeColors).forEach(element => { - // if(element != type){ - // nodeColorsP[element] = "#000000" - // } - // }); - // console.log(nodeColors); - // takeSnapshot(); - - // data.node.display_name = "1"; - // console.log(flow) - },[takeSnapshot]); - // useEffect(() => { - // takeSnapshot(); - // }, [nodeColors, nodeColorsP]); - return (
- ) : ( - - - isValidConnection(connection, reactFlowInstance) - } - onConnect={(params) => console.log('handle onConnect', params)} - className={classNames( - left ? "-ml-0.5 " : "-mr-0.5 ", - "h-3 w-3 rounded-full border-2 bg-background" - )} - onMouseDown={onMouseDownColor} - style={{ - borderColor: color, - top: position, - }} - > - - )} + !optionalHandle ? (<>) + : ( + + + isValidConnection(connection, reactFlowInstance) + } + className={classNames( + left ? "-ml-0.5 " : "-mr-0.5 ", + "h-3 w-3 rounded-full border-2 bg-background" + )} + style={{ + borderColor: color, + top: position, + }} + > + + )} {/* 左侧input输入项 */} {!data.node.template[name] ? null : left === true && @@ -390,10 +360,6 @@ export default function ParameterComponent({ ) : ['index_name', 'collection_name'].includes(name) ? ( // 知识库选择 { - data.node = nodeClass; - }} - nodeClass={data.node} disabled={disabled} id={data.node.template[name].collection_id ?? ""} value={data.node.template[name].value ?? ""} @@ -410,6 +376,35 @@ export default function ParameterComponent({ /> )}
+ ) : left === true && type === "knowledge_one" ? ( + // 单选知识库 +
+ { handleOnNewLibValue(val, id); val && handleRemoveMilvusEmbeddingEdge(data.id) }} + onChange={() => { }} + /> +
+ ) : left === true && type === "knowledge_list" ? ( + // 多选知识库 +
+ ({ + label: item.value, + value: item.key, + })) || []} + onChange={(vals) => { + handleOnNewValue(vals.map(v => ({ + key: v.value, + value: v.label + }))) + }} + /> +
) : left === true && type === "bool" ? (
{/* switch */} @@ -480,7 +475,6 @@ export default function ParameterComponent({ { - console.log(nodeClass) if (reactFlowInstance) { reactFlowInstance.setNodes((nds) => nds.map((nd) => { @@ -548,7 +542,7 @@ export default function ParameterComponent({
) : left === true && type === "variable" ? (
- { + { data.node!.template[name].value = newValue; handleOnNewValue(newValue); }} /> diff --git a/src/assets/.DS_Store b/src/assets/.DS_Store index 687a95a..7ae5f5a 100644 Binary files a/src/assets/.DS_Store and b/src/assets/.DS_Store differ diff --git a/src/assets/Login/.DS_Store b/src/assets/Login/.DS_Store index 1d11d77..7aff727 100644 Binary files a/src/assets/Login/.DS_Store and b/src/assets/Login/.DS_Store differ diff --git a/src/assets/Login/reset.png b/src/assets/Login/reset.png new file mode 100644 index 0000000..4e38654 Binary files /dev/null and b/src/assets/Login/reset.png differ diff --git a/src/assets/nav/.DS_Store b/src/assets/nav/.DS_Store index 35e6090..4170852 100644 Binary files a/src/assets/nav/.DS_Store and b/src/assets/nav/.DS_Store differ diff --git a/src/assets/nav/admin.png b/src/assets/nav/admin.png new file mode 100644 index 0000000..917a984 Binary files /dev/null and b/src/assets/nav/admin.png differ diff --git a/src/assets/nav/admin1.png b/src/assets/nav/admin1.png new file mode 100644 index 0000000..74057f1 Binary files /dev/null and b/src/assets/nav/admin1.png differ diff --git a/src/assets/nav/icon1-active.png b/src/assets/nav/icon1-active.png index f519402..549789d 100644 Binary files a/src/assets/nav/icon1-active.png and b/src/assets/nav/icon1-active.png differ diff --git a/src/assets/nav/icon1.png b/src/assets/nav/icon1.png index a1d3787..c1c1088 100644 Binary files a/src/assets/nav/icon1.png and b/src/assets/nav/icon1.png differ diff --git a/src/assets/nav/icon2-active.png b/src/assets/nav/icon2-active.png index fa7f990..d4e974b 100644 Binary files a/src/assets/nav/icon2-active.png and b/src/assets/nav/icon2-active.png differ diff --git a/src/assets/nav/icon2.png b/src/assets/nav/icon2.png index 758b09c..8cce748 100644 Binary files a/src/assets/nav/icon2.png and b/src/assets/nav/icon2.png differ diff --git a/src/assets/nav/icon3-active.png b/src/assets/nav/icon3-active.png index d8a44e0..30c2ea6 100644 Binary files a/src/assets/nav/icon3-active.png and b/src/assets/nav/icon3-active.png differ diff --git a/src/assets/nav/icon3.png b/src/assets/nav/icon3.png index d08d41a..05af796 100644 Binary files a/src/assets/nav/icon3.png and b/src/assets/nav/icon3.png differ diff --git a/src/assets/nav/icon4-active.png b/src/assets/nav/icon4-active.png index 678fb02..9a4308f 100644 Binary files a/src/assets/nav/icon4-active.png and b/src/assets/nav/icon4-active.png differ diff --git a/src/assets/nav/icon4.png b/src/assets/nav/icon4.png index 4932ca5..fd899f2 100644 Binary files a/src/assets/nav/icon4.png and b/src/assets/nav/icon4.png differ diff --git a/src/assets/nav/icon5-active.png b/src/assets/nav/icon5-active.png index d175730..a72d2c8 100644 Binary files a/src/assets/nav/icon5-active.png and b/src/assets/nav/icon5-active.png differ diff --git a/src/assets/nav/icon5.png b/src/assets/nav/icon5.png index f9f1e92..3c471bc 100644 Binary files a/src/assets/nav/icon5.png and b/src/assets/nav/icon5.png differ diff --git a/src/assets/nav/icon6-active.png b/src/assets/nav/icon6-active.png new file mode 100644 index 0000000..53aed60 Binary files /dev/null and b/src/assets/nav/icon6-active.png differ diff --git a/src/assets/nav/icon6.png b/src/assets/nav/icon6.png new file mode 100644 index 0000000..c344f1a Binary files /dev/null and b/src/assets/nav/icon6.png differ diff --git a/src/assets/nav/icon7-active.png b/src/assets/nav/icon7-active.png new file mode 100644 index 0000000..40dada3 Binary files /dev/null and b/src/assets/nav/icon7-active.png differ diff --git a/src/assets/nav/icon7.png b/src/assets/nav/icon7.png new file mode 100644 index 0000000..34a04a7 Binary files /dev/null and b/src/assets/nav/icon7.png differ diff --git a/src/assets/nav/mima.png b/src/assets/nav/mima.png new file mode 100644 index 0000000..0d13849 Binary files /dev/null and b/src/assets/nav/mima.png differ diff --git a/src/assets/nav/tuichu.png b/src/assets/nav/tuichu.png new file mode 100644 index 0000000..87bcbd4 Binary files /dev/null and b/src/assets/nav/tuichu.png differ diff --git a/src/components/.DS_Store b/src/components/.DS_Store index 9f9853f..9e5deb6 100644 Binary files a/src/components/.DS_Store and b/src/components/.DS_Store differ diff --git a/src/components/VariablesComponent/index.tsx b/src/components/VariablesComponent/index.tsx index 149e8c4..ac935c6 100644 --- a/src/components/VariablesComponent/index.tsx +++ b/src/components/VariablesComponent/index.tsx @@ -17,7 +17,8 @@ import { captureAndAlertRequestErrorHoc } from "../../controllers/request"; * 通过子组件VarDialog编辑每一项 */ -export default function VariablesComponent({ nodeId, flowId, onChange }: { +export default function VariablesComponent({vid, nodeId, flowId, onChange }: { + vid: number nodeId: string flowId: string onChange: (val: any) => void @@ -27,11 +28,12 @@ export default function VariablesComponent({ nodeId, flowId, onChange }: { useEffect(() => { // api nodeId -> items - flowId && getVariablesApi({ + flowId && vid && getVariablesApi({ + version_id: vid, flow_id: flowId, node_id: nodeId }).then(arr => setItems(arr)) - }, [flowId]) + }, [flowId, vid]) const { openPopUp, closePopUp } = useContext(PopUpContext); const { setErrorData } = useContext(alertContext); @@ -60,6 +62,7 @@ export default function VariablesComponent({ nodeId, flowId, onChange }: { const param: any = { "flow_id": flowId, "node_id": nodeId, + version_id: vid, "variable_name": _item.name, "value_type": Number(_item.type === VariableType.Select) + 1, "value": _item.type === VariableType.Text ? _item.maxLength : _item.options.map(el => el.value).join(',') @@ -68,7 +71,7 @@ export default function VariablesComponent({ nodeId, flowId, onChange }: { param.id = _item.id } captureAndAlertRequestErrorHoc(saveVariableApi(param).then(res => { - const _items = items.map(item => item.id === _item.id ? { ..._item, id: res.id } : item) + const _items = items.map(item => item.id === _item.id ? { ..._item, id: res.id, update: true } : item) // const hasValue = _items.find(item => item.name) // 保存时 id传出去保存,用来校验必填项 onChange(_items.map(el => el.name)) diff --git a/src/components/bs-comp/.DS_Store b/src/components/bs-comp/.DS_Store new file mode 100644 index 0000000..158edbb Binary files /dev/null and b/src/components/bs-comp/.DS_Store differ diff --git a/src/components/bs-comp/chatComponent/ChatInput.tsx b/src/components/bs-comp/chatComponent/ChatInput.tsx index e1dbf16..4956ee7 100644 --- a/src/components/bs-comp/chatComponent/ChatInput.tsx +++ b/src/components/bs-comp/chatComponent/ChatInput.tsx @@ -1,16 +1,18 @@ +import { ClearIcon } from "@/components/bs-icons/clear"; import { FormIcon } from "@/components/bs-icons/form"; import { SendIcon } from "@/components/bs-icons/send"; +import { Button } from "@/components/bs-ui/button"; import { Textarea } from "@/components/bs-ui/input"; import { useToast } from "@/components/bs-ui/toast/use-toast"; import { locationContext } from "@/contexts/locationContext"; +import { PauseIcon } from "@radix-ui/react-icons"; import { useContext, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; -import { useMessageStore } from "./messageStore"; import GuideQuestions from "./GuideQuestions"; -import { ClearIcon } from "@/components/bs-icons/clear"; +import { useMessageStore } from "./messageStore"; +import { formatDate } from "@/util/utils"; +import { StopIcon } from "@radix-ui/react-icons"; import duihua_send from "../../../assets/chat/duihua-send.png"; -import { Button } from "@/components/bs-ui/button"; -import { StopCircle } from "lucide-react"; export default function ChatInput({ clear, form, questions, inputForm, wsUrl, onBeforSend }) { const { toast } = useToast() @@ -21,11 +23,15 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on const [showWhenLocked, setShowWhenLocked] = useState(false) // 强制开启表单按钮,不限制于input锁定 const [inputLock, setInputLock] = useState({ locked: false, reason: '' }) - const { messages, chatId, createSendMsg, createWsMsg, updateCurrentMessage, destory, setShowGuideQuestion } = useMessageStore() + const { messages, hisMessages, chatId, createSendMsg, createWsMsg, updateCurrentMessage, destory, setShowGuideQuestion } = useMessageStore() const currentChatIdRef = useRef(null) const inputRef = useRef(null) + const continueRef = useRef(false) // 停止状态 - const [isStop, setIsStop] = useState(true) + const [stop, setStop] = useState({ + show: false, + disable: false + }) /** * 记录会话切换状态,等待消息加载完成时,控制表单在新会话自动展开 */ @@ -36,16 +42,17 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on if (changeChatedRef.current) { changeChatedRef.current = false // 新建的 form 技能,弹出窗口并锁定 input - if (form && messages.length === 0) { + if (form && messages.length === 0 && hisMessages.length === 0) { setInputLock({ locked: true, reason: '' }) setFormShow(true) setShowWhenLocked(true) } } - }, [messages]) + }, [messages, hisMessages]) useEffect(() => { if (!chatId) return + continueRef.current = false setInputLock({ locked: false, reason: '' }) // console.log('message chatid', messages, form, chatId); setShowWhenLocked(false) @@ -85,13 +92,14 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on const event = new Event('input', { bubbles: true, cancelable: true }); inputRef.current.value = '' inputRef.current.dispatchEvent(event); // 触发调节input高度 - const [wsMsg, inputKey] = onBeforSend('', value) + const contunue = continueRef.current ? 'continue' : '' + continueRef.current = false + const [wsMsg, inputKey] = onBeforSend(contunue, value) // msg to store createSendMsg(wsMsg.inputs, inputKey) // 锁定 input setInputLock({ locked: true, reason: '' }) await createWebSocket(chatId) - // console.log(wsMsg,inputKey); sendWsMsg(wsMsg) // 滚动聊天到底 @@ -100,15 +108,13 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on messageDom.scrollTop = messageDom.scrollHeight; } } - const stop = async () => { - const [wsMsg] = onBeforSend('', '') - wsMsg.action = "stop" - sendWsMsg(wsMsg) - // console.log(wsMsg); - // sendWsMsg(wsMsg) - } + + const diffRef = useRef(0) const sendWsMsg = async (msg) => { try { + diffRef.current = Date.now() + // console.log('WebSocket send: ' + diffRef.current + ' 毫秒'); + wsRef.current.send(JSON.stringify(msg)) } catch (error) { toast({ @@ -128,14 +134,23 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on return new Promise((res, rej) => { try { + let startTime = Date.now(); const ws = new WebSocket(`${webSocketProtocol}://${wsUrl}&chat_id=${chatId}`) wsRef.current = ws // websocket linsen ws.onopen = () => { + // 记录连接成功的时间 + let endTime = Date.now(); + + // 计算连接建立所需的时间 + let connectionTime = endTime - startTime; + + // console.log('WebSocket 连接建立时间: ' + connectionTime + ' 毫秒'); console.log("WebSocket connection established!"); res('ok') }; ws.onmessage = (event) => { + // console.log(`WebSocket get: ${Date.now()} 毫秒;与send差值${Date.now() - diffRef.current}毫秒`); const data = JSON.parse(event.data); const errorMsg = data.category === 'error' ? data.intermediate_steps : '' // 异常类型处理,提示 @@ -145,13 +160,17 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on handleWsMessage(data) // 群聊@自己时,开启input if (['end', 'end_cover'].includes(data.type) && data.receiver?.is_self) { - setInputLock({ locked: true, reason: '' }) + setInputLock({ locked: false, reason: '' }) + setStop({ show: false, disable: false }) + continueRef.current = true } } ws.onclose = (event) => { wsRef.current = null console.error('链接手动断开 event :>> ', event); - if ([1005, 1008].includes(event.code)) { + setStop({ show: false, disable: false }) + + if ([1005, 1008, 1009].includes(event.code)) { console.warn('即将废弃 :>> '); setInputLock({ locked: true, reason: event.reason }) } else { @@ -167,8 +186,8 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on }; ws.onerror = (ev) => { wsRef.current = null + setStop({ show: false, disable: false }) console.error('链接异常error', ev); - setIsStop(true) toast({ title: `${t('chat.networkError')}:`, variant: 'error', @@ -189,15 +208,14 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on // 接受 ws 消息 const handleWsMessage = (data) => { - // console.log(data) if (Array.isArray(data) && data.length) return - if (data.type === "begin") { - setIsStop(false) - }else if (data.type === 'start') { + if (data.type === 'start') { + // 非continue时,展示stop按钮 + !continueRef.current && setStop({ show: true, disable: false }) createWsMsg(data) } else if (data.type === 'stream') { + //@ts-ignore updateCurrentMessage({ - flow_id: data.flow_id, chat_id: data.chat_id, message: data.message, thought: data.intermediate_steps @@ -209,16 +227,16 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on thought: data.intermediate_steps || '', messageId: data.message_id, noAccess: false, - liked: 0 + liked: 0, + update_time: formatDate(new Date(), 'yyyy-MM-ddTHH:mm:ss') }, data.type === 'end_cover') } else if (data.type === "close") { - setIsStop(true) + setStop({ show: false, disable: false }) setInputLock({ locked: false, reason: '' }) } - } - // 监听重发消息事件 + // 触发发送消息事件(重试、表单) useEffect(() => { const handleCustomEvent = (e) => { if (!showWhenLocked && inputLock.locked) return console.error('弹窗已锁定,消息无法发送') @@ -247,12 +265,12 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on // setInputEmpty(textarea.value.trim() === '') } - return
-
+ return
+
{/* form */} { formShow &&
-
+
{inputForm}
@@ -265,34 +283,51 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on onClick={handleClickGuideWord} /> {/* clear */} - {/*
+
{ clear &&
{ !inputLock.locked && destory() }} - >
+ >
} -
*/} - {/* form */} +
+ {/* form switch */}
{ form &&
(showWhenLocked || !inputLock.locked) && setFormShow(!formShow)} - >
+ >
}
{/* send */}
-
{ !inputLock.locked && handleSendClick() }} - style={{borderRadius:"20px"}} - > - {/* */} - -
+ {stop.show ? +
{ + if (stop.disable) return + setStop({ show: true, disable: true }); + sendWsMsg({ "action": "stop" }); + }}> + {/* */} + {/* { + if (stop.disable) return + setStop({ show: true, disable: true }); + sendWsMsg({ "action": "stop" }); + }} /> */} +
+
+ :
{ !inputLock.locked && handleSendClick() }} style={{borderRadius:"20px"}}> + {/* */} + +
+ }
{/* question */} - {!isStop &&
- -
} - - {/*

内容由AI生成,仅供参考

*/}

{appConfig.dialogTips}

-}; +}; \ No newline at end of file diff --git a/src/components/bs-comp/chatComponent/FileBs.tsx b/src/components/bs-comp/chatComponent/FileBs.tsx index faa0e80..7c8717b 100644 --- a/src/components/bs-comp/chatComponent/FileBs.tsx +++ b/src/components/bs-comp/chatComponent/FileBs.tsx @@ -36,7 +36,9 @@ export default function FileBs({ data,flow_type }) {
{data.sender &&

{data.sender}

}
- {data.flow_id && } + {/* {data.flow_id && } */} + {flow_type.id && } + {/*
*/}
{ }, flow_type, onSou const handleCopyMessage = () => { copyText(messageRef.current) } - // console.log(data) const chatId = useMessageStore(state => state.chatId) return
@@ -94,7 +93,7 @@ export default function MessageBs({ data, onUnlike = () => { }, flow_type, onSou {/* {(data.flow_id == "06b1d374-ba97-46e6-8782-c56dec8dcc17" || data.flow_id == "ed8e21f6-9757-43d0-b076-8c6e81bb0580") && } {data.flow_id == "ca214b41-2b73-4585-b172-bf1e546cf6ec" && } {(data.flow_id != "06b1d374-ba97-46e6-8782-c56dec8dcc17" && data.flow_id != "ed8e21f6-9757-43d0-b076-8c6e81bb0580" && data.flow_id != "ca214b41-2b73-4585-b172-bf1e546cf6ec") && } */} - {data.flow_id && } + {flow_type && flow_type.id && }
{/*
diff --git a/src/components/bs-comp/chatComponent/MessagePanne.tsx b/src/components/bs-comp/chatComponent/MessagePanne.tsx index 0797967..1416b98 100644 --- a/src/components/bs-comp/chatComponent/MessagePanne.tsx +++ b/src/components/bs-comp/chatComponent/MessagePanne.tsx @@ -10,7 +10,7 @@ import RunLog from "./RunLog"; import Separator from "./Separator"; import { useMessageStore } from "./messageStore"; -export default function MessagePanne({ useName, guideWord, loadMore, flow_type }) { +export default function MessagePanne({logo, useName, guideWord, loadMore, flow_type }) { const { t } = useTranslation() const { chatId, messages } = useMessageStore() @@ -75,7 +75,6 @@ export default function MessagePanne({ useName, guideWord, loadMore, flow_type } } else if (msg.thought) { type = 'system' } - // console.log(type) switch (type) { case 'user': return ; diff --git a/src/components/bs-comp/chatComponent/index.tsx b/src/components/bs-comp/chatComponent/index.tsx index 92b024b..40b0c9d 100644 --- a/src/components/bs-comp/chatComponent/index.tsx +++ b/src/components/bs-comp/chatComponent/index.tsx @@ -1,9 +1,22 @@ import ChatInput from "./ChatInput"; import MessagePanne from "./MessagePanne"; -export default function ChatComponent({ clear = false, questions = [], form = false, useName, inputForm = null, guideWord, wsUrl, onBeforSend, type, loadMore = () => { } }) { +export default function ChatComponent({ + stop = false, + logo = '', + clear = false, + questions = [], + form = false, + useName, + inputForm = null, + guideWord, + wsUrl, + onBeforSend, + type, + loadMore = () => { } +}) { return
- +
}; diff --git a/src/components/bs-comp/chatComponent/messageStore.ts b/src/components/bs-comp/chatComponent/messageStore.ts index 2b13410..28ee300 100644 --- a/src/components/bs-comp/chatComponent/messageStore.ts +++ b/src/components/bs-comp/chatComponent/messageStore.ts @@ -4,6 +4,7 @@ import { MessageDB, getChatHistory } from '@/controllers/API' import { ChatMessageType } from '@/types/chat' import { cloneDeep } from 'lodash' import { create } from 'zustand' +import { formatDate } from '@/util/utils'; /** * 会话消息管理 @@ -19,6 +20,8 @@ type State = { /** 没有更多历史纪录 */ historyEnd: boolean, messages: ChatMessageType[] + /** 历史回话独立存储 */ + hisMessages: ChatMessageType[] /** * 控制引导问题的显示状态 */ @@ -26,8 +29,8 @@ type State = { } type Actions = { - loadHistoryMsg: (flowid: string, chatId: string, flow_type: string) => Promise; - loadMoreHistoryMsg: (flowid: string, flow_type: string) => Promise; + loadHistoryMsg: (flowid: string, chatId: string, data: { appendHistory: boolean, lastMsg: string }, flow_type: string) => Promise; + loadMoreHistoryMsg: (flowid: string, appendHistory: boolean, flow_type: string) => Promise; destory: () => void; createSendMsg: (inputs: any, inputKey?: string) => void; createWsMsg: (data: any) => void; @@ -38,6 +41,7 @@ type Actions = { insetSystemMsg: (text: string) => void; insetBsMsg: (text: string) => void; setShowGuideQuestion: (text: boolean) => void; + clearMsgs: () => void; } @@ -76,18 +80,32 @@ export const useMessageStore = create((set, get) => ({ running: false, chatId: '', messages: [], + hisMessages: [], historyEnd: false, showGuideQuestion: false, setShowGuideQuestion(bln: boolean) { set({ showGuideQuestion: bln }) }, - async loadHistoryMsg(flowid, chatId, flow_type) { + async loadHistoryMsg(flowid, chatId, { appendHistory, lastMsg }, flow_type) { const res = await getChatHistory(flowid, chatId, 30, 0, flow_type) const msgs = handleHistoryMsg(res) currentChatId = chatId - set({ historyEnd: false, messages: msgs.reverse() }) + const hisMessages = appendHistory ? [] : msgs.reverse() + if (hisMessages.length) { + hisMessages.push({ + ...bsMsgItem, + id: Math.random() * 1000000, + category: 'divider', + message: lastMsg, + }) + } + set({ + historyEnd: false, + messages: appendHistory ? msgs.reverse() : [], + hisMessages + }) }, - async loadMoreHistoryMsg(flowid, flow_type) { + async loadMoreHistoryMsg(flowid, appendHistory, flow_type) { if (get().running) return // 会话进行中禁止加载more历史 if (get().historyEnd) return // 没有更多历史纪录 const chatId = get().chatId @@ -101,11 +119,16 @@ export const useMessageStore = create((set, get) => ({ } const msgs = handleHistoryMsg(res) if (msgs.length) { - set({ messages: [...msgs.reverse(), ...prevMsgs] }) + set({ [appendHistory ? 'messages' : 'hisMessages']: [...msgs.reverse(), ...prevMsgs] }) } else { set({ historyEnd: true }) } }, + clearMsgs() { + setTimeout(() => { + set({ hisMessages: [], messages: [], historyEnd: true }) + }, 0); + }, destory() { set({ chatId: '', messages: [] }) }, @@ -122,7 +145,8 @@ export const useMessageStore = create((set, get) => ({ category: '', files: [], end: false, - user_name: "" + user_name: "", + update_time: formatDate(new Date(), 'yyyy-MM-ddTHH:mm:ss') }] })) }, @@ -151,13 +175,20 @@ export const useMessageStore = create((set, get) => ({ // if (wsdata.end) { // debugger // } - console.log('change updateCurrentMessage'); + // console.log('change updateCurrentMessage'); const messages = get().messages const isRunLog = runLogsTypes.includes(wsdata.category); // run log类型存在嵌套情况,使用 extra 匹配 currentMessage; 否则取最近 - const currentMessageIndex = isRunLog ? - messages.findLastIndex((msg) => msg.extra === wsdata.extra) - : messages.findLastIndex((msg) => !runLogsTypes.includes(msg.category)) + let currentMessageIndex = 0 + for (let i = messages.length - 1; i >= 0; i--) { + if (isRunLog && messages[i].extra === wsdata.extra) { + currentMessageIndex = i; + break; + } else if (!isRunLog && !runLogsTypes.includes(messages[i].category)) { + currentMessageIndex = i; + break; + } + } const currentMessage = messages[currentMessageIndex] const newCurrentMessage = { @@ -166,13 +197,17 @@ export const useMessageStore = create((set, get) => ({ id: isRunLog ? wsdata.extra : wsdata.messageId, // 每条消息必唯一 message: isRunLog ? JSON.parse(wsdata.message) : currentMessage.message + wsdata.message, thought: currentMessage.thought + (wsdata.thought ? `${wsdata.thought}\n` : ''), - files: wsdata.files || null, + files: wsdata.files || [], category: wsdata.category || '', source: wsdata.source } + // 无id补上(如文件解析完成消息,后端无返回messageid) + if (!newCurrentMessage.id) { + newCurrentMessage.id = Math.random() * 1000000 + // console.log('msg:', newCurrentMessage); + } messages[currentMessageIndex] = newCurrentMessage - // console.log(messages,currentMessageIndex,newCurrentMessage) // 会话特殊处理,兼容后端的缺陷 if (!isRunLog) { // start - end 之间没有内容删除load diff --git a/src/components/bs-comp/loadMore/index.tsx b/src/components/bs-comp/loadMore/index.tsx new file mode 100644 index 0000000..35e866a --- /dev/null +++ b/src/components/bs-comp/loadMore/index.tsx @@ -0,0 +1,24 @@ +import { useEffect, useRef } from "react"; + +export default function LoadMore({ onScrollLoad }) { + // scroll load + const footerRef = useRef(null) + useEffect(function () { + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + onScrollLoad() + } + }); + }, { + // root: null, // 视口 + rootMargin: '0px', // 视口的边距 + threshold: 0.1 // 目标元素超过视口的10%即触发回调 + }); + + observer.observe(footerRef.current); + return () => footerRef.current && observer.unobserve(footerRef.current); + }, []) + + return
+}; diff --git a/src/components/bs-comp/selectComponent/Users.tsx b/src/components/bs-comp/selectComponent/Users.tsx new file mode 100644 index 0000000..655e77c --- /dev/null +++ b/src/components/bs-comp/selectComponent/Users.tsx @@ -0,0 +1,48 @@ +import MultiSelect from "@/components/bs-ui/select/multi"; +import { getUsersApi } from "@/controllers/API/user"; +import { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; + +export default function UsersSelect({ multiple = false, lockedValues = [], value, disabled = false, onChange, children }: + { multiple?: boolean, lockedValues?: any[], value: any, disabled?: boolean, onChange: (a: any) => any, children?: (fun: any) => React.ReactNode }) { + + const { t } = useTranslation() + const [options, setOptions] = useState([]); + const originOptionsRef = useRef([]) + + const pageRef = useRef(1) + const reload = (page, name) => { + getUsersApi({ page, pageSize: 40, name }).then(res => { + pageRef.current = page + originOptionsRef.current = res.data + const opts = res.data.map(el => ({ label: el.user_name, value: el.user_id })) + setOptions(_ops => page > 1 ? [..._ops, ...opts] : opts) + }) + } + + useEffect(() => { + reload(1, '') + }, []) + + // 加载更多 + const loadMore = (name) => { + reload(pageRef.current + 1, name) + } + + return reload(1, '')} + onSearch={(val) => reload(1, val)} + onScrollLoad={(val) => loadMore(val)} + > + {children?.(reload)} + +}; diff --git a/src/components/bs-comp/selectComponent/knowledge.tsx b/src/components/bs-comp/selectComponent/knowledge.tsx new file mode 100644 index 0000000..5b22111 --- /dev/null +++ b/src/components/bs-comp/selectComponent/knowledge.tsx @@ -0,0 +1,51 @@ +import MultiSelect from "@/components/bs-ui/select/multi"; +import { readFileLibDatabase } from "@/controllers/API"; +import { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; + +export default function KnowledgeSelect({ multiple = false, value, disabled = false, onChange, children }: + { multiple?: boolean, value: any, disabled?: boolean, onChange: (a: any) => any, children?: (fun: any) => React.ReactNode }) { + + const { t } = useTranslation() + const [options, setOptions] = useState([]); + const originOptionsRef = useRef([]) + + const pageRef = useRef(1) + const reload = (page, name) => { + readFileLibDatabase(page, 60, name).then(res => { + pageRef.current = page + originOptionsRef.current = res.data + const opts = res.data.map(el => ({ label: el.name, value: el.id })) + setOptions(_ops => page > 1 ? [..._ops, ...opts] : opts) + }) + } + + useEffect(() => { + reload(1, '') + }, []) + + // const handleChange = (res) => { + // // id => obj + // onChange(res.map(el => originOptionsRef.current.find(el2 => el2.id === el))) + // } + + // 加载更多 + const loadMore = (name) => { + reload(pageRef.current + 1, name) + } + + return reload(1, '')} + onSearch={(val) => reload(1, val)} + onScrollLoad={(val) => loadMore(val)} + > + {children?.(reload)} + +}; diff --git a/src/components/bs-comp/sheets/SkillChatSheet.tsx b/src/components/bs-comp/sheets/SkillChatSheet.tsx index 8af1ac7..cf14c32 100644 --- a/src/components/bs-comp/sheets/SkillChatSheet.tsx +++ b/src/components/bs-comp/sheets/SkillChatSheet.tsx @@ -19,6 +19,8 @@ import zidingyi1 from "../../../assets/npc/zidingyi1.png"; import zidingyi2 from "../../../assets/npc/zidingyi2.png"; import npcIcon from "../../../assets/npc/npcIcon.png"; import nengliIcon from "../../../assets/npc/nengliIcon.png"; +import { useDebounce } from "@/util/hook"; +import LoadMore from "../loadMore"; export default function SkillChatSheet({ children, onSelect }) { const [open, setOpen] = useState(false) @@ -30,34 +32,45 @@ export default function SkillChatSheet({ children, onSelect }) { const [keyword, setKeyword] = useState(' ') const allDataRef = useRef([]) - useEffect(() => { - open && getChatOnlineApi().then(res => { - allDataRef.current = res - setKeyword('') + const pageRef = useRef(1) + const searchRef = useRef('') + const [options, setOptions] = useState([]) + + const loadData = (more = false) => { + open && getChatOnlineApi(pageRef.current, searchRef.current).then(res => { + setOptions(opts => more ? [...opts, ...res] : res) }) + } + const debounceLoad = useDebounce(loadData, 600, false) + + useEffect(() => { + // open && getChatOnlineApi().then(res => { + // allDataRef.current = res + // setKeyword('') + // }) // setKeyword(' ') + pageRef.current = 1 + searchRef.current = '' + loadData() }, [open]) - const options = useMemo(() => { - return allDataRef.current.filter(el => el.name.toLowerCase().includes(keyword.toLowerCase())) - }, [keyword]) + // const options = useMemo(() => { + // return allDataRef.current.filter(el => el.name.toLowerCase().includes(keyword.toLowerCase())) + // }, [keyword]) + + const handleSearch = (e) => { + pageRef.current = 1 + searchRef.current = e.target.value + debounceLoad() + } + + const handleLoadMore = () => { + pageRef.current++ + loadData(true) + } const render = (item: any) => ( { onSelect(item); setOpen(false) }}> - {/* */} - {/* -
{item.name}
-
{item.name}
-
*/} - {/* onSelect(item)}> - - -
- {item.name} -
- {item.description} -
-
*/}
@@ -68,16 +81,11 @@ export default function SkillChatSheet({ children, onSelect }) {
- {/* {(item.id == "06b1d374-ba97-46e6-8782-c56dec8dcc17" || item.id == "ed8e21f6-9757-43d0-b076-8c6e81bb0580") && } - {item.id == "ca214b41-2b73-4585-b172-bf1e546cf6ec" && } - {(item.id != "06b1d374-ba97-46e6-8782-c56dec8dcc17" && item.id != "ed8e21f6-9757-43d0-b076-8c6e81bb0580" && item.id != "ca214b41-2b73-4585-b172-bf1e546cf6ec") && } */} - {/* */}

{item.name}

- {/*
绘画类
-
绘画类
*/} +
@@ -100,31 +108,12 @@ export default function SkillChatSheet({ children, onSelect }) {
选择对话 选择一个您想使用的上线NPC或能力 - setKeyword(e.target.value)} /> + {/* setKeyword(e.target.value)} /> */} +
- {/* { - options.length ? options.map((flow, i) => ( - - {flow.flow_type === 'flow' ? '技能' : 'NPC'} - - } - onClick={() => { onSelect(flow); setOpen(false) }} - /> - )) :
-

{t('build.empty')}

- -
- } */} +
diff --git a/src/components/bs-icons/.DS_Store b/src/components/bs-icons/.DS_Store new file mode 100644 index 0000000..cdbb4d8 Binary files /dev/null and b/src/components/bs-icons/.DS_Store differ diff --git a/src/components/bs-icons/del/Del.svg b/src/components/bs-icons/del/Del.svg index 76aafca..8749d7c 100644 --- a/src/components/bs-icons/del/Del.svg +++ b/src/components/bs-icons/del/Del.svg @@ -1,11 +1,11 @@ - - + + + fill="#666" /> \ No newline at end of file diff --git a/src/components/bs-icons/del/index.tsx b/src/components/bs-icons/del/index.tsx index 9311cb6..d5ba290 100644 --- a/src/components/bs-icons/del/index.tsx +++ b/src/components/bs-icons/del/index.tsx @@ -5,5 +5,5 @@ export const DelIcon = forwardRef< SVGSVGElement & { className: any }, React.PropsWithChildren<{ className?: string }> >((props, ref) => { - return ; + return ; }); diff --git a/src/components/bs-icons/down/DropDown.svg b/src/components/bs-icons/down/DropDown.svg new file mode 100644 index 0000000..520177b --- /dev/null +++ b/src/components/bs-icons/down/DropDown.svg @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/src/components/bs-icons/down/index.tsx b/src/components/bs-icons/down/index.tsx new file mode 100644 index 0000000..432f3fe --- /dev/null +++ b/src/components/bs-icons/down/index.tsx @@ -0,0 +1,9 @@ +import React, { forwardRef } from "react"; +import { ReactComponent as DropDown } from "./DropDown.svg"; + +export const DropDownIcon = forwardRef< + SVGSVGElement & { className: any }, + React.PropsWithChildren<{ className?: string }> +>((props, ref) => { + return ; +}); \ No newline at end of file diff --git a/src/components/bs-icons/en/En.svg b/src/components/bs-icons/en/En.svg index e7d4ea3..f0cad0e 100644 --- a/src/components/bs-icons/en/En.svg +++ b/src/components/bs-icons/en/En.svg @@ -1,3 +1,3 @@ - + diff --git a/src/components/bs-icons/filter/Filter.svg b/src/components/bs-icons/filter/Filter.svg new file mode 100644 index 0000000..d4e2e2f --- /dev/null +++ b/src/components/bs-icons/filter/Filter.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/src/components/bs-icons/filter/index.tsx b/src/components/bs-icons/filter/index.tsx new file mode 100644 index 0000000..de5c392 --- /dev/null +++ b/src/components/bs-icons/filter/index.tsx @@ -0,0 +1,9 @@ +import React, { forwardRef } from "react"; +import { ReactComponent as Filter } from "./Filter.svg"; + +export const FilterIcon = forwardRef< + SVGSVGElement & { className: any }, + React.PropsWithChildren<{ className?: string }> +>((props, ref) => { + return ; +}); \ No newline at end of file diff --git a/src/components/bs-icons/index.ts b/src/components/bs-icons/index.ts new file mode 100644 index 0000000..8fcf52f --- /dev/null +++ b/src/components/bs-icons/index.ts @@ -0,0 +1,40 @@ +export { AddToIcon } from './addTo'; +export { ApplicationIcon } from './menu/application'; +export { AssistantIcon } from './assistant'; +export { AvatarIcon } from './avatar'; +export { BookOpenIcon } from './bookOpen'; +export { ClearIcon } from './clear'; +export { DelIcon } from './del'; +export { EnIcon } from './en'; +export { FilterIcon } from './filter'; +export { FormIcon } from './form'; +export { GithubIcon } from './github'; +export { GoIcon } from './go'; +export { KnowledgeIcon } from './knowledge'; +export { LoadIcon } from './loading'; +export { ModelIcon } from './menu/model'; +export { MoonIcon } from './moon'; +export { MoveOneIcon } from './moveOne'; +export { NewApplicationIcon } from './newApplication'; +export { WordIcon } from './office'; +export { PlusIcon } from './plus'; +export { PlusBoxIcon } from './plusBox'; +export { QuestionMarkIcon } from './questionMark'; +export { QuitIcon } from './quit'; +export { SaveIcon } from './save'; +export { SearchIcon } from './search'; +export { SendIcon } from './send'; +export { SettingIcon } from './setting'; +export { SkillIcon } from './skill'; +export { SystemIcon } from './menu/system'; +export { TabIcon } from './tab'; +export { TechnologyIcon } from './menu/technology'; +export { ThunmbIcon } from './thumbs'; +export { TipIcon } from './tip'; +export { ToastIcon } from './toast'; +export { ToolIcon } from './tool'; +export { UploadIcon } from './upload'; +export { UserIcon } from './user'; +export { LogIcon } from './menu/log'; +export { EvaluatingIcon } from './menu/evaluation'; +export { DropDownIcon } from './down' diff --git a/src/components/bs-icons/application/Application.svg b/src/components/bs-icons/menu/application/Application.svg similarity index 100% rename from src/components/bs-icons/application/Application.svg rename to src/components/bs-icons/menu/application/Application.svg diff --git a/src/components/bs-icons/application/index.tsx b/src/components/bs-icons/menu/application/index.tsx similarity index 100% rename from src/components/bs-icons/application/index.tsx rename to src/components/bs-icons/menu/application/index.tsx diff --git a/src/components/bs-icons/menu/evaluation/Evaluation.svg b/src/components/bs-icons/menu/evaluation/Evaluation.svg new file mode 100644 index 0000000..5775358 --- /dev/null +++ b/src/components/bs-icons/menu/evaluation/Evaluation.svg @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/src/components/bs-icons/menu/evaluation/index.tsx b/src/components/bs-icons/menu/evaluation/index.tsx new file mode 100644 index 0000000..c84605f --- /dev/null +++ b/src/components/bs-icons/menu/evaluation/index.tsx @@ -0,0 +1,9 @@ +import React, { forwardRef } from "react"; +import { ReactComponent as Icon } from "./Evaluation.svg"; + +export const EvaluatingIcon = forwardRef< + SVGSVGElement & { className: any }, + React.PropsWithChildren<{ className?: string }> +>(({ className, ...props }, ref) => { + return ; +}); \ No newline at end of file diff --git a/src/components/bs-icons/menu/log/Log.svg b/src/components/bs-icons/menu/log/Log.svg new file mode 100644 index 0000000..ac0bcf3 --- /dev/null +++ b/src/components/bs-icons/menu/log/Log.svg @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/src/components/bs-icons/menu/log/index.tsx b/src/components/bs-icons/menu/log/index.tsx new file mode 100644 index 0000000..3fa9b18 --- /dev/null +++ b/src/components/bs-icons/menu/log/index.tsx @@ -0,0 +1,9 @@ +import React, { forwardRef } from "react"; +import { ReactComponent as Log } from "./Log.svg"; + +export const LogIcon = forwardRef< + SVGSVGElement & { className: any }, + React.PropsWithChildren<{ className?: string }> +>(({ className, ...props }, ref) => { + return ; +}); \ No newline at end of file diff --git a/src/components/bs-icons/model/Model.svg b/src/components/bs-icons/menu/model/Model.svg similarity index 100% rename from src/components/bs-icons/model/Model.svg rename to src/components/bs-icons/menu/model/Model.svg diff --git a/src/components/bs-icons/model/index.tsx b/src/components/bs-icons/menu/model/index.tsx similarity index 100% rename from src/components/bs-icons/model/index.tsx rename to src/components/bs-icons/menu/model/index.tsx diff --git a/src/components/bs-icons/system/System.svg b/src/components/bs-icons/menu/system/System.svg similarity index 100% rename from src/components/bs-icons/system/System.svg rename to src/components/bs-icons/menu/system/System.svg diff --git a/src/components/bs-icons/system/index.tsx b/src/components/bs-icons/menu/system/index.tsx similarity index 100% rename from src/components/bs-icons/system/index.tsx rename to src/components/bs-icons/menu/system/index.tsx diff --git a/src/components/bs-icons/technology/Technology.svg b/src/components/bs-icons/menu/technology/Technology.svg similarity index 100% rename from src/components/bs-icons/technology/Technology.svg rename to src/components/bs-icons/menu/technology/Technology.svg diff --git a/src/components/bs-icons/technology/index.tsx b/src/components/bs-icons/menu/technology/index.tsx similarity index 100% rename from src/components/bs-icons/technology/index.tsx rename to src/components/bs-icons/menu/technology/index.tsx diff --git a/src/components/bs-icons/office/index.tsx b/src/components/bs-icons/office/index.tsx index 60a88a8..10e6163 100644 --- a/src/components/bs-icons/office/index.tsx +++ b/src/components/bs-icons/office/index.tsx @@ -5,6 +5,6 @@ export const WordIcon = forwardRef< SVGSVGElement & { className: any }, React.PropsWithChildren<{ className?: string }> >(({ className, ...props }, ref) => { - const _className = 'transition text-[#43AFD2] ' + (className || '') + const _className = 'transition text-gray-950 ' + (className || '') return ; }); diff --git a/src/components/bs-icons/plusBox/PlusBox.svg b/src/components/bs-icons/plusBox/PlusBox.svg index bbf3b3e..117bfcc 100644 --- a/src/components/bs-icons/plusBox/PlusBox.svg +++ b/src/components/bs-icons/plusBox/PlusBox.svg @@ -1,10 +1,13 @@ - - - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/src/components/bs-icons/questionMark/index.tsx b/src/components/bs-icons/questionMark/index.tsx index da07354..afed6f1 100644 --- a/src/components/bs-icons/questionMark/index.tsx +++ b/src/components/bs-icons/questionMark/index.tsx @@ -5,6 +5,6 @@ export const QuestionMarkIcon = forwardRef< SVGSVGElement & { className: any }, React.PropsWithChildren<{ className?: string }> >(({ className, ...props }, ref) => { - const _className = 'transition text-gray-950 ' + (className || '') + const _className = 'transition text-[#999] ' + (className || '') return ; }); diff --git a/src/components/bs-icons/quit/Quit-dark.svg b/src/components/bs-icons/quit/Quit-dark.svg deleted file mode 100644 index 37c3cc3..0000000 --- a/src/components/bs-icons/quit/Quit-dark.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/components/bs-icons/quit/Quit.svg b/src/components/bs-icons/quit/Quit.svg index 260f02a..6313b64 100644 --- a/src/components/bs-icons/quit/Quit.svg +++ b/src/components/bs-icons/quit/Quit.svg @@ -1,8 +1,11 @@ - - - - - - - + + + + + + + \ No newline at end of file diff --git a/src/components/bs-icons/quit/index.tsx b/src/components/bs-icons/quit/index.tsx index da09bec..bd198cd 100644 --- a/src/components/bs-icons/quit/index.tsx +++ b/src/components/bs-icons/quit/index.tsx @@ -1,17 +1,9 @@ import React, { forwardRef } from "react"; import { ReactComponent as Quit } from "./Quit.svg"; -import { ReactComponent as QuitDark } from "./Quit-dark.svg"; export const QuitIcon = forwardRef< SVGSVGElement & { className: any }, React.PropsWithChildren<{ className?: string }> ->(({className,...props}, ref) => { - return ; -}); - -export const QuitIconDark = forwardRef< - SVGSVGElement & { className: any }, - React.PropsWithChildren<{ className?: string }> ->(({className,...props}, ref) => { - return ; +>(({ className, ...props }, ref) => { + return ; }); \ No newline at end of file diff --git a/src/components/bs-icons/search/Search.svg b/src/components/bs-icons/search/Search.svg index 8929c0f..469d30f 100644 --- a/src/components/bs-icons/search/Search.svg +++ b/src/components/bs-icons/search/Search.svg @@ -1,8 +1,8 @@ - - + + \ No newline at end of file diff --git a/src/components/bs-icons/thumbs/copy.svg b/src/components/bs-icons/thumbs/copy.svg index a00df83..dd48ab2 100644 --- a/src/components/bs-icons/thumbs/copy.svg +++ b/src/components/bs-icons/thumbs/copy.svg @@ -1,8 +1,8 @@ - + - \ No newline at end of file diff --git a/src/components/bs-icons/thumbs/copyDark.svg b/src/components/bs-icons/thumbs/copyDark.svg deleted file mode 100644 index a917cc5..0000000 --- a/src/components/bs-icons/thumbs/copyDark.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/components/bs-icons/thumbs/index.tsx b/src/components/bs-icons/thumbs/index.tsx index eaa613d..fcfc2fe 100644 --- a/src/components/bs-icons/thumbs/index.tsx +++ b/src/components/bs-icons/thumbs/index.tsx @@ -1,11 +1,8 @@ import React, { forwardRef } from "react"; import { ReactComponent as copy } from "./copy.svg"; -import { ReactComponent as copyDark } from "./copyDark.svg"; import { ReactComponent as like } from "./like.svg"; -import { ReactComponent as likeDark } from "./likeDark.svg"; import { ReactComponent as unLike } from "./unLike.svg"; -import { ReactComponent as unLikeDark } from "./unLikeDark.svg"; - +import { cname } from "@/components/bs-ui/utils"; type ThunmbIconType = 'copy' | 'like' | 'unLike' | 'copyDark' | 'likeDark' | 'unLikeDark'; @@ -15,13 +12,10 @@ export const ThunmbIcon = forwardRef< >((props, ref) => { const comps = { 'copy': copy, - 'copyDark': copyDark, 'like': like, - 'likeDark': likeDark, 'unLike': unLike, - 'unLikeDark': unLikeDark, } const Comp = comps[props.type]; - const _className = 'transition text-gray-400 ' + (props.className || '') + const _className = cname('transition text-gray-400 hover:text-gray-500', props.className) return ; }); diff --git a/src/components/bs-icons/thumbs/like.svg b/src/components/bs-icons/thumbs/like.svg index 948999c..01c4599 100644 --- a/src/components/bs-icons/thumbs/like.svg +++ b/src/components/bs-icons/thumbs/like.svg @@ -1,15 +1,15 @@ - + - + + fill="transparent" /> diff --git a/src/components/bs-icons/thumbs/likeDark.svg b/src/components/bs-icons/thumbs/likeDark.svg deleted file mode 100644 index 67a6053..0000000 --- a/src/components/bs-icons/thumbs/likeDark.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/components/bs-icons/thumbs/unLike.svg b/src/components/bs-icons/thumbs/unLike.svg index e61a684..09e5c71 100644 --- a/src/components/bs-icons/thumbs/unLike.svg +++ b/src/components/bs-icons/thumbs/unLike.svg @@ -2,15 +2,15 @@ + transform="rotate(-180 19.25 14.25)" fill="transparent" stroke="currentColor" stroke-width="1.5" /> - + + fill="transparent" /> diff --git a/src/components/bs-icons/thumbs/unLikeDark.svg b/src/components/bs-icons/thumbs/unLikeDark.svg deleted file mode 100644 index 9224027..0000000 --- a/src/components/bs-icons/thumbs/unLikeDark.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/components/bs-icons/upload/index.tsx b/src/components/bs-icons/upload/index.tsx index eb5df7c..81dcbef 100644 --- a/src/components/bs-icons/upload/index.tsx +++ b/src/components/bs-icons/upload/index.tsx @@ -5,6 +5,6 @@ export const UploadIcon = forwardRef< SVGSVGElement & { className: any }, React.PropsWithChildren<{ className?: string }> >(({ className, ...props }, ref) => { - const _className = 'transition text-gray-950 ' + (className || '') + const _className = 'transition text-[#ffd025] ' + (className || '') return ; }); diff --git a/src/components/bs-ui/.DS_Store b/src/components/bs-ui/.DS_Store index 46b1205..2f620ee 100644 Binary files a/src/components/bs-ui/.DS_Store and b/src/components/bs-ui/.DS_Store differ diff --git a/src/components/bs-ui/calendar/datePicker.tsx b/src/components/bs-ui/calendar/datePicker.tsx new file mode 100644 index 0000000..8cfee38 --- /dev/null +++ b/src/components/bs-ui/calendar/datePicker.tsx @@ -0,0 +1,58 @@ +"use client" + +import * as React from "react" +import { CalendarIcon } from "@radix-ui/react-icons" +import { Button } from "../button" +import { Calendar } from "../calendar" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover" +import { cname } from "../utils" +import { useMemo } from "react" +import { formatDate } from "@/util/utils" + +export function DatePicker({ + value, + placeholder = '', + onChange +}) { + const [date, setDate] = React.useState(value) + + const dateStr = useMemo(() => { + return date ? formatDate(date, 'yyyy-MM-dd') : '' + }, [date]) + + React.useEffect(() => { + setDate(value) + },[value]) + + return ( + + + {placeholder}} + + + + { + setDate(d) + onChange?.(d) + }} + initialFocus + /> + + + ) +} diff --git a/src/components/bs-ui/calendar/index.tsx b/src/components/bs-ui/calendar/index.tsx new file mode 100644 index 0000000..6727c2b --- /dev/null +++ b/src/components/bs-ui/calendar/index.tsx @@ -0,0 +1,72 @@ +"use client" + +import * as React from "react" +import { ChevronLeftIcon, ChevronRightIcon } from "@radix-ui/react-icons" +import { DayPicker } from "react-day-picker" + +import { buttonVariants } from "@/components/bs-ui/button" +import { cname } from "../utils" + +export type CalendarProps = React.ComponentProps + +function Calendar({ + className, + classNames, + showOutsideDays = true, + ...props +}: CalendarProps) { + return ( + .day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md" + : "[&:has([aria-selected])]:rounded-md" + ), + day: cname( + buttonVariants({ variant: "ghost" }), + "h-8 w-8 p-0 font-normal aria-selected:opacity-100" + ), + day_range_start: "day-range-start", + day_range_end: "day-range-end", + day_selected: + "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground", + day_today: "bg-accent text-accent-foreground", + day_outside: + "day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30", + day_disabled: "text-muted-foreground opacity-50", + day_range_middle: + "aria-selected:bg-accent aria-selected:text-accent-foreground", + day_hidden: "invisible", + ...classNames, + }} + components={{ + IconLeft: ({ ...props }) => , + IconRight: ({ ...props }) => , + }} + {...props} + /> + ) +} +Calendar.displayName = "Calendar" + +export { Calendar } diff --git a/src/components/bs-ui/dialog/index.tsx b/src/components/bs-ui/dialog/index.tsx index fd87d5a..5b1bfa0 100644 --- a/src/components/bs-ui/dialog/index.tsx +++ b/src/components/bs-ui/dialog/index.tsx @@ -42,7 +42,7 @@ const DialogContent = React.forwardRef< {...props} > {children} - + Close diff --git a/src/components/bs-ui/input/avator.tsx b/src/components/bs-ui/input/avator.tsx new file mode 100644 index 0000000..960a1ae --- /dev/null +++ b/src/components/bs-ui/input/avator.tsx @@ -0,0 +1,46 @@ +import { Button } from "../button"; +import { useToast } from "../toast/use-toast"; +import { cname } from "../utils"; + +// 头像 +export default function Avator({ + size = 5 * 1024 * 1024, + accept = "image/jpeg,image/png", + value, + className, + buttonName = '上传头像', + onChange, + children +}) { + const { message } = useToast(); + + const handleFileChange = (event) => { + const file = event.target.files[0]; + if (file) { + const isValidSize = file.size <= size; + const isValidType = accept.split(',').some(type => file.type === type); + + const errormgs = [] + if (!isValidSize) errormgs.push(`文件大小不能超过 ${size / 1024 / 1024}MB`) + if (!isValidType) errormgs.push(`文件类型不符合要求:${accept}`) + + errormgs.length ? message({ + variant: 'error', + description: errormgs + }) : onChange(file) + } + }; + + return
+ {value ? : children} + +
+}; diff --git a/src/components/bs-ui/input/index.tsx b/src/components/bs-ui/input/index.tsx index 1bc4b29..e5b8e43 100644 --- a/src/components/bs-ui/input/index.tsx +++ b/src/components/bs-ui/input/index.tsx @@ -3,6 +3,9 @@ import { cname } from "../utils" import { SearchIcon } from "../../bs-icons/search" import { generateUUID } from "../utils" import { MinusCircledIcon } from "@radix-ui/react-icons" +import { EyeOpenIcon, EyeNoneIcon } from "@radix-ui/react-icons" +import { useState } from "react" + import sousuo from "../../../assets/npc/sousuo1.png" export interface InputProps extends React.InputHTMLAttributes { } @@ -13,7 +16,7 @@ const Input = React.forwardRef( { return
{/* */} - - +
+ +
+
} ) SearchInput.displayName = "SearchInput" +const PasswordInput = React.forwardRef( + ({ className, inputClassName, iconClassName, ...props }, ref) => { + const [type, setType] = useState('password') + const handleShowPwd = () => { + type === 'password' ? setType('text') : setType('password') + } + return
+ + { + type === 'password' + ? + : + } +
+ } +) +PasswordInput.displayName = 'PasswordInput' /** * 多行文本 @@ -146,4 +168,4 @@ const InputList = React.forwardRef void; + search: (event: React.ChangeEvent) => void; + onClearChecked: () => void; + onOk: () => void; +} + +const FilterUserGroup: React.FC = ({ + value = [], + options, + nameKey = 'name', + placeholder, + onChecked, + search, + onClearChecked, + onOk +}) => { + const { t } = useTranslation(); + + return ( +
+
+ +
+
+ {options.map((i) => ( +
+ onChecked(i.id)} /> + +
+ ))} + {options.length === 0 && ( +
+ +
+ )} +
+
+ + +
+
+ ); +} + +export default React.memo(FilterUserGroup); \ No newline at end of file diff --git a/src/components/bs-ui/select/hover.tsx b/src/components/bs-ui/select/hover.tsx new file mode 100644 index 0000000..b562965 --- /dev/null +++ b/src/components/bs-ui/select/hover.tsx @@ -0,0 +1,23 @@ +import { Button } from "../button"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../tooltip"; + +export function SelectHoverItem({ children, ...props }) { + + return
+ {children} +
+} + +export function SelectHover({ triagger, children }) { + + return + + + {triagger} + + + {children} + + + +}; diff --git a/src/components/bs-ui/select/index.tsx b/src/components/bs-ui/select/index.tsx index 8a266c5..d32d022 100644 --- a/src/components/bs-ui/select/index.tsx +++ b/src/components/bs-ui/select/index.tsx @@ -23,7 +23,7 @@ const SelectTrigger = React.forwardRef< span]:line-clamp-1 data-[placeholder]:text-gray-400", + "group flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border-input bg-[#1a1a1a] px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 data-[placeholder]:text-gray-400 border-[#666] text-[#999]", className )} {...props} @@ -79,7 +79,7 @@ const SelectContent = React.forwardRef< { +const MultiItem: React.FC< + { active: boolean; children: React.ReactNode; value: string; onClick: (value: string, label: string) => void } +> = ({ active, children, value, onClick }) => { - return
{ onClick(value) }} + return
{ onClick(value, children as string) }} > {active && } @@ -17,21 +21,45 @@ const MultiItem = ({ active, children, value, onClick }) => { {children}
} - - -interface IProps { - className?: string, - options: { label: string, value: string }[], - value?: string[], - defaultValue?: string[], - children?: React.ReactNode, - placeholder?: string, - searchPlaceholder?: string, - lockedValues?: string[], - onChange?: (value: string[]) => void +interface Option { + label: string; + value: string; } + +interface BaseProps { + /** 多选 */ + multiple?: boolean; + disabled?: boolean; + className?: string; + options: Option[]; + children?: React.ReactNode; + placeholder?: string; + searchPlaceholder?: string; + /** 锁定不可修改的值 */ + lockedValues?: string[]; + onLoad?: () => void; + onSearch?: (name: string) => void; + onChange?: (value: T) => void; +} + +// onScrollLoad有值表示开启分页、异步检索 +interface ScrollLoadProps extends BaseProps { + onScrollLoad: (name: string) => void; + value?: Option[]; + defaultValue?: Option[]; +} + +interface NonScrollLoadProps extends BaseProps { + onScrollLoad?: undefined; + value?: string[]; + defaultValue?: string[]; +} + +type IProps = ScrollLoadProps | NonScrollLoadProps; + // 临时用 andt 设计方案封装组件 const MultiSelect = ({ + multiple = false, className, value = [], defaultValue = [], @@ -40,13 +68,15 @@ const MultiSelect = ({ placeholder = '', searchPlaceholder = '', lockedValues = [], + onSearch, + onLoad, + onScrollLoad, onChange, ...props }: IProps) => { const [values, setValues] = React.useState(defaultValue) const [optionFilter, setOptionFilter] = React.useState(options) - - + const [created, creatInput] = useState(false) const inputRef = useRef(null) useEffect(() => { @@ -54,75 +84,145 @@ const MultiSelect = ({ }, [value]) useEffect(() => { - setOptionFilter(options) - if (inputRef.current) { - inputRef.current.value = '' - } - }, [options]) - // delete + // if (onScrollLoad) { + setOptionFilter(options); + // } + }, [options]); + + // delete const handleDelete = (value: string) => { - const newValues = values.filter((item) => { - return item !== value + const newValues = (values as any[]).filter((item) => { + const _value = onScrollLoad ? (item as Option).value : item; + return _value !== value }) setValues(newValues) onChange?.(newValues) } // add - const handleSwitch = (value: string) => { + const handleSwitch = (value: string, label: string) => { if (lockedValues.includes(value)) { return } - if (values.includes(value)) { - const newValues = values.filter((item) => { - return item !== value - }) - setValues(newValues) - onChange?.(newValues) + + const updateValues = (newValues: any) => { + setValues(newValues); + onChange?.(newValues); + }; + + // 单选 + if (!multiple) { + const newValues = onScrollLoad ? [{ label, value }] : [value] + updateValues(newValues); + return + } + + if (onScrollLoad) { + const newValues = (values as Option[]).some(item => item.value === value) + ? (values as Option[]).filter(item => item.value !== value) + : [...(values as Option[]), { label, value }]; + updateValues(newValues); } else { - const _newValues = [...values, value] - setValues(_newValues) - onChange?.(_newValues) + const newValues = (values as string[]).includes(value) + ? (values as string[]).filter(item => item !== value) + : [...(values as string[]), value]; + updateValues(newValues); } } // search - const handleSearch = (e) => { + const handleSearch = useDebounce((e) => { const newValues = options.filter((item) => { return item.label.toLowerCase().indexOf(e.target.value.toLowerCase()) !== -1 }) setOptionFilter(newValues) - } - return { + creatInput(e); + if (!e) { + onLoad?.(); + setOptionFilter(options); + } + }} + > + { - values.length - ?
+ !multiple && (values.length ? {onScrollLoad ? (values[0] as Option).label : options.find(op => op.value === values[0])?.label} : placeholder) + } + { + multiple && (values.length ? ( + onScrollLoad ?
{ - options.filter(option => values.includes(option.value)).map(option => - e.stopPropagation()} key={option.value} className="flex whitespace-normal items-center gap-1 select-none bg-primary/20 text-primary hover:bg-primary/15 m-[2px]"> + values.map(item => + e.stopPropagation()} key={item.value} className="flex whitespace-normal items-center gap-1 select-none bg-[#261F08] text-[#CCA831] hover:bg-[#261F08] m-[2px]"> + {item.label} + {lockedValues.includes(item.value) || handleDelete(item.value)}>} + + ) + } +
:
+ { + options.filter(option => (values as string[]).includes(option.value)).map(option => + e.stopPropagation()} key={option.value} className="flex whitespace-normal items-center gap-1 select-none bg-[#261F08] text-[#CCA831] hover:bg-[#261F08] m-[2px] break-all"> {option.label} {lockedValues.includes(option.value) || handleDelete(option.value)}>} ) } -
- : placeholder +
) + : placeholder) }
- - +
} footerNode={children} >
{ - optionFilter.map((item, index) => ( - {item.label} + optionFilter.map((item) => ( + val === item.value || val.value === item.value)} + value={item.value} + onClick={handleSwitch} + >{item.label} )) } +
diff --git a/src/components/bs-ui/select/select.tsx b/src/components/bs-ui/select/select.tsx new file mode 100644 index 0000000..0d8f2b8 --- /dev/null +++ b/src/components/bs-ui/select/select.tsx @@ -0,0 +1,44 @@ +import React, { ChangeEvent } from "react" +import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/bs-ui/select"; +import { SearchInput } from "../input"; + +interface SelectSearchProps { + value: string, + options: {label: string, value: string}[], + selectPlaceholder?:string, + inputPlaceholder?:string, + onOpenChange?: (open:boolean) => void, + onValueChange: (value: string) => void, + onChange: (e:ChangeEvent) => void, + selectClass?: string, + contentClass?: string +} + +const SelectSearch: React.FC = ({ + value, + options, + selectPlaceholder = '', + inputPlaceholder = '', + onOpenChange, + onValueChange, + onChange, + selectClass = '', + contentClass = '' +}) => { + return +} + +export default React.memo(SelectSearch) \ No newline at end of file diff --git a/src/components/bs-ui/table/index.tsx b/src/components/bs-ui/table/index.tsx index 643279f..fc5b186 100644 --- a/src/components/bs-ui/table/index.tsx +++ b/src/components/bs-ui/table/index.tsx @@ -57,7 +57,7 @@ const TableRow = React.forwardRef< [role=checkbox]]:translate-y-[2px] bg-[#2B2B2B] first:rounded-l-md last:rounded-r-md group-odd:bg-[#1a1a1a]", + "p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px] first:rounded-l-md last:rounded-r-md ", // group-hover:bg-[#1a1a1a] className )} @@ -102,7 +102,7 @@ const TableCaption = React.forwardRef< >(({ className, ...props }, ref) => ( )) diff --git a/src/components/bs-ui/utils.tsx b/src/components/bs-ui/utils.tsx index 6b22f63..674c729 100644 --- a/src/components/bs-ui/utils.tsx +++ b/src/components/bs-ui/utils.tsx @@ -1,20 +1,50 @@ import clsx, { ClassValue } from "clsx"; +import { useCallback, useEffect, useRef } from "react"; import { twMerge } from "tailwind-merge"; /** * 样式合并 */ export function cname(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); + return twMerge(clsx(inputs)); } export const generateUUID = (length: number) => { - let d = new Date().getTime() - const uuid = ''.padStart(length, 'x').replace(/[xy]/g, (c) => { - const r = (d + Math.random() * 16) % 16 | 0 - d = Math.floor(d / 16) - return (c == 'x' ? r : (r & 0x7 | 0x8)).toString(16) - }) - return uuid - } \ No newline at end of file + let d = new Date().getTime() + const uuid = ''.padStart(length, 'x').replace(/[xy]/g, (c) => { + const r = (d + Math.random() * 16) % 16 | 0 + d = Math.floor(d / 16) + return (c == 'x' ? r : (r & 0x7 | 0x8)).toString(16) + }) + return uuid +} + + +// 防抖 +export function useDebounce(func: any, wait: number, immediate: boolean, callback?: any,): (any?: any) => any { + let timer = useRef(); + const fnRef = useRef(func); + useEffect(() => { fnRef.current = func; }, [func]); + const timerCancel = function () { if (timer.current) clearTimeout(timer.current); }; + + function debounced(...args: any[]) { + const runFunction = () => { + return callback + ? callback(fnRef.current.apply(fnRef.current, args)) + : fnRef.current.apply(fnRef.current, args); + }; + timerCancel(); + if (immediate) { + let runNow = !timer.current; + timer.current = setTimeout(() => { timer.current = null; }, wait); + if (runNow) { + runFunction(); + } + } else { + timer.current = setTimeout(() => { runFunction(); }, wait); + } + } + debounced.cancel = function () { timerCancel(); timer.current = null; }; + return useCallback(debounced, [wait, immediate, timerCancel, func]); +} diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx index 24470f8..7f4cd6d 100644 --- a/src/components/ui/dialog.tsx +++ b/src/components/ui/dialog.tsx @@ -51,7 +51,7 @@ const DialogContent = React.forwardRef< {...props} > {children} - + Close diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx index 4560fdd..6a2025c 100644 --- a/src/components/ui/table.tsx +++ b/src/components/ui/table.tsx @@ -95,7 +95,7 @@ const TableCaption = React.forwardRef< >(({ className, ...props }, ref) => ( )); diff --git a/src/controllers/.DS_Store b/src/controllers/.DS_Store index 30ee575..e288fdb 100644 Binary files a/src/controllers/.DS_Store and b/src/controllers/.DS_Store differ diff --git a/src/controllers/API/.DS_Store b/src/controllers/API/.DS_Store new file mode 100644 index 0000000..8dc1586 Binary files /dev/null and b/src/controllers/API/.DS_Store differ diff --git a/src/controllers/API/assistant.ts b/src/controllers/API/assistant.ts index 5c72313..b77211e 100644 --- a/src/controllers/API/assistant.ts +++ b/src/controllers/API/assistant.ts @@ -27,8 +27,8 @@ export const createAssistantsApi = async (name, prompt, avatar_img, avatar_color }; // 获取助手详情 -export const getAssistantDetailApi = async (id): Promise => { - return await axios.get(`/api/v1/assistant/info/${id}`) +export const getAssistantDetailApi = async (id, version): Promise => { + return await axios.get(`/api/${version}/assistant/info/${id}`) }; // 获取助手系统模型 @@ -55,10 +55,18 @@ export const deleteAssistantApi = async (id) => { // 获取会话选择列表 -export const getChatOnlineApi = async () => { - return await axios.get(`/api/v1/chat/online`) -}; - +// export const getChatOnlineApi = async () => { +// return await axios.get(`/api/v1/chat/online`) +// }; +export const getChatOnlineApi = async (page, keyword, tag_id) => { + return await axios.get(`/api/v1/chat/online`, { + params: { + page, keyword, + limit: 40, + tag_id: tag_id === -1 ? null : tag_id + } + }) +} // 获取工具集合 export const getAssistantToolsApi = async (type: 'all' | 'default' | 'custom'): Promise => { diff --git a/src/controllers/API/evaluate.ts b/src/controllers/API/evaluate.ts new file mode 100644 index 0000000..fca9101 --- /dev/null +++ b/src/controllers/API/evaluate.ts @@ -0,0 +1,67 @@ +import { ReactFlowJsonObject } from "reactflow"; +import { FlowStyleType, FlowType, FlowVersionItem } from "../../types/flow"; +import axios from "../request"; + + +export type Evaluation = { + file_path: string, + file_name: string, + unique_id: string, + prompt: string, + result_score: { + answer_f1: string, + answer_precision: string, + answer_recall: string, + }, + create_time: string, + id: number, + user_id: number, + exec_type: string, + version: number, + status: number, + progress?: string, + result_file_path: string, + is_delete: number, + update_time: string, + unique_name: string, + version_name: string, + user_name: string, +} + +/** + * 获取评测列表 + * @param data + * @returns + */ +export const getEvaluationApi = async (page, limit): Promise => { + return await axios.get(`/api/v1/evaluation`, { + params: { + page, limit + } + }); +}; + +/** + * 创建测评任务 + */ +export const createEvaluationApi = async (data): Promise => { + return await axios.post(`/api/v1/evaluation`, data,{ + headers: { + 'Content-Type':'multipart/form-data' + } + }); +}; + +/** + * 删除测评任务 + */ +export const deleteEvaluationApi = async (id): Promise => { + return await axios.delete(`/api/v1/evaluation/${id}`); +}; + +/** + * 获取下载链接 + */ +export const getEvaluationUrlApi = async (id): Promise<{ url: string }> => { + return await axios.get(`/api/v1/evaluation/result/file/download?file_url=${id}`); +}; diff --git a/src/controllers/API/flow.ts b/src/controllers/API/flow.ts index e052ed4..9514143 100644 --- a/src/controllers/API/flow.ts +++ b/src/controllers/API/flow.ts @@ -114,8 +114,8 @@ export function getReportFormApi(flow_id): Promise { * @returns {Promise} The flow data. * @throws Will throw an error if fetching fails. */ -export async function getFlowApi(flowId: string): Promise { - return axios.get(`/api/v1/flows/${flowId}`) +export async function getFlowApi(flowId: string, version: string = 'v1'): Promise { + return await axios.get(`/api/${version}/flows/${flowId}`) } /** diff --git a/src/controllers/API/index.ts b/src/controllers/API/index.ts index e33f2d0..4bae8b2 100644 --- a/src/controllers/API/index.ts +++ b/src/controllers/API/index.ts @@ -62,7 +62,12 @@ export async function delComponentApi(name): Promise { export async function getAppConfig(): Promise { return await axios.get(`/api/v1/env`); } - +/** + * 获取平台配置 + */ +export async function saveThemeApi(data: string): Promise { + return await axios.post(`/api/v1/web/config`, { value: data }); +} /** * Reads all templates from the database. * @@ -280,9 +285,12 @@ export async function postValidatePrompt( /** * 获取会话列表 */ -export const getChatsApi = () => { - return (axios.get(`/api/v1/chat/list`) as Promise).then(res => - res?.filter(el => el.chat_id) || [] +export const getChatsApi = (page) => { + // return (axios.get(`/api/v1/chat/list`) as Promise).then(res => + // res?.filter(el => el.chat_id) || [] + // ) + return (axios.get(`/api/v1/chat/list?page=${page}&limit=40`) as Promise).then(res => + res?.filter((el, i) => el.chat_id) || [] ) }; diff --git a/src/controllers/API/log.ts b/src/controllers/API/log.ts new file mode 100644 index 0000000..8faf0a1 --- /dev/null +++ b/src/controllers/API/log.ts @@ -0,0 +1,56 @@ +import axios from "../request"; + +// 获取操作过组下资源的所有用户 +export async function getOperatorsApi():Promise<[]> { + return await axios.get('/api/v1/audit/operators') +} + +// 分页获取审计列表 +export async function getLogsApi({page, pageSize, userIds, groupId = '', start, end, moduleId = '', action = ''}:{ + page:number, + pageSize:number, + userIds?:number[], + groupId?:string, + start?:string, + end?:string, + moduleId?:string, + action?:string +}):Promise<{data:any[], total:number}> { + const uids = userIds?.reduce((pre,val) => `${pre}&operator_ids=${val}`, '') || '' + const startStr = start ? `&start_time=${start}` : '' + const endStr = end ? `&end_time=${end}` : '' + return await axios.get( + `/api/v1/audit?page=${page}&limit=${pageSize}&group_ids=${groupId}${uids}` + + `&system_id=${moduleId}&event_type=${action}` + startStr + endStr + ) +} + +// 系统模块 +export async function getModulesApi():Promise<{data:any[]}> { + return { + data: [{name:'会话', value:'chat'},{name:'构建', value:'build'},{name:'知识库', value:'knowledge'},{name:'系统', value:'system'}] + } +} + +const actions = [ + {name:'新建会话',value:'create_chat'},{name:'删除会话',value:'delete_chat'},{name:'新建应用',value:'create_build'},{name:'编辑应用',value:'update_build'}, + {name:'删除应用',value:'delete_build'},{name:'新建知识库',value:'create_knowledge'},{name:'删除知识库',value:'delete_knowledge'},{name:'知识库上传文件',value:'upload_file'}, + {name:'知识库删除文件',value:'delete_file'},{name:'用户编辑',value:'update_user'},{name:'停用用户',value:'forbid_user'},{name:'启用用户',value:'recover_user'}, + {name:'新建用户组',value:'create_user_group'},{name:'删除用户组',value:'delete_user_group'},{name:'编辑用户组',value:'update_user_group'},{name:'新建角色',value:'create_role'}, + {name:'删除角色',value:'delete_role'},{name:'编辑角色',value:'update_role'},{name:'用户登录',value:'user_login'} +] + +// 全部操作行为 +export async function getActionsApi() { + return actions +} + +// 系统模块下操作行为 +export async function getActionsByModuleApi(moduleId) { + switch(moduleId) { + case 'chat': return actions.filter(a => a.value.includes('chat')) + case 'build': return actions.filter(a => a.value.includes('build')) + case 'knowledge': return actions.filter(a => a.value.includes('knowledge') || a.value.includes('file')) + case 'system': return actions.filter(a => a.value.includes('user') || a.value.includes('role')) + } +} \ No newline at end of file diff --git a/src/controllers/API/pro.ts b/src/controllers/API/pro.ts new file mode 100644 index 0000000..3d196f5 --- /dev/null +++ b/src/controllers/API/pro.ts @@ -0,0 +1,93 @@ +import axios from "../request"; + +/** + * 保存敏感词 + */ +export const sensitiveSaveApi = async (data: any): Promise => { + const { id, type, isCheck, words, wordsType, autoReply } = data + + return await axios.post(`/api/sensitive/saveWords`, { + resource_id: id, + resource_type: type, + is_check: isCheck, + words, + words_type: wordsType, + auto_reply: autoReply + }); +}; + +/** + * 获取敏感词配置 + */ +export const getSensitiveApi = async (resourceId, resourceType): Promise => { + + return await axios.get(`/api/sensitive/wordsDetail`, { + params: { + resourceId, + resourceType + } + }); +}; + +/** + * 获取资源组流量 + */ +export const getGroupFlowsApi = async (page: number, pageSize: number, resourceType: string, groupId: number, name: string): Promise => { + if (!groupId) return Promise.resolve([{ data: [], total: 0 }]); + return await axios.get(`/api/resource/groupFlows`, { + params: { + name, + page, + pageSize, + resourceType, + groupId + } + }); +}; + + +/** + * 保存组信息 + */ +export const saveGroupApi = async (data: any): Promise => { + const { id, + groupLimit: group_limit, + adminUser: admin_user, + adminUserId: admin_user_id, + groupName: group_name, + assistant, + skill } = data; + // const {resourceId, groupId, resourceLimit} = assistant + + return await axios.post(`/api/group/save`, { + id, + group_limit, + admin_user, + admin_user_id, + group_name, + assistant, + skill + }); +}; + +// 用户组列表 +export function getUserGroupsProApi() { + return axios.get(`/api/group/list`); +} + +// GET sso URL +export function getSSOurlApi() { + // return Promise.resolve(url) + return axios.get(`/api/oauth2/list`) +} + +export async function getKeyApi() { + return await axios.get('/api/getkey') +} + +export async function ldapLoginApi(username:string, password:string) { + return await axios.post('/api/oauth2/ldap', { + username, + password + }) +} \ No newline at end of file diff --git a/src/controllers/API/user.ts b/src/controllers/API/user.ts index 1a57bf1..b1743ed 100644 --- a/src/controllers/API/user.ts +++ b/src/controllers/API/user.ts @@ -27,16 +27,40 @@ export async function registerApi(name, pwd, captcha_key?, captcha?) { return await axios.post(`/api/v1/user/regist`, { user_name: name, password: pwd, captcha_key, captcha }); } // 用户列表 -export async function getUsersApi(name: string, page: number, pageSize: number): Promise<{ data: User[], total: number }> { - return await axios.get(`/api/v1/user/list?page_num=${page}&page_size=${pageSize}&name=${name || ''}`) -} +// export async function getUsersApi(name: string, page: number, pageSize: number): Promise<{ data: User[], total: number }> { +// return await axios.get(`/api/v1/user/list?page_num=${page}&page_size=${pageSize}&name=${name || ''}`) +// } +export async function getUsersApi({ name = '', page, pageSize, groupId, roleId }: { + name: string, + page: number, + pageSize: number, + groupId?: number[], + roleId?: number[] + }): Promise<{ data: User[]; total: number }> { + const groupStr = groupId?.reduce((res, id) => `${res}&group_id=${id}`, '') || '' + const roleStr = roleId?.reduce((res, id) => `${res}&role_id=${id}`, '') || '' + + return await axios.get( + `/api/v1/user/list?page_num=${page}&page_size=${pageSize}&name=${name}${groupStr}${roleStr}` + ); + } // 修改用户状态(启\禁用) export async function disableUserApi(userid, status) { return await axios.post(`/api/v1/user/update`, { user_id: userid, delete: status }); } // 角色列表 -export async function getRolesApi(searchkey = ''): Promise<{ data: ROLE[] }> { +// export async function getRolesApi(searchkey = ''): Promise<{ data: ROLE[] }> { +// return await axios.get(`/api/v1/role/list?role_name=${searchkey}`) +// } +export async function getRolesApi(searchkey = ""): Promise<{ data: ROLE[] }> { return await axios.get(`/api/v1/role/list?role_name=${searchkey}`) + .then(res => res.data); + } +// 用户组下角色列表 +export async function getRolesByGroupApi(searchkey = "", groupIds:any[]): Promise<{ data: ROLE[] }> { + const groupStr = groupIds?.reduce((pre, id) => `${pre}&group_id=${id}`, '') || '' + return await axios.get(`/api/v1/group/roles?keyword=${searchkey}${groupStr}`) + .then(res => res.data); } /** * 获取配置 @@ -68,6 +92,20 @@ export async function getRoleAssistApi(params): Promise<{ data: any[], total: nu export async function getRoleLibsApi(params): Promise<{ data: any[], total: number }> { return await axios.get(`/api/v1/role_access/knowledge`, { params }); } +/** + * 根据用户组获取资源列表 + */ +export async function getGroupResourcesApi( + params: { + group_id: string, + resource_type: number, + name: string, + page_size: number, + page_num: number + } +): Promise<{ data: any[]; total: number }> { + return await axios.get(`/api/v1/group/get_group_resources`, { params }); +} /** * 新增角色 */ @@ -119,6 +157,43 @@ export async function delRoleApi(roleId) { return axios.delete(`/api/v1/role/${roleId}`) } +// 用户组列表 +export function getUserGroupsApi() { + return axios.get(`/api/v1/group/list`); + } + + + // 删除用户组post + export function delUserGroupApi(group_id) { + return axios.delete(`/api/v1/group/create`, { params: { group_id } }); + // return axios.post(`/api/v1/group/del/${userGroupId}`); + } + + // 保存用户组 +export function saveUserGroup(form, selected) { + console.log('form :>> ', form); + const { groupName: group_name } = form + return axios.post(`/api/v1/group/create`, { + group_name, + group_admins: selected.map(item => item.value), + }); + } + + // 修改用户组 + export function updateUserGroup(id, form, selected) { + const { groupName: group_name } = form + const a = axios.put(`/api/v1/group/create`, { + id, + group_name + }); + const b = axios.post(`/api/v1/group/set_group_admin`, { + group_id: id, + user_ids: selected.map(item => item.value) + }) + return Promise.all([a, b]) + } + + /** * 获取用户的角色信息 */ @@ -134,4 +209,59 @@ export async function updateUserRoles(userId, roles) { user_id: userId, role_id: roles }); -} \ No newline at end of file +} + +// 更新用户组 +export async function updateUserGroups(userId, groupIds) { + return await axios.post(`/api/v1/group/set_user_group`, { + user_id: userId, + group_id: groupIds, + is_group_admin: false + }); +} + +// 超管创建用户组 +export async function createUserApi(user_name:string, password:string, group_roles:any[]) { + return await axios.post('/api/v1/user/create', { + user_name, + password, + group_roles + }) + } + + /** + * 获取所有管理员 + */ + export async function getAdminsApi(): Promise { + return axios.get(`/api/v1/user/admin`); + } + + + /** + * 重置密码(管理员专用) + */ + export async function resetPasswordApi(userId, password): Promise { + return axios.post(`/api/v1/user/reset_password`, { + user_id: userId, + password + }); + } + + /** + * 密码过期重置个人密码 + */ + export async function changePasswordApi(userName, password, new_password): Promise { + return axios.post(`/api/v1/user/change_password_public`, { + username: userName, + password, + new_password + }); + } + + // 已登录状态重置个人密码 + export async function loggedChangePasswordApi(password, new_password): Promise { + return axios.post(`/api/v1/user/change_password`, { + password, + new_password + }) + } \ No newline at end of file diff --git a/src/controllers/request.ts b/src/controllers/request.ts index 4cf45c9..a1d9ffc 100644 --- a/src/controllers/request.ts +++ b/src/controllers/request.ts @@ -3,13 +3,28 @@ import axios from "axios"; import i18next from "i18next"; axios.defaults.withCredentials = true; const customAxios = axios.create({ + baseURL: import.meta.env.BASE_URL // 配置 }); +export const requestInterceptor = { + remoteLoginFuc(msg) { } +}; + customAxios.interceptors.response.use(function (response) { if (response.data.status_code === 200) { return response.data.data; } + // 无权访问 + if (response.data.status_code === 403) { + // location.href = __APP_ENV__.BASE_URL + '/403' + // return Promise.reject(response.data.status_message); + } + // 异地登录 + if (response.data.status_code === 10604) { + requestInterceptor.remoteLoginFuc(response.data.status_message) + return Promise.reject(response.data.status_message); + } return Promise.reject(response.data.status_message); }, function (error) { console.error('application error :>> ', error); diff --git a/src/layout/MainLayout.tsx b/src/layout/MainLayout.tsx index a4f7e4b..0ce29fc 100755 --- a/src/layout/MainLayout.tsx +++ b/src/layout/MainLayout.tsx @@ -1,9 +1,22 @@ +import { + ApplicationIcon, + BookOpenIcon, + EnIcon, + EvaluatingIcon, + GithubIcon, + KnowledgeIcon, + LogIcon, + ModelIcon, + QuitIcon, + SystemIcon, + TechnologyIcon +} from "@/components/bs-icons"; import i18next from "i18next"; -import { AppWindow, BookOpen, Github, Globe, HardDrive, Languages, LayoutDashboard, LogOut, MoonIcon, Puzzle, Settings, SunIcon } from "lucide-react"; -import { useContext, useEffect, useState } from "react"; +// import { AppWindow, BookOpen, Github, Globe, HardDrive, Languages, LayoutDashboard, LogOut, MoonIcon, Puzzle, Settings, SunIcon } from "lucide-react"; +import { useContext, useEffect, useMemo, useRef, useState } from "react"; import { ErrorBoundary } from "react-error-boundary"; import { useTranslation } from "react-i18next"; -import { Link, NavLink, Outlet } from "react-router-dom"; +import { Link, NavLink, Outlet, useNavigate } from "react-router-dom"; import CrashErrorComponent from "../components/CrashErrorComponent"; import { Separator } from "../components/ui/separator"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../components/ui/tooltip"; @@ -14,19 +27,59 @@ import { captureAndAlertRequestErrorHoc } from "../controllers/request"; import { User } from "../types/api/user"; import login from "../assets/login.jpg" import shuoming from "../assets/nav/shuoming.png" +import adminImg from "../assets/nav/admin.png" +import adminImg1 from "../assets/nav/admin1.png" +import mima from "../assets/nav/mima.png" +import tuichu from "../assets/nav/tuichu.png" +import { locationContext } from "@/contexts/locationContext"; +import { bsConfirm } from "@/components/bs-ui/alertDialog/useConfirm"; +import { SelectHover, SelectHoverItem } from "@/components/bs-ui/select/hover"; +import { CaretDownIcon, LockClosedIcon, MoonIcon, SunIcon } from "@radix-ui/react-icons"; +import { Button } from "@/components/ui/button"; export default function MainLayout() { const { dark, setDark } = useContext(darkContext); + const { appConfig } = useContext(locationContext) // 角色 const { user, setUser } = useContext(userContext); const { language, options, changLanguage, t } = useLanguage(user) + const { delShow, idRef, close, delConfirm } = useDelete(); + const handleLogout = () => { captureAndAlertRequestErrorHoc(logoutApi()).then(_ => { setUser(null) localStorage.removeItem('isLogin') }) + // bsConfirm({ + // title: `${t('prompt')}!`, + // desc: `${t('menu.logoutContent')}?`, + // okTxt: t('system.confirm'), + // onOk(next) { + // captureAndAlertRequestErrorHoc(logoutApi()).then(_ => { + // setUser(null) + // localStorage.removeItem('isLogin') + // }) + // next() + // } + // }) + } + + // 重置密码 + const navigator = useNavigate() + const JumpResetPage = () => { + localStorage.setItem('account', user.user_name) + navigator('/reset') + } + + // 系统管理员(超管、组超管) + const isAdmin = useMemo(() => { + return ['admin', 'group_admin'].includes(user.role) + }, [user]) + + const isMenu = (menu) => { + return user.web_menu.includes(menu) || user.role === 'admin' } return
@@ -37,30 +90,58 @@ export default function MainLayout() { {/* */} {t('menu.app')} - - {/* */} - {t('menu.skills')} - - - {/* */} - {t('menu.knowledge')} - - - {/* */} - {t('menu.models')} - { - user.role === 'admin' && <> - + isMenu('build') && + + {/* */} + {t('menu.skills')} + + } + + { + isMenu('filelib') && + + {/* */} + {t('menu.knowledge')} + + } + + { + isMenu('model') && + + {/* */} + {t('menu.models')} + + } + { + isMenu('evaluation') && + + {/* */} + {t('menu.evaluation')} + + } + + { + isAdmin && <> + {/* */} {t('menu.system')} } + + { + isAdmin && <> + + {/* */} + {t('menu.log')} + + + }
-
+
说明
@@ -119,8 +200,8 @@ export default function MainLayout() { */} {/*
*/} -
- +
+ {/*
@@ -130,7 +211,18 @@ export default function MainLayout() {

{t('menu.logoutDescription')}

- + */} + + + {user.user_name} + +
+ }> + {t('menu.changePwd')} + {t('menu.logout')} +
@@ -143,7 +235,7 @@ export default function MainLayout() {
{/* // mobile */} -
+ {/*

{t('menu.forBestExperience')}

@@ -155,7 +247,17 @@ export default function MainLayout() {
-
+
*/} + +
+

{t('prompt')}

+

确认退出登录吗?

+
+ + +
+
+
}; @@ -183,3 +285,20 @@ const useLanguage = (user: User) => { t } } + +const useDelete = () => { + const [delShow, setDelShow] = useState(false) + const idRef = useRef(null) + + return { + delShow, + idRef, + close: () => { + setDelShow(false) + }, + delConfirm: (id) => { + // idRef.current = id + setDelShow(true) + } + } +} \ No newline at end of file diff --git a/src/pages/.DS_Store b/src/pages/.DS_Store index b0e260b..3f63732 100644 Binary files a/src/pages/.DS_Store and b/src/pages/.DS_Store differ diff --git a/src/pages/ChatAppPage/chatAssitantShare.tsx b/src/pages/ChatAppPage/chatAssitantShare.tsx new file mode 100644 index 0000000..792c4bf --- /dev/null +++ b/src/pages/ChatAppPage/chatAssitantShare.tsx @@ -0,0 +1,21 @@ +// 支持嵌iframe、适配移动端 +import { useMemo, useState } from "react"; +import { useLocation, useParams } from "react-router-dom"; +import { generateUUID } from "../../utils"; +import ChatPanne from "./components/ChatPanne"; + +export default function chatAssitantShare() { + const { id: assitId } = useParams() + + const wsUrl = `/api/v2/assistant/chat/${assitId}` + + const [data] = useState({ id: assitId, chatId: generateUUID(32), type: 'assistant' }) + + if (!assitId) return
请选择会话
+ + return
+
+ +
+
+}; diff --git a/src/pages/ChatAppPage/chatShare.tsx b/src/pages/ChatAppPage/chatShare.tsx index cb6a27c..c64b4f5 100644 --- a/src/pages/ChatAppPage/chatShare.tsx +++ b/src/pages/ChatAppPage/chatShare.tsx @@ -1,21 +1,8 @@ // 嵌iframe、适配移动端 -import { useEffect, useMemo, useState, useRef, useContext } from "react"; +import { useMemo, useState } from "react"; import { useLocation, useParams } from "react-router-dom"; -import { getFlowApi, readOnlineFlows } from "../../controllers/API/flow"; -import { FlowType } from "../../types/flow"; import { generateUUID } from "../../utils"; import ChatPanne from "./components/ChatPanne"; -import { useTranslation } from "react-i18next"; -import { deleteChatApi, getChatsApi } from "../../controllers/API"; -import { captureAndAlertRequestErrorHoc } from "../../controllers/request"; -import { useDebounce, useTable } from "../../util/hook"; -import { TabsContext } from "../../contexts/tabsContext"; -import SkillTemps from "../SkillPage/components/SkillTemps"; -import duihuaDel from "../../assets/chat/duihua-del.png"; -import robot from "../../assets/robot.png"; -import duihuaItemTop from "../../assets/chat/duihua-item-top.png"; -import duihuaItemJia from "../../assets/chat/duihua-item-+.png"; -import duihuaItemGuan from "../../assets/chat/duihua-item-x.png"; export default function chatShare() { @@ -24,81 +11,13 @@ export default function chatShare() { const searchParams = new URLSearchParams(location.search); const libId = searchParams.get('lib') const tweak = searchParams.get('tweak') - const { t } = useTranslation(); - const [open, setOpen] = useState(false) - // 对话列表 - const { chatList, chatsRef, addChat, deleteChat } = useChatList() - const queryString = useMemo(() => { - const params = []; - - if (libId) params.push(`knowledge_id=${libId}`); - if (tweak) params.push(`tweak=${tweak}`); - - return params.length > 0 ? `&${params.join('&')}` : ''; - }, [libId, tweak]) - const chatIdRef = useRef('') - const handlerSelectFlow = async (node: FlowType) => { - // 会话ID - chatIdRef.current = generateUUID(32) - setOpen(false) - // add list - addChat({ - "flow_name": node.name, - "flow_description": node.description, - "flow_id": node.id, - "chat_id": chatIdRef.current, - "create_time": "-", - "update_time": "-" - }) - - const flow = await getFlowApi(node.id) - setFlow(flow) - setChatId(chatIdRef.current) - // setFace(false) - } - // - const { flow: initFlow } = useContext(TabsContext); - const [flow, setFlow] = useState(null) - const { - data: onlineFlows, - loading, - search, - } = useTable({}, (param) => - readOnlineFlows(param.page, param.keyword).then((res) => { - return res; - }) - ); - const [chatId, setChatId] = useState('') - console.log(flowId,libId,tweak) - useEffect(() => { - flowId && getFlowApi(flowId).then(node => { - // 会话ID - setFlow(node) - setChatId(generateUUID(32)) - }) - }, [flowId]) - // select chat - const handleSelectChat = useDebounce(async (chat) => { - if (chat.chat_id === chatId) return - - const flow = initFlow?.id === chat.flow_id ? initFlow : await getFlowApi(chat.flow_id) - - // if (!flow) { - // setInputState({ lock: true, errorCode: '1004' }) - // clearHistory() - // return setFace(false) - // } - - setFlow(flow) - setChatId(chat.chat_id) - }, 100, false) const wsUrl = useMemo(() => { const params = []; if (libId) params.push(`knowledge_id=${libId}`); - if (tweak) params.push(`tweak=${tweak}`); - + if (tweak) params.push(`tweak=${encodeURIComponent(tweak)}`); + const paramStr = params.length > 0 ? `${params.join('&')}` : ''; return `/api/v2/chat/ws/${flowId}?type=L1&${paramStr}` @@ -110,84 +29,7 @@ export default function chatShare() { return
- {/*
-
-
setOpen(true)}>{t('chat.newChat')}
-
- -
-
-
- { - chatList.map((chat, i) => ( -
handleSelectChat(chat)}> -
- -
-

{chat.flow_name}

-
离线
-
-
-
- - -
- -
- )) - } -
-
*/} - {/* chat */} - {/* {flow - ?
- {flow && } -
- :
-

{t('chat.selectChat')}

-
} */} - +
- {/* {flow ? : null} */} - {/* 选择对话技能 */} -
- // flow ? : null -}; -/** - * 本地对话列表 - */ -const useChatList = () => { - const [chatList, setChatList] = useState([]) - const chatsRef = useRef(null) - - useEffect(() => { - getChatsApi().then(setChatList) - }, []) - - return { - chatList, - chatsRef, - addChat: (chat) => { - const newList = [chat, ...chatList] - // localStorage.setItem(ITEM_KEY, JSON.stringify(newList)) - setChatList(newList) - setTimeout(() => { - chatsRef.current.scrollTop = 1 - }, 0); - }, - deleteChat: (id: string) => { - // api - captureAndAlertRequestErrorHoc(deleteChatApi(id).then(res => { - setChatList(oldList => oldList.filter(item => item.chat_id !== id)) - })) - } - } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/src/pages/ChatAppPage/components/ChatPanne.tsx b/src/pages/ChatAppPage/components/ChatPanne.tsx index 7bbd7dd..d2e5423 100644 --- a/src/pages/ChatAppPage/components/ChatPanne.tsx +++ b/src/pages/ChatAppPage/components/ChatPanne.tsx @@ -38,7 +38,7 @@ import { NewApplicationIcon } from "@/components/bs-icons/newApplication"; import { useToast } from "@/components/bs-ui/toast/use-toast"; import { useAssistantStore } from "@/store/assistantStore"; -export default function ChatPanne({ customWsHost = '', data }) { +export default function ChatPanne({ customWsHost = '', appendHistory = false, data, version = 'v1' }) { const { id, chatId, type } = data const { t } = useTranslation() @@ -54,24 +54,33 @@ export default function ChatPanne({ customWsHost = '', data }) { const build = useBuild() // 消息列表 // const { messages, messagesRef, loadHistory, setChatHistory, initGuide, changeHistoryByScroll } = useMessages(chatId, flow) - const { messages, loadHistoryMsg, loadMoreHistoryMsg, changeChatId } = useMessageStore() + // const { messages, loadHistoryMsg, loadMoreHistoryMsg, changeChatId } = useMessageStore() + const { messages, loadHistoryMsg, loadMoreHistoryMsg, changeChatId, clearMsgs} = useMessageStore() useEffect(() => { return destroy }, []) const init = async () => { if (type === 'flow') { setAssistant(null) - const _flow = await getFlowApi(id) + const _flow = await getFlowApi(id, version) await build(_flow, chatId) - loadHistoryMsg(_flow.id, chatId, type) + // loadHistoryMsg(_flow.id, chatId, type) + version === 'v1' ? loadHistoryMsg(_flow.id, chatId, { + appendHistory, + lastMsg: t('chat.historicalMessages') + }, type) : clearMsgs() flowRef.current = _flow setFlow(_flow) changeChatId(chatId) // ws } else { flowRef.current = null setFlow(null) - const _assistant = await loadAssistantState(id) - loadHistoryMsg(_assistant.id, chatId, type) + const _assistant = await loadAssistantState(id, version) + // loadHistoryMsg(_assistant.id, chatId, type) + version === 'v1' ? loadHistoryMsg(_assistant.id, chatId, { + appendHistory, + lastMsg: t('chat.historicalMessages') + }, type) : clearMsgs() setAssistant(_assistant) changeChatId(chatId) // ws } @@ -92,7 +101,7 @@ export default function ChatPanne({ customWsHost = '', data }) { const getWsParamData = (action, msg) => { if (type === 'flow') { const _flow = flowRef.current - console.log(_flow) + // console.log(_flow) let inputs = tabsState[_flow.id].formKeysData.input_keys; const input = inputs.find((el: any) => !el.type) const inputKey = input ? Object.keys(input)[0] : ''; @@ -115,7 +124,7 @@ export default function ChatPanne({ customWsHost = '', data }) { const inputKey = 'input'; const msgData = { chatHistory: messages, - flow_id: '', + flow_id: data?.id || '', chat_id: chatId, name: assistant.name, description: assistant.desc, @@ -382,33 +391,16 @@ export default function ChatPanne({ customWsHost = '', data }) {
*/} loadMoreHistoryMsg(flow.id,"flow")} - type={{"avatar_img":flow.avatar_img,"avatar_color":flow.avatar_color,"type":type}} + loadMore={() => loadMoreHistoryMsg(flow.id, appendHistory, "flow")} + type={{"avatar_img":flow.avatar_img,"avatar_color":flow.avatar_color,"type":type,"id":flow.id}} inputForm={flowSate.isForm ? : null} /> - {/*
- { - messages.map((c, i) => setSouce(c)} - onDislike={(chatId) => { thumbRef.current?.openModal(chatId) }} - onReSend={(msg) => { - inputRef.current.value = msg - handleSend() - }} - onEdit={(msg) => { inputRef.current.value = msg; setInputEmpty(!msg) }} - onSearch={(msg) => window.open(appConfig.dialogQuickSearch + encodeURIComponent(msg))} - >) - } -
*/}
} {/* 助手会话 */} @@ -420,13 +412,15 @@ export default function ChatPanne({ customWsHost = '', data }) { {assistant.name}
*/} item)} guideWord={assistantState.guide_word} wsUrl={wsUrl} onBeforSend={getWsParamData} - loadMore={() => loadMoreHistoryMsg(assistant.id,"assistant")} - type={{"avatar_img":assistant.avatar_img,"avatar_color":assistant.avatar_color,"type":type}} + loadMore={() => loadMoreHistoryMsg(assistant.id, appendHistory, "assistant")} + type={{"avatar_img":assistant.avatar_img,"avatar_color":assistant.avatar_color,"type":type,"id":assistant.id}} inputForm={null} />
diff --git a/src/pages/ChatAppPage/index.tsx b/src/pages/ChatAppPage/index.tsx index 8830844..c1f3618 100755 --- a/src/pages/ChatAppPage/index.tsx +++ b/src/pages/ChatAppPage/index.tsx @@ -22,6 +22,8 @@ import SkillChatSheet from "@/components/bs-comp/sheets/SkillChatSheet"; import { TitleIconBg } from "@/components/bs-comp/cardComponent"; import npcIcon from "../../assets/npc/npcIcon.png"; import nengliIcon from "../../assets/npc/nengliIcon.png"; +import { useMessageStore } from "@/components/bs-comp/chatComponent/messageStore"; +import { formatDate } from "@/util/utils"; export default function SkillChatPage() { @@ -44,8 +46,29 @@ export default function SkillChatPage() { return res; }) ); + + // scroll load + const footerRef = useRef(null) + useEffect(function () { + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + onScrollLoad() + } + }); + }, { + // root: null, // 视口 + rootMargin: '0px', // 视口的边距 + threshold: 0.1 // 目标元素超过视口的10%即触发回调 + }); + + observer.observe(footerRef.current); + return () => footerRef.current && observer.unobserve(footerRef.current); + }, []) + // 对话列表 - const { chatList, chatId, chatsRef, setChatId, addChat, deleteChat } = useChatList() + // const { chatList, chatId, chatsRef, setChatId, addChat, deleteChat } = useChatList() + const { chatList, chatId, chatsRef, setChatId, addChat, deleteChat, onScrollLoad } = useChatList() // select flow const handlerSelectFlow = async (card) => { @@ -71,7 +94,7 @@ export default function SkillChatPage() { // select chat const handleSelectChat = useDebounce(async (chat) => { - console.log('chat.id :>> ', chat); + // console.log('chat.id :>> ', chat); if (chat.chat_id === chatId) return setSelelctChat({ id: chat.flow_id, chatId: chat.chat_id, type: chat.flow_type }) setChatId(chat.chat_id) @@ -113,15 +136,16 @@ export default function SkillChatPage() {
handleSelectChat(chat)}> -
+
{/* {(chat.flow_id == "06b1d374-ba97-46e6-8782-c56dec8dcc17" || chat.flow_id == "ed8e21f6-9757-43d0-b076-8c6e81bb0580") && } {chat.flow_id == "ca214b41-2b73-4585-b172-bf1e546cf6ec" && } {(chat.flow_id != "06b1d374-ba97-46e6-8782-c56dec8dcc17" && chat.flow_id != "ed8e21f6-9757-43d0-b076-8c6e81bb0580" && chat.flow_id != "ca214b41-2b73-4585-b172-bf1e546cf6ec") && } */} {/* */} -
-

{chat.flow_name}

+
+

{chat.flow_name}

{/*
离线
*/} +

{chat.latest_message?.message || ''}

@@ -134,10 +158,17 @@ export default function SkillChatPage() {
)) } +
{/* chat */} - + {/* */} + + {/* { + location + ? + : + } */} {/* 选择对话技能 */} { const [id, setId] = useState('') const [chatList, setChatList] = useState([]) const chatsRef = useRef(null) + const { chatId, messages } = useMessageStore() useEffect(() => { - getChatsApi().then(setChatList) - }, []) + // getChatsApi().then(setChatList) + if (messages.length > 0) { + let latest: any = messages[messages.length - 1] + // 有分割线取上一条 + if (latest.category === 'divider') latest = messages[messages.length - 2] || {} + setChatList(chats => chats.map(chat => (chat.chat_id === chatId) + ? { + ...chat, + update_time: latest.update_time || formatDate(new Date(), 'yyyy-MM-ddTHH:mm:ss'), + latest_message: { + ...chat.latest_message, + message: (latest.thought || latest.message[latest.chatKey] || latest.message).substring(0, 40) + } + } + : chat) + ) + } + }, [messages, chatId]) + const pageRef = useRef(0) + const onScrollLoad = async () => { + pageRef.current++ + const res = await getChatsApi(pageRef.current) + setChatList((chats => [...chats, ...res])) + } + return { chatList, chatId: id, @@ -178,6 +233,7 @@ const useChatList = () => { captureAndAlertRequestErrorHoc(deleteChatApi(id).then(res => { setChatList(oldList => oldList.filter(item => item.chat_id !== id)) })) - } + }, + onScrollLoad } } \ No newline at end of file diff --git a/src/pages/ChatAppPage/mobile/ChatPanneM.tsx b/src/pages/ChatAppPage/mobile/ChatPanneM.tsx index 59b02e3..0deaa92 100644 --- a/src/pages/ChatAppPage/mobile/ChatPanneM.tsx +++ b/src/pages/ChatAppPage/mobile/ChatPanneM.tsx @@ -36,7 +36,7 @@ interface Iprops { version?: string } -export default function ChatPanne({ customWsHost = '', data }) { +export default function ChatPanne({ customWsHost = '', appendHistory = false, data, version = 'v1' }) { const { id, chatId, type } = data const { t } = useTranslation() @@ -47,7 +47,8 @@ export default function ChatPanne({ customWsHost = '', data }) { // build // const build = useBuild(flow, chatId) const build = useBuild() - const { messages, loadHistoryMsg, loadMoreHistoryMsg, changeChatId } = useMessageStore() + // const { messages, loadHistoryMsg, loadMoreHistoryMsg, changeChatId } = useMessageStore() + const { messages, loadHistoryMsg, loadMoreHistoryMsg, changeChatId, clearMsgs} = useMessageStore() useEffect(() => { return destroy }, []) @@ -55,17 +56,25 @@ export default function ChatPanne({ customWsHost = '', data }) { const init = async () => { if (type === 'flow') { setAssistant(null) - const _flow = await getFlowApi(id) + const _flow = await getFlowApi(id, version) await build(_flow, chatId) - loadHistoryMsg(_flow.id, chatId, type) + // loadHistoryMsg(_flow.id, chatId, type) + version === 'v1' ? loadHistoryMsg(_flow.id, chatId, { + appendHistory, + lastMsg: t('chat.historicalMessages') + }, type) : clearMsgs() flowRef.current = _flow setFlow(_flow) changeChatId(chatId) // ws } else { flowRef.current = null setFlow(null) - const _assistant = await loadAssistantState(id) - loadHistoryMsg(_assistant.id, chatId, type) + const _assistant = await loadAssistantState(id, version) + // loadHistoryMsg(_assistant.id, chatId, type) + version === 'v1' ? loadHistoryMsg(_assistant.id, chatId, { + appendHistory, + lastMsg: t('chat.historicalMessages') + }, type) : clearMsgs() setAssistant(_assistant) changeChatId(chatId) // ws } @@ -350,13 +359,13 @@ export default function ChatPanne({ customWsHost = '', data }) { {/*
setIsDuiHua(!isDuiHua)}>
*/} -

{flow && flow.name}

+

{flow && flow.name}{assistant && assistant.name}

{/*
setIsNpcInfo(!isNpcInfo)}>
*/}
-
+
{ flow &&
{/* {flow && } */} @@ -366,33 +375,34 @@ export default function ChatPanne({ customWsHost = '', data }) {
*/} loadMoreHistoryMsg(flow.id, type)} - type={{"avatar_img":flow.avatar_img,"avatar_color":flow.avatar_color,"type":type}} + loadMore={() => loadMoreHistoryMsg(flow.id, appendHistory, "flow")} + type={{"avatar_img":flow.avatar_img,"avatar_color":flow.avatar_color,"type":type,"id":flow.id}} inputForm={flowSate.isForm ? : null} /> - {/*
- { - messages.map((c, i) => setSouce(c)} - onDislike={(chatId) => { thumbRef.current?.openModal(chatId) }} - onReSend={(msg) => { - inputRef.current.value = msg - handleSend() - }} - onEdit={(msg) => { inputRef.current.value = msg; setInputEmpty(!msg) }} - onSearch={(msg) => window.open(appConfig.dialogQuickSearch + encodeURIComponent(msg))} - >) - } -
*/} +
+ } + + {/* 助手会话 */} + { + assistant &&
+ item)} + guideWord={assistantState.guide_word} + wsUrl={wsUrl} + onBeforSend={getWsParamData} + loadMore={() => loadMoreHistoryMsg(assistant.id, appendHistory, "assistant")} + type={{"avatar_img":assistant.avatar_img,"avatar_color":assistant.avatar_color,"type":type,"id":assistant.id}} + inputForm={null} + />
}
diff --git a/src/pages/ChatAppPage/mobile/chatAssitantShare.tsx b/src/pages/ChatAppPage/mobile/chatAssitantShare.tsx new file mode 100644 index 0000000..c17e4c8 --- /dev/null +++ b/src/pages/ChatAppPage/mobile/chatAssitantShare.tsx @@ -0,0 +1,25 @@ +// 支持嵌iframe、适配移动端 +import { useMemo, useState } from "react"; +import { useLocation, useParams } from "react-router-dom"; +import { generateUUID } from "../../../utils"; +import ChatPanne from "./../components/ChatPanne"; +import ChatPanneM from "./ChatPanneM"; + +export default function chatAssitantShare() { + const { id: assitId } = useParams() + + const wsUrl = `/api/v2/assistant/chat/${assitId}` + + const [data] = useState({ id: assitId, chatId: generateUUID(32), type: 'assistant' }) + + if (!assitId) return
请选择会话
+ +// return
+//
+// +//
+//
+ return
+ +
+}; diff --git a/src/pages/ChatAppPage/mobile/chatShareM.tsx b/src/pages/ChatAppPage/mobile/chatShareM.tsx index d45c66f..9398fbd 100644 --- a/src/pages/ChatAppPage/mobile/chatShareM.tsx +++ b/src/pages/ChatAppPage/mobile/chatShareM.tsx @@ -105,21 +105,8 @@ export default function chatShare() { if (!flowId) return
请选择技能
return
- {/*
- -

对话名称

- -
*/} - {/* {flow - ?
- {flow && } -
- :
-

{t('chat.selectChat')}

-
} */} - +
- // flow ? : null }; /** * 本地对话列表 diff --git a/src/pages/EvaluationPage/EvaluationCreate.tsx b/src/pages/EvaluationPage/EvaluationCreate.tsx new file mode 100644 index 0000000..44cc63e --- /dev/null +++ b/src/pages/EvaluationPage/EvaluationCreate.tsx @@ -0,0 +1,376 @@ +import { QuestionMarkIcon } from "@/components/bs-icons/questionMark"; +import { UploadIcon } from "@/components/bs-icons/upload"; +import { Input } from "@/components/bs-ui/input"; +import { AssistantItemDB, getAssistantsApi } from "@/controllers/API/assistant"; +import { createEvaluationApi } from "@/controllers/API/evaluate"; +import { TypeModal } from "@/utils"; +import { SelectViewport } from "@radix-ui/react-select"; +import { debounce, find } from "lodash"; +import { ArrowLeft } from "lucide-react"; +import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; +import { useDropzone } from "react-dropzone"; +import { useTranslation } from "react-i18next"; +import { useNavigate, useParams } from "react-router-dom"; +import ShadTooltip from "@/components/ShadTooltipComponent"; +import { Button } from "@/components/bs-ui/button"; +import { Label } from "@/components/bs-ui/label"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/bs-ui/select"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/bs-ui/tooltip"; +import { alertContext } from "@/contexts/alertContext"; +import { TabsContext } from "@/contexts/tabsContext"; +import { readFlowsFromDatabase } from "@/controllers/API/flow"; +import PromptAreaComponent from "./PromptCom"; +import defaultPrompt from "./defaultPrompt"; +import { DownloadIcon, PlayIcon, QuestionMarkCircledIcon } from "@radix-ui/react-icons"; + +export default function EvaluatingCreate() { + const { t } = useTranslation(); + + const { id } = useParams(); + const { flow: nextFlow } = useContext(TabsContext); + const { setErrorData } = useContext(alertContext); + const flow = useMemo(() => { + return id ? nextFlow : null; + }, [nextFlow]); + const [selectedType, setSelectedType] = useState<"flow" | "assistant" | "">( + "" + ); + const [selectedKeyId, setSelectedKeyId] = useState(""); + const [selectedVersion, setSelectedVersion] = useState(""); + const [query, setQuery] = useState(""); + const [dataSource, setDataSource] = useState([]); + const [prompt, setPrompt] = useState(defaultPrompt); + const [fileName, setFileName] = useState(""); + + const [loading, setLoading] = useState(false); + const fileRef = useRef(null); + + const onDrop = (acceptedFiles) => { + fileRef.current = acceptedFiles[0]; + const size = fileRef.current.size + const errorlist = []; + + // 限制文件最大为 10M + if (size > 10 * 1024 * 1024) { + errorlist.push(t("evaluation.fileSizeLimit")); + fileRef.current = null + return handleError(errorlist); + } + + const names = acceptedFiles[0].name; + setFileName(names); + }; + + const { getRootProps, getInputProps } = useDropzone({ + accept: { + "application/*": [".csv"], + }, + useFsAccessApi: false, + onDrop, + maxFiles: 1, + }); + + const navigate = useNavigate(); + + const handleCreateEvaluation = async () => { + const errorlist = []; + if (!selectedType) errorlist.push(t("evaluation.enterExecType")); + if (!selectedKeyId) errorlist.push(t("evaluation.enterUniqueId")); + if (selectedType === "flow" && !selectedVersion) + errorlist.push(t("evaluation.enterVersion")); + if (!fileRef.current) errorlist.push(t("evaluation.enterFile")); + if (!prompt) errorlist.push(t("evaluation.enterPrompt")); + + if (errorlist.length) return handleError(errorlist); + setLoading(true); + try { + await createEvaluationApi({ + exec_type: selectedType, + unique_id: selectedKeyId, + version: selectedVersion, + prompt, + file: fileRef.current, + }); + navigate(-1); + } finally { + setLoading(false); + } + }; + + const handleError = (list) => { + setErrorData({ + title: t("prompt"), + list, + }); + }; + + // 助手技能发生变化 + const handleTypeChange = (type) => { + setQuery(""); + if (type === "flow") { + readFlowsFromDatabase(1, 100, "").then((_flow) => { + setDataSource(_flow.data); + }); + } else if (type === "assistant") { + getAssistantsApi(1, 100, "").then((data) => { + setDataSource((data as any).data as AssistantItemDB[]); + }); + } + }; + + const handleDownloadTemplate = () => { + const link = document.createElement("a"); + link.href = __APP_ENV__.BASE_URL + "/template.csv"; // 文件路径 + link.download = "template.csv"; // 下载时的文件名 + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }; + + const handleSearch = useCallback(debounce((value) => { + if (selectedType === "flow") { + readFlowsFromDatabase(1, 100, value).then((_flow) => { + setDataSource(_flow.data); + }); + } else if (selectedType === "assistant") { + getAssistantsApi(1, 100, value).then((data) => { + setDataSource((data as any).data as AssistantItemDB[]); + }); + } + }, 300), [selectedType]) + + const handleInputChange = (event) => { + setQuery(event.target.value); + handleSearch(event.target.value); + }; + + useEffect(() => { + return () => { + handleSearch.cancel(); + }; + }, [handleSearch]); + + return ( +
+
+
+ + + +
+ {/* form */} +
+

{t("evaluation.createTitle")}

+
+ {/* base form */} +
+
+ +
+ + + + {dataSource.map((item) => { + return ( + + {item.name} + + ); + })} + + + + + {selectedType === "flow" && ( + + )} +
+
+
+
+ +
+
+
+ +
+ + + {t("code.uploadFile")} + +
+ {fileName && ( +
{fileName}
+ )} + +
+ +
+
+
+
+ +
+
+ { + setPrompt(t); + }} + /> +
+
+ +
+
+
+ + +
+
+
+
+
+
+
+ ); +} diff --git a/src/pages/EvaluationPage/PromptCom.tsx b/src/pages/EvaluationPage/PromptCom.tsx new file mode 100644 index 0000000..060d34b --- /dev/null +++ b/src/pages/EvaluationPage/PromptCom.tsx @@ -0,0 +1,91 @@ +import { useContext, useEffect, useState } from "react"; +import { PopUpContext } from "@/contexts/popUpContext"; +import GenericModal from "@/modals/genericModal"; +import { TextAreaComponentType } from "@/types/components"; +import { TypeModal } from "@/utils"; + +import { ExternalLink } from "lucide-react"; + +export default function PromptAreaComponent({ + field_name, + setNodeClass, + nodeClass, + value, + onChange, + disabled, + editNode = false, + type = TypeModal.PROMPT, +}: TextAreaComponentType) { + const [myValue, setMyValue] = useState(value); + const { openPopUp } = useContext(PopUpContext); + useEffect(() => { + if (disabled) { + setMyValue(""); + onChange(""); + } + }, [disabled, onChange]); + + const handleSave = (t: string) => { + setMyValue(t); + onChange(t); + }; + + return ( +
+
+ { + openPopUp( + { + setMyValue(t); + onChange(t); + }} + nodeClass={nodeClass} + setNodeClass={setNodeClass} + /> + ); + }} + className={ + editNode + ? "input-edit-node input-dialog" + : (disabled ? " input-disable text-ring " : "") + + " whitespace-wrap input-primary text-[#fff]" + } + > + {myValue !== "" ? myValue : "enter your prompt"} + + +
+
+ ); +} diff --git a/src/pages/EvaluationPage/defaultPrompt.js b/src/pages/EvaluationPage/defaultPrompt.js new file mode 100644 index 0000000..09bddb5 --- /dev/null +++ b/src/pages/EvaluationPage/defaultPrompt.js @@ -0,0 +1,67 @@ +export default `Extract following from given question and ground truth + +Question:What powers the sun and what is its primary function? +Answer: The sun is powered by nuclear fission, similar to nuclear reactors on Earth, and its primary function is to provide light to the solar system. +Ground truth: The sun is actually powered by nuclear fusion, not fission. In its core, hydrogen atoms fuse to form helium, releasing a tremendous amount of energy. This energy is what lights up the sun and provides heat and light, essential for life on Earth. The sun's light also plays a critical role in Earth's climate system and helps to drive the weather and ocean currents. +Extracted statements: +[ +{{ + "statements that are present in both the answer and the ground truth": ["The sun's primary function is to provide light"], + "statements present in the answer but not found in the ground truth": ["The sun is powered by nuclear fission", "similar to nuclear reactors on Earth"], + "relevant statements found in the ground truth but omitted in the answer": ["The sun is powered by nuclear fusion, not fission", "In its core, hydrogen atoms fuse to form helium, releasing a tremendous amount of energy", "This energy provides heat and light, essential for life on Earth", "The sun's light plays a critical role in Earth's climate system", "The sun helps to drive the weather and ocean currents"] +}} +] + +Question: What is the boiling point of water? +Answer: The boiling point of water is 100 degrees Celsius at sea level. +Ground truth: The boiling point of water is 100 degrees Celsius (212 degrees Fahrenheit) at sea level, but it can change with altitude. +Extracted statements: +[ + {{ + "statements that are present in both the answer and the ground truth": ["The boiling point of water is 100 degrees Celsius at sea level"], + "statements present in the answer but not found in the ground truth": [], + "relevant statements found in the ground truth but omitted in the answer": ["The boiling point can change with altitude", "The boiling point of water is 212 degrees Fahrenheit at sea level"] + }} +] + +Question: 公司2021年的研发费用占营业收入的比例是多少? +Answer: 根据提供的信息,公司2021年的研发费用占营业收入的比例为15.86%。 +Ground truth: 根据公司招股书披露数据,公司2021年的研发费用占营业收入的比例为15.86%。 +Extracted statements: +[ + {{ + "statements that are present in both the answer and the ground truth": ["公司2021年的研发费用占营业收入的比例为15.86%"], + "statements present in the answer but not found in the ground truth": [], + "relevant statements found in the ground truth but omitted in the answer": [] + }} +] + +Question: 达梦2021年的息税折旧摊销前利润是多少? +Answer: 达梦2021年的息税折旧摊销前利润为49,189.87万元。 +Ground truth: 根据达梦数据库招股书披露数据,达梦2021年的息税折旧摊销前利润为49,189.85万元。 +Extracted statements: +[ + {{ + "statements that are present in both the answer and the ground truth": [], + "statements present in the answer but not found in the ground truth": ["达梦2021年的息税折旧摊销前利润为49,189.87万元"], + "relevant statements found in the ground truth but omitted in the answer": ["根据达梦数据库招股书披露数据,达梦2021年的息税折旧摊销前利润为49,189.85万元"] + }} +] + +Question: 达梦2022年的应收账款周转率是多少? +Answer: 根据提供的信息,无法得知达梦2022年的应收账款周转率。 +Ground truth: 很抱歉,达梦尚未披露2022年报数据。 +Extracted statements: +[ + {{ + "statements that are present in both the answer and the ground truth": ["无法得知达梦2022年的应收账款周转率"], + "statements present in the answer but not found in the ground truth": [], + "relevant statements found in the ground truth but omitted in the answer": [], + }} +] + + +Question:{question} +Answer: {answer} +Ground truth: {ground_truth} +Extracted statements:"""` \ No newline at end of file diff --git a/src/pages/EvaluationPage/index.tsx b/src/pages/EvaluationPage/index.tsx new file mode 100755 index 0000000..9cf829a --- /dev/null +++ b/src/pages/EvaluationPage/index.tsx @@ -0,0 +1,223 @@ +import { Button } from "@/components/bs-ui/button"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/bs-ui/table"; +import { Tabs, TabsContent } from "@/components/bs-ui/tabs"; +import { useNavigate } from "react-router-dom"; + +import { bsConfirm } from "@/components/bs-ui/alertDialog/useConfirm"; +import { Badge } from "@/components/bs-ui/badge"; +import AutoPagination from "@/components/bs-ui/pagination/autoPagination"; +import { + Evaluation, + deleteEvaluationApi, + getEvaluationApi, + getEvaluationUrlApi, +} from "@/controllers/API/evaluate"; +import { captureAndAlertRequestErrorHoc } from "@/controllers/request"; +import { useTable } from "@/util/hook"; +import { downloadFile } from "@/util/utils"; +import { map } from "lodash"; +import { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { + EvaluationScore, + EvaluationScoreLabelMap, + EvaluationStatusEnum, + EvaluationStatusLabelMap, + EvaluationType, + EvaluationTypeLabelMap, +} from "./types"; +import { checkSassUrl } from "../ChatAppPage/components/FileView"; + +export default function EvaluationPage() { + const navigate = useNavigate(); + const { t } = useTranslation(); + + const { + page, + pageSize, + data: datalist, + total, + loading, + setPage, + search, + reload, + } = useTable({ cancelLoadingWhenReload: true }, (param) => + getEvaluationApi(param.page, param.pageSize) + ); + + useEffect(() => { + const intervalId = setInterval(() => { + reload(); + }, 6000); // 每 6 秒轮询一次 + + return () => clearInterval(intervalId); + }, [reload]); + + const handleDelete = (id) => { + bsConfirm({ + title: t("prompt"), + desc: t("evaluation.confirmDeleteEvaluation"), + onOk(next) { + captureAndAlertRequestErrorHoc( + deleteEvaluationApi(id).then((res) => { + reload(); + }) + ); + next(); + }, + }); + }; + + const handleDownload = async (el) => { + const { url } = await getEvaluationUrlApi(el.result_file_path); + await downloadFile(checkSassUrl(url), el.file_name); + }; + + return ( +
+ {loading && ( +
+ +
+ )} +
+

{t("evaluation.evaluationCollection")}

+ + +
+ +
+ + + + + {t("evaluation.id")} + + + {t("evaluation.filename")} + + {t("evaluation.skillAssistant")} + + {t("evaluation.status")} + + + {t("evaluation.score")} + + + {t("createTime")} + + + {t("operations")} + + + + + + {datalist.map((el: Evaluation) => ( + + {el.id} + {el.file_name || "--"} + +
+ + { + EvaluationTypeLabelMap[EvaluationType[el.exec_type]] + .label + } + +   + + {el.unique_name} + +   + + {el.version_name} + +
+
+ + {!!el.status && ( + + {EvaluationStatusLabelMap[el.status].label} + {el.status === EvaluationStatusEnum.running + ? ` ${el.progress}` + : null} + + )} + + +
+ {el.result_score + ? map(el.result_score, (value, key) => { + return ( + + { + EvaluationScoreLabelMap[ + EvaluationScore[key] + ].label + } + :{value}  + + ); + }) + : "-"} +
+
+ + {el.create_time.replace("T", " ") || "--"} + + +
+ + +
+
+
+ ))} +
+
+
+ +
+
+
+
+ setPage(newPage)} + /> +
+
+
+ ); +} diff --git a/src/pages/EvaluationPage/types.ts b/src/pages/EvaluationPage/types.ts new file mode 100644 index 0000000..b9e1d05 --- /dev/null +++ b/src/pages/EvaluationPage/types.ts @@ -0,0 +1,51 @@ +export enum EvaluationType { + flow = "flow", + assistant = "assistant", +} + +export const EvaluationTypeLabelMap = { + [EvaluationType.flow]: { + label: "能力", + }, + [EvaluationType.assistant]: { + label: "NPC", + }, +}; + +export enum EvaluationScore { + answer_f1 = "answer_f1", + answer_precision = "answer_precision", + answer_recall = "answer_recall", +} + +export const EvaluationScoreLabelMap = { + [EvaluationScore.answer_f1]: { + label: "F1", + }, + [EvaluationScore.answer_precision]: { + label: "准确率", + }, + [EvaluationScore.answer_recall]: { + label: "召回率", + }, +}; + +export enum EvaluationStatusEnum { + running = 1, + failed = 2, + success = 3, +} +export const EvaluationStatusLabelMap = { + [EvaluationStatusEnum.running]: { + label: "进行中", + variant: "secondary", + }, + [EvaluationStatusEnum.failed]: { + label: "失败", + variant: "destructive", + }, + [EvaluationStatusEnum.success]: { + label: "成功", + variant: "default", + }, +}; diff --git a/src/pages/LogPage/index.tsx b/src/pages/LogPage/index.tsx new file mode 100644 index 0000000..3d1ec6a --- /dev/null +++ b/src/pages/LogPage/index.tsx @@ -0,0 +1,226 @@ +import { Button } from "@/components/bs-ui/button"; +import { DatePicker } from "@/components/bs-ui/calendar/datePicker"; +import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/bs-ui/select"; +import MultiSelect from "@/components/bs-ui/select/multi"; +import { getActionsApi, getActionsByModuleApi, getLogsApi, getModulesApi, getOperatorsApi } from "@/controllers/API/log"; +import { getUserGroupsApi } from "@/controllers/API/user"; +import { useTable } from "@/util/hook"; +import { formatDate } from "@/util/utils"; +import { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import AutoPagination from "../../components/bs-ui/pagination/autoPagination"; +import { + Table, + TableBody, + TableCell, + TableFooter, + TableHead, + TableHeader, + TableRow +} from "../../components/bs-ui/table"; +import { transformEvent, transformModule, transformObjectType } from "./utils"; + +const useGroups = () => { + const [groups, setGroups] = useState([]) + const loadData = () => { + getUserGroupsApi().then((res:any) => setGroups(res.records)) + } + return { groups, loadData } +} +const useModules = () => { + const [modules, setModules] = useState([]) + const loadModules = () => { + getModulesApi().then(res => setModules(res.data)) + } + return { modules, loadModules } +} + +export default function index() { + const { t } = useTranslation() + const { users, selectedRef, loadUsers, searchUser } = useUsers() + const { groups, loadData } = useGroups() + const { modules, loadModules } = useModules() + const { page, pageSize, data: logs, total, loading, setPage, filterData } = useTable({ pageSize: 20 }, (param) => + getLogsApi({...param}) + ) + const init = { + userIds: [], + groupId: '', + start: undefined, + end: undefined, + moduleId: '', + action: '' + } + + const [actions, setActions] = useState([]) + const [keys, setKeys] = useState({...init}) + + const handleActionOpen = async () => { + setActions((keys.moduleId ? await getActionsByModuleApi(keys.moduleId) : await getActionsApi())) + } + const handleSearch = () => { + const startTime = keys.start && formatDate(keys.start, 'yyyy-MM-dd HH:mm:ss') + const endTime = keys.end && formatDate(keys.end, 'yyyy-MM-dd HH:mm:ss').replace('00:00:00','23:59:59') + filterData({...keys, start:startTime, end:endTime}) + } + const handleReset = () => { + setKeys({...init}) + filterData(init) + } + useEffect(() => { + loadUsers() + },[]) + + return
+
+

{t('log.auditManagement')}

+
+
+ { searchUser(key); selectedRef.current = keys.userIds}} + onChange={(values) => {setKeys({...keys,userIds:values}); console.log(values)}} + > +
+
+ +
+
+ setKeys({...keys,start:t})} /> +
+
+ setKeys({...keys,end:t})} /> +
+
+ +
+
+ +
+
+ + +
+
+ + + + {t('log.auditId')} + {t('log.username')} + {t('log.operationTime')} + {t('log.systemModule')} + {t('log.operationAction')} + {t('log.objectType')} + {t('log.operationObject')} + {t('log.ipAddress')} + {t('log.remark')} + + + { + loading + ?
+ +
+ : + {logs.map((log:any) => ( + + {log.id} +
{log.operator_name}
+ {log.create_time.replace('T', ' ')} + {transformModule(log.system_id)} + {transformEvent(log.event_type)} + {transformObjectType(log.object_type)} +
{log.object_name || '无'}
+ {log.ip_address} + +
{log.note?.replace('编辑后', `\n编辑后`) || '无'}
+
+
+ ))} +
+ } + + {!logs.length && + + {t('build.empty')} + + } +
+ {!logs.length &&
} +
+ {/* 分页 */} + {/* */} +
+ setPage(newPage)} + /> +
+
+}; + + +const useUsers = () => { + const [users, setUsers] = useState([]); + const userRef = useRef([]) + const selectedRef = useRef([]) + + const loadUsers = () => { + getOperatorsApi().then(res => { + const options = res.map((u:any) => ({label:u.user_name, value:u.user_id})) + userRef.current = options + setUsers(options) + }) + } + const search = (name) => { + const newUsers = userRef.current.filter(u => u.label.toLowerCase().includes(name.toLowerCase()) + || selectedRef.current.includes(u.value)) + setUsers(newUsers) + } + + return { + users, + selectedRef, + loadUsers, + searchUser(name) { + search(name) + } + } +} \ No newline at end of file diff --git a/src/pages/LogPage/utils/index.ts b/src/pages/LogPage/utils/index.ts new file mode 100644 index 0000000..32c511d --- /dev/null +++ b/src/pages/LogPage/utils/index.ts @@ -0,0 +1,48 @@ +export function transformModule(system: string): string { + switch(system) { + case 'chat': return '会话' + case 'build': return '构建' + case 'knowledge': return '知识库' + case 'system': return '系统' + default: return '转换失败' + } +} + +export function transformEvent(event: string): string { + switch(event) { + case 'create_chat': return '新建会话'; + case 'delete_chat': return '删除会话'; + case 'create_build': return '新建应用'; + case 'update_build': return '编辑应用'; + case 'delete_build': return '删除应用'; + case 'create_knowledge': return '新建知识库'; + case 'delete_knowledge': return '删除知识库'; + case 'upload_file': return '知识库上传文件'; + case 'delete_file': return '知识库删除文件'; + case 'update_user': return '用户编辑'; + case 'forbid_user': return '停用用户'; + case 'recover_user': return '启用用户'; + case 'create_user_group': return '新建用户组'; + case 'delete_user_group': return '删除用户组'; + case 'update_user_group': return '编辑用户组'; + case 'create_role': return '新建角色'; + case 'delete_role': return '删除角色'; + case 'update_role': return '编辑角色'; + case 'user_login': return '用户登录'; + default: return '转换失败' + } +} + +export function transformObjectType(object: string): string { + switch(object) { + case 'none': return '无' + case 'flow': return '技能' + case 'assistant': return '助手' + case 'knowledge': return '知识库' + case 'file': return '文件' + case 'user_conf': return '用户配置' + case 'user_group_conf': return '用户组配置' + case 'role_conf': return '角色配置' + default: return '转换失败' + } +} \ No newline at end of file diff --git a/src/pages/LoginPage/UserPwdModal.tsx b/src/pages/LoginPage/UserPwdModal.tsx new file mode 100644 index 0000000..e6e494e --- /dev/null +++ b/src/pages/LoginPage/UserPwdModal.tsx @@ -0,0 +1,90 @@ +import { Button } from "@/components/bs-ui/button"; +import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/bs-ui/dialog"; +import { PasswordInput } from "@/components/bs-ui/input"; +import { forwardRef, useImperativeHandle, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +// import { resetUserPasswordApi } from "../controllers/API/user"; // 假设这是重置密码的API函数 +import { useToast } from "@/components/bs-ui/toast/use-toast"; +import { resetPasswordApi } from "@/controllers/API/user"; +import { captureAndAlertRequestErrorHoc } from "@/controllers/request"; +import { handleEncrypt, PWD_RULE } from "./utils"; + +interface UserPwdModalProps { + // onSuccess: () => void; +} + +interface UserPwdModalRef { + open: (userId: string) => void; +} + +const UserPwdModal = forwardRef((props, ref) => { + const { t } = useTranslation(); + const { message } = useToast(); + + const [editShow, setEditShow] = useState(false); + const [error, setError] = useState(''); + const passwordRef = useRef(null) + const userIdRef = useRef(null); + + useImperativeHandle(ref, () => ({ + open: (userId) => { + userIdRef.current = userId; + setEditShow(true); + } + })); + + const handleSubmit = async () => { + // if (!PWD_RULE.test(passwordRef.current.value)) { + // return setError(t('login.passwordError')) + // } + const errors:string[] = [] + if(!passwordRef.current.value) errors.push(t('resetPassword.notEmpty')) + if (!/.{6,}/.test(passwordRef.current.value)) errors.push(t('resetPassword.newPasswordTooShort')) + if (!PWD_RULE.test(passwordRef.current.value)) errors.push(t('login.passwordError')) + if(errors.length) return message({title: t('prompt'), variant: 'error', description: errors}) + + const cryptPwd = await handleEncrypt(passwordRef.current.value) + const res = await captureAndAlertRequestErrorHoc(resetPasswordApi(userIdRef.current, cryptPwd)) + if (res === null) { + message({ + title: `${t('prompt')}`, + variant: 'success', + description: [t('resetPassword.adminResetSuccess')] + }); + setEditShow(false); + // onSuccess(); + } + }; + + return ( + + + + {t('resetPassword.resetButton')} + +
+
+ + passwordRef.current.value = e.target.value} + /> + {error &&

{error}

} +
+
+ + + + + + +
+
+ ); +}); + +export default UserPwdModal; diff --git a/src/pages/LoginPage/icons/wxpro.svg b/src/pages/LoginPage/icons/wxpro.svg new file mode 100644 index 0000000..ed87cc0 --- /dev/null +++ b/src/pages/LoginPage/icons/wxpro.svg @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/src/pages/LoginPage/login.tsx b/src/pages/LoginPage/login.tsx new file mode 100644 index 0000000..df4dc7c --- /dev/null +++ b/src/pages/LoginPage/login.tsx @@ -0,0 +1,223 @@ +import { BookOpenIcon } from '@/components/bs-icons/bookOpen'; +import { GithubIcon } from '@/components/bs-icons/github'; +import { useContext, useEffect, useRef, useState } from "react"; +import { useTranslation } from 'react-i18next'; +import json from "../../../package.json"; +import { Button } from "../../components/bs-ui/button"; +import { Input } from "../../components/bs-ui/input"; +// import { alertContext } from "../contexts/alertContext"; +import { useToast } from "@/components/bs-ui/toast/use-toast"; +import { useNavigate } from 'react-router-dom'; +import { getCaptchaApi, loginApi, registerApi } from "../../controllers/API/user"; +import { captureAndAlertRequestErrorHoc } from "../../controllers/request"; +import LoginBridge from './loginBridge'; +import { PWD_RULE, handleEncrypt, handleLdapEncrypt } from './utils'; +import { locationContext } from '@/contexts/locationContext'; +import { ldapLoginApi } from '@/controllers/API/pro'; + +export const LoginPage = () => { + // const { setErrorData, setSuccessData } = useContext(alertContext); + const { t, i18n } = useTranslation(); + const { message, toast } = useToast() + const navigate = useNavigate() + const { appConfig } = useContext(locationContext) + + const isLoading = false + + const mailRef = useRef(null) + const pwdRef = useRef(null) + const agenPwdRef = useRef(null) + + // login or register + const [showLogin, setShowLogin] = useState(true) + + // captcha + const captchaRef = useRef(null) + const [captchaData, setCaptchaData] = useState({ captcha_key: '', user_capthca: false, captcha: '' }); + + useEffect(() => { + fetchCaptchaData(); + }, []); + + const fetchCaptchaData = () => { + getCaptchaApi().then(setCaptchaData) + }; + + const [isLDAP, setIsLDAP] = useState(false) + const handleLogin = async () => { + const error = [] + const [mail, pwd] = [mailRef.current.value, pwdRef.current.value] + if (!mail) error.push(t('login.pleaseEnterAccount')) + if (!pwd) error.push(t('login.pleaseEnterPassword')) + if (captchaData.user_capthca && !captchaRef.current.value) error.push(t('login.pleaseEnterCaptcha')) + if (error.length) return message({ + title: `${t('prompt')}`, + variant: 'warning', + description: error + }) + // if (error.length) return setErrorData({ + // title: `${t('prompt')}:`, + // list: error, + // }); + + const encryptPwd = isLDAP ? await handleLdapEncrypt(pwd) : await handleEncrypt(pwd) + captureAndAlertRequestErrorHoc( + (isLDAP + ? ldapLoginApi(mail, encryptPwd) + : loginApi(mail, encryptPwd, captchaData.captcha_key, captchaRef.current?.value) + ).then((res: any) => { + localStorage.setItem('ws_token', res.access_token) + localStorage.setItem('isLogin', '1') + location.href = __APP_ENV__.BASE_URL + '/' + }) + ), (error) => { + if (error.indexOf('过期') !== -1) { // 有时间改为 code 判断 + localStorage.setItem('account', mail) + navigate('/reset', { state: { noback: true } }) + } + } + + fetchCaptchaData() + } + + const handleRegister = async () => { + const error = [] + const [mail, pwd, apwd] = [mailRef.current.value, pwdRef.current.value, agenPwdRef.current.value] + if (!mail) { + error.push(t('login.pleaseEnterAccount')) + } + if (mail.length < 3) { + error.push(t('login.accountTooShort')) + } + if (!/.{7,}/.test(pwd)) { + error.push(t('login.passwordTooShort')) + } + if (!PWD_RULE.test(pwd)) { + error.push(t('login.passwordError')) + } + if (pwd !== apwd) { + error.push(t('login.passwordMismatch')) + } + if (captchaData.user_capthca && !captchaRef.current.value) { + error.push(t('login.pleaseEnterCaptcha')) + } + if (error.length) { + return message({ + title: `${t('prompt')}`, + variant: 'warning', + description: error + }) + } + const encryptPwd = await handleEncrypt(pwd) + captureAndAlertRequestErrorHoc(registerApi(mail, encryptPwd, captchaData.captcha_key, captchaRef.current?.value).then(res => { + // setSuccessData({ title: t('login.registrationSuccess') }) + message({ + title: `${t('prompt')}`, + variant: 'success', + description: [t('login.registrationSuccess')] + }) + pwdRef.current.value = '' + setShowLogin(true) + })) + + fetchCaptchaData() + } + + return
+
+
+ logo_picture + logo_picture +
+
+
+
+ + + {t('login.slogen')} +
+
+
+ +
+
+ e.key === 'Enter' && showLogin && handleLogin()} /> +
+ { + !showLogin &&
+ +
+ } + { + captchaData.user_capthca && (
+ + captcha +
+ ) + } + { + showLogin ? <> + + + : + <> + + + + } + {appConfig.isPro && } +
+
+ v{json.version} + {!appConfig.noFace && } +
+
+
+
+
+}; \ No newline at end of file diff --git a/src/pages/LoginPage/loginBridge.tsx b/src/pages/LoginPage/loginBridge.tsx new file mode 100644 index 0000000..b284ff6 --- /dev/null +++ b/src/pages/LoginPage/loginBridge.tsx @@ -0,0 +1,35 @@ +import Separator from "@/components/bs-comp/chatComponent/Separator"; +import { Button } from "@/components/bs-ui/button"; +import { getSSOurlApi } from "@/controllers/API/pro"; +import { useEffect, useRef, useState } from "react"; +//@ts-ignore +import { ReactComponent as Wxpro } from "./icons/wxpro.svg"; +import { useTranslation } from "react-i18next"; + +export default function LoginBridge({ onHasLdap }) { + + const { t } = useTranslation() + + const urlRef = useRef('') + const [hasSSO, setHasSSO] = useState(false) + useEffect(() => { + getSSOurlApi().then((urls: any) => { + urlRef.current = urls.wx + setHasSSO(!!urls.sso) + urls.ldap && onHasLdap(true) + }) + }, []) + + const clickQwLogin = () => { + location.href = urlRef.current + } + + if (!hasSSO) return null + + return
+ +
+ +
+
+}; diff --git a/src/pages/LoginPage/resetPwd.tsx b/src/pages/LoginPage/resetPwd.tsx new file mode 100644 index 0000000..89c0771 --- /dev/null +++ b/src/pages/LoginPage/resetPwd.tsx @@ -0,0 +1,131 @@ +import { useToast } from "@/components/bs-ui/toast/use-toast"; +import { ArrowLeftIcon } from '@radix-ui/react-icons'; +import { useEffect, useRef } from "react"; +import { useTranslation } from 'react-i18next'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { Button } from "../../components/bs-ui/button"; +import { PasswordInput } from "../../components/bs-ui/input"; +import { loggedChangePasswordApi, changePasswordApi } from "../../controllers/API/user"; +import { captureAndAlertRequestErrorHoc } from "../../controllers/request"; +import { PWD_RULE, handleEncrypt } from './utils'; +import reset from "../../assets/Login/reset.png"; +import loginIcon from "../../assets/Login/login-icon.png"; + +export const ResetPwdPage = () => { + const { t } = useTranslation(); + const { message } = useToast(); + const { state } = useLocation() + const navigate = useNavigate() + + useEffect(() => { + state?.noback && message({ + title: `${t('prompt')}`, + variant: 'warning', + description: '您的密码已过期,请及时修改' + }) + }, []) + + const currentPwdRef = useRef(null); + const newPwdRef = useRef(null); + const confirmPwdRef = useRef(null); + + const handleResetPassword = async () => { + const errors: string[] = []; + const [currentPwd, newPwd, confirmPwd] = [ + currentPwdRef.current?.value, + newPwdRef.current?.value, + confirmPwdRef.current?.value + ]; + + if (!currentPwd) errors.push(t('resetPassword.pleaseEnterCurrentPassword')); + if (!newPwd) errors.push(t('resetPassword.pleaseEnterNewPassword')); + if (!confirmPwd) errors.push(t('resetPassword.pleaseEnterConfirmPassword')); + if (!/.{7,}/.test(newPwd)) errors.push(t('resetPassword.newPasswordTooShort')); + if (!PWD_RULE.test(newPwd)) errors.push(t('login.passwordError')) + if (newPwd !== confirmPwd) errors.push(t('resetPassword.passwordMismatch')); + + if (errors.length) { + return message({ + title: `${t('prompt')}`, + variant: 'warning', + description: errors + }); + } + + const account = localStorage.getItem('account'); + const encryptCurrentPwd = await handleEncrypt(currentPwd); + const encryptNewPwd = await handleEncrypt(newPwd); + + console.log(state) + const res = await captureAndAlertRequestErrorHoc(state ? changePasswordApi(account, encryptCurrentPwd, encryptNewPwd) : loggedChangePasswordApi(encryptCurrentPwd, encryptNewPwd)) + if (res === null) { + message({ + title: `${t('prompt')}`, + variant: 'success', + description: [t('resetPassword.passwordResetSuccess')] + }); + // Clear input fields + if (currentPwdRef.current) currentPwdRef.current.value = ''; + if (newPwdRef.current) newPwdRef.current.value = ''; + if (confirmPwdRef.current) confirmPwdRef.current.value = ''; + // if (!state?.noback) { + navigate(-1); + // } + } + // })); + }; + + return ( +
+
+ {!state?.noback && } +
+
+ + {/* small_logo */} + {t('resetPassword.slogen')} +
+
+
+ +
+
+ +
+
+ +
+ +
+
+
+
+ ); +}; + +export default ResetPwdPage; diff --git a/src/pages/LoginPage/utils.ts b/src/pages/LoginPage/utils.ts new file mode 100644 index 0000000..6178ac1 --- /dev/null +++ b/src/pages/LoginPage/utils.ts @@ -0,0 +1,20 @@ +import { getPublicKeyApi } from "@/controllers/API/user"; +import { getKeyApi } from "@/controllers/API/pro"; +import { JSEncrypt } from 'jsencrypt'; + +export const handleEncrypt = async (pwd: string): Promise => { + const { public_key } = await getPublicKeyApi(); + const encrypt = new JSEncrypt(); + encrypt.setPublicKey(public_key); + return encrypt.encrypt(pwd) as string; +}; + +export const handleLdapEncrypt = async (pwd: string): Promise => { + const public_key:any = await getKeyApi(); + const encrypt = new JSEncrypt(); + encrypt.setPublicKey(public_key); + return encrypt.encrypt(pwd) as string; +}; + +// export const PWD_RULE = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{8,}$/ +export const PWD_RULE = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d].{7,}$/ \ No newline at end of file diff --git a/src/pages/Page403.tsx b/src/pages/Page403.tsx new file mode 100644 index 0000000..79206f6 --- /dev/null +++ b/src/pages/Page403.tsx @@ -0,0 +1,24 @@ +import React from 'react'; + +export default function Page403() { + return ( +
+
+

+ 403 +

+

+ 您无权访问该页面 +

+
+ +
+
+
+ ); +}; diff --git a/src/pages/SkillPage/components/editAssistant/Setting.tsx b/src/pages/SkillPage/components/editAssistant/Setting.tsx index 72dae60..627a7c6 100644 --- a/src/pages/SkillPage/components/editAssistant/Setting.tsx +++ b/src/pages/SkillPage/components/editAssistant/Setting.tsx @@ -29,6 +29,7 @@ import { Link } from "react-router-dom"; import KnowledgeBaseMulti from "./KnowledgeBaseMulti"; import ModelSelect from "./ModelSelect"; import Temperature from "./Temperature"; +import KnowledgeSelect from "@/components/bs-comp/selectComponent/knowledge"; export default function Setting() { const { t } = useTranslation(); @@ -154,7 +155,7 @@ export default function Setting() {
- dispatchAssistant("setting", { knowledge_list: vals }) @@ -173,7 +174,28 @@ export default function Setting() {
)} - + */} + ({ label: el.name, value: el.id }))} + onChange={(vals) => + dispatchAssistant("setting", { knowledge_list: vals.map(el => ({ name: el.label, id: el.value })) }) + } + > + {(reload) => ( +
+ + + + +
+ )} +
diff --git a/src/pages/SkillPage/components/editAssistant/TestChat.tsx b/src/pages/SkillPage/components/editAssistant/TestChat.tsx index d5b2cb0..fdedb36 100644 --- a/src/pages/SkillPage/components/editAssistant/TestChat.tsx +++ b/src/pages/SkillPage/components/editAssistant/TestChat.tsx @@ -52,7 +52,7 @@ export default function TestChat({ assisId, guideQuestion }) { guideWord='' wsUrl={wsUrl} onBeforSend={getWsParamData} - type={{"avatar_img":assistantState.avatar_img,"avatar_color":assistantState.avatar_color,"type":"assistant"}} + type={{"avatar_img":assistantState.avatar_img,"avatar_color":assistantState.avatar_color,"type":"assistant","id":assistantState.id}} >
}; diff --git a/src/pages/SkillPage/editAssistant.tsx b/src/pages/SkillPage/editAssistant.tsx index 1f1d4ab..963bc32 100644 --- a/src/pages/SkillPage/editAssistant.tsx +++ b/src/pages/SkillPage/editAssistant.tsx @@ -21,7 +21,7 @@ export default function editAssistant() { const { startNewRound, insetSystemMsg, insetBsMsg, setShowGuideQuestion } = useMessageStore() useEffect(() => { - loadAssistantState(assisId).then((res) => { + loadAssistantState(assisId, 'v1').then((res) => { setShowGuideQuestion(true) setGuideQuestion(res.guide_question?.filter((item) => item) || []) res.guide_word && insetBsMsg(res.guide_word) diff --git a/src/pages/SkillPage/tabAssistant.tsx b/src/pages/SkillPage/tabAssistant.tsx index 54b072b..68a17e3 100644 --- a/src/pages/SkillPage/tabAssistant.tsx +++ b/src/pages/SkillPage/tabAssistant.tsx @@ -163,6 +163,10 @@ export default function Assistants() { 编辑
} + {openSwitch &&
{setIsShareLink(!isShareLink);setIsShareLinkData(location.origin + "/chat/assistant/" + item.id)}}> + +
} + {item.write &&
delConfirm(item)}>
} diff --git a/src/pages/SystemPage/.DS_Store b/src/pages/SystemPage/.DS_Store new file mode 100644 index 0000000..c541456 Binary files /dev/null and b/src/pages/SystemPage/.DS_Store differ diff --git a/src/pages/SystemPage/components/.DS_Store b/src/pages/SystemPage/components/.DS_Store new file mode 100644 index 0000000..92d05aa Binary files /dev/null and b/src/pages/SystemPage/components/.DS_Store differ diff --git a/src/pages/SystemPage/components/CreateUser.tsx b/src/pages/SystemPage/components/CreateUser.tsx new file mode 100644 index 0000000..9e52eeb --- /dev/null +++ b/src/pages/SystemPage/components/CreateUser.tsx @@ -0,0 +1,102 @@ +import { Button } from "@/components/bs-ui/button" +import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/bs-ui/dialog" +import { Input, PasswordInput } from "@/components/bs-ui/input" +import { Label } from "@/components/bs-ui/label" +import { useToast } from "@/components/bs-ui/toast/use-toast" +import { generateUUID } from "@/components/bs-ui/utils" +import { createUserApi } from "@/controllers/API/user" +import { captureAndAlertRequestErrorHoc } from "@/controllers/request" +import { handleEncrypt, PWD_RULE } from "@/pages/LoginPage/utils" +import { copyText } from "@/utils" +import { PlusIcon } from "@radix-ui/react-icons" +import { useState } from "react" +import { useTranslation } from "react-i18next" +import UserRoleItem from "./UserRoleItem" + +export default function CreateUser({open, onClose, onSave}) { + const { t } = useTranslation() + const { message } = useToast() + const initItems = { key:generateUUID(8), groupId:'', roles:[] } + const initUser = { + user_name:'', + password:'', + } + + const [items, setItems] = useState([initItems]) + const [form, setForm] = useState(initUser) + + const handleCancel = () => { + onClose(false) + setItems([initItems]) + setForm(initUser) + } + const errors = [] + const handleConfirm = async () => { + if(form.user_name === '') errors.push('用户名不可为空') + if(form.user_name.length > 30) errors.push('用户名最长 30 个字符') + if(!PWD_RULE.test(form.password)) errors.push('至少 7 个字符,需包含数字、字母') + if(items.every(item => item.roles.length === 0)) errors.push('至少选择一个角色') + if(errors.length > 0) return message({title:t('prompt'), description:errors, variant:'warning'}) + + const encryptPwd = await handleEncrypt(form.password) + const group_roles = items.map(item => ({ + group_id: Number(item.groupId), + role_ids: item.roles.map(r => Number(r)) + })) + captureAndAlertRequestErrorHoc(createUserApi(form.user_name, encryptPwd, group_roles).then(() => { + copyText(`用户名:${form.user_name},初始密码:${form.password}`).then(() => + message({title:t('prompt'), description:'创建用户成功!已复制用户名和初始密码到剪贴板', variant:'success'})) + onClose(false) + setItems([initItems]) + setForm(initUser) + onSave() + })) + } + + const handleChangeRoleItems = (index, groupId, roles) => { + setItems(items => items.map((item, i) => { + return i === index ? {...item, groupId:groupId[0], roles} : item + })) + } + + return onClose(b)}> + + + 创建用户 + +
+
+ + setForm({...form, user_name:e.target.value})} + placeholder="后续使用此用户名进行登录,用户名不可修改" className="h-[48px] npcInput mt-[10px]"/> +
+
+ + setForm({...form, password:e.target.value})} inputClassName="h-[48px] npcInput mt-[10px]"/> +
+
+ +
+ {items.map((item, index) => 1} + selectedRoles={[]} + onChange={(groupId, roles) => handleChangeRoleItems(index, groupId, roles)} + onDelete={() => setItems(items => items.filter((el, i) => i !== index))} + />)} +
+ +
+
+ + + + +
+
+} \ No newline at end of file diff --git a/src/pages/SystemPage/components/EditRole.tsx b/src/pages/SystemPage/components/EditRole.tsx index eb527e2..fdcc6c1 100644 --- a/src/pages/SystemPage/components/EditRole.tsx +++ b/src/pages/SystemPage/components/EditRole.tsx @@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next"; import { Button } from "../../../components/bs-ui/button"; import { Input, SearchInput } from "../../../components/bs-ui/input"; import AutoPagination from "../../../components/bs-ui/pagination/autoPagination"; +// import { Switch } from "../../../components/bs-ui/switch"; import { Switch } from "../../../components/ui/switch"; import { Table, @@ -13,27 +14,35 @@ import { TableRow } from "../../../components/bs-ui/table"; import { alertContext } from "../../../contexts/alertContext"; -import { createRole, getRoleAssistApi, getRoleLibsApi, getRolePermissionsApi, getRoleSkillsApi, updateRoleNameApi, updateRolePermissionsApi } from "../../../controllers/API/user"; +import { createRole, getGroupResourcesApi, getRolePermissionsApi, updateRoleNameApi, updateRolePermissionsApi } from "../../../controllers/API/user"; import { captureAndAlertRequestErrorHoc } from "../../../controllers/request"; import { useTable } from "../../../util/hook"; -const SearchPanne = ({ role_id, title, type, children }) => { +const SearchPanne = ({ groupId, title, type, children }) => { const { page, pageSize, data, total, loading, setPage, search } = useTable({ pageSize: 10 }, (params) => { const { page, pageSize, keyword } = params const param = { name: keyword, - role_id, + group_id: groupId, page_num: page, page_size: pageSize } - return type === 'skill' ? getRoleSkillsApi(param) - : (type === 'assistant' ? getRoleAssistApi({ ...param, type: 'assistant' }) - : getRoleLibsApi(param)) + + switch (type) { + case 'skill': + return getGroupResourcesApi({ ...param, resource_type: 2 }); + case 'tool': + return getGroupResourcesApi({ ...param, resource_type: 4 }); + case 'assistant': + return getGroupResourcesApi({ ...param, resource_type: 3 }); + default: + return getGroupResourcesApi({ ...param, resource_type: 1 }); + } }) return <>
-

{title}

+

{title}

search(e.target.value)}>
@@ -48,32 +57,44 @@ const SearchPanne = ({ role_id, title, type, children }) => { } +const enum MenuType { + BUILD = 'build', + KNOWLEDGE = 'knowledge', + MODEL = 'model', + EVALUATION = 'evaluation' +} // -1 id表示新增 -export default function EditRole({ id, name, onChange, onBeforeChange }) { +export default function EditRole({ id, name, groupId, onChange, onBeforeChange }) { const { setErrorData, setSuccessData } = useContext(alertContext); const { t } = useTranslation() + // 使用的权限 const [form, setForm] = useState({ name, useSkills: [], useLibs: [], useAssistant: [], - manageLibs: [] + manageLibs: [], + useTools: [], + useMenu: [MenuType.BUILD, MenuType.KNOWLEDGE] }) useEffect(() => { if (id !== -1) { // 获取详情,初始化选中数据 getRolePermissionsApi(id).then(res => { - const useSkills = [], useLibs = [], manageLibs = [], useAssistant = [] + const useSkills = [], useLibs = [], manageLibs = [], useAssistant = [], useTools = [], + useMenu = [] res.data.forEach(item => { switch (item.type) { case 1: useLibs.push(Number(item.third_id)); break; case 2: useSkills.push(item.third_id); break; case 3: manageLibs.push(Number(item.third_id)); break; + case 7: useTools.push(Number(item.third_id)); break; case 5: useAssistant.push(item.third_id); break; + case 99: useMenu.push(item.third_id); break; } }) - setForm({ name, useSkills, useLibs, useAssistant, manageLibs }) + setForm({ name, useSkills, useLibs, useAssistant, manageLibs, useTools, useMenu }) }) } }, [id]) @@ -100,7 +121,7 @@ export default function EditRole({ id, name, onChange, onBeforeChange }) { * 1.验证重名 * 2.新增时先保存基本信息 创建 ID * 3.修改时先更新基本信息 - * 4.批量 保存各个种类权限信息(助手、技能、知识库等) + * 4.再批量 保存各个种类权限信息(助手、技能、知识库等) * @returns */ const handleSave = async () => { @@ -119,7 +140,7 @@ export default function EditRole({ id, name, onChange, onBeforeChange }) { // 没有id时需要走创建流程,否则修改 let roleId = id if (id === -1) { - const res = await captureAndAlertRequestErrorHoc(createRole(form.name)) + const res = await captureAndAlertRequestErrorHoc(createRole(groupId, form.name)) roleId = res.id } else { // 更新基本信息 @@ -130,7 +151,9 @@ export default function EditRole({ id, name, onChange, onBeforeChange }) { updateRolePermissionsApi({ role_id: roleId, access_id: form.useSkills, type: 2 }), updateRolePermissionsApi({ role_id: roleId, access_id: form.useLibs, type: 1 }), updateRolePermissionsApi({ role_id: roleId, access_id: form.manageLibs, type: 3 }), - updateRolePermissionsApi({ role_id: roleId, access_id: form.useAssistant, type: 5 }) + updateRolePermissionsApi({ role_id: roleId, access_id: form.useTools, type: 7 }), + updateRolePermissionsApi({ role_id: roleId, access_id: form.useAssistant, type: 5 }), + updateRolePermissionsApi({ role_id: roleId, access_id: form.useMenu, type: 99 }), ]) console.log('form :>> ', form, res); @@ -140,21 +163,66 @@ export default function EditRole({ id, name, onChange, onBeforeChange }) { const roleId = id === -1 ? 0 : id - return
+ return
-

{t('system.roleName')}

+

{t('system.roleName')}

setForm({ ...form, name: e.target.value })} maxLength={60}>
+ {/* 菜单授权 */} +
+
+

菜单授权

+
+
+ + + + 一级菜单 + 查看权限 + + + + + NPC + + switchDataChange(MenuType.BUILD, 'useMenu', bln)} /> + + + + 知识库 + + switchDataChange(MenuType.KNOWLEDGE, 'useMenu', bln)} /> + + + + 模型 + + switchDataChange(MenuType.MODEL, 'useMenu', bln)} /> + + + + 评测 + + switchDataChange(MenuType.EVALUATION, 'useMenu', bln)} /> + + + +
+
+
{/* 助手 */}
- + {(data) => ( - NPC名称 - {t('system.creator')} - {t('system.usePermission')} + {t('system.assistantName')} + {t('system.creator')} + {t('system.usePermission')} @@ -162,7 +230,7 @@ export default function EditRole({ id, name, onChange, onBeforeChange }) { {el.name} {el.user_name} - + switchDataChange(el.id, 'useAssistant', bln)} /> @@ -174,14 +242,18 @@ export default function EditRole({ id, name, onChange, onBeforeChange }) { {/* 技能 */}
- + {(data) => (
- {t('system.skillName')} - {t('system.creator')} - {t('system.usePermission')} + {t('system.skillName')} + {t('system.creator')} + {t('system.usePermission')} @@ -189,7 +261,7 @@ export default function EditRole({ id, name, onChange, onBeforeChange }) { {el.name} {el.user_name} - + switchDataChange(el.id, 'useSkills', bln)} /> @@ -200,16 +272,19 @@ export default function EditRole({ id, name, onChange, onBeforeChange }) { {/* 知识库 */} -
- +
+ {(data) => (
- {t('lib.libraryName')} - {t('system.creator')} - {t('system.usePermission')} - {t('system.managePermission')} + {t('lib.libraryName')} + {t('system.creator')} + {t('system.usePermission')} + {t('system.managePermission')} @@ -217,10 +292,10 @@ export default function EditRole({ id, name, onChange, onBeforeChange }) { {el.name} {el.user_name} - + switchUseLib(el.id, bln)} /> - + switchLibManage(el.id, bln)} /> @@ -230,8 +305,44 @@ export default function EditRole({ id, name, onChange, onBeforeChange }) { )} -
- + {/* 工具 */} +
+ + {(data) => ( +
+ + + {t('lib.toolName')} + {t('system.creator')} + {t('system.usePermission')} + + + + {data.map((el) => ( + + {el.name} + {el.user_name} + + switchDataChange(el.id, 'useTools', bln)} + /> + + + ))} + +
+ )} +
+
+
+ {/* + */} +
diff --git a/src/pages/SystemPage/components/EditUserGroup.tsx b/src/pages/SystemPage/components/EditUserGroup.tsx new file mode 100644 index 0000000..b25e222 --- /dev/null +++ b/src/pages/SystemPage/components/EditUserGroup.tsx @@ -0,0 +1,266 @@ +import UsersSelect from "@/components/bs-comp/selectComponent/Users"; +import { Button } from "@/components/bs-ui/button"; +import { Label } from "@/components/bs-ui/label"; +import AutoPagination from "@/components/bs-ui/pagination/autoPagination"; +import { RadioGroup, RadioGroupItem } from "@/components/bs-ui/radio"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/bs-ui/table"; +import { useToast } from "@/components/bs-ui/toast/use-toast"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/bs-ui/tooltip"; +import { locationContext } from "@/contexts/locationContext"; +import { getGroupFlowsApi, saveGroupApi } from "@/controllers/API/pro"; +import { getAdminsApi, saveUserGroup, updateUserGroup } from "@/controllers/API/user"; +import { captureAndAlertRequestErrorHoc } from "@/controllers/request"; +import { useTable } from "@/util/hook"; +import { QuestionMarkCircledIcon } from "@radix-ui/react-icons"; +import { useContext, useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Input, SearchInput } from "../../../components/bs-ui/input"; + +/** + * + * 用户组编辑&创建接口逻辑 + * 创建 + * 1.用名字和管理员作为参数 调开源接口创建 + * 2.再调闭源接口设置 流控 + * 编辑 + * 1.用名字调开源接口修改 + * 2.用管理员s调开源接口修改 + * 3.再调闭源接口设置 流控 + * + * 资源流控控制,每次调接口只传变动的 limit + * limitState中转状态,limit只在初始化接收一次(不支持异步加载) + * @returns + */ +const enum LimitType { + LIMITED = 'limited', + UNLIMITED = 'unlimited' +} + +function FlowRadio({ limit, onChange }) { + const { t } = useTranslation() + const [status, setStatus] = useState(LimitType.UNLIMITED) + const [limitState, setLimitState] = useState(limit) + + const handleCommit = (type: LimitType, value: string = '0') => { + const valueNum = parseInt(value) + if (valueNum < 0 || valueNum > 9999) return + setStatus(type) + setLimitState(value) + onChange(Number(value)) + } + useEffect(() => { + setStatus(limit ? LimitType.LIMITED : LimitType.UNLIMITED) + setLimitState(limit) + }, [limit]) + + return
+ handleCommit(value, value === LimitType.LIMITED ? '10' : '0')}> +
+ +
+
+ +
+ {status === LimitType.LIMITED &&
+ + handleCommit(LimitType.LIMITED, e.target.value)} /> + +
} +
+
+} + +function FlowControl({ groupId, type, onChange }) { + const { t } = useTranslation() + const { name, label, placeholder } = type === 3 + ? { name: t('build.assistantName'), label: t('system.AssistantFlowCtrl'), placeholder: t('system.assistantName') } + : { name: t('skills.skillName'), label: t('system.SkillFlowCtrl'), placeholder: t('skills.skillName') } + const { page, pageSize, data, total, setPage, search, refreshData } = useTable({ pageSize: 10 }, (params) => + getGroupFlowsApi(params.page, params.pageSize, type, groupId, params.keyword) + ) + + const itemsRef = useRef([]) + const handleChange = (value, id) => { + // resourceId, groupId, resourceLimit + const item = itemsRef.current.find(item => item.resource_id === id) + if (item) { + item.resource_limit = value + } else { + itemsRef.current.push({ + resource_id: id, + group_id: groupId, + resource_limit: value + }) + } + refreshData((item) => item.id === id, { limit: value }) + onChange(itemsRef.current) + } + + const searchEndRef = useRef(false) + const handleSearch = (e) => { + searchEndRef.current = true + search(e.target.value) + } + + if (!searchEndRef.current && !data.length) return null + + return <> +
+
+

{label}

+ + + + + + +

{t('system.iconHover')}

+
+
+
+
+ +
+
+ + + + {name} + {t('system.createdBy')} + {t('system.flowCtrlStrategy')} + + + + {data.map((i: any) => ( + {i.name} + {i.user_name} + + handleChange(val, i.id)}> + + ))} + +
+ +
+ +} + +export default function EditUserGroup({ data, onBeforeChange, onChange }) { + const { t } = useTranslation() + const { toast } = useToast() // 类似于alert + const { appConfig } = useContext(locationContext) + + const [form, setForm] = useState({ + groupName: '', + adminUser: '', + groupLimit: 0, + assistant: [], + skill: [] + }) + /** + * 用户 + */ + const [selected, setSelected] = useState([]) + const [lockOptions, setLockOptions] = useState([]) + + const handleSave = async () => { + console.log('form', form); + + if (!form.groupName) { + setForm({ ...form, groupName: data.group_name || '' }) + return toast({ title: t('prompt'), description: t('system.groupNameRequired'), variant: 'error' }); + } + if (form.groupName.length > 30) { + setForm({ ...form, groupName: data.group_name || '' }) + return toast({ title: t('prompt'), description: t('system.groupNamePrompt'), variant: 'error' }); + } + const flag = onBeforeChange(form.groupName) + if (flag) { + setForm({ ...form, groupName: '' }) + return toast({ title: t('prompt'), description: t('system.groupNameExists'), variant: 'error' }); + } + + // 过滤系统管理员 + const users = selected.filter(item => !lockOptions.some(id => id === item.value)) + + const res: any = await (data.id ? updateUserGroup(data.id, form, users) : // 修改 + saveUserGroup(form, users)) // 保存 + + if (appConfig.isPro) { + await captureAndAlertRequestErrorHoc(saveGroupApi({ + ...form, + id: data.id || res.id, // 修改id:data.id, 创建id:res.id + adminUser: users.map(item => item.label).join(','), + adminUserId: users.map(item => item.value).join(',') + })) + } + + onChange(true) + } + + useEffect(() => { // 初始化数据 + setForm({ ...form, groupName: data.group_name, groupLimit: data.group_limit || 0 }) + async function init() { + const res = await getAdminsApi() + const users = data.group_admins?.map(d => ({ label: d.user_name, value: d.user_id })) || [] + const defaultUsers = res.map(d => ({ label: d.user_name, value: d.user_id })) + setLockOptions(defaultUsers.map(el => el.value)) + setSelected([...defaultUsers, ...users]) + } + init() + }, []) + + return
+
+

{t('system.groupName')}

+ setForm({ ...form, groupName: e.target.value })}> +
+
+

{t('system.admins')}

+
+ +
+
+ {appConfig.isPro && <> +
+

{t('system.flowControl')}

+ setForm({ ...form, groupLimit: f })}> +
+
+ setForm({ ...form, assistant: vals })} + > +
+
+ setForm({ ...form, skill: vals })} + > +
+ } +
+ + +
+
+} \ No newline at end of file diff --git a/src/pages/SystemPage/components/Roles.tsx b/src/pages/SystemPage/components/Roles.tsx index 42db983..354e1b4 100644 --- a/src/pages/SystemPage/components/Roles.tsx +++ b/src/pages/SystemPage/components/Roles.tsx @@ -1,101 +1,234 @@ -import { useEffect, useRef, useState } from "react"; +import { PlusIcon } from "@/components/bs-icons/plus"; +import { bsConfirm } from "@/components/bs-ui/alertDialog/useConfirm"; +import { Label } from "@/components/bs-ui/label"; +import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; -import { bsconfirm } from "../../../alerts/confirm"; -import { Button } from "../../../components/ui/button"; +import { Button } from "../../../components/bs-ui/button"; +import { SearchInput } from "../../../components/bs-ui/input"; import { Table, TableBody, - TableCaption, TableCell, + TableFooter, TableHead, TableHeader, TableRow -} from "../../../components/ui/table"; -import { delRoleApi, getRolesApi } from "../../../controllers/API/user"; +} from "../../../components/bs-ui/table"; +import { delRoleApi, getRolesByGroupApi, getUserGroupsApi } from "../../../controllers/API/user"; import { captureAndAlertRequestErrorHoc } from "../../../controllers/request"; import { ROLE } from "../../../types/api/user"; import EditRole from "./EditRole"; -import { Input } from "../../../components/ui/input"; -import { Search } from "lucide-react"; +import SelectSearch from "@/components/bs-ui/select/select" + +interface State { + roles: ROLE[]; + role: Partial | null; + searchWord: string; + group: string; + groups: { label: string; value: string }[]; +} + +const initialState: State = { + roles: [], + role: null, + searchWord: '', + group: '', + groups: [] +}; + +type Action = + | { type: 'SET_ROLES'; payload: ROLE[] } + | { type: 'SET_ROLE'; payload: Partial | null } + | { type: 'SET_SEARCH_WORD'; payload: string } + | { type: 'SET_GROUP'; payload: string } + | { type: 'SET_GROUPS'; payload: any }; + +function reducer(state: State, action: Action): State { + switch (action.type) { + case 'SET_ROLES': + return { ...state, roles: action.payload }; + case 'SET_ROLE': + return { ...state, role: action.payload }; + case 'SET_SEARCH_WORD': + return { ...state, searchWord: action.payload }; + case 'SET_GROUP': + return { ...state, group: action.payload }; + case 'SET_GROUPS': + return { ...state, groups: action.payload }; + default: + return state; + } +} export default function Roles() { - const { t } = useTranslation() + const { t } = useTranslation(); + const [state, dispatch] = useReducer(reducer, initialState); + const allRolesRef = useRef([]); + const { delShow, idRef, close, delConfirm } = useDelete(); - const [role, setRole] = useState | null>(null) - const [roles, setRoles] = useState([]) - const allRolesRef = useRef([]) + const loadData = useCallback(async () => { + const inputDom = document.getElementById('role-input') as HTMLInputElement; + if (inputDom) { + inputDom.value = ''; + } + try { + const data:any = await getRolesByGroupApi('', [state.group]); + dispatch({ type: 'SET_ROLES', payload: data }); + allRolesRef.current = data; + } catch (error) { + console.error(error); + } + }, [state.group]); - const handleChange = (change: boolean) => { - change && loadData() - setRole(null) - } - - const loadData = () => { - getRolesApi().then(data => { - setRoles(data) - allRolesRef.current = data + useEffect(() => { + getUserGroupsApi().then((res:any) => { + const groups = res.records.map(ug => ({ label: ug.group_name, value: ug.id })) + // 获取最近修改用户组 + dispatch({ type: 'SET_GROUP', payload: groups[0].value }); + dispatch({ type: 'SET_GROUPS', payload: groups }); }) - } + }, []); - useEffect(() => loadData(), []) - - // 删除 - const handleDelete = (item) => { - bsconfirm({ - desc: `${t('system.confirmText')} 【${item.role_name}】 ?`, - okTxt: t('delete'), - onOk(next) { - captureAndAlertRequestErrorHoc(delRoleApi(item.id).then(loadData)) - next() - } + const handleDelete = (user) => { + // const handleDelete = async (item: ROLE) => { + // bsConfirm({ + // desc: `${t('system.confirmText')} 【${item.role_name}】 ?`, + // okTxt: t('delete'), + // onOk: async (next) => { + // try { + // await captureAndAlertRequestErrorHoc(delRoleApi(item.id)); + // await loadData(); + // next(); + // } catch (error) { + // console.error(error); + // } + // } + // }); + captureAndAlertRequestErrorHoc(delRoleApi(idRef.current.id)).then(res => { + loadData() + close(); }) + }; + + const checkSameName = useCallback((name: string) => { + return state.roles.find(_role => _role.role_name === name && state.role?.id !== _role.id); + }, [state.roles, state.role]); + + const handleSearch = (e: React.ChangeEvent) => { + const word = e.target.value; + dispatch({ type: 'SET_SEARCH_WORD', payload: word }); + dispatch({ type: 'SET_ROLES', payload: allRolesRef.current.filter(item => item.role_name.toUpperCase().includes(word.toUpperCase())) }); + }; + useEffect(() => { + loadData() + }, [state.group]) + + const [keyWord, setKeyWord] = useState('') + const options = useMemo(() => { + if (!keyWord || !state.group) return state.groups + return state.groups.filter(group => group.label.toUpperCase().includes(keyWord.toUpperCase()) || group.value === state.group) + }, [keyWord, state.group]) + + if (state.role) { + return { + dispatch({ type: 'SET_ROLE', payload: null }) + loadData() + }} + />; } - // 验证重名 - const checkSameName = (name: string) => { - return (roles.find(_role => - _role.role_name === name && role.id !== _role.id)) - } - - // search - const [searchWord, setSearchWord] = useState('') - const handleSearch = (e) => { - const word = e.target.value - setSearchWord(word) - setRoles(allRolesRef.current.filter(item => item.role_name.toUpperCase().includes(word.toUpperCase()))) - } - - if (role) return - - return
-
-
- - + return ( +
+
+

{t('system.roleList')}.

+
+
+ + { + !open && setKeyWord('') + }} + onValueChange={(value) => { + dispatch({ type: 'SET_GROUP', payload: value}) + }} + onChange={e => setKeyWord(e.target.value)} + /> +
+
+
+ +
+ +
+
+ + + + {t('system.roleName')} + {t('createTime')} + {t('operations')} + + + + {state.roles.map(el => ( + + {el.role_name} + {el.create_time.replace('T', ' ')} + + + + + + ))} + + + {!state.roles.length && + {t('build.empty')} + } + +
- +
+
+ + + +
+

{t('prompt')}

+

确认删除该角色?

+
+ + +
+
+
- - {t('system.roleList')}. - - - {t('system.roleName')} - {t('createTime')} - {t('operations')} - - - - {roles.map((el) => ( - - {el.role_name} - {el.create_time.replace('T', ' ')} - - - - - - ))} - -
-
-}; + ); +} + +const useDelete = () => { + const [delShow, setDelShow] = useState(false) + const idRef = useRef(null) + + return { + delShow, + idRef, + close: () => { + setDelShow(false) + }, + delConfirm: (id) => { + idRef.current = id + setDelShow(true) + } + } +} \ No newline at end of file diff --git a/src/pages/SystemPage/components/UserGroup.tsx b/src/pages/SystemPage/components/UserGroup.tsx new file mode 100644 index 0000000..35a74ac --- /dev/null +++ b/src/pages/SystemPage/components/UserGroup.tsx @@ -0,0 +1,160 @@ +import { useTranslation } from "react-i18next" +import { Button } from "../../../components/bs-ui/button"; +import { SearchInput } from "../../../components/bs-ui/input"; +import { PlusIcon } from "@/components/bs-icons/plus"; +import { getUserGroupsApi, delUserGroupApi, getAdminsApi } from "@/controllers/API/user" +import { bsConfirm } from "@/components/bs-ui/alertDialog/useConfirm"; +import { captureAndAlertRequestErrorHoc } from "../../../controllers/request"; +import { useContext, useEffect, useRef, useState } from "react"; +import { + Table, + TableBody, + TableCell, + TableFooter, + TableHead, + TableHeader, + TableRow +} from "../../../components/bs-ui/table"; +import EditUserGroup from "./EditUserGroup"; +import { UserGroup } from "@/types/api/user"; +import { locationContext } from "@/contexts/locationContext"; +import { getUserGroupsProApi } from "@/controllers/API/pro"; + +export default function UserGroups() { + const { t } = useTranslation() + const [userGroups, setUserGroups] = useState([]) + const [userGroup, setUserGroup] = useState(null) + const tempRef = useRef([]) // 搜索功能的数据暂存 + const { appConfig } = useContext(locationContext) + const defaultAdminsRef = useRef([]) + const { delShow, idRef, close, delConfirm } = useDelete(); + + const loadData = async () => { + const res: any = await (appConfig.isPro ? getUserGroupsProApi : getUserGroupsApi)() + defaultAdminsRef.current = await getAdminsApi() + res.records.map(g => g.group_admins = [...defaultAdminsRef.current, ...g.group_admins]) + setUserGroups(res.records) + tempRef.current = res.records + } + + const handleSearch = (e) => { + const word = e.target.value + const newUgs = tempRef.current.filter(ug => ug.group_name.toUpperCase().includes(word.toUpperCase())) + setUserGroups(newUgs) + } + const handleDelete = (userGroup) => { + // bsConfirm({ + // desc: t('system.deleteGroup', { name: userGroup.group_name }), + // okTxt: t('delete'), + // onOk(next) { + // captureAndAlertRequestErrorHoc(delUserGroupApi(userGroup.id).then(res => { + // loadData() + // close(); + // })) + // next() + // } + // }) + captureAndAlertRequestErrorHoc(delUserGroupApi(idRef.current.id).then(res => { + loadData() + close(); + })) + } + + const checkSameName = (name: string) => { + return (userGroups.find(ug => + ug.group_name === name && ug.id !== userGroup.id)) + } + const handleChange = (flag: boolean) => { + flag && loadData() + setUserGroup(null) + } + + useEffect(() => { loadData() }, []) + + if (userGroup) return + + return
+
+

{t('system.userGroupList')}

+
+
+ +
+ +
+ + + + {t('system.groupName')} + {t('system.admins')} + {appConfig.isPro && {t('system.flowControl')}} + {t('system.changeTime')} + {t('operations')} + + + + {userGroups.map((ug: any) => ( + + {ug.group_name} + {(ug.admin_user || ug.group_admins).map(el => el.user_name).join(',')} + {appConfig.isPro && {ug.group_limit ? t('system.limit') : t('system.unlimited')}} + {ug.update_time.replace('T', ' ')} + + + + + + ))} + + + {!userGroups.length && + {t('build.empty')} + } + +
+
+
+ +
+ + +
+

{t('prompt')}

+

删除后数据无法恢复,请确认!

+
+ + +
+
+
+
+} + +const useDelete = () => { + const [delShow, setDelShow] = useState(false) + const idRef = useRef(null) + + return { + delShow, + idRef, + close: () => { + setDelShow(false) + }, + delConfirm: (id) => { + idRef.current = id + setDelShow(true) + } + } +} \ No newline at end of file diff --git a/src/pages/SystemPage/components/UserRoleItem.tsx b/src/pages/SystemPage/components/UserRoleItem.tsx new file mode 100644 index 0000000..67646eb --- /dev/null +++ b/src/pages/SystemPage/components/UserRoleItem.tsx @@ -0,0 +1,89 @@ +// import { DelIcon } from "@/components/bs-icons"; +import { Button } from "@/components/bs-ui/button"; +import MultiSelect from "@/components/bs-ui/select/multi"; +import { getRolesByGroupApi, getUserGroupsApi } from "@/controllers/API/user"; +import { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import SelectSearch from "@/components/bs-ui/select/select" +import { DelIcon } from "@/components/bs-icons/del"; + +export default function UserRoleItem({ showDel, groupId, selectedRoles, onDelete, onChange }: + { showDel: boolean, groupId: null | string, selectedRoles: any[], onDelete: any, onChange: any }) { + const { t } = useTranslation() + + // 用户组 + const [groups, setGroups] = useState([]) + const groupsRef = useRef([]) + const [userGroupSelected, setUserGroupSelected] = useState(groupId ? [groupId] : []) + const loadGroups = () => { + getUserGroupsApi().then((res: any) => { + const groups = res.records.map((ug) => { + return { + label: ug.group_name, + value: ug.id.toString() + } + }) + setGroups(groups) + groupsRef.current = groups + }) + } + useEffect(() => { + // 用户组option列表 + loadGroups() + }, []) + + const handleSelectGroup = (value) => { //单选之后value要改成数组传出去 + onChange([value], []) + setUserGroupSelected([value]); + setSelected([]) + } + const handleSearch = (e) => { + const keyword = e.target.value + const newGroups = groupsRef.current.filter(g => g.label.toUpperCase().includes(keyword.toUpperCase()) + || g.value === userGroupSelected[0]) + setGroups(newGroups) + } + + // 角色 + const [roles, setRoles] = useState([]) + const [selected, setSelected] = useState(selectedRoles) + useEffect(() => { + // setSelected([]) + // 用户组option列表 + getRolesByGroupApi('', userGroupSelected).then((res: any) => { + const roleOptions = res.map(role => { + return { + label: role.role_name, + value: role.id.toString() + } + }) + setRoles(roleOptions); + }) + }, [userGroupSelected]) + + const handleSelectRole = (values) => { + onChange(userGroupSelected, values) + setSelected(values) + } + + return
+ setGroups(groupsRef.current)} + onValueChange={handleSelectGroup} + onChange={handleSearch} + /> + + + {showDel && } +
+}; diff --git a/src/pages/SystemPage/components/UserRoleModal.tsx b/src/pages/SystemPage/components/UserRoleModal.tsx index 74f6236..167e324 100644 --- a/src/pages/SystemPage/components/UserRoleModal.tsx +++ b/src/pages/SystemPage/components/UserRoleModal.tsx @@ -1,101 +1,79 @@ -import { Listbox } from "@headlessui/react" -import { CheckIcon, ChevronsUpDown } from "lucide-react" +import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/bs-ui/dialog" +import { useToast } from "@/components/bs-ui/toast/use-toast" +import { PlusIcon } from "@radix-ui/react-icons" import { useEffect, useState } from "react" import { useTranslation } from "react-i18next" -import { Button } from "../../../components/ui/button" -import { getRolesApi, getUserRoles, updateUserRoles } from "../../../controllers/API/user" -import { ROLE } from "../../../types/api/user" -import { captureAndAlertRequestErrorHoc } from "../../../controllers/request" +import { Button } from "../../../components/bs-ui/button" +import UserRoleItem from "./UserRoleItem" +import { updateUserGroups, updateUserRoles } from "@/controllers/API/user" +import { captureAndAlertRequestErrorHoc } from "@/controllers/request" +import { generateUUID } from "@/components/bs-ui/utils" -export default function UserRoleModal({ id, onClose, onChange }) { +export default function UserRoleModal({ user, onClose, onChange }) { const { t } = useTranslation() - const [roles, setRoles] = useState([]) - const [selected, setSelected] = useState([]) - const [error, setError] = useState(false) - + // 初始化数据 + const [roleItems, setRoleItems] = useState([]) useEffect(() => { - if (!id) return - getRolesApi().then(data => { - const roleOptions = data.filter(role => role.id !== 1) - .map(role => ({ ...role, role_id: role.id })) - setRoles(roleOptions); + if (user) { + const { groups, roles } = user + const items = groups.map(item => { - getUserRoles(id).then(userRoles => { - // 默认设置 普通用户 - if (!userRoles.find(role => role.role_id === 2)) { - const roleByroles = roleOptions.find(role => role.role_id === 2) - userRoles.unshift({ ...roleByroles }) + return { + key: generateUUID(8), + groupId: item.id, + roles: roles.filter(role => role.group_id === item.id) + .map(el => el.id.toString()) } - setSelected(userRoles) }) - }) - setError(false) - }, [id]) + setRoleItems(items) + } + }, [user]) - function compareDepartments(a, b) { - return a.role_id === b.role_id + const handleChangeRoleItems = (index, groupId, roles) => { + setRoleItems(items => items.map((el, i) => { + return (index !== i) ? el : { groupId: groupId[0], roles } + })) } + const { message } = useToast() const handleSave = async () => { - if (!selected.length) return setError(true) - const res = await captureAndAlertRequestErrorHoc(updateUserRoles(id, selected.map(item => item.role_id))) - console.log('res :>> ', res); + const map = {} + const items = roleItems.filter(item => { + if (map[item.groupId] || !item.groupId) return false + map[item.groupId] = true + return true + }) + if (items.some(item => item.roles.length === 0)) return message({ title: t('prompt'), variant: 'warning', description: t('system.selectRole') }) + if (items.length === 0) return message({ title: t('prompt'), variant: 'warning', description: t('system.selectGroup') }) + captureAndAlertRequestErrorHoc(updateUserRoles(user.user_id, items.reduce((res, item) => [...res, ...item.roles], []))) + captureAndAlertRequestErrorHoc(updateUserGroups(user.user_id, items.map(item => item.groupId))) onChange() } - return -
-

{t('system.roleSelect')}

- -
- -
{selected.map(el => el.role_name).join(';')}
- - - -
- - - {roles.map((role, personIdx) => ( - - `relative select-none py-2 pl-10 pr-4 - ${active - ? 'bg-[#404040] text-[#fff]' - : 'text-[#fff] bg-[#333]'} - ${role.role_id === 2 - ? 'cursor-not-allowed text-gray-300' - : "cursor-default"}` - } - value={role} - disabled={role.role_id === 2} - > - {({ selected }) => ( - <> - - {role.role_name} - - {selected ? ( - - - ) : null} - - )} - - ))} - -
-
-
- - + return { !b && setRoleItems([]); onClose(b) }}> + + + {t('system.roleSelect')} + +
+ { + roleItems.map((item, i) => handleChangeRoleItems(i, g, r)} + showDel={roleItems.length > 1} + onDelete={() => setRoleItems(roleItems.filter((el, index) => index !== i))} + />) + }
-
-
-}; + + + + + + + +}; \ No newline at end of file diff --git a/src/pages/SystemPage/components/Users copy.tsx b/src/pages/SystemPage/components/Users copy.tsx new file mode 100644 index 0000000..8b6d6d9 --- /dev/null +++ b/src/pages/SystemPage/components/Users copy.tsx @@ -0,0 +1,129 @@ +import { useContext, useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Button } from "../../../components/ui/button"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow +} from "../../../components/ui/table"; +import { userContext } from "../../../contexts/userContext"; +import { disableUserApi, getUsersApi } from "../../../controllers/API/user"; +import UserRoleModal from "./UserRoleModal"; +import { captureAndAlertRequestErrorHoc } from "../../../controllers/request"; +import { useTable } from "../../../util/hook"; +import { Input } from "../../../components/ui/input"; +import PaginationComponent from "../../../components/PaginationComponent"; +import { Search } from "lucide-react"; + +export default function Users(params) { + const { user } = useContext(userContext); + const { t } = useTranslation() + + const { page, pageSize, data: users, total, loading, setPage, search, reload } = useTable({},(param) => + getUsersApi(param.keyword, param.page, param.pageSize) + ) + + // 禁用 + const { delShow, idRef, close, delConfim } = useDelete() + const handleDelete = () => { + captureAndAlertRequestErrorHoc(disableUserApi(idRef.current.user_id, 1).then(res => { + reload() + close() + })) + } + const handleEnableUser = (user) => { + captureAndAlertRequestErrorHoc(disableUserApi(user.user_id, 0).then(res => { + reload() + close() + })) + } + + // 编辑 + const [roleOpenId, setRoleOpenId] = useState(null) + const handleRoleChange = () => { + setRoleOpenId(null) + reload() + } + + return <> +
+
+ search(e.target.value)}> + +
+
+ + {/* 用户列表. */} + + + {t('system.username')} + {t('createTime')} + {t('operations')} + + + + {users.map((el) => ( + + {el.user_name} + {/* {el.role} */} + {el.update_time.replace('T', ' ')} + + {user.user_id === el.user_id ? {t('edit')} : + setRoleOpenId(el.user_id)} className="underline ml-4">{t('edit')}} + { + el.delete === 1 ? handleEnableUser(el)} className="underline ml-4">{t('enable')} : + user.user_id === el.user_id ? {t('disable')} : + delConfim(el)} className="underline ml-4 text-red-500">{t('disable')} + } + + + ))} + +
+ {/* 分页 */} + {/* */} +
+ setPage(newPage)} + /> +
+ + {/* 禁用确认 */} + +
+

{t('prompt')}!

+

{t('system.confirmDisable')}

+
+ + +
+
+
+ + setRoleOpenId(null)} onChange={handleRoleChange}> + +}; + + +const useDelete = () => { + const [delShow, setDelShow] = useState(false) + const idRef = useRef(null) + + return { + delShow, + idRef, + close: () => { + setDelShow(false) + }, + delConfim: (id) => { + idRef.current = id + setDelShow(true) + } + } +} \ No newline at end of file diff --git a/src/pages/SystemPage/components/Users.tsx b/src/pages/SystemPage/components/Users.tsx index 8b6d6d9..6408a17 100644 --- a/src/pages/SystemPage/components/Users.tsx +++ b/src/pages/SystemPage/components/Users.tsx @@ -1,92 +1,250 @@ -import { useContext, useEffect, useRef, useState } from "react"; +import { FilterIcon } from "@/components/bs-icons/filter"; +import { bsConfirm } from "@/components/bs-ui/alertDialog/useConfirm"; +import { Button } from "@/components/bs-ui/button"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/bs-ui/popover"; +import FilterUserGroup from "@/components/bs-ui/select/filter"; +import { getRolesApi, getUserGroupsApi } from "@/controllers/API/user"; +import { useContext, useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Button } from "../../../components/ui/button"; +import { SearchInput } from "../../../components/bs-ui/input"; +import AutoPagination from "../../../components/bs-ui/pagination/autoPagination"; import { Table, TableBody, TableCell, + TableFooter, TableHead, TableHeader, TableRow -} from "../../../components/ui/table"; +} from "../../../components/bs-ui/table"; import { userContext } from "../../../contexts/userContext"; import { disableUserApi, getUsersApi } from "../../../controllers/API/user"; -import UserRoleModal from "./UserRoleModal"; import { captureAndAlertRequestErrorHoc } from "../../../controllers/request"; import { useTable } from "../../../util/hook"; -import { Input } from "../../../components/ui/input"; -import PaginationComponent from "../../../components/PaginationComponent"; -import { Search } from "lucide-react"; +import UserRoleModal from "./UserRoleModal"; +import UserPwdModal from "@/pages/LoginPage/UserPwdModal"; +import { PlusIcon } from "@/components/bs-icons"; +import CreateUser from "./CreateUser"; + +function UsersFilter({ options, onChecked, nameKey, placeholder, onFilter }) { + const [open, setOpen] = useState(false) + const [_value, setValue] = useState([]) + const [searchKey, setSearchKey] = useState('') + // 点击 checkbox + const handlerChecked = (id) => { + setValue(val => { + const index = val.indexOf(id) + index === -1 ? val.push(id) : val.splice(index, 1) + return [...val] + }) + // 已选项上浮 + const checked = options.filter(o => _value.includes(o.id)) + const uncheck = options.filter(o => !_value.includes(o.id)) + onChecked([...checked, ...uncheck]) + } + + const filterData = () => { + onFilter(_value) + setOpen(false) + } + // 搜索 + const _options = useMemo(() => { + if (!searchKey) return options + return options.filter(a => a[nameKey].toUpperCase().includes(searchKey.toUpperCase())) + }, [searchKey, options]) + // 重置 + const reset = () => { + setValue([]) + setSearchKey('') + } + + return { setOpen(bln); setSearchKey('') }}> + + {/* @ts-ignore */} + setOpen(!open)} className={_value.length ? 'text-[#ffd025]' : 'text-[#999]'} /> + + + setSearchKey(e.target.value)} + onClearChecked={reset} + onOk={filterData} + /> + + + +} + export default function Users(params) { const { user } = useContext(userContext); const { t } = useTranslation() + const { delShow, idRef, close, delConfirm } = useDelete(); - const { page, pageSize, data: users, total, loading, setPage, search, reload } = useTable({},(param) => - getUsersApi(param.keyword, param.page, param.pageSize) + const { page, pageSize, data: users, total, setPage, search, reload, filterData } = useTable({ pageSize: 20 }, (param) => + getUsersApi({ + ...param, + name: param.keyword + }) ) - // 禁用 - const { delShow, idRef, close, delConfim } = useDelete() - const handleDelete = () => { + // 禁用确认 + const handleDelete = (user) => { + // bsConfirm({ + // title: `${t('prompt')}!`, + // desc: t('system.confirmDisable'), + // okTxt: t('disable'), + // onOk(next) { + // captureAndAlertRequestErrorHoc(disableUserApi(idRef.current.id, 1).then(res => { + // reload() + // })) + // next() + // } + // }) captureAndAlertRequestErrorHoc(disableUserApi(idRef.current.user_id, 1).then(res => { reload() - close() + close(); })) } const handleEnableUser = (user) => { captureAndAlertRequestErrorHoc(disableUserApi(user.user_id, 0).then(res => { reload() - close() })) } // 编辑 - const [roleOpenId, setRoleOpenId] = useState(null) + const [currentUser, setCurrentUser] = useState(null) + const userPwdModalRef = useRef(null) const handleRoleChange = () => { - setRoleOpenId(null) + setCurrentUser(null) reload() } - return <> -
-
- search(e.target.value)}> - -
+ // 获取用户组类型数据 + const [userGroups, setUserGroups] = useState([]) + const getUserGoups = async () => { + const res: any = await getUserGroupsApi() + setUserGroups(res.records) + } + // 获取角色类型数据 + const [roles, setRoles] = useState([]) + const getRoles = async () => { + const res: any = await getRolesApi() + setRoles(res) + } + // 已选项上浮 + const handleGroupChecked = (values) => { + setUserGroups(values) + } + const handleRoleChecked = (values) => { + setRoles(values) + } + + const [openCreate, setOpenCreate] = useState(false) + + useEffect(() => { + getUserGoups() + getRoles() + return () => { setUserGroups([]); setRoles([]) } + }, []) + + const operations = (el) => { + const isSuperAdmin = el.roles.some(role => role.id === 1) + // 禁止编辑admin用户 + if (isSuperAdmin) return
+ + {/* */} + +
- - {/* 用户列表. */} - - - {t('system.username')} - {t('createTime')} - {t('operations')} - - - - {users.map((el) => ( - - {el.user_name} - {/* {el.role} */} - {el.update_time.replace('T', ' ')} - - {user.user_id === el.user_id ? {t('edit')} : - setRoleOpenId(el.user_id)} className="underline ml-4">{t('edit')}} - { - el.delete === 1 ? handleEnableUser(el)} className="underline ml-4">{t('enable')} : - user.user_id === el.user_id ? {t('disable')} : - delConfim(el)} className="underline ml-4 text-red-500">{t('disable')} - } - + + return
+ {/* 编辑 */} + + {/* 重置密码 */} + {(user.role === 'admin' || user.role === 'group_admin') && + } + {/* 禁用 */} + { + el.delete === 1 ? : + + } +
+ } + + return
+
+

{t('system.userList')}

+
+
+ search(e.target.value)}> +
+ {user.role === 'admin' && } +
+
+ {/* 用户列表. */} + + + {t('system.username')} + +
+ {t('system.userGroup')} + filterData({ groupId: ids })} + > +
+
+ +
+ {t('system.role')} + filterData({ roleId: ids })} + > +
+
+ {t('system.changeTime')} + {t('operations')}
- ))} - -
+ + + {users.map((el: any) => ( + + {el.user_name} + {/* {el.role} */} + {(el.groups || []).map(el => el.name).join(',')} + {(el.roles || []).map(el => el.name).join(',')} + {el.update_time.replace('T', ' ')} + {operations(el)} + + ))} + + + {!users.length && + {t('build.empty')} + } + + +
{/* 分页 */} {/* */} -
- +
- {/* 禁用确认 */} + { setOpenCreate(bool); reload() }} onSave={reload} /> + setCurrentUser(null)} onChange={handleRoleChange}> + + -
-

{t('prompt')}!

-

{t('system.confirmDisable')}

-
- - + +

{t('prompt')}

+

确认禁用该用户?

+
+ +
- - setRoleOpenId(null)} onChange={handleRoleChange}> - +
}; - const useDelete = () => { const [delShow, setDelShow] = useState(false) const idRef = useRef(null) @@ -121,7 +279,7 @@ const useDelete = () => { close: () => { setDelShow(false) }, - delConfim: (id) => { + delConfirm: (id) => { idRef.current = id setDelShow(true) } diff --git a/src/pages/SystemPage/index.tsx b/src/pages/SystemPage/index.tsx index 065fe58..1968358 100644 --- a/src/pages/SystemPage/index.tsx +++ b/src/pages/SystemPage/index.tsx @@ -1,4 +1,6 @@ - +import { userContext } from "@/contexts/userContext"; +import { useContext } from "react"; +import { useTranslation } from "react-i18next"; import { Tabs, TabsContent, @@ -7,16 +9,18 @@ import { } from "../../components/ui/tabs"; import Roles from "./components/Roles"; import Config from "./components/Config"; +import Theme from "./theme"; +import UserGroups from "./components/UserGroup"; import Users from "./components/Users"; -import { useTranslation } from "react-i18next"; export default function FileLibPage() { - + const { user } = useContext(userContext); + const { t } = useTranslation() return
- + {/* {t('system.userManagement')} {t('system.roleManagement')} {t('system.systemConfiguration')} @@ -29,7 +33,29 @@ export default function FileLibPage() { + */} + + {t('system.userManagement')} + {user.role === 'admin' && {t('system.userGroupsM')}} + {t('system.roleManagement')} + {user.role === 'admin' && {t('system.systemConfiguration')}} + {/* {user.role === 'admin' && 主题配色} */} + + + + + + + + + + + + + {/* + + */}
}; diff --git a/src/pages/SystemPage/theme/Example.tsx b/src/pages/SystemPage/theme/Example.tsx new file mode 100644 index 0000000..5631683 --- /dev/null +++ b/src/pages/SystemPage/theme/Example.tsx @@ -0,0 +1,185 @@ +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/bs-ui/accordion"; +import { Badge } from "@/components/bs-ui/badge"; +import { Button } from "@/components/bs-ui/button"; +import { Calendar } from "@/components/bs-ui/calendar"; +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/bs-ui/card"; +import { Checkbox } from "@/components/bs-ui/checkBox"; +import { SearchInput, Textarea } from "@/components/bs-ui/input"; +import { Label } from "@/components/bs-ui/label"; +import AutoPagination from "@/components/bs-ui/pagination/autoPagination"; +import { RadioGroup, RadioGroupItem } from "@/components/bs-ui/radio"; +import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/bs-ui/select"; +import { Slider } from "@/components/bs-ui/slider"; +import { Switch } from "@/components/bs-ui/switch"; +import { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow } from "@/components/bs-ui/table"; + +const invoices = [ + { + invoice: "INV001", + paymentStatus: "Paid", + totalAmount: "$250.00", + paymentMethod: "Credit Card", + }, + { + invoice: "INV002", + paymentStatus: "Pending", + totalAmount: "$150.00", + paymentMethod: "PayPal", + }, + { + invoice: "INV003", + paymentStatus: "Unpaid", + totalAmount: "$350.00", + paymentMethod: "Bank Transfer", + }, +] + +export default function Example(params) { + + return
+ +
+ + + + + + +
+ +
+ Badge + Badge + Badge + Badge +
+ +
+ +

+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ + +
+ + +
+
+ + +
+
+
+ +
+ + A list of your recent invoices. + + + Invoice + Status + Method + Amount + + + + {invoices.map((invoice) => ( + + {invoice.invoice} + {invoice.paymentStatus} + {invoice.paymentMethod} + {invoice.totalAmount} + + ))} + + + + Total + $2,500.00 + + +
+ +
+ +
+ +
+ +
+ + + Create project + Deploy your new project in one-click. + + + 内容 + + + + + + +
+ +
+ + + Is it accessible? + + Yes. It adheres to the WAI-ARIA design pattern. + + + + Is it styled? + + Yes. It comes with default styles that matches the other + components' aesthetic. + + + + Is it animated? + + Yes. It's animated by default, but you can disable it if you prefer. + + + +
+
+}; diff --git a/src/pages/SystemPage/theme/HSLitem.tsx b/src/pages/SystemPage/theme/HSLitem.tsx new file mode 100644 index 0000000..4bce802 --- /dev/null +++ b/src/pages/SystemPage/theme/HSLitem.tsx @@ -0,0 +1,25 @@ +import { Button } from "@/components/bs-ui/button"; +import { Label } from "@/components/bs-ui/label"; +import { useState } from "react"; +import { SketchPicker } from 'react-color'; + +export default function HSLitem({ label, name, value, onChange }) { + const [show, setShow] = useState(false) + + return
+ +
+ + {show &&
+
setShow(false)}>
+ { onChange(name, e.hsl), setShow(false) }} + /> +
} +
+
+}; diff --git a/src/pages/SystemPage/theme/index.tsx b/src/pages/SystemPage/theme/index.tsx new file mode 100644 index 0000000..0a43b67 --- /dev/null +++ b/src/pages/SystemPage/theme/index.tsx @@ -0,0 +1,109 @@ +import { useState } from "react"; +import HSLitem from "./HSLitem"; +import Example from "./Example"; +import { Button } from "@/components/bs-ui/button"; +import { LoadIcon } from "@/components/bs-icons"; +import { ReloadIcon } from "@radix-ui/react-icons"; +import { saveThemeApi } from "@/controllers/API"; + +const defaultTheme = { + '--background': { h: 0, s: 0, l: 1 }, + '--foreground': { h: 222.2, s: 0.474, l: 0.112 }, + '--muted': { h: 210, s: 0.4, l: 0.98 }, + '--muted-foreground': { h: 215.4, s: 0.163, l: 0.469 }, + '--popover': { h: 0, s: 0, l: 1 }, + '--popover-foreground': { h: 222.2, s: 0.474, l: 0.112 }, + '--card': { h: 0, s: 0, l: 1 }, + '--card-foreground': { h: 222.2, s: 0.474, l: 0.112 }, + '--border': { h: 214.3, s: 0.218, l: 0.914 }, + '--input': { h: 223, s: 0.48, l: 0.44 }, + '--primary': { h: 220, s: 0.98, l: 0.45 }, + '--primary-foreground': { h: 210, s: 0.4, l: 0.98 }, + '--secondary': { h: 210, s: 0.4, l: 0.961 }, + '--secondary-foreground': { h: 222.2, s: 0.474, l: 0.112 }, + '--accent': { h: 210, s: 0.3, l: 0.961 }, + '--accent-foreground': { h: 222.2, s: 0.474, l: 0.112 }, + '--destructive': { h: 0, s: 1, l: 0.5 }, + '--destructive-foreground': { h: 210, s: 0.4, l: 0.98 }, + '--black-button': { h: 0, s: 0, l: 0.07 }, +}; + +const themeKeys = { + "--primary": "主题色", + "--primary-foreground": "主题前景色", + "--background": "背景色", + "--foreground": "前景色", + "--muted": "柔和背景色", + "--muted-foreground": "柔和前景色", + "--card": "卡片背景色", + "--card-foreground": "卡片前景色", + "--popover": "弹出框背景色", + "--popover-foreground": "弹出框前景色", + "--border": "边框色", + "--input": "输入框边框色", + "--secondary": "次要按钮背景色", + "--secondary-foreground": "次要按钮前景色", + "--accent": "强调色", + "--accent-foreground": "强调前景色", + "--destructive": "警告按钮背景色", + "--destructive-foreground": "警告按钮前景色", + "--ring": "聚焦边框色", + "--radius": "圆角半径", + "--warning": "警告色", + "--warning-foreground": "警告前景色", + '--black-button': '黑按钮', +}; + +export default function Theme() { + const [theme, setTheme] = useState(Object.keys(window.ThemeStyle.comp).length ? window.ThemeStyle.comp : { ...defaultTheme }); + + const applyTheme = (theme) => { + Object.keys(theme).forEach(key => { + document.documentElement.style.setProperty(key, handleHSLtoStr(theme[key])); + }); + setTheme(theme); + window.ThemeStyle = { comp: theme } + saveThemeApi(JSON.stringify({ comp: theme })) + }; + + // hsl -> '220 98% 95%' + const handleHSLtoStr = (hsl) => { + return `${hsl.h} ${hsl.s * 100}% ${hsl.l * 100}%` + } + + const handleHSLChange = (name, hsl) => { + const newTheme = { + ...theme, + [name]: hsl, + }; + setTheme(newTheme); + document.documentElement.style.setProperty(name, handleHSLtoStr(hsl)); + // save + window.ThemeStyle = { comp: newTheme } + saveThemeApi(JSON.stringify({ comp: newTheme })) + }; + + + return
+
+

+ 颜色配置 + +

+
+ { + Object.keys(theme).map(key => { + return + }) + } +
+
+
+

组件预览

+
+ {/* 组件列表 */} + +
+
+
+}; diff --git a/src/routes.tsx b/src/routes.tsx index 41844c8..6c6732b 100755 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -5,11 +5,15 @@ import FileLibPage from "./pages/FileLibPage"; import FilesPage from "./pages/FileLibPage/files"; // import FilesPage from "./pages/Knowledge/knowledge"; import FlowPage from "./pages/FlowPage"; +import LogPage from "./pages/LogPage"; +import { ResetPwdPage } from "./pages/LoginPage/resetPwd"; import ModelPage from "./pages/ModelPage"; import Doc from "./pages/ModelPage/doc"; +import Page403 from "./pages/Page403"; import Report from "./pages/Report"; import SkillChatPage from "./pages/ChatAppPage"; import ChatShare from "./pages/ChatAppPage/chatShare"; +import ChatAssitantShare from "./pages/ChatAppPage/chatAssitantShare"; import SkillAssisPage from "./pages/SkillPage/tabAssistant"; import EditAssistantPage from "./pages/SkillPage/editAssistant"; import SkillsPage from "./pages/SkillPage/tabSkills"; @@ -23,6 +27,8 @@ import Templates from "./pages/SkillPage/temps"; import DiffFlowPage from "./pages/DiffFlowPage"; import { ErrorBoundary } from "react-error-boundary"; import CrashErrorComponent from "./components/CrashErrorComponent"; +import EvaluatingPage from "./pages/EvaluationPage"; +import EvaluatingCreate from "./pages/EvaluationPage/EvaluationCreate"; // react 与 react router dom版本不匹配 // const FileLibPage = lazy(() => import(/* webpackChunkName: "FileLibPage" */ "./pages/FileLibPage")); @@ -68,13 +74,16 @@ const router = createBrowserRouter([ { path: "build/temps", element: }, { path: "model", element: }, { path: "sys", element: }, + { path: "log", element: }, + { path: "evaluation", element: }, + { path: "evaluation/create", element: }, ], }, { path: "model/doc", element: }, { path: "/flow/:id/", children: [ - { path: "", element: } + { path: "", element: } ] }, { @@ -86,9 +95,12 @@ const router = createBrowserRouter([ // 独立会话页 { path: "/chat", element: }, { path: "/chat/:id/", element: }, + { path: "/chat/assistant/:id/", element: }, { path: "/report/:id/", element: }, { path: "/diff/:id/:vid/:cid", element: }, + { path: "/reset", element: }, // { path: "/test", element: }, + { path: "/403", element: }, { path: "*", element: } ]); diff --git a/src/routesM.tsx b/src/routesM.tsx index 7e09070..9ded233 100755 --- a/src/routesM.tsx +++ b/src/routesM.tsx @@ -8,6 +8,7 @@ import Doc from "./pages/ModelPage/doc"; import Report from "./pages/Report"; import SkillChatPage from "./pages/ChatAppPage"; import ChatShare from "./pages/ChatAppPage/mobile/chatShareM"; +import ChatAssitantShare from "./pages/ChatAppPage/mobile/chatAssitantShare"; import SkillPage from "./pages/SkillPage"; import L2Edit from "./pages/SkillPage/l2Edit"; import SystemPage from "./pages/SystemPage"; @@ -44,6 +45,7 @@ const router = createBrowserRouter([ // 独立会话页 { path: "/chat", element: }, { path: "/chat/:id/", element: }, + { path: "/chat/assistant/:id/", element: }, { path: "/report/:id/", element: }, // { path: "/test", element: }, { path: "*", element: } diff --git a/src/store/assistantStore.tsx b/src/store/assistantStore.tsx index 802ba30..dc4ddd0 100644 --- a/src/store/assistantStore.tsx +++ b/src/store/assistantStore.tsx @@ -13,7 +13,7 @@ type State = { type Actions = { dispatchAssistant: (action: Action, assistantState: Partial) => void, - loadAssistantState: (id: string) => Promise + loadAssistantState: (id: string, version: string) => Promise saveAfter: () => void destroy: () => void } @@ -58,8 +58,8 @@ export const useAssistantStore = create((set) => ({ assistantState: { ...assistantTemp }, dispatchAssistant: (action: Action, data: Partial) => set((state) => assistantReducer(state, action, data)), // 加载助手状态 - loadAssistantState: (id) => { - return getAssistantDetailApi(id).then(data => { + loadAssistantState: (id, version) => { + return getAssistantDetailApi(id, version).then(data => { set({ assistantState: { ...data, diff --git a/src/style/zk.scss b/src/style/zk.scss index 2ba65db..5661490 100644 --- a/src/style/zk.scss +++ b/src/style/zk.scss @@ -1,17 +1,17 @@ -.nav-link1,.nav-link2,.nav-link3,.nav-link4,.nav-link5 { +.nav-link1,.nav-link2,.nav-link3,.nav-link4,.nav-link5,.nav-link6,.nav-link7 { background-image: none; text-decoration: none; color: white; display: flex; justify-content: center; align-items: flex-end; - margin-bottom: 40px; + margin-bottom: 20px; & span { font-weight: 400; font-size: 12px; color: #666666; - margin-bottom: -3px; + // margin-bottom: -3px; } } .nav-link1 { @@ -79,6 +79,32 @@ color: #FFCF33; } } + .nav-link6 { + background-image: url('../assets/nav/icon6.png'); + background-size: 100%; + background-repeat: no-repeat; + } + .nav-link6.active { + background-image: url('../assets/nav/icon6-active.png'); + background-size: 100%; + background-repeat: no-repeat; + & span { + color: #FFCF33; + } + } + .nav-link7 { + background-image: url('../assets/nav/icon7.png'); + background-size: 100%; + background-repeat: no-repeat; + } + .nav-link7.active { + background-image: url('../assets/nav/icon7-active.png'); + background-size: 100%; + background-repeat: no-repeat; + & span { + color: #FFCF33; + } + } .xinDuiHua-box{ background-image: url('../assets/chat/duihua-bg.png'); background-size: 30px 100%; @@ -132,7 +158,7 @@ background-color: rgba(255, 225, 128, 0.1); >div:nth-of-type(1){ >div{ - >p{ + >p:nth-of-type(1){ color: #E5BB2E!important; } } @@ -2481,6 +2507,9 @@ .border-radius-7{ border-radius: 7px; } + .border-radius-35{ + border-radius: 35px!important; + } .skillSettings{ width: 542px; margin: 0 auto; @@ -3026,7 +3055,7 @@ } } */ .zhanghaoTab{ - width: 203px; + width: 300px; height: 27px; background: #333333!important; border-radius: 7px; @@ -3402,7 +3431,7 @@ } } .selectNpcFlexbox{ - height: 127px; + min-height: 127px; padding-bottom: 14px; display: block; /* padding: 14px; */ @@ -3806,6 +3835,17 @@ height: 20px!important; background: #4D4D4D!important; } + .xiala .data-\[state\=checked\]\:bg-primary[data-state=checked]{ + width: 15px!important; + height: 15px!important; + background: #ffd025!important; + } + .xiala button{ + width: 15px!important; + height: 15px!important; + border: 1px solid #ffd025!important; + } + .build-tab{ display: flex; @@ -4214,4 +4254,7 @@ } } } + } + .bg-reset{ + box-shadow: 0px 3px 14px 0px rgba(255,255,255,0.2)!important; } \ No newline at end of file diff --git a/src/types/api/user.ts b/src/types/api/user.ts index a448550..7992eb9 100644 --- a/src/types/api/user.ts +++ b/src/types/api/user.ts @@ -19,3 +19,13 @@ export type ROLE = { role_name: string update_time: string } + +export type UserGroup = { + id: number + group_name: string + adminUser: string + group_admins: any[] + createTime: string + updateTime: string + groupLimit?: number +} \ No newline at end of file diff --git a/src/types/assistant/index.tsx b/src/types/assistant/index.tsx index 64f3cae..d8e614b 100644 --- a/src/types/assistant/index.tsx +++ b/src/types/assistant/index.tsx @@ -27,6 +27,8 @@ export interface AssistantDetail { create_time: string; /** 更新时间 */ update_time: string; + /** 内容安全审查对象 */ + // content_security: object; /** 助手的工具ID列表, 空列表则清空绑定的工具,为None则不更新 */ tool_list?: AssistantTool[]; /** 助手的技能ID列表,为None则不更新 */ diff --git a/src/util/hook.ts b/src/util/hook.ts index d7ca84c..7d13f96 100644 --- a/src/util/hook.ts +++ b/src/util/hook.ts @@ -57,7 +57,7 @@ export function useCopyText() { // 表格通用逻辑(分页展示、表格数据、关键词检索) export function useTable(param, apiFun) { - + const cancelLoadingWhenReload = param.cancelLoadingWhenReload || false; const [page, setPage] = useState({ page: 1, pageSize: param.pageSize || 20, @@ -71,7 +71,7 @@ export function useTable(param, apiFun) { const requestIdRef = useRef(0); // 控制请求响应顺序 const loadData = () => { - // setLoading(true); + !cancelLoadingWhenReload && setLoading(true); const requestId = ++requestIdRef.current apiFun({ ...page, ...paramRef.current }).then(res => { if (requestId !== requestIdRef.current) return @@ -79,7 +79,7 @@ export function useTable(param, apiFun) { // res.data.unshift({type:0}) setData(res.data); setTotal(res.total); - // setLoading(false); + setLoading(false); }).catch(() => { // setLoading(false); }) diff --git a/src/util/utils.ts b/src/util/utils.ts index 0a417d5..3be2240 100644 --- a/src/util/utils.ts +++ b/src/util/utils.ts @@ -77,6 +77,37 @@ export function formatMilliseconds(ms: number, format: string): string { return formattedString; } +// Date转换为目标格式 +export function formatDate(date: Date, format: string): string { + const addZero = (num) => num < 10 ? `0${num}` : `${num}` + const replacements = { + 'yyyy': date.getFullYear(), + 'MM': addZero(date.getMonth() + 1), + 'dd': addZero(date.getDate()), + 'HH': addZero(date.getHours()), + 'mm': addZero(date.getMinutes()), + 'ss': addZero(date.getSeconds()) + } + return format.replace(/yyyy|MM|dd|HH|mm|ss/g, (match) => replacements[match]) +} + +// param time: yyyy-mm-ddTxxxx +export function formatStrTime(time: string, notSameDayFormat: string): string { + if (!time) return '' + const date1 = new Date(time) + const date2 = new Date() + return date1.getFullYear() === date2.getFullYear() && + date1.getMonth() === date2.getMonth() && + date1.getDate() === date2.getDate() ? formatDate(date1, 'HH:mm') : formatDate(date1, notSameDayFormat) + // const newTime = time.substring(0, time.indexOf('T')).split('-') + // const arrayTime = newTime.map(t => Number(t)) + // const [year, month, day] = [date.getFullYear(), date.getMonth() + 1, date.getDay()] + // if(year === arrayTime[0] && month === arrayTime[1] && day === arrayTime[2]) { + // return time.substring(time.indexOf('T') + 1, time.length - 3) + // } + // return `${newTime[1]}月${newTime[2]}日` +} + export function toTitleCase(str: string | undefined): string { if (!str) return ""; let result = str @@ -113,4 +144,4 @@ export function getFieldTitle( : template[templateField].name ? toTitleCase(template[templateField].name!) : toTitleCase(templateField); -} +} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index 3ae5fe7..d5e3b19 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,14 +1,15 @@ import react from "@vitejs/plugin-react-swc"; import { visualizer } from "rollup-plugin-visualizer"; import { defineConfig } from "vite"; +import { createHtmlPlugin } from 'vite-plugin-html'; import { viteStaticCopy } from 'vite-plugin-static-copy'; import svgr from "vite-plugin-svgr"; const apiRoutes = ["^/api/", "/health"]; import path from "path"; // Use environment variable to determine the target. // const target = process.env.VITE_PROXY_TARGET || "http://localhost:7860"; - const target = process.env.VITE_PROXY_TARGET || "http://npcall.ai:3003"; -// const target = process.env.VITE_PROXY_TARGET || "http://172.30.40.241:7866"; +// const target = process.env.VITE_PROXY_TARGET || "http://npcall.ai:3101"; + const target = process.env.VITE_PROXY_TARGET || "http://dev.npcall.ai:3201"; const proxyTargets = apiRoutes.reduce((proxyObj, route) => { proxyObj[route] = { @@ -27,8 +28,11 @@ const proxyTargets = apiRoutes.reduce((proxyObj, route) => { return proxyObj; }, {}); +const app_env = { BASE_URL: '' } + export default defineConfig(() => { return { + base: app_env.BASE_URL || '/', build: { outDir: "build", rollupOptions: { @@ -50,6 +54,14 @@ export default defineConfig(() => { plugins: [ react(), svgr(), + createHtmlPlugin({ + minify: true, + inject: { + data: { + aceScriptSrc: ``, + }, + }, + }), viteStaticCopy({ targets: [ { @@ -73,6 +85,9 @@ export default defineConfig(() => { // open: true, // }) ], + define: { + __APP_ENV__: JSON.stringify(app_env) + }, server: { host: '0.0.0.0', port: 3001,