fix(styles): providers access token

This commit is contained in:
jialin
2026-02-03 18:21:08 +08:00
parent 26e3a1944b
commit 497813eced
29 changed files with 294 additions and 106 deletions
+6 -2
View File
@@ -77,7 +77,10 @@ const OptionNodes = (props: {
const SealCascader: React.FC< const SealCascader: React.FC<
CascaderAutoProps & CascaderAutoProps &
SealFormItemProps & { optionNode?: React.FC<{ data: any }> } SealFormItemProps & {
alwaysFocus?: boolean;
optionNode?: React.FC<{ data: any }>;
}
> = (props) => { > = (props) => {
const { const {
label, label,
@@ -88,6 +91,7 @@ const SealCascader: React.FC<
options, options,
allowNull, allowNull,
isInFormItems = true, isInFormItems = true,
alwaysFocus = false,
optionNode, optionNode,
notFoundContent, notFoundContent,
tagRender, tagRender,
@@ -167,7 +171,7 @@ const SealCascader: React.FC<
classList={visible ? 'dropdown-visible' : ''} classList={visible ? 'dropdown-visible' : ''}
status={status} status={status}
label={label} label={label}
isFocus={isFocus} isFocus={alwaysFocus || isFocus}
required={required} required={required}
description={description} description={description}
disabled={props.disabled} disabled={props.disabled}
+2
View File
@@ -255,6 +255,8 @@ export default {
'models.catalog.precision': 'Precision', 'models.catalog.precision': 'Precision',
'models.form.gpuPerReplica.tips': 'Enter a custom number', 'models.form.gpuPerReplica.tips': 'Enter a custom number',
'models.form.generic_proxy': 'Enable Generic Proxy', 'models.form.generic_proxy': 'Enable Generic Proxy',
'models.form.enable_model_access': 'Enable Model Access',
'models.form.enable_model_access.tips': 'Enable Model Access',
'models.form.generic_proxy.tips': 'models.form.generic_proxy.tips':
'After enabling the generic proxy, you can access URI paths that do not follow the OpenAI API standard.', 'After enabling the generic proxy, you can access URI paths that do not follow the OpenAI API standard.',
'models.form.generic_proxy.button': 'Generic Proxy', 'models.form.generic_proxy.button': 'Generic Proxy',
+5 -3
View File
@@ -4,13 +4,15 @@ export default {
'providers.table.providerName': 'Provider', 'providers.table.providerName': 'Provider',
'providers.table.models': 'Models', 'providers.table.models': 'Models',
'providers.table.proxy': 'Proxy', 'providers.table.proxy': 'Proxy',
'providers.form.tokens.title': ' API Tokens', 'providers.form.tokens.title': ' API Key',
'providers.form.tokens.add': 'Add API Token', 'providers.form.tokens.add': 'Add API Key',
'providers.form.models.add': 'Add Model', 'providers.form.models.add': 'Add Model',
'providers.form.proxy.enable': 'Enable Proxy', 'providers.form.proxy.enable': 'Enable Proxy',
'providers.form.proxy.url': 'Proxy URL', 'providers.form.proxy.url': 'Proxy URL',
'providers.form.proxy.timeout': 'Proxy Timeout (seconds)', 'providers.form.proxy.timeout': 'Proxy Timeout (seconds)',
'providers.form.customConfig': 'Custom Configuration', 'providers.form.customConfig': 'Custom Configuration',
'providers.form.model.test': 'Test', 'providers.form.model.test': 'Test',
'providers.form.model.test.tips': 'Test Connection' 'providers.form.model.test.tips': 'Test Connection',
'providers.form.endpoint.placeholder': 'provider/models',
'providers.form.fallback.token': 'Fallback API Keys'
}; };
+4
View File
@@ -255,6 +255,8 @@ export default {
'models.catalog.precision': 'Precision', 'models.catalog.precision': 'Precision',
'models.form.gpuPerReplica.tips': 'Enter a custom number', 'models.form.gpuPerReplica.tips': 'Enter a custom number',
'models.form.generic_proxy': 'Enable Generic Proxy', 'models.form.generic_proxy': 'Enable Generic Proxy',
'models.form.enable_model_access': 'Enable Model Access',
'models.form.enable_model_access.tips': 'Enable Model Access',
'models.form.generic_proxy.tips': 'models.form.generic_proxy.tips':
'After enabling the generic proxy, you can access URI paths that do not follow the OpenAI API standard.', 'After enabling the generic proxy, you can access URI paths that do not follow the OpenAI API standard.',
'models.form.generic_proxy.button': 'Generic Proxy', 'models.form.generic_proxy.button': 'Generic Proxy',
@@ -375,4 +377,6 @@ export default {
// 76. 'models.form.maxContextLength': 'Maximum Context Length', // 76. 'models.form.maxContextLength': 'Maximum Context Length',
// 77. 'models.form.backend.helperText': 'Not enabled yet. Will be enabled after deployment. ', // 77. 'models.form.backend.helperText': 'Not enabled yet. Will be enabled after deployment. ',
// 78. 'models.table.instance.benchmark': 'Run Benchmark' // 78. 'models.table.instance.benchmark': 'Run Benchmark'
// 77. 'models.form.enable_model_access': 'Enable Model Access',
// 78. 'models.form.enable_model_access.tips': 'Enable Model Access',
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+5 -3
View File
@@ -4,13 +4,15 @@ export default {
'providers.table.providerName': 'Provider', 'providers.table.providerName': 'Provider',
'providers.table.models': 'Models', 'providers.table.models': 'Models',
'providers.table.proxy': 'Proxy', 'providers.table.proxy': 'Proxy',
'providers.form.tokens.title': ' API Tokens', 'providers.form.tokens.title': ' API Key',
'providers.form.tokens.add': 'Add API Token', 'providers.form.tokens.add': 'Add API Key',
'providers.form.models.add': 'Add Model', 'providers.form.models.add': 'Add Model',
'providers.form.proxy.enable': 'Enable Proxy', 'providers.form.proxy.enable': 'Enable Proxy',
'providers.form.proxy.url': 'Proxy URL', 'providers.form.proxy.url': 'Proxy URL',
'providers.form.proxy.timeout': 'Proxy Timeout (seconds)', 'providers.form.proxy.timeout': 'Proxy Timeout (seconds)',
'providers.form.customConfig': 'Custom Configuration', 'providers.form.customConfig': 'Custom Configuration',
'providers.form.model.test': 'Test', 'providers.form.model.test': 'Test',
'providers.form.model.test.tips': 'Test Connection' 'providers.form.model.test.tips': 'Test Connection',
'providers.form.endpoint.placeholder': 'provider/models',
'providers.form.fallback.token': 'Fallback API Keys'
}; };
+4
View File
@@ -260,6 +260,8 @@ export default {
'models.catalog.precision': 'Точность', 'models.catalog.precision': 'Точность',
'models.form.gpuPerReplica.tips': 'Введите произвольное число', 'models.form.gpuPerReplica.tips': 'Введите произвольное число',
'models.form.generic_proxy': 'Включить универсальный прокси', 'models.form.generic_proxy': 'Включить универсальный прокси',
'models.form.enable_model_access': 'Enable Model Access',
'models.form.enable_model_access.tips': 'Enable Model Access',
'models.form.generic_proxy.tips': 'models.form.generic_proxy.tips':
'После включения универсального прокси вы можете получать доступ к URI-путям, которые не следуют стандарту OpenAI API.', 'После включения универсального прокси вы можете получать доступ к URI-путям, которые не следуют стандарту OpenAI API.',
'models.form.generic_proxy.button': 'Универсальный прокси', 'models.form.generic_proxy.button': 'Универсальный прокси',
@@ -287,4 +289,6 @@ export default {
// 1. 'models.form.maxContextLength': 'Maximum Context Length', // 1. 'models.form.maxContextLength': 'Maximum Context Length',
// 2. 'models.form.backend.helperText': 'Not enabled yet. Will be enabled after deployment. ', // 2. 'models.form.backend.helperText': 'Not enabled yet. Will be enabled after deployment. ',
// 3. 'models.table.instance.benchmark': 'Run Benchmark' // 3. 'models.table.instance.benchmark': 'Run Benchmark'
// 2. 'models.form.enable_model_access': 'Enable Model Access',
// 3. 'models.form.enable_model_access.tips': 'Enable Model Access',
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+5 -3
View File
@@ -4,13 +4,15 @@ export default {
'providers.table.providerName': 'Provider', 'providers.table.providerName': 'Provider',
'providers.table.models': 'Models', 'providers.table.models': 'Models',
'providers.table.proxy': 'Proxy', 'providers.table.proxy': 'Proxy',
'providers.form.tokens.title': ' API Tokens', 'providers.form.tokens.title': ' API Key',
'providers.form.tokens.add': 'Add API Token', 'providers.form.tokens.add': 'Add API Key',
'providers.form.models.add': 'Add Model', 'providers.form.models.add': 'Add Model',
'providers.form.proxy.enable': 'Enable Proxy', 'providers.form.proxy.enable': 'Enable Proxy',
'providers.form.proxy.url': 'Proxy URL', 'providers.form.proxy.url': 'Proxy URL',
'providers.form.proxy.timeout': 'Proxy Timeout (seconds)', 'providers.form.proxy.timeout': 'Proxy Timeout (seconds)',
'providers.form.customConfig': 'Custom Configuration', 'providers.form.customConfig': 'Custom Configuration',
'providers.form.model.test': 'Test', 'providers.form.model.test': 'Test',
'providers.form.model.test.tips': 'Test Connection' 'providers.form.model.test.tips': 'Test Connection',
'providers.form.endpoint.placeholder': 'provider/models',
'providers.form.fallback.token': 'Fallback API Keys'
}; };
+2
View File
@@ -242,6 +242,8 @@ export default {
'models.catalog.precision': '精度', 'models.catalog.precision': '精度',
'models.form.gpuPerReplica.tips': '输入自定义数值', 'models.form.gpuPerReplica.tips': '输入自定义数值',
'models.form.generic_proxy': '启用通用代理', 'models.form.generic_proxy': '启用通用代理',
'models.form.enable_model_access': '启用模型接入',
'models.form.enable_model_access.tips': '启用模型接入',
'models.form.generic_proxy.tips': 'models.form.generic_proxy.tips':
'启用通用代理后可支持访问非 OpenAI-API 标准的 URI 路径。', '启用通用代理后可支持访问非 OpenAI-API 标准的 URI 路径。',
'models.form.generic_proxy.button': '通用代理', 'models.form.generic_proxy.button': '通用代理',
+5 -3
View File
@@ -4,13 +4,15 @@ export default {
'providers.table.providerName': '提供商', 'providers.table.providerName': '提供商',
'providers.table.models': '模型', 'providers.table.models': '模型',
'providers.table.proxy': '代理', 'providers.table.proxy': '代理',
'providers.form.tokens.title': 'API 令牌', 'providers.form.tokens.title': 'API Key',
'providers.form.tokens.add': '添加 API 令牌', 'providers.form.tokens.add': '添加 API Key',
'providers.form.models.add': '添加模型', 'providers.form.models.add': '添加模型',
'providers.form.proxy.enable': '启用代理', 'providers.form.proxy.enable': '启用代理',
'providers.form.proxy.url': '代理地址', 'providers.form.proxy.url': '代理地址',
'providers.form.proxy.timeout': '代理超时(秒)', 'providers.form.proxy.timeout': '代理超时(秒)',
'providers.form.customConfig': '自定义配置', 'providers.form.customConfig': '自定义配置',
'providers.form.model.test': '测试', 'providers.form.model.test': '测试',
'providers.form.model.test.tips': '测试连接' 'providers.form.model.test.tips': '测试连接',
'providers.form.endpoint.placeholder': '提供商/模型',
'providers.form.fallback.token': '备用 API Key'
}; };
+30
View File
@@ -0,0 +1,30 @@
import SealSelect from '@/components/seal-form/seal-select';
import React from 'react';
import { categoryConfig } from './model-tag';
const CategorySelect = (props: any) => {
const { options, ...rest } = props;
const optionRenderer = (option: any) => {
const config = categoryConfig[option.value] || {};
console.log('option:', option, 'config:', config);
return (
<span className="flex-center">
<span style={{ color: config.color }}>
{React.cloneElement(config.icon)}
</span>
<span style={{ marginLeft: 8 }}>{option.label}</span>
</span>
);
};
return (
<SealSelect
options={options}
optionRenderer={optionRenderer}
labelRender={optionRenderer}
{...rest}
/>
);
};
export default CategorySelect;
+3 -2
View File
@@ -3,6 +3,7 @@ import { downloadFile, listFiles, listModels } from '@huggingface/hub';
import { PipelineType } from '@huggingface/tasks'; import { PipelineType } from '@huggingface/tasks';
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import qs from 'query-string'; import qs from 'query-string';
import { ACCESS_API } from '../../model-access/apis';
import { import {
AccessControlFormData, AccessControlFormData,
BackendItem, BackendItem,
@@ -418,7 +419,7 @@ export async function queryBackendList(params?: { cluster_id: number }) {
} }
export async function queryModelAccessUserList(id: number) { export async function queryModelAccessUserList(id: number) {
return request<{ items: UserListItem[] }>(`${MODELS_API}/${id}/access`, { return request<{ items: UserListItem[] }>(`${ACCESS_API}/${id}/access`, {
method: 'GET' method: 'GET'
}); });
} }
@@ -427,7 +428,7 @@ export async function updateModelAccessUser(params: {
id: number; id: number;
data: AccessControlFormData; data: AccessControlFormData;
}) { }) {
return request(`${MODELS_API}/${params.id}/access`, { return request(`${ACCESS_API}/${params.id}/access`, {
method: 'POST', method: 'POST',
data: params.data data: params.data
}); });
@@ -1,7 +1,7 @@
import AlertBlockInfo from '@/components/alert-info/block'; import AlertBlockInfo from '@/components/alert-info/block';
import ModalFooter from '@/components/modal-footer'; import ModalFooter from '@/components/modal-footer';
import ScrollerModal from '@/components/scroller-modal';
import { PageActionType } from '@/config/types'; import { PageActionType } from '@/config/types';
import FormDrawer from '@/pages/_components/form-drawer';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { message } from 'antd'; import { message } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
@@ -75,19 +75,13 @@ const AccessControlModal: React.FC<
}, [open, currentData]); }, [open, currentData]);
return ( return (
<ScrollerModal <FormDrawer
title={title} title={title}
open={open} open={open}
centered={true}
onCancel={onCancel} onCancel={onCancel}
destroyOnHidden={true}
closeIcon={true}
maskClosable={false}
keyboard={false}
width={700} width={700}
height={660}
footer={ footer={
<> <div style={{ paddingInline: 24, paddingBottom: 8 }}>
{isChanged && ( {isChanged && (
<AlertBlockInfo <AlertBlockInfo
type="warning" type="warning"
@@ -99,7 +93,7 @@ const AccessControlModal: React.FC<
></AlertBlockInfo> ></AlertBlockInfo>
)} )}
<ModalFooter onOk={handleSumit} onCancel={onCancel}></ModalFooter> <ModalFooter onOk={handleSumit} onCancel={onCancel}></ModalFooter>
</> </div>
} }
> >
<AccessControlForm <AccessControlForm
@@ -109,7 +103,7 @@ const AccessControlModal: React.FC<
onFinish={handleOnFinish} onFinish={handleOnFinish}
onValuesChange={handleOnValuesChange} onValuesChange={handleOnValuesChange}
/> />
</ScrollerModal> </FormDrawer>
); );
}; };
+7 -29
View File
@@ -1,12 +1,5 @@
import IconFont from '@/components/icon-font';
import { StatusMaps } from '@/config'; import { StatusMaps } from '@/config';
import { import { EditOutlined } from '@ant-design/icons';
AudioOutlined,
EditOutlined,
PictureOutlined,
WechatWorkOutlined
} from '@ant-design/icons';
import React from 'react';
import { backendOptionsMap } from './backend-parameters'; import { backendOptionsMap } from './backend-parameters';
export const backendTipsList = [ export const backendTipsList = [
@@ -299,42 +292,27 @@ export const modelCategoriesMap = {
export const categoryOptions = [ export const categoryOptions = [
{ {
label: 'LLM', label: 'LLM',
value: modelCategoriesMap.llm, value: modelCategoriesMap.llm
icon: React.createElement(WechatWorkOutlined, { style: { color: 'green' } })
}, },
{ {
label: 'Embedding', label: 'Embedding',
value: modelCategoriesMap.embedding, value: modelCategoriesMap.embedding
icon: React.createElement(IconFont, {
type: 'icon-cube',
style: { color: 'magenta' }
})
}, },
{ {
label: 'Reranker', label: 'Reranker',
value: modelCategoriesMap.reranker, value: modelCategoriesMap.reranker
icon: React.createElement(IconFont, {
type: 'icon-rank1',
style: { color: 'cyan' }
})
}, },
{ {
label: 'Image', label: 'Image',
value: modelCategoriesMap.image, value: modelCategoriesMap.image
icon: React.createElement(PictureOutlined, { style: { color: 'orange' } })
}, },
{ {
label: 'Text-to-Speech', label: 'Text-to-Speech',
value: modelCategoriesMap.text_to_speech, value: modelCategoriesMap.text_to_speech
icon: React.createElement(IconFont, {
type: 'icon-sound-wave',
style: { color: 'geekblue' }
})
}, },
{ {
label: 'Speech-to-Text', label: 'Speech-to-Text',
value: modelCategoriesMap.speech_to_text, value: modelCategoriesMap.speech_to_text
icon: React.createElement(AudioOutlined, { style: { color: 'processing' } })
} }
]; ];
+1
View File
@@ -44,6 +44,7 @@ export type SourceType =
export interface FormData { export interface FormData {
image_name?: string; image_name?: string;
run_command?: string; run_command?: string;
enable_model_access?: boolean;
backend: string; backend: string;
restart_on_error?: boolean; restart_on_error?: boolean;
env?: Record<string, any>; env?: Record<string, any>;
+27 -5
View File
@@ -16,8 +16,14 @@ const AdvanceConfig = () => {
const form = Form.useFormInstance(); const form = Form.useFormInstance();
const EnviromentVars = Form.useWatch('env', form); const EnviromentVars = Form.useWatch('env', form);
const backend = Form.useWatch('backend', form); const backend = Form.useWatch('backend', form);
const { onValuesChange, isGGUF, modelContextData, flatBackendOptions } = const modelAccessEnable = Form.useWatch('enable_model_access', form);
useFormContext(); const {
onValuesChange,
backendOptions,
flatBackendOptions,
isGGUF,
modelContextData
} = useFormContext();
const currentBackendOptions = useMemo(() => { const currentBackendOptions = useMemo(() => {
return flatBackendOptions?.find((item) => item.value === backend); return flatBackendOptions?.find((item) => item.value === backend);
@@ -128,19 +134,35 @@ const AdvanceConfig = () => {
></CheckboxField> ></CheckboxField>
</Form.Item> </Form.Item>
<Form.Item<FormData> <Form.Item<FormData>
name="generic_proxy" name="enable_model_access"
valuePropName="checked" valuePropName="checked"
style={{ marginBottom: 8 }} style={{ marginBottom: 8 }}
> >
<CheckboxField <CheckboxField
description={intl.formatMessage({ description={intl.formatMessage({
id: 'models.form.generic_proxy.tips' id: 'models.form.enable_model_access.tips'
})} })}
label={intl.formatMessage({ label={intl.formatMessage({
id: 'models.form.generic_proxy' id: 'models.form.enable_model_access'
})} })}
></CheckboxField> ></CheckboxField>
</Form.Item> </Form.Item>
{modelAccessEnable && (
<Form.Item<FormData>
name="generic_proxy"
valuePropName="checked"
style={{ marginBottom: 8 }}
>
<CheckboxField
description={intl.formatMessage({
id: 'models.form.generic_proxy.tips'
})}
label={intl.formatMessage({
id: 'models.form.generic_proxy'
})}
></CheckboxField>
</Form.Item>
)}
</> </>
); );
}; };
+1
View File
@@ -9,6 +9,7 @@ export interface FormData {
description: string; description: string;
api_tokens: string[]; api_tokens: string[];
models: ProviderModel[]; models: ProviderModel[];
api_key: string;
config: { config: {
type: maasProviderType; type: maasProviderType;
[key: string]: any; [key: string]: any;
+40 -10
View File
@@ -1,4 +1,5 @@
import ListInput from '@/components/list-input'; import MetadataList from '@/components/metadata-list';
import SealInput from '@/components/seal-form/seal-input';
import useAppUtils from '@/hooks/use-app-utils'; import useAppUtils from '@/hooks/use-app-utils';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Form } from 'antd'; import { Form } from 'antd';
@@ -10,17 +11,37 @@ const AccessToken = () => {
const form = Form.useFormInstance<FormData>(); const form = Form.useFormInstance<FormData>();
const tokenList = Form.useWatch('api_tokens', form) || []; const tokenList = Form.useWatch('api_tokens', form) || [];
const handleOnChange = (values: string[]) => { const onAdd = () => {
form.setFieldValue('api_tokens', values); const newList = [...tokenList];
newList.push('');
form.setFieldValue('api_tokens', newList);
};
const onDelete = (index: number) => {
const newList = [...tokenList];
newList.splice(index, 1);
form.setFieldValue('api_tokens', newList);
};
const handleInputChange = (
index: number,
e: React.ChangeEvent<HTMLInputElement>
) => {
const newList = [...tokenList];
newList[index] = e.target.value;
form.setFieldValue('api_tokens', newList);
}; };
return ( return (
<> <>
<Form.Item <Form.Item
name="api_tokens" name="api_tokens"
style={{
marginBottom: 8
}}
rules={[ rules={[
{ {
required: true, required: false,
message: getRuleMessage( message: getRuleMessage(
'input', 'input',
intl.formatMessage({ id: 'providers.form.tokens.title' }) intl.formatMessage({ id: 'providers.form.tokens.title' })
@@ -28,13 +49,22 @@ const AccessToken = () => {
} }
]} ]}
> >
<ListInput <MetadataList
required={true}
btnText={intl.formatMessage({ id: 'providers.form.tokens.add' })}
label={intl.formatMessage({ id: 'providers.form.tokens.title' })}
dataList={tokenList} dataList={tokenList}
onChange={handleOnChange} btnText={intl.formatMessage({ id: 'providers.form.tokens.add' })}
></ListInput> label={intl.formatMessage({ id: 'providers.form.fallback.token' })}
onAdd={onAdd}
onDelete={onDelete}
>
{(item, index) => (
<div style={{ width: '100%' }} key={index}>
<SealInput.Password
value={item}
onChange={(e) => handleInputChange(index, e)}
></SealInput.Password>
</div>
)}
</MetadataList>
</Form.Item> </Form.Item>
</> </>
); );
@@ -4,6 +4,7 @@ import YamlEditor from '@/pages/_components/yaml-editor';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Form } from 'antd'; import { Form } from 'antd';
import React, { forwardRef, useImperativeHandle } from 'react'; import React, { forwardRef, useImperativeHandle } from 'react';
import AccessToken from './access-token';
import ProxyConfig from './proxy-config'; import ProxyConfig from './proxy-config';
const AdvanceConfig: React.FC<{ const AdvanceConfig: React.FC<{
@@ -28,6 +29,7 @@ const AdvanceConfig: React.FC<{
return ( return (
<> <>
<div data-field="advanceConfig"></div> <div data-field="advanceConfig"></div>
<AccessToken></AccessToken>
<ProxyConfig></ProxyConfig> <ProxyConfig></ProxyConfig>
<Form.Item <Form.Item
hidden hidden
+16
View File
@@ -54,6 +54,22 @@ const Basic = () => {
})} })}
/> />
</Form.Item> </Form.Item>
<Form.Item<FormData>
name="api_key"
rules={[
{
required: true,
message: getRuleMessage('input', 'providers.form.tokens.title')
}
]}
>
<SealInput.Password
required
label={intl.formatMessage({
id: 'providers.form.tokens.title'
})}
/>
</Form.Item>
<Form.Item<FormData> name="description"> <Form.Item<FormData> name="description">
<SealInput.TextArea <SealInput.TextArea
scaleSize={true} scaleSize={true}
+15 -3
View File
@@ -6,6 +6,7 @@ import { useWrapperContext } from '@/pages/_components/column-wrapper/use-wrappe
import ScrollSpyTabs from '@/pages/_components/scroll-spy-tabs'; import ScrollSpyTabs from '@/pages/_components/scroll-spy-tabs';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Form } from 'antd'; import { Form } from 'antd';
import _ from 'lodash';
import { import {
forwardRef, forwardRef,
useEffect, useEffect,
@@ -15,7 +16,6 @@ import {
} from 'react'; } from 'react';
import FormContext from '../config/form-context'; import FormContext from '../config/form-context';
import { FormData, MaasProviderItem as ListItem } from '../config/types'; import { FormData, MaasProviderItem as ListItem } from '../config/types';
import AccessToken from './access-token';
import AdvanceConfig from './advance-config'; import AdvanceConfig from './advance-config';
import Basic from './basic'; import Basic from './basic';
import SupportedModels from './supported-models'; import SupportedModels from './supported-models';
@@ -63,6 +63,17 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
} }
]; ];
const handleOnFinish = (values: FormData) => {
const apiTokens = values.api_tokens?.filter?.(
(item) => item && item.trim() !== ''
);
const data = {
..._.omit(values, ['api_key']),
api_tokens: _.concat([], values.api_key, apiTokens || [])
};
onFinish(data);
};
const handleActiveChange = (key: string[]) => { const handleActiveChange = (key: string[]) => {
setActiveKey(key); setActiveKey(key);
}; };
@@ -87,6 +98,8 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
) { ) {
form.setFieldsValue({ form.setFieldsValue({
...currentData, ...currentData,
api_key: currentData.api_tokens?.[0] || '',
api_tokens: currentData.api_tokens?.slice(1) || [],
proxy_enabled: !!currentData.proxy_url proxy_enabled: !!currentData.proxy_url
}); });
} }
@@ -108,7 +121,7 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
<FormContext.Provider value={{ action }}> <FormContext.Provider value={{ action }}>
<Form <Form
form={form} form={form}
onFinish={onFinish} onFinish={handleOnFinish}
initialValues={{ initialValues={{
proxy_enabled: false, proxy_enabled: false,
proxy_url: '', proxy_url: '',
@@ -116,7 +129,6 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
}} }}
> >
<Basic /> <Basic />
<AccessToken />
<CollapsePanel <CollapsePanel
activeKey={activeKey} activeKey={activeKey}
accordion={false} accordion={false}
+1 -1
View File
@@ -48,7 +48,7 @@ const ModelItem: React.FC<ModelItemProps> = ({
const res = await runTestModel({ const res = await runTestModel({
data: { data: {
model_name: item.name, model_name: item.name,
api_token: form.getFieldValue('api_tokens')?.[0] || '', api_token: form.getFieldValue('api_key') || '',
config: { config: {
type: form.getFieldValue(['config', 'type']) || '' type: form.getFieldValue(['config', 'type']) || ''
} }
@@ -14,12 +14,12 @@ const SupportedModels = () => {
const handleOpenChange = async (open: boolean) => { const handleOpenChange = async (open: boolean) => {
try { try {
await form.validateFields(['api_tokens', ['config', 'type']]); await form.validateFields(['api_key']);
if (open && providerModelList.length === 0) { if (open && providerModelList.length === 0) {
fetchProviderModels({ fetchProviderModels({
data: { data: {
api_token: form.getFieldValue('api_tokens')?.[0] || '', api_token: form.getFieldValue('api_key') || '',
config: { config: {
type: form.getFieldValue(['config', 'type']) || '' type: form.getFieldValue(['config', 'type']) || ''
} }
@@ -20,11 +20,13 @@ const CellContent = styled.div`
interface ProviderModelProps { interface ProviderModelProps {
dataList: AccessPointItem[]; dataList: AccessPointItem[];
onSelect: (val: any, record: any) => void; onSelect: (val: any, record: any) => void;
sourceModels: any[];
} }
interface AccessItemProps { interface AccessItemProps {
onSelect: (val: any, record: any) => void; onSelect: (val: any, record: any) => void;
data: any; data: any;
sourceModels: any[];
} }
export const childActionList = [ export const childActionList = [
@@ -48,13 +50,24 @@ export const childActionList = [
} }
]; ];
const AccessItem: React.FC<AccessItemProps> = ({ onSelect, data }) => { const AccessItem: React.FC<AccessItemProps> = ({
onSelect,
data,
sourceModels
}) => {
const intl = useIntl(); const intl = useIntl();
const renderProviderSource = () => {
const model = sourceModels.find(
(item: any) => item.value === data.provider_id || item.id === data.model
);
return model.label || '-';
};
return ( return (
<div style={{ borderRadius: 'var(--ant-table-header-border-radius)' }}> <div style={{ borderRadius: 'var(--ant-table-header-border-radius)' }}>
<RowChildren> <RowChildren>
<Row gutter={16} style={{ width: '100%' }}> <Row gutter={16} style={{ width: '100%' }}>
<Col span={6}> <Col span={4}>
<CellContent <CellContent
style={{ style={{
paddingInline: 'var(--ant-table-cell-padding-inline)' paddingInline: 'var(--ant-table-cell-padding-inline)'
@@ -64,7 +77,7 @@ const AccessItem: React.FC<AccessItemProps> = ({ onSelect, data }) => {
</CellContent> </CellContent>
</Col> </Col>
<Col span={3}> <Col span={3}>
<CellContent>-</CellContent> <CellContent>{renderProviderSource()}</CellContent>
</Col> </Col>
<Col span={4}> <Col span={4}>
<CellContent> <CellContent>
@@ -88,7 +101,7 @@ const AccessItem: React.FC<AccessItemProps> = ({ onSelect, data }) => {
)} )}
</CellContent> </CellContent>
</Col> </Col>
<Col span={3}> <Col span={4}>
<CellContent> <CellContent>
<AutoTooltip ghost> <AutoTooltip ghost>
<StatusTag <StatusTag
@@ -102,16 +115,16 @@ const AccessItem: React.FC<AccessItemProps> = ({ onSelect, data }) => {
</CellContent> </CellContent>
</Col> </Col>
<Col span={5}> <Col span={5}>
<CellContent style={{ marginLeft: -6 }}> <CellContent style={{ paddingLeft: 45 }}>
<AutoTooltip ghost minWidth={20}> <AutoTooltip ghost minWidth={20}>
{dayjs(data.created_at).format('YYYY-MM-DD HH:mm:ss')} {dayjs(data.created_at).format('YYYY-MM-DD HH:mm:ss')}
</AutoTooltip> </AutoTooltip>
</CellContent> </CellContent>
</Col> </Col>
<Col span={3}> <Col span={4}>
<CellContent <CellContent
style={{ style={{
marginLeft: -12 paddingLeft: 38
}} }}
> >
<DropdownButtons <DropdownButtons
@@ -126,12 +139,21 @@ const AccessItem: React.FC<AccessItemProps> = ({ onSelect, data }) => {
); );
}; };
const AccessPoints: React.FC<ProviderModelProps> = ({ dataList, onSelect }) => { const AccessPoints: React.FC<ProviderModelProps> = ({
dataList,
onSelect,
sourceModels
}) => {
console.log('AccessPoints dataList:', dataList); console.log('AccessPoints dataList:', dataList);
return ( return (
<div> <div>
{dataList.map((item, index) => ( {dataList.map((item, index) => (
<AccessItem data={item} key={index} onSelect={onSelect}></AccessItem> <AccessItem
data={item}
key={index}
onSelect={onSelect}
sourceModels={sourceModels}
></AccessItem>
))} ))}
</div> </div>
); );
+1
View File
@@ -3,6 +3,7 @@ export interface FormData {
description: string; description: string;
categories: any[]; categories: any[];
meta: Record<string, any>; meta: Record<string, any>;
generic_proxy: boolean;
fallback_endpoint: { fallback_endpoint: {
provider_model_name?: string; provider_model_name?: string;
model_id?: number; model_id?: number;
+19 -4
View File
@@ -1,6 +1,7 @@
import CheckboxField from '@/components/seal-form/checkbox-field';
import SealInput from '@/components/seal-form/seal-input'; import SealInput from '@/components/seal-form/seal-input';
import SealSelect from '@/components/seal-form/seal-select';
import useAppUtils from '@/hooks/use-app-utils'; import useAppUtils from '@/hooks/use-app-utils';
import CategorySelect from '@/pages/_components/category-select';
import { categoryOptions } from '@/pages/llmodels/config'; import { categoryOptions } from '@/pages/llmodels/config';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Form } from 'antd'; import { Form } from 'antd';
@@ -31,13 +32,13 @@ const Basic = () => {
} }
]} ]}
> >
<SealSelect <CategorySelect
required required
options={categoryOptions} options={categoryOptions}
label={intl.formatMessage({ id: 'models.form.categories' })} label={intl.formatMessage({ id: 'models.form.categories' })}
></SealSelect> ></CategorySelect>
</Form.Item> </Form.Item>
<Form.Item name="description"> <Form.Item name="description" style={{ marginBottom: 8 }}>
<SealInput.TextArea <SealInput.TextArea
scaleSize={true} scaleSize={true}
label={intl.formatMessage({ label={intl.formatMessage({
@@ -45,6 +46,20 @@ const Basic = () => {
})} })}
></SealInput.TextArea> ></SealInput.TextArea>
</Form.Item> </Form.Item>
<Form.Item<FormData>
name="generic_proxy"
valuePropName="checked"
style={{ marginBottom: 8 }}
>
<CheckboxField
description={intl.formatMessage({
id: 'models.form.generic_proxy.tips'
})}
label={intl.formatMessage({
id: 'models.form.generic_proxy'
})}
></CheckboxField>
</Form.Item>
</> </>
); );
}; };
+26 -1
View File
@@ -5,9 +5,29 @@ import { useIntl } from '@umijs/max';
import { Form } from 'antd'; import { Form } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import styled from 'styled-components';
import { FormData } from '../config/types'; import { FormData } from '../config/types';
import useEndpointSourceModels from '../hooks/use-endpoint-source-models'; import useEndpointSourceModels from '../hooks/use-endpoint-source-models';
const Inner = styled.div`
display: flex;
align-items: center;
width: 100%;
ul.ant-cascader-menu:first-child {
li[data-path-key='deployments'] {
position: relative;
&::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: 0;
border-bottom: 1px solid var(--ant-color-split);
}
}
}
`;
const EndpointsForm = forwardRef((props, ref) => { const EndpointsForm = forwardRef((props, ref) => {
const intl = useIntl(); const intl = useIntl();
const { sourceModels, loading, fetchSourceModels } = const { sourceModels, loading, fetchSourceModels } =
@@ -164,6 +184,7 @@ const EndpointsForm = forwardRef((props, ref) => {
showSearch showSearch
expandTrigger="hover" expandTrigger="hover"
multiple={false} multiple={false}
alwaysFocus={true}
onChange={(value, options) => onChange={(value, options) =>
handleEndpointsChange(value, index, options) handleEndpointsChange(value, index, options)
} }
@@ -174,7 +195,7 @@ const EndpointsForm = forwardRef((props, ref) => {
}} }}
maxTagCount={1} maxTagCount={1}
placeholder={intl.formatMessage({ placeholder={intl.formatMessage({
id: 'accesses.form.endpoint.model' id: 'providers.form.endpoint.placeholder'
})} })}
value={item.value} value={item.value}
options={sourceModels} options={sourceModels}
@@ -201,6 +222,7 @@ const EndpointsForm = forwardRef((props, ref) => {
showSearch showSearch
expandTrigger="hover" expandTrigger="hover"
multiple={false} multiple={false}
alwaysFocus={true}
classNames={{ classNames={{
popup: { popup: {
root: 'cascader-popup-wrapper gpu-selector' root: 'cascader-popup-wrapper gpu-selector'
@@ -209,6 +231,9 @@ const EndpointsForm = forwardRef((props, ref) => {
label={intl.formatMessage({ label={intl.formatMessage({
id: 'accesses.form.endpoint.fallback' id: 'accesses.form.endpoint.fallback'
})} })}
placeholder={intl.formatMessage({
id: 'providers.form.endpoint.placeholder'
})}
maxTagCount={1} maxTagCount={1}
value={fallbackValues.value} value={fallbackValues.value}
options={sourceModels} options={sourceModels}
-13
View File
@@ -18,7 +18,6 @@ import { FormData, AccessItem as ListItem } from '../config/types';
import useEditEndpoints from '../hooks/use-edit-endpoints'; import useEditEndpoints from '../hooks/use-edit-endpoints';
import Basic from './basic'; import Basic from './basic';
import Endpoints from './endpoints'; import Endpoints from './endpoints';
import MetaData from './meta-data';
interface ProviderFormProps { interface ProviderFormProps {
ref?: any; ref?: any;
@@ -50,12 +49,6 @@ const AccessForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
icon: <IconFont type="icon-basic" />, icon: <IconFont type="icon-basic" />,
field: 'name' field: 'name'
}, },
{
value: TABKeysMap.METADATA,
label: intl.formatMessage({ id: 'accesses.form.metadata.title' }),
icon: <IconFont type="icon-database" />,
field: 'metaSize'
},
{ {
value: TABKeysMap.ENDPOINTS, value: TABKeysMap.ENDPOINTS,
label: intl.formatMessage({ id: 'accesses.form.endpoint.title' }), label: intl.formatMessage({ id: 'accesses.form.endpoint.title' }),
@@ -202,12 +195,6 @@ const AccessForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
accordion={false} accordion={false}
onChange={handleOnCollapseChange} onChange={handleOnCollapseChange}
items={[ items={[
{
key: TABKeysMap.METADATA,
label: intl.formatMessage({ id: 'accesses.form.metadata.title' }),
forceRender: true,
children: <MetaData></MetaData>
},
{ {
key: TABKeysMap.ENDPOINTS, key: TABKeysMap.ENDPOINTS,
label: intl.formatMessage({ id: 'accesses.form.endpoint.title' }), label: intl.formatMessage({ id: 'accesses.form.endpoint.title' }),
@@ -2,6 +2,7 @@ import { queryModelsList } from '@/pages/llmodels/apis';
import { ListItem as ModelListItem } from '@/pages/llmodels/config/types'; import { ListItem as ModelListItem } from '@/pages/llmodels/config/types';
import { queryMaasProviders } from '@/pages/maas-provider/apis'; import { queryMaasProviders } from '@/pages/maas-provider/apis';
import { MaasProviderItem } from '@/pages/maas-provider/config/types'; import { MaasProviderItem } from '@/pages/maas-provider/config/types';
import { useIntl } from '@umijs/max';
import { useState } from 'react'; import { useState } from 'react';
type EmptyObject = Record<never, never>; type EmptyObject = Record<never, never>;
@@ -15,6 +16,7 @@ type CascaderOption<T extends object = EmptyObject> = {
} & Partial<T>; } & Partial<T>;
const useEndpointSourceModels = () => { const useEndpointSourceModels = () => {
const intl = useIntl();
const [sourceModels, setSourceModels] = useState<CascaderOption[]>([]); const [sourceModels, setSourceModels] = useState<CascaderOption[]>([]);
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false);
@@ -28,7 +30,19 @@ const useEndpointSourceModels = () => {
const modelsList = [ const modelsList = [
{ {
label: 'Deployments', label: (
<span>
{intl.formatMessage({ id: 'menu.models.deployment' })}
<span
style={{
color: 'var(--ant-color-text-tertiary)',
marginLeft: 4
}}
>
[GPUStack]
</span>
</span>
),
value: 'deployments', value: 'deployments',
parent: true, parent: true,
children: models.items?.map?.((model: ModelListItem) => ({ children: models.items?.map?.((model: ModelListItem) => ({
+14 -1
View File
@@ -15,6 +15,7 @@ import { useMemoizedFn } from 'ahooks';
import { message } from 'antd'; import { message } from 'antd';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import _ from 'lodash'; import _ from 'lodash';
import { useEffect } from 'react';
import NoResult from '../_components/no-result'; import NoResult from '../_components/no-result';
import PageBox from '../_components/page-box'; import PageBox from '../_components/page-box';
import AccessControlModal from '../llmodels/components/access-control-modal'; import AccessControlModal from '../llmodels/components/access-control-modal';
@@ -35,6 +36,7 @@ import { FormData, AccessItem as ListItem } from './config/types';
import useAccessColumns from './hooks/use-access-columns'; import useAccessColumns from './hooks/use-access-columns';
import useAccessControl from './hooks/use-access-control'; import useAccessControl from './hooks/use-access-control';
import useCreateAccess from './hooks/use-create-access'; import useCreateAccess from './hooks/use-create-access';
import useEndpointSourceModels from './hooks/use-endpoint-source-models';
const Accesses: React.FC = () => { const Accesses: React.FC = () => {
const { const {
@@ -69,6 +71,7 @@ const Accesses: React.FC = () => {
closeAccessControlModal, closeAccessControlModal,
openAccessControlModalStatus openAccessControlModalStatus
} = useAccessControl(); } = useAccessControl();
const { sourceModels, fetchSourceModels } = useEndpointSourceModels();
const handleClickDropdown = () => { const handleClickDropdown = () => {
openAccessModal( openAccessModal(
@@ -188,9 +191,19 @@ const Accesses: React.FC = () => {
list: any, list: any,
options: { parent?: any; [key: string]: any } options: { parent?: any; [key: string]: any }
) => { ) => {
return <AccessPoints dataList={list} onSelect={onChildSelect} />; return (
<AccessPoints
dataList={list}
onSelect={onChildSelect}
sourceModels={sourceModels}
/>
);
}; };
useEffect(() => {
fetchSourceModels();
}, []);
const columns = useAccessColumns(handleSelect); const columns = useAccessColumns(handleSelect);
return ( return (