chore: generic_prox ux

This commit is contained in:
jialin
2025-11-11 20:06:25 +08:00
parent 2d4ddea915
commit 7eedea8035
21 changed files with 274 additions and 83 deletions
@@ -107,9 +107,6 @@ const AddWorkerStep: React.FC<AddModalProps> = ({
return (
<Container>
<Title>
{intl.formatMessage({ id: 'clusters.create.supportedGpu' })}
</Title>
<SupportedGPUs
onSelect={handleSelectProvider}
current={currentGPU}
@@ -58,6 +58,7 @@ import {
SourceType
} from '../config/types';
import useFormInitialValues from '../hooks/use-form-initial-values';
import useGenericProxy from '../hooks/use-generic-proxy';
import useModelsColumns from '../hooks/use-models-columns';
import AccessControlModal from './access-control-modal';
import APIAccessInfoModal from './api-access-info';
@@ -195,6 +196,8 @@ const Models: React.FC<ModelsProps> = ({
});
const modalRef = useRef<any>(null);
const { GenericProxyModal, openProxyModal } = useGenericProxy();
useEffect(() => {
if (deleteIds?.length) {
rowSelection.removeSelectedKey(deleteIds);
@@ -456,6 +459,10 @@ const Models: React.FC<ModelsProps> = ({
handleViewAPIInfo(row);
}
if (val === 'proxy') {
openProxyModal(row);
}
if (val === 'stop') {
modalRef.current?.show({
content: 'models.instances',
@@ -789,6 +796,7 @@ const Models: React.FC<ModelsProps> = ({
currentData={openAccessControlModal.currentData}
action={openAccessControlModal.action}
></AccessControlModal>
{GenericProxyModal}
</>
);
};
+6 -5
View File
@@ -50,11 +50,11 @@ export const ActionList: ActionItem[] = [
key: 'chat',
icon: icons.ExperimentOutlined
},
// {
// label: 'models.form.generic_proxy.button',
// key: 'proxy',
// icon: icons.CaptivePortal
// },
{
label: 'models.form.generic_proxy.button',
key: 'proxy',
icon: icons.CaptivePortal
},
{
label: 'models.table.button.apiAccessInfo',
key: 'api',
@@ -197,6 +197,7 @@ export const modelFileActions = [
export const categoryToPathMap: Record<string, string> = {
[modelCategoriesMap.llm]: '/playground/chat',
[modelCategoriesMap.image]: '/playground/text-to-image',
[modelCategoriesMap.text_to_speech]: '/playground/speech?type=tts',
[modelCategoriesMap.speech_to_text]: '/playground/speech?type=stt',
[modelCategoriesMap.reranker]: '/playground/rerank',
+1
View File
@@ -287,6 +287,7 @@ export const modelCategoriesMap = {
export const categoryOptions = [
{ label: 'LLM', value: modelCategoriesMap.llm },
{ label: 'Image', value: modelCategoriesMap.image },
{ label: 'Embedding', value: modelCategoriesMap.embedding },
{ label: 'Reranker', value: modelCategoriesMap.reranker },
{ label: 'Text-to-Speech', value: modelCategoriesMap.text_to_speech },
+2 -2
View File
@@ -133,7 +133,7 @@ const AdvanceConfig = () => {
})}
></CheckboxField>
</Form.Item>
{/* <Form.Item<FormData>
<Form.Item<FormData>
name="generic_proxy"
valuePropName="checked"
style={{ marginBottom: 8 }}
@@ -146,7 +146,7 @@ const AdvanceConfig = () => {
id: 'models.form.generic_proxy'
})}
></CheckboxField>
</Form.Item> */}
</Form.Item>
</>
);
};
+106 -28
View File
@@ -1,15 +1,62 @@
import ScrollerModal from '@/components/scroller-modal';
import CommandViewer from '@/pages/_components/command-viewer';
import { GPUSTACK_API, OPENAI_COMPATIBLE } from '@/pages/playground/apis';
import {
AUDIO_SPEECH_TO_TEXT_API,
AUDIO_TEXT_TO_SPEECH_API,
CHAT_API,
CREAT_IMAGE_API,
EMBEDDING_API,
MODEL_PROXY,
RERANKER_API
} from '@/pages/playground/apis';
import {
generateSpeechToTextCurlCode,
generateTextToSpeechCurlCode
} from '@/pages/playground/view-code/audio';
import { generateEmbeddingCurlCode } from '@/pages/playground/view-code/embedding';
import { generateImageCurlCode } from '@/pages/playground/view-code/image';
import { generateLLmCurlCode } from '@/pages/playground/view-code/llm';
import { generateRerankCurlCode } from '@/pages/playground/view-code/rerank';
import { formatCurlArgs } from '@/pages/playground/view-code/utils';
import { useIntl } from '@umijs/max';
import { useState } from 'react';
import { modelCategoriesMap } from '../config';
const API_MAP: Record<
string,
{ api: string; generateCurlCode: (args: any) => string }
> = {
[modelCategoriesMap.embedding]: {
api: EMBEDDING_API,
generateCurlCode: generateEmbeddingCurlCode
},
[modelCategoriesMap.llm]: {
api: CHAT_API,
generateCurlCode: generateLLmCurlCode
},
[modelCategoriesMap.image]: {
api: CREAT_IMAGE_API,
generateCurlCode: generateImageCurlCode
},
[modelCategoriesMap.text_to_speech]: {
api: AUDIO_TEXT_TO_SPEECH_API,
generateCurlCode: generateTextToSpeechCurlCode
},
[modelCategoriesMap.speech_to_text]: {
api: AUDIO_SPEECH_TO_TEXT_API,
generateCurlCode: generateSpeechToTextCurlCode
},
[modelCategoriesMap.reranker]: {
api: RERANKER_API,
generateCurlCode: generateRerankCurlCode
}
};
const langOptions = [{ label: 'Curl', value: 'bash' }];
const generateCode = ({ api: url, parameters }: Record<string, any>) => {
const host = window.location.origin;
const api = url.replace(OPENAI_COMPATIBLE, GPUSTACK_API);
const api = '/model/proxy/v1/';
// ========================= Curl =========================
const curlCode = `
@@ -17,6 +64,8 @@ curl ${host}${api} \\
-H "Content-Type: application/json" \\
-H "Authorization: Bearer $\{YOUR_GPUSTACK_API_KEY}" \\
${formatCurlArgs(parameters, false)}`.trim();
return curlCode;
};
const useGenericProxy = () => {
@@ -33,38 +82,67 @@ const useGenericProxy = () => {
});
};
const getModelCategory = (categories: string[]) => {
for (const [category, config] of Object.entries(API_MAP)) {
if (categories.includes(category)) {
return {
category,
api: `${MODEL_PROXY}${config.api}`,
generateCurlCode: config.generateCurlCode
};
}
}
return {
category: modelCategoriesMap.llm,
api: CHAT_API,
generateCurlCode: generateLLmCurlCode
};
};
const openProxyModal = (data?: any) => {
const { api, generateCurlCode } = getModelCategory(data?.categories || []);
setModalStatus({
open: true,
codeValue: data?.generic_proxy_command || ''
codeValue: generateCurlCode({
api,
modelProxy: true,
parameters: {
model: data?.name || ''
}
})
});
};
const GenericProxyModal = () => {
return (
<ScrollerModal
title={intl.formatMessage({
id: 'models.form.generic_title.button'
})}
open={modalStatus.open}
centered={true}
onCancel={onCancel}
destroyOnHidden={true}
closeIcon={true}
maskClosable={false}
keyboard={false}
width={700}
footer={false}
>
<CommandViewer
code={modalStatus.codeValue}
copyText={modalStatus.codeValue}
options={langOptions}
defaultValue={'bash'}
></CommandViewer>
</ScrollerModal>
);
};
const GenericProxyModal = (
<ScrollerModal
title={intl.formatMessage({
id: 'models.form.generic_proxy.button'
})}
open={modalStatus.open}
centered={true}
onCancel={onCancel}
destroyOnHidden={true}
closeIcon={true}
maskClosable={false}
keyboard={false}
width={700}
footer={false}
>
<div
style={{ marginBottom: 8 }}
dangerouslySetInnerHTML={{
__html: intl.formatMessage({ id: 'models.table.genericProxy' })
}}
></div>
<CommandViewer
code={modalStatus.codeValue}
copyText={modalStatus.codeValue}
options={langOptions}
defaultValue={'bash'}
></CommandViewer>
</ScrollerModal>
);
return {
GenericProxyModal,
+4 -1
View File
@@ -1,8 +1,9 @@
import { GPUSTACK_API_BASE_URL } from '@/config/settings';
import { request } from '@umijs/max';
export const OPENAI_COMPATIBLE = 'v1-openai';
export const GPUSTACK_API = 'v1';
export const GPUSTACK_API = GPUSTACK_API_BASE_URL;
export const CHAT_API = `/${OPENAI_COMPATIBLE}/chat/completions`;
@@ -19,6 +20,8 @@ export const AUDIO_TEXT_TO_SPEECH_API = `/${OPENAI_COMPATIBLE}/audio/speech`;
export const AUDIO_SPEECH_TO_TEXT_API = `/${OPENAI_COMPATIBLE}/audio/transcriptions`;
export const MODEL_PROXY = '/model/proxy';
export async function execChatCompletions(params: any) {
return request(`${CHAT_API}`, {
method: 'POST',
@@ -69,7 +69,7 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
width={600}
footer={null}
>
<div style={{ marginBottom: '10px' }}>
<div style={{ marginBottom: '8px' }}>
{intl.formatMessage({ id: 'playground.viewcode.info' })}
</div>
<div>
@@ -81,7 +81,7 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
onChange={handleOnChangeLang}
></CommandViewer>
<div
style={{ marginTop: 10, display: 'flex', alignItems: 'baseline' }}
style={{ marginTop: 8, display: 'flex', alignItems: 'baseline' }}
>
<BulbOutlined className="m-r-8" />
<span>
+33 -4
View File
@@ -1,8 +1,9 @@
import { GPUSTACK_API, OPENAI_COMPATIBLE } from '../apis';
import { fomatNodeJsParams, formatCurlArgs, formatPyParams } from './utils';
export const speechToTextCode = ({
export const generateSpeechToTextCurlCode = ({
api: url,
modelProxy,
parameters
}: Record<string, any>) => {
const host = window.location.origin;
@@ -13,12 +14,26 @@ export const speechToTextCode = ({
const curlCode = `
curl ${host}${api} \\
-H "Content-Type: multipart/form-data" \\
-H "Authorization: Bearer $\{YOUR_GPUSTACK_API_KEY}" \\
-H "Authorization: Bearer $\{YOUR_GPUSTACK_API_KEY}" \\${modelProxy ? `\n-H "X-GPUStack-Model: ${parameters.model}" \\` : ''}
-F file="@/path/to/file/audio.mp3;type=audio/mpeg" \\
${formatCurlArgs(parameters, true)}`
.trim()
.replace(/\\$/g, '');
return curlCode;
};
export const speechToTextCode = ({
api: url,
parameters
}: Record<string, any>) => {
const host = window.location.origin;
// replace url OPENAI_COMPATIBLE with GPUSTACK
const api = url.replace(OPENAI_COMPATIBLE, GPUSTACK_API);
// ========================= Curl =========================
const curlCode = generateSpeechToTextCurlCode({ api: url, parameters });
// ========================= Python =========================
const pythonCode = `
from openai import OpenAI\n
@@ -67,8 +82,9 @@ main();`.trim();
};
};
export const TextToSpeechCode = ({
export const generateTextToSpeechCurlCode = ({
api: url,
modelProxy,
parameters
}: Record<string, any>) => {
const host = window.location.origin;
@@ -78,9 +94,22 @@ export const TextToSpeechCode = ({
const curlCode = `
curl ${host}${api} \\
-H "Content-Type: application/json" \\
-H "Authorization: Bearer $\{YOUR_GPUSTACK_API_KEY}" \\
-H "Authorization: Bearer $\{YOUR_GPUSTACK_API_KEY}" \\${modelProxy ? `\n-H "X-GPUStack-Model: ${parameters.model}" \\` : ''}
${formatCurlArgs(parameters, false)} \\\n--output output.${parameters.response_format}`.trim();
return curlCode;
};
export const TextToSpeechCode = ({
api: url,
parameters
}: Record<string, any>) => {
const host = window.location.origin;
const api = url.replace(OPENAI_COMPATIBLE, GPUSTACK_API);
// ========================= Curl =========================
const curlCode = generateTextToSpeechCurlCode({ api: url, parameters });
// ========================= Python =========================
const pythonCode = `
from pathlib import Path
+16 -2
View File
@@ -1,8 +1,9 @@
import { GPUSTACK_API, OPENAI_COMPATIBLE } from '../apis';
import { fomatNodeJsParams, formatCurlArgs, formatPyParams } from './utils';
export const generateEmbeddingCode = ({
export const generateEmbeddingCurlCode = ({
api: url,
modelProxy,
parameters
}: Record<string, any>) => {
const host = window.location.origin;
@@ -12,9 +13,22 @@ export const generateEmbeddingCode = ({
const curlCode = `
curl ${host}${api} \\
-H "Content-Type: application/json" \\
-H "Authorization: Bearer $\{YOUR_GPUSTACK_API_KEY}" \\
-H "Authorization: Bearer $\{YOUR_GPUSTACK_API_KEY}" \\${modelProxy ? `\n-H "X-GPUStack-Model: ${parameters.model}" \\` : ''}
${formatCurlArgs(parameters, false)}`.trim();
return curlCode;
};
export const generateEmbeddingCode = ({
api: url,
parameters
}: Record<string, any>) => {
const host = window.location.origin;
const api = url.replace(OPENAI_COMPATIBLE, GPUSTACK_API);
// ========================= Curl =========================
const curlCode = generateEmbeddingCurlCode({ api: url, parameters });
// ========================= Python =========================
const pythonCode = `
from openai import OpenAI\n
+38 -17
View File
@@ -2,6 +2,38 @@ import _ from 'lodash';
import { GPUSTACK_API, OPENAI_COMPATIBLE } from '../apis';
import { fomatNodeJsParams, formatCurlArgs, formatPyParams } from './utils';
export const generateImageCurlCode = ({
api: url,
parameters,
modelProxy,
isFormdata = false,
edit = false
}: Record<string, any>) => {
const host = window.location.origin;
const api = url.replace(OPENAI_COMPATIBLE, GPUSTACK_API);
// ========================= Curl =========================
let curlCode = `
curl ${host}${api} \\
-H "Content-Type: application/json" \\
-H "Authorization: Bearer $\{YOUR_GPUSTACK_API_KEY}" \\${modelProxy ? `\n-H "X-GPUStack-Model: ${parameters.model}" \\` : ''}
${formatCurlArgs(parameters, isFormdata)}`.trim();
if (edit) {
curlCode = `
curl ${host}${api} \\
-H "Content-Type: multipart/form-data" \\
-H "Authorization: Bearer $\{YOUR_GPUSTACK_API_KEY}" \\${modelProxy ? `\n-H "X-GPUStack-Model: ${parameters.model}" \\` : ''}
-F image="@image.png" \\
-F mask="@mask.png" \\
${formatCurlArgs(_.omit(parameters, ['mask', 'image']), isFormdata)}`
.trim()
.replace(/\\$/, '');
}
return curlCode;
};
export const generateImageCode = ({
api: url,
parameters,
@@ -12,23 +44,12 @@ export const generateImageCode = ({
const api = url.replace(OPENAI_COMPATIBLE, GPUSTACK_API);
// ========================= Curl =========================
let curlCode = `
curl ${host}${api} \\
-H "Content-Type: application/json" \\
-H "Authorization: Bearer $\{YOUR_GPUSTACK_API_KEY}" \\
${formatCurlArgs(parameters, isFormdata)}`.trim();
if (edit) {
curlCode = `
curl ${host}${api} \\
-H "Content-Type: multipart/form-data" \\
-H "Authorization: Bearer $\{YOUR_GPUSTACK_API_KEY}" \\
-F image="@image.png" \\
-F mask="@mask.png" \\
${formatCurlArgs(_.omit(parameters, ['mask', 'image']), isFormdata)}`
.trim()
.replace(/\\$/, '');
}
let curlCode = generateImageCurlCode({
api: url,
parameters,
isFormdata,
edit
});
// ========================= Python =========================
const pythonCode = `
+16 -2
View File
@@ -1,8 +1,9 @@
import { GPUSTACK_API, OPENAI_COMPATIBLE } from '../apis';
import { fomatNodeJsParams, formatCurlArgs, formatPyParams } from './utils';
export const generateLLMCode = ({
export const generateLLmCurlCode = ({
api: url,
modelProxy,
parameters
}: Record<string, any>) => {
const host = window.location.origin;
@@ -12,9 +13,22 @@ export const generateLLMCode = ({
const curlCode = `
curl ${host}${api} \\
-H "Content-Type: application/json" \\
-H "Authorization: Bearer $\{YOUR_GPUSTACK_API_KEY}" \\
-H "Authorization: Bearer $\{YOUR_GPUSTACK_API_KEY}" \\${modelProxy ? `\n-H "X-GPUStack-Model: ${parameters.model}" \\` : ''}
${formatCurlArgs(parameters, false)}`.trim();
return curlCode;
};
export const generateLLMCode = ({
api: url,
parameters
}: Record<string, any>) => {
const host = window.location.origin;
const api = url.replace(OPENAI_COMPATIBLE, GPUSTACK_API);
// ========================= Curl =========================
const curlCode = generateLLmCurlCode({ api: url, parameters });
// ========================= Python =========================
const pythonCode = `
from openai import OpenAI\n
+15 -2
View File
@@ -1,7 +1,8 @@
import { formatCurlArgs } from './utils';
export const generateRerankCode = ({
export const generateRerankCurlCode = ({
api,
modelProxy,
parameters
}: Record<string, any>) => {
const host = window.location.origin;
@@ -10,9 +11,21 @@ export const generateRerankCode = ({
const curlCode = `
curl ${host}${api} \\
-H "Content-Type: application/json" \\
-H "Authorization: Bearer $\{YOUR_GPUSTACK_API_KEY}" \\
-H "Authorization: Bearer $\{YOUR_GPUSTACK_API_KEY}" \\${modelProxy ? `\n-H "X-GPUStack-Model: ${parameters.model}" \\` : ''}
${formatCurlArgs(parameters, false)}`.trim();
return curlCode;
};
export const generateRerankCode = ({
api,
parameters
}: Record<string, any>) => {
const host = window.location.origin;
// ========================= Curl =========================
const curlCode = generateRerankCurlCode({ api, parameters });
// ========================= Python =========================
const pythonCode = `
import requests\n