mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-12 22:00:57 +00:00
fix(marketplace): use external icon URL when icon field is absolute
Many MCP / skill records store their icon as an absolute external URL (simpleicons.org / iconify.design) rather than an uploaded file, so the /resources/icon endpoint 404s and the card icon breaks. Add resolveMarketplaceIconURL() which prefers an absolute http(s) icon field and otherwise falls back to the resources endpoint.
This commit is contained in:
@@ -184,11 +184,12 @@ function AddExtensionContent() {
|
|||||||
const cloud = getCloudServiceClientSync();
|
const cloud = getCloudServiceClientSync();
|
||||||
const a = installInfo.plugin_author || '';
|
const a = installInfo.plugin_author || '';
|
||||||
const n = installInfo.plugin_name || '';
|
const n = installInfo.plugin_name || '';
|
||||||
if (installExtensionType === 'mcp')
|
return cloud.resolveMarketplaceIconURL(
|
||||||
return cloud.getMCPMarketplaceIconURL(a, n);
|
installExtensionType,
|
||||||
if (installExtensionType === 'skill')
|
a,
|
||||||
return cloud.getSkillMarketplaceIconURL(a, n);
|
n,
|
||||||
return cloud.getPluginIconURL(a, n);
|
installInfo.plugin_icon,
|
||||||
|
);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const [popoverOpen, setPopoverOpen] = useState(false);
|
const [popoverOpen, setPopoverOpen] = useState(false);
|
||||||
@@ -326,6 +327,7 @@ function AddExtensionContent() {
|
|||||||
plugin_version: plugin.latest_version,
|
plugin_version: plugin.latest_version,
|
||||||
plugin_label: extractI18nObject(plugin.label) || plugin.name,
|
plugin_label: extractI18nObject(plugin.label) || plugin.name,
|
||||||
plugin_description: extractI18nObject(plugin.description) || '',
|
plugin_description: extractI18nObject(plugin.description) || '',
|
||||||
|
plugin_icon: plugin.icon || '',
|
||||||
});
|
});
|
||||||
setInstallExtensionType(plugin.type || 'plugin');
|
setInstallExtensionType(plugin.type || 'plugin');
|
||||||
setPluginInstallStatus(PluginInstallStatus.ASK_CONFIRM);
|
setPluginInstallStatus(PluginInstallStatus.ASK_CONFIRM);
|
||||||
|
|||||||
@@ -216,12 +216,12 @@ function MarketPageContent({
|
|||||||
const transformToVO = useCallback(
|
const transformToVO = useCallback(
|
||||||
(plugin: PluginV4): PluginMarketCardVO => {
|
(plugin: PluginV4): PluginMarketCardVO => {
|
||||||
const cloudClient = getCloudServiceClientSync();
|
const cloudClient = getCloudServiceClientSync();
|
||||||
const iconURL =
|
const iconURL = cloudClient.resolveMarketplaceIconURL(
|
||||||
plugin.type === 'mcp'
|
plugin.type,
|
||||||
? cloudClient.getMCPMarketplaceIconURL(plugin.author, plugin.name)
|
plugin.author,
|
||||||
: plugin.type === 'skill'
|
plugin.name,
|
||||||
? cloudClient.getSkillMarketplaceIconURL(plugin.author, plugin.name)
|
plugin.icon,
|
||||||
: cloudClient.getPluginIconURL(plugin.author, plugin.name);
|
);
|
||||||
|
|
||||||
return new PluginMarketCardVO({
|
return new PluginMarketCardVO({
|
||||||
pluginId: plugin.author + ' / ' + plugin.name,
|
pluginId: plugin.author + ' / ' + plugin.name,
|
||||||
|
|||||||
@@ -23,13 +23,14 @@ function pluginToVO(
|
|||||||
t: (key: string) => string,
|
t: (key: string) => string,
|
||||||
): PluginMarketCardVO {
|
): PluginMarketCardVO {
|
||||||
const cloudClient = getCloudServiceClientSync();
|
const cloudClient = getCloudServiceClientSync();
|
||||||
// Recommendation lists are mixed-type; resolve the icon per extension type.
|
// Recommendation lists are mixed-type; resolve the icon per extension type,
|
||||||
const iconURL =
|
// preferring an absolute external icon URL when the record carries one.
|
||||||
plugin.type === 'mcp'
|
const iconURL = cloudClient.resolveMarketplaceIconURL(
|
||||||
? cloudClient.getMCPMarketplaceIconURL(plugin.author, plugin.name)
|
plugin.type,
|
||||||
: plugin.type === 'skill'
|
plugin.author,
|
||||||
? cloudClient.getSkillMarketplaceIconURL(plugin.author, plugin.name)
|
plugin.name,
|
||||||
: cloudClient.getPluginIconURL(plugin.author, plugin.name);
|
plugin.icon,
|
||||||
|
);
|
||||||
|
|
||||||
return new PluginMarketCardVO({
|
return new PluginMarketCardVO({
|
||||||
pluginId: plugin.author + ' / ' + plugin.name,
|
pluginId: plugin.author + ' / ' + plugin.name,
|
||||||
|
|||||||
@@ -230,6 +230,29 @@ export class CloudServiceClient extends BaseHttpClient {
|
|||||||
return `${this.baseURL}/api/v1/marketplace/skills/${author}/${name}/resources/icon`;
|
return `${this.baseURL}/api/v1/marketplace/skills/${author}/${name}/resources/icon`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the best icon URL for a marketplace extension.
|
||||||
|
*
|
||||||
|
* Many MCP / skill records store their ``icon`` as an absolute external URL
|
||||||
|
* (e.g. simpleicons.org / iconify.design logos) rather than a file uploaded
|
||||||
|
* to Space storage. For those, the ``/resources/icon`` endpoint 404s, so we
|
||||||
|
* must use the external URL directly. Records whose ``icon`` is empty or a
|
||||||
|
* relative path fall back to the ``/resources/icon`` endpoint (real uploads).
|
||||||
|
*/
|
||||||
|
public resolveMarketplaceIconURL(
|
||||||
|
type: 'plugin' | 'mcp' | 'skill' | undefined,
|
||||||
|
author: string,
|
||||||
|
name: string,
|
||||||
|
icon?: string,
|
||||||
|
): string {
|
||||||
|
if (icon && /^https?:\/\//i.test(icon)) {
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
if (type === 'mcp') return this.getMCPMarketplaceIconURL(author, name);
|
||||||
|
if (type === 'skill') return this.getSkillMarketplaceIconURL(author, name);
|
||||||
|
return this.getPluginIconURL(author, name);
|
||||||
|
}
|
||||||
|
|
||||||
public getPluginAssetURL(
|
public getPluginAssetURL(
|
||||||
author: string,
|
author: string,
|
||||||
pluginName: string,
|
pluginName: string,
|
||||||
|
|||||||
Reference in New Issue
Block a user