chore: model list ux

This commit is contained in:
jialin
2024-06-13 19:39:39 +08:00
parent c9ea96e078
commit 73c68dee1b
26 changed files with 589 additions and 204 deletions
+4 -2
View File
@@ -8,6 +8,7 @@ interface EditorwrapProps {
children: React.ReactNode;
showHeader?: boolean;
copyText: string;
defaultValue?: string;
langOptions?: { label: string; value: string }[];
onChangeLang?: (value: string) => void;
}
@@ -17,6 +18,7 @@ const EditorWrap: React.FC<EditorwrapProps> = ({
copyText,
langOptions,
onChangeLang,
defaultValue,
showHeader = true
}) => {
const handleChangeLang = (value: string) => {
@@ -30,7 +32,7 @@ const EditorWrap: React.FC<EditorwrapProps> = ({
return (
<div className="editor-header">
<Select
defaultValue="JSON"
defaultValue={defaultValue}
style={{ width: 120 }}
size="middle"
variant="filled"
@@ -51,4 +53,4 @@ const EditorWrap: React.FC<EditorwrapProps> = ({
);
};
export default EditorWrap;
export default React.memo(EditorWrap);
+8
View File
@@ -5,6 +5,8 @@ type FormButtonsProps = {
onCancel?: () => void;
cancelText?: string;
okText?: string;
showOk?: boolean;
showCancel?: boolean;
htmlType?: 'submit' | 'button';
};
const FormButtons: React.FC<FormButtonsProps> = ({
@@ -12,10 +14,13 @@ const FormButtons: React.FC<FormButtonsProps> = ({
onCancel,
cancelText,
okText,
showCancel = true,
showOk = true,
htmlType = 'button'
}) => {
return (
<Space size={40} style={{ marginTop: '80px' }}>
{showOk && (
<Button
type="primary"
onClick={onOk}
@@ -24,9 +29,12 @@ const FormButtons: React.FC<FormButtonsProps> = ({
>
{okText || '保存'}
</Button>
)}
{showCancel && (
<Button onClick={onCancel} style={{ width: '98px' }}>
{cancelText || '取消'}
</Button>
)}
</Space>
);
};
+36
View File
@@ -0,0 +1,36 @@
import { Progress } from 'antd';
import { memo, useMemo } from 'react';
const RenderProgress = memo(
(props: { percent: number; steps?: number; download?: boolean }) => {
const { percent, steps = 5, download } = props;
const strokeColor = useMemo(() => {
if (download) {
return 'var(--ant-color-primary)';
}
if (percent <= 50) {
return 'var(--ant-color-primary)';
}
if (percent <= 80) {
return 'var(--ant-color-warning)';
}
return 'var(--ant-color-error)';
}, [percent]);
return (
<Progress
steps={steps}
format={() => {
return (
<span style={{ color: 'var(--ant-color-text)' }}>{percent}%</span>
);
}}
percent={percent}
strokeColor={strokeColor}
/>
);
}
);
export default RenderProgress;
@@ -0,0 +1,86 @@
import { AutoComplete, Form } from 'antd';
import type { AutoCompleteProps } from 'antd/lib';
import { useEffect, useRef, useState } from 'react';
import Wrapper from './components/wrapper';
import { SealFormItemProps } from './types';
const SealAutoComplete: React.FC<AutoCompleteProps & SealFormItemProps> = (
props
) => {
const {
label,
placeholder,
required,
description,
isInFormItems = true,
...rest
} = props;
const [isFocus, setIsFocus] = useState(false);
const inputRef = useRef<any>(null);
let status = '';
if (isInFormItems) {
const statusData = Form?.Item?.useStatus?.();
status = statusData?.status || '';
}
useEffect(() => {
if (props.value) {
setIsFocus(true);
}
}, [props.value]);
const handleClickWrapper = () => {
if (!props.disabled && !isFocus) {
inputRef.current?.focus?.();
setIsFocus(true);
}
};
const handleChange = (value: string, option: any) => {
props.onChange?.(value, option);
};
const handleOnFocus = (e: any) => {
setIsFocus(true);
props.onFocus?.(e);
};
const handleOnBlur = (e: any) => {
if (!props.value) {
setIsFocus(false);
props.onBlur?.(e);
}
};
const handleSearch = (text: string) => {
props.onSearch?.(text);
};
const handleOnSelect = (value: any, option: any) => {
props.onSelect?.(value, option);
};
return (
<Wrapper
status={status}
label={label || (placeholder as string)}
isFocus={isFocus}
required={required}
description={description}
disabled={props.disabled}
onClick={handleClickWrapper}
>
<AutoComplete
{...rest}
ref={inputRef}
onSelect={handleOnSelect}
onFocus={handleOnFocus}
onBlur={handleOnBlur}
onSearch={handleSearch}
onChange={handleChange}
></AutoComplete>
</Wrapper>
);
};
export default SealAutoComplete;
@@ -5,7 +5,7 @@ import './label-info.less';
interface NoteInfoProps {
required?: boolean;
label: string;
label: React.ReactNode;
description?: React.ReactNode;
}
const NoteInfo: React.FC<NoteInfoProps> = (props) => {
@@ -4,7 +4,7 @@ import LabelInfo from './label-info';
import wrapperStyle from './wrapper.less';
interface WrapperProps {
children: React.ReactNode;
label: string;
label: React.ReactNode;
isFocus: boolean;
status?: string;
required?: boolean;
+2 -1
View File
@@ -1,9 +1,10 @@
import classNames from 'classnames';
import React from 'react';
import LabelInfo from './components/label-info';
import wrapperStyle from './components/wrapper.less';
interface WrapperProps {
children: React.ReactNode;
label: string;
label: React.ReactNode;
status?: string;
className?: string;
disabled?: boolean;
-4
View File
@@ -64,10 +64,6 @@ const SealTextArea: React.FC<TextAreaProps & SealFormItemProps> = (props) => {
const handleOnBlur = useCallback(
(e: any) => {
console.log(
'inputRef.current==========',
inputRef.current?.resizableTextArea?.textArea?.value
);
if (!inputRef.current?.resizableTextArea?.textArea?.value) {
setIsFocus(false);
onBlur?.(e);
+1
View File
@@ -5,5 +5,6 @@
padding: 2px 10px;
border-radius: 20px;
width: fit-content;
height: 26px;
font-size: var(--font-size-base);
}
+18 -7
View File
@@ -1,7 +1,8 @@
import { StatusColorMap } from '@/config';
import { StatusType } from '@/config/types';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import styles from './index.less';
import './index.less';
export const StatusMaps = {
transitioning: 'blue',
@@ -16,9 +17,12 @@ type StatusTagProps = {
status: StatusType;
text: string;
};
download?: {
percent: number;
};
};
const StatusTag: React.FC<StatusTagProps> = ({ statusValue }) => {
const StatusTag: React.FC<StatusTagProps> = ({ statusValue, download }) => {
const { text, status } = statusValue;
const [statusColor, setStatusColor] = useState<{ text: string; bg: string }>({
text: '',
@@ -31,11 +35,18 @@ const StatusTag: React.FC<StatusTagProps> = ({ statusValue }) => {
return (
<span
className={styles['status-tag']}
style={{
color: statusColor.text,
border: `1px solid ${statusColor.text}`
}}
className={classNames('status-tag', { download: download })}
style={
download
? {
color: StatusColorMap['success']['text'],
border: `1px solid ${StatusColorMap['success']['text']}`
}
: {
color: statusColor?.text,
border: `1px solid ${statusColor?.text}`
}
}
>
{text}
</span>
+2 -13
View File
@@ -1,4 +1,5 @@
import PageTools from '@/components/page-tools';
import ProgressBar from '@/components/progress-bar';
import StatusTag from '@/components/status-tag';
import useTableRowSelection from '@/hooks/use-table-row-selection';
import useTableSort from '@/hooks/use-table-sort';
@@ -6,7 +7,6 @@ import { SyncOutlined } from '@ant-design/icons';
import { Button, Input, Space, Table } from 'antd';
import { useState } from 'react';
import { Gpu } from '../config/types';
import RenderProgress from './render-progress';
const { Column } = Table;
const dataSource: Gpu[] = [
@@ -205,18 +205,7 @@ const Models: React.FC = () => {
dataIndex="GRAM"
key="VRAM"
render={(text, record: Gpu) => {
return <RenderProgress percent={0}></RenderProgress>;
}}
/>
<Column
title="Operation"
key="operation"
render={(text, record) => {
return (
<Space>
<Button size="middle">Logs</Button>
</Space>
);
return <ProgressBar percent={0}></ProgressBar>;
}}
/>
</Table>
+8 -19
View File
@@ -1,4 +1,5 @@
import PageTools from '@/components/page-tools';
import ProgressBar from '@/components/progress-bar';
import StatusTag from '@/components/status-tag';
import useTableRowSelection from '@/hooks/use-table-row-selection';
import useTableSort from '@/hooks/use-table-sort';
@@ -8,7 +9,6 @@ import _ from 'lodash';
import { useEffect, useState } from 'react';
import { queryNodesList } from '../apis';
import { ListItem } from '../config/types';
import RenderProgress from './render-progress';
const { Column } = Table;
const Models: React.FC = () => {
@@ -138,9 +138,9 @@ const Models: React.FC = () => {
key="CPU"
render={(text, record: ListItem) => {
return (
<RenderProgress
<ProgressBar
percent={_.round(record?.status?.cpu.utilization_rate, 2)}
></RenderProgress>
></ProgressBar>
);
}}
/>
@@ -150,12 +150,12 @@ const Models: React.FC = () => {
key="Memory"
render={(text, record: ListItem) => {
return (
<RenderProgress
<ProgressBar
percent={formateUtilazation(
record?.status?.memory.used,
record?.status?.memory.total
)}
></RenderProgress>
></ProgressBar>
);
}}
/>
@@ -165,14 +165,14 @@ const Models: React.FC = () => {
key="GPU"
render={(text, record: ListItem) => {
return (
<RenderProgress
<ProgressBar
percent={_.get(record, [
'status',
'gpu',
'0',
'core_utilization_rate'
])}
></RenderProgress>
></ProgressBar>
);
}}
/>
@@ -181,18 +181,7 @@ const Models: React.FC = () => {
dataIndex="VRAM"
key="VRAM"
render={(text, record: ListItem) => {
return <RenderProgress percent={0}></RenderProgress>;
}}
/>
<Column
title="Operation"
key="operation"
render={(text, record) => {
return (
<Space>
<Button size="middle">Logs</Button>
</Space>
);
return <ProgressBar percent={0}></ProgressBar>;
}}
/>
</Table>
@@ -1,30 +0,0 @@
import { Progress } from 'antd';
import { memo, useMemo } from 'react';
const RenderProgress = memo((props: { percent: number }) => {
const { percent } = props;
console.log('percent====', percent);
const strokeColor = useMemo(() => {
if (percent <= 50) {
return 'var(--ant-color-primary)';
}
if (percent <= 80) {
return 'var(--ant-color-warning)';
}
return 'var(--ant-color-error)';
}, [percent]);
return (
<Progress
steps={10}
format={() => {
return (
<span style={{ color: 'var(--ant-color-text)' }}>{percent}%</span>
);
}}
percent={percent}
strokeColor={strokeColor}
/>
);
});
export default RenderProgress;
+23 -2
View File
@@ -1,5 +1,10 @@
import { request } from '@umijs/max';
import { FormData, ListItem, ModelInstanceListItem } from '../config/types';
import {
FormData,
ListItem,
ModelInstanceFormData,
ModelInstanceListItem
} from '../config/types';
export const MODELS_API = '/models';
@@ -55,7 +60,9 @@ export async function queryModelInstancesList(
);
}
export async function createModelInstance(params: { data: FormData }) {
export async function createModelInstance(params: {
data: ModelInstanceFormData;
}) {
return request(`${MODEL_INSTANCE_API}`, {
method: 'POST',
data: params.data
@@ -91,3 +98,17 @@ export async function queryModelInstanceLogs(id: number) {
}
// ===================== Model Instances end =====================
// ===================== call huggingface quicksearch api =====================
export async function callHuggingfaceQuickSearch(params: any) {
return request<{
models: Array<{
id: string;
_id: string;
}>;
}>(`https://huggingface.co/api/quicksearch`, {
method: 'GET',
params
});
}
+82 -7
View File
@@ -1,10 +1,13 @@
import ModalFooter from '@/components/modal-footer';
import SealAutoComplete from '@/components/seal-form/auto-complete';
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 { Form, Modal } from 'antd';
import { useEffect } from 'react';
import _ from 'lodash';
import { useEffect, useState } from 'react';
import { callHuggingfaceQuickSearch } from '../apis';
import { FormData } from '../config/types';
type AddModalProps = {
@@ -17,16 +20,20 @@ type AddModalProps = {
const sourceOptions = [
{ label: 'Huggingface', value: 'huggingface', key: 'huggingface' },
{ label: 'Ollama', value: 'ollama_library', key: 'ollama_library' },
{ label: 'S3', value: 's3', key: 's3' }
];
const AddModal: React.FC<AddModalProps> = (props) => {
const { title, action, open, onOk, onCancel } = props || {};
if (!open) {
return null;
}
const [form] = Form.useForm();
const modelSource = Form.useWatch('source', form);
const [repoOptions, setRepoOptions] = useState<
{ label: string; value: string }[]
>([]);
const [fileOptions, setFileOptions] = useState<
{ label: string; value: string }[]
>([]);
const initFormValue = () => {
if (action === PageAction.CREATE && open) {
@@ -40,6 +47,33 @@ const AddModal: React.FC<AddModalProps> = (props) => {
initFormValue();
}, [open]);
const handleInputRepoChange = (value: string) => {
console.log('repo change', value);
};
const debounceSearch = _.debounce((text: string) => {
handleOnSearchRepo(text);
}, 300);
const handleOnSearchRepo = async (text: string) => {
try {
const params = {
q: text,
type: 'model'
};
const res = await callHuggingfaceQuickSearch(params);
const list = _.map(res.models || [], (item: any) => {
return {
value: item.id,
label: item.id
};
});
setRepoOptions(list);
} catch (error) {
setRepoOptions([]);
}
};
const renderHuggingfaceFields = () => {
return (
<>
@@ -47,11 +81,18 @@ const AddModal: React.FC<AddModalProps> = (props) => {
name="huggingface_repo_id"
rules={[{ required: true }]}
>
<SealInput.Input label="Repo ID" required></SealInput.Input>
<SealAutoComplete
label="Repo ID"
required
showSearch
onChange={handleInputRepoChange}
onSearch={debounceSearch}
options={repoOptions}
></SealAutoComplete>
</Form.Item>
<Form.Item<FormData>
name="huggingface_filename"
rules={[{ required: false }]}
rules={[{ required: true }]}
>
<SealInput.Input label="File Name" required></SealInput.Input>
</Form.Item>
@@ -69,6 +110,32 @@ const AddModal: React.FC<AddModalProps> = (props) => {
);
};
const renderOllamaModelFields = () => {
return (
<>
<Form.Item<FormData>
name="ollama_library_model_name"
rules={[{ required: true }]}
>
<SealInput.Input label="Model Name" required></SealInput.Input>
</Form.Item>
</>
);
};
const renderFieldsBySource = () => {
switch (modelSource) {
case 'huggingface':
return renderHuggingfaceFields();
case 'ollama_library':
return renderOllamaModelFields();
case 's3':
return renderS3Fields();
default:
return null;
}
};
const handleSourceChange = (value: string) => {
console.log('source change', value);
};
@@ -111,7 +178,15 @@ const AddModal: React.FC<AddModalProps> = (props) => {
onChange={handleSourceChange}
></SealSelect>
</Form.Item>
{modelSource === 's3' ? renderS3Fields() : renderHuggingfaceFields()}
{renderFieldsBySource()}
<Form.Item<FormData> name="replicas" rules={[{ required: true }]}>
<SealInput.Number
style={{ width: '100%' }}
label="Replicas"
required
min={1}
></SealInput.Number>
</Form.Item>
<Form.Item<FormData> name="description">
<SealInput.TextArea label="Description"></SealInput.TextArea>
</Form.Item>
@@ -0,0 +1,35 @@
import { Modal } from 'antd';
import React from 'react';
type ViewModalProps = {
content?: string;
title: string;
open: boolean;
onCancel: () => void;
};
const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
const { title, open, onCancel, content } = props || {};
if (!open) {
return null;
}
return (
<Modal
title={title}
open={open}
onCancel={onCancel}
destroyOnClose={true}
closeIcon={true}
maskClosable={false}
keyboard={false}
width={600}
style={{ top: '80px' }}
footer={null}
>
<div>{content}</div>
</Modal>
);
};
export default ViewCodeModal;
+10
View File
@@ -15,7 +15,9 @@ export interface FormData {
huggingface_repo_id: string;
huggingface_filename: string;
s3_address: string;
ollama_library_model_name: 'string';
name: string;
replicas: number;
description: string;
}
@@ -36,3 +38,11 @@ export interface ModelInstanceListItem {
created_at: string;
updated_at: string;
}
export interface ModelInstanceFormData {
model_id: number;
model_name: string;
source: string;
huggingface_repo_id: 'string';
huggingface_filename: 'string';
}
+70 -9
View File
@@ -1,4 +1,5 @@
import PageTools from '@/components/page-tools';
import ProgressBar from '@/components/progress-bar';
import SealTable from '@/components/seal-table';
import RowChildren from '@/components/seal-table/components/row-children';
import SealColumn from '@/components/seal-table/components/seal-column';
@@ -33,12 +34,15 @@ import _ from 'lodash';
import { useEffect, useState } from 'react';
import {
createModel,
createModelInstance,
deleteModel,
deleteModelInstance,
queryModelInstanceLogs,
queryModelInstancesList,
queryModelsList
} from './apis';
import AddModal from './components/add-modal';
import ViewLogsModal from './components/view-logs-modal';
import { status } from './config';
import { FormData, ListItem, ModelInstanceListItem } from './config/types';
@@ -51,6 +55,9 @@ const Models: React.FC = () => {
const { sortOrder, setSortOrder } = useTableSort({
defaultSortOrder: 'descend'
});
const [logContent, setLogContent] = useState('');
const [openLogModal, setOpenLogModal] = useState(false);
const [hoverChildIndex, setHoverChildIndex] = useState(-1);
const [total, setTotal] = useState(100);
const [openAddModal, setOpenAddModal] = useState(false);
const [loading, setLoading] = useState(false);
@@ -132,6 +139,10 @@ const Models: React.FC = () => {
console.log('handleModalCancel');
setOpenAddModal(false);
};
const handleLogModalCancel = () => {
setOpenLogModal(false);
};
const handleDelete = async (row: any) => {
Modal.confirm({
title: '',
@@ -165,8 +176,26 @@ const Models: React.FC = () => {
navigate(`/playground?model=${row.name}`);
};
const handleViewLogs = (row: any) => {
console.log('handleViewLogs', row);
const handleDeployInstance = async (row: any) => {
try {
const data = {
model_id: row.id,
model_name: row.name,
huggingface_repo_id: row.huggingface_repo_id,
huggingface_filename: row.huggingface_filename,
source: row.source
};
await createModelInstance({ data });
message.success('successfully!');
} catch (error) {}
};
const handleViewLogs = async (row: any) => {
try {
const data = await queryModelInstanceLogs(row.id);
setLogContent(data);
setOpenLogModal(true);
} catch (error) {}
};
const handleDeleteInstace = (row: any) => {
Modal.confirm({
@@ -184,6 +213,14 @@ const Models: React.FC = () => {
});
};
const handleOnMouseEnter = (index: number) => {
setHoverChildIndex(index);
};
const handleOnMouseLeave = () => {
setHoverChildIndex(-1);
};
const getModelInstances = async (row: any) => {
const params = {
id: row.id,
@@ -196,28 +233,45 @@ const Models: React.FC = () => {
const renderChildren = (list: any) => {
return (
<>
{_.map(list, (item: ModelInstanceListItem) => {
<Space size={16} direction="vertical" style={{ width: '100%' }}>
{_.map(list, (item: ModelInstanceListItem, index: number) => {
return (
<RowChildren key={item.id}>
<div
key={`${item.id}`}
onMouseEnter={() => handleOnMouseEnter(index)}
onMouseLeave={handleOnMouseLeave}
>
<RowChildren>
<Row style={{ width: '100%' }} align="middle">
<Col span={4}>
{item.node_ip}:{item.port}
</Col>
<Col span={5}>{item.huggingface_repo_id}</Col>
<Col span={5}>
<span>{item.huggingface_repo_id}</span>
<div style={{ marginTop: '4px' }}>
<ProgressBar
download
percent={item.download_progress || 0}
></ProgressBar>
</div>
</Col>
<Col span={4}>
{dayjs(item.updated_at).format('YYYY-MM-DD HH:mm:ss')}
</Col>
<Col span={4}>
{item.state && (
<StatusTag
download={{ percent: 10 }}
statusValue={{
status: status[item.state] as any,
text: item.state
}}
></StatusTag>
)}
</Col>
<Col span={7}>
<Space>
{hoverChildIndex === index && (
<Space size={20}>
<Tooltip title="Delete">
<Button
size="small"
@@ -234,12 +288,14 @@ const Models: React.FC = () => {
></Button>
</Tooltip>
</Space>
)}
</Col>
</Row>
</RowChildren>
</div>
);
})}
</>
</Space>
);
};
@@ -356,7 +412,7 @@ const Models: React.FC = () => {
key="operation"
render={(text, record) => {
return !record.transition ? (
<Space>
<Space size={20}>
<Tooltip title="Open in PlayGround">
<Button
size="small"
@@ -387,6 +443,11 @@ const Models: React.FC = () => {
onCancel={handleModalCancel}
onOk={handleModalOk}
></AddModal>
<ViewLogsModal
title="View Logs"
open={openLogModal}
onCancel={handleLogModalCancel}
></ViewLogsModal>
</>
);
};
@@ -48,7 +48,7 @@ const ChatFooter: React.FC<ChatFooterProps> = (props) => {
onClick={onView}
disabled={disabled}
>
View
View Code
</Button>
<Button
disabled={disabled}
@@ -179,6 +179,9 @@ const MessageList: React.FC<MessageProps> = (props) => {
</div>
<ViewCodeModal
open={show}
systemMessage={systemMessage}
messageList={messageList}
parameters={parameters}
onCancel={handleCloseViewCode}
title="View code"
></ViewCodeModal>
@@ -22,11 +22,6 @@ type ParamsSettingsProps = {
params?: ParamsSettingsFormProps;
setParams: (params: any) => void;
};
// const dataList = [
// { value: 'llama3:latest', label: 'llama3:latest' },
// { value: 'wangfuyun/AnimateLCM', label: 'wangfuyun/AnimateLCM' },
// { value: 'Revanthraja/Text_to_Vision', label: 'Revanthraja/Text_to_Vision' }
// ];
const ParamsSettings: React.FC<ParamsSettingsProps> = ({
onClose,
@@ -126,7 +121,6 @@ const ParamsSettings: React.FC<ParamsSettingsProps> = ({
max={2}
step={0.1}
style={{ marginBottom: 0 }}
tooltip={{ open: true }}
></Slider>
</FieldWrapper>
</Form.Item>
@@ -149,7 +143,6 @@ const ParamsSettings: React.FC<ParamsSettingsProps> = ({
max={1}
step={0.1}
style={{ marginBottom: 0 }}
tooltip={{ open: true }}
></Slider>
</FieldWrapper>
</Form.Item>
@@ -1,50 +1,135 @@
import EditorWrap from '@/components/editor-wrap';
import Editor from '@monaco-editor/react';
import { Modal } from 'antd';
import React, { useRef, useState } from 'react';
import { Modal, Spin } from 'antd';
import _ from 'lodash';
import React, { useEffect, useRef, useState } from 'react';
type ViewModalProps = {
systemMessage?: string;
messageList: any[];
parameters: any;
title: string;
open: boolean;
onCancel: () => void;
};
const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
const { title, open, onCancel } = props || {};
if (!open) {
return null;
}
const {
title,
open,
onCancel,
systemMessage,
messageList,
parameters = {}
} = props || {};
const editorRef = useRef(null);
const [loaded, setLoaded] = useState(false);
const [codeValue, setCodeValue] = useState('');
const [lang, setLang] = useState('json');
const [lang, setLang] = useState('shell');
const langOptions = [
{ label: 'curl', value: 'curl' },
{ label: 'JSON', value: 'json' },
{ label: 'JavaScript', value: 'javascript' },
{ label: 'Python', value: 'python' }
{ label: 'Curl', value: 'shell' },
{ label: 'Python', value: 'python' },
{ label: 'Nodejs', value: 'javascript' }
];
useEffect(() => {
generateCode();
}, [lang, systemMessage, messageList, parameters]);
const generateCode = () => {
if (lang === 'shell') {
const systemList = systemMessage
? [{ role: 'system', content: systemMessage }]
: [];
const code = `curl ${window.location.origin}/v1/chat/completions \n-H "Content-Type: application/json" \n-H "Authorization: Bearer $\{GPUSTACK_API_KEY}\" \n-d '${JSON.stringify(
{
...parameters,
messages: [...systemList, ...messageList]
},
null,
2
)}'`;
setCodeValue(code);
} else if (lang === 'javascript') {
const systemList = systemMessage
? [{ role: 'system', content: systemMessage }]
: [];
const code = `import OpenAI from "openai";\nconst openai = new OpenAI();\n\nasync function main(){\nconst params = ${JSON.stringify(
{
...parameters,
messages: [...systemList, ...messageList]
},
null,
2
)};\nconst chatCompletion = await openai.chat.completions.create(params);\nfor await (const chunk of chatCompletion) {\n process.stdout.write(chunk.choices[0]?.delta?.content || '');\n}\n}\nmain();`;
setCodeValue(code);
} else if (lang === 'python') {
const formattedParams = _.keys(parameters).reduce(
(acc: string, key: string) => {
if (parameters[key] === null) {
return acc;
}
const value =
typeof parameters[key] === 'string'
? `"${parameters[key]}"`
: parameters[key];
return acc + ` ${key}=${value},\n`;
},
''
);
const systemList = systemMessage
? [{ role: 'system', content: systemMessage }]
: [];
const code = `from openai import OpenAI\nclient = OpenAI()\n\ncompletion = client.chat.completions.create(\n${formattedParams} messages=${JSON.stringify([...systemList, ...messageList], null, 2)})\nprint(completion.choices[0].message)`;
setCodeValue(code);
}
formatCode();
};
const handleEditorDidMount = (editor: any, monaco: any) => {
editorRef.current = editor;
setLoaded(true);
};
function formatCode() {
if (editorRef.current) {
setTimeout(() => {
editorRef.current
?.getAction?.('editor.action.formatDocument')
?.run()
.then(() => {
console.log('format success');
});
}, 100);
}
}
const handleOnChangeLang = (value: string) => {
setLang(value);
};
const handleClose = () => {
setLang('shell');
onCancel();
};
const editorConfig = {
minimap: {
enabled: false
},
formatOnType: true,
formatOnPaste: true,
scrollbar: {
verticalSliderSize: 8
}
};
return (
<>
<Modal
title={title}
open={open}
onCancel={onCancel}
onCancel={handleClose}
destroyOnClose={true}
closeIcon={true}
maskClosable={false}
@@ -54,30 +139,35 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
footer={null}
>
<div style={{ marginBottom: '10px' }}>
You can use the following code to start integrating your current prompt
and settings into your application.
You can use the following code to start integrating your current
prompt and settings into your application.
</div>
<Spin spinning={!loaded}>
<EditorWrap
copyText={codeValue}
langOptions={langOptions}
defaultValue="shell"
showHeader={loaded}
onChangeLang={handleOnChangeLang}
>
<Editor
height="400px"
theme="vs-dark"
className="monaco-editor"
defaultLanguage="javascript"
defaultValue="// some comment"
defaultLanguage="shell"
language={lang}
value={codeValue}
options={editorConfig}
onMount={handleEditorDidMount}
/>
</EditorWrap>
</Spin>
<div style={{ marginTop: '10px' }}>
our API Key can be foundhere You should use environment variables or a
secret management tool to expose your key to your applications.
</div>
</Modal>
</>
);
};
+5 -1
View File
@@ -72,7 +72,11 @@ const Profile: React.FC = () => {
style={{ width: INPUT_WIDTH.default }}
></SealInput.Password>
</Form.Item>
<FormButtons htmlType="submit" onCancel={handleCancel}></FormButtons>
<FormButtons
htmlType="submit"
onCancel={handleCancel}
showCancel={false}
></FormButtons>
</Form>
</PageContainer>
</StrictMode>
+3 -2
View File
@@ -214,7 +214,7 @@ const Models: React.FC = () => {
onChange: handlePageChange
}}
>
<Column title="Name" dataIndex="name" key="name" width={400} />
<Column title="Name" dataIndex="name" key="name" width={200} />
<Column
title="Create Time"
dataIndex="created_at"
@@ -243,9 +243,10 @@ const Models: React.FC = () => {
<Column
title="Operation"
key="operation"
width={200}
render={(text, record: ListItem) => {
return (
<Space>
<Space size={20}>
<Tooltip title="编辑">
<Button
size="small"
-4
View File
@@ -1,4 +0,0 @@
// 示例方法,没有实际意义
export function trim(str: string) {
return str.trim();
}
+7
View File
@@ -4,3 +4,10 @@ export const isNotEmptyValue = (value: any) => {
}
return value !== null && value !== undefined && value !== '';
};
export const handleBatchRequest = async (
list: any[],
fn: (args: any) => void
) => {
return Promise.all(list.map((item) => fn(item)));
};