From 3cd96ba2e846a1c02eacb8ee7088e12e43351b37 Mon Sep 17 00:00:00 2001 From: jialin Date: Tue, 24 Sep 2024 20:03:46 +0800 Subject: [PATCH] feat: vllm support --- src/components/copy-button/index.tsx | 2 +- src/components/editor-wrap/index.less | 9 +- src/components/editor-wrap/index.tsx | 8 +- .../highlight-code/code-viewer-dark.tsx | 11 +- .../highlight-code/code-viewer-light.tsx | 11 +- src/components/highlight-code/code-viewer.tsx | 9 +- src/components/highlight-code/index.tsx | 28 +- .../highlight-code/styles/index.less | 1 + src/components/label-selector/label-item.tsx | 2 +- .../label-selector/styles/wrapper.less | 1 + src/components/list-input/hint-input.tsx | 97 +++ src/components/list-input/index.tsx | 109 ++++ src/components/list-input/list-item.tsx | 43 ++ .../list-input/styles/list-item.less | 15 + src/components/markdown-viewer/index.less | 77 +++ src/components/markdown-viewer/index.tsx | 135 +++- .../markdown-viewer/render-rules.ts | 1 + src/components/seal-form/auto-complete.tsx | 6 +- src/config/global.d.ts | 6 + src/global.less | 26 + src/locales/en-US/common.ts | 3 +- src/locales/en-US/models.ts | 4 +- src/locales/zh-CN/common.ts | 3 +- src/locales/zh-CN/models.ts | 4 +- src/pages/llmodels/apis/index.ts | 2 +- .../llmodels/components/advance-config.tsx | 116 +++- .../llmodels/components/column-wrapper.tsx | 4 +- src/pages/llmodels/components/data-form.tsx | 70 ++- .../llmodels/components/deploy-modal.tsx | 28 +- .../llmodels/components/hf-model-file.tsx | 39 +- .../llmodels/components/hf-model-item.tsx | 2 +- src/pages/llmodels/components/model-card.tsx | 51 +- .../llmodels/components/search-model.tsx | 54 +- .../llmodels/components/search-result.tsx | 37 +- src/pages/llmodels/components/table-list.tsx | 4 +- .../llmodels/components/update-modal.tsx | 63 +- src/pages/llmodels/config/index.ts | 24 + src/pages/llmodels/config/llama-config.ts | 26 + src/pages/llmodels/config/types.ts | 2 + src/pages/llmodels/config/vllm-config.ts | 593 ++++++++++++++++++ src/pages/llmodels/style/hf-model-file.less | 2 - src/pages/llmodels/style/title-wrapper.less | 5 + .../playground/components/ground-left.tsx | 10 +- .../playground/components/message-input.tsx | 11 +- .../components/multiple-chat/model-item.tsx | 16 +- .../components/reference-params.tsx | 11 +- .../playground/components/view-code-modal.tsx | 81 +-- 47 files changed, 1610 insertions(+), 252 deletions(-) create mode 100644 src/components/list-input/hint-input.tsx create mode 100644 src/components/list-input/index.tsx create mode 100644 src/components/list-input/list-item.tsx create mode 100644 src/components/list-input/styles/list-item.less create mode 100644 src/components/markdown-viewer/index.less create mode 100644 src/components/markdown-viewer/render-rules.ts create mode 100644 src/pages/llmodels/config/llama-config.ts create mode 100644 src/pages/llmodels/config/vllm-config.ts diff --git a/src/components/copy-button/index.tsx b/src/components/copy-button/index.tsx index 2e02568b..56f9fb9f 100644 --- a/src/components/copy-button/index.tsx +++ b/src/components/copy-button/index.tsx @@ -18,7 +18,7 @@ const CopyButton: React.FC = ({ text, disabled, type = 'text', - shape = 'circle', + shape = 'default', fontSize = '14px', style, size = 'middle' diff --git a/src/components/editor-wrap/index.less b/src/components/editor-wrap/index.less index 3840439f..9f6a8a47 100644 --- a/src/components/editor-wrap/index.less +++ b/src/components/editor-wrap/index.less @@ -1,10 +1,16 @@ .editor-wrap { border-radius: var(--border-radius-mini); overflow: hidden; + font-size: 0; + + .code-pre { + margin-bottom: 0; + } .editor-header { display: flex; - padding: 6px 10px; + height: 40px; + padding: 0 10px; justify-content: space-between; align-items: center; background-color: rgb(56, 56, 56); @@ -12,6 +18,7 @@ .ant-select-selector { background-color: rgba(255, 255, 255, 9%) !important; color: rgba(255, 255, 255, 70%); + border-radius: var(--border-radius-2px); .ant-select-selection-item { color: rgba(255, 255, 255, 70%); diff --git a/src/components/editor-wrap/index.tsx b/src/components/editor-wrap/index.tsx index 80ae3c05..556ad258 100644 --- a/src/components/editor-wrap/index.tsx +++ b/src/components/editor-wrap/index.tsx @@ -10,6 +10,11 @@ interface EditorwrapProps { copyText: string; defaultValue?: string; langOptions?: { label: string; value: string }[]; + styles?: { + wrapper?: React.CSSProperties; + header?: React.CSSProperties; + content?: React.CSSProperties; + }; onChangeLang?: (value: string) => void; } const EditorWrap: React.FC = ({ @@ -19,6 +24,7 @@ const EditorWrap: React.FC = ({ langOptions, onChangeLang, defaultValue, + styles = {}, showHeader = true }) => { const handleChangeLang = (value: string) => { @@ -52,7 +58,7 @@ const EditorWrap: React.FC = ({ return null; }; return ( -
+
{renderHeader()}
{children}
diff --git a/src/components/highlight-code/code-viewer-dark.tsx b/src/components/highlight-code/code-viewer-dark.tsx index 03a8e662..d040b09b 100644 --- a/src/components/highlight-code/code-viewer-dark.tsx +++ b/src/components/highlight-code/code-viewer-dark.tsx @@ -8,12 +8,21 @@ interface CodeViewerProps { autodetect?: boolean; ignoreIllegals?: boolean; copyable?: boolean; + height?: string | number; } const DarkViewer: React.FC = (props) => { - const { code, lang, autodetect, ignoreIllegals, copyable } = props || {}; + const { + code, + lang, + autodetect, + ignoreIllegals, + copyable, + height = 'auto' + } = props || {}; return ( = (props) => { - const { code, lang, autodetect, ignoreIllegals, copyable } = props || {}; + const { + code, + lang, + autodetect, + ignoreIllegals, + copyable, + height = 'auto' + } = props || {}; return ( = (props) => { @@ -18,7 +19,8 @@ const CodeViewer: React.FC = (props) => { lang, autodetect = true, ignoreIllegals = true, - copyable = true + copyable = true, + height = 'auto' } = props || {}; const highlightedCode = useMemo(() => { @@ -58,11 +60,14 @@ const CodeViewer: React.FC = (props) => { return (
        = (props) => {
-  const { code, lang = 'bash', copyable = true, theme = 'dark' } = props;
+  const {
+    code,
+    lang = 'bash',
+    copyable = true,
+    theme = 'dark',
+    height = 'auto'
+  } = props;
 
   return (
-    
+
{theme === 'dark' ? ( - + ) : ( - + )}
); }; -export default HighlightCode; +export default React.memo(HighlightCode); diff --git a/src/components/highlight-code/styles/index.less b/src/components/highlight-code/styles/index.less index 784cb150..a81bb7f1 100644 --- a/src/components/highlight-code/styles/index.less +++ b/src/components/highlight-code/styles/index.less @@ -1,5 +1,6 @@ .high-light-wrapper { text-align: left; + font-size: var(--font-size-base); .hljs { font-weight: var(--font-weight-normal); diff --git a/src/components/label-selector/label-item.tsx b/src/components/label-selector/label-item.tsx index 4caf6b00..3afe4db9 100644 --- a/src/components/label-selector/label-item.tsx +++ b/src/components/label-selector/label-item.tsx @@ -52,7 +52,7 @@ const LabelItem: React.FC = ({ // has duplicate key const duplicates = _.filter( labelList, - (item: Global.BaseListItem) => val && val === item.key + (item: Global.BaseListItem) => val && val === item.key ); if (duplicates.length > 1) { setOpen(true); diff --git a/src/components/label-selector/styles/wrapper.less b/src/components/label-selector/styles/wrapper.less index fb973fad..ad82f3ec 100644 --- a/src/components/label-selector/styles/wrapper.less +++ b/src/components/label-selector/styles/wrapper.less @@ -5,6 +5,7 @@ border: 1px solid var(--ant-color-border); border-radius: var(--border-radius-base); display: flex; + width: 100%; flex-direction: column; :global { diff --git a/src/components/list-input/hint-input.tsx b/src/components/list-input/hint-input.tsx new file mode 100644 index 00000000..e23d51b2 --- /dev/null +++ b/src/components/list-input/hint-input.tsx @@ -0,0 +1,97 @@ +import AutoComplete from '@/components/seal-form/auto-complete'; +import _ from 'lodash'; +import React from 'react'; + +interface HintInputProps { + value: string; + label?: string; + onChange: (value: string) => void; + sourceOptions?: Global.HintOptions[]; +} + +const matchReg = /[^=]+=[^=]*$/; + +const HintInput: React.FC = (props) => { + const { value, label, onChange, sourceOptions } = props; + const cursorPosRef = React.useRef(0); + const contextBeforeCursorRef = React.useRef(''); + const [options, setOptions] = React.useState< + Array> + >([]); + + const generateOptions = (context: string) => { + if (!context) { + setOptions([]); + return; + } + const match = context.match(matchReg); + if (!match) { + const list = _.filter(sourceOptions, (item: Global.HintOptions) => + item.label.includes(context) + ); + setOptions(list); + return; + } + const [key, value] = _.split(match[0], '='); + const data = _.find( + sourceOptions, + (item: Global.HintOptions) => item.label === key + ); + if (!data) { + setOptions([]); + return; + } + const list = _.filter(data.opts, (item: Global.BaseOption) => + item.label.includes(value) + ); + setOptions(list); + }; + + const replaceLastEqual = (value: string) => { + const matchStr = contextBeforeCursorRef.current.match(matchReg); + if (matchStr) { + const arr = _.split(matchStr[0], '='); + onChange(`${arr[0]}=${value}`); + } else { + onChange(value?.trim()); + } + }; + + const getContextBeforeCursor = _.debounce((e: any) => { + cursorPosRef.current = e.target.selectionStart; + contextBeforeCursorRef.current = e.target.value.slice( + 0, + cursorPosRef.current + ); + generateOptions(contextBeforeCursorRef.current); + }, 100); + + const handleInput = (e: any) => { + getContextBeforeCursor(e); + onChange(e.target.value?.trim()); + }; + + const handleOnChange = (value: string) => { + onChange(value?.trim()); + }; + + const handleOnSelect = (value: string) => { + replaceLastEqual(value); + setOptions([]); + }; + + return ( + + ); +}; + +export default React.memo(HintInput); diff --git a/src/components/list-input/index.tsx b/src/components/list-input/index.tsx new file mode 100644 index 00000000..61cfdc47 --- /dev/null +++ b/src/components/list-input/index.tsx @@ -0,0 +1,109 @@ +import { PlusOutlined } from '@ant-design/icons'; +import { useIntl } from '@umijs/max'; +import { Button } from 'antd'; +import _ from 'lodash'; +import React from 'react'; +import Wrapper from '../label-selector/wrapper'; +import ListItem from './list-item'; + +interface ListInputProps { + dataList: string[]; + label: string; + description?: string; + btnText?: string; + options?: Global.HintOptions[]; + onChange: (data: string[]) => void; +} + +const ListInput: React.FC = (props) => { + const intl = useIntl(); + const { dataList, label, description, onChange, btnText, options } = props; + const [list, setList] = React.useState<{ value: string; uid: number }[]>([]); + const countRef = React.useRef(0); + const buttonRef = React.useRef(null); + + const updateCountRef = () => { + countRef.current = countRef.current + 1; + }; + + const handleOnRemove = (index: number) => { + const values = _.cloneDeep(list); + values.splice(index, 1); + const valueList = _.map(values, 'value').filter((val: string) => !!val); + setList(values); + onChange(valueList); + }; + + const handleOnChange = (value: string, index: number) => { + const values = _.cloneDeep(list); + values[index].value = value; + const valueList = _.map(values, 'value').filter((val: string) => !!val); + setList(values); + onChange(valueList); + }; + + const handleOnAdd = () => { + updateCountRef(); + const values = _.cloneDeep(list); + values.push({ + value: '', + uid: countRef.current + }); + setList(values); + setTimeout(() => { + buttonRef.current?.scrollIntoView?.({ behavior: 'smooth' }); + }, 100); + }; + + React.useEffect(() => { + const valueList = _.map(list, 'value').filter((val: string) => !!val); + if (!_.isEqual(valueList, dataList)) { + const values = _.map(dataList, (value: string) => { + updateCountRef(); + return { + value, + uid: countRef.current + }; + }); + setList(values); + } + }, [dataList]); + + return ( + + <> + {_.map(list, (item: any, index: number) => { + return ( + handleOnRemove(index)} + onChange={(val) => handleOnChange(val, index)} + /> + ); + })} +
+ +
+ +
+ ); +}; + +export default React.memo(ListInput); diff --git a/src/components/list-input/list-item.tsx b/src/components/list-input/list-item.tsx new file mode 100644 index 00000000..196f4a7e --- /dev/null +++ b/src/components/list-input/list-item.tsx @@ -0,0 +1,43 @@ +// import AutoComplete from '@/components/seal-form/auto-complete'; +import { MinusOutlined } from '@ant-design/icons'; +import { Button } from 'antd'; +import React from 'react'; +import HintInput from './hint-input'; +import './styles/list-item.less'; + +interface LabelItemProps { + onRemove: () => void; + onChange: (value: string) => void; + value: string; + label?: string; + options?: Global.HintOptions[]; +} + +const ListItem: React.FC = (props) => { + const { onRemove, onChange, label, value, options } = props; + + const handleOnChange = (value: any) => { + onChange(value); + }; + + return ( +
+ +
+ ); +}; + +export default React.memo(ListItem); diff --git a/src/components/list-input/styles/list-item.less b/src/components/list-input/styles/list-item.less new file mode 100644 index 00000000..3eb71d85 --- /dev/null +++ b/src/components/list-input/styles/list-item.less @@ -0,0 +1,15 @@ +.list-item { + display: flex; + align-items: center; + justify-content: flex-start; + width: 100%; + margin-bottom: 12px; + + .field-wrapper { + flex: 1; + } + + .btn { + margin-left: 10px; + } +} diff --git a/src/components/markdown-viewer/index.less b/src/components/markdown-viewer/index.less new file mode 100644 index 00000000..2725b418 --- /dev/null +++ b/src/components/markdown-viewer/index.less @@ -0,0 +1,77 @@ +.markdown-viewer { + font-family: var(--font-family) !important; + white-space: pre-wrap; + + .hr { + border: none; + height: 1px; + background-color: var(--ant-color-split); + } + + p { + margin-bottom: 0; + } + + h1, + h2, + h3, + h4 { + font-weight: 700; + font-weight: var(--font-weight-bold); + font-size: 14px; + } + + .hj-wrapper { + margin-top: 16px; + } + + strong { + font-weight: var(--font-weight-bold); + } + + ul { + margin-bottom: 0; + padding-left: 20px; + line-height: 2; + } + + ol { + margin-bottom: 0; + padding-left: 20px; + line-height: 2; + } + + table { + width: 100%; + + th { + text-align: left; + font-weight: var(--font-weight-bold); + line-height: 2; + padding-inline: 6px; + border-bottom: 1px solid var(--ant-color-split); + } + + td { + line-height: 2; + padding-inline: 6px; + border-bottom: 1px solid var(--ant-color-split); + } + + div { + display: inline; + } + } + + a { + div { + display: inline; + } + } + + .item-token { + color: inherit; + font-size: inherit; + font-weight: inherit; + } +} diff --git a/src/components/markdown-viewer/index.tsx b/src/components/markdown-viewer/index.tsx index 45e31fc9..7f889df0 100644 --- a/src/components/markdown-viewer/index.tsx +++ b/src/components/markdown-viewer/index.tsx @@ -1,26 +1,143 @@ -import { marked, Tokens } from 'marked'; -import React from 'react'; +import { EyeOutlined } from '@ant-design/icons'; +import { Image, Typography } from 'antd'; +import { TokensList, marked } from 'marked'; +import React, { Fragment } from 'react'; +import HighlightCode from '../highlight-code'; +import './index.less'; + +const { Text, Link, Paragraph } = Typography; interface MarkdownViewerProps { content: string; height?: string; + theme?: 'light' | 'dark'; } const MarkdownViewer: React.FC = ({ content, - height = 'auto' + height = 'auto', + theme = 'light' }) => { const renderer = new marked.Renderer(); + const tokens = marked.lexer(content); + const reDefineTypes = [ + 'code', + 'link', + 'hr', + 'heading', + 'paragraph', + 'codespan', + 'strong', + 'text', + 'image', + 'em', + 'list', + 'list_item', + 'br', + 'html' + ]; - renderer.link = ({ href, title, text }: Tokens.Link) => { - return `${text}`; + const renderItem = (token: any, render: any) => { + // console.log('token======66==', token.raw, token.type); + if (!reDefineTypes.includes(token.type)) { + return ( + + ); + } + let htmlstr: any = null; + let child: any = null; + if (token.tokens?.length) { + child = render?.(token.tokens as TokensList, render); + } + const text = child ? child : token.text; + + if (token.type === 'html') { + htmlstr =
; + } + if (token.type === 'list') { + htmlstr = token.order ? ( +
    {render?.(token.items, render)}
+ ) : ( +
    {render?.(token.items, render)}
+ ); + } + + if (token.type === 'list_item') { + htmlstr =
  • {text}
  • ; + } + + if (token.type === 'br') { + htmlstr =
    ; + } + + if (token.type === 'em') { + htmlstr = {text}; + } + + if (token.type === 'image') { + htmlstr = ( + + }} + /> + ); + } + if (token.type === 'text') { + htmlstr = text; + } + if (token.type === 'codespan') { + htmlstr = {text}; + } + if (token.type === 'strong') { + htmlstr = {text}; + } + if (token.type === 'heading') { + htmlstr = {text}; + } + if (token.type === 'paragraph') { + htmlstr = {text}; + } + if (token.type === 'code') { + htmlstr = ( + + ); + } + if (token.type === 'link') { + htmlstr = ( + + {text} + + ); + } + if (token.type === 'hr') { + htmlstr =
    ; + } + + return htmlstr; + }; + const renderTokens = (tokens: TokensList): any => { + return tokens?.map((token: any, index: number) => { + return {renderItem(token, renderTokens)}; + }); }; return ( -
    -
    +
    + {renderTokens(tokens)}
    ); }; diff --git a/src/components/markdown-viewer/render-rules.ts b/src/components/markdown-viewer/render-rules.ts new file mode 100644 index 00000000..ff8b4c56 --- /dev/null +++ b/src/components/markdown-viewer/render-rules.ts @@ -0,0 +1 @@ +export default {}; diff --git a/src/components/seal-form/auto-complete.tsx b/src/components/seal-form/auto-complete.tsx index cded4e42..dd8269c4 100644 --- a/src/components/seal-form/auto-complete.tsx +++ b/src/components/seal-form/auto-complete.tsx @@ -4,9 +4,9 @@ import { useEffect, useRef, useState } from 'react'; import Wrapper from './components/wrapper'; import { SealFormItemProps } from './types'; -const SealAutoComplete: React.FC = ( - props -) => { +const SealAutoComplete: React.FC< + AutoCompleteProps & SealFormItemProps & { onInput: (e: Event) => void } +> = (props) => { const { label, placeholder, diff --git a/src/config/global.d.ts b/src/config/global.d.ts index 74334eb4..b16d08fc 100644 --- a/src/config/global.d.ts +++ b/src/config/global.d.ts @@ -32,5 +32,11 @@ declare namespace Global { value: T; } + interface HintOptions { + label: string; + value: string; + opts?: Array>; + } + type SearchParams = Pagination & { search?: string }; } diff --git a/src/global.less b/src/global.less index 31492b74..f33e2148 100644 --- a/src/global.less +++ b/src/global.less @@ -2,6 +2,7 @@ @import url('src/assets/styles/menu.less'); html { + --font-family: 'noto sans', sans-serif; --ant-color-text-secondary: rgba(0, 0, 0, 65%); --ant-color-text-tertiary: rgba(0, 0, 0, 45%); --ant-color-text-quaternary: rgba(0, 0, 0, 25%); @@ -12,6 +13,8 @@ html { --color-text-light-1: rgba(255, 255, 255, 90%); --color-fill-1: var(--ant-color-fill-tertiary); --color-scrollbar-thumb: rgba(193, 193, 193, 80%); + --color-editor-dark: #282c34; + --color-editor-light: #fafafa; --color-scrollbar-track: var(--ant-color-fill-tertiary); // --color-fill-1: #fff; --ant-color-text: #000; @@ -29,6 +32,7 @@ html { --border-radius-small: 4px; --border-radius-mdium: 4px; --border-radius-mini: 4px; + --border-radius-2px: 2px; --color-white-1: rgba(255, 255, 255, 100%); --color-fill-sider: #f4f5f4; --font-weight-normal: 500; @@ -491,6 +495,28 @@ body { } } +.custom-scrollbar-horizontal { + &::-webkit-scrollbar { + height: 8px; + } + + &::-webkit-scrollbar-thumb { + background-color: transparent; + border-radius: 4px; + } + + &::-webkit-scrollbar-track { + background-color: transparent; + } + + &:hover { + &::-webkit-scrollbar-thumb { + background-color: var(--color-scrollbar-thumb); + border-radius: 4px; + } + } +} + .ant-message-notice-wrapper { .ant-message-notice-error { .ant-message-notice-content { diff --git a/src/locales/en-US/common.ts b/src/locales/en-US/common.ts index 4af69947..8f47a163 100644 --- a/src/locales/en-US/common.ts +++ b/src/locales/en-US/common.ts @@ -205,5 +205,6 @@ export default { 'common.button.version': 'Version', 'common.title.delete.confirm': 'Confirm delete', 'common.button.addLabel': 'Add Labels', - 'common.button.addSelector': 'Add Selectors' + 'common.button.addSelector': 'Add Selectors', + 'common.button.addParams': 'Add Parameter' }; diff --git a/src/locales/en-US/models.ts b/src/locales/en-US/models.ts index 04756781..a49f0f86 100644 --- a/src/locales/en-US/models.ts +++ b/src/locales/en-US/models.ts @@ -55,5 +55,7 @@ export default { 'models.table.backend': 'Backends', 'models.table.acrossworker': 'Distribution Across Workers', 'models.table.cpuoffload': 'CPU Offload', - 'models.table.layers': 'Layers' + 'models.table.layers': 'Layers', + 'models.form.backend': 'Backend', + 'models.form.backend_parameters': 'Backend Parameters' }; diff --git a/src/locales/zh-CN/common.ts b/src/locales/zh-CN/common.ts index c5255e0d..4822487c 100644 --- a/src/locales/zh-CN/common.ts +++ b/src/locales/zh-CN/common.ts @@ -198,5 +198,6 @@ export default { 'common.button.version': '版本', 'common.title.delete.confirm': '确认删除', 'common.button.addLabel': '添加标签', - 'common.button.addSelector': '添加选择器' + 'common.button.addSelector': '添加选择器', + 'common.button.addParams': '添加参数' }; diff --git a/src/locales/zh-CN/models.ts b/src/locales/zh-CN/models.ts index 24030620..0b403d29 100644 --- a/src/locales/zh-CN/models.ts +++ b/src/locales/zh-CN/models.ts @@ -54,5 +54,7 @@ export default { 'models.table.backend': '后端', 'models.table.acrossworker': '跨 worker 推理', 'models.table.cpuoffload': 'CPU 卸载', - 'models.table.layers': '层' + 'models.table.layers': '层', + 'models.form.backend': '后端', + 'models.form.backend_parameters': '后端参数' }; diff --git a/src/pages/llmodels/apis/index.ts b/src/pages/llmodels/apis/index.ts index 5ac34da3..a1956d3c 100644 --- a/src/pages/llmodels/apis/index.ts +++ b/src/pages/llmodels/apis/index.ts @@ -165,7 +165,7 @@ export async function queryModelScopeModels( }, body: JSON.stringify({ ...params, - Name: `${params.Name} gguf`, + Name: `${params.Name}`, PageSize: 100, PageNumber: 1 }) diff --git a/src/pages/llmodels/components/advance-config.tsx b/src/pages/llmodels/components/advance-config.tsx index 7ac670c4..3f23623c 100644 --- a/src/pages/llmodels/components/advance-config.tsx +++ b/src/pages/llmodels/components/advance-config.tsx @@ -1,5 +1,8 @@ import LabelSelector from '@/components/label-selector'; +import ListInput from '@/components/list-input'; import SealSelect from '@/components/seal-form/seal-select'; +import { PageAction } from '@/config'; +import { PageActionType } from '@/config/types'; import { InfoCircleOutlined, RightOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; import { @@ -13,22 +16,28 @@ import { } from 'antd'; import _ from 'lodash'; import React, { useCallback, useMemo } from 'react'; -import { placementStrategyOptions } from '../config'; +import { backendOptionsMap, placementStrategyOptions } from '../config'; +import llamaConfig from '../config/llama-config'; import { FormData } from '../config/types'; +import vllmConfig from '../config/vllm-config'; import dataformStyles from '../style/data-form.less'; import GPUCard from './gpu-card'; interface AdvanceConfigProps { + isGGUF: boolean; form: FormInstance; gpuOptions: Array; + action: PageActionType; } const AdvanceConfig: React.FC = (props) => { - const { form, gpuOptions } = props; + const { form, gpuOptions, isGGUF, action } = props; const intl = useIntl(); const wokerSelector = Form.useWatch('worker_selector', form); const scheduleType = Form.useWatch('scheduleType', form); + const backend = Form.useWatch('backend', form); + const [params, setParams] = React.useState([]); const placementStrategyTips = [ { @@ -64,6 +73,10 @@ const AdvanceConfig: React.FC = (props) => { } ]; + const paramsConfig = useMemo(() => { + return backend === backendOptionsMap.llamaBox ? llamaConfig : vllmConfig; + }, [backend]); + const renderSelectTips = (list: Array<{ title: string; tips: string }>) => { return (
    @@ -96,6 +109,10 @@ const AdvanceConfig: React.FC = (props) => { [] ); + const handleBackendParametersChange = useCallback((list: string[]) => { + form.setFieldValue('backend_parameters', list); + }, []); + const collapseItems = useMemo(() => { const children = ( <> @@ -176,6 +193,33 @@ const AdvanceConfig: React.FC = (props) => { )} + + + + name="backend_parameters"> + + {scheduleType === 'manual' && ( name="gpu_selector" @@ -202,34 +246,36 @@ const AdvanceConfig: React.FC = (props) => { )} -
    - - name="cpu_offloading" - valuePropName="checked" - style={{ padding: '0 10px', marginBottom: 0 }} - noStyle - > - - - - {intl.formatMessage({ - id: 'resources.form.enablePartialOffload' + {isGGUF && ( +
    + + name="cpu_offloading" + valuePropName="checked" + style={{ padding: '0 10px', marginBottom: 0 }} + noStyle + > + + - - - - -
    - {scheduleType === 'auto' && ( + > + + {intl.formatMessage({ + id: 'resources.form.enablePartialOffload' + })} + + +
    +
    + +
    + )} + {scheduleType === 'auto' && isGGUF && (
    name="distributed_inference_across_workers" @@ -270,7 +316,15 @@ const AdvanceConfig: React.FC = (props) => { children } ]; - }, [form, intl, gpuOptions, scheduleType, wokerSelector]); + }, [ + form, + intl, + gpuOptions, + paramsConfig, + scheduleType, + wokerSelector, + isGGUF + ]); return ( = (props) => { ); }; -export default AdvanceConfig; +export default React.memo(AdvanceConfig); diff --git a/src/pages/llmodels/components/column-wrapper.tsx b/src/pages/llmodels/components/column-wrapper.tsx index 368eebba..ce080028 100644 --- a/src/pages/llmodels/components/column-wrapper.tsx +++ b/src/pages/llmodels/components/column-wrapper.tsx @@ -10,7 +10,7 @@ const ColumnWrapper: React.FC = ({ children, footer, height }) => {
    @@ -23,7 +23,7 @@ const ColumnWrapper: React.FC = ({ children, footer, height }) => { } return (
    - + {children}
    diff --git a/src/pages/llmodels/components/data-form.tsx b/src/pages/llmodels/components/data-form.tsx index 91f9790d..7e14d0e9 100644 --- a/src/pages/llmodels/components/data-form.tsx +++ b/src/pages/llmodels/components/data-form.tsx @@ -14,7 +14,11 @@ import React, { useState } from 'react'; import { queryGPUList } from '../apis'; -import { modelSourceMap, ollamaModelOptions } from '../config'; +import { + backendOptionsMap, + modelSourceMap, + ollamaModelOptions +} from '../config'; import { FormData, GPUListItem } from '../config/types'; import AdvanceConfig from './advance-config'; @@ -23,6 +27,7 @@ interface DataFormProps { source: string; action: PageActionType; selectedModel: any; + isGGUF: boolean; onOk: (values: FormData) => void; } @@ -49,7 +54,7 @@ const SEARCH_SOURCE = [ ]; const DataForm: React.FC = forwardRef((props, ref) => { - const { action, onOk } = props; + const { action, isGGUF, onOk } = props; const [form] = Form.useForm(); const intl = useIntl(); const [gpuOptions, setGpuOptions] = useState< @@ -140,27 +145,29 @@ const DataForm: React.FC = forwardRef((props, ref) => { disabled={true} > - - name="file_name" - key="file_name" - rules={[ - { - required: true, - message: intl.formatMessage( - { - id: 'common.form.rule.input' - }, - { name: intl.formatMessage({ id: 'models.form.filename' }) } - ) - } - ]} - > - - + {isGGUF && ( + + name="file_name" + key="file_name" + rules={[ + { + required: true, + message: intl.formatMessage( + { + id: 'common.form.rule.input' + }, + { name: intl.formatMessage({ id: 'models.form.filename' }) } + ) + } + ]} + > + + + )} ); }; @@ -238,7 +245,7 @@ const DataForm: React.FC = forwardRef((props, ref) => { } return null; - }, [props.source]); + }, [props.source, isGGUF]); const handleOk = (formdata: FormData) => { const gpu = _.find(gpuOptions, (item: any) => { @@ -260,6 +267,14 @@ const DataForm: React.FC = forwardRef((props, ref) => { } }; + useEffect(() => { + if (action === PageAction.CREATE) { + form.setFieldValue( + 'backend', + isGGUF ? backendOptionsMap.llamaBox : backendOptionsMap.vllm + ); + } + }, [isGGUF]); useEffect(() => { handleOnSelectModel(); }, [props.selectedModel.name]); @@ -360,7 +375,12 @@ const DataForm: React.FC = forwardRef((props, ref) => { })} > - + ); }); diff --git a/src/pages/llmodels/components/deploy-modal.tsx b/src/pages/llmodels/components/deploy-modal.tsx index 88d622f5..33b03b8e 100644 --- a/src/pages/llmodels/components/deploy-modal.tsx +++ b/src/pages/llmodels/components/deploy-modal.tsx @@ -45,6 +45,8 @@ const AddModal: React.FC = (props) => { const [selectedModel, setSelectedModel] = useState({}); const [collapsed, setCollapsed] = useState(false); const [loadingModel, setLoadingModel] = useState(false); + const [isGGUF, setIsGGUF] = useState(false); + const modelFileRef = useRef(null); const handleSelectModelFile = useCallback((item: any) => { form.current?.setFieldValue?.('file_name', item.fakeName); @@ -58,6 +60,15 @@ const AddModal: React.FC = (props) => { form.current?.submit?.(); }; + const handleSetIsGGUF = (flag: boolean) => { + setIsGGUF(flag); + if (flag) { + setTimeout(() => { + modelFileRef.current?.fetchModelFiles?.(); + }, 50); + } + }; + useEffect(() => { return () => { setSelectedModel({}); @@ -121,13 +132,17 @@ const AddModal: React.FC = (props) => { onCollapse={setCollapsed} collapsed={collapsed} modelSource={props.source} + setIsGGUF={handleSetIsGGUF} > - + {isGGUF && ( + + )}
    @@ -159,6 +174,7 @@ const AddModal: React.FC = (props) => { selectedModel={selectedModel} onOk={onOk} ref={form} + isGGUF={isGGUF} > diff --git a/src/pages/llmodels/components/hf-model-file.tsx b/src/pages/llmodels/components/hf-model-file.tsx index 0cab7803..db940f8c 100644 --- a/src/pages/llmodels/components/hf-model-file.tsx +++ b/src/pages/llmodels/components/hf-model-file.tsx @@ -4,7 +4,15 @@ import { useIntl } from '@umijs/max'; import { Col, Empty, Row, Select, Spin, Tag, Tooltip } from 'antd'; import classNames from 'classnames'; import _ from 'lodash'; -import { memo, useCallback, useEffect, useRef, useState } from 'react'; +import { + forwardRef, + memo, + useCallback, + useEffect, + useImperativeHandle, + useRef, + useState +} from 'react'; import SimpleBar from 'simplebar-react'; import 'simplebar-react/dist/simplebar.min.css'; import { queryHuggingfaceModelFiles, queryModelScopeModelFiles } from '../apis'; @@ -19,12 +27,16 @@ interface HFModelFileProps { collapsed?: boolean; loadingModel?: boolean; modelSource: string; + ref: any; onSelectFile?: (file: any) => void; } -const pattern = /^(.*)-(\d+)-of-(\d+)\.gguf$/; +const pattern = /^(.*)-(\d+)-of-(\d+)\.(.*)$/; -const HFModelFile: React.FC = (props) => { +const filterReg = /\.(safetensors|gguf)$/i; +const includeReg = /\.(safetensors|gguf)$/i; + +const HFModelFile: React.FC = forwardRef((props, ref) => { const { collapsed, modelSource } = props; const intl = useIntl(); const [dataSource, setDataSource] = useState({ @@ -58,7 +70,8 @@ const HFModelFile: React.FC = (props) => { return { filename: match[1], part: parseInt(match[2], 10), - total: parseInt(match[3], 10) + total: parseInt(match[3], 10), + extension: match[4] }; } else { return null; @@ -101,7 +114,7 @@ const HFModelFile: React.FC = (props) => { (value: any[], filename: string) => { return { path: filename, - fakeName: `${filename}*.gguf`, + fakeName: `${filename}*.${_.get(value, '[0].extension')}`, size: _.sumBy(value, 'size'), parts: value }; @@ -125,8 +138,9 @@ const HFModelFile: React.FC = (props) => { }); const list = _.filter(fileList, (file: any) => { - return _.endsWith(file.path, '.gguf') || _.includes(file.path, '.gguf'); + return filterReg.test(file.path) || _.includes(includeReg, file.path); }); + return list; } catch (error) { return []; @@ -145,7 +159,7 @@ const HFModelFile: React.FC = (props) => { } ); const fileList = _.filter(_.get(data, ['Data', 'Files']), (file: any) => { - return _.endsWith(file.Path, '.gguf') || _.includes(file.Path, '.gguf'); + return filterReg.test(file.path) || _.includes(includeReg, file.path); }); const list = _.map(fileList, (item: any) => { return { @@ -226,10 +240,13 @@ const HFModelFile: React.FC = (props) => { handleSelectModelFile(item); } }; + useImperativeHandle(ref, () => ({ + fetchModelFiles: handleFetchModelFiles + })); - useEffect(() => { - handleFetchModelFiles(); - }, [props.selectedModel.name]); + // useEffect(() => { + // handleFetchModelFiles(); + // }, [props.selectedModel.name]); useEffect(() => { return () => { @@ -340,6 +357,6 @@ const HFModelFile: React.FC = (props) => {
    ); -}; +}); export default memo(HFModelFile); diff --git a/src/pages/llmodels/components/hf-model-item.tsx b/src/pages/llmodels/components/hf-model-item.tsx index 88db2f28..9d4a80a8 100644 --- a/src/pages/llmodels/components/hf-model-item.tsx +++ b/src/pages/llmodels/components/hf-model-item.tsx @@ -23,7 +23,7 @@ interface HFModelItemProps { source?: string; tags?: string[]; } -const warningTask = ['image', 'audio', 'video']; +const warningTask = ['audio', 'video']; const SUPPORTEDSOURCE = [ modelSourceMap.huggingface_value, diff --git a/src/pages/llmodels/components/model-card.tsx b/src/pages/llmodels/components/model-card.tsx index 4ef1493e..864d90dd 100644 --- a/src/pages/llmodels/components/model-card.tsx +++ b/src/pages/llmodels/components/model-card.tsx @@ -1,5 +1,5 @@ -import HighlightCode from '@/components/highlight-code'; import IconFont from '@/components/icon-font'; +import MarkdownViewer from '@/components/markdown-viewer'; import useRequestToken from '@/hooks/use-request-token'; import { DownOutlined, @@ -7,7 +7,7 @@ import { RightOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; -import { Button, Empty, Tag, Tooltip } from 'antd'; +import { Button, Empty, Spin, Tag, Tooltip } from 'antd'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import SimpleBar from 'simplebar-react'; import 'simplebar-react/dist/simplebar.min.css'; @@ -21,19 +21,22 @@ import '../style/model-card.less'; import TitleWrapper from './title-wrapper'; const ModelCard: React.FC<{ - selectedModel: any; onCollapse: (flag: boolean) => void; + setIsGGUF: (flag: boolean) => void; + selectedModel: any; collapsed: boolean; loadingModel?: boolean; modelSource: string; }> = (props) => { - const { onCollapse, collapsed, modelSource } = props; + const { onCollapse, setIsGGUF, collapsed, modelSource } = props; const intl = useIntl(); const requestSource = useRequestToken(); const [modelData, setModelData] = useState({}); const [readmeText, setReadmeText] = useState(null); const requestToken = useRef(null); const axiosTokenRef = useRef(null); + const [isGGUFModel, setIsGGUFModel] = useState(false); + const [loading, setLoading] = useState(false); const loadFile = async (repo: string, sha: string) => { try { @@ -70,9 +73,13 @@ const ModelCard: React.FC<{ setModelData(modelcard); setReadmeText(readme); + setIsGGUF(modelcard.tags?.includes('gguf')); + setIsGGUFModel(modelcard.tags?.includes('gguf')); } catch (error) { setModelData({}); setReadmeText(null); + setIsGGUF(false); + setIsGGUFModel(false); } }; @@ -86,15 +93,18 @@ const ModelCard: React.FC<{ token: requestToken.current.token } ); - console.log('detaildata==========', data); setModelData({ ...data?.Data, name: `${data.Data?.Path}/${data.Data?.Name}` }); setReadmeText(data?.Data?.ReadMeContent); + setIsGGUF(data.Data?.Tags?.includes('gguf')); + setIsGGUFModel(data.Data?.Tags?.includes('gguf')); } catch (error) { setModelData({}); setReadmeText(null); + setIsGGUF(false); + setIsGGUFModel(false); } }; @@ -105,11 +115,13 @@ const ModelCard: React.FC<{ } requestToken.current?.cancel?.(); requestToken.current = requestSource(); + setLoading(true); if (modelSource === modelSourceMap.huggingface_value) { - getHuggingfaceModelDetail(); + await getHuggingfaceModelDetail(); } else if (modelSource === modelSourceMap.modelscope_value) { - getModelScopeModelDetail(); + await getModelScopeModelDetail(); } + setLoading(false); }; const handleCollapse = useCallback(() => { @@ -191,7 +203,7 @@ const ModelCard: React.FC<{ )}
    - {readmeText && ( + {readmeText && isGGUFModel && (
    - + >
    )} @@ -230,6 +240,21 @@ const ModelCard: React.FC<{ > )}
    + {!isGGUFModel && readmeText && ( +
    + +
    README.md
    +
    +
    + + + +
    +
    + )} ); }; diff --git a/src/pages/llmodels/components/search-model.tsx b/src/pages/llmodels/components/search-model.tsx index ad60365a..64768d52 100644 --- a/src/pages/llmodels/components/search-model.tsx +++ b/src/pages/llmodels/components/search-model.tsx @@ -1,6 +1,6 @@ import { BulbOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; -import { Select } from 'antd'; +import { Checkbox, Select } from 'antd'; import _ from 'lodash'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { queryHuggingfaceModels, queryModelScopeModels } from '../apis'; @@ -44,6 +44,7 @@ const SearchModel: React.FC = (props) => { const cacheRepoOptions = useRef([]); const axiosTokenRef = useRef(null); const searchInputRef = useRef(''); + const filterGGUFRef = useRef(true); const modelFilesSortOptions = useRef([ { label: intl.formatMessage({ id: 'models.sort.trending' }), @@ -77,7 +78,7 @@ const SearchModel: React.FC = (props) => { search: { query: searchInputRef.current || '', sort: sort, - tags: ['gguf'], + tags: filterGGUFRef.current ? ['gguf'] : [], task } }; @@ -101,7 +102,9 @@ const SearchModel: React.FC = (props) => { const getModelsFromModelscope = useCallback(async (sort: string) => { try { const params = { - Name: searchInputRef.current || '', + Name: filterGGUFRef.current + ? `${searchInputRef.current} gguf` + : searchInputRef.current || '', SortBy: ModelScopeSortType[sort] }; const data = await queryModelScopeModels(params, { @@ -205,6 +208,12 @@ const SearchModel: React.FC = (props) => { handleOnSearchRepo(value || ''); }; + const handleFilterGGUFChange = (e: any) => { + console.log('filterggufChange:', e.target.checked); + filterGGUFRef.current = e.target.checked; + handleOnSearchRepo(); + }; + const renderHFSearch = () => { return ( <> @@ -221,21 +230,30 @@ const SearchModel: React.FC = (props) => { )} - + + + GGUF + + +
    ); diff --git a/src/pages/llmodels/components/search-result.tsx b/src/pages/llmodels/components/search-result.tsx index 0bd5101f..df4f7367 100644 --- a/src/pages/llmodels/components/search-result.tsx +++ b/src/pages/llmodels/components/search-result.tsx @@ -1,10 +1,11 @@ import IconFont from '@/components/icon-font'; import { SearchOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; -import { Col, Empty, Row, Spin } from 'antd'; +import { Button, Col, Empty, Row, Spin } from 'antd'; import React from 'react'; import SimpleBar from 'simplebar-react'; import 'simplebar-react/dist/simplebar.min.css'; +import { modelSourceMap } from '../config'; import '../style/search-result.less'; import HFModelItem from './hf-model-item'; @@ -49,24 +50,26 @@ const SearchResult: React.FC = (props) => { > } description={ -
    - - {intl.formatMessage({ id: 'models.search.networkerror' })} - - {/* + source === modelSourceMap.huggingface_value ? ( +
    - {intl.formatMessage({ id: 'models.search.hfvisit' })} + {intl.formatMessage({ id: 'models.search.networkerror' })} - - */} -
    + + + {intl.formatMessage({ id: 'models.search.hfvisit' })} + + + +
    + ) : null } /> ); diff --git a/src/pages/llmodels/components/table-list.tsx b/src/pages/llmodels/components/table-list.tsx index df4e6090..da464b4e 100644 --- a/src/pages/llmodels/components/table-list.tsx +++ b/src/pages/llmodels/components/table-list.tsx @@ -418,10 +418,10 @@ const Models: React.FC = ({ const generateSource = useCallback((record: ListItem) => { if (record.source === modelSourceMap.modelscope_value) { - return `${modelSourceMap.modelScope} / ${record.model_scope_file_path}`; + return `${modelSourceMap.modelScope} / ${record.model_scope_file_path || record.model_scope_model_id}`; } if (record.source === modelSourceMap.huggingface_value) { - return `${modelSourceMap.huggingface} / ${record.huggingface_filename}`; + return `${modelSourceMap.huggingface} / ${record.huggingface_filename || record.huggingface_repo_id}`; } return `${modelSourceMap.ollama_library} / ${record.ollama_library_model_name}`; }, []); diff --git a/src/pages/llmodels/components/update-modal.tsx b/src/pages/llmodels/components/update-modal.tsx index fcd24ee3..a3b9937b 100644 --- a/src/pages/llmodels/components/update-modal.tsx +++ b/src/pages/llmodels/components/update-modal.tsx @@ -12,7 +12,11 @@ import { memo, useEffect, useMemo, useState } from 'react'; import SimpleBar from 'simplebar-react'; import 'simplebar-react/dist/simplebar.min.css'; import { queryGPUList, queryHuggingfaceModelFiles } from '../apis'; -import { modelSourceMap, setSourceRepoConfigValue } from '../config'; +import { + backendOptionsMap, + modelSourceMap, + setSourceRepoConfigValue +} from '../config'; import { FormData, GPUListItem, ListItem } from '../config/types'; import AdvanceConfig from './advance-config'; @@ -160,29 +164,31 @@ const UpdateModal: React.FC = (props) => { disabled={true} > - - name="file_name" - rules={[ - { - required: true, - message: intl.formatMessage( - { - id: 'common.form.rule.input' - }, - { name: intl.formatMessage({ id: 'models.form.filename' }) } - ) - } - ]} - > - - + {form.getFieldValue('file_name') && ( + + name="file_name" + rules={[ + { + required: true, + message: intl.formatMessage( + { + id: 'common.form.rule.input' + }, + { name: intl.formatMessage({ id: 'models.form.filename' }) } + ) + } + ]} + > + + + )} ); }; @@ -418,7 +424,14 @@ const UpdateModal: React.FC = (props) => { > - + diff --git a/src/pages/llmodels/config/index.ts b/src/pages/llmodels/config/index.ts index 34b5d419..6adf7718 100644 --- a/src/pages/llmodels/config/index.ts +++ b/src/pages/llmodels/config/index.ts @@ -81,6 +81,11 @@ export const ollamaModelOptions = [ } ]; +export const backendOptionsMap = { + llamaBox: 'llama-box', + vllm: 'vllm' +}; + export const modelSourceMap: Record = { huggingface: 'Hugging Face', ollama_library: 'Ollama Library', @@ -232,3 +237,22 @@ export const setSourceRepoConfigValue = ( omits: omits }; }; + +export const getbackendParameters = (data: any) => { + const backendParameters = data.backend_parameters || {}; + const result: string[] = []; + Object.keys(backendParameters)?.forEach((key: string) => { + result.push(`${key}=${backendParameters[key]}`); + }); + return result; +}; + +export const setbackendParameters = (data: any) => { + const result: Record = {}; + const backendParameters = data.backend_parameters || []; + backendParameters.forEach((item: string) => { + const [key, value] = item.split('='); + result[key] = value; + }); + return result; +}; diff --git a/src/pages/llmodels/config/llama-config.ts b/src/pages/llmodels/config/llama-config.ts new file mode 100644 index 00000000..0fc1d6b9 --- /dev/null +++ b/src/pages/llmodels/config/llama-config.ts @@ -0,0 +1,26 @@ +export default [ + { + label: '--chat-template', + value: '--chat-template' + }, + { + label: '--ctx-size', + value: '--ctx-size' + }, + { + label: '--flash-attn', + value: '--flash-attn' + }, + { + label: '--parallel', + value: '--parallel' + }, + { + label: '--batch-size', + value: '--batch-size' + }, + { + label: '--ubatch-size', + value: '--ubatch-size' + } +]; diff --git a/src/pages/llmodels/config/types.ts b/src/pages/llmodels/config/types.ts index 5500cdf8..595097e0 100644 --- a/src/pages/llmodels/config/types.ts +++ b/src/pages/llmodels/config/types.ts @@ -24,6 +24,8 @@ export interface ListItem { } export interface FormData { + backend?: string; + backend_parameters?: string[]; source: string; repo_id: string; file_name: string; diff --git a/src/pages/llmodels/config/vllm-config.ts b/src/pages/llmodels/config/vllm-config.ts new file mode 100644 index 00000000..ad10f820 --- /dev/null +++ b/src/pages/llmodels/config/vllm-config.ts @@ -0,0 +1,593 @@ +const options = [ + { + label: '--uvicorn-log-level', + value: '--uvicorn-log-level', + options: ['debug', 'info', 'warning', 'error', 'critical', 'trace'] + }, + { + label: '--allow-credentials', + value: '--allow-credentials', + options: [] + }, + { + label: '--allowed-origins', + value: '--allowed-origins', + options: [] + }, + { + label: '--allowed-methods', + value: '--allowed-methods', + options: [] + }, + { + label: '--allowed-headers', + value: '--allowed-headers', + options: [] + }, + { + label: '--api-key', + value: '--api-key', + options: [] + }, + { + label: '--lora-modules', + value: '--lora-modules', + options: [] + }, + { + label: '--prompt-adapters', + value: '--prompt-adapters', + options: [] + }, + { + label: '--chat-template', + value: '--chat-template', + options: [] + }, + { + label: '--response-role', + value: '--response-role', + options: [] + }, + { + label: '--ssl-keyfile', + value: '--ssl-keyfile', + options: [] + }, + { + label: '--ssl-certfile', + value: '--ssl-certfile', + options: [] + }, + { + label: '--ssl-ca-certs', + value: '--ssl-ca-certs', + options: [] + }, + { + label: '--ssl-cert-reqs', + value: '--ssl-cert-reqs', + options: [] + }, + { + label: '--root-path', + value: '--root-path', + options: [] + }, + { + label: '--middleware', + value: '--middleware', + options: [] + }, + { + label: '--return-tokens-as-token-ids', + value: '--return-tokens-as-token-ids', + options: [] + }, + { + label: '--disable-frontend-multiprocessing', + value: '--disable-frontend-multiprocessing', + options: [] + }, + { + label: '--enable-auto-tool-choice', + value: '--enable-auto-tool-choice', + options: [] + }, + { + label: '--tool-call-parser', + value: '--tool-call-parser', + options: ['mistral', 'hermes'] + }, + { + label: '--model', + value: '--model', + options: [] + }, + { + label: '--tokenizer', + value: '--tokenizer', + options: [] + }, + { + label: '--skip-tokenizer-init', + value: '--skip-tokenizer-init', + options: [] + }, + { + label: '--revision', + value: '--revision', + options: [] + }, + { + label: '--code-revision', + value: '--code-revision', + options: [] + }, + { + label: '--tokenizer-revision', + value: '--tokenizer-revision', + options: [] + }, + { + label: '--tokenizer-mode', + value: '--tokenizer-mode', + options: ['auto', 'slow', 'mistral'] + }, + { + label: '--trust-remote-code', + value: '--trust-remote-code', + options: [] + }, + { + label: '--download-dir', + value: '--download-dir', + options: [] + }, + { + label: '--load-format', + value: '--load-format', + options: [ + 'auto', + 'pt', + 'safetensors', + 'npcache', + 'dummy', + 'tensorizer', + 'sharded_state', + 'gguf', + 'bitsandbytes', + 'mistral' + ] + }, + { + label: '--config-format', + value: '--config-format', + options: ['auto', 'hf', 'mistral'] + }, + { + label: '--dtype', + value: '--dtype', + options: ['auto', 'half', 'float16', 'bfloat16', 'float', 'float32'] + }, + { + label: '--kv-cache-dtype', + value: '--kv-cache-dtype', + options: ['auto', 'fp8', 'fp8_e5m2', 'fp8_e4m3'] + }, + { + label: '--quantization-param-path', + value: '--quantization-param-path', + options: [] + }, + { + label: '--max-model-len', + value: '--max-model-len', + options: [] + }, + { + label: '--guided-decoding-backend', + value: '--guided-decoding-backend', + options: ['outlines', 'lm-format-enforcer'] + }, + { + label: '--distributed-executor-backend', + value: '--distributed-executor-backend', + options: ['ray', 'mp'] + }, + { + label: '--worker-use-ray', + value: '--worker-use-ray', + options: [] + }, + { + label: '--pipeline-parallel-size', + value: '--pipeline-parallel-size', + options: [] + }, + { + label: '--tensor-parallel-size', + value: '--tensor-parallel-size', + options: [] + }, + { + label: '--max-parallel-loading-workers', + value: '--max-parallel-loading-workers', + options: [] + }, + { + label: '--ray-workers-use-nsight', + value: '--ray-workers-use-nsight', + options: [] + }, + { + label: '--block-size', + value: '--block-size', + options: ['8', '16', '32'] + }, + { + label: '--enable-prefix-caching', + value: '--enable-prefix-caching', + options: [] + }, + { + label: '--disable-sliding-window', + value: '--disable-sliding-window', + options: [] + }, + { + label: '--use-v2-block-manager', + value: '--use-v2-block-manager', + options: [] + }, + { + label: '--num-lookahead-slots', + value: '--num-lookahead-slots', + options: [] + }, + { + label: '--seed', + value: '--seed', + options: [] + }, + { + label: '--swap-space', + value: '--swap-space', + options: [] + }, + { + label: '--cpu-offload-gb', + value: '--cpu-offload-gb', + options: [] + }, + { + label: '--gpu-memory-utilization', + value: '--gpu-memory-utilization', + options: [] + }, + { + label: '--num-gpu-blocks-override', + value: '--num-gpu-blocks-override', + options: [] + }, + { + label: '--max-num-batched-tokens', + value: '--max-num-batched-tokens', + options: [] + }, + { + label: '--max-num-seqs', + value: '--max-num-seqs', + options: [] + }, + { + label: '--max-logprobs', + value: '--max-logprobs', + options: [] + }, + { + label: '--disable-log-stats', + value: '--disable-log-stats', + options: [] + }, + { + label: '--quantization', + value: '--quantization', + options: [ + 'aqlm', + 'awq', + 'deepspeedfp', + 'tpu_int8', + 'fp8', + 'fbgemm_fp8', + 'modelopt', + 'marlin', + 'gguf', + 'gptq_marlin_24', + 'gptq_marlin', + 'awq_marlin', + 'gptq', + 'compressed-tensors', + 'bitsandbytes', + 'qqq', + 'experts_int8', + 'neuron_quant', + 'None' + ] + }, + { + label: '--rope-scaling', + value: '--rope-scaling', + options: [] + }, + { + label: '--rope-theta', + value: '--rope-theta', + options: [] + }, + { + label: '--enforce-eager', + value: '--enforce-eager', + options: [] + }, + { + label: '--max-context-len-to-capture', + value: '--max-context-len-to-capture', + options: [] + }, + { + label: '--max-seq-len-to-capture', + value: '--max-seq-len-to-capture', + options: [] + }, + { + label: '--disable-custom-all-reduce', + value: '--disable-custom-all-reduce', + options: [] + }, + { + label: '--tokenizer-pool-size', + value: '--tokenizer-pool-size', + options: [] + }, + { + label: '--tokenizer-pool-type', + value: '--tokenizer-pool-type', + options: [] + }, + { + label: '--tokenizer-pool-extra-config', + value: '--tokenizer-pool-extra-config', + options: [] + }, + { + label: '--limit-mm-per-prompt', + value: '--limit-mm-per-prompt', + options: [] + }, + { + label: '--enable-lora', + value: '--enable-lora', + options: [] + }, + { + label: '--max-loras', + value: '--max-loras', + options: [] + }, + { + label: '--max-lora-rank', + value: '--max-lora-rank', + options: [] + }, + { + label: '--lora-extra-vocab-size', + value: '--lora-extra-vocab-size', + options: [] + }, + { + label: '--lora-dtype', + value: '--lora-dtype', + options: ['auto', 'float16', 'bfloat16', 'float32'] + }, + { + label: '--long-lora-scaling-factors', + value: '--long-lora-scaling-factors', + options: [] + }, + { + label: '--max-cpu-loras', + value: '--max-cpu-loras', + options: [] + }, + { + label: '--fully-sharded-loras', + value: '--fully-sharded-loras', + options: [] + }, + { + label: '--enable-prompt-adapter', + value: '--enable-prompt-adapter', + options: [] + }, + { + label: '--max-prompt-adapters', + value: '--max-prompt-adapters', + options: [] + }, + { + label: '--max-prompt-adapter-token', + value: '--max-prompt-adapter-token', + options: [] + }, + { + label: '--device', + value: '--device', + options: ['auto', 'cuda', 'neuron', 'cpu', 'openvino', 'tpu', 'xpu'] + }, + { + label: '--num-scheduler-steps', + value: '--num-scheduler-steps', + options: [] + }, + { + label: '--scheduler-delay-factor', + value: '--scheduler-delay-factor', + options: [] + }, + { + label: '--enable-chunked-prefill', + value: '--enable-chunked-prefill', + options: [] + }, + { + label: '--speculative-model', + value: '--speculative-model', + options: [] + }, + { + label: '--speculative-model-quantization', + value: '--speculative-model-quantization', + options: [ + 'aqlm', + 'awq', + 'deepspeedfp', + 'tpu_int8', + 'fp8', + 'fbgemm_fp8', + 'modelopt', + 'marlin', + 'gguf', + 'gptq_marlin_24', + 'gptq_marlin', + 'awq_marlin', + 'gptq', + 'compressed-tensors', + 'bitsandbytes', + 'qqq', + 'experts_int8', + 'neuron_quant', + 'None' + ] + }, + { + label: '--num-speculative-tokens', + value: '--num-speculative-tokens', + options: [] + }, + { + label: '--speculative-draft-tensor-parallel-size', + value: '--speculative-draft-tensor-parallel-size', + options: [] + }, + { + label: '--speculative-max-model-len', + value: '--speculative-max-model-len', + options: [] + }, + { + label: '--speculative-disable-by-batch-size', + value: '--speculative-disable-by-batch-size', + options: [] + }, + { + label: '--ngram-prompt-lookup-max', + value: '--ngram-prompt-lookup-max', + options: [] + }, + { + label: '--ngram-prompt-lookup-min', + value: '--ngram-prompt-lookup-min', + options: [] + }, + { + label: '--spec-decoding-acceptance-method', + value: '--spec-decoding-acceptance-method', + options: ['rejection_sampler', 'typical_acceptance_sampler'] + }, + { + label: '--typical-acceptance-sampler-posterior-threshold', + value: '--typical-acceptance-sampler-posterior-threshold', + options: [] + }, + { + label: '--typical-acceptance-sampler-posterior-alpha', + value: '--typical-acceptance-sampler-posterior-alpha', + options: [] + }, + { + label: '--disable-logprobs-during-spec-decoding', + value: '--disable-logprobs-during-spec-decoding', + options: [] + }, + { + label: '--model-loader-extra-config', + value: '--model-loader-extra-config', + options: [] + }, + { + label: '--ignore-patterns', + value: '--ignore-patterns', + options: [] + }, + { + label: '--preemption-mode', + value: '--preemption-mode', + options: [] + }, + { + label: '--served-model-name', + value: '--served-model-name', + options: [] + }, + { + label: '--qlora-adapter-name-or-path', + value: '--qlora-adapter-name-or-path', + options: [] + }, + { + label: '--otlp-traces-endpoint', + value: '--otlp-traces-endpoint', + options: [] + }, + { + label: '--collect-detailed-traces', + value: '--collect-detailed-traces', + options: [] + }, + { + label: '--disable-async-output-proc', + value: '--disable-async-output-proc', + options: [] + }, + { + label: '--override-neuron-config', + value: '--override-neuron-config', + options: [] + }, + { + label: '--disable-log-requests', + value: '--disable-log-requests', + options: [] + }, + { + label: '--max-log-len', + value: '--max-log-len', + options: [] + } +]; + +const resultList = options.map((option) => { + return { + label: option.label, + value: option.value, + opts: option.options.map((opt) => { + return { + label: opt, + value: opt + }; + }) + }; +}); + +export default resultList; diff --git a/src/pages/llmodels/style/hf-model-file.less b/src/pages/llmodels/style/hf-model-file.less index 15553476..6b016f9b 100644 --- a/src/pages/llmodels/style/hf-model-file.less +++ b/src/pages/llmodels/style/hf-model-file.less @@ -37,8 +37,6 @@ font-size: 12px; height: 22px; opacity: 0.7; - // border: 1px solid var(--ant-color-border); - // color: var(--ant-color-text-secondary); } .btn { diff --git a/src/pages/llmodels/style/title-wrapper.less b/src/pages/llmodels/style/title-wrapper.less index 642925ce..905aeac3 100644 --- a/src/pages/llmodels/style/title-wrapper.less +++ b/src/pages/llmodels/style/title-wrapper.less @@ -11,5 +11,10 @@ padding: @padding; padding-top: 10px; margin-bottom: 0; + font-weight: var(--font-weight-bold); background-color: var(--color-white-1); + + .title { + font-weight: var(--font-weight-bold); + } } diff --git a/src/pages/playground/components/ground-left.tsx b/src/pages/playground/components/ground-left.tsx index 53c75138..9d7d9d54 100644 --- a/src/pages/playground/components/ground-left.tsx +++ b/src/pages/playground/components/ground-left.tsx @@ -99,7 +99,7 @@ const GroundLeft: React.FC = forwardRef((props, ref) => { if (!chunk) { return; } - if (_.get(chunk, 'choices.0.finish_reason')) { + if (!_.get(chunk, 'choices', []).length) { setTokenResult({ ...chunk?.usage }); @@ -142,6 +142,9 @@ const GroundLeft: React.FC = forwardRef((props, ref) => { : []; contentRef.current = ''; + setMessageList((pre) => { + return [...pre, ...currentMessageRef.current]; + }); const formatMessages = _.map( [...messageList, ...currentMessageRef.current], (item: MessageItem) => { @@ -217,9 +220,10 @@ const GroundLeft: React.FC = forwardRef((props, ref) => { setTokenResult(null); }; - const handleSendMessage = (message: { role: string; content: string }) => { + const handleSendMessage = (message: Omit) => { console.log('message:', message); - const currentMessage = message.content ? message : undefined; + const currentMessage = + message.content || message.imgs?.length ? message : undefined; submitMessage(currentMessage); }; diff --git a/src/pages/playground/components/message-input.tsx b/src/pages/playground/components/message-input.tsx index 24a62e75..e43a26ac 100644 --- a/src/pages/playground/components/message-input.tsx +++ b/src/pages/playground/components/message-input.tsx @@ -110,7 +110,9 @@ const MessageInput: React.FC = ({ const inputRef = useRef(null); const isDisabled = useMemo(() => { - return disabled ? true : !message.content && isEmpty; + return disabled + ? true + : !message.content && isEmpty && !message.imgs?.length; }, [disabled, message.content, isEmpty]); const resetMessage = () => { @@ -214,9 +216,6 @@ const MessageInput: React.FC = ({ dataUrl: img }; }); - // setImgList((pre) => { - // return [...pre, ...list]; - // }); setMessage({ ...message, imgs: [...(message.imgs || []), ...list] @@ -253,7 +252,7 @@ const MessageInput: React.FC = ({ if (text) { setMessage?.({ ...message, - content: text + content: message.content + text }); } else { getPasteContent(e); @@ -446,7 +445,7 @@ const MessageInput: React.FC = ({