feat(user-models): clickable model card opens API access info
This commit is contained in:
@@ -44,7 +44,7 @@ const ModelItemContent = styled.div`
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
cursor: default;
|
cursor: pointer;
|
||||||
.content {
|
.content {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -127,15 +127,23 @@ const renderTag = (item: any, index = 0) => {
|
|||||||
|
|
||||||
const ModelItem: React.FC<{
|
const ModelItem: React.FC<{
|
||||||
model: Record<string, any>;
|
model: Record<string, any>;
|
||||||
|
onClick?: (model: Record<string, any>) => void;
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
const { model } = props;
|
const { model, onClick } = props;
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleCardClick = () => {
|
||||||
|
onClick?.(model);
|
||||||
|
};
|
||||||
|
|
||||||
// ``model.name`` from ``/v2/my-models`` is the OpenAI-style id
|
// ``model.name`` from ``/v2/my-models`` is the OpenAI-style id
|
||||||
// (org-prefixed for non-platform routes, bare for platform). Use it
|
// (org-prefixed for non-platform routes, bare for platform). Use it
|
||||||
// verbatim — the playground / dispatcher both key off that exact id.
|
// verbatim — the playground / dispatcher both key off that exact id.
|
||||||
const handleOpenPlayGroundClick = () => {
|
const handleOpenPlayGroundClick = (e: React.MouseEvent) => {
|
||||||
|
// Card is clickable (opens API access info); keep the playground
|
||||||
|
// action isolated so it doesn't also trigger the card click.
|
||||||
|
e.stopPropagation();
|
||||||
const modelName = encodeURIComponent(model.name);
|
const modelName = encodeURIComponent(model.name);
|
||||||
for (const [category, path] of Object.entries(categoryToPathMap)) {
|
for (const [category, path] of Object.entries(categoryToPathMap)) {
|
||||||
if (
|
if (
|
||||||
@@ -178,9 +186,10 @@ const ModelItem: React.FC<{
|
|||||||
<CardWrapper>
|
<CardWrapper>
|
||||||
<TemplateCard
|
<TemplateCard
|
||||||
height={140}
|
height={140}
|
||||||
clickable={false}
|
clickable={true}
|
||||||
hoverable={true}
|
hoverable={true}
|
||||||
ghost
|
ghost
|
||||||
|
onClick={handleCardClick}
|
||||||
header={
|
header={
|
||||||
<Header>
|
<Header>
|
||||||
<span className="text gap-8">
|
<span className="text gap-8">
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
const useViewApIInfo = () => {
|
||||||
|
const [apiAccessInfo, setAPIAccessInfo] = useState<any>({
|
||||||
|
show: false,
|
||||||
|
data: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
const openViewAPIInfo = (row: any) => {
|
||||||
|
setAPIAccessInfo({
|
||||||
|
show: true,
|
||||||
|
data: row
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeViewAPIInfo = () => {
|
||||||
|
setAPIAccessInfo({
|
||||||
|
show: false,
|
||||||
|
data: {}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
apiAccessInfo,
|
||||||
|
openViewAPIInfo,
|
||||||
|
closeViewAPIInfo
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useViewApIInfo;
|
||||||
@@ -14,8 +14,10 @@ import { Button, Input, Space } from 'antd';
|
|||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import PageBox from '../_components/page-box';
|
import PageBox from '../_components/page-box';
|
||||||
import { MY_MODELS_API, queryMyModels } from './apis';
|
import { MY_MODELS_API, queryMyModels } from './apis';
|
||||||
|
import APIAccessInfoModal from './components/api-access-info';
|
||||||
import ModelItem from './components/model-item';
|
import ModelItem from './components/model-item';
|
||||||
import { categoryOptions, MyModelsStatusValueMap } from './config';
|
import { categoryOptions, MyModelsStatusValueMap } from './config';
|
||||||
|
import useViewApIInfo from './hooks/use-view-api-info';
|
||||||
const Dot = ({ color }: { color: string }) => {
|
const Dot = ({ color }: { color: string }) => {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
@@ -57,6 +59,7 @@ const UserModels: React.FC = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const { apiAccessInfo, openViewAPIInfo, closeViewAPIInfo } = useViewApIInfo();
|
||||||
|
|
||||||
const statusOptions = useMemo(() => {
|
const statusOptions = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
@@ -91,7 +94,7 @@ const UserModels: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderCard = (data: any) => {
|
const renderCard = (data: any) => {
|
||||||
return <ModelItem model={data} />;
|
return <ModelItem model={data} onClick={openViewAPIInfo} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadMore = useMemoizedFn((nextPage: number) => {
|
const loadMore = useMemoizedFn((nextPage: number) => {
|
||||||
@@ -152,86 +155,95 @@ const UserModels: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageBox>
|
<>
|
||||||
<PageTools
|
<PageBox>
|
||||||
marginBottom={22}
|
<PageTools
|
||||||
marginTop={0}
|
marginBottom={22}
|
||||||
left={
|
marginTop={0}
|
||||||
<Space>
|
left={
|
||||||
<Input
|
<Space>
|
||||||
placeholder={intl.formatMessage({ id: 'common.filter.name' })}
|
<Input
|
||||||
style={{ width: 230 }}
|
placeholder={intl.formatMessage({ id: 'common.filter.name' })}
|
||||||
size="large"
|
style={{ width: 230 }}
|
||||||
allowClear
|
size="large"
|
||||||
onClear={() =>
|
allowClear
|
||||||
handleNameChange({
|
onClear={() =>
|
||||||
target: {
|
handleNameChange({
|
||||||
value: ''
|
target: {
|
||||||
}
|
value: ''
|
||||||
})
|
}
|
||||||
}
|
})
|
||||||
onChange={handleNameChange}
|
}
|
||||||
></Input>
|
onChange={handleNameChange}
|
||||||
<BaseSelect
|
></Input>
|
||||||
allowClear
|
<BaseSelect
|
||||||
showSearch={false}
|
allowClear
|
||||||
placeholder={intl.formatMessage({ id: 'models.filter.category' })}
|
showSearch={false}
|
||||||
style={{ width: 180 }}
|
placeholder={intl.formatMessage({
|
||||||
size="large"
|
id: 'models.filter.category'
|
||||||
maxTagCount={1}
|
})}
|
||||||
options={categoryOptions}
|
style={{ width: 180 }}
|
||||||
onChange={handleCategoryChange}
|
size="large"
|
||||||
></BaseSelect>
|
maxTagCount={1}
|
||||||
<BaseSelect
|
options={categoryOptions}
|
||||||
allowClear
|
onChange={handleCategoryChange}
|
||||||
showSearch={false}
|
></BaseSelect>
|
||||||
placeholder={intl.formatMessage({ id: 'common.filter.status' })}
|
<BaseSelect
|
||||||
style={{ width: 180 }}
|
allowClear
|
||||||
size="large"
|
showSearch={false}
|
||||||
maxTagCount={1}
|
placeholder={intl.formatMessage({ id: 'common.filter.status' })}
|
||||||
optionRender={optionRender}
|
style={{ width: 180 }}
|
||||||
labelRender={labelRender}
|
size="large"
|
||||||
options={statusOptions}
|
maxTagCount={1}
|
||||||
onChange={handleStatusChange}
|
optionRender={optionRender}
|
||||||
></BaseSelect>
|
labelRender={labelRender}
|
||||||
<Button
|
options={statusOptions}
|
||||||
type="text"
|
onChange={handleStatusChange}
|
||||||
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
></BaseSelect>
|
||||||
icon={<SyncOutlined></SyncOutlined>}
|
<Button
|
||||||
onClick={handleRefresh}
|
type="text"
|
||||||
></Button>
|
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
||||||
</Space>
|
icon={<SyncOutlined></SyncOutlined>}
|
||||||
}
|
onClick={handleRefresh}
|
||||||
></PageTools>
|
></Button>
|
||||||
<InfiniteScrollerProvider
|
</Space>
|
||||||
value={{
|
}
|
||||||
total: dataSource.totalPage,
|
></PageTools>
|
||||||
current: queryParams.page,
|
<InfiniteScrollerProvider
|
||||||
loading: dataSource.loading,
|
value={{
|
||||||
refresh: loadMore
|
total: dataSource.totalPage,
|
||||||
}}
|
current: queryParams.page,
|
||||||
>
|
loading: dataSource.loading,
|
||||||
<TemplateCardList
|
refresh: loadMore
|
||||||
dataList={dataList}
|
}}
|
||||||
loading={dataSource.loading}
|
>
|
||||||
activeId={false}
|
<TemplateCardList
|
||||||
isFirst={!dataSource.loadend}
|
dataList={dataList}
|
||||||
renderItem={renderCard}
|
loading={dataSource.loading}
|
||||||
></TemplateCardList>
|
activeId={false}
|
||||||
<NoResult
|
isFirst={!dataSource.loadend}
|
||||||
loading={dataSource.loading}
|
renderItem={renderCard}
|
||||||
loadend={dataSource.loadend}
|
></TemplateCardList>
|
||||||
dataSource={dataList}
|
<NoResult
|
||||||
image={<IconFont type="icon-models" />}
|
loading={dataSource.loading}
|
||||||
filters={{ ...queryParams }}
|
loadend={dataSource.loadend}
|
||||||
noFoundText={intl.formatMessage({
|
dataSource={dataList}
|
||||||
id: 'noresult.mymodels.nofound'
|
image={<IconFont type="icon-models" />}
|
||||||
})}
|
filters={{ ...queryParams }}
|
||||||
title={intl.formatMessage({ id: 'noresult.mymodels.title' })}
|
noFoundText={intl.formatMessage({
|
||||||
subTitle={intl.formatMessage({ id: 'noresult.mymodels.subTitle' })}
|
id: 'noresult.mymodels.nofound'
|
||||||
></NoResult>
|
})}
|
||||||
</InfiniteScrollerProvider>
|
title={intl.formatMessage({ id: 'noresult.mymodels.title' })}
|
||||||
</PageBox>
|
subTitle={intl.formatMessage({ id: 'noresult.mymodels.subTitle' })}
|
||||||
|
></NoResult>
|
||||||
|
</InfiniteScrollerProvider>
|
||||||
|
</PageBox>
|
||||||
|
<APIAccessInfoModal
|
||||||
|
open={apiAccessInfo.show}
|
||||||
|
data={apiAccessInfo.data}
|
||||||
|
onClose={closeViewAPIInfo}
|
||||||
|
></APIAccessInfoModal>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user