Compare commits

...

30 Commits

Author SHA1 Message Date
ningyv 1d8e579a10 Merge remote-tracking branch 'origin/main' into feature/1.4 2025-01-23 13:57:01 +08:00
ningyv 095c09c8c0 chroe: optimize AI model node graphics 2025-01-21 11:50:58 +08:00
Dot.L 3482d5416c Merge pull request #181 from APIParkLab/feature/openapi
fix:ai init bug
2025-01-20 14:08:37 +08:00
Liujian d8cb4a0c94 fix:ai init bug 2025-01-20 14:03:03 +08:00
Dot.L 59acfa7a47 Merge pull request #180 from APIParkLab/feature/openapi
Feature/openapi
2025-01-20 13:55:59 +08:00
Liujian 2eb2e690d1 update ai bug 2025-01-20 13:54:58 +08:00
Liujian 7e7be7f040 add openapi 2025-01-17 16:03:09 +08:00
Dot.L 0187fd16b2 Merge pull request #174 from jeak01/patch-2
Update readme-zh-cn.md
2025-01-17 15:55:09 +08:00
Dot.L ba0bdb5e99 Merge pull request #175 from jeak01/patch-3
Update readme-zh-tw.md
2025-01-17 15:54:50 +08:00
Dot.L 9d3e4f07bf Merge pull request #176 from jeak01/patch-4
Update readme-jp.md
2025-01-17 15:54:37 +08:00
Dot.L bd81d7584d Merge pull request #177 from jeak01/patch-1
Update README.md
2025-01-17 15:54:20 +08:00
jeak 9577339e14 Update readme-jp.md 2025-01-17 14:59:10 +08:00
jeak 5c292ef1cb Update readme-zh-tw.md 2025-01-17 14:58:46 +08:00
jeak 4f3de85068 Update readme-zh-cn.md 2025-01-17 14:58:19 +08:00
jeak 07a25c9643 Update README.md 2025-01-17 14:57:31 +08:00
Dot.L 8f60426b4c Merge pull request #173 from APIParkLab/feature/ai-balance
fix: Nsq returns no error directly after parsing JSON exceptionNsq re…
2025-01-17 11:35:43 +08:00
Liujian 37f87615bd fix: Nsq returns no error directly after parsing JSON exceptionNsq returns no error directly after parsing JSON exception 2025-01-17 11:34:34 +08:00
Dot.L 3f96de660b Merge pull request #172 from APIParkLab/feature/ai-balance
fix: ai event handler read event error
2025-01-17 10:42:14 +08:00
Liujian e86999770f fix: ai event handler read event error 2025-01-17 10:38:35 +08:00
Dot.L a8bb0c24ec Merge pull request #170 from APIParkLab/feature/ai-balance
update init plugin config
2025-01-16 18:58:36 +08:00
Liujian 6ba2a08b62 update init plugin config 2025-01-16 18:53:58 +08:00
Dot.L d232269416 Merge pull request #167 from APIParkLab/feature/ai-balance
Feature/ai balance
2025-01-16 16:37:41 +08:00
Liujian 9d2208e14d update provider status default value 2025-01-16 16:36:25 +08:00
Liujian 8d69d45d1d update build script 2025-01-16 16:36:06 +08:00
ScarChin a6105cfc3c fix: 1.3-beta版本,超级管理员(admin)账户无法修改分类和添加子分类,页面显示无权限操作 (#164) 2025-01-14 17:52:07 +08:00
scarqin b0dacbda0d fix: When the current supplier is abnormal, there should be a line on the model pointing to the next model, which means that the APIs on this link are associated with the next valid supplier. 2025-01-07 18:40:43 +08:00
scarqin d5abde2593 fix: The language option is wrong. The current language is Chinese, but the option is displayed as English. 2025-01-07 18:14:25 +08:00
scarqin bc3290de3b fix: jump link error 2025-01-07 17:56:16 +08:00
scarqin 7f438bf776 fix: When the current supplier is abnormal, there should be a line on the model pointing to the next model, which means that the APIs on this link are associated with the next valid supplier. 2025-01-07 17:54:53 +08:00
scarqin 13cfe24b2f fix: error line 2025-01-07 17:21:21 +08:00
30 changed files with 345 additions and 249 deletions
+1 -1
View File
@@ -210,7 +210,7 @@ APIPark uses the Apache 2.0 License. For more details, please refer to the LICEN
For enterprise-level features and professional technical support, contact our pre-sales experts for personalized demos, customized solutions, and pricing. For enterprise-level features and professional technical support, contact our pre-sales experts for personalized demos, customized solutions, and pricing.
- Website: https://apipark.com - Website: https://apipark.com
- Email: dev@apipark.com - Email: contact@apipark.com
<br> <br>
+17 -11
View File
@@ -3,6 +3,7 @@ package main
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"log" "log"
"strings" "strings"
"time" "time"
@@ -27,8 +28,8 @@ func init() {
} }
type NSQConfig struct { type NSQConfig struct {
Addr string `json:"addr"` Addr string `json:"addr" yaml:"addr"`
TopicPrefix string `json:"topic_prefix"` TopicPrefix string `json:"topic_prefix" yaml:"topic_prefix"`
} }
// 定义 NSQ 消息结构 // 定义 NSQ 消息结构
@@ -78,6 +79,11 @@ func convertInt(value interface{}) int {
} }
} }
func genAIKey(key string, provider string) string {
keys := strings.Split(key, "@")
return strings.TrimSuffix(keys[0], fmt.Sprintf("-%s", provider))
}
// HandleMessage 处理从 NSQ 读取的消息 // HandleMessage 处理从 NSQ 读取的消息
func (h *NSQHandler) HandleMessage(message *nsq.Message) error { func (h *NSQHandler) HandleMessage(message *nsq.Message) error {
log.Printf("Received message: %s", string(message.Body)) log.Printf("Received message: %s", string(message.Body))
@@ -87,14 +93,14 @@ func (h *NSQHandler) HandleMessage(message *nsq.Message) error {
err := json.Unmarshal(message.Body, &data) err := json.Unmarshal(message.Body, &data)
if err != nil { if err != nil {
log.Printf("Failed to unmarshal message: %v", err) log.Printf("Failed to unmarshal message: %v", err)
return err return nil
} }
// 将时间字符串转换为 time.Time // 将时间字符串转换为 time.Time
timestamp, err := time.Parse(time.RFC3339, data.TimeISO8601) timestamp, err := time.Parse(time.RFC3339, data.TimeISO8601)
if err != nil { if err != nil {
log.Printf("Failed to parse timestamp: %v", err) log.Printf("Failed to parse timestamp: %v", err)
return err return nil
} }
day := time.Date(timestamp.Year(), timestamp.Month(), timestamp.Day(), 0, 0, 0, 0, timestamp.Location()) day := time.Date(timestamp.Year(), timestamp.Month(), timestamp.Day(), 0, 0, 0, 0, timestamp.Location())
@@ -104,14 +110,13 @@ func (h *NSQHandler) HandleMessage(message *nsq.Message) error {
finalStatus := &AIProviderStatus{} finalStatus := &AIProviderStatus{}
for _, s := range data.AI.ProviderStats { for _, s := range data.AI.ProviderStats {
status := ToKeyStatus(s.Status).Int() status := ToKeyStatus(s.Status).Int()
keys := strings.Split(s.Key, "@") key := genAIKey(s.Key, s.Provider)
key := keys[0]
err = h.aiKeyService.Save(ctx, key, &ai_key.Edit{ err = h.aiKeyService.Save(ctx, key, &ai_key.Edit{
Status: &status, Status: &status,
}) })
if err != nil { if err != nil {
log.Printf("Failed to save AI key: %v", err) log.Printf("Failed to save AI key: %v", err)
return err return nil
} }
if s.Provider != data.AI.Provider { if s.Provider != data.AI.Provider {
@@ -128,11 +133,12 @@ func (h *NSQHandler) HandleMessage(message *nsq.Message) error {
finalStatus = &s finalStatus = &s
} }
if finalStatus != nil { if finalStatus != nil {
keys := strings.Split(finalStatus.Key, "@") //keys := strings.Split(finalStatus.Key, "@")
err = h.aiKeyService.IncrUseToken(ctx, keys[0], convertInt(data.AI.TotalToken)) key := genAIKey(finalStatus.Key, finalStatus.Provider)
err = h.aiKeyService.IncrUseToken(ctx, key, convertInt(data.AI.TotalToken))
if err != nil { if err != nil {
log.Printf("Failed to increment AI key token: %v", err) log.Printf("Failed to increment AI key token: %v", err)
return err return nil
} }
} }
@@ -151,7 +157,7 @@ func (h *NSQHandler) HandleMessage(message *nsq.Message) error {
}) })
if err != nil { if err != nil {
log.Printf("Failed to call AI API: %v", err) log.Printf("Failed to call AI API: %v", err)
return err return nil
} }
log.Printf("Message processed and saved to MySQL: %+v", data) log.Printf("Message processed and saved to MySQL: %+v", data)
@@ -59,6 +59,7 @@ const LanguageSetting = ({ mode = 'light' }: { mode?: 'dark' | 'light' }) => {
i18n.changeLanguage(supportedLang) i18n.changeLanguage(supportedLang)
} }
}, []) }, [])
return ( return (
<Dropdown <Dropdown
trigger={['hover']} trigger={['hover']}
@@ -1,8 +1,8 @@
import { PERMISSION_DEFINITION } from '@common/const/permissions'
import { $t } from '@common/locales'
import { Button, Tooltip, Upload } from 'antd' import { Button, Tooltip, Upload } from 'antd'
import { ReactElement, cloneElement, useEffect, useMemo, useState } from 'react' import { ReactElement, cloneElement, useEffect, useMemo, useState } from 'react'
import { useGlobalContext } from '../../contexts/GlobalStateContext' import { useGlobalContext } from '../../contexts/GlobalStateContext'
import { PERMISSION_DEFINITION } from '@common/const/permissions'
import { $t } from '@common/locales'
type WithPermissionProps = { type WithPermissionProps = {
access?: string | string[] access?: string | string[]
@@ -18,7 +18,11 @@ export const checkAccess: (access: AccessDataType, accessData: Map<string, strin
if (accLevel === 'team') { if (accLevel === 'team') {
accessSet = new Set(Array.from(accessSet).concat(accessData?.get('team') || [])) accessSet = new Set(Array.from(accessSet).concat(accessData?.get('team') || []))
} }
return accessSet!.size > 0 ? hasIntersection(neededBackendAccessArr, accessSet) : false if (!accessSet!.size) {
return false
}
const hasAccess = hasIntersection(neededBackendAccessArr, accessSet)
return hasAccess
} }
const hasIntersection = (arr1: string[], set1: Set<string>) => { const hasIntersection = (arr1: string[], set1: Set<string>) => {
+6 -6
View File
@@ -151,15 +151,15 @@ function App() {
form={{ validateMessages }} form={{ validateMessages }}
> >
<PluginEventHubProvider> <PluginEventHubProvider>
<AppAntd className="h-full" message={{ maxCount: 1 }}> <GlobalProvider>
<PluginSlotHubProvider> <AppAntd className="h-full" message={{ maxCount: 1 }}>
<GlobalProvider> <PluginSlotHubProvider>
<BreadcrumbProvider> <BreadcrumbProvider>
<RenderRoutes /> <RenderRoutes />
</BreadcrumbProvider> </BreadcrumbProvider>
</GlobalProvider> </PluginSlotHubProvider>
</PluginSlotHubProvider> </AppAntd>
</AppAntd> </GlobalProvider>
</PluginEventHubProvider> </PluginEventHubProvider>
</ConfigProvider> </ConfigProvider>
</StyleProvider> </StyleProvider>
@@ -27,6 +27,7 @@ export const KeyStatusNode: React.FC<{ data: KeyStatusNodeData }> = ({ data }) =
<div <div
className="flex gap-1 w-full" className="flex gap-1 w-full"
style={{ style={{
minWidth: keys.length > 5 ? '118px' : 'auto',
maxWidth: `calc(${MAX_KEYS} * ${KEY_SIZE} + (${MAX_KEYS} - 1) * ${KEY_GAP})`, maxWidth: `calc(${MAX_KEYS} * ${KEY_SIZE} + (${MAX_KEYS} - 1) * ${KEY_GAP})`,
minHeight: KEY_SIZE minHeight: KEY_SIZE
}} }}
@@ -29,39 +29,41 @@ export const ModelCardNode: React.FC<{ data: ModelCardNodeData }> = ({ data }) =
const statusConfig = getStatusIcon(status) const statusConfig = getStatusIcon(status)
return ( return (
<div <>
className="node-card bg-white rounded-lg shadow-sm p-4 min-w-[280px] group" <div
style={{ border: '1px solid var(--border-color)' }} className="node-card bg-white rounded-lg shadow-sm p-4 min-w-[280px] group"
> style={{ border: '1px solid var(--border-color)' }}
<Handle type="target" position={Position.Left} /> >
<Handle type="source" position={Position.Right} /> <Handle type="target" position={Position.Left} />
<div> <Handle type="source" position={Position.Right} />
<div className="flex justify-between items-center"> <div>
<div className="flex gap-2 items-center"> <div className="flex justify-between items-center">
<div className="flex flex-1 overflow-hidden items-center gap-[4px]"> <div className="flex gap-2 items-center">
<span <div className="flex flex-1 overflow-hidden items-center gap-[4px]">
className="flex items-center h-[22px] ai-setting-svg-container" <span
dangerouslySetInnerHTML={{ __html: logo }} className="flex items-center h-[22px] ai-setting-svg-container"
></span> dangerouslySetInnerHTML={{ __html: logo }}
></span>
</div>
<span className="text-base text-gray-900 max-w-[180px] truncate">{name}</span>
<Icon icon={statusConfig?.icon} className={`text-xl ${statusConfig?.color}`} />
</div> </div>
<span className="text-base text-gray-900 max-w-[180px] truncate">{name}</span>
<Icon icon={statusConfig?.icon} className={`text-xl ${statusConfig?.color}`} />
</div>
{/* Action buttons */} {/* Action buttons */}
<div className="flex gap-2 transition-opacity duration-200"> <div className="flex gap-2 transition-opacity duration-200">
<Icon <Icon
icon="mdi:cog" icon="mdi:cog"
className="text-xl text-gray-400 cursor-pointer hover:text-[--primary-color]" className="text-xl text-gray-400 cursor-pointer hover:text-[--primary-color]"
onClick={() => { onClick={() => {
openConfigModal({ id: data.id, defaultLlm: defaultLlm } as AiSettingListItem) openConfigModal({ id: data.id, defaultLlm: defaultLlm } as AiSettingListItem)
}} }}
/> />
</div>
</div>
<div className="mt-2 text-sm text-gray-500">
{$t('默认:')}
{defaultLlm}
</div> </div>
</div>
<div className="mt-2 text-sm text-gray-500">
{$t('默认:')}
{defaultLlm}
</div> </div>
{status !== 'enabled' && alternativeModel && ( {status !== 'enabled' && alternativeModel && (
<div className="mt-1 text-sm text-gray-500"> <div className="mt-1 text-sm text-gray-500">
@@ -69,6 +71,11 @@ export const ModelCardNode: React.FC<{ data: ModelCardNodeData }> = ({ data }) =
</div> </div>
)} )}
</div> </div>
</div> {status !== 'enabled' && alternativeModel && (
<div className="ml-4 mt-1 text-sm text-gray-500">
{$t('关联 API 已转用')} {alternativeModel.name}/{alternativeModel.defaultLlm}
</div>
)}
</>
) )
} }
@@ -18,11 +18,15 @@
.react-flow__node { .react-flow__node {
padding: 0; padding: 0;
border-radius: 8px; border-radius: 8px;
min-width: 150px;
width: auto; width: auto;
max-width: 100%; max-width: 100%;
} }
.react-flow__node-modelCard,
.react-flow__node-serviceCard {
min-width: 150px;
}
/* Custom Node Styles */ /* Custom Node Styles */
.custom-node { .custom-node {
background: white; background: white;
@@ -28,7 +28,7 @@ export default function ServiceCategory() {
const { accessData } = useGlobalContext() const { accessData } = useGlobalContext()
const [loading, setLoading] = useState<boolean>(false) const [loading, setLoading] = useState<boolean>(false)
const onDrop: TreeProps['onDrop'] = info => { const onDrop: TreeProps['onDrop'] = (info) => {
const dropKey = info.node.key const dropKey = info.node.key
const dragKey = info.dragNode.key const dragKey = info.dragNode.key
const dropPos = info.node.pos.split('-') const dropPos = info.node.pos.split('-')
@@ -59,7 +59,7 @@ export default function ServiceCategory() {
if (!info.dropToGap) { if (!info.dropToGap) {
// Drop on the content // Drop on the content
loop(data, dropKey, item => { loop(data, dropKey, (item) => {
item.children = item.children || [] item.children = item.children || []
// where to insert. New item was inserted to the start of the array in this example, but can be anywhere // where to insert. New item was inserted to the start of the array in this example, but can be anywhere
item.children.unshift(dragObj) item.children.unshift(dragObj)
@@ -129,9 +129,9 @@ export default function ServiceCategory() {
const treeData = useMemo(() => { const treeData = useMemo(() => {
setExpandedKeys([]) setExpandedKeys([])
const loop = (data: CategorizesType[]): DataNode[] => const loop = (data: CategorizesType[]): DataNode[] =>
data?.map(item => { data?.map((item) => {
if (item.children) { if (item.children) {
setExpandedKeys(prev => [...prev, item.id]) setExpandedKeys((prev) => [...prev, item.id])
return { return {
title: ( title: (
<TreeWithMore stopClick={false} dropdownMenu={dropdownMenu(item as CategorizesType)}> <TreeWithMore stopClick={false} dropdownMenu={dropdownMenu(item as CategorizesType)}>
@@ -169,40 +169,22 @@ export default function ServiceCategory() {
return !checkAccess(permission, accessData) return !checkAccess(permission, accessData)
} }
const openModal = ( const openModal = (type: 'addCate' | 'addChildCate' | 'renameCate' | 'delete', entity?: CategorizesType) => {
type: 'addCate' | 'addChildCate' | 'renameCate' | 'delete',
entity?: CategorizesType
) => {
let title: string = '' let title: string = ''
let content: string | React.ReactNode = '' let content: string | React.ReactNode = ''
switch (type) { switch (type) {
case 'addCate': case 'addCate': {
title = $t('添加分类') title = $t('添加分类')
content = ( content = <ServiceHubCategoryConfig ref={addRef} type={type} />
<ServiceHubCategoryConfig WithPermission={WithPermission} ref={addRef} type={type} />
)
break break
}
case 'addChildCate': case 'addChildCate':
title = $t('添加子分类') title = $t('添加子分类')
content = ( content = <ServiceHubCategoryConfig ref={addChildRef} type={type} entity={entity} />
<ServiceHubCategoryConfig
WithPermission={WithPermission}
ref={addChildRef}
type={type}
entity={entity}
/>
)
break break
case 'renameCate': case 'renameCate':
title = $t('重命名分类') title = $t('重命名分类')
content = ( content = <ServiceHubCategoryConfig ref={renameRef} type={type} entity={entity} />
<ServiceHubCategoryConfig
WithPermission={WithPermission}
ref={renameRef}
type={type}
entity={entity}
/>
)
break break
case 'delete': case 'delete':
title = $t('删除') title = $t('删除')
@@ -215,19 +197,19 @@ export default function ServiceCategory() {
onOk: () => { onOk: () => {
switch (type) { switch (type) {
case 'addCate': case 'addCate':
return addRef.current?.save().then(res => { return addRef.current?.save().then((res) => {
if (res === true) getCategoryList() if (res === true) getCategoryList()
}) })
case 'addChildCate': case 'addChildCate':
return addChildRef.current?.save().then(res => { return addChildRef.current?.save().then((res) => {
if (res === true) getCategoryList() if (res === true) getCategoryList()
}) })
case 'renameCate': case 'renameCate':
return renameRef.current?.save().then(res => { return renameRef.current?.save().then((res) => {
if (res === true) getCategoryList() if (res === true) getCategoryList()
}) })
case 'delete': case 'delete':
return deleteCate(entity!).then(res => { return deleteCate(entity!).then((res) => {
if (res === true) getCategoryList() if (res === true) getCategoryList()
}) })
} }
@@ -249,7 +231,7 @@ export default function ServiceCategory() {
method: 'DELETE', method: 'DELETE',
eoParams: { catalogue: entity.id } eoParams: { catalogue: entity.id }
}) })
.then(response => { .then((response) => {
const { code, msg } = response const { code, msg } = response
if (code === STATUS_CODE.SUCCESS) { if (code === STATUS_CODE.SUCCESS) {
message.success(msg || $t(RESPONSE_TIPS.success)) message.success(msg || $t(RESPONSE_TIPS.success))
@@ -259,14 +241,14 @@ export default function ServiceCategory() {
reject(msg || $t(RESPONSE_TIPS.error)) reject(msg || $t(RESPONSE_TIPS.error))
} }
}) })
.catch(errorInfo => reject(errorInfo)) .catch((errorInfo) => reject(errorInfo))
}) })
} }
const sortCategories = (newData: CategorizesType[]) => { const sortCategories = (newData: CategorizesType[]) => {
setLoading(true) setLoading(true)
fetchData<BasicResponse<null>>('catalogue/sort', { method: 'PUT', eoBody: newData }) fetchData<BasicResponse<null>>('catalogue/sort', { method: 'PUT', eoBody: newData })
.then(response => { .then((response) => {
const { code, msg } = response const { code, msg } = response
if (code === STATUS_CODE.SUCCESS) { if (code === STATUS_CODE.SUCCESS) {
getCategoryList() getCategoryList()
@@ -288,7 +270,7 @@ export default function ServiceCategory() {
fetchData<BasicResponse<{ catalogues: CategorizesType[]; tags: EntityItem[] }>>('catalogues', { fetchData<BasicResponse<{ catalogues: CategorizesType[]; tags: EntityItem[] }>>('catalogues', {
method: 'GET' method: 'GET'
}) })
.then(response => { .then((response) => {
const { code, data, msg } = response const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) { if (code === STATUS_CODE.SUCCESS) {
setGData(data.catalogues) setGData(data.catalogues)
@@ -308,11 +290,7 @@ export default function ServiceCategory() {
return ( return (
<div className="border border-solid border-BORDER p-[20px] rounded-[10px] "> <div className="border border-solid border-BORDER p-[20px] rounded-[10px] ">
<Spin <Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={loading} className="">
indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />}
spinning={loading}
className=""
>
<Tree <Tree
showIcon showIcon
draggable draggable
@@ -10,132 +10,131 @@ import {
import { App, Form, Input } from 'antd' import { App, Form, Input } from 'antd'
import { forwardRef, useEffect, useImperativeHandle } from 'react' import { forwardRef, useEffect, useImperativeHandle } from 'react'
export const ServiceHubCategoryConfig = forwardRef< export const ServiceHubCategoryConfig = forwardRef<ServiceHubCategoryConfigHandle, ServiceHubCategoryConfigProps>(
ServiceHubCategoryConfigHandle, (props, ref) => {
ServiceHubCategoryConfigProps const { message } = App.useApp()
>((props, ref) => { const [form] = Form.useForm()
const { message } = App.useApp() const { type, entity } = props
const [form] = Form.useForm() const { fetchData } = useFetch()
const { type, entity } = props
const { fetchData } = useFetch()
const save: () => Promise<boolean | string> = () => { const save: () => Promise<boolean | string> = () => {
const url: string = 'catalogue' const url: string = 'catalogue'
let method: string let method: string
switch (type) { switch (type) {
case 'addCate': case 'addCate':
case 'addChildCate': case 'addChildCate':
method = 'POST' method = 'POST'
break break
case 'renameCate': case 'renameCate':
method = 'PUT' method = 'PUT'
break break
}
return new Promise((resolve, reject) => {
if (!url || !method) {
reject($t(RESPONSE_TIPS.error))
return
} }
form return new Promise((resolve, reject) => {
.validateFields() if (!url || !method) {
.then(value => { reject($t(RESPONSE_TIPS.error))
fetchData<BasicResponse<null>>(url, { return
method, }
eoBody: value, form
eoParams: { ...(type === 'renameCate' ? { catalogue: value.id } : undefined) } .validateFields()
}) .then((value) => {
.then(response => { fetchData<BasicResponse<null>>(url, {
const { code, msg } = response method,
if (code === STATUS_CODE.SUCCESS) { eoBody: value,
message.success(msg || $t(RESPONSE_TIPS.success)) eoParams: { ...(type === 'renameCate' ? { catalogue: value.id } : undefined) }
resolve(true)
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
reject(msg || $t(RESPONSE_TIPS.error))
}
}) })
.catch(errorInfo => reject(errorInfo)) .then((response) => {
}) const { code, msg } = response
.catch(errorInfo => reject(errorInfo)) if (code === STATUS_CODE.SUCCESS) {
}) message.success(msg || $t(RESPONSE_TIPS.success))
} resolve(true)
} else {
useImperativeHandle(ref, () => ({ message.error(msg || $t(RESPONSE_TIPS.error))
save reject(msg || $t(RESPONSE_TIPS.error))
})) }
})
useEffect(() => { .catch((errorInfo) => reject(errorInfo))
switch (type) { })
case 'addCate': .catch((errorInfo) => reject(errorInfo))
form.setFieldsValue({}) })
break
case 'addChildCate':
form.setFieldsValue({ parent: entity!.id })
break
case 'renameCate':
form.setFieldsValue(entity)
break
} }
}, [])
return ( useImperativeHandle(ref, () => ({
<WithPermission save
access={ }))
type === 'addCate'
? 'system.api_market.service_classification.add' useEffect(() => {
: 'system.api_market.service_classification.edit' switch (type) {
case 'addCate':
form.setFieldsValue({})
break
case 'addChildCate':
form.setFieldsValue({ parent: entity!.id })
break
case 'renameCate':
form.setFieldsValue(entity)
break
} }
> }, [])
<Form
layout="vertical"
scrollToFirstError
labelAlign="left"
form={form}
className="mx-auto"
name="serviceHubCategoryConfig"
autoComplete="off"
>
{type === 'renameCate' && (
<Form.Item<ServiceHubCategoryConfigFieldType>
label={$t('ID')}
name="id"
hidden
rules={[{ required: true, whitespace: true }]}
>
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
</Form.Item>
)}
{(type === 'addCate' || type === 'renameCate') && (
<Form.Item<ServiceHubCategoryConfigFieldType>
label={$t('分类名称')}
name="name"
rules={[{ required: true, whitespace: true }]}
>
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
</Form.Item>
)}
{type === 'addChildCate' && ( return (
<> <WithPermission
access={
type === 'addCate'
? 'system.api_market.service_classification.add'
: 'system.api_market.service_classification.edit'
}
>
<Form
layout="vertical"
scrollToFirstError
labelAlign="left"
form={form}
className="mx-auto"
name="serviceHubCategoryConfig"
autoComplete="off"
>
{type === 'renameCate' && (
<Form.Item<ServiceHubCategoryConfigFieldType> <Form.Item<ServiceHubCategoryConfigFieldType>
label={$t('父分类 ID')} label={$t('ID')}
name="parent" name="id"
hidden hidden
rules={[{ required: true, whitespace: true }]} rules={[{ required: true, whitespace: true }]}
> >
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} /> <Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
</Form.Item> </Form.Item>
)}
{(type === 'addCate' || type === 'renameCate') && (
<Form.Item<ServiceHubCategoryConfigFieldType> <Form.Item<ServiceHubCategoryConfigFieldType>
label={$t('分类名称')} label={$t('分类名称')}
name="name" name="name"
rules={[{ required: true, whitespace: true }]} rules={[{ required: true, whitespace: true }]}
> >
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} /> <Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
</Form.Item> </Form.Item>
</> )}
)}
</Form> {type === 'addChildCate' && (
</WithPermission> <>
) <Form.Item<ServiceHubCategoryConfigFieldType>
}) label={$t('父分类 ID')}
name="parent"
hidden
rules={[{ required: true, whitespace: true }]}
>
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
</Form.Item>
<Form.Item<ServiceHubCategoryConfigFieldType>
label={$t('子分类名称')}
name="name"
rules={[{ required: true, whitespace: true }]}
>
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
</Form.Item>
</>
)}
</Form>
</WithPermission>
)
}
)
@@ -1,7 +1,6 @@
import { DefaultOptionType } from 'antd/es/select'
import { EntityItem } from '@common/const/type' import { EntityItem } from '@common/const/type'
import { SubscribeEnum, SubscribeFromEnum } from '@core/const/system/const' import { SubscribeEnum, SubscribeFromEnum } from '@core/const/system/const'
import WithPermission from '@common/components/aoplatform/WithPermission' import { DefaultOptionType } from 'antd/es/select'
export type ServiceBasicInfoType = { export type ServiceBasicInfoType = {
app: EntityItem app: EntityItem
@@ -37,7 +36,6 @@ export type ServiceHubCategoryConfigFieldType = {
export type ServiceHubCategoryConfigProps = { export type ServiceHubCategoryConfigProps = {
type: 'addCate' | 'addChildCate' | 'renameCate' type: 'addCate' | 'addChildCate' | 'renameCate'
entity?: { [k: string]: unknown } entity?: { [k: string]: unknown }
WithPermission: typeof WithPermission
} }
export type ServiceHubCategoryConfigHandle = { export type ServiceHubCategoryConfigHandle = {
@@ -5,7 +5,7 @@ import WithPermission from '@common/components/aoplatform/WithPermission'
import { BasicResponse, DATA_SHOW_TYPE_OPTIONS, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' import { BasicResponse, DATA_SHOW_TYPE_OPTIONS, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
import { SimpleTeamItem } from '@common/const/type' import { SimpleTeamItem } from '@common/const/type'
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext' import { useBreadcrumb } from '@common/contexts/BreadcrumbContext'
import { GlobalProvider, useGlobalContext } from '@common/contexts/GlobalStateContext' import { useGlobalContext } from '@common/contexts/GlobalStateContext'
import { useFetch } from '@common/hooks/http' import { useFetch } from '@common/hooks/http'
import { $t } from '@common/locales' import { $t } from '@common/locales'
import { RouterParams } from '@core/components/aoplatform/RenderRoutes' import { RouterParams } from '@core/components/aoplatform/RenderRoutes'
@@ -158,28 +158,8 @@ export default function ServiceHubManagement() {
switch (type) { switch (type) {
case 'add': case 'add':
title = $t('添加消费者') title = $t('添加消费者')
content = ( content = <ManagementConfig ref={addManagementRef} dataShowType={dataShowType} type={type} teamId={teamId!} />
<GlobalProvider>
<ManagementConfig ref={addManagementRef} dataShowType={dataShowType} type={type} teamId={teamId!} />
</GlobalProvider>
)
break break
// case 'edit':{
// title='配置 Open Api'
// message.loading('正在加载数据')
// const {code,data,msg} = await fetchData<BasicResponse<{app:ManagementConfigFieldType}>>('external-app',{method:'GET',eoParams:{id:entity!.id}})
// message.destroy()
// if(code === STATUS_CODE.SUCCESS){
// content=<ManagementConfig ref={editManagementRef} type={type} entity={data.app}/>
// }else{
// message.error(msg || $t(RESPONSE_TIPS.error))
// return
// }
// break;}
// case 'delete':
// title='删除'
// content='该数据删除后将无法找回,请确认是否删除?'
// break;
} }
modal.confirm({ modal.confirm({
+1 -1
View File
@@ -28,7 +28,7 @@
b: "subscription_service:#{application}" b: "subscription_service:#{application}"
response: response:
status_code: 403 status_code: 403
content_typ: "text/plan" content_type: "text/plan"
charset: "utf-8" charset: "utf-8"
body: "Forbidden" body: "Forbidden"
+1
View File
@@ -5,6 +5,7 @@ import (
_ "github.com/APIParkLab/APIPark/frontend" _ "github.com/APIParkLab/APIPark/frontend"
_ "github.com/APIParkLab/APIPark/gateway/apinto" _ "github.com/APIParkLab/APIPark/gateway/apinto"
_ "github.com/APIParkLab/APIPark/plugins/core" _ "github.com/APIParkLab/APIPark/plugins/core"
_ "github.com/APIParkLab/APIPark/plugins/openapi"
_ "github.com/APIParkLab/APIPark/plugins/permit" _ "github.com/APIParkLab/APIPark/plugins/permit"
_ "github.com/APIParkLab/APIPark/plugins/publish_flow" _ "github.com/APIParkLab/APIPark/plugins/publish_flow"
_ "github.com/APIParkLab/APIPark/resources/locale" _ "github.com/APIParkLab/APIPark/resources/locale"
+9 -7
View File
@@ -533,6 +533,7 @@ func (i *imlProviderModule) UpdateProviderConfig(ctx context.Context, id string,
DefaultLLM: &input.DefaultLLM, DefaultLLM: &input.DefaultLLM,
Config: &input.Config, Config: &input.Config,
Priority: input.Priority, Priority: input.Priority,
Status: &status,
} }
_, err = i.aiKeyService.DefaultKey(ctx, id) _, err = i.aiKeyService.DefaultKey(ctx, id)
if err != nil { if err != nil {
@@ -558,17 +559,18 @@ func (i *imlProviderModule) UpdateProviderConfig(ctx context.Context, id string,
return err return err
} }
if input.Enable != nil { //if input.Enable != nil {
status = 0 // status = 0
if *input.Enable { // if *input.Enable {
status = 1 // status = 1
} // }
pInfo.Status = &status // pInfo.Status = &status
} //}
err = i.providerService.Save(ctx, id, pInfo) err = i.providerService.Save(ctx, id, pInfo)
if err != nil { if err != nil {
return err return err
} }
if *pInfo.Status == 0 { if *pInfo.Status == 0 {
return i.syncGateway(ctx, cluster.DefaultClusterID, []*gateway.DynamicRelease{ return i.syncGateway(ctx, cluster.DefaultClusterID, []*gateway.DynamicRelease{
{ {
+18
View File
@@ -0,0 +1,18 @@
package openapi
import (
"net/http"
"github.com/eolinker/go-common/pm3"
)
func (p *plugin) appAuthorizationApis() []pm3.Api {
return []pm3.Api{
pm3.CreateApiWidthDoc(http.MethodPost, "/openapi/v1/app/authorization", []string{"context", "query:app", "body"}, []string{"authorization"}, p.authorizationController.AddAuthorization),
pm3.CreateApiWidthDoc(http.MethodPut, "/openapi/v1/app/authorization", []string{"context", "query:app", "query:authorization", "body"}, []string{"authorization"}, p.authorizationController.EditAuthorization),
pm3.CreateApiWidthDoc(http.MethodDelete, "/openapi/v1/app/authorization", []string{"context", "query:app", "query:authorization"}, nil, p.authorizationController.DeleteAuthorization),
pm3.CreateApiWidthDoc(http.MethodGet, "/openapi/v1/app/authorization", []string{"context", "query:app", "query:authorization"}, []string{"authorization"}, p.authorizationController.Info),
pm3.CreateApiWidthDoc(http.MethodGet, "/openapi/v1/app/authorizations", []string{"context", "query:app"}, []string{"authorizations"}, p.authorizationController.Authorizations),
pm3.CreateApiWidthDoc(http.MethodGet, "/openapi/v1/app/authorization/details", []string{"context", "query:app", "query:authorization"}, []string{"details"}, p.authorizationController.Detail),
}
}
+45
View File
@@ -0,0 +1,45 @@
package openapi
import (
"strings"
"github.com/eolinker/eosc/env"
"github.com/gin-gonic/gin"
)
var (
defaultAPIKey = "37eb0ebf"
openCheck = newOpenapiCheck()
)
type openapiCheck struct {
apikey string
}
func newOpenapiCheck() *openapiCheck {
apikey, has := env.GetEnv("API_KEY")
if !has {
apikey = defaultAPIKey
}
return &openapiCheck{apikey: apikey}
}
func (o *openapiCheck) Check(method string, path string) (bool, []gin.HandlerFunc) {
if strings.HasPrefix(path, "/openapi/") {
return true, []gin.HandlerFunc{o.Handler}
}
return false, nil
}
func (o *openapiCheck) Sort() int {
return -1
}
func (o *openapiCheck) Handler(ginCtx *gin.Context) {
authorization := ginCtx.GetHeader("Authorization")
if authorization == "" {
ginCtx.AbortWithStatusJSON(403, gin.H{"code": -8, "msg": "invalid token", "success": "fail"})
return
}
}
+19
View File
@@ -0,0 +1,19 @@
package openapi
import (
"github.com/eolinker/go-common/autowire"
"github.com/eolinker/go-common/pm3"
)
func init() {
pm3.Register("openapi", new(Driver))
}
type Driver struct {
}
func (d *Driver) Create() (pm3.IPlugin, error) {
p := new(plugin)
autowire.Autowired(p)
return p, nil
}
+33
View File
@@ -0,0 +1,33 @@
package openapi
import (
application_authorization "github.com/APIParkLab/APIPark/controller/application-authorization"
"github.com/eolinker/go-common/pm3"
)
var (
_ pm3.IPlugin = (*plugin)(nil)
_ pm3.IPluginMiddleware = (*plugin)(nil)
)
type plugin struct {
apis []pm3.Api
authorizationController application_authorization.IAuthorizationController `autowired:""`
}
func (p *plugin) Middlewares() []pm3.IMiddleware {
return []pm3.IMiddleware{
openCheck,
}
}
func (p *plugin) APis() []pm3.Api {
return p.apis
}
func (p *plugin) Name() string {
return "openapi"
}
func (p *plugin) OnComplete() {
p.apis = p.appAuthorizationApis()
}
+1 -1
View File
@@ -211,7 +211,7 @@ APIParkはApache 2.0ライセンスの下で提供されています。詳細に
エンタープライズ機能や専門的な技術サポートについては、プリセールスの専門家に連絡し、個別デモ、カスタムソリューション、価格情報を入手してください。 エンタープライズ機能や専門的な技術サポートについては、プリセールスの専門家に連絡し、個別デモ、カスタムソリューション、価格情報を入手してください。
- ウェブサイト: https://apipark.com - ウェブサイト: https://apipark.com
- メール: dev@apipark.com - メール: contact@apipark.com
<br> <br>
+1 -1
View File
@@ -215,7 +215,7 @@ APIPark 使用 Apache 2.0 许可证。更多详情请查看 LICENSE 文件。
对于企业级功能和专业技术支持,请联系售前专家进行个性化演示、定制方案和获取报价。 对于企业级功能和专业技术支持,请联系售前专家进行个性化演示、定制方案和获取报价。
- 网站: https://apipark.com - 网站: https://apipark.com
- 电子邮件: dev@apipark.com - 电子邮件: contact@apipark.com
<br> <br>
+1 -1
View File
@@ -212,7 +212,7 @@ APIPark 使用 Apache 2.0 授權條款。更多詳情請參閱 LICENSE 文件。
如需企業級功能與專業技術支援,請聯絡我們的售前專家,獲取個性化演示、定制方案和報價。 如需企業級功能與專業技術支援,請聯絡我們的售前專家,獲取個性化演示、定制方案和報價。
- 網站: https://apipark.com - 網站: https://apipark.com
- 電子郵件: dev@apipark.com - 電子郵件: contact@apipark.com
<br> <br>
+2 -2
View File
@@ -1,4 +1,4 @@
version: v7 version: v8
sort: sort:
- "access_log" - "access_log"
- "monitor" - "monitor"
@@ -41,7 +41,7 @@ plugin:
b: "subscription_service:#{application}" b: "subscription_service:#{application}"
response: response:
status_code: 403 status_code: 403
content_typ: "text/plan" content_type: "text/plan"
charset: "utf-8" charset: "utf-8"
body: "Forbidden" body: "Forbidden"
+2 -2
View File
@@ -8,8 +8,8 @@ RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ARG APP ARG APP
ENV NSQ_ADDR=nsq:4150 ENV NSQ_ADDR=${APP}-nsq:4150
ENV NSQ_TOPIC_PREFIX=apipark ENV NSQ_TOPIC_PREFIX=${APP}
RUN mkdir -p /${APP} RUN mkdir -p /${APP}
+1 -1
View File
@@ -17,7 +17,7 @@ source ./scripts/common.sh
APP="apipark" APP="apipark"
mkdir -p scripts/cmd/ && cp cmd/${APP} scripts/cmd/ mkdir -p scripts/cmd/ && cp cmd/${APP} scripts/cmd/ && cp cmd/apipark_ai_event_listen scripts/cmd/
VERSION=$(gen_version) VERSION=$(gen_version)
+1 -1
View File
@@ -27,7 +27,7 @@ echo -e " - $s" >> config.yml
done done
echo -e "nsq:" >> config.yml echo -e "nsq:" >> config.yml
echo -e " addr: ${NSQ_ADDR}" >> config.yml echo -e " addr: ${NSQ_ADDR}" >> config.yml
echo -e " topic: ${NSQ_TOPIC}" >> config.yml echo -e " topic_prefix: ${NSQ_TOPIC_PREFIX}" >> config.yml
echo -e "port: 8288" >> config.yml echo -e "port: 8288" >> config.yml
echo -e "error_log:" >> config.yml echo -e "error_log:" >> config.yml
echo -e " dir: ${ERROR_DIR}" >> config.yml echo -e " dir: ${ERROR_DIR}" >> config.yml
+1 -1
View File
@@ -8,7 +8,7 @@ type Provider struct {
Name string `gorm:"type:varchar(100);not null;column:name;comment:name"` Name string `gorm:"type:varchar(100);not null;column:name;comment:name"`
DefaultLLM string `gorm:"type:varchar(255);not null;column:default_llm;comment:默认模型ID"` DefaultLLM string `gorm:"type:varchar(255);not null;column:default_llm;comment:默认模型ID"`
Config string `gorm:"type:text;not null;column:config;comment:配置信息"` Config string `gorm:"type:text;not null;column:config;comment:配置信息"`
Status int `gorm:"type:tinyint(1);not null;column:status;comment:状态,0:停用;1:启用,2:异常"` Status int `gorm:"type:tinyint(1);not null;column:status;comment:状态,0:停用;1:启用,2:异常;default:1"`
Priority int `gorm:"type:int;not null;column:priority;comment:优先级,值越小优先级越高"` Priority int `gorm:"type:int;not null;column:priority;comment:优先级,值越小优先级越高"`
Creator string `gorm:"size:36;not null;column:creator;comment:创建人;index:creator" aovalue:"creator"` // 创建人 Creator string `gorm:"size:36;not null;column:creator;comment:创建人;index:creator" aovalue:"creator"` // 创建人
Updater string `gorm:"size:36;not null;column:updater;comment:更新人;index:updater" aovalue:"updater"` // 更新人 Updater string `gorm:"size:36;not null;column:updater;comment:更新人;index:updater" aovalue:"updater"` // 更新人
+1 -1
View File
@@ -55,7 +55,7 @@ type Doc struct {
Id int64 `gorm:"column:id;type:BIGINT(20);AUTO_INCREMENT;NOT NULL;comment:id;primary_key;comment:主键ID;"` Id int64 `gorm:"column:id;type:BIGINT(20);AUTO_INCREMENT;NOT NULL;comment:id;primary_key;comment:主键ID;"`
UUID string `gorm:"type:varchar(36);not null;column:uuid;uniqueIndex:uuid;comment:UUID;"` UUID string `gorm:"type:varchar(36);not null;column:uuid;uniqueIndex:uuid;comment:UUID;"`
Service string `gorm:"size:36;not null;column:service;comment:服务;index:service"` Service string `gorm:"size:36;not null;column:service;comment:服务;index:service"`
Content string `gorm:"type:text;null;column:content;comment:文档内容"` Content string `gorm:"type:longtext;null;column:content;comment:文档内容"`
Updater string `gorm:"size:36;not null;column:updater;comment:更新人;index:updater" aovalue:"updater"` Updater string `gorm:"size:36;not null;column:updater;comment:更新人;index:updater" aovalue:"updater"`
UpdateAt time.Time `gorm:"type:timestamp;NOT NULL;DEFAULT:CURRENT_TIMESTAMP;column:update_at;comment:更新时间"` UpdateAt time.Time `gorm:"type:timestamp;NOT NULL;DEFAULT:CURRENT_TIMESTAMP;column:update_at;comment:更新时间"`
APICount int64 `gorm:"type:int(11);not null;column:api_count;comment:接口数量"` APICount int64 `gorm:"type:int(11);not null;column:api_count;comment:接口数量"`
+1 -1
View File
@@ -7,7 +7,7 @@ type Commit[H any] struct {
UUID string `gorm:"size:36;not null;column:uuid;comment:uuid;uniqueIndex:uuid;"` UUID string `gorm:"size:36;not null;column:uuid;comment:uuid;uniqueIndex:uuid;"`
Target string `gorm:"column:target;type:varchar(36);NOT NULL;comment:目标id;index:target;"` Target string `gorm:"column:target;type:varchar(36);NOT NULL;comment:目标id;index:target;"`
Key string `gorm:"size:50;not null;column:key;comment:类型;index:key;"` Key string `gorm:"size:50;not null;column:key;comment:类型;index:key;"`
Data *H `gorm:"type:text;not null;column:data;comment:数据;charset=utf8mb4;serializer:json"` Data *H `gorm:"type:longtext;not null;column:data;comment:数据;charset=utf8mb4;serializer:json"`
CreateAt time.Time `gorm:"type:timestamp;NOT NULL;DEFAULT:CURRENT_TIMESTAMP;column:create_at;comment:创建时间"` CreateAt time.Time `gorm:"type:timestamp;NOT NULL;DEFAULT:CURRENT_TIMESTAMP;column:create_at;comment:创建时间"`
Operator string `gorm:"size:36;not null;column:operator;comment:操作人;index:operator;"` Operator string `gorm:"size:36;not null;column:operator;comment:操作人;index:operator;"`
} }