Merge branch 'develop' of github.com:SuanmoSuanyangTechnology/MemoryBear into develop
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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="系统版本获取成功")
|
||||
"""获取系统版本号+说明"""
|
||||
return success(data={
|
||||
"version": settings.SYSTEM_VERSION,
|
||||
"introduction": settings.SYSTEM_INTRODUCTION
|
||||
}, msg="系统版本获取成功")
|
||||
@@ -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:
|
||||
"""
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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: (
|
||||
<span data-menu-id={menu.path}>
|
||||
{menu.i18nKey ? t(menu.i18nKey) : menu.label}
|
||||
</span>
|
||||
),
|
||||
icon: iconSrc ? <img
|
||||
src={iconSrc}
|
||||
className="rb:w-[16px] rb:h-[16px] rb:mr-[8px]"
|
||||
|
||||
@@ -57,6 +57,16 @@ export const en = {
|
||||
createNow: 'Create Now',
|
||||
goConfig: 'Go Configure',
|
||||
},
|
||||
indexTour:{
|
||||
startTitle:'Welcome to Memory Bear 👋',
|
||||
startDescription:'Not sure where to start? Head to Model Management first to get your models ready — everything else builds on that.👉 Click Model Management in the left menu to get started.',
|
||||
stepOne: 'This is Model Management',
|
||||
stepOneDescription: 'Here you can view and configure available models to get ready for building applications.Once your models are set, head to Space Management to create a space and continue.👉 Click Space Management in the left menu to move on.',
|
||||
stepTwo: 'This is Space Management',
|
||||
stepTwoDescription: 'Here you can create and manage spaces to organize models and data for different use cases.Once your spaces are ready, head to User Management to invite members and manage access.👉 Click User Management in the left menu to continue.',
|
||||
stepThree: 'This is User Management',
|
||||
stepThreeDescription: 'Here you can create users, assign roles, and manage access for your team.Once users are set up, the basic configuration is complete and you’re ready to start using the platform 🎉',
|
||||
},
|
||||
menu: {
|
||||
home: 'Home',
|
||||
tenantManagement: 'Tenant Management',
|
||||
|
||||
@@ -57,6 +57,16 @@ export const zh = {
|
||||
createNow: '立即创建',
|
||||
goConfig: '去配置',
|
||||
},
|
||||
indexTour:{
|
||||
startTitle:'欢迎来到 Memory Bear 👋',
|
||||
startDescription:'不知道从哪里开始?不妨先去 Model Management 看看,先把模型准备好,后面的操作会更顺畅。👉 点击左侧 Model Management 开始吧。',
|
||||
stepOne: '这里是 Model Management',
|
||||
stepOneDescription: '你可以在这里查看和配置可用的模型,为后续应用做好准备。模型准备好后,下一步去 Space Management 创建空间并开始使用吧。👉 点击左侧 Space Management 继续。',
|
||||
stepTwo: '这里是 Space Management',
|
||||
stepTwoDescription: '你可以在这里创建和管理不同的空间,把模型和数据组织到具体的使用场景中。空间创建完成后,可以去 User Management 邀请成员、分配权限,一起协作使用。👉 点击左侧 User Management 继续。',
|
||||
stepThree: '这里是用户管理页',
|
||||
stepThreeDescription: '你可以在这里创建用户、分配角色,并管理团队成员的访问权限。完成用户设置后,基础配置就准备好了,可以开始实际使用平台的各项功能了 🎉',
|
||||
},
|
||||
menu: {
|
||||
home: '首页',
|
||||
tenantManagement: '租户管理',
|
||||
|
||||
@@ -1,31 +1,92 @@
|
||||
import React from 'react';
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import guideBgImg from '@/assets/images/index/guide_bg@2x.png'
|
||||
import { Button } from 'antd';
|
||||
import { ArrowRightOutlined } from '@ant-design/icons'
|
||||
import { Button, Tour } from 'antd';
|
||||
import type { TourProps } from 'antd';
|
||||
import arrowRight from '@/assets/images/index/arrow_right_blue.svg'
|
||||
|
||||
const GuideCard: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [currentStep, setCurrentStep] = useState<number>(0);
|
||||
const startButtonRef = useRef<HTMLButtonElement>(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 (
|
||||
<>
|
||||
<div className='rb:w-full rb:h-[204px] rb:p-4' style={{ backgroundImage: `url(${guideBgImg})`, backgroundSize: '100% 100%' }}>
|
||||
<div className='rb:flex rb:justify-start rb:text-white rb:text-base rb:font-semibold'>
|
||||
<div className='rb:flex rb:justify-start rb:text-white rb:text-base rb:font-semibold' >
|
||||
{ t('index.getStarted')}
|
||||
</div>
|
||||
<div className='rb:flex rb:text-xs rb:text-white rb:leading-[18px] rb:mt-3'>
|
||||
{ t('index.startedDesc')}
|
||||
</div>
|
||||
<div className='rb:flex rb:w-full rb:items-center rb:justify-between rb:gap-3 rb:mt-4'>
|
||||
<Button className='rb:gap-2 rb:flex rb:items-center rb:text-[#155EEF] '>
|
||||
<Button ref={startButtonRef} className='rb:gap-2 rb:w-full rb:flex rb:items-center rb:text-[#155EEF]' onClick={handleStartGuide}>
|
||||
<span className='rb:text-xs'>{ t('index.viewGuide')}</span>
|
||||
<img src={arrowRight} className='rb:size-4' />
|
||||
</Button>
|
||||
<Button className='rb:gap-2 rb:flex rb:items-center rb:text-[#155EEF]'>
|
||||
{/* <Button className='rb:gap-2 rb:flex rb:items-center rb:text-[#155EEF]'>
|
||||
<span className='rb:text-xs'>{ t('index.watchVideo')}</span>
|
||||
<img src={arrowRight} className='rb:size-4' />
|
||||
</Button>
|
||||
</Button> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Tour
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
steps={steps}
|
||||
current={currentStep}
|
||||
onChange={handleStepChange}
|
||||
onFinish={handleTourFinish}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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<QuickActionsProps> = ({ 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<QuickActionsProps> = ({ 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
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ const list = [
|
||||
]
|
||||
const TopCardList: FC<{data?: DataResponse}> = ({ data }) => {
|
||||
const { t } = useTranslation()
|
||||
debugger
|
||||
return (
|
||||
<div className="rb:grid rb:grid-cols-4 rb:gap-[16px]">
|
||||
{list.map((item) => {
|
||||
|
||||
@@ -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<versionResponse | null>(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 (
|
||||
<div className='rb:w-full rb:h-[186px] rb:p-4 rb:border-1 rb:border-[#DFE4ED] rb:bg-[#FBFDFF] rb:rounded-xl'>
|
||||
<div className='rb:flex rb:justify-start rb:text-[#5B6167] rb:text-base rb:font-semibold'>
|
||||
<div className='rb:w-full rb:p-4 rb:border-1 rb:border-[#DFE4ED] rb:bg-[#FBFDFF] rb:rounded-xl'>
|
||||
<div className='rb:flex rb:items-center rb:justify-start rb:text-[#5B6167] rb:text-base rb:font-semibold'>
|
||||
{ t('index.latestUpdate')}
|
||||
{versionInfo && (
|
||||
<span className='rb:ml-2 rb:text-sm rb:text-[#1890FF]'>
|
||||
{versionInfo.version}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className='rb:flex rb:text-xs rb:text-[#5B6167] rb:leading-[18px] rb:mt-3 rb:pl-2'>
|
||||
{ t('index.latestUpdateDesc')}
|
||||
{loading ? (
|
||||
t('index.loading')
|
||||
) : (
|
||||
versionInfo?.introduction || t('index.latestUpdateDesc')
|
||||
)}
|
||||
</div>
|
||||
<div className='rb:flex rb:w-full rb:items-center rb:justify-between rb:gap-3 rb:mt-4'>
|
||||
{/* <div className='rb:flex rb:w-full rb:items-center rb:justify-between rb:gap-3 rb:mt-4'>
|
||||
<Button className='rb:gap-2 rb:flex rb:items-center rb:text-[#212332] '>
|
||||
<span className='rb:text-xs'>{ t('index.viewDetails')}</span>
|
||||
<img src={arrowRight} className='rb:size-4' />
|
||||
@@ -22,7 +51,7 @@ const GuideCard: React.FC = () => {
|
||||
<span className='rb:text-xs'>{ t('index.changeLog')}</span>
|
||||
<img src={arrowRight} className='rb:size-4' />
|
||||
</Button>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 (
|
||||
<div className="rb:pb-[24px]">
|
||||
<Row className="rb:mt-[16px]" gutter={16}>
|
||||
<Col span={19}>
|
||||
<div className="rb:mt-[16px] rb:flex rb:gap-4">
|
||||
<div className='rb:flex-1'>
|
||||
<div className='rb:flex-col rb:w-full rb:h-[120px] rb:mb-4 rb:p-6 rb:leading-[30px]' style={{backgroundImage: `url(${bgImg})`, backgroundSize: '100% 100%'}}>
|
||||
<div className='rb:flex rb:text-[22px] rb:text-[#0041C3] rb:font-semibold'>
|
||||
{ t('index.spaceTitle' )}
|
||||
@@ -121,8 +121,8 @@ const Index = () => {
|
||||
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={5}>
|
||||
</div>
|
||||
<div className='rb:flex-0 rb:min-w-80'>
|
||||
{/* 引导 */}
|
||||
<GuideCard />
|
||||
<div className='rb:w-full rb:mt-4 '>
|
||||
@@ -130,10 +130,10 @@ const Index = () => {
|
||||
</div>
|
||||
{/* 快捷操作 */}
|
||||
<div className='rb:w-full rb:mt-4'>
|
||||
<QuickActions />
|
||||
<QuickActions onNavigate={navigate} />
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user