Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions packages/global/openapi/core/plugin/marketplace/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,16 @@ const formatToolDetailSchema = z.object({});
const formatToolSimpleSchema = z.object({});

// Create intersection types for extended schemas
export const MarketplaceToolListItemSchema = formatToolSimpleSchema.extend({
downloadUrl: z.string()
});
export const MarketplaceToolListItemSchema = formatToolSimpleSchema;
export type MarketplaceToolListItemType = ToolSimpleType & {
downloadUrl: string;
downloadCount: number;
};

export const MarketplaceToolDetailItemSchema = formatToolDetailSchema.extend({
readme: z.string().optional()
});
export const MarketplaceToolDetailSchema = z.object({
tools: z.array(MarketplaceToolDetailItemSchema),
downloadUrl: z.string()
tools: z.array(MarketplaceToolDetailItemSchema)
});

// List
Expand Down Expand Up @@ -57,7 +54,12 @@ export type GetSystemInstalledPluginsQueryType = z.infer<
typeof GetSystemInstalledPluginsQuerySchema
>;
export const GetSystemInstalledPluginsResponseSchema = z.object({
ids: z.array(z.string())
list: z.array(
z.object({
id: z.string(),
version: z.string()
})
)
});
export type GetSystemInstalledPluginsResponseType = z.infer<
typeof GetSystemInstalledPluginsResponseSchema
Expand Down
2 changes: 1 addition & 1 deletion packages/global/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@fastgpt/global",
"version": "1.0.0",
"dependencies": {
"@fastgpt-sdk/plugin": "0.2.15",
"@fastgpt-sdk/plugin": "0.2.16",
"@apidevtools/swagger-parser": "^10.1.0",
"@bany/curl-to-json": "^1.2.8",
"axios": "^1.12.1",
Expand Down
1 change: 1 addition & 0 deletions packages/web/components/common/Icon/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const iconPaths = {
'common/disable': () => import('./icons/common/disable.svg'),
'common/downArrowFill': () => import('./icons/common/downArrowFill.svg'),
'common/download': () => import('./icons/common/download.svg'),
'common/downloadLine': () => import('./icons/common/downloadLine.svg'),
'common/edit': () => import('./icons/common/edit.svg'),
'common/editor/resizer': () => import('./icons/common/editor/resizer.svg'),
'common/ellipsis': () => import('./icons/common/ellipsis.svg'),
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
219 changes: 163 additions & 56 deletions packages/web/components/core/plugin/tool/ToolCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,6 @@ import MyIcon from '../../../common/Icon';
import { parseI18nString } from '@fastgpt/global/common/i18n/utils';
import { PluginStatusEnum } from '@fastgpt/global/core/plugin/type';

/*
3 种使用场景:
1. admin 视角插件市场:显示是否安装,无状态,显示安装/卸载
2. team 视角资源库:显示是否安装,状态文本,以及安装/卸载
3. 开放的插件市场:不显示任何状态,只显示下载按钮
*/
export type ToolCardItemType = {
id: string;
name: string;
Expand All @@ -23,21 +17,35 @@ export type ToolCardItemType = {
downloadUrl?: string;
status?: number;
installed?: boolean;
update?: boolean;
downloadCount?: number;
};

/**
3 种使用场景:
1. admin 视角插件市场:显示是否安装,是否更新,无状态,显示安装/卸载
2. team 视角资源库:显示是否安装,不显示更新,状态文本,以及安装/卸载
3. 开放的插件市场:不显示任何状态,只显示下载按钮
*/
const ToolCard = ({
item,
systemTitle,
isLoading,
isInstallingOrDeleting,
isUpdating,
mode,
onClickButton,
onInstall,
onDelete,
onUpdate,
onClickCard
}: {
item: ToolCardItemType;
systemTitle?: string;
isLoading?: boolean;
isInstallingOrDeleting?: boolean;
isUpdating?: boolean;
mode: 'admin' | 'team' | 'marketplace';
onClickButton: (installed: boolean) => void;
onInstall: () => Promise<void>;
onDelete?: () => Promise<void>;
onUpdate?: () => Promise<void>;
onClickCard?: () => void;
}) => {
const { t, i18n } = useTranslation();
Expand Down Expand Up @@ -76,7 +84,7 @@ const ToolCard = ({
};
}, [item.tags]);

const statusMap = useMemo(() => {
const statusLabel = useMemo(() => {
if (mode === 'marketplace') return null;

const pluginStatusMap: Record<number, { label: string; color: string; icon?: string } | null> =
Expand All @@ -91,23 +99,27 @@ const ToolCard = ({
}
};

const installedStatusMap = item.installed
? {
label: t('app:toolkit_installed'),
color: 'myGray.500',
icon: 'common/check'
}
: null;

if (mode === 'admin') {
return installedStatusMap;
return item.installed
? {
label: t('app:toolkit_installed'),
color: 'myGray.500',
icon: 'common/check'
}
: null;
}

if (mode === 'team') {
if (item.status && pluginStatusMap[item.status]) {
return pluginStatusMap[item.status];
}
return installedStatusMap;
return item.installed
? {
label: t('app:toolkit_installed'),
color: 'myGray.500',
icon: 'common/check'
}
: null;
}
}, [item.installed, item.status]);

Expand All @@ -122,23 +134,77 @@ const ToolCard = ({
display={'flex'}
flexDirection={'column'}
cursor={onClickCard ? 'pointer' : 'default'}
onClick={onClickCard}
position={'relative'}
onClick={() => {
if (isInstallingOrDeleting || isUpdating) return;
onClickCard?.();
}}
_hover={{
boxShadow: '0 4px 4px 0 rgba(19, 51, 107, 0.05), 0 0 1px 0 rgba(19, 51, 107, 0.08);',
'& .install-button': {
display: 'flex'
},
'& .update-button': {
display: 'flex'
},
// Only hide author info when there are multiple buttons
...(item.update && mode === 'admin'
? {
'& .author-info': {
display: 'none'
}
}
: {}),
'& .download-count': {
display: 'none'
}
}}
>
{/* Update badge in top-right corner */}
{item.update && mode === 'admin' && (
<Flex
alignItems="center"
position={'absolute'}
top={4}
right={4}
px={2}
py={0.5}
bg={'rgb(255, 247, 237)'}
color={'rgba(234,88,12,1)'}
fontSize={'12px'}
fontWeight={'medium'}
borderRadius={'0.5rem'}
borderColor={'rgba(255,237,213,1)'}
borderWidth={'1px'}
zIndex={1}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="12"
height="12"
viewBox="0 0 24 24"
fill="currentColor"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"></path>
</svg>
{t('app:app.modules.has new version')}
</Flex>
)}

<HStack>
<Avatar src={item.icon} borderRadius={'sm'} w={'1.5rem'} />
<Box color={'myGray.900'} fontWeight={'medium'}>
{parseI18nString(item.name, i18n.language)}
</Box>
{statusMap && (
<Flex fontSize={'12px'} fontWeight={'medium'} color={statusMap.color} gap={1}>
{statusMap.icon && <MyIcon name={statusMap.icon as any} w={4} />}
{statusMap.label}
{statusLabel && (
<Flex fontSize={'12px'} fontWeight={'medium'} color={statusLabel.color} gap={1}>
{statusLabel.icon && <MyIcon name={statusLabel.icon as any} w={4} />}
{statusLabel.label}
</Flex>
)}
</HStack>
Expand Down Expand Up @@ -194,36 +260,77 @@ const ToolCard = ({
</Flex>

<Flex w={'full'} fontSize={'mini'} alignItems={'end'} justifyContent={'space-between'}>
<Box color={'myGray.500'} mt={3}>{`by ${item.author || systemTitle || 'FastGPT'}`}</Box>
{mode === 'marketplace' ? (
<Button
className="install-button"
size={'sm'}
variant={'primary'}
onClick={(e) => {
e.stopPropagation();
onClickButton(false);
}}
isLoading={isLoading}
{...(!isLoading ? { display: 'none' } : {})}
>
{t('common:Download')}
</Button>
) : (
<Button
className="install-button"
{...(!isLoading ? { display: 'none' } : {})}
size={'sm'}
variant={item.installed ? 'primaryOutline' : 'primary'}
onClick={(e) => {
e.stopPropagation();
onClickButton(!item.installed);
}}
isLoading={isLoading}
>
{item.installed ? t('app:toolkit_uninstall') : t('app:toolkit_install')}
</Button>
)}
<Box
className="author-info"
color={'myGray.500'}
mt={3}
>{`by ${item.author || systemTitle || 'FastGPT'}`}</Box>
{/*TODO: when statistics is ready*/}
{/*<Flex flexDirection={'row'} gap={1} className="download-count" color={'myGray.500'} mt={3}>
<MyIcon name="common/downloadLine" />
{!item.downloadCount
? 0
: item.downloadCount < 1000
? `${item.downloadCount}`
: `${(item.downloadCount / 1000).toFixed(1)}k`}
</Flex>*/}

<Flex gap={2} alignItems={'center'} ml={'auto'}>
{mode === 'marketplace' ? (
<Button
className="install-button"
size={'sm'}
variant={'primary'}
onClick={(e) => {
e.stopPropagation();
onInstall();
}}
isLoading={isInstallingOrDeleting}
{...(!isInstallingOrDeleting ? { display: 'none' } : {})}
>
{t('common:Download')}
</Button>
) : (
<Button
className="install-button"
size={'sm'}
variant={item.installed ? 'primaryOutline' : 'primary'}
onClick={async (e) => {
e.stopPropagation();
if (item.installed) {
// delete
if (onDelete) {
return onDelete();
}
} else {
return onInstall();
}
}}
isLoading={isInstallingOrDeleting}
{...(!isInstallingOrDeleting ? { display: 'none' } : {})}
isDisabled={isUpdating}
>
{item.installed ? t('app:toolkit_uninstall') : t('app:toolkit_install')}
</Button>
)}

{/* Update button for admin mode when update is available */}
{item.update && mode === 'admin' && onUpdate && (
<Button
className="update-button"
size={'sm'}
variant={'primary'}
onClick={async (e) => {
e.stopPropagation();
return onUpdate();
}}
isLoading={isUpdating}
display={'none'}
>
{t('app:custom_plugin_update')}
</Button>
)}
</Flex>
</Flex>
</MyBox>
);
Expand Down
Loading
Loading