feat(user-models): clickable model card opens API access info

This commit is contained in:
jialin
2026-07-09 20:36:52 +08:00
committed by jialin
parent 794ac724ca
commit dc54c21d70
3 changed files with 136 additions and 85 deletions
+13 -4
View File
@@ -44,7 +44,7 @@ const ModelItemContent = styled.div`
flex-direction: column;
height: 100%;
width: 100%;
cursor: default;
cursor: pointer;
.content {
display: flex;
justify-content: space-between;
@@ -127,15 +127,23 @@ const renderTag = (item: any, index = 0) => {
const ModelItem: React.FC<{
model: Record<string, any>;
onClick?: (model: Record<string, any>) => void;
}> = (props) => {
const { model } = props;
const { model, onClick } = props;
const intl = useIntl();
const navigate = useNavigate();
const handleCardClick = () => {
onClick?.(model);
};
// ``model.name`` from ``/v2/my-models`` is the OpenAI-style id
// (org-prefixed for non-platform routes, bare for platform). Use it
// 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);
for (const [category, path] of Object.entries(categoryToPathMap)) {
if (
@@ -178,9 +186,10 @@ const ModelItem: React.FC<{
<CardWrapper>
<TemplateCard
height={140}
clickable={false}
clickable={true}
hoverable={true}
ghost
onClick={handleCardClick}
header={
<Header>
<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;
+93 -81
View File
@@ -14,8 +14,10 @@ import { Button, Input, Space } from 'antd';
import React, { useCallback, useMemo } from 'react';
import PageBox from '../_components/page-box';
import { MY_MODELS_API, queryMyModels } from './apis';
import APIAccessInfoModal from './components/api-access-info';
import ModelItem from './components/model-item';
import { categoryOptions, MyModelsStatusValueMap } from './config';
import useViewApIInfo from './hooks/use-view-api-info';
const Dot = ({ color }: { color: string }) => {
return (
<span
@@ -57,6 +59,7 @@ const UserModels: React.FC = () => {
}
});
const intl = useIntl();
const { apiAccessInfo, openViewAPIInfo, closeViewAPIInfo } = useViewApIInfo();
const statusOptions = useMemo(() => {
return [
@@ -91,7 +94,7 @@ const UserModels: React.FC = () => {
};
const renderCard = (data: any) => {
return <ModelItem model={data} />;
return <ModelItem model={data} onClick={openViewAPIInfo} />;
};
const loadMore = useMemoizedFn((nextPage: number) => {
@@ -152,86 +155,95 @@ const UserModels: React.FC = () => {
};
return (
<PageBox>
<PageTools
marginBottom={22}
marginTop={0}
left={
<Space>
<Input
placeholder={intl.formatMessage({ id: 'common.filter.name' })}
style={{ width: 230 }}
size="large"
allowClear
onClear={() =>
handleNameChange({
target: {
value: ''
}
})
}
onChange={handleNameChange}
></Input>
<BaseSelect
allowClear
showSearch={false}
placeholder={intl.formatMessage({ id: 'models.filter.category' })}
style={{ width: 180 }}
size="large"
maxTagCount={1}
options={categoryOptions}
onChange={handleCategoryChange}
></BaseSelect>
<BaseSelect
allowClear
showSearch={false}
placeholder={intl.formatMessage({ id: 'common.filter.status' })}
style={{ width: 180 }}
size="large"
maxTagCount={1}
optionRender={optionRender}
labelRender={labelRender}
options={statusOptions}
onChange={handleStatusChange}
></BaseSelect>
<Button
type="text"
style={{ color: 'var(--ant-color-text-tertiary)' }}
icon={<SyncOutlined></SyncOutlined>}
onClick={handleRefresh}
></Button>
</Space>
}
></PageTools>
<InfiniteScrollerProvider
value={{
total: dataSource.totalPage,
current: queryParams.page,
loading: dataSource.loading,
refresh: loadMore
}}
>
<TemplateCardList
dataList={dataList}
loading={dataSource.loading}
activeId={false}
isFirst={!dataSource.loadend}
renderItem={renderCard}
></TemplateCardList>
<NoResult
loading={dataSource.loading}
loadend={dataSource.loadend}
dataSource={dataList}
image={<IconFont type="icon-models" />}
filters={{ ...queryParams }}
noFoundText={intl.formatMessage({
id: 'noresult.mymodels.nofound'
})}
title={intl.formatMessage({ id: 'noresult.mymodels.title' })}
subTitle={intl.formatMessage({ id: 'noresult.mymodels.subTitle' })}
></NoResult>
</InfiniteScrollerProvider>
</PageBox>
<>
<PageBox>
<PageTools
marginBottom={22}
marginTop={0}
left={
<Space>
<Input
placeholder={intl.formatMessage({ id: 'common.filter.name' })}
style={{ width: 230 }}
size="large"
allowClear
onClear={() =>
handleNameChange({
target: {
value: ''
}
})
}
onChange={handleNameChange}
></Input>
<BaseSelect
allowClear
showSearch={false}
placeholder={intl.formatMessage({
id: 'models.filter.category'
})}
style={{ width: 180 }}
size="large"
maxTagCount={1}
options={categoryOptions}
onChange={handleCategoryChange}
></BaseSelect>
<BaseSelect
allowClear
showSearch={false}
placeholder={intl.formatMessage({ id: 'common.filter.status' })}
style={{ width: 180 }}
size="large"
maxTagCount={1}
optionRender={optionRender}
labelRender={labelRender}
options={statusOptions}
onChange={handleStatusChange}
></BaseSelect>
<Button
type="text"
style={{ color: 'var(--ant-color-text-tertiary)' }}
icon={<SyncOutlined></SyncOutlined>}
onClick={handleRefresh}
></Button>
</Space>
}
></PageTools>
<InfiniteScrollerProvider
value={{
total: dataSource.totalPage,
current: queryParams.page,
loading: dataSource.loading,
refresh: loadMore
}}
>
<TemplateCardList
dataList={dataList}
loading={dataSource.loading}
activeId={false}
isFirst={!dataSource.loadend}
renderItem={renderCard}
></TemplateCardList>
<NoResult
loading={dataSource.loading}
loadend={dataSource.loadend}
dataSource={dataList}
image={<IconFont type="icon-models" />}
filters={{ ...queryParams }}
noFoundText={intl.formatMessage({
id: 'noresult.mymodels.nofound'
})}
title={intl.formatMessage({ id: 'noresult.mymodels.title' })}
subTitle={intl.formatMessage({ id: 'noresult.mymodels.subTitle' })}
></NoResult>
</InfiniteScrollerProvider>
</PageBox>
<APIAccessInfoModal
open={apiAccessInfo.show}
data={apiAccessInfo.data}
onClose={closeViewAPIInfo}
></APIAccessInfoModal>
</>
);
};