chore: model access statement

This commit is contained in:
jialin
2025-10-13 15:21:17 +08:00
parent 663b6c7b4e
commit bb7c8e9c85
18 changed files with 976 additions and 357 deletions
@@ -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>
);
};
+1
View File
@@ -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
},
+1 -1
View File
@@ -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
});