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

@ -1,11 +1,14 @@
import type { CategoryKey, TagKey } from './constants'
import type { PluginDetail } from './types'
import { useQuery } from '@tanstack/react-query'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { consoleQuery } from '@/service/client'
import {
categoryKeys,
tagKeys,
} from './constants'
import { PluginCategoryEnum } from './types'
import { PluginCategoryEnum, PluginSource } from './types'
export type Tag = {
name: TagKey
@ -95,3 +98,39 @@ export const usePluginPageTabs = () => {
]
return tabs
}
const EMPTY_PLUGINS: PluginDetail[] = []
export function usePluginsWithLatestVersion(plugins: PluginDetail[] = EMPTY_PLUGINS): PluginDetail[] {
const marketplacePluginIds = useMemo(
() => plugins
.filter(p => p.source === PluginSource.marketplace)
.map(p => p.plugin_id),
[plugins],
)
const { data: latestVersionData } = useQuery(consoleQuery.plugins.latestVersions.queryOptions({
input: { body: { plugin_ids: marketplacePluginIds } },
enabled: !!marketplacePluginIds.length,
}))
return useMemo(() => {
const versions = latestVersionData?.versions
if (!versions)
return plugins
return plugins.map((plugin) => {
const info = versions[plugin.plugin_id]
if (!info)
return plugin
return {
...plugin,
latest_version: info.version,
latest_unique_identifier: info.unique_identifier,
status: info.status,
deprecated_reason: info.deprecated_reason,
alternative_plugin_id: info.alternative_plugin_id,
}
})
}, [plugins, latestVersionData])
}

View File

@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import Badge from '@/app/components/base/badge'
import Button from '@/app/components/base/button'
import Tooltip from '@/app/components/base/tooltip'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip'
import { AuthCategory, PluginAuth } from '@/app/components/plugins/plugin-auth'
import OperationDropdown from '@/app/components/plugins/plugin-detail-panel/operation-dropdown'
import PluginVersionPicker from '@/app/components/plugins/update-plugin/plugin-version-picker'
@ -191,25 +191,43 @@ const DetailHeader = ({
{/* Auto Update Badge */}
{isAutoUpgradeEnabled && !isReadmeView && (
<Tooltip popupContent={t('autoUpdate.nextUpdateTime', { ns: 'plugin', time: timeOfDayToDayjs(convertUTCDaySecondsToLocalSeconds(autoUpgradeInfo?.upgrade_time_of_day || 0, timezone!)).format('hh:mm A') })}>
<div>
<Badge className="mr-1 cursor-pointer px-1">
<AutoUpdateLine className="size-3" />
</Badge>
</div>
<Tooltip>
<TooltipTrigger
delay={0}
render={(
<div>
<Badge className="mr-1 cursor-pointer px-1">
<AutoUpdateLine className="size-3" />
</Badge>
</div>
)}
/>
<TooltipContent>
{t('autoUpdate.nextUpdateTime', { ns: 'plugin', time: timeOfDayToDayjs(convertUTCDaySecondsToLocalSeconds(autoUpgradeInfo?.upgrade_time_of_day || 0, timezone!)).format('hh:mm A') })}
</TooltipContent>
</Tooltip>
)}
{/* Update Button */}
{(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>
)}
</div>

View File

@ -52,7 +52,7 @@ const OperationDropdown: FC<Props> = ({
placement={placement}
sideOffset={sideOffset}
alignOffset={alignOffset}
popupClassName={cn('w-[160px]', popupClassName)}
popupClassName={cn('w-auto min-w-[160px]', popupClassName)}
>
{source === PluginSource.github && (
<DropdownMenuItem onClick={onInfo}>

View File

@ -9,8 +9,8 @@ import Loading from '@/app/components/base/loading'
import PluginDetailPanel from '@/app/components/plugins/plugin-detail-panel'
import { useGetLanguage } from '@/context/i18n'
import { renderI18nObject } from '@/i18n-config'
import { useInstalledLatestVersion, useInstalledPluginList, useInvalidateInstalledPluginList } from '@/service/use-plugins'
import { PluginSource } from '../types'
import { useInstalledPluginList, useInvalidateInstalledPluginList } from '@/service/use-plugins'
import { usePluginsWithLatestVersion } from '../hooks'
import { usePluginPageContext } from './context'
import Empty from './empty'
import FilterManagement from './filter-management'
@ -47,11 +47,7 @@ const PluginsPanel = () => {
const filters = usePluginPageContext(v => v.filters) as FilterState
const setFilters = usePluginPageContext(v => v.setFilters)
const { data: pluginList, isLoading: isPluginListLoading, isFetching, isLastPage, loadNextPage } = useInstalledPluginList()
const { data: installedLatestVersion } = useInstalledLatestVersion(
pluginList?.plugins
.filter(plugin => plugin.source === PluginSource.marketplace)
.map(plugin => plugin.plugin_id) ?? [],
)
const pluginListWithLatestVersion = usePluginsWithLatestVersion(pluginList?.plugins)
const invalidateInstalledPluginList = useInvalidateInstalledPluginList()
const currentPluginID = usePluginPageContext(v => v.currentPluginID)
const setCurrentPluginID = usePluginPageContext(v => v.setCurrentPluginID)
@ -60,17 +56,6 @@ const PluginsPanel = () => {
setFilters(filters)
}, { wait: 500 })
const pluginListWithLatestVersion = useMemo(() => {
return pluginList?.plugins.map(plugin => ({
...plugin,
latest_version: installedLatestVersion?.versions[plugin.plugin_id]?.version ?? '',
latest_unique_identifier: installedLatestVersion?.versions[plugin.plugin_id]?.unique_identifier ?? '',
status: installedLatestVersion?.versions[plugin.plugin_id]?.status ?? 'active',
deprecated_reason: installedLatestVersion?.versions[plugin.plugin_id]?.deprecated_reason ?? '',
alternative_plugin_id: installedLatestVersion?.versions[plugin.plugin_id]?.alternative_plugin_id ?? '',
})) || []
}, [pluginList, installedLatestVersion])
const filteredList = useMemo(() => {
const { categories, searchQuery, tags } = filters
const filteredList = pluginListWithLatestVersion.filter((plugin) => {