Merge branch 'feat/model-plugins-implementing' into deploy/dev

# Conflicts:
#	web/contract/router.ts
This commit is contained in:
yyh
2026-03-10 23:29:41 +08:00
13 changed files with 178 additions and 105 deletions

View File

@ -83,8 +83,21 @@ vi.mock('./system-model-selector', () => ({
default: () => <div data-testid="system-model-selector" />,
}))
vi.mock('@/service/use-plugins', () => ({
useCheckInstalled: () => ({ data: undefined }),
vi.mock('@tanstack/react-query', async (importOriginal) => {
const actual = await importOriginal<typeof import('@tanstack/react-query')>()
return {
...actual,
useQuery: () => ({ data: undefined }),
}
})
vi.mock('@/service/client', () => ({
consoleQuery: {
plugins: {
checkInstalled: { queryOptions: () => ({}) },
latestVersions: { queryOptions: () => ({}) },
},
},
}))
describe('ModelProviderPage', () => {

View File

@ -2,13 +2,15 @@ import type {
ModelProvider,
} from './declarations'
import type { PluginDetail } from '@/app/components/plugins/types'
import { useQuery } from '@tanstack/react-query'
import { useDebounce } from 'ahooks'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { usePluginsWithLatestVersion } from '@/app/components/plugins/hooks'
import { IS_CLOUD_EDITION } from '@/config'
import { useSystemFeaturesQuery } from '@/context/global-public-context'
import { useProviderContext } from '@/context/provider-context'
import { useCheckInstalled } from '@/service/use-plugins'
import { consoleQuery } from '@/service/client'
import { cn } from '@/utils/classnames'
import {
CustomConfigurationStatusEnum,
@ -45,18 +47,18 @@ const ModelProviderPage = ({ searchText }: Props) => {
const allPluginIds = useMemo(() => {
return [...new Set(providers.map(p => providerToPluginId(p.provider)).filter(Boolean))]
}, [providers])
const { data: installedPlugins } = useCheckInstalled({
pluginIds: allPluginIds,
const { data: installedPlugins } = useQuery(consoleQuery.plugins.checkInstalled.queryOptions({
input: { body: { plugin_ids: allPluginIds } },
enabled: allPluginIds.length > 0,
})
staleTime: 0,
}))
const enrichedPlugins = usePluginsWithLatestVersion(installedPlugins?.plugins)
const pluginDetailMap = useMemo(() => {
const map = new Map<string, PluginDetail>()
if (installedPlugins?.plugins) {
for (const plugin of installedPlugins.plugins)
map.set(plugin.plugin_id, plugin)
}
for (const plugin of enrichedPlugins)
map.set(plugin.plugin_id, plugin)
return map
}, [installedPlugins])
}, [enrichedPlugins])
const enableMarketplace = systemFeatures?.enable_marketplace ?? false
const isDefaultModelLoading = isTextGenerationDefaultModelLoading
|| isEmbeddingsDefaultModelLoading
@ -72,7 +74,7 @@ const ModelProviderPage = ({ searchText }: Props) => {
provider.custom_configuration.status === CustomConfigurationStatusEnum.active
|| (
provider.system_configuration.enabled === true
&& provider.system_configuration.quota_configurations.find(item => item.quota_type === provider.system_configuration.current_quota_type)
&& provider.system_configuration.quota_configurations.some(item => item.quota_type === provider.system_configuration.current_quota_type)
)
) {
configuredProviders.push(provider)

View File

@ -2,7 +2,9 @@ import type { FC } from 'react'
import type { PluginDetail } from '@/app/components/plugins/types'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import Badge from '@/app/components/base/badge'
import Button from '@/app/components/base/button'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip'
import { HeaderModals } from '@/app/components/plugins/plugin-detail-panel/detail-header/components'
import { useDetailHeaderState, usePluginOperations } from '@/app/components/plugins/plugin-detail-panel/detail-header/hooks'
import OperationDropdown from '@/app/components/plugins/plugin-detail-panel/operation-dropdown'
@ -84,31 +86,42 @@ const ProviderCardActions: FC<Props> = ({ detail, onUpdate }) => {
sideOffset={4}
alignOffset={0}
trigger={(
<span
<Badge
className={cn(
'relative inline-flex min-w-5 items-center justify-center gap-[3px] rounded-md border border-divider-deep bg-state-base-hover px-[5px] py-[2px] text-text-tertiary system-xs-medium-uppercase',
isFromMarketplace && 'cursor-pointer hover:bg-state-base-hover-alt',
isFromMarketplace && 'cursor-pointer hover:bg-state-base-hover',
)}
>
<span>{version}</span>
{isFromMarketplace && <span aria-hidden className="i-ri-arrow-left-right-line h-3 w-3" />}
{hasNewVersion && (
<span className="absolute -right-0.5 -top-0.5 h-1.5 w-1.5 rounded-full bg-state-destructive-solid" />
uppercase={false}
text={(
<>
<span>{version}</span>
{isFromMarketplace && <span aria-hidden className="i-ri-arrow-left-right-line ml-1 h-3 w-3" />}
</>
)}
</span>
hasRedCornerMark={hasNewVersion}
/>
)}
/>
)}
{(hasNewVersion || isFromGitHub) && (
<Button
variant="secondary-accent"
size="small"
className="!h-5"
onClick={handleTriggerLatestUpdate}
>
{t('detailPanel.operation.update', { ns: 'plugin' })}
</Button>
<Tooltip>
<TooltipTrigger
delay={300}
render={(
<Button
variant="secondary-accent"
size="small"
className="!h-5"
onClick={handleTriggerLatestUpdate}
>
{t('detailPanel.operation.update', { ns: 'plugin' })}
</Button>
)}
/>
<TooltipContent>
{t('detailPanel.operation.updateTooltip', { ns: 'plugin' })}
</TooltipContent>
</Tooltip>
)}
<OperationDropdown
@ -118,7 +131,6 @@ const ProviderCardActions: FC<Props> = ({ detail, onUpdate }) => {
onRemove={modalStates.showDeleteConfirm}
detailUrl={detailUrl}
placement="bottom-start"
popupClassName="w-[192px]"
/>
<HeaderModals