fix: playground edit message

This commit is contained in:
jialin
2024-09-25 16:17:25 +08:00
parent 3cd96ba2e8
commit a698c65a07
32 changed files with 338 additions and 190 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ const isProduction = env === 'production';
const t = Date.now(); const t = Date.now();
export default defineConfig({ export default defineConfig({
proxy: { proxy: {
...proxy() ...proxy('http://192.168.50.4')
}, },
history: { history: {
type: 'hash' type: 'hash'
@@ -60,11 +60,14 @@ const CodeViewer: React.FC<CodeViewerProps> = (props) => {
return ( return (
<pre <pre
className={classNames('code-pre custome-scrollbar ', { className={classNames(
dark: props.theme === 'dark', 'code-pre custome-scrollbar custom-scrollbar-horizontal ',
light: props.theme === 'light', {
copyable: copyable dark: props.theme === 'dark',
})} light: props.theme === 'light',
copyable: copyable
}
)}
style={{ style={{
height: height height: height
}} }}
@@ -12,17 +12,20 @@
} }
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
background-color: rgba(211, 211, 220, 50%); background-color: transparent;
border-radius: 6px; border-radius: 4px;
&:hover {
background-color: rgba(190, 190, 190, 100%);
}
} }
&::-webkit-scrollbar-track { &::-webkit-scrollbar-track {
background-color: transparent; background-color: transparent;
} }
&:hover {
&::-webkit-scrollbar-thumb {
background-color: var(--color-scrollbar-thumb);
border-radius: 4px;
}
}
} }
.code-pre { .code-pre {
@@ -30,6 +33,10 @@
position: relative; position: relative;
border-radius: var(--border-radius-mini); border-radius: var(--border-radius-mini);
code {
line-height: 1.5;
}
&.copyable { &.copyable {
padding-inline: 12px 32px; padding-inline: 12px 32px;
} }
+3 -1
View File
@@ -18,7 +18,7 @@
h4 { h4 {
font-weight: 700; font-weight: 700;
font-weight: var(--font-weight-bold); font-weight: var(--font-weight-bold);
font-size: 14px; font-size: var(--font-size-small);
} }
.hj-wrapper { .hj-wrapper {
@@ -50,12 +50,14 @@
line-height: 2; line-height: 2;
padding-inline: 6px; padding-inline: 6px;
border-bottom: 1px solid var(--ant-color-split); border-bottom: 1px solid var(--ant-color-split);
word-break: break-word;
} }
td { td {
line-height: 2; line-height: 2;
padding-inline: 6px; padding-inline: 6px;
border-bottom: 1px solid var(--ant-color-split); border-bottom: 1px solid var(--ant-color-split);
word-break: break-word;
} }
div { div {
+12 -6
View File
@@ -1,7 +1,8 @@
import { EyeOutlined } from '@ant-design/icons'; import { EyeOutlined } from '@ant-design/icons';
import { Image, Typography } from 'antd'; import { Image, Typography } from 'antd';
import { unescape } from 'lodash';
import { TokensList, marked } from 'marked'; import { TokensList, marked } from 'marked';
import React, { Fragment } from 'react'; import React, { Fragment, useCallback } from 'react';
import HighlightCode from '../highlight-code'; import HighlightCode from '../highlight-code';
import './index.less'; import './index.less';
@@ -34,12 +35,13 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
'list', 'list',
'list_item', 'list_item',
'br', 'br',
'html' 'html',
'escape'
]; ];
const renderItem = (token: any, render: any) => { const renderItem = useCallback((token: any, render: any) => {
// console.log('token======66==', token.raw, token.type);
if (!reDefineTypes.includes(token.type)) { if (!reDefineTypes.includes(token.type)) {
console.log('token======66==', token.type, token);
return ( return (
<span <span
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
@@ -53,7 +55,11 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
if (token.tokens?.length) { if (token.tokens?.length) {
child = render?.(token.tokens as TokensList, render); child = render?.(token.tokens as TokensList, render);
} }
const text = child ? child : token.text; const text = child ? child : unescape(token.text);
if (token.type === 'escape') {
htmlstr = text;
}
if (token.type === 'html') { if (token.type === 'html') {
htmlstr = <div dangerouslySetInnerHTML={{ __html: token.text }} />; htmlstr = <div dangerouslySetInnerHTML={{ __html: token.text }} />;
@@ -125,7 +131,7 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
} }
return htmlstr; return htmlstr;
}; }, []);
const renderTokens = (tokens: TokensList): any => { const renderTokens = (tokens: TokensList): any => {
return tokens?.map((token: any, index: number) => { return tokens?.map((token: any, index: number) => {
return <Fragment key={index}>{renderItem(token, renderTokens)}</Fragment>; return <Fragment key={index}>{renderItem(token, renderTokens)}</Fragment>;
+16
View File
@@ -0,0 +1,16 @@
const htmlUnescapes: Record<string, string> = {
'&amp;': '&',
'&lt;': '<',
'&gt;': '>',
'&quot;': '"',
'&#39;': "'"
};
const reEscapedHtml = /&(?:amp|lt|gt|quot|#(?:0+)?39);/g;
const reHasEscapedHtml = RegExp(reEscapedHtml.source);
export const unescape = (str = '') => {
return reHasEscapedHtml.test(str)
? str.replace(reEscapedHtml, (entity) => htmlUnescapes[entity] || "'")
: str;
};
+1 -1
View File
@@ -5,7 +5,7 @@ import Wrapper from './components/wrapper';
import { SealFormItemProps } from './types'; import { SealFormItemProps } from './types';
const SealAutoComplete: React.FC< const SealAutoComplete: React.FC<
AutoCompleteProps & SealFormItemProps & { onInput: (e: Event) => void } AutoCompleteProps & SealFormItemProps & { onInput?: (e: Event) => void }
> = (props) => { > = (props) => {
const { const {
label, label,
+1
View File
@@ -42,6 +42,7 @@ html {
--color-text-3: rgba(0, 0, 0, 45%); --color-text-3: rgba(0, 0, 0, 45%);
--color-text-2: rgba(0, 0, 0, 65%); --color-text-2: rgba(0, 0, 0, 65%);
--color-bg-light-1: #e6f6ff; --color-bg-light-1: #e6f6ff;
--font-size-small: 13px;
--font-size-base: 12px; --font-size-base: 12px;
--font-size-large: 16px; --font-size-large: 16px;
--font-size-middle: 14px; --font-size-middle: 14px;
+4 -1
View File
@@ -40,5 +40,8 @@ export default {
'playground.toolbar.compare2Model': '2-Model Compare', 'playground.toolbar.compare2Model': '2-Model Compare',
'playground.toolbar.compare3Model': '3-Model Compare', 'playground.toolbar.compare3Model': '3-Model Compare',
'playground.toolbar.compare4Model': '4-Model Compare', 'playground.toolbar.compare4Model': '4-Model Compare',
'playground.toolbar.compare6Model': '6-Model Compare' 'playground.toolbar.compare6Model': '6-Model Compare',
'playground.input.holder': 'Type <kbd>/</kbd> to input message',
'playground.compare.apply': 'Apply',
'playground.compare.applytoall': 'Apply to all models'
}; };
+4 -1
View File
@@ -40,5 +40,8 @@ export default {
'playground.toolbar.compare2Model': '2 模型对比', 'playground.toolbar.compare2Model': '2 模型对比',
'playground.toolbar.compare3Model': '3 模型对比', 'playground.toolbar.compare3Model': '3 模型对比',
'playground.toolbar.compare4Model': '4 模型对比', 'playground.toolbar.compare4Model': '4 模型对比',
'playground.toolbar.compare6Model': '6 模型对比' 'playground.toolbar.compare6Model': '6 模型对比',
'playground.input.holder': '按 <kbd>/</kbd> 开始输入',
'playground.compare.apply': '应用',
'playground.compare.applytoall': '应用到所有模型'
}; };
@@ -1,7 +1,5 @@
import LabelSelector from '@/components/label-selector'; import LabelSelector from '@/components/label-selector';
import ListInput from '@/components/list-input';
import SealSelect from '@/components/seal-form/seal-select'; import SealSelect from '@/components/seal-form/seal-select';
import { PageAction } from '@/config';
import { PageActionType } from '@/config/types'; import { PageActionType } from '@/config/types';
import { InfoCircleOutlined, RightOutlined } from '@ant-design/icons'; import { InfoCircleOutlined, RightOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
@@ -193,7 +191,7 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
</Form.Item> </Form.Item>
</> </>
)} )}
<Form.Item name="backend"> {/* <Form.Item name="backend">
<SealSelect <SealSelect
label={intl.formatMessage({ id: 'models.form.backend' })} label={intl.formatMessage({ id: 'models.form.backend' })}
options={[ options={[
@@ -210,8 +208,8 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
]} ]}
disabled={action === PageAction.EDIT} disabled={action === PageAction.EDIT}
></SealSelect> ></SealSelect>
</Form.Item> </Form.Item> */}
<Form.Item<FormData> name="backend_parameters"> {/* <Form.Item<FormData> name="backend_parameters">
<ListInput <ListInput
btnText="common.button.addParams" btnText="common.button.addParams"
label={intl.formatMessage({ id: 'models.form.backend_parameters' })} label={intl.formatMessage({ id: 'models.form.backend_parameters' })}
@@ -219,7 +217,7 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
onChange={handleBackendParametersChange} onChange={handleBackendParametersChange}
options={paramsConfig} options={paramsConfig}
></ListInput> ></ListInput>
</Form.Item> </Form.Item> */}
{scheduleType === 'manual' && ( {scheduleType === 'manual' && (
<Form.Item<FormData> <Form.Item<FormData>
name="gpu_selector" name="gpu_selector"
@@ -220,6 +220,7 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
> >
<SealAutoComplete <SealAutoComplete
filterOption filterOption
defaultActiveFirstOption
disabled={action === PageAction.EDIT} disabled={action === PageAction.EDIT}
label={intl.formatMessage({ id: 'model.form.ollama.model' })} label={intl.formatMessage({ id: 'model.form.ollama.model' })}
placeholder={intl.formatMessage({ id: 'model.form.ollamaholder' })} placeholder={intl.formatMessage({ id: 'model.form.ollamaholder' })}
+1 -1
View File
@@ -35,4 +35,4 @@ const GPUCard: React.FC<{
); );
}; };
export default GPUCard; export default React.memo(GPUCard);
@@ -159,7 +159,7 @@ const HFModelFile: React.FC<HFModelFileProps> = forwardRef((props, ref) => {
} }
); );
const fileList = _.filter(_.get(data, ['Data', 'Files']), (file: any) => { const fileList = _.filter(_.get(data, ['Data', 'Files']), (file: any) => {
return filterReg.test(file.path) || _.includes(includeReg, file.path); return filterReg.test(file.Path) || _.includes(includeReg, file.Path);
}); });
const list = _.map(fileList, (item: any) => { const list = _.map(fileList, (item: any) => {
return { return {
@@ -192,6 +192,7 @@ const HFModelFile: React.FC<HFModelFileProps> = forwardRef((props, ref) => {
} }
const newList = generateGroupByFilename(list); const newList = generateGroupByFilename(list);
console.log('newList====', newList);
const sortList = _.sortBy(newList, (item: any) => { const sortList = _.sortBy(newList, (item: any) => {
return sortType === 'size' ? item.size : item.path; return sortType === 'size' ? item.size : item.path;
}); });
@@ -199,6 +200,7 @@ const HFModelFile: React.FC<HFModelFileProps> = forwardRef((props, ref) => {
handleSelectModelFile(sortList[0]); handleSelectModelFile(sortList[0]);
setDataSource({ fileList: sortList, loading: false }); setDataSource({ fileList: sortList, loading: false });
} catch (error) { } catch (error) {
console.log('error======', error);
setDataSource({ fileList: [], loading: false }); setDataSource({ fileList: [], loading: false });
handleSelectModelFile({}); handleSelectModelFile({});
} }
@@ -210,6 +210,7 @@ const InstanceItem: React.FC<InstanceItemProps> = ({
)} )}
{item?.distributed_servers?.rpc_servers?.length && ( {item?.distributed_servers?.rpc_servers?.length && (
<Tooltip <Tooltip
trigger={['click']}
overlayInnerStyle={{ overlayInnerStyle={{
width: '400px' width: '400px'
}} }}
+11 -5
View File
@@ -31,7 +31,7 @@ const ModelCard: React.FC<{
const { onCollapse, setIsGGUF, collapsed, modelSource } = props; const { onCollapse, setIsGGUF, collapsed, modelSource } = props;
const intl = useIntl(); const intl = useIntl();
const requestSource = useRequestToken(); const requestSource = useRequestToken();
const [modelData, setModelData] = useState<any>({}); const [modelData, setModelData] = useState<any>(null);
const [readmeText, setReadmeText] = useState<string | null>(null); const [readmeText, setReadmeText] = useState<string | null>(null);
const requestToken = useRef<any>(null); const requestToken = useRef<any>(null);
const axiosTokenRef = useRef<any>(null); const axiosTokenRef = useRef<any>(null);
@@ -76,7 +76,7 @@ const ModelCard: React.FC<{
setIsGGUF(modelcard.tags?.includes('gguf')); setIsGGUF(modelcard.tags?.includes('gguf'));
setIsGGUFModel(modelcard.tags?.includes('gguf')); setIsGGUFModel(modelcard.tags?.includes('gguf'));
} catch (error) { } catch (error) {
setModelData({}); setModelData(null);
setReadmeText(null); setReadmeText(null);
setIsGGUF(false); setIsGGUF(false);
setIsGGUFModel(false); setIsGGUFModel(false);
@@ -101,7 +101,7 @@ const ModelCard: React.FC<{
setIsGGUF(data.Data?.Tags?.includes('gguf')); setIsGGUF(data.Data?.Tags?.includes('gguf'));
setIsGGUFModel(data.Data?.Tags?.includes('gguf')); setIsGGUFModel(data.Data?.Tags?.includes('gguf'));
} catch (error) { } catch (error) {
setModelData({}); setModelData(null);
setReadmeText(null); setReadmeText(null);
setIsGGUF(false); setIsGGUF(false);
setIsGGUFModel(false); setIsGGUFModel(false);
@@ -140,7 +140,7 @@ const ModelCard: React.FC<{
size="small" size="small"
type="link" type="link"
target="_blank" target="_blank"
href={`https://huggingface.co/${modelData.id}`} href={`https://huggingface.co/${modelData?.id}`}
> >
<IconFont type="icon-external-link"></IconFont> <IconFont type="icon-external-link"></IconFont>
</Button> </Button>
@@ -155,7 +155,7 @@ const ModelCard: React.FC<{
size="small" size="small"
type="link" type="link"
target="_blank" target="_blank"
href={`https://modelscope.cn/models/${modelData.name}`} href={`https://modelscope.cn/models/${modelData?.name}`}
> >
<IconFont type="icon-external-link"></IconFont> <IconFont type="icon-external-link"></IconFont>
</Button> </Button>
@@ -202,6 +202,11 @@ const ModelCard: React.FC<{
</span> </span>
</Tag> </Tag>
)} )}
{isGGUFModel && (
<Tag className="tag-item" color="magenta">
<span style={{ opacity: 0.65 }}>GGUF</span>
</Tag>
)}
</div> </div>
{readmeText && isGGUFModel && ( {readmeText && isGGUFModel && (
<div <div
@@ -222,6 +227,7 @@ const ModelCard: React.FC<{
</span> </span>
<SimpleBar <SimpleBar
style={{ style={{
paddingTop: collapsed ? 12 : 0,
maxHeight: collapsed ? 300 : 0 maxHeight: collapsed ? 300 : 0
}} }}
> >
@@ -32,12 +32,6 @@ const SearchInput: React.FC<{
)} )}
prefix={ prefix={
<> <>
{/* <SearchOutlined
style={{
fontSize: '16px',
color: 'var(--ant-color-text-quaternary)'
}}
/> */}
<IconFont <IconFont
className="font-size-16" className="font-size-16"
type={ type={
+10 -8
View File
@@ -1,6 +1,6 @@
import { BulbOutlined } from '@ant-design/icons'; import { BulbOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Checkbox, Select } from 'antd'; import { Select } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
import React, { useCallback, useEffect, useRef, useState } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react';
import { queryHuggingfaceModels, queryModelScopeModels } from '../apis'; import { queryHuggingfaceModels, queryModelScopeModels } from '../apis';
@@ -78,7 +78,8 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
search: { search: {
query: searchInputRef.current || '', query: searchInputRef.current || '',
sort: sort, sort: sort,
tags: filterGGUFRef.current ? ['gguf'] : [], // tags: filterGGUFRef.current ? ['gguf'] : [],
tags: ['gguf'],
task task
} }
}; };
@@ -102,9 +103,10 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
const getModelsFromModelscope = useCallback(async (sort: string) => { const getModelsFromModelscope = useCallback(async (sort: string) => {
try { try {
const params = { const params = {
Name: filterGGUFRef.current // Name: filterGGUFRef.current
? `${searchInputRef.current} gguf` // ? `${searchInputRef.current} gguf`
: searchInputRef.current || '', // : searchInputRef.current || '',
Name: `${searchInputRef.current} gguf`,
SortBy: ModelScopeSortType[sort] SortBy: ModelScopeSortType[sort]
}; };
const data = await queryModelScopeModels(params, { const data = await queryModelScopeModels(params, {
@@ -115,7 +117,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
path: item.Path, path: item.Path,
name: `${item.Path}/${item.Name}`, name: `${item.Path}/${item.Name}`,
downloads: item.Downloads, downloads: item.Downloads,
id: item.Name, id: `${item.Path}/${item.Name}`,
updatedAt: item.LastUpdatedTime * 1000, updatedAt: item.LastUpdatedTime * 1000,
likes: item.Stars, likes: item.Stars,
value: item.Name, value: item.Name,
@@ -231,13 +233,13 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
</span> </span>
</span> </span>
<span> <span>
<Checkbox {/* <Checkbox
onChange={handleFilterGGUFChange} onChange={handleFilterGGUFChange}
className="m-r-5" className="m-r-5"
checked={filterGGUFRef.current} checked={filterGGUFRef.current}
> >
GGUF GGUF
</Checkbox> </Checkbox> */}
<Select <Select
allowClear allowClear
value={dataSource.sortType} value={dataSource.sortType}
@@ -20,7 +20,6 @@ interface SearchResultProps {
} }
const SearchResult: React.FC<SearchResultProps> = (props) => { const SearchResult: React.FC<SearchResultProps> = (props) => {
console.log('SearchResult======');
const { resultList, onSelect, source, networkError } = props; const { resultList, onSelect, source, networkError } = props;
const intl = useIntl(); const intl = useIntl();
@@ -265,7 +265,6 @@ const Models: React.FC<ModelsProps> = ({
); );
const handleModalCancel = useCallback(() => { const handleModalCancel = useCallback(() => {
console.log('handleModalCancel');
setOpenAddModal(false); setOpenAddModal(false);
}, []); }, []);
+3 -3
View File
@@ -32,7 +32,7 @@ export interface FormData {
huggingface_repo_id: string; huggingface_repo_id: string;
huggingface_filename: string; huggingface_filename: string;
s3_address: string; s3_address: string;
ollama_library_model_name: 'string'; ollama_library_model_name: string;
distributed_inference_across_workers?: boolean; distributed_inference_across_workers?: boolean;
model_scope_model_id?: string; model_scope_model_id?: string;
model_scope_file_path?: string; model_scope_file_path?: string;
@@ -85,8 +85,8 @@ export interface ModelInstanceFormData {
model_id: number; model_id: number;
model_name: string; model_name: string;
source: string; source: string;
huggingface_repo_id: 'string'; huggingface_repo_id: string;
huggingface_filename: 'string'; huggingface_filename: string;
} }
export interface GPUListItem { export interface GPUListItem {
@@ -123,11 +123,11 @@ const MessageInput: React.FC<MessageInputProps> = ({
}); });
}; };
const handleInputChange = (value: string) => { const handleInputChange = (e: any) => {
console.log('input change:', value); console.log('input change:', e.target?.value);
setMessage({ setMessage({
...message, ...message,
content: value content: e.target?.value
}); });
}; };
const handleSendMessage = () => { const handleSendMessage = () => {
@@ -177,54 +177,57 @@ const MessageInput: React.FC<MessageInputProps> = ({
resetMessage(); resetMessage();
}; };
const getPasteContent = useCallback(async (event: any) => { const getPasteContent = useCallback(
const clipboardData = event.clipboardData || window.clipboardData; async (event: any) => {
const items = clipboardData.items; const clipboardData = event.clipboardData || window.clipboardData;
const imgPromises: Promise<string>[] = []; const items = clipboardData.items;
const imgPromises: Promise<string>[] = [];
for (let i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
let item = items[i]; let item = items[i];
console.log('item===========', item);
if (item.kind === 'file' && item.type.indexOf('image') !== -1) { if (item.kind === 'file' && item.type.indexOf('image') !== -1) {
const file = item.getAsFile(); const file = item.getAsFile();
const imgPromise = new Promise<string>((resolve, reject) => { const imgPromise = new Promise<string>((resolve, reject) => {
const reader = new FileReader(); const reader = new FileReader();
reader.onload = function (event) { reader.onload = function (event) {
const base64String = event.target?.result as string; const base64String = event.target?.result as string;
if (base64String) { if (base64String) {
resolve(base64String); resolve(base64String);
} else { } else {
reject('Failed to convert image to base64'); reject('Failed to convert image to base64');
} }
}; };
reader.readAsDataURL(file); reader.readAsDataURL(file);
}); });
imgPromises.push(imgPromise); imgPromises.push(imgPromise);
} else if (item.kind === 'string') { } else if (item.kind === 'string') {
// string // string
}
} }
}
try { try {
const imgs = await Promise.all(imgPromises); const imgs = await Promise.all(imgPromises);
if (imgs.length) {
const list = _.map(imgs, (img: string) => { if (imgs.length) {
imgCountRef.current += 1; const list = _.map(imgs, (img: string) => {
return { imgCountRef.current += 1;
uid: imgCountRef.current, return {
dataUrl: img uid: imgCountRef.current,
}; dataUrl: img
}); };
setMessage({ });
...message, setMessage({
imgs: [...(message.imgs || []), ...list] ...message,
}); imgs: [...(message.imgs || []), ...list]
});
}
} catch (error) {
console.error('Error processing images:', error);
} }
} catch (error) { },
console.error('Error processing images:', error); [message]
} );
}, []);
// ========== upload image ========== // ========== upload image ==========
const handleUpdateImgList = ( const handleUpdateImgList = (
@@ -248,6 +251,7 @@ const MessageInput: React.FC<MessageInputProps> = ({
}; };
const handleOnPaste = (e: any) => { const handleOnPaste = (e: any) => {
e.preventDefault();
const text = e.clipboardData.getData('text'); const text = e.clipboardData.getData('text');
if (text) { if (text) {
setMessage?.({ setMessage?.({
@@ -446,7 +450,7 @@ const MessageInput: React.FC<MessageInputProps> = ({
<TextArea <TextArea
ref={inputRef} ref={inputRef}
autoSize={{ minRows: 3, maxRows: 8 }} autoSize={{ minRows: 3, maxRows: 8 }}
onChange={(e) => handleInputChange(e.target.value)} onChange={handleInputChange}
value={message.content} value={message.content}
size="large" size="large"
variant="borderless" variant="borderless"
@@ -456,9 +460,12 @@ const MessageInput: React.FC<MessageInputProps> = ({
onPaste={handleOnPaste} onPaste={handleOnPaste}
></TextArea> ></TextArea>
{!message.content && !focused && ( {!message.content && !focused && (
<span className="holder"> <span
Type <kbd>/</kbd> to input message className="holder"
</span> dangerouslySetInnerHTML={{
__html: intl.formatMessage({ id: 'playground.input.holder' })
}}
></span>
)} )}
</div> </div>
<PromptModal <PromptModal
@@ -48,74 +48,75 @@ const ContentItem: React.FC<MessageItemProps> = ({
}); });
}; };
const getPasteContent = useCallback(async (event: any) => { const getPasteContent = useCallback(
const clipboardData = event.clipboardData || window.clipboardData; async (event: any) => {
const items = clipboardData.items; const clipboardData = event.clipboardData || window.clipboardData;
const imgPromises: Promise<string>[] = []; const items = clipboardData.items;
const imgPromises: Promise<string>[] = [];
for (let i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
let item = items[i]; let item = items[i];
console.log('item===========', item); console.log('item===========', item);
if (item.kind === 'file' && item.type.indexOf('image') !== -1) { if (item.kind === 'file' && item.type.indexOf('image') !== -1) {
const file = item.getAsFile(); const file = item.getAsFile();
const imgPromise = new Promise<string>((resolve, reject) => { const imgPromise = new Promise<string>((resolve, reject) => {
const reader = new FileReader(); const reader = new FileReader();
reader.onload = function (event) { reader.onload = function (event) {
const base64String = event.target?.result as string; const base64String = event.target?.result as string;
if (base64String) { if (base64String) {
resolve(base64String); resolve(base64String);
} else { } else {
reject('Failed to convert image to base64'); reject('Failed to convert image to base64');
} }
}; };
reader.readAsDataURL(file); reader.readAsDataURL(file);
}); });
imgPromises.push(imgPromise); imgPromises.push(imgPromise);
} else if (item.kind === 'string') { } else if (item.kind === 'string') {
// string // string
}
} }
}
try { try {
const imgs = await Promise.all(imgPromises); const imgs = await Promise.all(imgPromises);
if (imgs.length) { if (imgs.length) {
const list = _.map(imgs, (img: string) => { const list = _.map(imgs, (img: string) => {
imgCountRef.current += 1; imgCountRef.current += 1;
return { return {
uid: imgCountRef.current, uid: imgCountRef.current,
dataUrl: img dataUrl: img
}; };
}); });
updateMessage?.({ updateMessage?.({
role: data.role, role: data.role,
content: data.content, content: data.content,
uid: data.uid, uid: data.uid,
imgs: [...(data.imgs || []), ...list] imgs: [...(data.imgs || []), ...list]
}); });
} }
} catch (error) { } catch (error) {
console.error('Error processing images:', error); console.error('Error processing images:', error);
}
}, []);
const handleOnPaste = useCallback(
(e: any) => {
const text = e.clipboardData.getData('text');
if (text) {
updateMessage?.({
role: data.role,
content: inputRef.current?.resizableTextArea?.textArea?.value || '',
uid: data.uid
});
} else {
getPasteContent(e);
} }
}, },
[getPasteContent, data, updateMessage] [data]
); );
const handleOnPaste = (e: any) => {
e.preventDefault();
const text = e.clipboardData.getData('text');
if (text) {
updateMessage?.({
role: data.role,
content: data.content + text,
uid: data.uid
});
} else {
getPasteContent(e);
}
};
const handleUpdateImgList = useCallback( const handleUpdateImgList = useCallback(
(list: { uid: number | string; dataUrl: string }[]) => { (list: { uid: number | string; dataUrl: string }[]) => {
console.log('list===========', data.imgs, list); console.log('list===========', data.imgs, list);
@@ -111,16 +111,38 @@ const MultiCompare: React.FC<MultiCompareProps> = ({ modelList }) => {
}); });
}; };
const adjustSpan = () => {
const count = spans.count - 1;
if (spans.count === 6) {
setSpans({
span: 8,
count: count
});
} else if (spans.count === 5) {
setSpans({
span: 12,
count: count
});
} else if (spans.count === 4) {
setSpans({
span: 8,
count: count
});
} else if (spans.count === 3) {
setSpans({
span: 12,
count: count
});
}
};
const handleDeleteModel = (instanceId: symbol) => { const handleDeleteModel = (instanceId: symbol) => {
const newModelList = modelSelections.filter( const newModelList = modelSelections.filter(
(model) => model.instanceId !== instanceId (model) => model.instanceId !== instanceId
); );
pruneInstanceSymbol(instanceId); pruneInstanceSymbol(instanceId);
const span = Math.floor(24 / (24 / spans.span - 1));
setSpans({ adjustSpan();
span,
count: spans.count
});
setModelSelections(newModelList); setModelSelections(newModelList);
}; };
@@ -143,6 +165,14 @@ const MultiCompare: React.FC<MultiCompareProps> = ({ modelList }) => {
setModelSelections(updateList); setModelSelections(updateList);
}; };
const handleApplySystemChangeToAll = (message: string) => {
const modelRefList = Object.getOwnPropertySymbols(modelRefs.current);
modelRefList.forEach((instanceId: symbol) => {
const ref = modelRefs.current[instanceId];
ref?.setSystemMessage(message);
});
};
const handlePresetPrompt = (list: { role: string; content: string }[]) => { const handlePresetPrompt = (list: { role: string; content: string }[]) => {
const sysMsg = list.filter((item) => item.role === 'system'); const sysMsg = list.filter((item) => item.role === 'system');
const userMsg = list.filter((item) => item.role === 'user'); const userMsg = list.filter((item) => item.role === 'user');
@@ -227,6 +257,8 @@ const MultiCompare: React.FC<MultiCompareProps> = ({ modelList }) => {
spans, spans,
globalParams, globalParams,
loadingStatus, loadingStatus,
modelFullList: modelList,
handleApplySystemChangeToAll,
setGlobalParams, setGlobalParams,
setLoadingStatus: handleSetLoadingStatus, setLoadingStatus: handleSetLoadingStatus,
handleDeleteModel: handleDeleteModel handleDeleteModel: handleDeleteModel
@@ -252,7 +284,7 @@ const MultiCompare: React.FC<MultiCompareProps> = ({ modelList }) => {
setModelSelections={handleUpdateModelSelections} setModelSelections={handleUpdateModelSelections}
presetPrompt={handlePresetPrompt} presetPrompt={handlePresetPrompt}
modelList={modelFullList} modelList={modelFullList}
showModelSelection={true} showModelSelection={false}
/> />
</div> </div>
</div> </div>
@@ -47,6 +47,8 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
setGlobalParams, setGlobalParams,
setLoadingStatus, setLoadingStatus,
handleDeleteModel, handleDeleteModel,
handleApplySystemChangeToAll,
modelFullList,
loadingStatus loadingStatus
} = useContext(CompareContext); } = useContext(CompareContext);
const intl = useIntl(); const intl = useIntl();
@@ -224,7 +226,7 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
isApplyToAllModels.current = e.target.checked; isApplyToAllModels.current = e.target.checked;
if (e.target.checked) { if (e.target.checked) {
setGlobalParams({ setGlobalParams({
...params ..._.omit(params, 'model')
}); });
} }
}; };
@@ -289,10 +291,10 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
}; };
const modelOptions = useMemo(() => { const modelOptions = useMemo(() => {
return modelList.filter((item) => { return modelFullList.filter((item) => {
return item.type !== 'empty'; return item.type !== 'empty';
}); });
}, [modelList]); }, [modelFullList]);
useEffect(() => { useEffect(() => {
console.log('globalParams:', globalParams.model, globalParams); console.log('globalParams:', globalParams.model, globalParams);
@@ -326,9 +328,9 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
<div className="header"> <div className="header">
<span className="title"> <span className="title">
<Select <Select
style={{ minWidth: '100px' }} style={{ minWidth: '120px' }}
variant="borderless" variant="borderless"
options={modelOptions} options={modelFullList}
onChange={handleModelChange} onChange={handleModelChange}
value={params.model} value={params.model}
></Select> ></Select>
@@ -379,7 +381,9 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
title={ title={
<div> <div>
<Checkbox onChange={handleApplyToAllModels}> <Checkbox onChange={handleApplyToAllModels}>
Apply to all models {intl.formatMessage({
id: 'playground.compare.applytoall'
})}
</Checkbox> </Checkbox>
</div> </div>
} }
@@ -401,7 +405,9 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
</span> </span>
</div> </div>
<SystemMessage <SystemMessage
showApplyToAll={true}
systemMessage={systemMessage} systemMessage={systemMessage}
applyToAll={handleApplySystemChangeToAll}
setSystemMessage={setSystemMessage} setSystemMessage={setSystemMessage}
></SystemMessage> ></SystemMessage>
<SimpleBar style={{ maxHeight: maxHeight }}> <SimpleBar style={{ maxHeight: maxHeight }}>
@@ -1,18 +1,23 @@
import { CloseOutlined } from '@ant-design/icons'; import { CloseOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Button, Divider, Input, Tooltip } from 'antd'; import { Button, Checkbox, Divider, Input, Tooltip } from 'antd';
import classNames from 'classnames';
import React, { useState } from 'react'; import React, { useState } from 'react';
import '../../style/sys-message.less'; import '../../style/sys-message.less';
interface SystemMessageProps { interface SystemMessageProps {
style?: React.CSSProperties; style?: React.CSSProperties;
systemMessage: string; systemMessage: string;
showApplyToAll?: boolean;
applyToAll?: (e: any) => void;
setSystemMessage: (value: string) => void; setSystemMessage: (value: string) => void;
} }
const SystemMessage: React.FC<SystemMessageProps> = (props) => { const SystemMessage: React.FC<SystemMessageProps> = (props) => {
const { systemMessage, setSystemMessage, style } = props; const { systemMessage, showApplyToAll, setSystemMessage, style, applyToAll } =
props;
const intl = useIntl(); const intl = useIntl();
const [isChange, setIsChange] = useState(false);
const systemMessageRef = React.useRef<any>(null); const systemMessageRef = React.useRef<any>(null);
const [autoSize, setAutoSize] = useState<{ const [autoSize, setAutoSize] = useState<{
minRows: number; minRows: number;
@@ -33,26 +38,51 @@ const SystemMessage: React.FC<SystemMessageProps> = (props) => {
}, 100); }, 100);
}; };
const handleBlur = () => { const handleBlur = (e: any) => {
setAutoSize({ setAutoSize({
minRows: 1, minRows: 1,
maxRows: 1, maxRows: 1,
focus: false focus: false
}); });
setIsChange(false);
};
const handleOnChange = (e: any) => {
setSystemMessage(e.target.value);
setIsChange(true);
}; };
const handleClearSystemMessage = () => { const handleClearSystemMessage = () => {
setSystemMessage(''); setSystemMessage('');
}; };
const handleClickCheckbox = (e?: any) => {
e.preventDefault();
};
const handleApplyToAllModels = (e: any) => {
if (e.target.checked) {
applyToAll?.(systemMessage);
}
};
return ( return (
<div className="sys-message" style={{ ...style }}> <div
className={classNames('sys-message', {
focus: autoSize.focus
})}
style={{ ...style }}
>
{ {
<div style={{ display: autoSize.focus ? 'block' : 'none' }}> <div
style={{ display: autoSize.focus ? 'block' : 'none' }}
className="textarea-wrapper"
>
<span className="system-label"> <span className="system-label">
{intl.formatMessage({ id: 'playground.systemMessage' })} {intl.formatMessage({ id: 'playground.systemMessage' })}
</span> </span>
<Input.TextArea <Input.TextArea
className="custome-scrollbar"
ref={systemMessageRef} ref={systemMessageRef}
variant="filled" variant="filled"
placeholder={intl.formatMessage({ id: 'playground.system.tips' })} placeholder={intl.formatMessage({ id: 'playground.system.tips' })}
@@ -68,8 +98,17 @@ const SystemMessage: React.FC<SystemMessageProps> = (props) => {
onFocus={handleFocus} onFocus={handleFocus}
onBlur={handleBlur} onBlur={handleBlur}
allowClear={false} allowClear={false}
onChange={(e) => setSystemMessage(e.target.value)} onChange={handleOnChange}
></Input.TextArea> ></Input.TextArea>
{isChange && showApplyToAll && (
<span className="apply-check" onMouseDown={handleClickCheckbox}>
<Checkbox onChange={handleApplyToAllModels}>
{intl.formatMessage({
id: 'playground.compare.applytoall'
})}
</Checkbox>
</span>
)}
<Divider style={{ margin: '0' }}></Divider> <Divider style={{ margin: '0' }}></Divider>
</div> </div>
} }
@@ -57,7 +57,7 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
type="default" type="default"
onClick={() => handleSelect(item)} onClick={() => handleSelect(item)}
> >
Apply {intl.formatMessage({ id: 'playground.compare.apply' })}
</Button> </Button>
</h3> </h3>
{item.data.map((data, i) => { {item.data.map((data, i) => {
@@ -8,6 +8,8 @@ interface CompareContextProps {
systemMessage?: string; systemMessage?: string;
globalParams: Record<string, any>; globalParams: Record<string, any>;
loadingStatus: Record<symbol, boolean>; loadingStatus: Record<symbol, boolean>;
modelFullList: (Global.BaseOption<string> & { type?: string })[];
handleApplySystemChangeToAll: (val: string) => void;
handleDeleteModel: (instanceId: symbol) => void; handleDeleteModel: (instanceId: symbol) => void;
setSystemMessage?: (message: string) => void; setSystemMessage?: (message: string) => void;
setGlobalParams: (value: Record<string, any>) => void; setGlobalParams: (value: Record<string, any>) => void;
+1 -1
View File
@@ -13,7 +13,7 @@
.role { .role {
position: relative; position: relative;
cursor: pointer; cursor: pointer;
padding: 2px 4px; padding: 0 4px;
border-radius: var(--border-radius-mini); border-radius: var(--border-radius-mini);
} }
@@ -48,14 +48,21 @@
} }
textarea::-webkit-scrollbar-track { textarea::-webkit-scrollbar-track {
background-color: #f1f1f1; background-color: transparent;
} }
textarea::-webkit-scrollbar-thumb { textarea::-webkit-scrollbar-thumb {
background-color: #d9d9d9; background-color: transparent;
border-radius: 6px; border-radius: 6px;
} }
textarea:hover {
&::-webkit-scrollbar-thumb {
background-color: var(--color-scrollbar-thumb);
border-radius: 6px;
}
}
.send-btn { .send-btn {
display: flex; display: flex;
justify-content: center; justify-content: center;
+14 -1
View File
@@ -1,6 +1,19 @@
.sys-message { .sys-message {
position: relative; position: relative;
&.focus {
padding-top: 9px;
}
.apply-check {
position: absolute;
bottom: 10px;
right: 10px;
direction: rtl;
background-color: var(--color-white-1);
box-shadow: var(--ant-box-shadow-tertiary);
}
.sys-content-wrap { .sys-content-wrap {
position: relative; position: relative;
display: flex; display: flex;
@@ -26,7 +39,7 @@
.system-label { .system-label {
font-weight: var(--font-weight-bold); font-weight: var(--font-weight-bold);
padding-left: 16px; padding-left: 14px;
} }
.sys-content { .sys-content {
+2 -4
View File
@@ -45,8 +45,6 @@ const ActionList = [
]; ];
const Resources: React.FC = () => { const Resources: React.FC = () => {
console.log('resources======workers');
const { sortOrder, setSortOrder } = useTableSort({ const { sortOrder, setSortOrder } = useTableSort({
defaultSortOrder: 'descend' defaultSortOrder: 'descend'
}); });
@@ -400,7 +398,7 @@ const Resources: React.FC = () => {
return ( return (
<span className="flex-column flex-gap-2"> <span className="flex-column flex-gap-2">
{_.map( {_.map(
record?.status?.gpu_devices, _.sortBy(record?.status?.gpu_devices || [], ['index']),
(item: GPUDeviceItem, index: string) => { (item: GPUDeviceItem, index: string) => {
return ( return (
<span className="flex-center" key={index}> <span className="flex-center" key={index}>
@@ -431,7 +429,7 @@ const Resources: React.FC = () => {
return ( return (
<span className="flex-column"> <span className="flex-column">
{_.map( {_.map(
record?.status?.gpu_devices, _.sortBy(record?.status?.gpu_devices || [], ['index']),
(item: GPUDeviceItem, index: string) => { (item: GPUDeviceItem, index: string) => {
return ( return (
<span key={index}> <span key={index}>