fix: Details has two slashes

This commit is contained in:
scarqin
2025-01-07 11:32:29 +08:00
parent 0adc963129
commit b97acc7862
6 changed files with 322 additions and 196 deletions
@@ -674,10 +674,21 @@ export const PERMISSION_DEFINITION = [
anyOf: [{ backend: ['project.permission_manager'] }]
}
},
'system.settings.ai_key_resource.view': {
granted: {
anyOf: [{ backend: ['system.settings.ai_key_resource.view'] }]
}
},
'system.settings.ai_key_resource.manager': {
granted: {
anyOf: [{ backend: ['system.settings.ai_key_resource.manager'] }]
}
},
'system.settings.ai_api.view': {
granted: {
anyOf: [{ backend: ['system.settings.ai_api.view'] }]
}
}
}
]
@@ -151,15 +151,15 @@ const mockData = [
name: 'APIKey 资源池',
key: 'aiKeys',
path: '/keysetting',
icon: 'ic:baseline-key'
// access: 'system.settings.ai_key_resource.view'
icon: 'ic:baseline-key',
access: 'system.settings.ai_key_resource.view'
},
{
name: 'AI API',
key: 'aiApiList',
path: '/aiApis',
icon: 'ic:baseline-api'
// access: 'system.settings.ai_api.view'
icon: 'ic:baseline-api',
access: 'system.settings.ai_api.view'
}
]
},
+13 -8
View File
@@ -478,17 +478,22 @@ export const routerMap: Map<string, RouterMapConfig> = new Map([
{
path: 'list',
key: 'apiList',
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiApis/index.tsx')),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiApis/index.tsx'))
},
{
path: 'service/:teamId/aiInside/:serviceId/route/:routeId/:type',
key: 'apiDetail',
lazy: lazy(
() =>
import(
/* webpackChunkName: "[request]" */ '@core/pages/aiService/api/AiServiceInsideRouterCreate.tsx'
path: 'service/:teamId/aiInside/:serviceId',
key: 'aiApisServiceInside',
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/AiServiceInsidePage.tsx')),
children: [
{
path: 'route/:routeId/:type',
key: 'aiApisServiceInsideRouteDetail',
lazy: lazy(
() =>
import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/api/AiServiceInsideRouterCreate')
)
)
}
]
}
]
}
@@ -39,12 +39,14 @@ const ApiSettings: React.FC = () => {
const handlePreview = (record: APIs) => {
navigate(`../service/${record.team.id}/aiInside/${record.service.id}/route/${record.id}/apiDetail`)
}
const requestApis = async (params: any & {
pageSize: number;
current: number;
},
const requestApis = async (
params: any & {
pageSize: number
current: number
},
sort: Record<string, string>,
filter: Record<string, string>) => {
filter: Record<string, string>
) => {
if (!selectedProvider) return
setQueryBtnLoading(true)
try {
@@ -75,16 +77,22 @@ const ApiSettings: React.FC = () => {
setTotal(response.data.total)
const modalMap: {
[key: string]: string
} = response.data?.condition?.models.reduce((acc: { [key: string]: string }, item: { id: string; name: string }) => {
acc[item.id] = $t(item.name)
return acc
}, {})
} = response.data?.condition?.models.reduce(
(acc: { [key: string]: string }, item: { id: string; name: string }) => {
acc[item.id] = $t(item.name)
return acc
},
{}
)
const serviceMap: {
[key: string]: string
} = response.data?.condition?.services.reduce((acc: { [key: string]: string }, item: { id: string; name: string }) => {
acc[item.id] = $t(item.name)
return acc
}, {})
} = response.data?.condition?.services.reduce(
(acc: { [key: string]: string }, item: { id: string; name: string }) => {
acc[item.id] = $t(item.name)
return acc
},
{}
)
setTableColumns(modalMap, serviceMap)
return {
data: response.data.apis || [],
@@ -107,11 +115,14 @@ const ApiSettings: React.FC = () => {
}
}
}
const setTableColumns = (modalMap: {
[key: string]: string
}, serviceMap: {
[key: string]: string
}) => {
const setTableColumns = (
modalMap: {
[key: string]: string
},
serviceMap: {
[key: string]: string
}
) => {
setColumns([
{
title: $t('AI 服务'),
@@ -287,15 +298,18 @@ const ApiSettings: React.FC = () => {
</div>
</div>
}
request={async (params: any & {
pageSize: number;
current: number;
},
request={async (
params: any & {
pageSize: number
current: number
},
sort: Record<string, string>,
filter: Record<string, string>) => requestApis(params, sort, filter)}
filter: Record<string, string>
) => requestApis(params, sort, filter)}
onSearchWordChange={(e) => {
setSearchWord(e.target.value)
}}
onRowClick={(row: APIs) => handlePreview(row)}
showPagination={true}
searchPlaceholder={$t('请输入 APIURL 搜索')}
columns={columns}
@@ -1,166 +1,262 @@
import InsidePage from '@common/components/aoplatform/InsidePage.tsx'
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
import { PERMISSION_DEFINITION } from '@common/const/permissions.ts'
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
import { useFetch } from '@common/hooks/http.ts'
import { $t } from '@common/locales/index.ts'
import { getItem } from '@common/utils/navigation.tsx'
import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx'
import { AiServiceConfigFieldType } from '@core/const/ai-service/type.ts'
import { App, Menu, MenuProps } from 'antd'
import { ItemType, MenuItemGroupType, MenuItemType } from 'antd/es/menu/interface'
import Paragraph from 'antd/es/typography/Paragraph'
import { cloneDeep } from 'lodash-es'
import { FC, useEffect, useMemo, useState } from 'react'
import { Link, Outlet, useLocation, useNavigate, useParams } from 'react-router-dom'
import { useAiServiceContext } from '../../contexts/AiServiceContext.tsx'
const APP_MODE = import.meta.env.VITE_APP_MODE
import {FC, useEffect, useMemo, useState} from "react";
import {Link, Outlet, useLocation, useNavigate, useParams} from "react-router-dom";
import {RouterParams} from "@core/components/aoplatform/RenderRoutes.tsx";
import {App, Menu, MenuProps} from "antd";
import {BasicResponse, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx";
import {useFetch} from "@common/hooks/http.ts";
import { useAiServiceContext} from "../../contexts/AiServiceContext.tsx";
import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx";
import { PERMISSION_DEFINITION } from "@common/const/permissions.ts";
import InsidePage from "@common/components/aoplatform/InsidePage.tsx";
import Paragraph from "antd/es/typography/Paragraph";
import { cloneDeep } from "lodash-es";
import { $t } from "@common/locales/index.ts";
import { getItem } from "@common/utils/navigation.tsx";
import { AiServiceConfigFieldType } from "@core/const/ai-service/type.ts";
import { MenuItemGroupType, MenuItemType, ItemType } from "antd/es/menu/interface";
const APP_MODE = import.meta.env.VITE_APP_MODE;
const AiServiceInsidePage: FC = () => {
const { message } = App.useApp()
const { teamId, serviceId, apiId, routeId, policyId } = useParams<RouterParams>()
const location = useLocation()
const currentUrl = location.pathname
const { fetchData } = useFetch()
const { setPrefixForce, setApiPrefix, aiServiceInfo, setAiServiceInfo } = useAiServiceContext()
const { accessData, checkPermission, accessInit, state } = useGlobalContext()
const [activeMenu, setActiveMenu] = useState<string>()
const navigateTo = useNavigate()
const [showMenu, setShowMenu] = useState<boolean>(false)
const AiServiceInsidePage:FC = ()=> {
const { message } = App.useApp()
const { teamId,serviceId,apiId, routeId,policyId } = useParams<RouterParams>();
const location = useLocation()
const currentUrl = location.pathname
const {fetchData} = useFetch()
const { setPrefixForce,setApiPrefix ,aiServiceInfo ,setAiServiceInfo} = useAiServiceContext()
const { accessData,checkPermission,accessInit,state} = useGlobalContext()
const [activeMenu, setActiveMenu] = useState<string>()
const navigateTo = useNavigate()
const [showMenu, setShowMenu] = useState<boolean>(false)
const getAiServiceInfo = () => {
fetchData<BasicResponse<{ service: AiServiceConfigFieldType }>>('service/info', {
method: 'GET',
eoParams: { team: teamId, service: serviceId }
}).then((response) => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
setAiServiceInfo(data.service)
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
}
})
}
const getAiServiceInfo = ()=>{
fetchData<BasicResponse<{ service:AiServiceConfigFieldType }>>('service/info',{method:'GET',eoParams:{team:teamId, service:serviceId}}).then(response=>{
const {code,data,msg} = response
if(code === STATUS_CODE.SUCCESS){
setAiServiceInfo(data.service)
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
}
})
}
const getApiDefine = () => {
setApiPrefix('')
setPrefixForce(false)
fetchData<BasicResponse<{ prefix: string; force: boolean }>>('service/router/define', {
method: 'GET',
eoParams: { service: serviceId, team: teamId }
}).then((response) => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
setApiPrefix(data.prefix)
setPrefixForce(data.force)
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
}
})
}
const getApiDefine = ()=>{
setApiPrefix('')
setPrefixForce(false)
fetchData<BasicResponse<{ prefix:string, force:boolean }>>('service/router/define',{method:'GET',eoParams:{service:serviceId,team:teamId}}).then(response=>{
const {code,data,msg} = response
if(code === STATUS_CODE.SUCCESS){
setApiPrefix(data.prefix)
setPrefixForce(data.force)
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
}
})
}
const SYSTEM_PAGE_MENU_ITEMS = useMemo(()=>[
getItem($t('服务'), 'assets', null,
const SYSTEM_PAGE_MENU_ITEMS = useMemo(
() => [
getItem(
$t('服务'),
'assets',
null,
[
getItem(<Link to="./route">{$t('API 路由')}</Link>, 'route',undefined,undefined,undefined,'team.service.router.view'),
getItem(<Link to="./api">{$t('API 文档')}</Link>, 'api',undefined,undefined,undefined,'team.service.api_doc.view'),
getItem(<Link to="./document">{$t('使用说明')}</Link>, 'document',undefined,undefined,undefined,'team.service.service_intro.view'),
getItem(<Link to="./servicepolicy">{$t('服务策略')}</Link>, 'servicepolicy', undefined, undefined, undefined, 'team.service.policy.view'),
getItem(<Link to="./publish">{$t('发布')}</Link>, 'publish',undefined,undefined,undefined,'team.service.release.view'),
],
'group'),
getItem($t('订阅管理'), 'provideSer', null,
[
getItem(<Link to="./approval">{$t('订阅审核')}</Link>, 'approval',undefined,undefined,undefined,'team.service.subscription.view'),
getItem(<Link to="./subscriber">{$t('订阅方管理')}</Link>, 'subscriber',undefined,undefined,undefined,'team.service.subscription.view'),
getItem(
<Link to="./route">{$t('API 路由')}</Link>,
'route',
undefined,
undefined,
undefined,
'team.service.router.view'
),
getItem(
<Link to="./api">{$t('API 文档')}</Link>,
'api',
undefined,
undefined,
undefined,
'team.service.api_doc.view'
),
getItem(
<Link to="./document">{$t('使用说明')}</Link>,
'document',
undefined,
undefined,
undefined,
'team.service.service_intro.view'
),
getItem(
<Link to="./servicepolicy">{$t('服务策略')}</Link>,
'servicepolicy',
undefined,
undefined,
undefined,
'team.service.policy.view'
),
getItem(
<Link to="./publish">{$t('发布')}</Link>,
'publish',
undefined,
undefined,
undefined,
'team.service.release.view'
)
],
'group'),
getItem($t('管理'), 'mng', null,
'group'
),
getItem(
$t('订阅管理'),
'provideSer',
null,
[
APP_MODE === 'pro' ? getItem(<Link to="./topology">{$t('调用拓扑图')}</Link>, 'topology',undefined,undefined,undefined,'project.myAiService.topology.view'):null,
getItem(<Link to="./setting">{$t('设置')}</Link>, 'setting',undefined,undefined,undefined,'')],
'group'),
],[state.language])
getItem(
<Link to="./approval">{$t('订阅审核')}</Link>,
'approval',
undefined,
undefined,
undefined,
'team.service.subscription.view'
),
getItem(
<Link to="./subscriber">{$t('订阅方管理')}</Link>,
'subscriber',
undefined,
undefined,
undefined,
'team.service.subscription.view'
)
],
'group'
),
getItem(
$t('管理'),
'mng',
null,
[
APP_MODE === 'pro'
? getItem(
<Link to="./topology">{$t('调用拓扑图')}</Link>,
'topology',
undefined,
undefined,
undefined,
'project.myAiService.topology.view'
)
: null,
getItem(<Link to="./setting">{$t('设置')}</Link>, 'setting', undefined, undefined, undefined, '')
],
'group'
)
],
[state.language]
)
const menuData = useMemo(()=>{
const filterMenu = (menu:MenuItemGroupType<MenuItemType>[])=>{
const newMenu = cloneDeep(menu)
return newMenu!.filter((m:MenuItemGroupType )=>{
if(m&&m.children && m.children.length > 0){
m.children = m.children.filter(
(c)=>{
if(!c) return false
return (((c as MenuItemType&{access:string} ).access ?
checkPermission((c as MenuItemType&{access:string} ).access as keyof typeof PERMISSION_DEFINITION[0]):
true))})
}
return m.children && m.children.length > 0
})
const menuData = useMemo(() => {
const filterMenu = (menu: MenuItemGroupType<MenuItemType>[]) => {
const newMenu = cloneDeep(menu)
return newMenu!.filter((m: MenuItemGroupType) => {
if (m && m.children && m.children.length > 0) {
m.children = m.children.filter((c) => {
if (!c) return false
return (c as MenuItemType & { access: string }).access
? checkPermission(
(c as MenuItemType & { access: string }).access as keyof (typeof PERMISSION_DEFINITION)[0]
)
: true
})
}
const filteredMenu = filterMenu(SYSTEM_PAGE_MENU_ITEMS as MenuItemGroupType<MenuItemType>[])
const menu = activeMenu ?? filteredMenu[0]?.children ? filteredMenu[0]?.children?.[0]?.key : filteredMenu[0]?.key
if(menu && currentUrl.split('/')[-1] !== menu){
navigateTo(`/service/${teamId}/aiInside/${serviceId}/${menu}`)
}
return filteredMenu || []
},[accessData,accessInit, SYSTEM_PAGE_MENU_ITEMS])
const onMenuClick: MenuProps['onClick'] = ({key}) => {
setActiveMenu(key)
};
useEffect(() => {
// route edit and policy edit page don't need to show menu
setShowMenu(!routeId && !currentUrl.includes('route/create') && !policyId &&!currentUrl.includes('servicepolicy/datamasking/create'))
return m.children && m.children.length > 0
})
}
const filteredMenu = filterMenu(SYSTEM_PAGE_MENU_ITEMS as MenuItemGroupType<MenuItemType>[])
const menu = (activeMenu ?? filteredMenu[0]?.children) ? filteredMenu[0]?.children?.[0]?.key : filteredMenu[0]?.key
if (menu && currentUrl.split('/')[-1] !== menu) {
navigateTo(`/service/${teamId}/aiInside/${serviceId}/${menu}`)
}
return filteredMenu || []
}, [accessData, accessInit, SYSTEM_PAGE_MENU_ITEMS])
if(apiId !== undefined){
setActiveMenu('api')
} else if(currentUrl.includes('servicepolicy')){
setActiveMenu('servicepolicy')
} else if(serviceId !== currentUrl.split('/')[currentUrl.split('/').length - 1]){
setActiveMenu(currentUrl.split('/')[currentUrl.split('/').length - 1])
}else{
setActiveMenu('route')
}
}, [currentUrl]);
const onMenuClick: MenuProps['onClick'] = ({ key }) => {
setActiveMenu(key)
}
useEffect(()=>{
if(accessData && checkPermission('team.service.router.view')){
getApiDefine()
}
},[accessData])
useEffect(()=>{
if( activeMenu && serviceId === currentUrl.split('/')[currentUrl.split('/').length - 1]){
navigateTo(`/service/${teamId}/aiInside/${serviceId}/${activeMenu}`)
}
},[activeMenu])
useEffect(() => {
serviceId && getAiServiceInfo()
}, [serviceId]);
return (
<>{showMenu ?
<InsidePage pageTitle={aiServiceInfo?.name || '-'}
tagList={[{label:
<Paragraph className="mb-0" copyable={serviceId ? { text: serviceId } : false}>{$t('服务 ID')}{serviceId || '-'}</Paragraph>
}]}
backUrl="/service/list">
<div className="flex flex-1 h-full">
<Menu
onClick={onMenuClick}
className="h-full overflow-y-auto"
style={{ width: 220 }}
selectedKeys={[activeMenu!]}
mode="inline"
items={menuData as unknown as ItemType<MenuItemType>[] }
/>
<div className={` ${['setting', 'upstream'].indexOf(activeMenu!) !== -1 ? '' :''} w-full h-full flex flex-1 flex-col overflow-auto bg-MAIN_BG pt-[20px] pl-[20px] pb-PAGE_INSIDE_B ` }>
<Outlet/>
</div>
</div>
</InsidePage>: <Outlet/> }
</>
useEffect(() => {
// route edit and policy edit page don't need to show menu
setShowMenu(
!routeId &&
!currentUrl.includes('route/create') &&
!policyId &&
!currentUrl.includes('servicepolicy/datamasking/create')
)
if (apiId !== undefined) {
setActiveMenu('api')
} else if (currentUrl.includes('servicepolicy')) {
setActiveMenu('servicepolicy')
} else if (serviceId !== currentUrl.split('/')[currentUrl.split('/').length - 1]) {
setActiveMenu(currentUrl.split('/')[currentUrl.split('/').length - 1])
} else {
setActiveMenu('route')
}
}, [currentUrl])
useEffect(() => {
if (accessData && checkPermission('team.service.router.view')) {
getApiDefine()
}
}, [accessData])
useEffect(() => {
if (activeMenu && serviceId === currentUrl.split('/')[currentUrl.split('/').length - 1]) {
navigateTo(`/service/${teamId}/aiInside/${serviceId}/${activeMenu}`)
}
}, [activeMenu])
useEffect(() => {
serviceId && getAiServiceInfo()
}, [serviceId])
return (
<>
{showMenu ? (
<InsidePage
pageTitle={aiServiceInfo?.name || '-'}
tagList={[
{
label: (
<Paragraph className="mb-0" copyable={serviceId ? { text: serviceId } : false}>
{$t('服务 ID')}{serviceId || '-'}
</Paragraph>
)
}
]}
backUrl="/service/list"
>
<div className="flex flex-1 h-full">
<Menu
onClick={onMenuClick}
className="overflow-y-auto h-full"
style={{ width: 220 }}
selectedKeys={[activeMenu!]}
mode="inline"
items={menuData as unknown as ItemType<MenuItemType>[]}
/>
<div
className={` ${['setting', 'upstream'].indexOf(activeMenu!) !== -1 ? '' : ''} w-full h-full flex flex-1 flex-col overflow-auto bg-MAIN_BG pt-[20px] pl-[20px] pb-PAGE_INSIDE_B `}
>
<Outlet />
</div>
</div>
</InsidePage>
) : (
<Outlet />
)}
</>
)
}
export default AiServiceInsidePage
export default AiServiceInsidePage
@@ -104,8 +104,8 @@ const AiServiceInsideRouterCreate = () => {
})
.catch((errInfo) => Promise.reject(errInfo))
}
const isDelete = type === 'apiDetail'
const backUrl = isDelete ? `/aiApis/list` : `/service/${teamId}/aiInside/${serviceId}/route`
const isAIApiPreview = type === 'apiDetail'
const backUrl = isAIApiPreview ? `/aiApis/list` : `/service/${teamId}/aiInside/${serviceId}/route`
const openDrawer = (type: 'edit') => {
setDrawerType(type)
}
@@ -210,7 +210,7 @@ const AiServiceInsideRouterCreate = () => {
}, [])
const addVariable = () => {
if (isDelete) return
if (isAIApiPreview) return
form.setFieldsValue({
variables: [...form.getFieldValue('variables'), { key: '', value: '', require: true }]
})
@@ -273,7 +273,7 @@ const AiServiceInsideRouterCreate = () => {
<Button
icon={<Icon icon="ic:baseline-tune" height={18} width={18} />}
iconPosition="end"
disabled={isDelete}
disabled={isAIApiPreview}
onClick={() => openDrawer('edit')}
>
<div className="flex items-center gap-[10px]">
@@ -285,7 +285,7 @@ const AiServiceInsideRouterCreate = () => {
{defaultLlm?.scopes?.map((x) => <Tag>{x?.toLocaleUpperCase()}</Tag>)}
</div>
</Button>
{type !== 'apiDetail' && (
{!isAIApiPreview && (
<Button type="primary" onClick={onFinish}>
{$t('保存')}
</Button>
@@ -298,7 +298,7 @@ const AiServiceInsideRouterCreate = () => {
spinning={loading}
wrapperClassName=" pb-PAGE_INSIDE_B pr-PAGE_INSIDE_X"
>
<WithPermission disabled={isDelete}>
<WithPermission disabled={isAIApiPreview}>
<Form
layout="vertical"
labelAlign="left"
@@ -350,7 +350,7 @@ const AiServiceInsideRouterCreate = () => {
<Form.Item<AiServiceRouterField> label={$t('提示词')} name="prompt">
<PromptEditorResizable
disabled={isDelete}
disabled={isAIApiPreview}
variablesChange={handleVariablesChange}
promptVariables={variablesTable}
/>
@@ -361,7 +361,7 @@ const AiServiceInsideRouterCreate = () => {
<div className="flex justify-between items-center w-full">
<span>{$t('变量')}</span>
<a
className={`flex items-center gap-[4px] ${isDelete ? 'cursor-not-allowed' : ''}`}
className={`flex items-center gap-[4px] ${isAIApiPreview ? 'cursor-not-allowed' : ''}`}
onClick={addVariable}
>
<Icon icon="ic:baseline-add" width={16} height={16} />