UNPKG

@lobehub/chat

Version:

Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.

366 lines (313 loc) 11.2 kB
import { CategoryItem, CategoryListQuery, PluginManifest } from '@lobehub/market-sdk'; import { CallReportRequest, InstallReportRequest } from '@lobehub/market-types'; import { lambdaClient } from '@/libs/trpc/client'; import { globalHelpers } from '@/store/global/helpers'; import { AssistantListResponse, AssistantQueryParams, DiscoverAssistantDetail, DiscoverMcpDetail, DiscoverModelDetail, DiscoverPluginDetail, DiscoverProviderDetail, IdentifiersResponse, McpListResponse, McpQueryParams, ModelListResponse, ModelQueryParams, PluginListResponse, PluginQueryParams, ProviderListResponse, ProviderQueryParams, } from '@/types/discover'; import { MCPPluginListParams } from '@/types/plugins'; import { cleanObject } from '@/utils/object'; class DiscoverService { private _isRetrying = false; // ============================== Assistant Market ============================== getAssistantCategories = async (params: CategoryListQuery = {}): Promise<CategoryItem[]> => { const locale = globalHelpers.getCurrentLanguage(); return lambdaClient.market.getAssistantCategories.query({ ...params, locale, }); }; getAssistantDetail = async (params: { identifier: string; locale?: string; }): Promise<DiscoverAssistantDetail | undefined> => { const locale = globalHelpers.getCurrentLanguage(); return lambdaClient.market.getAssistantDetail.query({ ...params, locale, }); }; getAssistantIdentifiers = async (): Promise<IdentifiersResponse> => { return lambdaClient.market.getAssistantIdentifiers.query(); }; getAssistantList = async (params: AssistantQueryParams = {}): Promise<AssistantListResponse> => { const locale = globalHelpers.getCurrentLanguage(); return lambdaClient.market.getAssistantList.query({ ...params, locale, page: params.page ? Number(params.page) : 1, pageSize: params.pageSize ? Number(params.pageSize) : 20, }); }; // ============================== MCP Market ============================== getMcpCategories = async (params: CategoryListQuery = {}): Promise<CategoryItem[]> => { const locale = globalHelpers.getCurrentLanguage(); return lambdaClient.market.getMcpCategories.query({ ...params, locale, }); }; getMcpDetail = async (params: { identifier: string; locale?: string; version?: string; }): Promise<DiscoverMcpDetail> => { const locale = globalHelpers.getCurrentLanguage(); return lambdaClient.market.getMcpDetail.query({ ...params, locale, }); }; getMcpIdentifiers = async (): Promise<IdentifiersResponse> => { return lambdaClient.market.getMcpIdentifiers.query(); }; getMcpList = async (params: McpQueryParams = {}): Promise<McpListResponse> => { const locale = globalHelpers.getCurrentLanguage(); return lambdaClient.market.getMcpList.query({ ...params, locale, page: params.page ? Number(params.page) : 1, pageSize: params.pageSize ? Number(params.pageSize) : 20, }); }; getMCPPluginList = async (params: MCPPluginListParams): Promise<McpListResponse> => { await this.injectMPToken(); const locale = globalHelpers.getCurrentLanguage(); return lambdaClient.market.getMcpList.query({ ...params, locale, page: params.page ? Number(params.page) : 1, pageSize: params.pageSize ? Number(params.pageSize) : 21, }); }; getMcpManifest = async (params: { identifier: string; locale?: string; version?: string }) => { const locale = globalHelpers.getCurrentLanguage(); return lambdaClient.market.getMcpManifest.query({ ...params, locale, }); }; getMCPPluginManifest = async ( identifier: string, options: { install?: boolean } = {}, ): Promise<PluginManifest> => { const locale = globalHelpers.getCurrentLanguage(); return lambdaClient.market.getMcpManifest.query({ identifier, install: options.install, locale, }); }; registerClient = () => { return lambdaClient.market.registerClientInMarketplace.mutate({}); }; /** * 上报 MCP 插件安装结果 */ reportMcpInstallResult = async ({ success, manifest, errorMessage, errorCode, ...params }: InstallReportRequest) => { await this.injectMPToken(); const reportData = { errorCode: success ? undefined : errorCode, errorMessage: success ? undefined : errorMessage, manifest: success ? manifest : undefined, success, ...params, }; lambdaClient.market.reportMcpInstallResult .mutate(cleanObject(reportData)) .catch((reportError) => { console.warn('Failed to report MCP installation result:', reportError); }); }; /** * 上报插件调用结果 */ reportPluginCall = async (reportData: CallReportRequest) => { await this.injectMPToken(); lambdaClient.market.reportCall.mutate(cleanObject(reportData)).catch((reportError) => { console.warn('Failed to report call:', reportError); }); }; // ============================== Models ============================== getModelCategories = async (params: CategoryListQuery = {}): Promise<CategoryItem[]> => { return lambdaClient.market.getModelCategories.query(params); }; getModelDetail = async (params: { identifier: string; locale?: string; }): Promise<DiscoverModelDetail | undefined> => { const locale = globalHelpers.getCurrentLanguage(); return lambdaClient.market.getModelDetail.query({ ...params, locale, }); }; getModelIdentifiers = async (): Promise<IdentifiersResponse> => { return lambdaClient.market.getModelIdentifiers.query(); }; getModelList = async (params: ModelQueryParams = {}): Promise<ModelListResponse> => { const locale = globalHelpers.getCurrentLanguage(); return lambdaClient.market.getModelList.query({ ...params, locale, page: params.page ? Number(params.page) : 1, pageSize: params.pageSize ? Number(params.pageSize) : 20, }); }; // ============================== Plugin Market ============================== getPluginCategories = async (params: CategoryListQuery = {}): Promise<CategoryItem[]> => { const locale = globalHelpers.getCurrentLanguage(); return lambdaClient.market.getPluginCategories.query({ ...params, locale, }); }; getPluginDetail = async (params: { identifier: string; locale?: string; withManifest?: boolean; }): Promise<DiscoverPluginDetail | undefined> => { const locale = globalHelpers.getCurrentLanguage(); return lambdaClient.market.getPluginDetail.query({ ...params, locale, }); }; getPluginIdentifiers = async (): Promise<IdentifiersResponse> => { return lambdaClient.market.getPluginIdentifiers.query(); }; getPluginList = async (params: PluginQueryParams = {}): Promise<PluginListResponse> => { const locale = globalHelpers.getCurrentLanguage(); return lambdaClient.market.getPluginList.query({ ...params, locale, page: params.page ? Number(params.page) : 1, pageSize: params.pageSize ? Number(params.pageSize) : 20, }); }; // ============================== Providers ============================== getProviderDetail = async (params: { identifier: string; locale?: string; withReadme?: boolean; }): Promise<DiscoverProviderDetail | undefined> => { const locale = globalHelpers.getCurrentLanguage(); return lambdaClient.market.getProviderDetail.query({ ...params, locale, }); }; getProviderIdentifiers = async (): Promise<IdentifiersResponse> => { return lambdaClient.market.getProviderIdentifiers.query(); }; getProviderList = async (params: ProviderQueryParams = {}): Promise<ProviderListResponse> => { const locale = globalHelpers.getCurrentLanguage(); return lambdaClient.market.getProviderList.query({ ...params, locale, page: params.page ? Number(params.page) : 1, pageSize: params.pageSize ? Number(params.pageSize) : 20, }); }; // ============================== Helpers ============================== private async injectMPToken() { if (typeof localStorage === 'undefined') return; // 检查服务端设置的状态标记 cookie const tokenStatus = this.getTokenStatusFromCookie(); if (tokenStatus === 'active') return; let clientId: string; let clientSecret: string; // 1. 从 localStorage 获取客户端信息 const item = localStorage.getItem('_mpc'); if (!item) { // 2. 如果没有,则注册客户端 const clientInfo = await this.registerClient(); clientId = clientInfo.clientId; clientSecret = clientInfo.clientSecret; // 3. Base64 编码并保存到 localStorage const clientData = JSON.stringify({ clientId, clientSecret }); const encodedData = btoa(clientData); localStorage.setItem('_mpc', encodedData); } else { // 4. 如果有,则解码获取客户端信息 try { const decodedData = atob(item); const clientData = JSON.parse(decodedData); clientId = clientData.clientId; clientSecret = clientData.clientSecret; } catch (error) { console.error('Failed to decode client data:', error); // 如果解码失败,重新注册 const clientInfo = await this.registerClient(); clientId = clientInfo.clientId; clientSecret = clientInfo.clientSecret; const clientData = JSON.stringify({ clientId, clientSecret }); const encodedData = btoa(clientData); localStorage.setItem('_mpc', encodedData); } } // 5. 获取访问令牌(服务端会自动设置 HTTP-Only cookie) try { const result = await lambdaClient.market.registerM2MToken.query({ clientId, clientSecret, }); // 检查服务端返回的结果 if (!result.success) { console.warn( 'Token registration failed, client credentials may be invalid. Clearing and retrying...', ); // 清空相关的本地存储数据 localStorage.removeItem('_mpc'); // 重新执行完整的注册流程(但只重试一次) if (!this._isRetrying) { this._isRetrying = true; try { await this.injectMPToken(); } finally { this._isRetrying = false; } } else { console.error('Failed to re-register after credential invalidation'); } return; } } catch (error) { console.error('Failed to register M2M token:', error); return null; } } private getTokenStatusFromCookie(): string | null { if (typeof document === 'undefined') return null; const cookies = document.cookie.split(';'); for (const cookie of cookies) { const [name, value] = cookie.trim().split('='); if (name === 'mp_token_status') { return value; } } return null; } } export const discoverService = new DiscoverService();