silent prefetching

This commit is contained in:
pashpashpash 2025-02-17 16:34:02 -08:00
parent 7722234761
commit 2d05dfd986
3 changed files with 92 additions and 45 deletions

View file

@ -378,6 +378,66 @@ export class ClineProvider implements vscode.WebviewViewProvider {
*
* @param webview A reference to the extension webview
*/
private async fetchMcpMarketplaceFromApi(silent: boolean = false): Promise<McpMarketplaceCatalog | undefined> {
try {
const response = await axios.get("https://api.cline.bot/v1/mcp/marketplace", {
headers: {
"Content-Type": "application/json",
},
})
if (!response.data) {
throw new Error("Invalid response from MCP marketplace API")
}
const catalog: McpMarketplaceCatalog = {
items: (response.data || []).map((item: any) => ({
...item,
githubStars: item.githubStars ?? 0,
downloadCount: item.downloadCount ?? 0,
tags: item.tags ?? [],
})),
}
// Store in global state
await this.updateGlobalState("mcpMarketplaceCatalog", catalog)
return catalog
} catch (error) {
console.error("Failed to fetch MCP marketplace:", error)
if (!silent) {
const errorMessage = error instanceof Error ? error.message : "Failed to fetch MCP marketplace"
await this.postMessageToWebview({
type: "mcpMarketplaceCatalog",
error: errorMessage,
})
vscode.window.showErrorMessage(errorMessage)
}
return undefined
}
}
async prefetchMcpMarketplace() {
try {
await this.fetchMcpMarketplaceFromApi(true)
} catch (error) {
console.error("Failed to prefetch MCP marketplace:", error)
}
}
async silentlyRefreshMcpMarketplace() {
try {
const catalog = await this.fetchMcpMarketplaceFromApi(true)
if (catalog) {
await this.postMessageToWebview({
type: "mcpMarketplaceCatalog",
mcpMarketplaceCatalog: catalog,
})
}
} catch (error) {
console.error("Failed to silently refresh MCP marketplace:", error)
}
}
private async fetchMcpMarketplace(forceRefresh: boolean = false) {
try {
// Check if we have cached data
@ -390,41 +450,12 @@ export class ClineProvider implements vscode.WebviewViewProvider {
return
}
try {
const response = await axios.get("https://api.cline.bot/v1/mcp/marketplace", {
headers: {
"Content-Type": "application/json",
},
})
if (!response.data) {
throw new Error("Invalid response from MCP marketplace API")
}
const catalog: McpMarketplaceCatalog = {
items: (response.data || []).map((item: any) => ({
...item,
githubStars: item.githubStars ?? 0,
downloadCount: item.downloadCount ?? 0,
tags: item.tags ?? [],
})),
}
// Store in global state
await this.updateGlobalState("mcpMarketplaceCatalog", catalog)
const catalog = await this.fetchMcpMarketplaceFromApi(false)
if (catalog) {
await this.postMessageToWebview({
type: "mcpMarketplaceCatalog",
mcpMarketplaceCatalog: catalog,
})
} catch (error) {
console.error("Failed to fetch MCP marketplace:", error)
const errorMessage = error instanceof Error ? error.message : "Failed to fetch MCP marketplace"
await this.postMessageToWebview({
type: "mcpMarketplaceCatalog",
error: errorMessage,
})
vscode.window.showErrorMessage(errorMessage)
}
} catch (error) {
console.error("Failed to handle cached MCP marketplace:", error)
@ -540,19 +571,23 @@ export class ClineProvider implements vscode.WebviewViewProvider {
// gui relies on model info to be up-to-date to provide the most accurate pricing, so we need to fetch the latest details on launch.
// we do this for all users since many users switch between api providers and if they were to switch back to openrouter it would be showing outdated model info if we hadn't retrieved the latest at this point
// (see normalizeApiConfiguration > openrouter)
this.refreshOpenRouterModels().then(async (openRouterModels) => {
if (openRouterModels) {
// update model info in state (this needs to be done here since we don't want to update state while settings is open, and we may refresh models there)
const { apiConfiguration } = await this.getState()
if (apiConfiguration.openRouterModelId) {
await this.updateGlobalState(
"openRouterModelInfo",
openRouterModels[apiConfiguration.openRouterModelId],
)
await this.postStateToWebview()
// Prefetch marketplace and OpenRouter models
Promise.all([
this.prefetchMcpMarketplace(),
this.refreshOpenRouterModels().then(async (openRouterModels) => {
if (openRouterModels) {
// update model info in state (this needs to be done here since we don't want to update state while settings is open, and we may refresh models there)
const { apiConfiguration } = await this.getState()
if (apiConfiguration.openRouterModelId) {
await this.updateGlobalState(
"openRouterModelInfo",
openRouterModels[apiConfiguration.openRouterModelId],
)
await this.postStateToWebview()
}
}
}
})
}),
]).catch(console.error)
break
case "newTask":
// Code that should run in response to the hello message command
@ -929,6 +964,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
}
break
}
case "silentlyRefreshMcpMarketplace": {
await this.silentlyRefreshMcpMarketplace()
break
}
case "openMcpMarketplaceServerDetails": {
if (message.mcpId) {
const response = await fetch(`https://api.cline.bot/v1/mcp/marketplace/item?mcpId=${message.mcpId}`)

View file

@ -46,6 +46,7 @@ export interface WebviewMessage {
| "fetchMcpMarketplace"
| "downloadMcp"
| "openMcpMarketplaceServerDetails"
| "silentlyRefreshMcpMarketplace"
// | "relaunchChromeDebugMode"
text?: string
disabled?: boolean

View file

@ -23,6 +23,13 @@ const McpView = ({ onDone }: McpViewProps) => {
const { mcpServers: servers } = useExtensionState()
const [activeTab, setActiveTab] = useState("marketplace")
const handleTabChange = (tab: string) => {
setActiveTab(tab)
if (tab === "marketplace") {
vscode.postMessage({ type: "silentlyRefreshMcpMarketplace" })
}
}
// const [servers, setServers] = useState<McpServer[]>([
// // Add some mock servers for testing
// {
@ -115,10 +122,10 @@ const McpView = ({ onDone }: McpViewProps) => {
padding: "0 20px 0 20px",
borderBottom: "1px solid var(--vscode-panel-border)",
}}>
<TabButton isActive={activeTab === "marketplace"} onClick={() => setActiveTab("marketplace")}>
<TabButton isActive={activeTab === "marketplace"} onClick={() => handleTabChange("marketplace")}>
Marketplace
</TabButton>
<TabButton isActive={activeTab === "installed"} onClick={() => setActiveTab("installed")}>
<TabButton isActive={activeTab === "installed"} onClick={() => handleTabChange("installed")}>
Installed
</TabButton>
</div>