feat: vllm support
This commit is contained in:
@@ -18,7 +18,7 @@ const CopyButton: React.FC<CopyButtonProps> = ({
|
||||
text,
|
||||
disabled,
|
||||
type = 'text',
|
||||
shape = 'circle',
|
||||
shape = 'default',
|
||||
fontSize = '14px',
|
||||
style,
|
||||
size = 'middle'
|
||||
|
||||
@@ -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%);
|
||||
|
||||
@@ -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<EditorwrapProps> = ({
|
||||
@@ -19,6 +24,7 @@ const EditorWrap: React.FC<EditorwrapProps> = ({
|
||||
langOptions,
|
||||
onChangeLang,
|
||||
defaultValue,
|
||||
styles = {},
|
||||
showHeader = true
|
||||
}) => {
|
||||
const handleChangeLang = (value: string) => {
|
||||
@@ -52,7 +58,7 @@ const EditorWrap: React.FC<EditorwrapProps> = ({
|
||||
return null;
|
||||
};
|
||||
return (
|
||||
<div className="editor-wrap">
|
||||
<div className="editor-wrap" style={{ ...styles.wrapper }}>
|
||||
{renderHeader()}
|
||||
<div className="editor-content">{children}</div>
|
||||
</div>
|
||||
|
||||
@@ -8,12 +8,21 @@ interface CodeViewerProps {
|
||||
autodetect?: boolean;
|
||||
ignoreIllegals?: boolean;
|
||||
copyable?: boolean;
|
||||
height?: string | number;
|
||||
}
|
||||
const DarkViewer: React.FC<CodeViewerProps> = (props) => {
|
||||
const { code, lang, autodetect, ignoreIllegals, copyable } = props || {};
|
||||
const {
|
||||
code,
|
||||
lang,
|
||||
autodetect,
|
||||
ignoreIllegals,
|
||||
copyable,
|
||||
height = 'auto'
|
||||
} = props || {};
|
||||
|
||||
return (
|
||||
<CodeViewer
|
||||
height={height}
|
||||
code={code}
|
||||
lang={lang}
|
||||
theme="dark"
|
||||
|
||||
@@ -8,12 +8,21 @@ interface CodeViewerProps {
|
||||
autodetect?: boolean;
|
||||
ignoreIllegals?: boolean;
|
||||
copyable?: boolean;
|
||||
height?: string | number;
|
||||
}
|
||||
const LightViewer: React.FC<CodeViewerProps> = (props) => {
|
||||
const { code, lang, autodetect, ignoreIllegals, copyable } = props || {};
|
||||
const {
|
||||
code,
|
||||
lang,
|
||||
autodetect,
|
||||
ignoreIllegals,
|
||||
copyable,
|
||||
height = 'auto'
|
||||
} = props || {};
|
||||
|
||||
return (
|
||||
<CodeViewer
|
||||
height={height}
|
||||
code={code}
|
||||
lang={lang}
|
||||
theme="light"
|
||||
|
||||
@@ -10,6 +10,7 @@ interface CodeViewerProps {
|
||||
autodetect?: boolean;
|
||||
ignoreIllegals?: boolean;
|
||||
copyable?: boolean;
|
||||
height?: string | number;
|
||||
theme?: 'light' | 'dark';
|
||||
}
|
||||
const CodeViewer: React.FC<CodeViewerProps> = (props) => {
|
||||
@@ -18,7 +19,8 @@ const CodeViewer: React.FC<CodeViewerProps> = (props) => {
|
||||
lang,
|
||||
autodetect = true,
|
||||
ignoreIllegals = true,
|
||||
copyable = true
|
||||
copyable = true,
|
||||
height = 'auto'
|
||||
} = props || {};
|
||||
|
||||
const highlightedCode = useMemo(() => {
|
||||
@@ -58,11 +60,14 @@ const CodeViewer: React.FC<CodeViewerProps> = (props) => {
|
||||
|
||||
return (
|
||||
<pre
|
||||
className={classNames('code-pre', {
|
||||
className={classNames('code-pre custome-scrollbar ', {
|
||||
dark: props.theme === 'dark',
|
||||
light: props.theme === 'light',
|
||||
copyable: copyable
|
||||
})}
|
||||
style={{
|
||||
height: height
|
||||
}}
|
||||
>
|
||||
<code
|
||||
className={highlightedCode.className}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from 'react';
|
||||
import CodeViewerDark from './code-viewer-dark';
|
||||
import CodeViewerLight from './code-viewer-light';
|
||||
import './styles/index.less';
|
||||
@@ -7,18 +8,35 @@ const HighlightCode: React.FC<{
|
||||
lang?: string;
|
||||
copyable?: boolean;
|
||||
theme?: 'light' | 'dark';
|
||||
height?: string | number;
|
||||
}> = (props) => {
|
||||
const { code, lang = 'bash', copyable = true, theme = 'dark' } = props;
|
||||
const {
|
||||
code,
|
||||
lang = 'bash',
|
||||
copyable = true,
|
||||
theme = 'dark',
|
||||
height = 'auto'
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className="high-light-wrapper">
|
||||
<div className="high-light-wrapper hj-wrapper">
|
||||
{theme === 'dark' ? (
|
||||
<CodeViewerDark lang={lang} code={code} copyable={copyable} />
|
||||
<CodeViewerDark
|
||||
lang={lang}
|
||||
code={code}
|
||||
copyable={copyable}
|
||||
height={height}
|
||||
/>
|
||||
) : (
|
||||
<CodeViewerLight lang={lang} code={code} copyable={copyable} />
|
||||
<CodeViewerLight
|
||||
lang={lang}
|
||||
code={code}
|
||||
copyable={copyable}
|
||||
height={height}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HighlightCode;
|
||||
export default React.memo(HighlightCode);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
.high-light-wrapper {
|
||||
text-align: left;
|
||||
font-size: var(--font-size-base);
|
||||
|
||||
.hljs {
|
||||
font-weight: var(--font-weight-normal);
|
||||
|
||||
@@ -52,7 +52,7 @@ const LabelItem: React.FC<LabelItemProps> = ({
|
||||
// has duplicate key
|
||||
const duplicates = _.filter(
|
||||
labelList,
|
||||
(item: Global.BaseListItem) => val && val === item.key
|
||||
(item: Global.BaseListItem<string>) => val && val === item.key
|
||||
);
|
||||
if (duplicates.length > 1) {
|
||||
setOpen(true);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<HintInputProps> = (props) => {
|
||||
const { value, label, onChange, sourceOptions } = props;
|
||||
const cursorPosRef = React.useRef(0);
|
||||
const contextBeforeCursorRef = React.useRef('');
|
||||
const [options, setOptions] = React.useState<
|
||||
Array<Global.BaseOption<string>>
|
||||
>([]);
|
||||
|
||||
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<string>) =>
|
||||
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 (
|
||||
<AutoComplete
|
||||
defaultActiveFirstOption={true}
|
||||
value={value}
|
||||
onInput={handleInput}
|
||||
onSelect={handleOnSelect}
|
||||
onFocus={getContextBeforeCursor}
|
||||
label={label}
|
||||
options={options}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(HintInput);
|
||||
@@ -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<ListInputProps> = (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<HTMLButtonElement>(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 (
|
||||
<Wrapper label={label} description={description}>
|
||||
<>
|
||||
{_.map(list, (item: any, index: number) => {
|
||||
return (
|
||||
<ListItem
|
||||
options={options}
|
||||
key={item.uid}
|
||||
value={item.value}
|
||||
label={`${index + 1}`}
|
||||
onRemove={() => handleOnRemove(index)}
|
||||
onChange={(val) => handleOnChange(val, index)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<div className="flex justify-center">
|
||||
<Button
|
||||
ref={buttonRef}
|
||||
type="text"
|
||||
block
|
||||
style={{
|
||||
marginTop: 16,
|
||||
backgroundColor: 'var(--ant-color-fill-secondary)'
|
||||
}}
|
||||
onClick={handleOnAdd}
|
||||
>
|
||||
<PlusOutlined className="font-size-14" />{' '}
|
||||
{intl.formatMessage({
|
||||
id: btnText
|
||||
})}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ListInput);
|
||||
@@ -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<LabelItemProps> = (props) => {
|
||||
const { onRemove, onChange, label, value, options } = props;
|
||||
|
||||
const handleOnChange = (value: any) => {
|
||||
onChange(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="list-item">
|
||||
<HintInput
|
||||
value={value}
|
||||
onChange={handleOnChange}
|
||||
label={label}
|
||||
sourceOptions={options}
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
className="btn"
|
||||
type="default"
|
||||
shape="circle"
|
||||
icon={<MinusOutlined />}
|
||||
onClick={onRemove}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ListItem);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<MarkdownViewerProps> = ({
|
||||
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 `<a href="${href}" title="${title || ''}" target="_blank" rel="noopener noreferrer">${text}</a>`;
|
||||
const renderItem = (token: any, render: any) => {
|
||||
// console.log('token======66==', token.raw, token.type);
|
||||
if (!reDefineTypes.includes(token.type)) {
|
||||
return (
|
||||
<span
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: marked.parser([token], { renderer })
|
||||
}}
|
||||
></span>
|
||||
);
|
||||
}
|
||||
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 = <div dangerouslySetInnerHTML={{ __html: token.text }} />;
|
||||
}
|
||||
if (token.type === 'list') {
|
||||
htmlstr = token.order ? (
|
||||
<ol>{render?.(token.items, render)}</ol>
|
||||
) : (
|
||||
<ul>{render?.(token.items, render)}</ul>
|
||||
);
|
||||
}
|
||||
|
||||
if (token.type === 'list_item') {
|
||||
htmlstr = <li>{text}</li>;
|
||||
}
|
||||
|
||||
if (token.type === 'br') {
|
||||
htmlstr = <br />;
|
||||
}
|
||||
|
||||
if (token.type === 'em') {
|
||||
htmlstr = <em>{text}</em>;
|
||||
}
|
||||
|
||||
if (token.type === 'image') {
|
||||
htmlstr = (
|
||||
<Image
|
||||
src={token.href}
|
||||
preview={{
|
||||
mask: <EyeOutlined />
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (token.type === 'text') {
|
||||
htmlstr = text;
|
||||
}
|
||||
if (token.type === 'codespan') {
|
||||
htmlstr = <Text code>{text}</Text>;
|
||||
}
|
||||
if (token.type === 'strong') {
|
||||
htmlstr = <Text strong>{text}</Text>;
|
||||
}
|
||||
if (token.type === 'heading') {
|
||||
htmlstr = <Typography.Title level={4}>{text}</Typography.Title>;
|
||||
}
|
||||
if (token.type === 'paragraph') {
|
||||
htmlstr = <Paragraph> {text}</Paragraph>;
|
||||
}
|
||||
if (token.type === 'code') {
|
||||
htmlstr = (
|
||||
<HighlightCode theme={theme} code={token.text} lang={token.lang} />
|
||||
);
|
||||
}
|
||||
if (token.type === 'link') {
|
||||
htmlstr = (
|
||||
<Link
|
||||
href={token.href}
|
||||
title={token.title || ''}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
if (token.type === 'hr') {
|
||||
htmlstr = <hr className="hr" />;
|
||||
}
|
||||
|
||||
return htmlstr;
|
||||
};
|
||||
const renderTokens = (tokens: TokensList): any => {
|
||||
return tokens?.map((token: any, index: number) => {
|
||||
return <Fragment key={index}>{renderItem(token, renderTokens)}</Fragment>;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ height, overflow: 'auto' }}>
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: marked(content, { renderer }) }}
|
||||
/>
|
||||
<div
|
||||
style={{ height, overflow: 'auto' }}
|
||||
className="markdown-viewer custom-scrollbar-horizontal"
|
||||
>
|
||||
{renderTokens(tokens)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export default {};
|
||||
@@ -4,9 +4,9 @@ import { useEffect, useRef, useState } from 'react';
|
||||
import Wrapper from './components/wrapper';
|
||||
import { SealFormItemProps } from './types';
|
||||
|
||||
const SealAutoComplete: React.FC<AutoCompleteProps & SealFormItemProps> = (
|
||||
props
|
||||
) => {
|
||||
const SealAutoComplete: React.FC<
|
||||
AutoCompleteProps & SealFormItemProps & { onInput: (e: Event) => void }
|
||||
> = (props) => {
|
||||
const {
|
||||
label,
|
||||
placeholder,
|
||||
|
||||
Vendored
+6
@@ -32,5 +32,11 @@ declare namespace Global {
|
||||
value: T;
|
||||
}
|
||||
|
||||
interface HintOptions {
|
||||
label: string;
|
||||
value: string;
|
||||
opts?: Array<BaseOption<string>>;
|
||||
}
|
||||
|
||||
type SearchParams = Pagination & { search?: string };
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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'
|
||||
};
|
||||
|
||||
@@ -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'
|
||||
};
|
||||
|
||||
@@ -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': '添加参数'
|
||||
};
|
||||
|
||||
@@ -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': '后端参数'
|
||||
};
|
||||
|
||||
@@ -165,7 +165,7 @@ export async function queryModelScopeModels(
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...params,
|
||||
Name: `${params.Name} gguf`,
|
||||
Name: `${params.Name}`,
|
||||
PageSize: 100,
|
||||
PageNumber: 1
|
||||
})
|
||||
|
||||
@@ -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<any>;
|
||||
action: PageActionType;
|
||||
}
|
||||
|
||||
const AdvanceConfig: React.FC<AdvanceConfigProps> = (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<string[]>([]);
|
||||
|
||||
const placementStrategyTips = [
|
||||
{
|
||||
@@ -64,6 +73,10 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
||||
}
|
||||
];
|
||||
|
||||
const paramsConfig = useMemo(() => {
|
||||
return backend === backendOptionsMap.llamaBox ? llamaConfig : vllmConfig;
|
||||
}, [backend]);
|
||||
|
||||
const renderSelectTips = (list: Array<{ title: string; tips: string }>) => {
|
||||
return (
|
||||
<div>
|
||||
@@ -96,6 +109,10 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
||||
[]
|
||||
);
|
||||
|
||||
const handleBackendParametersChange = useCallback((list: string[]) => {
|
||||
form.setFieldValue('backend_parameters', list);
|
||||
}, []);
|
||||
|
||||
const collapseItems = useMemo(() => {
|
||||
const children = (
|
||||
<>
|
||||
@@ -176,6 +193,33 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
<Form.Item name="backend">
|
||||
<SealSelect
|
||||
label={intl.formatMessage({ id: 'models.form.backend' })}
|
||||
options={[
|
||||
{
|
||||
label: `llama-box(llama.cpp)`,
|
||||
value: backendOptionsMap.llamaBox,
|
||||
disabled: !isGGUF
|
||||
},
|
||||
{
|
||||
label: 'vLLM',
|
||||
value: backendOptionsMap.vllm,
|
||||
disabled: isGGUF
|
||||
}
|
||||
]}
|
||||
disabled={action === PageAction.EDIT}
|
||||
></SealSelect>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData> name="backend_parameters">
|
||||
<ListInput
|
||||
btnText="common.button.addParams"
|
||||
label={intl.formatMessage({ id: 'models.form.backend_parameters' })}
|
||||
dataList={form.getFieldValue('backend_parameters') || []}
|
||||
onChange={handleBackendParametersChange}
|
||||
options={paramsConfig}
|
||||
></ListInput>
|
||||
</Form.Item>
|
||||
{scheduleType === 'manual' && (
|
||||
<Form.Item<FormData>
|
||||
name="gpu_selector"
|
||||
@@ -202,34 +246,36 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
||||
</SealSelect>
|
||||
</Form.Item>
|
||||
)}
|
||||
<div style={{ paddingBottom: 22, paddingLeft: 10 }}>
|
||||
<Form.Item<FormData>
|
||||
name="cpu_offloading"
|
||||
valuePropName="checked"
|
||||
style={{ padding: '0 10px', marginBottom: 0 }}
|
||||
noStyle
|
||||
>
|
||||
<Checkbox className="p-l-6">
|
||||
<Tooltip
|
||||
trigger={['click']}
|
||||
title={intl.formatMessage({
|
||||
id: 'models.form.partialoffload.tips'
|
||||
})}
|
||||
>
|
||||
<span style={{ color: 'var(--ant-color-text-tertiary)' }}>
|
||||
{intl.formatMessage({
|
||||
id: 'resources.form.enablePartialOffload'
|
||||
{isGGUF && (
|
||||
<div style={{ paddingBottom: 22, paddingLeft: 10 }}>
|
||||
<Form.Item<FormData>
|
||||
name="cpu_offloading"
|
||||
valuePropName="checked"
|
||||
style={{ padding: '0 10px', marginBottom: 0 }}
|
||||
noStyle
|
||||
>
|
||||
<Checkbox className="p-l-6">
|
||||
<Tooltip
|
||||
trigger={['click']}
|
||||
title={intl.formatMessage({
|
||||
id: 'models.form.partialoffload.tips'
|
||||
})}
|
||||
</span>
|
||||
<InfoCircleOutlined
|
||||
className="m-l-4"
|
||||
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Checkbox>
|
||||
</Form.Item>
|
||||
</div>
|
||||
{scheduleType === 'auto' && (
|
||||
>
|
||||
<span style={{ color: 'var(--ant-color-text-tertiary)' }}>
|
||||
{intl.formatMessage({
|
||||
id: 'resources.form.enablePartialOffload'
|
||||
})}
|
||||
</span>
|
||||
<InfoCircleOutlined
|
||||
className="m-l-4"
|
||||
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Checkbox>
|
||||
</Form.Item>
|
||||
</div>
|
||||
)}
|
||||
{scheduleType === 'auto' && isGGUF && (
|
||||
<div style={{ paddingBottom: 22, paddingLeft: 10 }}>
|
||||
<Form.Item<FormData>
|
||||
name="distributed_inference_across_workers"
|
||||
@@ -270,7 +316,15 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
||||
children
|
||||
}
|
||||
];
|
||||
}, [form, intl, gpuOptions, scheduleType, wokerSelector]);
|
||||
}, [
|
||||
form,
|
||||
intl,
|
||||
gpuOptions,
|
||||
paramsConfig,
|
||||
scheduleType,
|
||||
wokerSelector,
|
||||
isGGUF
|
||||
]);
|
||||
|
||||
return (
|
||||
<Collapse
|
||||
@@ -289,4 +343,4 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default AdvanceConfig;
|
||||
export default React.memo(AdvanceConfig);
|
||||
|
||||
@@ -10,7 +10,7 @@ const ColumnWrapper: React.FC<any> = ({ children, footer, height }) => {
|
||||
<div className="column-wrapper">
|
||||
<SimpleBar
|
||||
style={{
|
||||
height: height || 'calc(100vh - 89px)',
|
||||
maxHeight: height || 'calc(100vh - 89px)',
|
||||
paddingBottom: '50px'
|
||||
}}
|
||||
>
|
||||
@@ -23,7 +23,7 @@ const ColumnWrapper: React.FC<any> = ({ children, footer, height }) => {
|
||||
}
|
||||
return (
|
||||
<div className="column-wrapper">
|
||||
<SimpleBar style={{ height: height || 'calc(100vh - 89px)' }}>
|
||||
<SimpleBar style={{ maxHeight: height || 'calc(100vh - 89px)' }}>
|
||||
{children}
|
||||
</SimpleBar>
|
||||
</div>
|
||||
|
||||
@@ -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<DataFormProps> = 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<DataFormProps> = forwardRef((props, ref) => {
|
||||
disabled={true}
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData>
|
||||
name="file_name"
|
||||
key="file_name"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: intl.formatMessage(
|
||||
{
|
||||
id: 'common.form.rule.input'
|
||||
},
|
||||
{ name: intl.formatMessage({ id: 'models.form.filename' }) }
|
||||
)
|
||||
}
|
||||
]}
|
||||
>
|
||||
<SealInput.Input
|
||||
label={intl.formatMessage({ id: 'models.form.filename' })}
|
||||
required
|
||||
disabled={true}
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
{isGGUF && (
|
||||
<Form.Item<FormData>
|
||||
name="file_name"
|
||||
key="file_name"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: intl.formatMessage(
|
||||
{
|
||||
id: 'common.form.rule.input'
|
||||
},
|
||||
{ name: intl.formatMessage({ id: 'models.form.filename' }) }
|
||||
)
|
||||
}
|
||||
]}
|
||||
>
|
||||
<SealInput.Input
|
||||
label={intl.formatMessage({ id: 'models.form.filename' })}
|
||||
required
|
||||
disabled={true}
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -238,7 +245,7 @@ const DataForm: React.FC<DataFormProps> = 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<DataFormProps> = 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<DataFormProps> = forwardRef((props, ref) => {
|
||||
})}
|
||||
></SealInput.TextArea>
|
||||
</Form.Item>
|
||||
<AdvanceConfig form={form} gpuOptions={gpuOptions}></AdvanceConfig>
|
||||
<AdvanceConfig
|
||||
form={form}
|
||||
gpuOptions={gpuOptions}
|
||||
isGGUF={isGGUF}
|
||||
action={action}
|
||||
></AdvanceConfig>
|
||||
</Form>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -45,6 +45,8 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
||||
const [selectedModel, setSelectedModel] = useState<any>({});
|
||||
const [collapsed, setCollapsed] = useState<boolean>(false);
|
||||
const [loadingModel, setLoadingModel] = useState<boolean>(false);
|
||||
const [isGGUF, setIsGGUF] = useState<boolean>(false);
|
||||
const modelFileRef = useRef<any>(null);
|
||||
|
||||
const handleSelectModelFile = useCallback((item: any) => {
|
||||
form.current?.setFieldValue?.('file_name', item.fakeName);
|
||||
@@ -58,6 +60,15 @@ const AddModal: React.FC<AddModalProps> = (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<AddModalProps> = (props) => {
|
||||
onCollapse={setCollapsed}
|
||||
collapsed={collapsed}
|
||||
modelSource={props.source}
|
||||
setIsGGUF={handleSetIsGGUF}
|
||||
></ModelCard>
|
||||
<HFModelFile
|
||||
selectedModel={selectedModel}
|
||||
modelSource={props.source}
|
||||
onSelectFile={handleSelectModelFile}
|
||||
collapsed={collapsed}
|
||||
></HFModelFile>
|
||||
{isGGUF && (
|
||||
<HFModelFile
|
||||
ref={modelFileRef}
|
||||
selectedModel={selectedModel}
|
||||
modelSource={props.source}
|
||||
onSelectFile={handleSelectModelFile}
|
||||
collapsed={collapsed}
|
||||
></HFModelFile>
|
||||
)}
|
||||
</ColumnWrapper>
|
||||
<Separator></Separator>
|
||||
</div>
|
||||
@@ -159,6 +174,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
||||
selectedModel={selectedModel}
|
||||
onOk={onOk}
|
||||
ref={form}
|
||||
isGGUF={isGGUF}
|
||||
></DataForm>
|
||||
</>
|
||||
</ColumnWrapper>
|
||||
|
||||
@@ -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<HFModelFileProps> = (props) => {
|
||||
const filterReg = /\.(safetensors|gguf)$/i;
|
||||
const includeReg = /\.(safetensors|gguf)$/i;
|
||||
|
||||
const HFModelFile: React.FC<HFModelFileProps> = forwardRef((props, ref) => {
|
||||
const { collapsed, modelSource } = props;
|
||||
const intl = useIntl();
|
||||
const [dataSource, setDataSource] = useState<any>({
|
||||
@@ -58,7 +70,8 @@ const HFModelFile: React.FC<HFModelFileProps> = (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<HFModelFileProps> = (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<HFModelFileProps> = (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<HFModelFileProps> = (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<HFModelFileProps> = (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<HFModelFileProps> = (props) => {
|
||||
</SimpleBar>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default memo(HFModelFile);
|
||||
|
||||
@@ -23,7 +23,7 @@ interface HFModelItemProps {
|
||||
source?: string;
|
||||
tags?: string[];
|
||||
}
|
||||
const warningTask = ['image', 'audio', 'video'];
|
||||
const warningTask = ['audio', 'video'];
|
||||
|
||||
const SUPPORTEDSOURCE = [
|
||||
modelSourceMap.huggingface_value,
|
||||
|
||||
@@ -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<any>({});
|
||||
const [readmeText, setReadmeText] = useState<string | null>(null);
|
||||
const requestToken = useRef<any>(null);
|
||||
const axiosTokenRef = useRef<any>(null);
|
||||
const [isGGUFModel, setIsGGUFModel] = useState<boolean>(false);
|
||||
const [loading, setLoading] = useState<boolean>(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<{
|
||||
</Tag>
|
||||
)}
|
||||
</div>
|
||||
{readmeText && (
|
||||
{readmeText && isGGUFModel && (
|
||||
<div
|
||||
style={{
|
||||
borderRadius: 4,
|
||||
@@ -213,12 +225,10 @@ const ModelCard: React.FC<{
|
||||
maxHeight: collapsed ? 300 : 0
|
||||
}}
|
||||
>
|
||||
<HighlightCode
|
||||
code={readmeText}
|
||||
lang="markdown"
|
||||
copyable={false}
|
||||
<MarkdownViewer
|
||||
content={readmeText}
|
||||
theme="light"
|
||||
></HighlightCode>
|
||||
></MarkdownViewer>
|
||||
</SimpleBar>
|
||||
</div>
|
||||
)}
|
||||
@@ -230,6 +240,21 @@ const ModelCard: React.FC<{
|
||||
></Empty>
|
||||
)}
|
||||
</div>
|
||||
{!isGGUFModel && readmeText && (
|
||||
<div>
|
||||
<TitleWrapper>
|
||||
<div className="title">README.md</div>
|
||||
</TitleWrapper>
|
||||
<div className="card-wrapper">
|
||||
<Spin spinning={loading}>
|
||||
<MarkdownViewer
|
||||
content={readmeText}
|
||||
theme="light"
|
||||
></MarkdownViewer>
|
||||
</Spin>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<SearchInputProps> = (props) => {
|
||||
const cacheRepoOptions = useRef<any[]>([]);
|
||||
const axiosTokenRef = useRef<any>(null);
|
||||
const searchInputRef = useRef<any>('');
|
||||
const filterGGUFRef = useRef<boolean>(true);
|
||||
const modelFilesSortOptions = useRef<any[]>([
|
||||
{
|
||||
label: intl.formatMessage({ id: 'models.sort.trending' }),
|
||||
@@ -77,7 +78,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
||||
search: {
|
||||
query: searchInputRef.current || '',
|
||||
sort: sort,
|
||||
tags: ['gguf'],
|
||||
tags: filterGGUFRef.current ? ['gguf'] : [],
|
||||
task
|
||||
}
|
||||
};
|
||||
@@ -101,7 +102,9 @@ const SearchModel: React.FC<SearchInputProps> = (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<SearchInputProps> = (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<SearchInputProps> = (props) => {
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
<Select
|
||||
allowClear
|
||||
value={dataSource.sortType}
|
||||
onChange={handleSortChange}
|
||||
labelRender={({ label }) => {
|
||||
return (
|
||||
<span>
|
||||
{intl.formatMessage({ id: 'model.deploy.sort' })}: {label}
|
||||
</span>
|
||||
);
|
||||
}}
|
||||
options={modelFilesSortOptions.current}
|
||||
size="middle"
|
||||
style={{ width: '150px' }}
|
||||
></Select>
|
||||
<span>
|
||||
<Checkbox
|
||||
onChange={handleFilterGGUFChange}
|
||||
className="m-r-5"
|
||||
checked={filterGGUFRef.current}
|
||||
>
|
||||
GGUF
|
||||
</Checkbox>
|
||||
<Select
|
||||
allowClear
|
||||
value={dataSource.sortType}
|
||||
onChange={handleSortChange}
|
||||
labelRender={({ label }) => {
|
||||
return (
|
||||
<span>
|
||||
{intl.formatMessage({ id: 'model.deploy.sort' })}: {label}
|
||||
</span>
|
||||
);
|
||||
}}
|
||||
options={modelFilesSortOptions.current}
|
||||
size="middle"
|
||||
style={{ width: '150px' }}
|
||||
></Select>
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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<SearchResultProps> = (props) => {
|
||||
></IconFont>
|
||||
}
|
||||
description={
|
||||
<div className="flex-column gap-5">
|
||||
<span>
|
||||
{intl.formatMessage({ id: 'models.search.networkerror' })}
|
||||
</span>
|
||||
{/* <span>
|
||||
source === modelSourceMap.huggingface_value ? (
|
||||
<div className="flex-column gap-5">
|
||||
<span>
|
||||
{intl.formatMessage({ id: 'models.search.hfvisit' })}
|
||||
{intl.formatMessage({ id: 'models.search.networkerror' })}
|
||||
</span>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
href="https://huggingface.co/"
|
||||
target="_blank"
|
||||
>
|
||||
Hugging Face
|
||||
</Button>
|
||||
</span> */}
|
||||
</div>
|
||||
<span>
|
||||
<span>
|
||||
{intl.formatMessage({ id: 'models.search.hfvisit' })}
|
||||
</span>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
href="https://huggingface.co/"
|
||||
target="_blank"
|
||||
>
|
||||
Hugging Face
|
||||
</Button>
|
||||
</span>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -418,10 +418,10 @@ const Models: React.FC<ModelsProps> = ({
|
||||
|
||||
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}`;
|
||||
}, []);
|
||||
|
||||
@@ -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<AddModalProps> = (props) => {
|
||||
disabled={true}
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData>
|
||||
name="file_name"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: intl.formatMessage(
|
||||
{
|
||||
id: 'common.form.rule.input'
|
||||
},
|
||||
{ name: intl.formatMessage({ id: 'models.form.filename' }) }
|
||||
)
|
||||
}
|
||||
]}
|
||||
>
|
||||
<SealAutoComplete
|
||||
filterOption
|
||||
label={intl.formatMessage({ id: 'models.form.filename' })}
|
||||
required
|
||||
options={fileOptions}
|
||||
loading={loading}
|
||||
disabled={action === PageAction.EDIT}
|
||||
></SealAutoComplete>
|
||||
</Form.Item>
|
||||
{form.getFieldValue('file_name') && (
|
||||
<Form.Item<FormData>
|
||||
name="file_name"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: intl.formatMessage(
|
||||
{
|
||||
id: 'common.form.rule.input'
|
||||
},
|
||||
{ name: intl.formatMessage({ id: 'models.form.filename' }) }
|
||||
)
|
||||
}
|
||||
]}
|
||||
>
|
||||
<SealAutoComplete
|
||||
filterOption
|
||||
label={intl.formatMessage({ id: 'models.form.filename' })}
|
||||
required
|
||||
options={fileOptions}
|
||||
loading={loading}
|
||||
disabled={action === PageAction.EDIT}
|
||||
></SealAutoComplete>
|
||||
</Form.Item>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -418,7 +424,14 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
|
||||
></SealInput.TextArea>
|
||||
</Form.Item>
|
||||
|
||||
<AdvanceConfig form={form} gpuOptions={gpuOptions}></AdvanceConfig>
|
||||
<AdvanceConfig
|
||||
form={form}
|
||||
gpuOptions={gpuOptions}
|
||||
action={PageAction.EDIT}
|
||||
isGGUF={
|
||||
form.getFieldValue('backend') === backendOptionsMap.llamaBox
|
||||
}
|
||||
></AdvanceConfig>
|
||||
</Form>
|
||||
</SimpleBar>
|
||||
</Modal>
|
||||
|
||||
@@ -81,6 +81,11 @@ export const ollamaModelOptions = [
|
||||
}
|
||||
];
|
||||
|
||||
export const backendOptionsMap = {
|
||||
llamaBox: 'llama-box',
|
||||
vllm: 'vllm'
|
||||
};
|
||||
|
||||
export const modelSourceMap: Record<string, string> = {
|
||||
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<string, string> = {};
|
||||
const backendParameters = data.backend_parameters || [];
|
||||
backendParameters.forEach((item: string) => {
|
||||
const [key, value] = item.split('=');
|
||||
result[key] = value;
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
];
|
||||
@@ -24,6 +24,8 @@ export interface ListItem {
|
||||
}
|
||||
|
||||
export interface FormData {
|
||||
backend?: string;
|
||||
backend_parameters?: string[];
|
||||
source: string;
|
||||
repo_id: string;
|
||||
file_name: string;
|
||||
|
||||
@@ -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;
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ const GroundLeft: React.FC<MessageProps> = 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<MessageProps> = 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<MessageProps> = forwardRef((props, ref) => {
|
||||
setTokenResult(null);
|
||||
};
|
||||
|
||||
const handleSendMessage = (message: { role: string; content: string }) => {
|
||||
const handleSendMessage = (message: Omit<MessageItem, 'uid'>) => {
|
||||
console.log('message:', message);
|
||||
const currentMessage = message.content ? message : undefined;
|
||||
const currentMessage =
|
||||
message.content || message.imgs?.length ? message : undefined;
|
||||
submitMessage(currentMessage);
|
||||
};
|
||||
|
||||
|
||||
@@ -110,7 +110,9 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
||||
const inputRef = useRef<any>(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<MessageInputProps> = ({
|
||||
dataUrl: img
|
||||
};
|
||||
});
|
||||
// setImgList((pre) => {
|
||||
// return [...pre, ...list];
|
||||
// });
|
||||
setMessage({
|
||||
...message,
|
||||
imgs: [...(message.imgs || []), ...list]
|
||||
@@ -253,7 +252,7 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
||||
if (text) {
|
||||
setMessage?.({
|
||||
...message,
|
||||
content: text
|
||||
content: message.content + text
|
||||
});
|
||||
} else {
|
||||
getPasteContent(e);
|
||||
@@ -446,7 +445,7 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
||||
<div className="input-box">
|
||||
<TextArea
|
||||
ref={inputRef}
|
||||
autoSize={{ minRows: 3, maxRows: 3 }}
|
||||
autoSize={{ minRows: 3, maxRows: 8 }}
|
||||
onChange={(e) => handleInputChange(e.target.value)}
|
||||
value={message.content}
|
||||
size="large"
|
||||
|
||||
@@ -83,7 +83,7 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
||||
if (!chunk) {
|
||||
return;
|
||||
}
|
||||
if (_.get(chunk, 'choices.0.finish_reason')) {
|
||||
if (!_.get(chunk, 'choices', [].length)) {
|
||||
setTokenResult({
|
||||
...chunk?.usage
|
||||
});
|
||||
@@ -120,6 +120,9 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
||||
}
|
||||
]
|
||||
: [];
|
||||
setMessageList((preList) => {
|
||||
return [...preList, ...currentMessageRef.current];
|
||||
});
|
||||
console.log('currentMessageRef.current 1:', currentMessageRef.current);
|
||||
console.log('currentMessage==========4', messageList);
|
||||
const messages = _.map(
|
||||
@@ -208,12 +211,11 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleSubmit = (currentMessage: {
|
||||
role: string;
|
||||
content: string;
|
||||
}) => {
|
||||
console.log('currentMessage==========2', currentMessage);
|
||||
const currentMsg = currentMessage.content ? currentMessage : undefined;
|
||||
const handleSubmit = (currentMessage: Omit<MessageItem, 'uid'>) => {
|
||||
const currentMsg =
|
||||
currentMessage.content || currentMessage.imgs?.length
|
||||
? currentMessage
|
||||
: undefined;
|
||||
submitMessage(currentMsg);
|
||||
};
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ const ReferenceParams = (props: ReferenceParamsProps) => {
|
||||
if (!usage) {
|
||||
return null;
|
||||
}
|
||||
console.log('ReferenceParams usage:', usage);
|
||||
return (
|
||||
<div className="reference-params">
|
||||
<span className="usage">
|
||||
@@ -48,14 +49,18 @@ const ReferenceParams = (props: ReferenceParamsProps) => {
|
||||
<Tooltip
|
||||
title={
|
||||
<Space>
|
||||
<span>TPOT: {_.round(usage.time_per_output_token_ms, 2)} ms</span>
|
||||
<span>TTFT: {_.round(usage.time_to_first_token_ms, 2)} ms</span>
|
||||
<span>
|
||||
TPOT: {_.round(usage.time_per_output_token_ms, 2) || 0} ms
|
||||
</span>
|
||||
<span>
|
||||
TTFT: {_.round(usage.time_to_first_token_ms, 2) || 0} ms
|
||||
</span>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<span>
|
||||
{intl.formatMessage({ id: 'playground.tokenoutput' })}:{' '}
|
||||
{_.round(usage.tokens_per_second, 2)} Tokens/s
|
||||
{_.round(usage.tokens_per_second, 2) || 0} Tokens/s
|
||||
</span>
|
||||
</Tooltip>
|
||||
</span>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import EditorWrap from '@/components/editor-wrap';
|
||||
import HighlightCode from '@/components/highlight-code';
|
||||
import { BulbOutlined } from '@ant-design/icons';
|
||||
import Editor from '@monaco-editor/react';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button, Modal } from 'antd';
|
||||
import _ from 'lodash';
|
||||
@@ -16,6 +16,18 @@ type ViewModalProps = {
|
||||
onCancel: () => void;
|
||||
};
|
||||
|
||||
const langMap = {
|
||||
shell: 'bash',
|
||||
python: 'python',
|
||||
javascript: 'javascript'
|
||||
};
|
||||
|
||||
const langOptions = [
|
||||
{ label: 'Curl', value: langMap.shell },
|
||||
{ label: 'Python', value: langMap.python },
|
||||
{ label: 'Nodejs', value: langMap.javascript }
|
||||
];
|
||||
|
||||
const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
|
||||
const {
|
||||
title,
|
||||
@@ -31,7 +43,7 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
|
||||
const editorRef = useRef(null);
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const [codeValue, setCodeValue] = useState('');
|
||||
const [lang, setLang] = useState('shell');
|
||||
const [lang, setLang] = useState(langMap.shell);
|
||||
|
||||
const BaseURL = `${window.location.origin}/v1-openai`;
|
||||
const ClientType = apiType === 'chat' ? 'chat.completions' : 'embeddings';
|
||||
@@ -39,24 +51,6 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
|
||||
const logcommand =
|
||||
apiType === 'chat' ? 'choices[0].message.content' : 'data[0].embedding';
|
||||
|
||||
const langOptions = [
|
||||
{ label: 'Curl', value: 'shell' },
|
||||
{ label: 'Python', value: 'python' },
|
||||
{ label: 'Nodejs', value: 'javascript' }
|
||||
];
|
||||
|
||||
const formatCode = () => {
|
||||
if (editorRef.current) {
|
||||
setTimeout(() => {
|
||||
editorRef.current
|
||||
?.getAction?.('editor.action.formatDocument')
|
||||
?.run()
|
||||
.then(() => {
|
||||
console.log('format success');
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
const generateCode = () => {
|
||||
const systemList = systemMessage
|
||||
? [
|
||||
@@ -91,7 +85,7 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
|
||||
]
|
||||
};
|
||||
});
|
||||
if (lang === 'shell') {
|
||||
if (lang === langMap.shell) {
|
||||
const messages = [...systemList, ...formatMessageList];
|
||||
const code = `curl ${window.location.origin}/v1-openai/${api} \\\n-H "Content-Type: application/json" \\\n-H "Authorization: Bearer $\{YOUR_GPUSTACK_API_KEY}" \\\n-d '${JSON.stringify(
|
||||
{
|
||||
@@ -102,7 +96,7 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
|
||||
2
|
||||
)}'`;
|
||||
setCodeValue(code);
|
||||
} else if (lang === 'javascript') {
|
||||
} else if (lang === langMap.javascript) {
|
||||
const messages = [...systemList, ...formatMessageList];
|
||||
const code = `const OpenAI = require("openai");\n\nconst openai = new OpenAI({\n "apiKey": "YOUR_GPUSTACK_API_KEY",\n "baseURL": "${BaseURL}"\n});\n\nasync function main(){\n const params = ${JSON.stringify(
|
||||
{
|
||||
@@ -113,7 +107,7 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
|
||||
4
|
||||
)};\nconst response = await openai.${ClientType}.create(params);\n console.log(response.${logcommand});\n}\nmain();`;
|
||||
setCodeValue(code);
|
||||
} else if (lang === 'python') {
|
||||
} else if (lang === langMap.python) {
|
||||
const formattedParams = _.keys(parameters).reduce(
|
||||
(acc: string, key: string) => {
|
||||
if (parameters[key] === null) {
|
||||
@@ -138,20 +132,6 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
|
||||
const code = `from openai import OpenAI\n\nclient = OpenAI(\n base_url="${BaseURL}", \n api_key="YOUR_GPUSTACK_API_KEY"\n)\n\nresponse = client.${ClientType}.create(\n${formattedParams} ${messages})\nprint(response.${logcommand})`;
|
||||
setCodeValue(code);
|
||||
}
|
||||
formatCode();
|
||||
};
|
||||
const handleEditorDidMount = (editor: any, monaco: any) => {
|
||||
editorRef.current = editor;
|
||||
setLoaded(true);
|
||||
console.log('loaded====', editor, monaco);
|
||||
};
|
||||
|
||||
const handleBeforeMount = (monaco: any) => {
|
||||
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
|
||||
noSemanticValidation: false,
|
||||
noSyntaxValidation: false,
|
||||
diagnosticCodesToIgnore: [80001]
|
||||
});
|
||||
};
|
||||
|
||||
const handleOnChangeLang = (value: string) => {
|
||||
@@ -159,7 +139,7 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setLang('shell');
|
||||
setLang(langMap.shell);
|
||||
onCancel();
|
||||
};
|
||||
const editorConfig = {
|
||||
@@ -203,21 +183,22 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
|
||||
<EditorWrap
|
||||
copyText={codeValue}
|
||||
langOptions={langOptions}
|
||||
defaultValue="shell"
|
||||
showHeader={loaded}
|
||||
defaultValue={langMap.shell}
|
||||
showHeader={true}
|
||||
onChangeLang={handleOnChangeLang}
|
||||
styles={{
|
||||
wrapper: {
|
||||
backgroundColor: 'var(--color-editor-dark)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Editor
|
||||
<HighlightCode
|
||||
height={380}
|
||||
theme="vs-dark"
|
||||
className="monaco-editor"
|
||||
defaultLanguage="shell"
|
||||
language={lang}
|
||||
value={codeValue}
|
||||
options={editorConfig}
|
||||
beforeMount={handleBeforeMount}
|
||||
onMount={handleEditorDidMount}
|
||||
/>
|
||||
theme="dark"
|
||||
code={codeValue}
|
||||
lang={lang}
|
||||
copyable={false}
|
||||
></HighlightCode>
|
||||
</EditorWrap>
|
||||
<div
|
||||
style={{ marginTop: 10, display: 'flex', alignItems: 'baseline' }}
|
||||
|
||||
Reference in New Issue
Block a user