254 lines
6.3 KiB
TypeScript
254 lines
6.3 KiB
TypeScript
import {
|
|
IconFont,
|
|
StatusTag,
|
|
TagsWrapper,
|
|
TemplateCard,
|
|
ThemeTag
|
|
} from '@gpustack/core-ui';
|
|
import { useIntl, useNavigate } from '@umijs/max';
|
|
import { Button } from 'antd';
|
|
import _ from 'lodash';
|
|
import React, { useMemo } from 'react';
|
|
import styled from 'styled-components';
|
|
import { categoryConfig } from '../../_components/model-tag';
|
|
import {
|
|
modelCategories,
|
|
modelCategoriesMap,
|
|
modelSourceMap,
|
|
MyModelsStatusLabelMap,
|
|
MyModelsStatusMap,
|
|
MyModelsStatusValueMap
|
|
} from '../config';
|
|
import { categoryToPathMap } from '../config/button-actions';
|
|
|
|
const CardWrapper = styled.div`
|
|
&:hover {
|
|
.content {
|
|
.btn {
|
|
display: block;
|
|
}
|
|
}
|
|
}
|
|
`;
|
|
|
|
const Dot = styled.span`
|
|
background-color: var(--ant-color-text-quaternary);
|
|
border-radius: 50%;
|
|
flex: none;
|
|
height: 3px;
|
|
width: 3px;
|
|
`;
|
|
|
|
const ModelItemContent = styled.div`
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
width: 100%;
|
|
cursor: default;
|
|
.content {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
flex: 1;
|
|
}
|
|
.footer {
|
|
width: 100%;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-end;
|
|
.btn {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
.time {
|
|
color: var(--ant-color-text-secondary);
|
|
font-size: var(--font-size-small);
|
|
font-weight: 400;
|
|
}
|
|
.extra-info {
|
|
width: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
.tag-item {
|
|
margin-right: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 2px 6px;
|
|
border-radius: 4px;
|
|
font-size: 12px;
|
|
height: 22px;
|
|
opacity: 0.7;
|
|
}
|
|
}
|
|
`;
|
|
|
|
const Header = styled.div`
|
|
margin-bottom: 8px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
width: 100%;
|
|
.anticon {
|
|
font-size: 16px;
|
|
color: var(--ant-color-text-secondary);
|
|
}
|
|
.text {
|
|
display: flex;
|
|
align-items: center;
|
|
font-size: var(--font-size-middle);
|
|
font-weight: 500;
|
|
color: var(--ant-color-text);
|
|
}
|
|
`;
|
|
|
|
const sourceIconMap = {
|
|
[modelSourceMap.local_path_value]: 'icon-hard-disk',
|
|
[modelSourceMap.huggingface_value]: 'icon-huggingface',
|
|
[modelSourceMap.modelscope_value]: 'icon-tu2'
|
|
};
|
|
|
|
const renderTag = (item: any, index = 0) => {
|
|
return (
|
|
<ThemeTag key={item} className="tag-item" color="purple">
|
|
{item}
|
|
</ThemeTag>
|
|
);
|
|
};
|
|
|
|
const ModelItem: React.FC<{
|
|
model: Record<string, any>;
|
|
onClick: (model: any) => void;
|
|
}> = (props) => {
|
|
const { model, onClick } = props;
|
|
const intl = useIntl();
|
|
const navigate = useNavigate();
|
|
|
|
const handleOpenPlayGround = () => {
|
|
for (const [category, path] of Object.entries(categoryToPathMap)) {
|
|
if (
|
|
model.categories?.includes(category) &&
|
|
[
|
|
modelCategoriesMap.text_to_speech,
|
|
modelCategoriesMap.speech_to_text
|
|
].includes(category)
|
|
) {
|
|
navigate(`${path}&model=${model.name}`);
|
|
return;
|
|
}
|
|
if (model.categories?.includes(category)) {
|
|
navigate(`${path}?model=${model.name}`);
|
|
return;
|
|
}
|
|
}
|
|
navigate(`/playground/chat?model=${model.name}`);
|
|
};
|
|
|
|
// context length
|
|
const maxToken = useMemo(() => {
|
|
const meta = model.meta || {};
|
|
const { max_model_len, n_ctx, n_slot, max_total_tokens } = meta || {};
|
|
|
|
let max_tokens: number = 0;
|
|
|
|
if (n_ctx && n_slot) {
|
|
max_tokens = _.divide(n_ctx, n_slot);
|
|
} else if (max_model_len) {
|
|
max_tokens = max_model_len;
|
|
} else if (max_total_tokens) {
|
|
max_tokens = max_total_tokens;
|
|
}
|
|
|
|
return _.round(max_tokens / 1024);
|
|
}, [model]);
|
|
|
|
return (
|
|
<CardWrapper>
|
|
<TemplateCard
|
|
height={140}
|
|
onClick={() => onClick(model)}
|
|
clickable={false}
|
|
hoverable={true}
|
|
ghost
|
|
header={
|
|
<Header>
|
|
<span className="text gap-8">
|
|
<IconFont
|
|
type={sourceIconMap[model.source]}
|
|
style={{ fontSize: 24 }}
|
|
/>
|
|
<span>{model.name}</span>
|
|
</span>
|
|
<StatusTag
|
|
maxTooltipWidth={400}
|
|
statusValue={{
|
|
status: MyModelsStatusMap[model.status],
|
|
text: intl.formatMessage({
|
|
id: MyModelsStatusLabelMap[model.status] || ''
|
|
}),
|
|
message: model.state_message
|
|
}}
|
|
></StatusTag>
|
|
</Header>
|
|
}
|
|
>
|
|
<ModelItemContent>
|
|
<div className="content">
|
|
<div className="footer">
|
|
<div className="extra-info">
|
|
{model.categories?.length > 0 &&
|
|
model.categories.map((sItem: string) => {
|
|
return (
|
|
<ThemeTag
|
|
icon={categoryConfig[sItem]?.icon}
|
|
key={sItem}
|
|
className="tag-item"
|
|
color={categoryConfig[sItem]?.color || 'blue'}
|
|
opacity={0.7}
|
|
>
|
|
{_.find(modelCategories, { value: sItem })?.label ||
|
|
sItem}
|
|
</ThemeTag>
|
|
);
|
|
})}
|
|
|
|
{maxToken > 0 && (
|
|
<>
|
|
<Dot></Dot>
|
|
<ThemeTag className="tag-item" color="purple">
|
|
{maxToken}K context
|
|
</ThemeTag>
|
|
</>
|
|
)}
|
|
{model.meta?.voices?.length > 0 && (
|
|
<>
|
|
<Dot></Dot>
|
|
<TagsWrapper
|
|
gap={8}
|
|
dataList={model.meta?.voices}
|
|
renderTag={renderTag}
|
|
></TagsWrapper>
|
|
</>
|
|
)}
|
|
</div>
|
|
{[MyModelsStatusValueMap.Active].includes(model.status) && (
|
|
<Button
|
|
size="middle"
|
|
className="btn"
|
|
type="primary"
|
|
onClick={handleOpenPlayGround}
|
|
>
|
|
{intl.formatMessage({ id: 'models.openinplayground' })}
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</ModelItemContent>
|
|
</TemplateCard>
|
|
</CardWrapper>
|
|
);
|
|
};
|
|
|
|
export default ModelItem;
|