feat: add api access info modal

main
jialin 1 year ago
parent 7c79accda7
commit 48580cb5b0

@ -5,7 +5,7 @@ import styled from 'styled-components';
type ModalFooterProps = {
onOk?: () => void;
onCancel: () => void;
onCancel?: () => void;
cancelText?: string;
okText?: string;
htmlType?: 'button' | 'submit';
@ -40,6 +40,7 @@ const ModalFooter: React.FC<ModalFooterProps> = ({
showOkBtn = true,
description,
extra,
showCancelBtn = true,
form
}) => {
const intl = useIntl();
@ -47,13 +48,15 @@ const ModalFooter: React.FC<ModalFooterProps> = ({
<Wrapper>
<div>{description}</div>
<Space size={20} style={{ ...style }}>
<Button
onClick={onCancel}
style={{ width: '88px' }}
{...cancelBtnProps}
>
{cancelText || intl.formatMessage({ id: 'common.button.cancel' })}
</Button>
{showCancelBtn && (
<Button
onClick={onCancel}
style={{ width: '88px' }}
{...cancelBtnProps}
>
{cancelText || intl.formatMessage({ id: 'common.button.cancel' })}
</Button>
)}
{extra}
{showOkBtn && (
<Button

@ -63,8 +63,6 @@ const KeyMap: Record<KeyBindingType, KeybindingValue> = KeybiningList.reduce(
{}
);
console.log('KeyMap=========', KeyMap);
export { KeyMap, KeybiningList };
export default KeybindingsMap;

@ -131,7 +131,16 @@ export default {
'models.form.check.claims':
'The model requires approximately {vram} VRAM and {ram} RAM.',
'models.form.check.claims2': 'The model requires approximately {vram} VRAM.',
'models.form.check.claims3': 'The model requires approximately {ram} RAM.',
'models.form.update.tips':
'Changes will only apply after you delete and recreate the instance.',
'models.table.download.progress': 'Download Progress'
'models.table.download.progress': 'Download Progress',
'models.table.button.apiAccessInfo': 'API Access Info',
'models.table.button.apiAccessInfo.tips': `To integrate this model with third-party applications, use the following details: access URL, model name, and API key. These credentials are required to ensure proper connection and usage of the model service.`,
'models.table.apiAccessInfo.enpoint': 'Access URL',
'models.table.apiAccessInfo.modelName': 'Model Name',
'models.table.apiAccessInfo.apikey': 'API Key',
'models.table.apiAccessInfo.openaiCompatible': 'OpenAI Compatible',
'models.table.apiAccessInfo.jinaCompatible': 'Jina Compatible',
'models.table.apiAccessInfo.gotoCreate': 'Go to Create'
};

@ -128,9 +128,18 @@ export default {
'models.form.check.claims':
'The model requires approximately {vram} VRAM and {ram} RAM.',
'models.form.check.claims2': 'The model requires approximately {vram} VRAM.',
'models.form.check.claims3': 'The model requires approximately {ram} RAM.',
'models.form.update.tips':
'Changes will only apply after you delete and recreate the instance.',
'models.table.download.progress': 'Download Progress'
'models.table.download.progress': 'Download Progress',
'models.table.button.apiAccessInfo': 'API Access Info',
'models.table.button.apiAccessInfo.tips': `To integrate this model with third-party applications, use the following details: access URL, model name, and API key. These credentials are required to ensure proper connection and usage of the model service.`,
'models.table.apiAccessInfo.enpoint': 'Access URL',
'models.table.apiAccessInfo.modelName': 'Model Name',
'models.table.apiAccessInfo.apikey': 'API Key',
'models.table.apiAccessInfo.openaiCompatible': 'OpenAI Compatible',
'models.table.apiAccessInfo.jinaCompatible': 'Jina Compatible',
'models.table.apiAccessInfo.gotoCreate': 'Go to Create'
};
// ========== To-Do: Translate Keys (Remove After Translation) ==========
@ -149,5 +158,14 @@ export default {
// 13. 'model.deploy.search.placeholder',
// 14. 'models.form.update.tips,
// 15. models.form.check.claims2,
// 16. 'models.table.download.progress'
// 16. 'models.table.download.progress',
// 17. 'models.form.check.claims3',
// 18. 'models.table.button.apiAccessInfo',
// 19. 'models.table.button.apiAccessInfo.tips',
// 20. 'models.table.apiAccessInfo.enpoint',
// 21. 'models.table.apiAccessInfo.modelName',
// 22. 'models.table.apiAccessInfo.apikey',
// 23. 'models.table.apiAccessInfo.openaiCompatible',
// 24. 'models.table.apiAccessInfo.jinaCompatible',
// 25. 'models.table.apiAccessInfo.gotoCreate',
// ========== End of To-Do List ==========

@ -131,9 +131,18 @@ export default {
'models.form.check.claims':
'Модель требует примерно {vram} VRAM и {ram} RAM.',
'models.form.check.claims2': 'Модель требует примерно {vram} VRAM.',
'models.form.check.claims3': 'Модель требует примерно {ram} RAM.',
'models.form.update.tips':
'Changes will only apply after you delete and recreate the instance.',
'models.table.download.progress': 'Download Progress'
'models.table.download.progress': 'Download Progress',
'models.table.button.apiAccessInfo': 'API Access Info',
'models.table.button.apiAccessInfo.tips': `To integrate this model with third-party applications, use the following details: access URL, model name, and API key. These credentials are required to ensure proper connection and usage of the model service.`,
'models.table.apiAccessInfo.enpoint': 'Access URL',
'models.table.apiAccessInfo.modelName': 'Model Name',
'models.table.apiAccessInfo.apikey': 'API Key',
'models.table.apiAccessInfo.openaiCompatible': 'OpenAI Compatible',
'models.table.apiAccessInfo.jinaCompatible': 'Jina Compatible',
'models.table.apiAccessInfo.gotoCreate': 'Go to Create'
};
// ========== To-Do: Translate Keys (Remove After Translation) ==========
@ -141,5 +150,13 @@ export default {
// 2. 'models.form.partialoffload.tips
// 3. 'model.deploy.search.placeholder',
// 4. 'models.form.update.tips',
// 5. 'models.table.download.progress
// 5. 'models.table.download.progress,
// 6. 'models.table.button.apiAccessInfo',
// 7. 'models.table.button.apiAccessInfo.tips',
// 8. 'models.table.apiAccessInfo.enpoint',
// 9. 'models.table.apiAccessInfo.modelName',
// 10. 'models.table.apiAccessInfo.apikey',
// 11. 'models.table.apiAccessInfo.openaiCompatible',
// 12. 'models.table.apiAccessInfo.jinaCompatible',
// 13. 'models.table.apiAccessInfo.gotoCreate',
// ========== End of To-Do List ==========

@ -124,6 +124,15 @@ export default {
'models.form.check.passed': '兼容性检查通过',
'models.form.check.claims': '该模型大约需要 {vram} 显存和 {ram} 内存。',
'models.form.check.claims2': '该模型大约需要 {vram} 显存。',
'models.form.check.claims3': '该模型大约需要 {ram} 内存。',
'models.form.update.tips': '更改仅在删除并重新创建实例后生效。',
'models.table.download.progress': '下载进度'
'models.table.download.progress': '下载进度',
'models.table.button.apiAccessInfo': 'API 接入信息',
'models.table.button.apiAccessInfo.tips': `当您需要将本模型与第三方应用集成时,请使用以下信息:访问地址、模型名称和 API 密钥。这些信息是确保外部系统能正确连接并调用模型服务的关键凭证。`,
'models.table.apiAccessInfo.enpoint': '接入地址',
'models.table.apiAccessInfo.modelName': '模型名称',
'models.table.apiAccessInfo.apikey': 'API 密钥',
'models.table.apiAccessInfo.openaiCompatible': 'OpenAI 兼容',
'models.table.apiAccessInfo.jinaCompatible': 'Jina 兼容',
'models.table.apiAccessInfo.gotoCreate': '去创建'
};

@ -0,0 +1,197 @@
import AutoTooltip from '@/components/auto-tooltip';
import CopyButton from '@/components/copy-button';
import IconFont from '@/components/icon-font';
import ScrollerModal from '@/components/scroller-modal';
import { BulbOutlined } from '@ant-design/icons';
import { useIntl, useNavigate } from '@umijs/max';
import { Button, Tag } from 'antd';
import _ from 'lodash';
import { useMemo } from 'react';
import styled from 'styled-components';
import { modelCategoriesMap } from '../config';
const ApiAccessInfoWrapper = styled.div`
display: flex;
flex-direction: column;
gap: 12px;
padding-left: 20px;
.item {
display: flex;
justify-content: space-between;
align-items: center;
.label-wrapper {
display: flex;
justify-content: flex-start;
align-items: center;
gap: 16px;
.label {
width: 86px;
font-weight: 600;
}
.value {
display: flex;
align-items: center;
color: var(--ant-color-text-secondary);
}
}
.copy-btn {
display: flex;
align-items: center;
gap: 4px;
}
}
`;
const Tips = styled.div`
color: var(--ant-color-text-secondary);
font-size: var(--font-size-small);
.tips {
display: flex;
align-items: flex-start;
gap: 8px;
}
dd {
margin-bottom: 16px;
}
`;
const APITAG = styled(Tag)`
border-radius: 12px;
margin: 0;
margin-left: 8px;
`;
const CreateButton = styled(Button)`
padding-inline: 0;
`;
interface ApiAccessInfoProps {
open: boolean;
data: any;
onClose: () => void;
}
const ApiAccessInfo = ({ open, data, onClose }: ApiAccessInfoProps) => {
const intl = useIntl();
const navigate = useNavigate();
const endPoint = `${window.location.origin}/v1`;
const isRanker = useMemo(() => {
return _.includes(data.categories, modelCategoriesMap.reranker);
}, [data]);
const handleClose = () => {
onClose();
};
return (
<ScrollerModal
open={open}
style={{
top: '20%'
}}
title={intl.formatMessage({ id: 'models.table.button.apiAccessInfo' })}
width={510}
destroyOnClose
closable={true}
maskClosable={false}
onOk={handleClose}
onCancel={handleClose}
styles={{
content: {
padding: '0 0 16px 0'
},
header: {
padding: 'var(--ant-modal-content-padding)',
paddingBottom: '0'
},
body: {
padding: '16px 24px 32px'
},
footer: {
padding: '16px 24px',
margin: '0'
}
}}
footer={null}
>
<Tips>
<dl className="tips">
<dt>
<BulbOutlined />
</dt>
<dd>
{intl.formatMessage({
id: 'models.table.button.apiAccessInfo.tips'
})}
</dd>
</dl>
</Tips>
<ApiAccessInfoWrapper>
<div className="item">
<span className="label-wrapper">
<span className="label">
{intl.formatMessage({ id: 'models.table.apiAccessInfo.enpoint' })}
</span>
<span className="value">
<AutoTooltip ghost maxWidth={200}>
{endPoint}
</AutoTooltip>
<APITAG color="geekblue">
{intl.formatMessage({
id: isRanker
? 'models.table.apiAccessInfo.jinaCompatible'
: 'models.table.apiAccessInfo.openaiCompatible'
})}
</APITAG>
</span>
</span>
<span className="copy-btn">
<CopyButton text={endPoint} type="link" size="small"></CopyButton>
</span>
</div>
<div className="item">
<span className="label-wrapper">
<span className="label">
{intl.formatMessage({
id: 'models.table.apiAccessInfo.modelName'
})}
</span>
<span className="value">
<AutoTooltip ghost maxWidth={300}>
{data.name}
</AutoTooltip>
</span>
</span>
<span className="copy-btn">
<CopyButton text={data.name} type="link" size="small"></CopyButton>
</span>
</div>
<div className="item">
<span className="label-wrapper">
<span className="label">
{intl.formatMessage({ id: 'models.table.apiAccessInfo.apikey' })}
</span>
<span className="value">
<CreateButton
type="link"
size="small"
onClick={() => navigate('/api-keys')}
>
{intl.formatMessage({
id: 'models.table.apiAccessInfo.gotoCreate'
})}
<IconFont
type="icon-external-link"
className="font-size-14"
></IconFont>
</CreateButton>
</span>
</span>
</div>
</ApiAccessInfoWrapper>
</ScrollerModal>
);
};
export default ApiAccessInfo;

@ -78,12 +78,12 @@ import {
SourceType
} from '../config/types';
import { useGenerateFormEditInitialValues } from '../hooks';
import APIAccessInfoModal from './api-access-info';
import DeployModal from './deploy-modal';
import Instances from './instances';
import ModelTag from './model-tag';
import UpdateModel from './update-modal';
import ViewLogsModal from './view-logs-modal';
interface ModelsProps {
handleSearch: () => void;
handleNameChange: (e: any) => void;
@ -174,6 +174,10 @@ const Models: React.FC<ModelsProps> = ({
defaultSortOrder: 'descend'
});
const [apiAccessInfo, setAPIAccessInfo] = useState<any>({
show: false,
data: {}
});
const [openLogModal, setOpenLogModal] = useState(false);
const [openAddModal, setOpenAddModal] = useState(false);
const [openDeployModal, setOpenDeployModal] = useState<{
@ -438,6 +442,17 @@ const Models: React.FC<ModelsProps> = ({
saveScrollHeight();
};
const handleViewAPIInfo = useCallback((row: ListItem) => {
setAPIAccessInfo({
show: true,
data: {
id: row.id,
name: row.name,
categories: row.categories,
url: `${MODELS_API}/${row.id}/instances`
}
});
}, []);
const handleSelect = useCallback(
async (val: any, row: ListItem) => {
try {
@ -456,6 +471,10 @@ const Models: React.FC<ModelsProps> = ({
updateExpandedRowKeys([row.id, ...expandedRowKeys]);
}
if (val === 'api') {
handleViewAPIInfo(row);
}
if (val === 'stop') {
modalRef.current.show({
content: 'models.instances',
@ -848,6 +867,16 @@ const Models: React.FC<ModelsProps> = ({
onCancel={handleLogModalCancel}
></ViewLogsModal>
<DeleteModal ref={modalRef}></DeleteModal>
<APIAccessInfoModal
open={apiAccessInfo.show}
data={apiAccessInfo.data}
onClose={() => {
setAPIAccessInfo({
...apiAccessInfo,
show: false
});
}}
></APIAccessInfoModal>
</>
);
};

@ -1,6 +1,7 @@
import IconFont from '@/components/icon-font';
import HotKeys from '@/config/hotkeys';
import {
ApiOutlined,
DeleteOutlined,
DownloadOutlined,
EditOutlined,
@ -19,6 +20,7 @@ const icons = {
ThunderboltOutlined: React.createElement(ThunderboltOutlined),
RetweetOutlined: React.createElement(RetweetOutlined),
DownloadOutlined: React.createElement(DownloadOutlined),
ApiOutlined: React.createElement(ApiOutlined),
Stop: React.createElement(IconFont, { type: 'icon-stop1' }),
Play: React.createElement(IconFont, { type: 'icon-outline-play' }),
Catalog: React.createElement(IconFont, { type: 'icon-catalog' }),
@ -76,6 +78,11 @@ export const ActionList: ActionItem[] = [
key: 'chat',
icon: icons.ExperimentOutlined
},
{
label: 'models.table.button.apiAccessInfo',
key: 'api',
icon: icons.ApiOutlined
},
{
label: 'common.button.stop',
key: 'stop',
@ -177,7 +184,7 @@ export const generateSource = (record: any) => {
export const setModelActionList = (record: any) => {
return _.filter(ActionList, (action: any) => {
if (action.key === 'chat') {
if (action.key === 'chat' || action.key === 'api') {
return record.ready_replicas > 0;
}
if (action.key === 'start') {

@ -306,14 +306,16 @@ export const useCheckCompatibility = () => {
if (hasClaim) {
const ram = convertFileSize(resource_claim.ram, 2);
const vram = convertFileSize(resource_claim.vram, 2);
let messageId = 'models.form.check.claims';
if (!ram) {
messageId = 'models.form.check.claims2';
}
if (!vram) {
messageId = 'models.form.check.claims3';
}
msgData = {
title: intl.formatMessage({ id: 'models.form.check.passed' }),
message: ram
? intl.formatMessage(
{ id: 'models.form.check.claims' },
{ ram, vram }
)
: intl.formatMessage({ id: 'models.form.check.claims2' }, { vram })
message: intl.formatMessage({ id: messageId }, { ram, vram })
};
}

Loading…
Cancel
Save