@@ -13,6 +13,16 @@ const PLUGIN_REPO_OWNER = 'Mai-with-u'
1313const PLUGIN_REPO_NAME = 'plugin-repo'
1414const PLUGIN_REPO_BRANCH = 'main'
1515const 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