chore: model access statement
This commit is contained in:
@@ -1,11 +1,29 @@
|
||||
import TransferInner from '@/pages/_components/transfer';
|
||||
import { PageAction } from '@/config';
|
||||
import { PageActionType } from '@/config/types';
|
||||
import SelectPanel from '@/pages/_components/select-panel';
|
||||
import { queryModelsList } from '@/pages/llmodels/apis';
|
||||
import { Form } from 'antd';
|
||||
import { Divider, Form, Radio } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { ListItem } from '../../config/types';
|
||||
|
||||
const AllowModelsForm: React.FC = () => {
|
||||
const Label = styled.div`
|
||||
font-weight: 500;
|
||||
margin-block: -8px 12px;
|
||||
font-size: 14px;
|
||||
margin-left: 4px;
|
||||
`;
|
||||
|
||||
const AllowModelsForm: React.FC<{
|
||||
currentData?: Partial<ListItem> | null;
|
||||
action: PageActionType;
|
||||
}> = ({ currentData, action }) => {
|
||||
const form = Form.useFormInstance();
|
||||
const targetKeys = Form.useWatch('allowed_model_names', form);
|
||||
const allowedModelNames = Form.useWatch(
|
||||
'allowed_model_names',
|
||||
form
|
||||
) as string[];
|
||||
const allowedType = Form.useWatch('allowed_type', form);
|
||||
const [modelList, setModelList] = useState<{ key: string; title: string }[]>(
|
||||
[]
|
||||
);
|
||||
@@ -17,36 +35,53 @@ const AllowModelsForm: React.FC = () => {
|
||||
const getModelList = async () => {
|
||||
try {
|
||||
const res = await queryModelsList(queryParams);
|
||||
const options = res.items.map((item) => ({
|
||||
title: item.name,
|
||||
key: item.name
|
||||
}));
|
||||
setModelList(options);
|
||||
const options = res.items.map((item) => item.name);
|
||||
if (action === PageAction.EDIT && currentData) {
|
||||
const list = new Set([
|
||||
...options,
|
||||
...(currentData.allowed_model_names?.map((item) => item) || [])
|
||||
]);
|
||||
setModelList(
|
||||
Array.from(list).map((item) => ({ key: item, title: item }))
|
||||
);
|
||||
} else {
|
||||
setModelList(options.map((item) => ({ key: item, title: item })));
|
||||
}
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getModelList();
|
||||
}, []);
|
||||
}, [action, currentData]);
|
||||
|
||||
return (
|
||||
<Form.Item name="allowed_model_names">
|
||||
<TransferInner
|
||||
dataSource={modelList}
|
||||
targetKeys={targetKeys}
|
||||
onChange={(nextTargetKeys) => {
|
||||
form.setFieldsValue({ allowed_model_names: nextTargetKeys });
|
||||
}}
|
||||
render={(item) => item.title}
|
||||
titles={['Available Models', 'Allowed Models']}
|
||||
showSearch={{
|
||||
placeholder: 'Filter by model name'
|
||||
}}
|
||||
filterOption={(inputValue, item) =>
|
||||
item.title.toLowerCase().includes(inputValue.toLowerCase())
|
||||
}
|
||||
></TransferInner>
|
||||
</Form.Item>
|
||||
<div>
|
||||
<Divider></Divider>
|
||||
<Label>Model Access</Label>
|
||||
<Form.Item
|
||||
name="allowed_type"
|
||||
initialValue="all"
|
||||
style={{ marginBottom: 8 }}
|
||||
>
|
||||
<Radio.Group
|
||||
options={[
|
||||
{ label: 'All models', value: 'all' },
|
||||
{ label: 'Selected models', value: 'custom' }
|
||||
]}
|
||||
></Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item name="allowed_model_names" hidden={allowedType === 'all'}>
|
||||
<SelectPanel
|
||||
height={300}
|
||||
searchPlaceholder="Search by model name"
|
||||
options={modelList}
|
||||
selectedKeys={allowedModelNames || []}
|
||||
onSelectChange={(selectedKeys) => {
|
||||
form.setFieldsValue({ allowed_model_names: selectedKeys });
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import SealInput from '@/components/seal-form/seal-input';
|
||||
import SealSelect from '@/components/seal-form/seal-select';
|
||||
import { PageAction } from '@/config';
|
||||
import { PageActionType } from '@/config/types';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Form } from 'antd';
|
||||
import React from 'react';
|
||||
import { expirationOptions } from '../../config';
|
||||
import { FormData } from '../../config/types';
|
||||
import { FormData, ListItem } from '../../config/types';
|
||||
import AllowModelsForm from './allow-models';
|
||||
|
||||
const APIKeyForm: React.FC = () => {
|
||||
const APIKeyForm: React.FC<{
|
||||
action: PageActionType;
|
||||
currentData?: Partial<ListItem> | null;
|
||||
}> = ({ action, currentData }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
@@ -27,6 +32,8 @@ const APIKeyForm: React.FC = () => {
|
||||
]}
|
||||
>
|
||||
<SealInput.Input
|
||||
trim
|
||||
disabled={action === PageAction.EDIT}
|
||||
label={intl.formatMessage({ id: 'common.table.name' })}
|
||||
required
|
||||
></SealInput.Input>
|
||||
@@ -48,18 +55,22 @@ const APIKeyForm: React.FC = () => {
|
||||
]}
|
||||
>
|
||||
<SealSelect
|
||||
disabled={action === PageAction.EDIT}
|
||||
options={expirationOptions}
|
||||
label={intl.formatMessage({ id: 'apikeys.form.expiretime' })}
|
||||
required
|
||||
></SealSelect>
|
||||
</Form.Item>
|
||||
<AllowModelsForm></AllowModelsForm>
|
||||
<Form.Item<FormData> name="description" rules={[{ required: false }]}>
|
||||
<SealInput.TextArea
|
||||
scaleSize={true}
|
||||
label={intl.formatMessage({ id: 'common.table.description' })}
|
||||
></SealInput.TextArea>
|
||||
</Form.Item>
|
||||
<AllowModelsForm
|
||||
currentData={currentData}
|
||||
action={action}
|
||||
></AllowModelsForm>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import CopyButton from '@/components/copy-button';
|
||||
import ModalFooter from '@/components/modal-footer';
|
||||
import ScrollerModal from '@/components/scroller-modal';
|
||||
import GSDrawer from '@/components/scroller-modal/gs-drawer';
|
||||
import SealInput from '@/components/seal-form/seal-input';
|
||||
import { PageAction } from '@/config';
|
||||
import { PageActionType } from '@/config/types';
|
||||
import ColumnWrapper from '@/pages/_components/column-wrapper';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button, Form, Tag } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import _ from 'lodash';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { createApisKey, updateApisKey } from '../../apis';
|
||||
import { expirationOptions } from '../../config';
|
||||
import { FormData, ListItem } from '../../config/types';
|
||||
import AllowModelsForm from './allow-models';
|
||||
import APIKeyForm from './form';
|
||||
|
||||
type AddModalProps = {
|
||||
@@ -37,25 +38,6 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
const [apikeyValue, setAPIKeyValue] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const initValues = () => {
|
||||
if (action === PageAction.CREATE && open) {
|
||||
form.setFieldsValue({
|
||||
expires_in: 1
|
||||
});
|
||||
}
|
||||
if (action === PageAction.EDIT && currentData && open) {
|
||||
form.setFieldsValue({
|
||||
name: currentData.name,
|
||||
description: currentData.description,
|
||||
allowed_model_names: currentData.allowed_model_names || []
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
initValues();
|
||||
}, [open]);
|
||||
|
||||
const getExpireValue = (val: number | null) => {
|
||||
const expires_in = val;
|
||||
if (expires_in === -1) {
|
||||
@@ -74,6 +56,28 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
return res;
|
||||
};
|
||||
|
||||
// 7d, 1m, 6m, -1
|
||||
const parseExpireValue = (data: ListItem) => {
|
||||
const createdAt = dayjs(data.created_at);
|
||||
const expiresAt = dayjs(data.expires_at);
|
||||
|
||||
if (!data.expires_at) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const diffInDays = expiresAt.diff(createdAt, 'day');
|
||||
|
||||
if (diffInDays < 10) {
|
||||
return 7;
|
||||
}
|
||||
|
||||
if (diffInDays < 60) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 6;
|
||||
};
|
||||
|
||||
const createAPIKey = async (data: FormData) => {
|
||||
const params = {
|
||||
...data,
|
||||
@@ -89,13 +93,22 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
onOk();
|
||||
};
|
||||
|
||||
const handleOnOk = async (data: FormData) => {
|
||||
const handleOnOk = async (formdata: FormData) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = {
|
||||
..._.omit(formdata, ['allowed_type']),
|
||||
allowed_model_names:
|
||||
formdata.allowed_type === 'all'
|
||||
? []
|
||||
: formdata.allowed_model_names || []
|
||||
};
|
||||
if (action === PageAction.CREATE) {
|
||||
await createAPIKey(data);
|
||||
} else if (action === PageAction.EDIT && currentData?.id) {
|
||||
await updateAPIKey(data);
|
||||
await updateAPIKey({
|
||||
..._.omit(data, ['expires_in'])
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
@@ -115,66 +128,115 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
setShowKey(false);
|
||||
};
|
||||
|
||||
const initValues = () => {
|
||||
if (action === PageAction.CREATE && open) {
|
||||
form.setFieldsValue({
|
||||
expires_in: 1
|
||||
});
|
||||
}
|
||||
if (action === PageAction.EDIT && currentData && open) {
|
||||
parseExpireValue(currentData as ListItem);
|
||||
form.setFieldsValue({
|
||||
name: currentData.name,
|
||||
description: currentData.description,
|
||||
allowed_type: currentData.allowed_model_names?.length
|
||||
? 'custom'
|
||||
: 'all',
|
||||
expires_in: parseExpireValue(currentData as ListItem),
|
||||
allowed_model_names: currentData.allowed_model_names || []
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
initValues();
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<ScrollerModal
|
||||
<GSDrawer
|
||||
title={
|
||||
!showKey ? title : intl.formatMessage({ id: 'apikeys.title.save' })
|
||||
}
|
||||
open={open}
|
||||
centered={true}
|
||||
onOk={handleSumit}
|
||||
onCancel={onCancel}
|
||||
onClose={onCancel}
|
||||
afterOpenChange={handleAfterOpenChange}
|
||||
destroyOnHidden={true}
|
||||
closeIcon={false}
|
||||
maskClosable={false}
|
||||
keyboard={false}
|
||||
width={700}
|
||||
styles={{}}
|
||||
footer={
|
||||
!showKey ? (
|
||||
<ModalFooter
|
||||
onOk={handleSumit}
|
||||
onCancel={onCancel}
|
||||
loading={loading}
|
||||
></ModalFooter>
|
||||
) : (
|
||||
<Button type="primary" onClick={handleDone}>
|
||||
{intl.formatMessage({ id: 'common.button.done' })}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
styles={{
|
||||
body: {
|
||||
height: 'calc(100vh - 57px)',
|
||||
padding: '16px 0',
|
||||
overflowX: 'hidden'
|
||||
},
|
||||
content: {
|
||||
borderRadius: '6px 0 0 6px'
|
||||
}
|
||||
}}
|
||||
width={600}
|
||||
footer={false}
|
||||
>
|
||||
<Form name="addAPIKey" form={form} onFinish={handleOnOk} preserve={false}>
|
||||
{action === PageAction.EDIT && <AllowModelsForm></AllowModelsForm>}
|
||||
{!showKey && action === PageAction.CREATE && <APIKeyForm></APIKeyForm>}
|
||||
{showKey && action === PageAction.CREATE && (
|
||||
<Form.Item>
|
||||
<div>
|
||||
<Tag
|
||||
bordered={false}
|
||||
color="error"
|
||||
style={{ padding: '6px 8px', marginBottom: 16 }}
|
||||
>
|
||||
{intl.formatMessage({ id: 'apikeys.table.save.tips' })}
|
||||
</Tag>
|
||||
</div>
|
||||
<SealInput.Input
|
||||
label={intl.formatMessage({ id: 'apikeys.form.apikey' })}
|
||||
value={apikeyValue}
|
||||
addAfter={
|
||||
<CopyButton
|
||||
text={apikeyValue}
|
||||
shape="default"
|
||||
size="middle"
|
||||
type="text"
|
||||
></CopyButton>
|
||||
}
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form>
|
||||
</ScrollerModal>
|
||||
<ColumnWrapper
|
||||
styles={{
|
||||
container: { paddingTop: 0 }
|
||||
}}
|
||||
footer={
|
||||
!showKey ? (
|
||||
<ModalFooter
|
||||
onOk={handleSumit}
|
||||
onCancel={onCancel}
|
||||
loading={loading}
|
||||
style={{
|
||||
padding: '16px 24px',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end'
|
||||
}}
|
||||
></ModalFooter>
|
||||
) : (
|
||||
<Button type="primary" onClick={handleDone}>
|
||||
{intl.formatMessage({ id: 'common.button.done' })}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
>
|
||||
<Form
|
||||
name="addAPIKey"
|
||||
form={form}
|
||||
onFinish={handleOnOk}
|
||||
preserve={false}
|
||||
>
|
||||
{!showKey && (
|
||||
<APIKeyForm action={action} currentData={currentData}></APIKeyForm>
|
||||
)}
|
||||
{showKey && action === PageAction.CREATE && (
|
||||
<Form.Item>
|
||||
<div>
|
||||
<Tag
|
||||
bordered={false}
|
||||
color="error"
|
||||
style={{ padding: '6px 8px', marginBottom: 16 }}
|
||||
>
|
||||
{intl.formatMessage({ id: 'apikeys.table.save.tips' })}
|
||||
</Tag>
|
||||
</div>
|
||||
<SealInput.Input
|
||||
label={intl.formatMessage({ id: 'apikeys.form.apikey' })}
|
||||
value={apikeyValue}
|
||||
addAfter={
|
||||
<CopyButton
|
||||
text={apikeyValue}
|
||||
shape="default"
|
||||
size="middle"
|
||||
type="text"
|
||||
></CopyButton>
|
||||
}
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form>
|
||||
</ColumnWrapper>
|
||||
</GSDrawer>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ export interface ListItem {
|
||||
|
||||
export interface FormData {
|
||||
name: string;
|
||||
allowed_type: 'all' | 'custom';
|
||||
description: string;
|
||||
allowed_model_names: string[];
|
||||
expires_in: number | null;
|
||||
|
||||
@@ -16,7 +16,7 @@ interface ColumnsHookProps {
|
||||
|
||||
const actionList: Global.ActionItem[] = [
|
||||
{
|
||||
label: 'Edit Allowed Models',
|
||||
label: 'common.button.edit',
|
||||
key: 'edit',
|
||||
icon: icons.EditOutlined
|
||||
},
|
||||
|
||||
@@ -58,7 +58,7 @@ const APIKeys: React.FC = () => {
|
||||
const handleEditKey = (record: ListItem) => {
|
||||
setOpenAddModal({
|
||||
open: true,
|
||||
title: 'Edit Allowed Models',
|
||||
title: 'Edit API Key',
|
||||
action: PageAction.EDIT,
|
||||
currentData: record
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user