fix(style): catalog size tags overflow
This commit is contained in:
@@ -11,13 +11,13 @@
|
||||
:global {
|
||||
.copy-button-wrapper {
|
||||
display: none;
|
||||
background-color: #383838;
|
||||
background-color: rgba(255, 255, 255, 20%);
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
right: -28px;
|
||||
right: 0;
|
||||
top: 0;
|
||||
padding: 2px;
|
||||
border-radius: 4px;
|
||||
border-radius: 0 4px;
|
||||
}
|
||||
|
||||
.simplebar-content {
|
||||
|
||||
@@ -28,6 +28,7 @@ type StatusTagProps = {
|
||||
download?: {
|
||||
percent: number;
|
||||
};
|
||||
extra?: React.ReactNode;
|
||||
actions?: {
|
||||
label: string;
|
||||
icon?: React.ReactNode;
|
||||
@@ -40,6 +41,7 @@ type StatusTagProps = {
|
||||
const StatusTag: React.FC<StatusTagProps> = ({
|
||||
statusValue,
|
||||
download,
|
||||
extra,
|
||||
actions = [],
|
||||
type = 'tag'
|
||||
}) => {
|
||||
@@ -78,7 +80,6 @@ const StatusTag: React.FC<StatusTagProps> = ({
|
||||
style={{ color: 'rgba(255,255,255,.8)' }}
|
||||
text={statusValue.message || ''}
|
||||
size="small"
|
||||
placement="right"
|
||||
></CopyButton>
|
||||
{actions?.map((item) => {
|
||||
return (
|
||||
@@ -104,7 +105,7 @@ const StatusTag: React.FC<StatusTagProps> = ({
|
||||
})}
|
||||
</div>
|
||||
|
||||
<SimpleBar style={{ maxHeight: 200 }}>
|
||||
<SimpleBar style={{ maxHeight: 200 }} autoHide={false}>
|
||||
<div
|
||||
style={{
|
||||
width: 'max-content',
|
||||
@@ -115,6 +116,7 @@ const StatusTag: React.FC<StatusTagProps> = ({
|
||||
}}
|
||||
>
|
||||
{statusValue.message}
|
||||
{extra}
|
||||
</div>
|
||||
</SimpleBar>
|
||||
</div>
|
||||
|
||||
@@ -13,4 +13,8 @@
|
||||
height: 22px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.tags-content {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,99 +1,126 @@
|
||||
import { MoreOutlined } from '@ant-design/icons';
|
||||
import { Dropdown, Tag } from 'antd';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import _ from 'lodash';
|
||||
import ResizeObserver from 'rc-resize-observer';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import './index.less';
|
||||
|
||||
const TagsWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
interface TagsWrapperProps {
|
||||
gap?: number;
|
||||
dataList: any[];
|
||||
renderTag: (item: any) => React.ReactNode;
|
||||
}
|
||||
|
||||
const TagsWrapper: React.FC<TagsWrapperProps> = (props) => {
|
||||
const { gap = 0, dataList, renderTag } = props;
|
||||
const tagsContentRef = useRef<HTMLDivElement>(null);
|
||||
const [hiddenIndices, setHiddenIndices] = useState({
|
||||
start: 0,
|
||||
end: dataList.length
|
||||
});
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const observer = useRef<IntersectionObserver | null>(null);
|
||||
const resizeObserver = useRef<ResizeObserver | null>(null);
|
||||
const [hiddenIndex, setHiddenIndex] = useState<number>(4);
|
||||
const uid = useRef(0);
|
||||
const moreBtnRef = useRef<HTMLDivElement>(null);
|
||||
const moreButtonWidth = useRef(0);
|
||||
const nodeSizeList = useRef<number[]>([]);
|
||||
|
||||
const updateUid = () => {
|
||||
uid.current += 1;
|
||||
return uid.current;
|
||||
};
|
||||
const calculateHiddenIndices = () => {
|
||||
const wrapperWidth = wrapperRef.current?.offsetWidth || 0;
|
||||
const childNodes = tagsContentRef.current?.childNodes;
|
||||
let sizeList = nodeSizeList.current;
|
||||
|
||||
const dropItems = useMemo(() => {
|
||||
return React.Children.toArray(children)
|
||||
.slice(hiddenIndex)
|
||||
.map((child) => ({
|
||||
label: child,
|
||||
key: updateUid()
|
||||
}));
|
||||
}, [children, hiddenIndex]);
|
||||
if (!sizeList.length && childNodes?.length) {
|
||||
sizeList = _.map(childNodes, (node: HTMLDivElement, index: number) => {
|
||||
if (index === childNodes.length - 1) {
|
||||
return node.offsetWidth;
|
||||
}
|
||||
return node.offsetWidth + gap;
|
||||
});
|
||||
nodeSizeList.current = sizeList;
|
||||
}
|
||||
|
||||
const updateHiddenIndex = () => {
|
||||
if (!wrapperRef.current || !observer.current) return;
|
||||
if (wrapperWidth === 0 || !sizeList.length) return;
|
||||
|
||||
const childrenList = Array.from(wrapperRef.current.children);
|
||||
let newHiddenIndex = 0;
|
||||
// cache more button width
|
||||
if (moreBtnRef.current?.offsetWidth) {
|
||||
moreButtonWidth.current = moreBtnRef.current?.offsetWidth + gap;
|
||||
}
|
||||
|
||||
childrenList.forEach((child, index) => {
|
||||
const rect = child.getBoundingClientRect();
|
||||
// visible in wrapperRef
|
||||
const issivible =
|
||||
rect.top < wrapperRef.current!.clientHeight &&
|
||||
rect.bottom > 0 &&
|
||||
rect.left < wrapperRef.current!.clientWidth &&
|
||||
rect.right > 0;
|
||||
let totalWidth = 0;
|
||||
let start = 0;
|
||||
let end = dataList.length;
|
||||
|
||||
if (!issivible) {
|
||||
newHiddenIndex = Math.max(newHiddenIndex, index + 1);
|
||||
for (let i = 0; i < sizeList.length; i++) {
|
||||
const nodeWidth = sizeList[i];
|
||||
|
||||
if (totalWidth + moreButtonWidth.current >= wrapperWidth) {
|
||||
end = i - 1;
|
||||
break;
|
||||
}
|
||||
});
|
||||
totalWidth += nodeWidth;
|
||||
|
||||
setHiddenIndex(newHiddenIndex);
|
||||
if (totalWidth >= wrapperWidth) {
|
||||
end = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hiddenIndices.start !== start || hiddenIndices.end !== end) {
|
||||
setHiddenIndices({
|
||||
start,
|
||||
end
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!wrapperRef.current) return;
|
||||
if (tagsContentRef.current) {
|
||||
calculateHiddenIndices();
|
||||
}
|
||||
}, [dataList, gap]);
|
||||
|
||||
// observer.current = new IntersectionObserver(
|
||||
// (entries) => {
|
||||
// const lastEntry = entries[entries.length - 1];
|
||||
// if (lastEntry && lastEntry.intersectionRatio < 1) {
|
||||
// setHiddenIndex((prev) =>
|
||||
// Math.max(prev, React.Children.count(children))
|
||||
// );
|
||||
// }
|
||||
// },
|
||||
// { root: wrapperRef.current, threshold: 1 }
|
||||
// );
|
||||
|
||||
// const childrenList = wrapperRef.current.children;
|
||||
// if (childrenList.length > 0) {
|
||||
// observer.current.observe(childrenList[childrenList.length - 1]);
|
||||
// }
|
||||
|
||||
resizeObserver.current = new ResizeObserver(() => {
|
||||
// updateHiddenIndex();
|
||||
});
|
||||
resizeObserver.current.observe(wrapperRef.current);
|
||||
|
||||
return () => {
|
||||
observer.current?.disconnect();
|
||||
resizeObserver.current?.disconnect();
|
||||
};
|
||||
}, [children]);
|
||||
const handleContentResize = _.throttle(() => {
|
||||
calculateHiddenIndices();
|
||||
}, 200);
|
||||
|
||||
return (
|
||||
<div className="tags-wrapper" ref={wrapperRef}>
|
||||
{React.Children.toArray(children).slice(0, hiddenIndex)}
|
||||
{React.Children.toArray(children).length > 4 && (
|
||||
<Dropdown
|
||||
trigger={['hover']}
|
||||
menu={{
|
||||
items: dropItems
|
||||
}}
|
||||
>
|
||||
<Tag className="more">
|
||||
<MoreOutlined rotate={90} />
|
||||
</Tag>
|
||||
</Dropdown>
|
||||
)}
|
||||
</div>
|
||||
<ResizeObserver onResize={handleContentResize}>
|
||||
<div className="tags-wrapper" ref={wrapperRef}>
|
||||
<ResizeObserver onResize={handleContentResize}>
|
||||
<div className="tags-content" ref={tagsContentRef} style={{ gap }}>
|
||||
{_.map(
|
||||
_.slice(dataList, hiddenIndices.start, hiddenIndices.end),
|
||||
(item: any, index: number) => {
|
||||
return <span key={index}>{renderTag?.(item)}</span>;
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</ResizeObserver>
|
||||
{hiddenIndices.end < dataList.length && (
|
||||
<Dropdown
|
||||
trigger={['hover']}
|
||||
menu={{
|
||||
items: _.map(
|
||||
_.slice(dataList, hiddenIndices.end),
|
||||
(item: any, index: number) => {
|
||||
return {
|
||||
label: renderTag?.(item),
|
||||
key: index
|
||||
};
|
||||
}
|
||||
)
|
||||
}}
|
||||
>
|
||||
<Tag
|
||||
className="more"
|
||||
style={{ marginInline: `${gap}px 0` }}
|
||||
ref={moreBtnRef}
|
||||
>
|
||||
<MoreOutlined rotate={90} />
|
||||
</Tag>
|
||||
</Dropdown>
|
||||
)}
|
||||
</div>
|
||||
</ResizeObserver>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { MoreOutlined } from '@ant-design/icons';
|
||||
import { Dropdown, Tag } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
interface MoreDropdownProps {
|
||||
items: any[];
|
||||
}
|
||||
|
||||
const MoreDropdown: React.FC<MoreDropdownProps> = (props) => {
|
||||
const { items } = props;
|
||||
return (
|
||||
<>
|
||||
{items && items.length > 0 ? (
|
||||
<Dropdown
|
||||
trigger={['hover']}
|
||||
menu={{
|
||||
items: items
|
||||
}}
|
||||
>
|
||||
<Tag className="more">
|
||||
<MoreOutlined rotate={90} />
|
||||
</Tag>
|
||||
</Dropdown>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(MoreDropdown);
|
||||
@@ -89,5 +89,6 @@ export default {
|
||||
'models.form.search.gguftips':
|
||||
'If using macOS or Windows as a worker, check GGUF (uncheck for audio models).',
|
||||
'models.form.button.addlabel': 'Add Label',
|
||||
'models.filter.category': 'Filter by category'
|
||||
'models.filter.category': 'Filter by category',
|
||||
'models.list.more.logs': 'View More'
|
||||
};
|
||||
|
||||
@@ -85,5 +85,6 @@ export default {
|
||||
'models.form.search.gguftips':
|
||||
'当 macOS 或 Windows 作 worker 时勾选 GGUF(搜索音频模型时取消勾选)',
|
||||
'models.form.button.addlabel': '添加标签',
|
||||
'models.filter.category': '按类别筛选'
|
||||
'models.filter.category': '按类别筛选',
|
||||
'models.list.more.logs': '查看更多'
|
||||
};
|
||||
|
||||
@@ -27,6 +27,28 @@ const CatalogItem: React.FC<CatalogItemProps> = (props) => {
|
||||
e.target.src = fallbackImg;
|
||||
};
|
||||
|
||||
const renderTag = (sItem: any) => {
|
||||
return (
|
||||
<Tag
|
||||
key={sItem}
|
||||
className="tag-item"
|
||||
style={{
|
||||
marginRight: 0,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
padding: '2px 6px',
|
||||
borderRadius: 4,
|
||||
fontSize: 12,
|
||||
opacity: 0.7,
|
||||
height: 22
|
||||
}}
|
||||
>
|
||||
{sItem}B
|
||||
</Tag>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={handleOnClick}
|
||||
@@ -81,7 +103,7 @@ const CatalogItem: React.FC<CatalogItemProps> = (props) => {
|
||||
<span className="flex-center">
|
||||
{_.map(data.licenses, (license: string, index: number) => {
|
||||
return (
|
||||
<span key={license} className="flex-center m-r-16">
|
||||
<span key={license} className="flex-center m-r-8">
|
||||
<IconFont type="icon-justice1" className="m-r-5"></IconFont>
|
||||
<span>{license}</span>
|
||||
</span>
|
||||
@@ -113,15 +135,11 @@ const CatalogItem: React.FC<CatalogItemProps> = (props) => {
|
||||
<>
|
||||
<span className="dot"></span>
|
||||
<div className="box">
|
||||
<TagWrapper>
|
||||
{data.sizes.map((sItem, i) => {
|
||||
return (
|
||||
<Tag key={sItem} className="tag-item">
|
||||
{sItem}B
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</TagWrapper>
|
||||
<TagWrapper
|
||||
gap={8}
|
||||
dataList={data.sizes}
|
||||
renderTag={renderTag}
|
||||
></TagWrapper>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
ThunderboltFilled
|
||||
} from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Col, Divider, Row, Space, Tag, Tooltip } from 'antd';
|
||||
import { Button, Col, Divider, Row, Space, Tag, Tooltip } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import _ from 'lodash';
|
||||
import React, { useCallback } from 'react';
|
||||
@@ -282,6 +282,21 @@ const InstanceItem: React.FC<InstanceItemProps> = ({
|
||||
? { percent: item.download_progress }
|
||||
: undefined
|
||||
}
|
||||
extra={
|
||||
item.state === InstanceStatusMap.Error ? (
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() =>
|
||||
handleChildSelect('viewlog', item, list)
|
||||
}
|
||||
>
|
||||
{intl.formatMessage({
|
||||
id: 'models.list.more.logs'
|
||||
})}
|
||||
</Button>
|
||||
) : null
|
||||
}
|
||||
statusValue={{
|
||||
status:
|
||||
item.state === InstanceStatusMap.Downloading &&
|
||||
|
||||
@@ -31,7 +31,8 @@ const PlaygroundEmbedding: React.FC = () => {
|
||||
const getModelListByEmbedding = async () => {
|
||||
try {
|
||||
const params = {
|
||||
categories: modelCategoriesMap.embedding
|
||||
categories: modelCategoriesMap.embedding,
|
||||
with_meta: true
|
||||
};
|
||||
const res = await queryModelsList(params);
|
||||
const list = _.map(res.data || [], (item: any) => {
|
||||
|
||||
@@ -33,7 +33,8 @@ const PlaygroundRerank: React.FC = () => {
|
||||
const getModelListByReranker = async () => {
|
||||
try {
|
||||
const params = {
|
||||
categories: modelCategoriesMap.reranker
|
||||
categories: modelCategoriesMap.reranker,
|
||||
with_meta: true
|
||||
};
|
||||
const res = await queryModelsList(params);
|
||||
const list = _.map(res.data || [], (item: any) => {
|
||||
|
||||
@@ -59,7 +59,6 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
|
||||
{ version: versionInfo.version }
|
||||
)}
|
||||
</li>
|
||||
<li>{intl.formatMessage({ id: 'resources.worker.select.command' })}</li>
|
||||
<li>
|
||||
<span>
|
||||
{intl.formatMessage({ id: 'resources.worker.driver.install' })}
|
||||
|
||||
@@ -57,6 +57,11 @@ export const addWorkerGuide: Record<string, any> = {
|
||||
return `docker run -d --ipc=host --network=host gpustack/gpustack:${params.tag} --server-url ${params.server} --token ${params.token}`;
|
||||
}
|
||||
},
|
||||
rocm: {
|
||||
registerWorker(params: { server: string; tag: string; token: string }) {
|
||||
return `docker run -d --network=host --ipc=host --group-add=video --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --device /dev/kfd --device /dev/dri gpustack/gpustack:${params.tag} --server-url ${params.server} --token ${params.token}`;
|
||||
}
|
||||
},
|
||||
container: {
|
||||
getToken:
|
||||
'docker run -it ${gpustack_container_id} cat /var/lib/gpustack/token'
|
||||
@@ -65,6 +70,7 @@ export const addWorkerGuide: Record<string, any> = {
|
||||
|
||||
export const containerInstallOptions = [
|
||||
{ label: 'CUDA', value: 'cuda' },
|
||||
{ label: 'AMD', value: 'rocm' },
|
||||
{ label: 'CANN', value: 'npu' },
|
||||
{ label: 'MUSA', value: 'musa' },
|
||||
{ label: 'CPU', value: 'cpu' }
|
||||
|
||||
Reference in New Issue
Block a user