From c9ea96e0789a2fb9b293d56f6cee91819492e01e Mon Sep 17 00:00:00 2001 From: jialin Date: Wed, 12 Jun 2024 18:13:23 +0800 Subject: [PATCH] chore: playground api request --- .../seal-table/components/row-children.tsx | 9 ++ .../seal-table/components/table-row.tsx | 31 +++- src/components/seal-table/index.tsx | 14 +- src/components/seal-table/styles/index.less | 2 +- .../seal-table/styles/row-children.less | 13 ++ src/components/seal-table/types.ts | 2 + src/components/type-word-effect/index.tsx | 22 +++ src/config/global.d.ts | 2 +- src/config/index.ts | 8 + src/pages/Resources/components/nodes.tsx | 13 +- .../Resources/components/render-progress.tsx | 24 +-- src/pages/Resources/index.tsx | 6 - src/pages/llmodels/apis/index.ts | 62 +++++++- src/pages/llmodels/components/add-modal.tsx | 13 +- src/pages/llmodels/config/index.ts | 6 + src/pages/llmodels/config/types.ts | 18 +++ src/pages/llmodels/index.tsx | 100 +++++++++++- src/pages/playground/apis/index.ts | 10 ++ .../playground/components/chat-footer.tsx | 17 ++- .../playground/components/ground-left.tsx | 97 ++++++++++-- .../playground/components/message-item.tsx | 72 +++++++-- .../playground/components/params-settings.tsx | 142 +++++++++++++----- .../components/reference-params.tsx | 28 +++- src/pages/playground/config/index.ts | 4 +- src/pages/playground/index.tsx | 26 ++-- 25 files changed, 594 insertions(+), 147 deletions(-) create mode 100644 src/components/seal-table/components/row-children.tsx create mode 100644 src/components/seal-table/styles/row-children.less create mode 100644 src/components/type-word-effect/index.tsx create mode 100644 src/pages/playground/apis/index.ts diff --git a/src/components/seal-table/components/row-children.tsx b/src/components/seal-table/components/row-children.tsx new file mode 100644 index 00000000..a3280092 --- /dev/null +++ b/src/components/seal-table/components/row-children.tsx @@ -0,0 +1,9 @@ +import React from 'react'; +import '../styles/row-children.less'; + +const RowChildren = (props: any) => { + const { children } = props; + return
{children}
; +}; + +export default React.memo(RowChildren); diff --git a/src/components/seal-table/components/table-row.tsx b/src/components/seal-table/components/table-row.tsx index f5b540d7..5158e01a 100644 --- a/src/components/seal-table/components/table-row.tsx +++ b/src/components/seal-table/components/table-row.tsx @@ -1,5 +1,5 @@ import { DownOutlined, RightOutlined } from '@ant-design/icons'; -import { Button, Checkbox, Col, Row } from 'antd'; +import { Button, Checkbox, Col, Empty, Row, Spin } from 'antd'; import classNames from 'classnames'; import _ from 'lodash'; import React, { useEffect, useState } from 'react'; @@ -17,11 +17,15 @@ const TableRow: React.FC< rowSelection, rowKey, columns, - onExpand + onExpand, + renderChildren, + loadChildren } = props; const [expanded, setExpanded] = useState(false); const [checked, setChecked] = useState(false); + const [childrenData, setChildrenData] = useState([]); + const [loading, setLoading] = useState(false); useEffect(() => { if (rowSelection) { @@ -34,9 +38,18 @@ const TableRow: React.FC< } }, [rowSelection]); - const handleRowExpand = () => { - setExpanded(!expanded); - onExpand?.(!expanded, record); + const handleRowExpand = async () => { + try { + setExpanded(!expanded); + onExpand?.(!expanded, record); + setLoading(true); + const data = await loadChildren?.(record); + setChildrenData(data || []); + setLoading(false); + } catch (error) { + setChildrenData([]); + setLoading(false); + } }; const handleSelectChange = (e: any) => { @@ -126,7 +139,13 @@ const TableRow: React.FC< {expanded && (
-
rowchildren
+ + {childrenData.length ? ( + renderChildren?.(childrenData) + ) : ( + + )} +
)} diff --git a/src/components/seal-table/index.tsx b/src/components/seal-table/index.tsx index 63e67e59..94735157 100644 --- a/src/components/seal-table/index.tsx +++ b/src/components/seal-table/index.tsx @@ -8,8 +8,16 @@ import './styles/index.less'; import { SealColumnProps, SealTableProps } from './types'; const SealTable: React.FC = (props) => { - const { children, rowKey, onExpand, loading, expandable, rowSelection } = - props; + const { + children, + rowKey, + onExpand, + loading, + expandable, + rowSelection, + renderChildren, + loadChildren + } = props; const [selectAll, setSelectAll] = useState(false); const [indeterminate, setIndeterminate] = useState(false); @@ -121,6 +129,8 @@ const SealTable: React.FC = (props) => { rowSelection={rowSelection} expandable={expandable} rowKey={rowKey} + renderChildren={renderChildren} + loadChildren={loadChildren} onExpand={onExpand} > ); diff --git a/src/components/seal-table/styles/index.less b/src/components/seal-table/styles/index.less index c80ecef7..642814eb 100644 --- a/src/components/seal-table/styles/index.less +++ b/src/components/seal-table/styles/index.less @@ -22,7 +22,7 @@ } .expanded-row { background-color: var(--color-white-1); - padding: 16px 20px; + padding: 16px 16px; border: 1px solid var(--color-fill-1); border-top: 0; border-radius: 0 0 var(--ant-table-header-border-radius) diff --git a/src/components/seal-table/styles/row-children.less b/src/components/seal-table/styles/row-children.less new file mode 100644 index 00000000..121c4fba --- /dev/null +++ b/src/components/seal-table/styles/row-children.less @@ -0,0 +1,13 @@ +.row-children { + display: flex; + align-items: center; + height: 54px; + padding: 0 16px; + border-radius: var(--ant-table-header-border-radius); + background-color: var(--color-fill-1); + transition: all 0.2s ease; + &:hover { + background-color: var(--ant-table-row-hover-bg); + transition: all 0.2s ease; + } +} diff --git a/src/components/seal-table/types.ts b/src/components/seal-table/types.ts index e544a766..bfdf007b 100644 --- a/src/components/seal-table/types.ts +++ b/src/components/seal-table/types.ts @@ -31,6 +31,8 @@ export interface SealTableProps { dataSource: any[]; loading?: boolean; onExpand?: (expanded: boolean, record: any) => void; + renderChildren?: (data: any) => React.ReactNode; + loadChildren?: (record: any) => Promise; rowKey: string; } diff --git a/src/components/type-word-effect/index.tsx b/src/components/type-word-effect/index.tsx new file mode 100644 index 00000000..275fa057 --- /dev/null +++ b/src/components/type-word-effect/index.tsx @@ -0,0 +1,22 @@ +import { useEffect, useState } from 'react'; + +const TypingEffect: React.FC<{ text?: string }> = ({ text = '' }) => { + const [displayedText, setDisplayedText] = useState(''); + + useEffect(() => { + let index = 0; + const intervalId = setInterval(() => { + setDisplayedText((prev) => prev + text[index]); + index += 1; + if (index === text.length) { + clearInterval(intervalId); + } + }, 20); + + return () => clearInterval(intervalId); + }, [text]); + + return
{displayedText}
; +}; + +export default TypingEffect; diff --git a/src/config/global.d.ts b/src/config/global.d.ts index 07dfdd81..1109008e 100644 --- a/src/config/global.d.ts +++ b/src/config/global.d.ts @@ -1,7 +1,7 @@ declare namespace Global { interface Pagination { page: number; - perPage: number; + perPage?: number; watch?: boolean; } interface PageResponse { diff --git a/src/config/index.ts b/src/config/index.ts index b3a2fb79..ce964099 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -29,3 +29,11 @@ export const StatusColorMap: Record = bg: `var(--ant-color-fill)` } }; + +export const StatusMaps = { + error: 'error', + warning: 'warning', + transitioning: 'transitioning', + success: 'success', + inactive: 'inactive' +}; diff --git a/src/pages/Resources/components/nodes.tsx b/src/pages/Resources/components/nodes.tsx index 4268d2a4..43fbee30 100644 --- a/src/pages/Resources/components/nodes.tsx +++ b/src/pages/Resources/components/nodes.tsx @@ -164,12 +164,21 @@ const Models: React.FC = () => { dataIndex="GPU" key="GPU" render={(text, record: ListItem) => { - return ; + return ( + + ); }} /> { return ; diff --git a/src/pages/Resources/components/render-progress.tsx b/src/pages/Resources/components/render-progress.tsx index 8a6c5d3a..487c6f8c 100644 --- a/src/pages/Resources/components/render-progress.tsx +++ b/src/pages/Resources/components/render-progress.tsx @@ -3,27 +3,7 @@ import { memo, useMemo } from 'react'; const RenderProgress = memo((props: { percent: number }) => { const { percent } = props; - // const { record, dataIndex } = props; - // const value1 = useMemo(() => { - // let value = _.get(record, ['resources', 'allocable', dataIndex]); - // if (['gram', 'memory'].includes(dataIndex)) { - // value = _.toNumber(value.replace(/GiB|Gib/, '')); - // } - // return value; - // }, [record, dataIndex]); - - // const value2 = useMemo(() => { - // let value = _.get(record, ['resources', 'capacity', dataIndex]); - // if (['gram', 'memory'].includes(dataIndex)) { - // value = _.toNumber(value.replace(/GiB|Gib/, '')); - // } - // return value; - // }, [record, dataIndex]); - - // if (!value1 || !value2) { - // return ; - // } - // const percent = _.round(value1 / value2, 2) * 100; + console.log('percent====', percent); const strokeColor = useMemo(() => { if (percent <= 50) { return 'var(--ant-color-primary)'; @@ -35,7 +15,7 @@ const RenderProgress = memo((props: { percent: number }) => { }, [percent]); return ( { return ( {percent}% diff --git a/src/pages/Resources/index.tsx b/src/pages/Resources/index.tsx index 8f2c19cf..84fd96be 100644 --- a/src/pages/Resources/index.tsx +++ b/src/pages/Resources/index.tsx @@ -4,14 +4,8 @@ import { Tabs } from 'antd'; import { useState } from 'react'; import GPUs from './components/gpus'; import Nodes from './components/nodes'; -import Test from './components/test'; const items: TabsProps['items'] = [ - { - key: 'test', - label: 'Test', - children: - }, { key: 'nodes', label: 'Nodes', diff --git a/src/pages/llmodels/apis/index.ts b/src/pages/llmodels/apis/index.ts index 25ac94d4..6c4a5cda 100644 --- a/src/pages/llmodels/apis/index.ts +++ b/src/pages/llmodels/apis/index.ts @@ -1,8 +1,11 @@ import { request } from '@umijs/max'; -import { FormData, ListItem } from '../config/types'; +import { FormData, ListItem, ModelInstanceListItem } from '../config/types'; export const MODELS_API = '/models'; +export const MODEL_INSTANCE_API = '/model_instances'; + +// ===================== Models ===================== export async function queryModelsList( params: Global.Pagination & { query?: string } ) { @@ -31,3 +34,60 @@ export async function updateModel(params: { id: number; data: FormData }) { data: params.data }); } + +export async function queryModelDetail(id: number) { + return request(`${MODELS_API}/${id}`, { + method: 'GET' + }); +} + +// ===================== Model Instances start ===================== + +export async function queryModelInstancesList( + params: Global.Pagination & { query?: string; id: number } +) { + return request>( + `${MODELS_API}/${params.id}/instances`, + { + method: 'GET', + params + } + ); +} + +export async function createModelInstance(params: { data: FormData }) { + return request(`${MODEL_INSTANCE_API}`, { + method: 'POST', + data: params.data + }); +} + +export async function deleteModelInstance(id: number) { + return request(`${MODEL_INSTANCE_API}/${id}`, { + method: 'DELETE' + }); +} + +export async function updateModelInstance(params: { + id: number; + data: FormData; +}) { + return request(`${MODEL_INSTANCE_API}/${params.id}`, { + method: 'PUT', + data: params.data + }); +} + +export async function queryModelInstanceDetail(id: number) { + return request(`${MODEL_INSTANCE_API}/${id}`, { + method: 'GET' + }); +} + +export async function queryModelInstanceLogs(id: number) { + return request(`${MODEL_INSTANCE_API}/${id}/logs`, { + method: 'GET' + }); +} + +// ===================== Model Instances end ===================== diff --git a/src/pages/llmodels/components/add-modal.tsx b/src/pages/llmodels/components/add-modal.tsx index 9dcc9c1e..16135b11 100644 --- a/src/pages/llmodels/components/add-modal.tsx +++ b/src/pages/llmodels/components/add-modal.tsx @@ -47,17 +47,14 @@ const AddModal: React.FC = (props) => { name="huggingface_repo_id" rules={[{ required: true }]} > - + - {/* + name="huggingface_filename" - rules={[{ required: true }]} + rules={[{ required: false }]} > - - */} + + ); }; diff --git a/src/pages/llmodels/config/index.ts b/src/pages/llmodels/config/index.ts index d2e89f59..7754e136 100644 --- a/src/pages/llmodels/config/index.ts +++ b/src/pages/llmodels/config/index.ts @@ -1,3 +1,5 @@ +import { StatusMaps } from '@/config'; + export const modelInstanceCols = [ { title: 'Name', @@ -29,3 +31,7 @@ export const modelInstanceCols = [ key: 'Operation' } ]; + +export const status: any = { + Running: StatusMaps.success +}; diff --git a/src/pages/llmodels/config/types.ts b/src/pages/llmodels/config/types.ts index a8fb243c..9e34b4cd 100644 --- a/src/pages/llmodels/config/types.ts +++ b/src/pages/llmodels/config/types.ts @@ -18,3 +18,21 @@ export interface FormData { name: string; description: string; } + +export interface ModelInstanceListItem { + source: string; + huggingface_repo_id: string; + huggingface_filename: string; + s3_address: string; + node_id: number; + node_ip: string; + pid: number; + port: number; + state: string; + download_progress: number; + model_id: number; + model_name: string; + id: number; + created_at: string; + updated_at: string; +} diff --git a/src/pages/llmodels/index.tsx b/src/pages/llmodels/index.tsx index 9d595339..ca0fb7eb 100644 --- a/src/pages/llmodels/index.tsx +++ b/src/pages/llmodels/index.tsx @@ -1,12 +1,15 @@ import PageTools from '@/components/page-tools'; import SealTable from '@/components/seal-table'; +import RowChildren from '@/components/seal-table/components/row-children'; import SealColumn from '@/components/seal-table/components/seal-column'; +import StatusTag from '@/components/status-tag'; import { PageAction } from '@/config'; import type { PageActionType } from '@/config/types'; import useTableRowSelection from '@/hooks/use-table-row-selection'; import useTableSort from '@/hooks/use-table-sort'; import { DeleteOutlined, + FieldTimeOutlined, PlusOutlined, SyncOutlined, WechatWorkOutlined @@ -16,21 +19,28 @@ import { Access, useAccess, useIntl, useNavigate } from '@umijs/max'; import { App, Button, + Col, Input, Modal, Progress, + Row, Space, - Table, Tooltip, message } from 'antd'; import dayjs from 'dayjs'; import _ from 'lodash'; import { useEffect, useState } from 'react'; -import { createModel, deleteModel, queryModelsList } from './apis'; +import { + createModel, + deleteModel, + deleteModelInstance, + queryModelInstancesList, + queryModelsList +} from './apis'; import AddModal from './components/add-modal'; -import { FormData, ListItem } from './config/types'; -const { Column } = Table; +import { status } from './config'; +import { FormData, ListItem, ModelInstanceListItem } from './config/types'; const Models: React.FC = () => { const { modal } = App.useApp(); @@ -152,7 +162,85 @@ const Models: React.FC = () => { const handleOpenPlayGround = (row: any) => { console.log('handleOpenPlayGround', row); - navigate('/playground'); + navigate(`/playground?model=${row.name}`); + }; + + const handleViewLogs = (row: any) => { + console.log('handleViewLogs', row); + }; + const handleDeleteInstace = (row: any) => { + Modal.confirm({ + title: '', + content: 'Are you sure you want to delete the instance?', + async onOk() { + console.log('OK'); + await deleteModelInstance(row.id); + message.success('successfully!'); + fetchData(); + }, + onCancel() { + console.log('Cancel'); + } + }); + }; + + const getModelInstances = async (row: any) => { + const params = { + id: row.id, + page: 1, + perPage: 100 + }; + const data = await queryModelInstancesList(params); + return data.items || []; + }; + + const renderChildren = (list: any) => { + return ( + <> + {_.map(list, (item: ModelInstanceListItem) => { + return ( + + + + {item.node_ip}:{item.port} + + {item.huggingface_repo_id} + + {dayjs(item.updated_at).format('YYYY-MM-DD HH:mm:ss')} + + + + + + + + + + + + + + + + + ); + })} + + ); }; // request data @@ -217,6 +305,8 @@ const Models: React.FC = () => { rowKey="id" expandable={true} onChange={handleTableChange} + loadChildren={getModelInstances} + renderChildren={renderChildren} pagination={{ showSizeChanger: true, pageSize: queryParams.perPage, diff --git a/src/pages/playground/apis/index.ts b/src/pages/playground/apis/index.ts new file mode 100644 index 00000000..7412b1dc --- /dev/null +++ b/src/pages/playground/apis/index.ts @@ -0,0 +1,10 @@ +import { request } from '@umijs/max'; + +export const CHAT_API = '/chat/completions'; + +export async function execChatCompletions(params: any) { + return request(`${CHAT_API}`, { + method: 'POST', + data: params + }); +} diff --git a/src/pages/playground/components/chat-footer.tsx b/src/pages/playground/components/chat-footer.tsx index 18e74474..0c04007f 100644 --- a/src/pages/playground/components/chat-footer.tsx +++ b/src/pages/playground/components/chat-footer.tsx @@ -12,24 +12,30 @@ interface ChatFooterProps { onClear: () => void; onNewMessage: () => void; onView: () => void; + disabled?: boolean; feedback?: React.ReactNode; } const ChatFooter: React.FC = (props) => { - const { onSubmit, onClear, onNewMessage, onView, feedback } = props; + const { onSubmit, onClear, onNewMessage, onView, feedback, disabled } = props; return (
- @@ -37,10 +43,15 @@ const ChatFooter: React.FC = (props) => { {feedback} -
@@ -51,6 +92,7 @@ const MessageContent: React.FC<{ autoSize={true} variant="filled" onChange={handleMessageChange} + onBlur={handleBlur} >
@@ -66,4 +108,4 @@ const MessageContent: React.FC<{ ); }; -export default MessageContent; +export default memo(MessageItem); diff --git a/src/pages/playground/components/params-settings.tsx b/src/pages/playground/components/params-settings.tsx index b6659350..c9c28528 100644 --- a/src/pages/playground/components/params-settings.tsx +++ b/src/pages/playground/components/params-settings.tsx @@ -2,39 +2,85 @@ import FieldWrapper from '@/components/seal-form/field-wrapper'; import SealInput from '@/components/seal-form/seal-input'; import SealSelect from '@/components/seal-form/seal-select'; import { INPUT_WIDTH } from '@/constants'; +import { queryModelsList } from '@/pages/llmodels/apis'; import { Form, Slider } from 'antd'; -import { useState } from 'react'; +import _ from 'lodash'; +import { useEffect, useState } from 'react'; + +type ParamsSettingsFormProps = { + seed?: number; + stop?: number; + temperature?: number; + top_p?: number; + model?: string; + max_tokens?: number; +}; type ParamsSettingsProps = { - seed?: number; - stopSequence?: number; - temperature?: number; - topP?: number; - model?: string; - maxTokens?: number; + onClose?: () => void; + selectedModel?: string; + 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 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<{ onClose: () => void }> = ({ onClose }) => { - const [ModelList, setModelList] = useState(dataList); +const ParamsSettings: React.FC = ({ + onClose, + selectedModel, + setParams +}) => { + const [ModelList, setModelList] = useState([]); const initialValues = { - seed: 1, - stopSequence: 1, + seed: null, + stop: null, temperature: 1, - topK: 1, - topP: 1, - repeatPenalty: 1, - repeatLastN: 1, - tfsZ: 1, - contextLength: 256, - maxTokens: 256 + top_p: 1, + max_tokens: 1024 }; const [form] = Form.useForm(); + useEffect(() => { + const getModelList = async () => { + try { + const params = { + page: 1, + perPage: 100 + }; + const res = await queryModelsList(params); + const list = _.map(res.items || [], (item: any) => { + return { + value: item.name, + label: item.name + }; + }); + setModelList(list); + form.setFieldsValue({ + model: selectedModel || _.get(list, '[0].value'), + ...initialValues + }); + setParams({ + model: selectedModel || _.get(list, '[0].value'), + ...initialValues + }); + } catch (error) { + setModelList([]); + form.setFieldsValue({ + model: selectedModel || '', + ...initialValues + }); + setParams({ + model: selectedModel || '', + ...initialValues + }); + } + }; + getModelList(); + }, []); + const handleOnFinish = (values: any) => { console.log('handleOnFinish', values); }; @@ -45,61 +91,79 @@ const ParamsSettings: React.FC<{ onClose: () => void }> = ({ onClose }) => { const handleCancel = () => { form.resetFields(); - onClose(); + onClose?.(); + }; + + const handleValuesChange = (changedValues: any, allValues: any) => { + console.log('handleValuesChange', changedValues, allValues); + setParams?.(allValues); }; return (

Model

- + name="model" rules={[{ required: true }]} >

Parameters

- + name="temperature" rules={[{ required: true }]} > - + - - name="maxTokens" + + name="max_tokens" rules={[{ required: true }]} > - + > - - name="topP" + + name="top_p" rules={[{ required: true }]} > - + - + name="seed" rules={[{ required: true }]} > - + > - - name="stopSequence" + + name="stop" rules={[{ required: true }]} > { +interface ReferenceParamsProps { + usage: { + completion_tokens: number; + prompt_tokens: number; + total_tokens: number; + }; +} + +const ReferenceParams = (props: ReferenceParamsProps) => { + const { usage } = props; + if (!usage) { + return null; + } return (
- Inference: 597 ms - - Tokens/s: 561 + + Completion: {usage.completion_tokens} + Prompt: {usage.prompt_tokens} + + } + > + Token Usage: {usage.total_tokens} +
); }; diff --git a/src/pages/playground/config/index.ts b/src/pages/playground/config/index.ts index 3ef6fa46..29368f94 100644 --- a/src/pages/playground/config/index.ts +++ b/src/pages/playground/config/index.ts @@ -1,6 +1,6 @@ export const Roles = { - User: 'User', - Assistant: 'Assistant' + User: 'user', + Assistant: 'assistant' }; export const playGroundRoles = [ { diff --git a/src/pages/playground/index.tsx b/src/pages/playground/index.tsx index e0c62cb5..52605e12 100644 --- a/src/pages/playground/index.tsx +++ b/src/pages/playground/index.tsx @@ -1,24 +1,22 @@ +import { useSearchParams } from '@umijs/max'; import { Divider } from 'antd'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import GroundLeft from './components/ground-left'; import ParamsSettings from './components/params-settings'; import './style/play-ground.less'; const Playground: React.FC = () => { - const [messageList, setMessageList] = useState([]); + const [searchParams] = useSearchParams(); const [selectedModel, setSelectedModel] = useState('llama3:latest'); const [showPopover, setShowPopover] = useState(false); + const selectModel = searchParams.get('model') || ''; + const [params, setParams] = useState({}); + console.log('query======', searchParams, selectModel); const handleSelectChange = (value: string) => { setSelectedModel(value); }; - const getMessageList = () => { - // fetch message list from server - console.log('getModelList'); - setMessageList(['1']); - }; - const handleTogglePopover = () => { setShowPopover(!showPopover); }; @@ -27,20 +25,20 @@ const Playground: React.FC = () => { setShowPopover(false); }; - useEffect(() => { - getMessageList(); - }, [selectedModel]); - return (
- +
- +
);