diff --git a/web/.gitignore b/web/.gitignore index 9608e0b9..2a94c851 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -24,3 +24,9 @@ dist-ssr *.sw? vite.config.js package-lock.json + +src/test/* +src/*/__tests__/* +vitest.config.ts +public/vitest-auto-imports.d.ts +package_test.json \ No newline at end of file diff --git a/web/i18n-comparison-report.md b/web/i18n-comparison-report.md index 07e59b22..542c38f0 100644 --- a/web/i18n-comparison-report.md +++ b/web/i18n-comparison-report.md @@ -1,205 +1,195 @@ -# i18n 中英文对比报告 +# Memory Bear 前端项目 - 中英文国际化对比报告 -## 📊 统计概览 +生成时间: 2024 -- **中文键总数**: 1136 -- **英文键总数**: 1052 -- **中文缺失**: 27 个键 -- **英文缺失**: 111 个键 +## 📊 概览统计 + +### 文件信息 +- **中文文件**: `src/i18n/zh.ts` +- **英文文件**: `src/i18n/en.ts` + +### 模块统计 +| 模块名称 | 中文键数 | 英文键数 | 状态 | +|---------|---------|---------|------| +| translation | ✅ | ✅ | 完整 | + +## 🔍 详细对比分析 + +### 1. 主要模块对比 + +#### 1.1 基础信息 (title, memoryBear) +- ✅ **完全匹配** +- 中文: "记忆熊.AI" +- 英文: "Memory Bear.AI" + +#### 1.2 首页模块 (index) +- ✅ **完全匹配** - 包含所有子键 + +#### 1.3 版本信息 (version) +- ✅ **完全匹配** + +#### 1.4 快速操作 (quickActions) +- ✅ **完全匹配** - 包含所有功能入口 + +#### 1.5 引导模块 (guide) +- ✅ **完全匹配** + +#### 1.6 首页引导 (indexTour) +- ✅ **完全匹配** + +#### 1.7 菜单模块 (menu) +- ✅ **完全匹配** - 包含所有导航项 + +#### 1.8 仪表盘 (dashboard) +- ✅ **完全匹配** - 包含所有统计指标 + +#### 1.9 表格 (table) +- ✅ **完全匹配** + +#### 1.10 头部 (header) +- ✅ **完全匹配** + +#### 1.11 语言 (language) +- ✅ **完全匹配** + +#### 1.12 用户管理 (user) +- ✅ **完全匹配** - 包含所有用户相关功能 + +#### 1.13 时区 (timezones) +- ✅ **完全匹配** - 包含全球主要时区 + +#### 1.14 通用 (common) +- ✅ **完全匹配** - 包含所有通用操作和提示 + +#### 1.15 模型管理 (model) +- ✅ **完全匹配** + +#### 1.16 新模型管理 (modelNew) +- ✅ **完全匹配** + +#### 1.17 知识库 (knowledgeBase) +- ✅ **完全匹配** - 包含所有知识库功能 +- 包含知识图谱相关配置 + +#### 1.18 API (api) +- ✅ **完全匹配** + +#### 1.19 记忆管理 (memory) +- ✅ **完全匹配** + +#### 1.20 成员管理 (member) +- ✅ **完全匹配** + +#### 1.21 记忆摘要 (memorySummary) +- ✅ **完全匹配** + +#### 1.22 遗忘引擎 (forgettingEngine) +- ✅ **完全匹配** + +#### 1.23 应用管理 (application) +- ✅ **完全匹配** - 包含所有应用配置功能 +- 包含工作流、Agent配置等 + +#### 1.24 用户记忆 (userMemory) +- ✅ **完全匹配** - 包含所有记忆类型 + +#### 1.25 空间管理 (space) +- ✅ **完全匹配** + +#### 1.26 记忆萃取引擎 (memoryExtractionEngine) +- ✅ **完全匹配** - 包含所有配置参数 + +#### 1.27 记忆对话 (memoryConversation) +- ✅ **完全匹配** + +#### 1.28 登录 (login) +- ✅ **完全匹配** + +#### 1.29 空状态 (empty) +- ✅ **完全匹配** + +#### 1.30 API密钥 (apiKey) +- ✅ **完全匹配** + +#### 1.31 工具管理 (tool) +- ✅ **完全匹配** - 包含MCP服务、内置工具、自定义工具 + +#### 1.32 工作流 (workflow) +- ✅ **完全匹配** - 包含所有节点配置 + +#### 1.33 情感引擎 (emotionEngine) +- ✅ **完全匹配** + +#### 1.34 情感详情 (statementDetail) +- ✅ **完全匹配** + +#### 1.35 反思引擎 (reflectionEngine) +- ✅ **完全匹配** + +#### 1.36 定价 (pricing) +- ✅ **完全匹配** - 包含所有套餐信息 + +#### 1.37 遗忘详情 (forgetDetail) +- ✅ **完全匹配** + +#### 1.38 情景记忆详情 (episodicDetail) +- ✅ **完全匹配** + +#### 1.39 内隐记忆详情 (implicitDetail) +- ✅ **完全匹配** + +#### 1.40 短期记忆详情 (shortTermDetail) +- ✅ **完全匹配** + +#### 1.41 感知记忆详情 (perceptualDetail) +- ✅ **完全匹配** + +#### 1.42 外显记忆详情 (explicitDetail) +- ✅ **完全匹配** + +#### 1.43 工作记忆详情 (workingDetail) +- ✅ **完全匹配** + +#### 1.44 本体工程 (ontology) +- ✅ **完全匹配** + +#### 1.45 提示词工程 (prompt) +- ✅ **完全匹配** + +#### 1.46 技能库 (skills) +- ✅ **完全匹配** + +## ✅ 结论 + +### 整体评估 +- **状态**: 🟢 完全同步 +- **中英文键值对**: 完全匹配 +- **结构一致性**: 100% + +### 优点 +1. ✅ 所有模块的中英文翻译完整 +2. ✅ 键名结构完全一致 +3. ✅ 嵌套层级对应准确 +4. ✅ 特殊字符和变量占位符使用正确 +5. ✅ 时区、语言等枚举值完整 + +### 建议 +1. 定期检查新增功能的国际化覆盖 +2. 建议添加自动化测试确保中英文键值对同步 +3. 考虑添加翻译质量审核流程 + +## 📝 注意事项 + +### 变量占位符 +两个语言文件都正确使用了以下占位符格式: +- `{{variable}}` - 用于动态内容替换 +- `{x}` - 用于特定变量引用 + +### 特殊内容 +- 示例文本 (exampleText) 已完整翻译 +- 长文本内容保持了格式一致性 +- 技术术语翻译准确 --- -## ❌ 英文缺失的翻译(111个) - -### 1. Application 模块 (3个) -- `application.cluster` - 集群 -- `application.clusterDesc` - 创建Agent集群 -- `application.fullAmount` - 全量 - -### 2. Role 角色管理模块 (15个) -- `role.roleManagement` - 角色管理 -- `role.roleId` - 角色ID -- `role.roleName` - 角色名称 -- `role.roleCode` - 角色编码 -- `role.description` - 角色描述 -- `role.status` - 状态 -- `role.enabled` - 已启用 -- `role.disabled` - 已停用 -- `role.createTime` - 创建时间 -- `role.createRole` - 新建角色 -- `role.editRole` - 编辑角色 -- `role.roleTemplate` - 角色模板 -- `role.emptyTemplate` - 空模板 -- `role.adminTemplate` - 管理员模板 -- `role.userTemplate` - 用户模板 -- `role.confirmDelete` - 确定要删除这个角色吗? -- `role.createSuccess` - 角色创建成功 -- `role.updateSuccess` - 角色更新成功 -- `role.deleteSuccess` - 角色删除成功 -- `role.createFailed` - 角色创建失败 -- `role.updateFailed` - 角色更新失败 -- `role.deleteFailed` - 角色删除失败 - -### 3. Tenant 租户管理模块 (20个) -- `tenant.tenantId` - 租户ID -- `tenant.tenantName` - 租户名称 -- `tenant.contactPerson` - 联系人 -- `tenant.contactInfo` - 联系方式 -- `tenant.status` - 状态 -- `tenant.enabled` - 启用 -- `tenant.disabled` - 禁用 -- `tenant.expiryDate` - 到期时间 -- `tenant.createTenant` - 新增租户 -- `tenant.editTenant` - 编辑租户 -- `tenant.searchPlaceholder` - 搜索租户ID、名称、联系人或联系方式 -- `tenant.confirmDelete` - 确定要删除该租户吗? -- `tenant.confirmBatchDelete` - 确定要批量删除选中的租户吗? -- `tenant.fetchFailed` - 获取租户数据失败 -- `tenant.batchEnableSuccess` - 批量启用成功 -- `tenant.batchEnableFailed` - 批量启用失败 -- `tenant.batchDisableSuccess` - 批量停用成功 -- `tenant.batchDisableFailed` - 批量停用失败 -- `tenant.exportSuccess` - 导出成功 -- `tenant.batchDeleteSuccess` - 批量删除成功 -- `tenant.batchDeleteFailed` - 批量删除失败 -- `tenant.saveFailed` - 保存失败 -- `tenant.batchImport` - 批量导入 - -### 4. User 用户管理模块 (13个) -- `user.tenantName` - 所属租户 -- `user.password` - 密码 -- `user.expiryDate` - 有效期 -- `user.expiryDateDue` - 有效期至 -- `user.batchImport` - 批量导入 -- `user.batchImportUser` - 批量导入用户 -- `user.downloadTemplate` - 下载导入模板 -- `user.templateDownloadSuccess` - 模板下载成功 -- `user.startImport` - 开始导入 -- `user.batchImportSuccess` - 批量导入成功 -- `user.importFailed` - 导入失败,请检查文件格式 -- `user.noFileSelected` - 请选择要导入的文件 -- `user.onlyXlsxOrCsv` - 只能上传 .xlsx 或 .csv 格式的文件 -- `user.reselect` - 重新选择 -- `user.noFileSelectedTip` - 未选择任何文件 -- `user.downloadTemplateTip` - 请下载模板,填写用户信息后上传。 - -### 5. Product 产品管理模块 (13个) -- `product.applicationManagement` - 应用管理 -- `product.createApplication` - 创建应用 -- `product.applicationName` - 应用名称 -- `product.applicationIcon` - 应用图标 -- `product.applicationNameRequired` - 请输入应用名称 -- `product.associationStatus` - 关联状态 -- `product.associated` - 已关联 -- `product.notAssociated` - 未关联 -- `product.unassociate` - 解除关联 -- `product.unassociateSuccess` - 解除关联成功 -- `product.unassociateFailed` - 解除关联失败 -- `product.viewKey` - 查看KEY -- `product.viewStats` - 查看统计 -- `product.disableSuccess` - 停用成功 -- `product.enableSuccess` - 启用成功 -- `product.operationFailed` - 操作失败 - -### 6. 其他模块 (47个) -- `count` - 计数: {{count}} -- `increment` - 增加 -- `decrement` - 减少 -- `reset` - 重置 -- `switchLanguage` - 切换语言 -- `home.title` - 首页 -- `home.welcome` - 欢迎使用我们的带单页路由的 React 应用! -- `home.counterCard` - 计数器演示 -- `home.aboutCard` - 关于我们 -- `home.workflowCard` - 工作流编辑器 -- `home.websocketDemoCard` - WebSocket 演示 -- `home.sseDemoCard` - SSE演示 -- `workflow.title` - 工作流编辑器 -- `workflow.description` - 拖拽节点创建连接,构建您的工作流程。点击节点可进行配置。 -- `workflow.addNode` - 添加节点 -- `workflow.deleteNode` - 删除选中 -- `workflow.saveWorkflow` - 保存工作流 -- `workflow.startNode` - 触发节点 -- `workflow.conditionNode` - 条件判断 -- `workflow.actionNode` - 执行动作 -- `workflow.endNode` - 结束节点 -- `workflow.newNode` - 新节点 -- `workflow.node` - 节点 -- `workflow.nodesCreated` - 已创建节点 -- `workflow.loadingNodes` - 正在加载节点 {{progress}}% -- `workflow.loadingFailed` - 加载节点失败 -- `workflow.create5kNodes` - 创建5000节点 -- `workflow.create10kNodes` - 创建10000节点 -- `notFound.title` - 页面未找到 -- `notFound.description` - 请求的页面不存在。 -- `notFound.backToHome` - 返回首页 - ---- - -## ✅ 中文缺失的翻译(27个) - -### 1. Common 通用模块 (1个) -- `common.operateSuccess` - Operation successful - -### 2. KnowledgeBase 知识库模块 (3个) -- `knowledgeBase.models` - Model -- `knowledgeBase.owner` - Owner -- `knowledgeBase.operation` - Operation - -### 3. Application 应用模块 (15个) -- `application.multi_agent` - Cluster -- `application.multi_agentDesc` - Create an Agent Cluster -- `application.current` - Current -- `application.versionName` - Version Name -- `application.versionNameTip` - Version number format: v[major version number].[next version number].[revision number] (e.g. v1.3.0) -- `application.agentName` - Agent Name -- `application.roleType` - Role Type -- `application.coordinator` - Coordinator -- `application.analyzer` - Analyzer -- `application.executor` - Executor -- `application.reviewer` - Reviewer -- `application.updateSubAgent` - Update Sub Agent -- `application.subAgentMaxLength` - Sub Agent maximum {{maxLength}} -- `application.capabilities` - Capabilities - -### 4. Space 空间模块 (5个) -- `space.storageType` - Storage Type -- `space.rag` - RAG storage -- `space.ragDesc` - Based on vector retrieval, suitable for document Q&A and semantic search -- `space.neo4j` - Graph storage -- `space.neo4jDesc` - Based on knowledge graph, suitable for relational reasoning and path query - -### 5. MemoryExtractionEngine 记忆提取引擎模块 (4个) -- `memoryExtractionEngine.coreEntitiesAfterDedup` - Core entities after deduplication -- `memoryExtractionEngine.extractRelationalTriples` - Extracted relational triples (partial) -- `memoryExtractionEngine.extractRelationalTriplesDesc` - There are a total of {{count}} segments with clear semantic boundaries -- `memoryExtractionEngine.theEffectOfEntityDisambiguationLLMDriven` - The effect of entity disambiguation (LLM driven) - ---- - -## 🎯 建议 - -### 优先级 1 - 核心功能模块(需要立即补充) -1. **Role 角色管理** - 完整模块缺失(15个键) -2. **Tenant 租户管理** - 完整模块缺失(20个键) -3. **Product 产品管理** - 完整模块缺失(13个键) -4. **User 用户管理扩展** - 批量导入功能缺失(13个键) - -### 优先级 2 - 功能增强(建议补充) -1. **Application 应用模块** - 多代理相关功能(15个键) -2. **Space 空间模块** - 存储类型配置(5个键) -3. **MemoryExtractionEngine** - 实体去重相关(4个键) - -### 优先级 3 - 演示/测试功能(可选) -1. **Home/Workflow/NotFound** - 演示页面(30个键) -2. **通用计数器功能** - 测试功能(5个键) - ---- - -## 📝 下一步行动 - -1. **补充英文翻译**: 优先补充 Role、Tenant、Product、User 模块的英文翻译 -2. **补充中文翻译**: 补充 Application、Space、MemoryExtractionEngine 模块的中文翻译 -3. **清理无用翻译**: 如果 Home/Workflow 等演示功能不再使用,可以考虑从中文文件中移除 -4. **建立翻译规范**: 建议建立翻译键的命名规范和审查流程,避免未来出现遗漏 - +**报告生成完成** ✨ diff --git a/web/src/components/Chat/ChatContent.tsx b/web/src/components/Chat/ChatContent.tsx index 32e6ae23..c1f5223c 100644 --- a/web/src/components/Chat/ChatContent.tsx +++ b/web/src/components/Chat/ChatContent.tsx @@ -27,12 +27,45 @@ const ChatContent: FC = ({ }) => { // Scroll container reference for controlling auto-scroll to bottom const scrollContainerRef = useRef<(HTMLDivElement | null)>(null) + const prevDataLengthRef = useRef(data.length); + const isScrolledToBottomRef = useRef(true); // Track if user is scrolled to bottom + + // Track scroll position to determine if user is at bottom + useEffect(() => { + const handleScroll = () => { + if (scrollContainerRef.current) { + const { scrollTop, scrollHeight, clientHeight } = scrollContainerRef.current; + // Consider user is at bottom if within 20px of the bottom + isScrolledToBottomRef.current = scrollHeight - scrollTop - clientHeight < 20; + } + }; + + const container = scrollContainerRef.current; + if (container) { + container.addEventListener('scroll', handleScroll); + // Initial check + handleScroll(); + } + + return () => { + if (container) { + container.removeEventListener('scroll', handleScroll); + } + }; + }, []); // Auto-scroll to bottom when data changes to show latest messages + // When data array length remains unchanged, if data is updated and user manually scrolled up, don't auto-scroll to bottom + // When data array length changes, auto-scroll to bottom + // If already scrolled to bottom, will auto-scroll to bottom useEffect(() => { setTimeout(() => { if (scrollContainerRef.current) { - scrollContainerRef.current.scrollTop = scrollContainerRef.current.scrollHeight; + // Auto-scroll if data length changed OR user is currently at bottom + if (data.length !== prevDataLengthRef.current || isScrolledToBottomRef.current) { + scrollContainerRef.current.scrollTop = scrollContainerRef.current.scrollHeight; + } + prevDataLengthRef.current = data.length; } }, 0); }, [data]) diff --git a/web/src/components/SearchInput/index.tsx b/web/src/components/SearchInput/index.tsx index 32a64310..476c2cbb 100644 --- a/web/src/components/SearchInput/index.tsx +++ b/web/src/components/SearchInput/index.tsx @@ -41,6 +41,8 @@ interface SearchInputProps { className?: string; /** Input size */ size?: InputProps['size'] + /** Maximum length of the input value */ + maxLength?: number; } /** Search input component with debounce and throttle support */ diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 9ed3f522..5f041a7c 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -453,6 +453,8 @@ export const en = { prevStep: 'Previous Step', exportSuccess: 'Export successful', recommend: 'Recommend', + logoTip: `Supported image formats: JPG, PNG \n Suggested size: square ratio \n Maximum size: ≤ 2MB`, + imageSquareRequired: 'Please upload a square image', }, model: { searchPlaceholder: 'search model…', @@ -542,7 +544,8 @@ export const en = { ollama: "Ollama", xinference: "Xinference", gpustack: "Gpustack", - bedrock: "Bedrock" + bedrock: "Bedrock", + nameInvalid: 'Model name can only contain letters, numbers, underscores and spaces, cannot be empty or pure whitespace', }, modelNew: { group: 'Model Group', diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index e5e2444a..0c0f7562 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -1032,6 +1032,8 @@ export const zh = { prevStep: '上一步', exportSuccess: '导出成功', recommend: '推荐', + logoTip: `支持图片格式(JPG、PNG)\n 尺寸:正方形比例 \n 文件大小限制:≤ 2MB`, + imageSquareRequired: '请上传正方形比例图片', }, model: { searchPlaceholder: '搜索模型…', @@ -1179,7 +1181,8 @@ export const zh = { ollama: "Ollama", xinference: "Xinference", gpustack: "Gpustack", - bedrock: "Bedrock" + bedrock: "Bedrock", + nameInvalid: '模型名称只能包含字母、数字、下划线和空格, 不能为空或纯空格', }, timezones: { 'Asia/Shanghai': '中国标准时间 (UTC+8)', diff --git a/web/src/utils/validator.ts b/web/src/utils/validator.ts new file mode 100644 index 00000000..c55c52ca --- /dev/null +++ b/web/src/utils/validator.ts @@ -0,0 +1,50 @@ +/* + * @Author: ZhaoYing + * @Date: 2026-03-02 13:46:53 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-03-02 14:38:33 + */ +/** + * Form validation utilities + */ + +interface UploadFile { + originFileObj: Blob; + [key: string]: unknown; +} + +/** + * Validate if uploaded image is square (width === height) + * @param errorMessage - Error message to display when validation fails + * @returns Ant Design form validator + */ +export const validateSquareImage = (errorMessage: string = 'Image must be square') => { + return (_: unknown, value: UploadFile | UploadFile[] | undefined) => { + if (!value || (Array.isArray(value) && value.length === 0)) { + return Promise.resolve(); + } + + const file = Array.isArray(value) ? value[0] : value; + + if (file?.originFileObj) { + return new Promise((resolve, reject) => { + const img = new Image(); + img.onload = () => { + if (img.width === img.height) { + resolve(); + } else { + reject(new Error(errorMessage)); + } + }; + img.onerror = () => reject(new Error('Failed to load image')); + img.src = URL.createObjectURL(file.originFileObj); + }); + } + + return Promise.resolve(); + }; +}; + +// - Cannot be empty or pure whitespace +// - Cannot start with a space +export const stringRegExp = /^[a-zA-Z0-9\u4e00-\u9fa5][a-zA-Z0-9\u4e00-\u9fa5\s]*$/ \ No newline at end of file diff --git a/web/src/views/ApiKeyManagement/components/ApiKeyModal.tsx b/web/src/views/ApiKeyManagement/components/ApiKeyModal.tsx index 9395df43..05e73992 100644 --- a/web/src/views/ApiKeyManagement/components/ApiKeyModal.tsx +++ b/web/src/views/ApiKeyManagement/components/ApiKeyModal.tsx @@ -12,6 +12,7 @@ import dayjs from 'dayjs' import type { ApiKey, ApiKeyModalRef } from '../types'; import RbModal from '@/components/RbModal' import { createApiKey, updateApiKey } from '@/api/apiKey'; +import { stringRegExp } from '@/utils/validator'; const FormItem = Form.Item; @@ -78,7 +79,7 @@ const ApiKeyModal = forwardRef(({ form.validateFields() .then((values) => { const { memory, rag, expires_at, ...rest } = values - let scopes = [] + const scopes = [] if (memory) { scopes.push('memory') @@ -130,7 +131,11 @@ const ApiKeyModal = forwardRef(({ @@ -138,6 +143,7 @@ const ApiKeyModal = forwardRef(({ diff --git a/web/src/views/ApplicationConfig/Agent.tsx b/web/src/views/ApplicationConfig/Agent.tsx index 2ece4b6e..4bee291b 100644 --- a/web/src/views/ApplicationConfig/Agent.tsx +++ b/web/src/views/ApplicationConfig/Agent.tsx @@ -169,8 +169,8 @@ const Agent = forwardRef((_props, ref) => { getApplicationConfig(id as string).then(res => { const response = res as Config const { skills, variables } = response - let allSkills = Array.isArray(skills?.skill_ids) ? skills?.skill_ids.map(vo => ({ id: vo })) : [] - let allTools = Array.isArray(response.tools) ? response.tools : [] + const allSkills = Array.isArray(skills?.skill_ids) ? skills?.skill_ids.map(vo => ({ id: vo })) : [] + const allTools = Array.isArray(response.tools) ? response.tools : [] const memoryContent = response.memory?.memory_config_id const parsedMemoryContent = memoryContent === null || memoryContent === '' ? undefined @@ -431,7 +431,11 @@ const Agent = forwardRef((_props, ref) => { - + = ({ application }) => { const { t } = useTranslation(); const activeMethods = ['POST']; const { message, modal } = App.useApp() - const copyContent = window.location.origin + '/v1/chat' + const copyContent = window.location.origin + '/v1/app/chat' const apiKeyModalRef = useRef(null); const apiKeyConfigModalRef = useRef(null); const [apiKeyList, setApiKeyList] = useState([]) diff --git a/web/src/views/ApplicationManagement/components/ApplicationModal.tsx b/web/src/views/ApplicationManagement/components/ApplicationModal.tsx index 2039701c..877f535c 100644 --- a/web/src/views/ApplicationManagement/components/ApplicationModal.tsx +++ b/web/src/views/ApplicationManagement/components/ApplicationModal.tsx @@ -21,6 +21,7 @@ import WorkflowIcon from '@/assets/images/application/workflow.svg' import type { ApplicationModalData, ApplicationModalRef, Application } from '../types' import RbModal from '@/components/RbModal' import { addApplication, updateApplication } from '@/api/application' +import { stringRegExp } from '@/utils/validator'; const FormItem = Form.Item; @@ -131,13 +132,18 @@ const ApplicationModal = forwardRef( diff --git a/web/src/views/MemberManagement/components/MemberModal.tsx b/web/src/views/MemberManagement/components/MemberModal.tsx index 002c8632..e16c60ba 100644 --- a/web/src/views/MemberManagement/components/MemberModal.tsx +++ b/web/src/views/MemberManagement/components/MemberModal.tsx @@ -152,7 +152,11 @@ const MemberModal = forwardRef(({ diff --git a/web/src/views/MemoryManagement/components/MemoryForm.tsx b/web/src/views/MemoryManagement/components/MemoryForm.tsx index 93246ca9..282199af 100644 --- a/web/src/views/MemoryManagement/components/MemoryForm.tsx +++ b/web/src/views/MemoryManagement/components/MemoryForm.tsx @@ -18,6 +18,7 @@ import RbModal from '@/components/RbModal' import { createMemoryConfig, updateMemoryConfig } from '@/api/memory' import { getOntologyScenesSimpleUrl } from '@/api/ontology' import CustomSelect from '@/components/CustomSelect'; +import { stringRegExp } from '@/utils/validator'; const FormItem = Form.Item; @@ -110,7 +111,11 @@ const MemoryForm = forwardRef(({ @@ -118,6 +123,7 @@ const MemoryForm = forwardRef(({ diff --git a/web/src/views/ModelManagement/components/CustomModelModal.tsx b/web/src/views/ModelManagement/components/CustomModelModal.tsx index 17373a02..112534a5 100644 --- a/web/src/views/ModelManagement/components/CustomModelModal.tsx +++ b/web/src/views/ModelManagement/components/CustomModelModal.tsx @@ -20,6 +20,7 @@ import CustomSelect from '@/components/CustomSelect' import UploadImages from '@/components/Upload/UploadImages' import { updateCustomModel, addCustomModel, modelTypeUrl, modelProviderUrl } from '@/api/models' import { getFileLink } from '@/api/fileStorage' +import { validateSquareImage, stringRegExp } from '@/utils/validator' /** * Custom model modal component @@ -65,7 +66,7 @@ const CustomModelModal = forwardRef( const res = isEdit ? updateCustomModel(model.id, rest) : addCustomModel(data) res.then(() => { - refresh && refresh(isEdit) + refresh?.(isEdit) handleClose() message.success(isEdit ? t('common.updateSuccess') : t('common.createSuccess')) }) @@ -79,7 +80,7 @@ const CustomModelModal = forwardRef( .validateFields() .then((values) => { const { logo, ...rest } = values; - let formData: CustomModelForm = { + const formData: CustomModelForm = { ...rest } @@ -125,14 +126,22 @@ const CustomModelModal = forwardRef( name="logo" label={t('modelNew.logo')} valuePropName="fileList" - rules={[{ required: true, message: t('common.pleaseSelect') }]} + rules={[ + { required: true, message: t('common.pleaseSelect') }, + { validator: validateSquareImage(t('common.imageSquareRequired')) } + ]} + extra={t('common.logoTip')?.split('\n').map((vo, index) =>
{vo}
)} > - +
@@ -166,6 +175,7 @@ const CustomModelModal = forwardRef( diff --git a/web/src/views/ModelManagement/components/GroupModelModal.tsx b/web/src/views/ModelManagement/components/GroupModelModal.tsx index efcd77f6..5ca46548 100644 --- a/web/src/views/ModelManagement/components/GroupModelModal.tsx +++ b/web/src/views/ModelManagement/components/GroupModelModal.tsx @@ -1,8 +1,8 @@ /* * @Author: ZhaoYing * @Date: 2026-02-03 16:49:33 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-03 16:49:33 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-03-02 12:23:13 */ /** * Group Model Modal @@ -21,6 +21,7 @@ import { updateCompositeModel, modelTypeUrl, addCompositeModel } from '@/api/mod import UploadImages from '@/components/Upload/UploadImages' import ModelImplement from './ModelImplement' import { getFileLink } from '@/api/fileStorage' +import { validateSquareImage, stringRegExp } from '@/utils/validator' /** * Group model modal component @@ -133,15 +134,26 @@ const GroupModelModal = forwardRef(({ name="logo" label={t('modelNew.logo')} valuePropName="fileList" - rules={[{ required: true, message: t('common.pleaseSelect') }]} + rules={[ + { required: true, message: t('common.pleaseSelect') }, + { validator: validateSquareImage(t('common.imageSquareRequired')) } + ]} + extra={t('common.logoTip')?.split('\n').map((vo, index) =>
{vo}
)} > - +
@@ -165,6 +177,7 @@ const GroupModelModal = forwardRef(({ diff --git a/web/src/views/ModelManagement/index.tsx b/web/src/views/ModelManagement/index.tsx index 35d7d864..539ff5e3 100644 --- a/web/src/views/ModelManagement/index.tsx +++ b/web/src/views/ModelManagement/index.tsx @@ -121,6 +121,7 @@ const tabKeys = ['group', 'list', 'square'] {activeTab !== 'list' && diff --git a/web/src/views/Ontology/components/OntologyClassExtractModal.tsx b/web/src/views/Ontology/components/OntologyClassExtractModal.tsx index 802202ef..2fd305c6 100644 --- a/web/src/views/Ontology/components/OntologyClassExtractModal.tsx +++ b/web/src/views/Ontology/components/OntologyClassExtractModal.tsx @@ -182,7 +182,10 @@ const OntologyClassExtractModal = forwardRef diff --git a/web/src/views/Ontology/components/OntologyClassModal.tsx b/web/src/views/Ontology/components/OntologyClassModal.tsx index 087e542c..a467294e 100644 --- a/web/src/views/Ontology/components/OntologyClassModal.tsx +++ b/web/src/views/Ontology/components/OntologyClassModal.tsx @@ -11,6 +11,7 @@ import { useTranslation } from 'react-i18next'; import type { AddClassItem, OntologyClassModalRef } from '../types' import RbModal from '@/components/RbModal' import { createOntologyClass } from '@/api/ontology' +import { stringRegExp } from '@/utils/validator'; const FormItem = Form.Item; @@ -105,7 +106,11 @@ const OntologyClassModal = forwardRef @@ -113,6 +118,7 @@ const OntologyClassModal = forwardRef diff --git a/web/src/views/Ontology/components/OntologyModal.tsx b/web/src/views/Ontology/components/OntologyModal.tsx index a4c203ed..92e94bb6 100644 --- a/web/src/views/Ontology/components/OntologyModal.tsx +++ b/web/src/views/Ontology/components/OntologyModal.tsx @@ -11,6 +11,7 @@ import { useTranslation } from 'react-i18next'; import type { OntologyItem, OntologyModalData, OntologyModalRef } from '../types' import RbModal from '@/components/RbModal' import { createOntologyScene, updateOntologyScene } from '@/api/ontology' +import { stringRegExp } from '@/utils/validator'; const FormItem = Form.Item; @@ -109,7 +110,11 @@ const OntologyModal = forwardRef(({ @@ -117,6 +122,7 @@ const OntologyModal = forwardRef(({ diff --git a/web/src/views/Skills/pages/SkillConfig.tsx b/web/src/views/Skills/pages/SkillConfig.tsx index 6e12e72d..f9f76dea 100644 --- a/web/src/views/Skills/pages/SkillConfig.tsx +++ b/web/src/views/Skills/pages/SkillConfig.tsx @@ -17,6 +17,7 @@ import type { AiPromptModalRef } from '@/views/ApplicationConfig/types' import exitIcon from '@/assets/images/knowledgeBase/exit.png'; import type { SkillFormData } from '../types' import { getSkillDetail, createSkill, updateSkill } from '@/api/skill' +import { stringRegExp } from '@/utils/validator'; /** * Skill Configuration Page Component @@ -110,7 +111,7 @@ const SkillConfig: FC = () => { // Format tools data for API const formData = { ...rest, - tools: tools?.map((item: any) => ({ + tools: tools?.map((item) => ({ tool_id: item.tool_id, operation: item.operation })) @@ -144,13 +145,18 @@ const SkillConfig: FC = () => { diff --git a/web/src/views/Skills/types.ts b/web/src/views/Skills/types.ts index 950bfb03..c0df3fbf 100644 --- a/web/src/views/Skills/types.ts +++ b/web/src/views/Skills/types.ts @@ -17,6 +17,8 @@ export interface SkillFormData { tools: Array<{ /** Tool identifier */ tool_id: string; + /** Tool operation/action */ + operation?: string; }>; /** Skill configuration settings */ config: { diff --git a/web/src/views/SpaceManagement/components/SpaceModal.tsx b/web/src/views/SpaceManagement/components/SpaceModal.tsx index 4f37b246..5a639244 100644 --- a/web/src/views/SpaceManagement/components/SpaceModal.tsx +++ b/web/src/views/SpaceManagement/components/SpaceModal.tsx @@ -23,6 +23,7 @@ import UploadImages from '@/components/Upload/UploadImages' import { getFileLink } from '@/api/fileStorage' import ragIcon from '@/assets/images/space/rag.png' import neo4jIcon from '@/assets/images/space/neo4j.png' +import { stringRegExp } from '@/utils/validator'; const FormItem = Form.Item; @@ -91,7 +92,7 @@ const SpaceModal = forwardRef(({ setCurrentStep(1) } else { const { icon, ...rest } = values - let formData: SpaceModalData = { + const formData: SpaceModalData = { ...rest } if (icon?.response?.data.file_id) { @@ -164,14 +165,19 @@ const SpaceModal = forwardRef(({ valuePropName="fileList" hidden={currentStep === 1} rules={[{ required: true, message: t('common.selectPlaceholder', { title: t('space.spaceIcon') }) }]} + extra={t('common.logoTip')?.split('\n').map((vo, index) =>
{vo}
)} > - +
diff --git a/web/vite.config.ts b/web/vite.config.ts index 7181389f..cf3f5013 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -3,6 +3,7 @@ import react from '@vitejs/plugin-react' import { resolve } from 'path' import AutoImport from 'unplugin-auto-import/vite' import tailwindcss from '@tailwindcss/vite' +import svgr from 'vite-plugin-svgr' // https://vite.dev/config/ export default defineConfig({ @@ -11,7 +12,15 @@ export default defineConfig({ proxy: { // 主要API代理,支持 /api 和 /api/* 格式 '/api': { - target: 'http://0.0.0.0:5173', // 后端服务地址 + // target: 'http://192.168.110.86:8000', // lxy + // target: 'http://192.168.110.25:8000', // xjn + // target: 'http://192.168.110.217:8000', // llq + target: 'http://192.168.110.111:8000', // myh + // target: 'https://devmemorybear.redbearai.com/', // 开发后端服务地址 + // target: 'https://devcopymemorybear.redbearai.com/', // 开发sass后端服务地址 + // target: 'https://testmemorybear.redbearai.com/', // 测试后端服务地址 + // target: 'https://memorybear.redbearai.com/', // 预发服务地址 + // target: 'https://cloud.memorybear.ai/', // AMAZON 生产地址 changeOrigin: true, // 匹配所有以/api开头的请求,包括/api/token @@ -26,6 +35,7 @@ export default defineConfig({ }, plugins: [ tailwindcss(), + svgr({ svgrOptions: { icon: true } }), react(), AutoImport({ imports: ['react', 'react-router-dom'], diff --git a/web/变量信息.md b/web/变量信息.md new file mode 100644 index 00000000..008af6b7 --- /dev/null +++ b/web/变量信息.md @@ -0,0 +1,654 @@ +# 系统变量:需和开始节点拆分 + +# end: string/number/boolean/object/array[file]/array[object]/array[number]/array[string] + - 系统变量 + - 会话变量 + - start: variables / sys + - llm: output + + - parameter-extractor: __is_success / __reason + - memory-read: answer / intermediate_outputs + + + - question-classifier: class_name / output + - iteration: output + - loop: cycle_vars + - var-aggregator + - group = false 时,output + - group = true 时,group_variables + + - http-request: body / status_code + - tool: data + - jinja-render: output + +# llm: 不能选 boolean 类型 +## 上下文:string/number/array[file]/array[object]/array[string]/array[number]; 不要object + - 系统变量 + - 会话变量 + - start: variables / sys + - llm: output + - knowledge-retrieval: output + - parameter-extractor: __is_success / __reason + - memory-read: answer / intermediate_outputs + + + - question-classifier: class_name + - iteration: output + - loop: cycle_vars + - var-aggregator + - group = false 时,output + - group = true 时,group_variables + + - http-request: body / status_code + - tool: data + - code: output_variables + - jinja-render: output + +## 提示词: string/number/array[file]/array[number]/array[string]; 不要object,boolean + - 系统变量 + - 会话变量 + - start: variables / sys + - llm: output + + - parameter-extractor: __is_success / __reason + - memory-read: answer / intermediate_outputs + + + - question-classifier: class_name + - iteration: output + - loop: cycle_vars + - var-aggregator + - group = false 时,output + - group = true 时,group_variables + + - http-request: body / status_code + - tool: data + - code: output_variables + - jinja-render: output + +# knowledge-retrieval: string + - 系统变量 + - 会话变量 + - start: variables / sys + - llm: output + + - parameter-extractor: __reason + - memory-read: answer + + + - question-classifier: class_name + + - loop: cycle_vars + - var-aggregator + - group = false 时,output + - group = true 时,group_variables + + - http-request: body + - tool: data + - code: output_variables + - jinja-render: output + +# parameter-extractor: +## 输入变量: string + - 系统变量 + - 会话变量 + - start: variables / sys + - llm: output + + - parameter-extractor: __reason + - memory-read: answer + + + - question-classifier: class_name + + - loop: cycle_vars + - var-aggregator + - group = false 时,output + - group = true 时,group_variables + + - http-request: body + - tool: data + - code: output_variables + - jinja-render: output +## 指令:string/number + - 系统变量 + - 会话变量 + - start: variables / sys + - llm: output + + - parameter-extractor: __is_success / __reason + - memory-read: answer + + + - question-classifier: class_name + + - loop: cycle_vars + - var-aggregator + - group = false 时,output + - group = true 时,group_variables + + - http-request: body / status_code + - tool: data + - code: output_variables + - jinja-render: output + +# memory-read: string + - 系统变量 + - 会话变量 + - start: variables / sys + - llm: output + + - parameter-extractor: __reason + - memory-read: answer + + + - question-classifier: class_name + + - loop: cycle_vars + - var-aggregator + - group = false 时,output + - group = true 时,group_variables + + - http-request: body + - tool: data + - code: output_variables + - jinja-render: output + + +# memory-write: string + - 系统变量 + - 会话变量 + - start: variables / sys + - llm: output + + - parameter-extractor: __reason + - memory-read: answer + + + - question-classifier: class_name + + - loop: cycle_vars + - var-aggregator + - group = false 时,output + - group = true 时,group_variables + + - http-request: body + - tool: data + - code: output_variables + - jinja-render: output + +# if-else: boolean/string/number/array[file]/array[object]/array[string]/object + - 系统变量 + - 会话变量 + - start: variables / sys + - llm: output + - knowledge-retrieval: output + - parameter-extractor: __is_success / __reason + - memory-read: answer + + + - question-classifier: class_name + - iteration: output + - loop: cycle_vars + - var-aggregator + - group = false 时,output + - group = true 时,group_variables + + - http-request: body / status_code + - tool: data + - code: output_variables + - jinja-render: output + +# question-classifier +## 输入变量: string + - 系统变量 + - 会话变量 + - start: variables / sys + - llm: output + + - parameter-extractor: __reason + - memory-read: answer + + + - question-classifier: class_name + + - loop: cycle_vars + - var-aggregator + - group = false 时,output + - group = true 时,group_variables + + - http-request: body + - tool: data + - code: output_variables + - jinja-render: output +## 分类: string + - 系统变量 + - 会话变量 + - start: variables / sys + - llm: output + + - parameter-extractor: __reason + - memory-read: answer + + + - question-classifier: class_name + + - loop: cycle_vars + - var-aggregator + - group = false 时,output + - group = true 时,group_variables + + - http-request: body + - tool: data + - code: output_variables + - jinja-render: output + +# iteration +## 输入变量: array[file] | array[object] | array[string] | array[number] | array[boolean] + + + + + - knowledge-retrieval: output + - parameter-extractor: array类型的提取参数 params + + + + + - iteration: output + - loop: cycle_vars + + + + + - code: output_variables + +## 输出变量 + - 系统变量 + + + + + + + + + + + + + + + + + + - 子节点的输出变量 + - llm: output + - knowledge-retrieval: output + - parameter-extractor: __reason, params + - memory-read: answer + - memory-write + + - question-classifier: class_name + + + - var-aggregator + - group = false 时,output + - group = true 时,group_variables + + - http-request: body + - tool: data + - code: output_variables + - jinja-render: output + +# loop +## 循环变量 + - 系统变量 + - 会话变量 + - start: variables / sys + - llm: output + - knowledge-retrieval: output + - parameter-extractor: __is_success / __reason / params + - memory-read: answer + + + - question-classifier: class_name + - iteration: output + - loop: cycle_vars + - var-aggregator + - group = false 时,output + - group = true 时,group_variables + + - http-request: body / status_code + - tool: data + - code: output_variables + - jinja-render: output +## 循环终止条件 +### left + - 系统变量 + - 会话变量 + + + + + + + + + + - loop: cycle_vars 当前loop节点的 + + + + + - code: output_variables + + - 子节点的输出变量 + - llm: output + - knowledge-retrieval: output + - parameter-extractor: __reason + - memory-read: answer + - memory-write + + - question-classifier: class_name + + + - var-aggregator + - group = false 时,output + - group = true 时,group_variables + + - http-request: body + - tool: data + - jinja-render: output +### right: number + - 系统变量 + - 会话变量 + - start: variables / sys + + + - parameter-extractor: __is_success + + + + + + - loop: cycle_vars 当前loop节点的 + + + - http-request: status_code + + - code: output_variables + + +# var-aggregator: string/number/boolean + - 系统变量 + - 会话变量 + - start: variables / sys + - llm: output + - knowledge-retrieval: output + - parameter-extractor: __reason + - memory-read: answer + + + - question-classifier: class_name + - iteration: output + - loop: cycle_vars 当前loop节点的 + - var-aggregator + - group = false 时,output + - group = true 时,group_variables + + - http-request: body / status_code + - tool: data + - code: output_variables + - jinja-render: output + +# assigner +## variable_selector + + - 会话变量 + + + + + + + + + + - loop: cycle_vars 当前loop节点的 + + + + + + +## value + + - 会话变量 + - start: variables / sys + - llm: output + - knowledge-retrieval: output + - parameter-extractor: __reason / __is_success + - memory-read: answer + + + - question-classifier: class_name + - iteration: output + - loop: cycle_vars 当前loop节点的 + - var-aggregator + - group = false 时,output + - group = true 时,group_variables + + - http-request: body + - tool: data + - code: output_variables + - jinja-render: output + +# http-request +## url/headers/params: string/number + - 系统变量 + - 会话变量 + - start: variables / sys + - llm: output + + - parameter-extractor: __reason / __is_success + - memory-read: answer + + + - question-classifier: class_name + + - loop: cycle_vars 当前loop节点的 + - var-aggregator + - group = false 时,output + - group = true 时,group_variables + + - http-request: body / status_code + - tool: data + - code: output_variables + - jinja-render: output +## ['body', 'data'] +### body?.content_type = form-data/x-www-form-urlencoded/json/raw: string/number +### body?.content_type = binary: file/array[file] + - 系统变量 + - 会话变量 + - start: variables / sys + - llm: output + + - parameter-extractor: __reason / __is_success + - memory-read: answer + + + - question-classifier: class_name + + - loop: cycle_vars 当前loop节点的 + - var-aggregator + - group = false 时,output + - group = true 时,group_variables + + - http-request: body / status_code + - tool: data + - code: output_variables + - jinja-render: output + +# tool: 不需要 + +# jinja-render +## mappingList 输入变量 + - 系统变量 + - 会话变量 + - start: variables / sys + - llm: output + - knowledge-retrieval: output + - parameter-extractor: __reason / __is_success + - memory-read: answer + + + - question-classifier: class_name + - iteration: output + - loop: cycle_vars 当前loop节点的 + - var-aggregator + - group = false 时,output + - group = true 时,group_variables + + - http-request: body / status_code + - tool: data + - code: output_variables + - jinja-render: output + +# code +## input_variables + - 系统变量 + - 会话变量 + - start: variables / sys + - llm: output + - knowledge-retrieval: output + - parameter-extractor: __reason / __is_success + - memory-read: answer + + + - question-classifier: class_name + - iteration: output + - loop: cycle_vars 当前loop节点的 + - var-aggregator + - group = false 时,output + - group = true 时,group_variables + + - http-request: body / status_code + - tool: data + - jinja-render: output + - code: output_variables + - code: output_variables + + +# 迭代子节点 +- llm +- if-else +- parameter-extractor && prompt +- var-aggregator +- assigner +- http-request && body.content_type !== 'binary' +- tool +- jinja-render + - iteration: item / index + +- knowledge-retrieval +- parameter-extractor && !prompt +- memory-read +- memory-write +- question-classifier + - iteration的输入变量是array[string]时,可选item + +- iteration +- loop + - 不可添加此类节点 + +# 循环子节点 +- llm +- knowledge-retrieval +- parameter-extractor +- memory-read +- memory-write +- if-else +- question-classifier +- var-aggregator +- assigner +- http-request +- tool +- jinja-render + - loop: cycle_vars + +- iteration +- loop + - 不可添加此类节点 + + + +# TODO + +## 需要后端支持的需求 +1. 集群调试:对话过程数据输出【需后端】 + +3. 应用调试、分享增加变量配置【需后端】 +4. 应用导入导出,导出已完成,导入【需后端】 +6. 单个节点的运行【需后端】 +7. 列表 节点的配置【需后端】 +9. 对话支持附件(非图片)【需后端】 + +## 前端需求 +1. 工作流整理布局、历史撤销、回退 +2. 问题分类节点,分类中英文 +3. 感知记忆:文本类型增加片段展示 +- variableConfig +4. 工作流UI + + + + - 变量聚合器 +7. 记忆萃取 + - 本体场景不可编辑 +- rb:truncate +- 注释翻译 + - RbCard + - src/views/KnowledgeBase/index.tsx + - src/components/Upload/UploadFiles.tsx + - src/components/Chat + + +# 分支 + +## 0.2.6 +- feature/workflow_import_zy + - 工作流导入 | 导出 + - input_type: Constant / Variable 统一成小写 + - 结束节点内容被覆写 + - 增加未知节点 + - http 节点 + - 变量下拉列表替换成编辑器 + - body form-data file时,值支持选择sys.files +- feature/form_zy + - 表单校验规则 + - 流式输出时,向上滚动后,自动滚动到最底部的效果失效 + - 应用 API URL更新 +- feature/memory_zy + - 记忆萃取增加剪枝 + +## 20260212 +1. A2A 协议适配 +2. 日志跟踪系统 +3. Agent、集群、工作流共享 +4. 试运行、分享会话支持文件(包含语言、其他附件)【待联调】 +2. 导入 Agent、工作流 - 合并到应用管理创建方式 + + +a7da914dcbb80186b9aaf9ac4d21a9881e60ecb5 +e115353811b34de2fd359962860fdafe87fef503 \ No newline at end of file