Skip to content

Commit c2aaf5f

Browse files
committed
feat: 复活插件统计
1 parent 58cb4ec commit c2aaf5f

10 files changed

Lines changed: 413 additions & 200 deletions

File tree

dashboard/src/components/plugin-stats.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
import { Textarea } from '@/components/ui/textarea'
1919
import { useToast } from '@/hooks/use-toast'
2020
import {
21-
getPluginStatsByIds,
21+
getPluginStats,
2222
likePlugin,
2323
dislikePlugin,
2424
ratePlugin,
@@ -27,11 +27,10 @@ import {
2727

2828
interface PluginStatsProps {
2929
pluginId: string
30-
pluginIds?: string[]
3130
compact?: boolean // 紧凑模式(只显示数字)
3231
}
3332

34-
export function PluginStats({ pluginId, pluginIds, compact = false }: PluginStatsProps) {
33+
export function PluginStats({ pluginId, compact = false }: PluginStatsProps) {
3534
const [stats, setStats] = useState<PluginStatsData | null>(null)
3635
const [loading, setLoading] = useState(true)
3736
const [userRating, setUserRating] = useState(0)
@@ -42,7 +41,7 @@ export function PluginStats({ pluginId, pluginIds, compact = false }: PluginStat
4241
// 加载统计数据
4342
const loadStats = async () => {
4443
setLoading(true)
45-
const data = await getPluginStatsByIds(pluginIds?.length ? pluginIds : [pluginId])
44+
const data = await getPluginStats(pluginId)
4645
if (data) {
4746
setStats(data)
4847
}

dashboard/src/components/route-pending-fallback.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
1+
import { useEffect, useState } from 'react'
2+
13
export function RoutePendingFallback() {
4+
const [dotCount, setDotCount] = useState(6)
5+
6+
useEffect(() => {
7+
const timer = window.setInterval(() => {
8+
setDotCount((current) => (current >= 6 ? 2 : current + 1))
9+
}, 450)
10+
11+
return () => window.clearInterval(timer)
12+
}, [])
13+
214
return (
315
<div className="flex h-full items-center justify-center bg-background/80">
4-
<div className="rounded-xl border bg-card px-4 py-3 text-sm text-muted-foreground shadow-sm">
5-
正在切换页面...
16+
<div className="min-w-[10rem] rounded-xl border bg-card px-5 py-3.5 text-base font-medium text-muted-foreground shadow-sm">
17+
Thinking{'.'.repeat(dotCount)}
618
</div>
719
</div>
820
)

dashboard/src/lib/plugin-api/marketplace.ts

Lines changed: 106 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@ const PLUGIN_REPO_OWNER = 'Mai-with-u'
1313
const PLUGIN_REPO_NAME = 'plugin-repo'
1414
const PLUGIN_REPO_BRANCH = 'main'
1515
const PLUGIN_DETAILS_FILE = 'plugin_details.json'
16+
const PLUGIN_LIST_CACHE_TTL = 5 * 60 * 1000
17+
const PLUGIN_LIST_STORAGE_KEY = 'maibot-plugin-market-list-cache'
18+
19+
let pluginListCache: { timestamp: number; result: ApiResponse<PluginInfo[]> } | null = null
20+
let pluginListRequest: Promise<ApiResponse<PluginInfo[]>> | null = null
21+
22+
interface PluginListStorageCache {
23+
timestamp: number
24+
data: PluginInfo[]
25+
}
1626

1727
/**
1828
* 插件列表 API 响应类型(只包含我们需要的字段)
@@ -78,10 +88,69 @@ function normalizePluginManifest(manifest: PluginApiResponse['manifest']): Plugi
7888
}
7989
}
8090

91+
function readPluginListStorageCache(): PluginListStorageCache | null {
92+
if (typeof localStorage === 'undefined') {
93+
return null
94+
}
95+
96+
try {
97+
const rawCache = localStorage.getItem(PLUGIN_LIST_STORAGE_KEY)
98+
if (!rawCache) {
99+
return null
100+
}
101+
102+
const cache = JSON.parse(rawCache) as Partial<PluginListStorageCache>
103+
if (!cache.timestamp || !Array.isArray(cache.data)) {
104+
return null
105+
}
106+
107+
return {
108+
timestamp: Number(cache.timestamp),
109+
data: cache.data,
110+
}
111+
} catch (error) {
112+
console.warn('读取插件市场缓存失败:', error)
113+
return null
114+
}
115+
}
116+
117+
function writePluginListStorageCache(data: PluginInfo[]): void {
118+
if (typeof localStorage === 'undefined') {
119+
return
120+
}
121+
122+
try {
123+
localStorage.setItem(
124+
PLUGIN_LIST_STORAGE_KEY,
125+
JSON.stringify({
126+
timestamp: Date.now(),
127+
data,
128+
})
129+
)
130+
} catch (error) {
131+
console.warn('写入插件市场缓存失败:', error)
132+
}
133+
}
134+
135+
export function getCachedPluginList(): PluginInfo[] | null {
136+
if (pluginListCache?.result.success) {
137+
return pluginListCache.result.data
138+
}
139+
140+
const storedCache = readPluginListStorageCache()
141+
if (!storedCache) {
142+
return null
143+
}
144+
145+
const result: ApiResponse<PluginInfo[]> = { success: true, data: storedCache.data }
146+
pluginListCache = { timestamp: storedCache.timestamp, result }
147+
return storedCache.data
148+
}
149+
81150
/**
82151
* 从远程获取插件列表(通过后端代理避免 CORS)
83152
*/
84-
export async function fetchPluginList(): Promise<ApiResponse<PluginInfo[]>> {
153+
async function fetchPluginListUncached(): Promise<ApiResponse<PluginInfo[]>> {
85154
const response = await fetchWithAuth('/api/webui/plugins/fetch-raw', {
86155
method: 'POST',
87156
body: JSON.stringify({
@@ -133,7 +202,7 @@ export async function fetchPluginList(): Promise<ApiResponse<PluginInfo[]>> {
133202
return {
134203
id: pluginId,
135204
marketplace_id: marketplaceId,
136-
stats_ids: uniqueNonEmptyValues([marketplaceId, manifestId, pluginId]),
205+
stats_ids: uniqueNonEmptyValues([manifestId]),
137206
manifest: normalizePluginManifest({ ...item.manifest, id: pluginId }),
138207
downloads: 0,
139208
rating: 0,
@@ -151,6 +220,41 @@ export async function fetchPluginList(): Promise<ApiResponse<PluginInfo[]>> {
151220
}
152221
}
153222

223+
export async function fetchPluginList(options: { forceRefresh?: boolean } = {}): Promise<ApiResponse<PluginInfo[]>> {
224+
if (
225+
!options.forceRefresh
226+
&& pluginListCache
227+
&& Date.now() - pluginListCache.timestamp < PLUGIN_LIST_CACHE_TTL
228+
) {
229+
return pluginListCache.result
230+
}
231+
232+
if (!options.forceRefresh && !pluginListCache) {
233+
const storedCache = readPluginListStorageCache()
234+
if (storedCache && Date.now() - storedCache.timestamp < PLUGIN_LIST_CACHE_TTL) {
235+
const result: ApiResponse<PluginInfo[]> = { success: true, data: storedCache.data }
236+
pluginListCache = { timestamp: storedCache.timestamp, result }
237+
return result
238+
}
239+
}
240+
241+
if (!pluginListRequest || options.forceRefresh) {
242+
pluginListRequest = fetchPluginListUncached()
243+
.then((result) => {
244+
if (result.success) {
245+
pluginListCache = { timestamp: Date.now(), result }
246+
writePluginListStorageCache(result.data)
247+
}
248+
return result
249+
})
250+
.finally(() => {
251+
pluginListRequest = null
252+
})
253+
}
254+
255+
return pluginListRequest
256+
}
257+
154258
/**
155259
* 检查本机 Git 安装状态
156260
*/

0 commit comments

Comments
 (0)