From b00049e94edcf4455b0a4f670b93320b1da5181e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=A2=E4=BF=8A=E7=94=B7?= Date: Mon, 12 Jan 2026 16:22:34 +0800 Subject: [PATCH 1/2] feat(home page and apps): 1. Add a version introduction to the homepage; 2. Query the app list. When the 'ids' parameter is provided, retrieve the specified applications by splitting them with commas, without pagination --- api/app/controllers/app_controller.py | 14 ++++++- api/app/controllers/home_page_controller.py | 7 +++- api/app/core/config.py | 3 +- api/app/services/app_service.py | 41 +++++++++++++++++++++ 4 files changed, 60 insertions(+), 5 deletions(-) diff --git a/api/app/controllers/app_controller.py b/api/app/controllers/app_controller.py index f30b959d..2300f148 100644 --- a/api/app/controllers/app_controller.py +++ b/api/app/controllers/app_controller.py @@ -48,6 +48,7 @@ def list_apps( include_shared: bool = True, page: int = 1, pagesize: int = 10, + ids: Optional[str] = None, db: Session = Depends(get_db), current_user=Depends(get_current_user), ): @@ -55,8 +56,19 @@ def list_apps( - 默认包含本工作空间的应用和分享给本工作空间的应用 - 设置 include_shared=false 可以只查看本工作空间的应用 + - 当提供 ids 参数时,按逗号分割获取指定应用,不分页 """ workspace_id = current_user.current_workspace_id + service = app_service.AppService(db) + + # 当 ids 存在且不为 None 时,根据 ids 获取应用 + if ids is not None: + app_ids = [id.strip() for id in ids.split(',') if id.strip()] + items_orm = app_service.get_apps_by_ids(db, app_ids, workspace_id) + items = [service._convert_to_schema(app, workspace_id) for app in items_orm] + return success(data=items) + + # 正常分页查询 items_orm, total = app_service.list_apps( db, workspace_id=workspace_id, @@ -69,8 +81,6 @@ def list_apps( pagesize=pagesize, ) - # 使用 AppService 的转换方法来设置 is_shared 字段 - service = app_service.AppService(db) items = [service._convert_to_schema(app, workspace_id) for app in items_orm] meta = PageMeta(page=page, pagesize=pagesize, total=total, hasnext=(page * pagesize) < total) return success(data=PageData(page=meta, items=items)) diff --git a/api/app/controllers/home_page_controller.py b/api/app/controllers/home_page_controller.py index f1a5310d..77db9d8f 100644 --- a/api/app/controllers/home_page_controller.py +++ b/api/app/controllers/home_page_controller.py @@ -31,5 +31,8 @@ def get_workspace_list( @router.get("/version", response_model=ApiResponse) def get_system_version(): - """获取系统版本号""" - return success(data={"version": settings.SYSTEM_VERSION}, msg="系统版本获取成功") \ No newline at end of file + """获取系统版本号+说明""" + return success(data={ + "version": settings.SYSTEM_VERSION, + "introduction": settings.SYSTEM_INTRODUCTION + }, msg="系统版本获取成功") \ No newline at end of file diff --git a/api/app/core/config.py b/api/app/core/config.py index b02b94a5..d9b9cea8 100644 --- a/api/app/core/config.py +++ b/api/app/core/config.py @@ -166,7 +166,8 @@ class Settings: ENABLE_TOOL_MANAGEMENT: bool = os.getenv("ENABLE_TOOL_MANAGEMENT", "true").lower() == "true" # official environment system version - SYSTEM_VERSION: str = os.getenv("SYSTEM_VERSION", "v1.0.0") + SYSTEM_VERSION: str = os.getenv("SYSTEM_VERSION", "v0.2.0") + SYSTEM_INTRODUCTION: str = os.getenv("SYSTEM_INTRODUCTION", "") def get_memory_output_path(self, filename: str = "") -> str: """ diff --git a/api/app/services/app_service.py b/api/app/services/app_service.py index b97bf874..6d5204f8 100644 --- a/api/app/services/app_service.py +++ b/api/app/services/app_service.py @@ -812,6 +812,37 @@ class AppService: ) return items, int(total) + def get_apps_by_ids( + self, + app_ids: List[str], + workspace_id: uuid.UUID + ) -> List[App]: + """根据ID列表获取应用 + + Args: + app_ids: 应用ID列表 + workspace_id: 工作空间ID(用于权限验证) + + Returns: + List[App]: 应用列表 + """ + if not app_ids: + return [] + + # 转换字符串ID为UUID + try: + uuid_ids = [uuid.UUID(app_id) for app_id in app_ids] + except ValueError: + return [] + + # 查询本工作空间的应用 + 分享给本工作空间的应用 + stmt = select(App).where( + App.id.in_(uuid_ids), + App.workspace_id == workspace_id + ) + + return list(self.db.scalars(stmt).all()) + # ==================== Agent 配置管理 ==================== def update_agent_config( @@ -2068,6 +2099,16 @@ def list_apps( ) +def get_apps_by_ids( + db: Session, + app_ids: List[str], + workspace_id: uuid.UUID +) -> List[App]: + """根据ID列表获取应用(向后兼容接口)""" + service = AppService(db) + return service.get_apps_by_ids(app_ids, workspace_id) + + # ==================== 向后兼容的函数接口 ==================== async def draft_run( From d957e2750177fe930cfb51bfc889f3872ad40779 Mon Sep 17 00:00:00 2001 From: yujiangping Date: Mon, 12 Jan 2026 16:32:58 +0800 Subject: [PATCH 2/2] feat(homepage): add guided tour and version display functionality - Add version API endpoint and response interface in common.ts - Implement interactive guided tour with 4 steps for new users covering Model Management, Space Management, and User Management - Add tour translation keys for both English and Chinese locales - Add data-menu-id attributes to sidebar menu items for tour targeting - Create VersionCard component to display current platform version - Update GuideCard component with tour state management and navigation logic - Enhance homepage dashboard with version information display - Improve user onboarding experience with step-by-step guided navigation --- web/src/api/common.ts | 9 +++ web/src/components/SiderMenu/index.tsx | 6 +- web/src/i18n/en.ts | 10 +++ web/src/i18n/zh.ts | 10 +++ web/src/views/Index/components/GuideCard.tsx | 75 +++++++++++++++++-- .../views/Index/components/QuickActions.tsx | 74 ++++++++++-------- .../Index/components/TopCardList/index.tsx | 1 + .../views/Index/components/VersionCard.tsx | 41 ++++++++-- web/src/views/Index/index.tsx | 16 ++-- 9 files changed, 189 insertions(+), 53 deletions(-) diff --git a/web/src/api/common.ts b/web/src/api/common.ts index 9a33fd9a..025dcb4d 100644 --- a/web/src/api/common.ts +++ b/web/src/api/common.ts @@ -23,6 +23,10 @@ export interface DataResponse { new_apps_this_week: Number; app_week_growth_rate: Number } +export interface versionResponse{ + version: string; + introduction: string; +} // 首页数据统计 export const getDashboardData = `/home-page/workspaces` @@ -30,4 +34,9 @@ export const getDashboardData = `/home-page/workspaces` export const getDashboardStatistics = async () => { const response = await request.get(`/home-page/statistics`); return response as DataResponse; +}; +// 获取版本号 +export const getVersion = async () => { + const response = await request.get(`/home-page/version`); + return response as versionResponse; }; \ No newline at end of file diff --git a/web/src/components/SiderMenu/index.tsx b/web/src/components/SiderMenu/index.tsx index a39a0b4e..61a1836d 100644 --- a/web/src/components/SiderMenu/index.tsx +++ b/web/src/components/SiderMenu/index.tsx @@ -115,7 +115,11 @@ const Menu: FC<{ return { key: menu.path, title: menu.i18nKey ? t(menu.i18nKey) : menu.label, - label: menu.i18nKey ? t(menu.i18nKey) : menu.label, + label: ( + + {menu.i18nKey ? t(menu.i18nKey) : menu.label} + + ), icon: iconSrc ? { const { t } = useTranslation(); + const navigate = useNavigate(); + const [open, setOpen] = useState(false); + const [currentStep, setCurrentStep] = useState(0); + const startButtonRef = useRef(null); + + // Tour 步骤配置 + const steps: TourProps['steps'] = [ + { + title: t('indexTour.startTitle'), + description: t('indexTour.startDescription'), + target: () => startButtonRef.current!, + }, + { + title: t('indexTour.stepOne'), + description: t('indexTour.stepOneDescription'), + target: () => document.querySelector('[data-menu-id="/model"]') as HTMLElement, + }, + { + title: t('indexTour.stepTwo'), + description: t('indexTour.stepTwoDescription'), + target: () => document.querySelector('[data-menu-id="/space"]') as HTMLElement, + }, + { + title: t('indexTour.stepThree'), + description: t('indexTour.stepThreeDescription'), + target: () => document.querySelector('[data-menu-id="/user-management"]') as HTMLElement, + } + ]; + + // 开始引导 + const handleStartGuide = () => { + setCurrentStep(0); + setOpen(true); + }; + + // Tour 步骤变化处理 + const handleStepChange = (current: number) => { + setCurrentStep(current); + // 不再自动跳转页面,让用户通过点击菜单项来导航 + }; + + // Tour 完成处理 + const handleTourFinish = () => { + setOpen(false); + setCurrentStep(0); + // 完成后导航到模型管理页面 + navigate('/model'); + }; return ( + <>
-
+
{ t('index.getStarted')}
{ t('index.startedDesc')}
- - + */}
+ + setOpen(false)} + steps={steps} + current={currentStep} + onChange={handleStepChange} + onFinish={handleTourFinish} + /> + ); }; diff --git a/web/src/views/Index/components/QuickActions.tsx b/web/src/views/Index/components/QuickActions.tsx index 7cdb90ec..edf5166e 100644 --- a/web/src/views/Index/components/QuickActions.tsx +++ b/web/src/views/Index/components/QuickActions.tsx @@ -3,11 +3,7 @@ import { useTranslation } from 'react-i18next'; import modelIcon from '@/assets/images/index/model_mgt.svg' import spaceIcon from '@/assets/images/index/space_mgt.svg' -import workflowIcon from '@/assets/images/index/workflow_mgt.svg' import userIcon from '@/assets/images/index/user_mgt.svg' -import dataExportIcon from '@/assets/images/index/data_export.svg' -import logIcon from '@/assets/images/index/log_mgt.svg' -import noteIcon from '@/assets/images/index/note_mgt.svg' import helpCenterIcon from '@/assets/images/index/help_center.svg' interface QuickAction { key: string; @@ -22,14 +18,30 @@ interface QuickActionsProps { } const QuickActions: FC = ({ onNavigate }) => { - const { t } = useTranslation(); + const { t, i18n } = useTranslation(); + + // 根据当前语言环境打开帮助中心 + const openHelpCenter = () => { + const currentLang = i18n.language; + const lang = currentLang === 'zh' ? 'zh' : 'en'; + const helpUrl = `https://docs.redbearai.com/s/${lang}-memorybear`; + + // 创建隐藏的 a 标签来避免弹窗拦截 + const link = document.createElement('a'); + link.href = helpUrl; + link.target = '_blank'; + link.rel = 'noopener noreferrer'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }; const quickActions: QuickAction[] = [ { key: 'model-management', icon: modelIcon, title: t('quickActions.modelManagement'), - onClick: () => onNavigate?.('/model-management') + onClick: () => onNavigate?.('/model') }, { key: 'space-management', @@ -37,42 +49,42 @@ const QuickActions: FC = ({ onNavigate }) => { title: t('quickActions.spaceManagement'), onClick: () => onNavigate?.('/spce') }, - { - key: 'workflow-orchestration', - icon: workflowIcon, - title: t('quickActions.workflowOrchestration'), - onClick: () => onNavigate?.('/workflow') - }, + // { + // key: 'workflow-orchestration', + // icon: workflowIcon, + // title: t('quickActions.workflowOrchestration'), + // onClick: () => onNavigate?.('/workflow') + // }, { key: 'user-management', icon: userIcon, title: t('quickActions.userManagement'), onClick: () => onNavigate?.('/user-management') }, - { - key: 'data-export', - icon: dataExportIcon, - title: t('quickActions.dataExport'), - onClick: () => onNavigate?.('/') - }, - { - key: 'log-query', - icon: logIcon, - title: t('quickActions.logQuery'), - onClick: () => onNavigate?.('/log') - }, - { - key: 'notification-reminder', - icon: noteIcon, - title: t('quickActions.notificationReminder'), - onClick: () => onNavigate?.('/notification-reminder') - }, + // { + // key: 'data-export', + // icon: dataExportIcon, + // title: t('quickActions.dataExport'), + // onClick: () => onNavigate?.('/') + // }, + // { + // key: 'log-query', + // icon: logIcon, + // title: t('quickActions.logQuery'), + // onClick: () => onNavigate?.('/log') + // }, + // { + // key: 'notification-reminder', + // icon: noteIcon, + // title: t('quickActions.notificationReminder'), + // onClick: () => onNavigate?.('/notification-reminder') + // }, { key: 'help-center', icon: helpCenterIcon, title: t('quickActions.helpCenter'), - onClick: () => onNavigate?.('/help-center') + onClick: openHelpCenter } ]; diff --git a/web/src/views/Index/components/TopCardList/index.tsx b/web/src/views/Index/components/TopCardList/index.tsx index 273ce936..fbc434fd 100644 --- a/web/src/views/Index/components/TopCardList/index.tsx +++ b/web/src/views/Index/components/TopCardList/index.tsx @@ -59,6 +59,7 @@ const list = [ ] const TopCardList: FC<{data?: DataResponse}> = ({ data }) => { const { t } = useTranslation() + debugger return (
{list.map((item) => { diff --git a/web/src/views/Index/components/VersionCard.tsx b/web/src/views/Index/components/VersionCard.tsx index 3967dac4..9b7af54a 100644 --- a/web/src/views/Index/components/VersionCard.tsx +++ b/web/src/views/Index/components/VersionCard.tsx @@ -1,19 +1,48 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Button } from 'antd'; import arrowRight from '@/assets/images/index/arrow_right.svg' +import { getVersion, type versionResponse } from '@/api/common' + const GuideCard: React.FC = () => { const { t } = useTranslation(); + const [versionInfo, setVersionInfo] = useState(null); + const [loading, setLoading] = useState(false); + useEffect(() => { + const fetchVersion = async () => { + try { + setLoading(true); + const response = await getVersion(); + setVersionInfo(response); + } catch (error) { + console.error('Failed to fetch version:', error); + } finally { + setLoading(false); + } + }; + + fetchVersion(); + }, []); + return ( -
-
+
+
{ t('index.latestUpdate')} + {versionInfo && ( + + {versionInfo.version} + + )}
- { t('index.latestUpdateDesc')} + {loading ? ( + t('index.loading') + ) : ( + versionInfo?.introduction || t('index.latestUpdateDesc') + )}
-
+ {/*
-
+
*/}
); }; diff --git a/web/src/views/Index/index.tsx b/web/src/views/Index/index.tsx index e6265d3b..3cab0276 100644 --- a/web/src/views/Index/index.tsx +++ b/web/src/views/Index/index.tsx @@ -1,7 +1,7 @@ import { useEffect, useState, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; -import { Row, Col, Space, Button } from 'antd'; +import { Space, Button } from 'antd'; import TopCardList from './components/TopCardList'; import GuideCard from './components/GuideCard'; import VersionCard from './components/VersionCard'; @@ -100,8 +100,8 @@ const Index = () => { return (
- - +
+
{ t('index.spaceTitle' )} @@ -121,8 +121,8 @@ const Index = () => { />
- - +
+
{/* 引导 */}
@@ -130,10 +130,10 @@ const Index = () => {
{/* 快捷操作 */}
- +
- - +
+