diff --git a/web/src/api/package.ts b/web/src/api/package.ts new file mode 100644 index 00000000..da52d355 --- /dev/null +++ b/web/src/api/package.ts @@ -0,0 +1,14 @@ +import { request } from '@/utils/request' + +import type { Package } from '@/views/Package/types' + +export const SYS_API_PREFIX = '/sys'; +// 套餐列表 +export const getPackageListUrl = `${SYS_API_PREFIX}/package-plans` +export const getPackageList = (query: { category: Package['category']; status: boolean; }) => { + return request.get(getPackageListUrl, query) +} +// 获取套餐详情 +export const getPackageDetail = (package_plan_id: string) => { + return request.get(`${SYS_API_PREFIX}/package-plans/${package_plan_id}`) +} \ No newline at end of file diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index fc3a041d..6bcc5034 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -3016,5 +3016,69 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re apply: 'Apply', tools: 'Tools', }, + package: { + package: 'Package Management', + saas_personal: 'SaaS Personal', + commercial_deployment: 'Commercial Deployment', + noCommercialPackages: 'No commercial deployment packages available', + + addPackage: 'Add Plan', + packageName: 'Plan Name', + packageNameZh: 'Plan Name (中文)', + packageNameEn: 'Plan Name (English)', + packageNamePlaceholder: '中文, 例如:记忆体验版', + packageNamePlaceholderEn: 'English, e.g. Memory Trial Plan', + packageCategory: 'Package Category', + price: 'Price', + pricePlaceholder: 'e.g. 0, 19, 299 or Contact Us', + billingPeriod: 'Billing Period', + monthly: 'Monthly', + yearly: 'Yearly', + permanent_free: 'Permanent Free', + local_deployment: 'Local Deployment', + coreValue: 'Core Value', + coreValueZh: 'Core Value (中文)', + coreValueEn: 'Core Value (English)', + coreValuePlaceholder: '中文, 一句话描述核心价值', + coreValuePlaceholderEn: 'EngLish, describe the core value in one sentence', + tech_support: 'Technical Support', + tech_support_zh: 'Technical Support (中文)', + tech_support_en: 'Technical Support (English)', + technicalSupportPlaceholder: '中文, 例如:社群交流、工单支持', + technicalSupportPlaceholderEn: 'English, e.g. Community support, ticket support', + sla: 'SLA & Compliance', + slaZh: 'SLA & Compliance (中文)', + slaEn: 'SLA & Compliance (English)', + slaPlaceholder: '中文, 例如:无、验证力加强+审计日志', + slaPlaceholderEn: 'English, e.g. None, dedicated compute pool + audit logs', + customPage: 'Chat Page Customization', + customPageZh: 'Chat Page Customization (中文)', + customPageEn: 'Chat Page Customization (English)', + customPagePlaceholder: '中文, 例如:LOGO定制', + customPagePlaceholderEn: 'English, e.g. Logo customization', + primaryColor: 'Primary Color', + status: 'Status', + active: 'Active', + inactive: 'Inactive', + api_ops_rate_limit: 'API OPS Rate Limit', + ops: 'req/s', + pcs: 'pcs', + GB: 'GB', + tier_level: 'Tier Level', + numberPlaceholder: 'e.g. 10', + + packageDetail: 'Package Detail', + basicInfo: 'Basic Info', + featureConfig: 'Billing Unit Quota', + workspace_quota: 'Workspace Quota', + skill_quota: 'Skill Library Quota', + app_quota: 'App Quota', + knowledge_capacity_quota: 'Knowledge Base Capacity', + memory_engine_quota: 'Memory Engine Quota', + end_user_quota: 'Memorable End Users', + ontology_project_quota: 'Ontology Project', + model_quota: 'Model Quota', + editPackage: 'Edit Package', + }, }, }; diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 01c766b8..fff8c1af 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -2980,5 +2980,69 @@ export const zh = { apply: '应用', tools: '工具', }, + package: { + package: '套餐管理', + saas_personal: 'SaaS 个人版', + commercial_deployment: '商业化部署', + noCommercialPackages: '暂无商业化部署套餐', + + addPackage: '添加套餐', + packageName: '套餐名称', + packageNameZh: '套餐名称 (中文)', + packageNameEn: '套餐名称 (English)', + packageNamePlaceholder: '中文, 例如:记忆体验版', + packageNamePlaceholderEn: 'English, e.g. Memory Trial Plan', + packageCategory: '套餐分类', + price: '价格', + pricePlaceholder: '例如: 0, 19, 299 或联系我们', + billingPeriod: '计费周期', + monthly: '月', + yearly: '年', + permanent_free: '永久免费', + local_deployment: '本地化部署', + coreValue: '核心价值', + coreValueZh: '核心价值 (中文)', + coreValueEn: '核心价值 (English)', + coreValuePlaceholder: '中文, 一句话描述核心价值', + coreValuePlaceholderEn: 'EngLish, describe the core value in one sentence', + tech_support: '技术支持', + tech_support_zh: '技术支持 (中文)', + tech_support_en: '技术支持 (English)', + technicalSupportPlaceholder: '中文, 例如:社群交流、工单支持', + technicalSupportPlaceholderEn: 'English, e.g. Community support, ticket support', + sla: 'SLA与合规', + slaZh: 'SLA与合规 (中文)', + slaEn: 'SLA与合规 (English)', + slaPlaceholder: '中文, 例如:无、验证力加强+审计日志', + slaPlaceholderEn: 'English, e.g. None, dedicated compute pool + audit logs', + customPage: '对应页面个性化配置', + customPageZh: '对应页面个性化配置 (中文)', + customPageEn: '对应页面个性化配置 (English)', + customPagePlaceholder: '中文, 例如:LOGO定制', + customPagePlaceholderEn: 'English, e.g. Logo customization', + primaryColor: '主题色', + status: '状态', + active: '启用', + inactive: '停用', + api_ops_rate_limit: 'API OPS 频次', + ops: '次/秒', + pcs: '个', + GB: 'GB', + tier_level: '层级', + numberPlaceholder: '如: 10', + + packageDetail: '套餐详情', + basicInfo: '基础信息', + featureConfig: '计费单元配额', + workspace_quota: '空间数量', + skill_quota: '技能库数量', + app_quota: '应用数量', + knowledge_capacity_quota: '知识库容量', + memory_engine_quota: '记忆引擎数量', + end_user_quota: '可记忆终端用户数', + ontology_project_quota: '本体工程', + model_quota: '可负载模型数量', + editPackage: '编辑套餐', + }, }, } \ No newline at end of file diff --git a/web/src/routes/index.tsx b/web/src/routes/index.tsx index 92f7a5cf..7b940068 100644 --- a/web/src/routes/index.tsx +++ b/web/src/routes/index.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-02 16:33:11 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-04 18:11:34 + * @Last Modified time: 2026-04-13 16:53:15 */ /** * Route Configuration @@ -76,13 +76,12 @@ const componentMap: Record>> = SpaceManagement: lazy(() => import('@/views/SpaceManagement')), ApiKeyManagement: lazy(() => import('@/views/ApiKeyManagement')), EmotionEngine: lazy(() => import('@/views/EmotionEngine')), - StatementDetail: lazy(() => import('@/views/UserMemoryDetail/pages/StatementDetail')), ForgetDetail: lazy(() => import('@/views/UserMemoryDetail/pages/ForgetDetail')), MemoryNodeDetail: lazy(() => import('@/views/UserMemoryDetail/pages/index')), SelfReflectionEngine: lazy(() => import('@/views/SelfReflectionEngine')), OrderPayment: lazy(() => import('@/views/OrderPayment')), OrderHistory: lazy(() => import('@/views/OrderHistory')), - Pricing: lazy(() => import('@/views/Pricing')), + Package: lazy(() => import('@/views/Package')), ToolManagement: lazy(() => import('@/views/ToolManagement')), SpaceConfig: lazy(() => import('@/views/SpaceConfig')), Ontology: lazy(() => import('@/views/Ontology')), diff --git a/web/src/routes/routes.json b/web/src/routes/routes.json index 5ff1f90c..422387a7 100644 --- a/web/src/routes/routes.json +++ b/web/src/routes/routes.json @@ -7,7 +7,7 @@ { "path": "/model", "element": "ModelManagement" }, { "path": "/space", "element": "SpaceManagement" }, { "path": "/tool", "element": "ToolManagement" }, - { "path": "/pricing", "element": "Pricing" }, + { "path": "/pricing", "element": "Package" }, { "path": "/order-pay", "element": "OrderPayment" }, { "path": "/orders", "element": "OrderHistory" }, { "path": "/skills", "element": "Skills" }, @@ -48,7 +48,6 @@ { "path": "/application/config/:id", "element": "ApplicationConfig" }, { "path": "/application/config/:id/:source", "element": "ApplicationConfig" }, { "path": "/user-memory/neo4j/:id", "element": "Neo4jUserMemoryDetail" }, - { "path": "/statement/:id", "element": "StatementDetail" }, { "path": "/user-memory/detail/:id/:type", "element": "MemoryNodeDetail" }, { "path": "/ontology/:id", "element": "OntologyDetail" } ] diff --git a/web/src/store/menu.json b/web/src/store/menu.json index 8d30dcc4..90b546ca 100644 --- a/web/src/store/menu.json +++ b/web/src/store/menu.json @@ -421,21 +421,7 @@ "display": false, "level": 3, "sort": 0, - "subs": [ - { - "id": 2211, - "parent": 221, - "code": "statementDetail", - "label": "记忆详情", - "i18nKey": "menu.statementDetail", - "path": "/statement/:id", - "enable": true, - "display": false, - "level": 4, - "sort": 0, - "subs": null - } - ] + "subs": [] }, { "id": 222, diff --git a/web/src/utils/request.ts b/web/src/utils/request.ts index 80c12f85..318738dd 100644 --- a/web/src/utils/request.ts +++ b/web/src/utils/request.ts @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-02 16:35:15 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-06 10:39:00 + * @Last Modified time: 2026-04-14 14:43:54 */ /** * HTTP Request Utility Module @@ -23,6 +23,7 @@ import { clearAuthData } from './auth'; import { message } from 'antd'; import { refreshTokenUrl, refreshToken, loginUrl, logoutUrl } from '@/api/user' import i18n from '@/i18n' +import { SYS_API_PREFIX } from '@/api/package' /** * Standard API response structure @@ -74,6 +75,10 @@ let requests: RequestQueueItem[] = []; // Request interceptor service.interceptors.request.use( (config) => { + console.log('config', config, config.url?.startsWith(SYS_API_PREFIX)) + if (config.url?.startsWith(SYS_API_PREFIX)) { + config.baseURL = ''; + } if (!config.headers.Authorization) { const token = cookieUtils.get('authToken'); if (token) { diff --git a/web/src/views/Package/constant.ts b/web/src/views/Package/constant.ts new file mode 100644 index 00000000..e4b04719 --- /dev/null +++ b/web/src/views/Package/constant.ts @@ -0,0 +1,34 @@ +export const billingUnits = [ + { + key: 'workspace_quota', + unit: '个', placeholder: '如: 10', + }, + { + key: 'skill_quota', + unit: '个', placeholder: '如: 10', + }, + { + key: 'app_quota', + unit: '个', placeholder: '如: 10', + }, + { + key: 'knowledge_capacity_quota', + unit: 'GB', placeholder: '如: 10', + }, + { + key: 'memory_engine_quota', + unit: '个', placeholder: '如: 10', + }, + { + key: 'end_user_quota', + unit: '个', placeholder: '如: 10', + }, + { + key: 'ontology_project_quota', + unit: '个', placeholder: '如: 10', + }, + { + key: 'model_quota', + unit: '次/秒', placeholder: '如: 10', + }, +] \ No newline at end of file diff --git a/web/src/views/Package/index.tsx b/web/src/views/Package/index.tsx new file mode 100644 index 00000000..0e8a347f --- /dev/null +++ b/web/src/views/Package/index.tsx @@ -0,0 +1,145 @@ +/* + * @Author: ZhaoYing + * @Date: 2026-02-25 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-04-14 14:48:13 + */ +/** + * Package Component + * + * Package management page with: + * - Tabs for SaaS Personal and Commercial Deployment + * - Package cards showing features and pricing + * - Edit and delete actions + * + * @component + */ + +import { useMemo, useState, useEffect, type FC } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; +import { Flex, Row, Col, type SegmentedProps } from 'antd'; +import clsx from 'clsx'; + +import type { Package } from './types' +import { getPackageList } from '@/api/package'; +import PageTabs from '@/components/PageTabs' +import { billingUnits } from './constant' +import RbCard from '@/components/RbCard/Card' +import BodyWrapper from '@/components/Empty/BodyWrapper' +import { useI18n } from '@/store/locale' +import RbButton from '@/components/RbButton' + +const Package: FC = () => { + const { t } = useTranslation(); + const { language } = useI18n() + const navigate = useNavigate(); + const [data, setData] = useState([]) + + const [activeTab, setActiveTab] = useState('saas_personal'); + const formatTabItems = useMemo(() => { + return ['saas_personal', 'commercial_deployment'].map(value => ({ + value, + label: t(`package.${value}`), + })) + }, [t]) + /** Handle tab change */ + const handleChangeTab = (value: SegmentedProps['value']) => { + setActiveTab(value as string); + } + const getList = () => { + getPackageList({ category: activeTab as Package['category'], status: true }) + .then(res => { + setData(res as Package[] || []) + }) + } + + useEffect(() => { + getList() + }, [activeTab]) + + const getKeyWithLanguage = (key: string) => { + return (language === 'en' ? `${key}_en` : key) as keyof Package + } + /** Navigate to order history */ + const goToHistory = () => { + navigate('/orders'); + } + return ( + <> + + + +
+ {t('pricing.orderHistory')} +
+
+ + + {data.map((pkg) => ( + + + +
+ {/* Header */} +
+

+ {String(pkg[getKeyWithLanguage('name')] ?? '')} +

+

{String(pkg[getKeyWithLanguage('core_value')] ?? '')}

+
+ {pkg.billing_cycle !== 'permanent_free' && <>¥{pkg.price}} + {pkg.billing_cycle && {pkg.billing_cycle !== 'permanent_free' && '/'}{t(`package.${pkg.billing_cycle}`)}} +
+
+ + {/* Features */} +
+ {billingUnits.map(({ key, unit }) => { + if (typeof pkg.quotas[key as keyof Package['quotas']] === 'number') { + return ( +
+ {t(`package.${key}`)} + {pkg.quotas[key as keyof Package['quotas']]}{unit} +
+ ) + } + })} + {pkg.tech_support && +
+ {t(`package.tech_support`)} + {String(pkg[getKeyWithLanguage('tech_support')] ?? '')} +
+ } + {pkg.api_ops_rate_limit && +
+ {t(`package.api_ops_rate_limit`)} + {pkg.api_ops_rate_limit}(次/秒) +
+ } +
+
+
+ +
+ + ))} +
+
+ + ); +}; + +export default Package; diff --git a/web/src/views/Package/types.ts b/web/src/views/Package/types.ts new file mode 100644 index 00000000..6517f63a --- /dev/null +++ b/web/src/views/Package/types.ts @@ -0,0 +1,61 @@ +/* + * @Author: ZhaoYing + * @Date: 2026-04-14 11:35:01 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-04-14 14:28:46 + */ +export interface Package { + id: string; + // 名称 + name: string; + name_en: string; + // 类型 + category: "saas_personal" | "commercial_deployment"; + tier_level: number; + // 版本 + version: string; + // 状态 + status: boolean; + // 价格 + price: string; + // 计费周期 + billing_cycle: "monthly" | "yearly" | "permanent_free" | "local_deployment"; + // 核心价值 + core_value: string; + core_value_en: string; + // 技术支持 + tech_support: string; + tech_support_en: string; + // SLA与合规 + sla_compliance: string; + sla_compliance_en: string; + // 对话页面个性化配置 + page_customization: string; + page_customization_en: string; + // API OPS 频次(次/秒) + api_ops_rate_limit: number; + // 主题色 + theme_color: string; + quotas: { + // 空间数量 + workspace_quota: number; + // 技能库数量 + skill_quota: number; + // 应用数量 + app_quota: number; + // 知识库容量 + knowledge_capacity_quota: string; + // 记忆引擎数量 + memory_engine_quota: number; + // 可记忆终端用户数 + end_user_quota: number; + // 本体工程 + ontology_project_quota: number; + // 可负载模型数量 + model_quota: number; + }, + created_at: number; + updated_at: number; + created_by: string; + updated_by: string | null; +}