mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-30 22:27:13 +00:00
fix(add-extension): load real icon in install confirm dialog from URL params
When the install confirm dialog is opened via URL query params (e.g. from a marketplace deep link), installInfo carried no icon, so the icon fell back to the /resources/icon endpoint which 404s for extensions whose icon is an external URL (simpleicons / iconify), showing a Package placeholder. Fetch the icon from the marketplace detail API (mcp/skill/plugin) after opening the dialog and inject it into installInfo, and reset the icon-failed state when the resolved URL changes so the <img> retries instead of sticking on the placeholder.
This commit is contained in:
@@ -192,6 +192,13 @@ function AddExtensionContent() {
|
|||||||
);
|
);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
// When the resolved icon URL changes (e.g. the real external icon arrives
|
||||||
|
// after an async fetch), clear any prior load failure so the <img> retries
|
||||||
|
// instead of staying on the placeholder.
|
||||||
|
useEffect(() => {
|
||||||
|
setInstallIconFailed(false);
|
||||||
|
}, [installIconURL]);
|
||||||
|
|
||||||
const [popoverOpen, setPopoverOpen] = useState(false);
|
const [popoverOpen, setPopoverOpen] = useState(false);
|
||||||
const [popoverView, setPopoverView] = useState<PopoverView>('menu');
|
const [popoverView, setPopoverView] = useState<PopoverView>('menu');
|
||||||
const [isDragOver, setIsDragOver] = useState(false);
|
const [isDragOver, setIsDragOver] = useState(false);
|
||||||
@@ -279,6 +286,23 @@ function AddExtensionContent() {
|
|||||||
setInstallIconFailed(false);
|
setInstallIconFailed(false);
|
||||||
setModalOpen(true);
|
setModalOpen(true);
|
||||||
|
|
||||||
|
// The icon is not carried in the URL params, so fetch it from the
|
||||||
|
// marketplace record. Without this the confirm dialog falls back to the
|
||||||
|
// /resources/icon endpoint, which 404s for extensions whose icon is an
|
||||||
|
// external URL (simpleicons / iconify), showing a placeholder.
|
||||||
|
const cloud = getCloudServiceClientSync();
|
||||||
|
cloud
|
||||||
|
.fetchMarketplaceIcon(extType, author, name)
|
||||||
|
.then((icon) => {
|
||||||
|
if (!icon) return;
|
||||||
|
setInstallInfo((prev) =>
|
||||||
|
prev.plugin_author === author && prev.plugin_name === name
|
||||||
|
? { ...prev, plugin_icon: icon }
|
||||||
|
: prev,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
|
||||||
setSearchParams(
|
setSearchParams(
|
||||||
(current) => {
|
(current) => {
|
||||||
const next = new URLSearchParams(current);
|
const next = new URLSearchParams(current);
|
||||||
|
|||||||
@@ -207,6 +207,52 @@ export class CloudServiceClient extends BaseHttpClient {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getMCPDetail(
|
||||||
|
author: string,
|
||||||
|
name: string,
|
||||||
|
): Promise<{ mcp: PluginV4 }> {
|
||||||
|
return this.get<{ mcp: PluginV4 }>(
|
||||||
|
`/api/v1/marketplace/mcps/${author}/${name}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSkillDetail(
|
||||||
|
author: string,
|
||||||
|
name: string,
|
||||||
|
): Promise<{ skill: PluginV4 }> {
|
||||||
|
return this.get<{ skill: PluginV4 }>(
|
||||||
|
`/api/v1/marketplace/skills/${author}/${name}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the marketplace ``icon`` field for an extension by author/name/type.
|
||||||
|
* Used when the icon is not already known locally (e.g. an install confirm
|
||||||
|
* dialog opened from a URL query param carries no icon). Returns the raw
|
||||||
|
* ``icon`` value from the marketplace record (often an absolute external URL),
|
||||||
|
* or an empty string if it cannot be fetched.
|
||||||
|
*/
|
||||||
|
public async fetchMarketplaceIcon(
|
||||||
|
type: 'plugin' | 'mcp' | 'skill' | undefined,
|
||||||
|
author: string,
|
||||||
|
name: string,
|
||||||
|
): Promise<string> {
|
||||||
|
try {
|
||||||
|
if (type === 'mcp') {
|
||||||
|
const resp = await this.getMCPDetail(author, name);
|
||||||
|
return resp?.mcp?.icon || '';
|
||||||
|
}
|
||||||
|
if (type === 'skill') {
|
||||||
|
const resp = await this.getSkillDetail(author, name);
|
||||||
|
return resp?.skill?.icon || '';
|
||||||
|
}
|
||||||
|
const resp = await this.getPluginDetail(author, name);
|
||||||
|
return resp?.plugin?.icon || '';
|
||||||
|
} catch {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public getPluginREADME(
|
public getPluginREADME(
|
||||||
author: string,
|
author: string,
|
||||||
pluginName: string,
|
pluginName: string,
|
||||||
|
|||||||
Reference in New Issue
Block a user