feat: vllm support

This commit is contained in:
jialin
2024-09-25 16:17:25 +08:00
parent 0652f850d6
commit 3cd96ba2e8
47 changed files with 1610 additions and 252 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ const CopyButton: React.FC<CopyButtonProps> = ({
text,
disabled,
type = 'text',
shape = 'circle',
shape = 'default',
fontSize = '14px',
style,
size = 'middle'
+8 -1
View File
@@ -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%);
+7 -1
View File
@@ -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}
+23 -5
View File
@@ -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);
+1 -1
View File
@@ -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 {
+97
View File
@@ -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);
+109
View File
@@ -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);
+43
View File
@@ -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;
}
}
+77
View File
@@ -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;
}
}
+126 -9
View File
@@ -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 {};
+3 -3
View File
@@ -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,