feat: Enhance Marketplace and Skill Management UI with improved error handling and user feedback
- Updated MarketplaceDrawer to include security notes and manual installation hints. - Refactored SkillDetailDrawer to display default icons for skills. - Simplified SkillListItem to use default icons for better readability. - Integrated gateway status checks and warnings in SkillsPage for improved user awareness. - Enhanced error handling for skill installation and fetching, providing clearer feedback to users. - Added new translations for error messages and gateway warnings to improve localization support.
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import type { BrowserWindow } from 'electron';
|
||||
import type { gatewayManager } from '@electron/gateway/manager';
|
||||
import type { providerApiService } from '@electron/service/provider-api-service';
|
||||
import type { ClawHubService } from '@electron/gateway/clawhub';
|
||||
|
||||
export interface HostApiContext {
|
||||
gatewayManager: typeof gatewayManager;
|
||||
providerApiService: typeof providerApiService;
|
||||
mainWindow: BrowserWindow | null;
|
||||
clawHubService: ClawHubService;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { BrowserWindow } from 'electron';
|
||||
import { gatewayManager } from '@electron/gateway/manager';
|
||||
import { providerApiService } from '@electron/service/provider-api-service';
|
||||
import { ClawHubService } from '@electron/gateway/clawhub';
|
||||
import type { HostApiContext } from './context';
|
||||
import type { HostApiRequest } from './route-utils';
|
||||
import { normalizeRequest } from './route-utils';
|
||||
@@ -13,6 +14,7 @@ import { handleKnowledgeRoutes } from './routes/knowledge';
|
||||
import { handleModelRoutes } from './routes/models';
|
||||
import { handleProviderRoutes } from './routes/providers';
|
||||
import { handleSessionRoutes } from './routes/sessions';
|
||||
import { handleSkillRoutes } from './routes/skills';
|
||||
|
||||
type RouteHandler = (
|
||||
request: ReturnType<typeof normalizeRequest>,
|
||||
@@ -29,6 +31,7 @@ const routeHandlers: RouteHandler[] = [
|
||||
handleKnowledgeRoutes,
|
||||
handleFileRoutes,
|
||||
handleSessionRoutes,
|
||||
handleSkillRoutes,
|
||||
];
|
||||
|
||||
function createContext(): HostApiContext {
|
||||
@@ -36,6 +39,7 @@ function createContext(): HostApiContext {
|
||||
gatewayManager,
|
||||
providerApiService,
|
||||
mainWindow: BrowserWindow.getAllWindows()[0] ?? null,
|
||||
clawHubService: new ClawHubService(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
152
electron/api/routes/skills.ts
Normal file
152
electron/api/routes/skills.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import type { HostApiResult } from '@src/types/runtime';
|
||||
import type { HostApiContext } from '../context';
|
||||
import type { NormalizedHostApiRequest } from '../route-utils';
|
||||
import { fail, ok, parseJsonBody } from '../route-utils';
|
||||
import { getAllSkillConfigs, updateSkillConfig } from '../../utils/skill-config';
|
||||
import { shell } from 'electron';
|
||||
import { getOpenClawConfigDir } from '../../utils/paths';
|
||||
|
||||
export async function handleSkillRoutes(
|
||||
request: NormalizedHostApiRequest,
|
||||
ctx: HostApiContext,
|
||||
): Promise<HostApiResult<unknown> | null> {
|
||||
const { pathname, method } = request;
|
||||
|
||||
if (pathname === '/api/skills/configs' && method === 'GET') {
|
||||
try {
|
||||
return ok(await getAllSkillConfigs());
|
||||
} catch (error) {
|
||||
return fail(500, error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
}
|
||||
|
||||
if (pathname === '/api/skills/config' && method === 'PUT') {
|
||||
try {
|
||||
const body = parseJsonBody<{
|
||||
skillKey: string;
|
||||
apiKey?: string;
|
||||
env?: Record<string, string>;
|
||||
enabled?: boolean;
|
||||
}>(request.body);
|
||||
if (!body?.skillKey || !String(body.skillKey).trim()) {
|
||||
return fail(400, 'skillKey is required');
|
||||
}
|
||||
const result = await updateSkillConfig(body.skillKey, {
|
||||
apiKey: body.apiKey,
|
||||
env: body.env,
|
||||
enabled: body.enabled,
|
||||
});
|
||||
return ok(result);
|
||||
} catch (error) {
|
||||
return fail(500, error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
}
|
||||
|
||||
if (pathname === '/api/clawhub/capability' && method === 'GET') {
|
||||
try {
|
||||
return ok({
|
||||
success: true,
|
||||
capability: await ctx.clawHubService.getMarketplaceCapability(),
|
||||
});
|
||||
} catch (error) {
|
||||
return fail(500, error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
}
|
||||
|
||||
if (pathname === '/api/clawhub/search' && method === 'POST') {
|
||||
try {
|
||||
const body = parseJsonBody<Record<string, unknown>>(request.body);
|
||||
return ok({
|
||||
success: true,
|
||||
results: await ctx.clawHubService.search(body as { query: string; limit?: number }),
|
||||
});
|
||||
} catch (error) {
|
||||
return fail(500, error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
}
|
||||
|
||||
if (pathname === '/api/clawhub/install' && method === 'POST') {
|
||||
try {
|
||||
const body = parseJsonBody<Record<string, unknown>>(request.body);
|
||||
await ctx.clawHubService.install(body as { slug: string; version?: string; force?: boolean });
|
||||
return ok({ success: true });
|
||||
} catch (error) {
|
||||
return fail(500, error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
}
|
||||
|
||||
if (pathname === '/api/clawhub/uninstall' && method === 'POST') {
|
||||
try {
|
||||
const body = parseJsonBody<Record<string, unknown>>(request.body);
|
||||
await ctx.clawHubService.uninstall(body as { slug: string });
|
||||
return ok({ success: true });
|
||||
} catch (error) {
|
||||
return fail(500, error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
}
|
||||
|
||||
if (pathname === '/api/clawhub/list' && method === 'GET') {
|
||||
try {
|
||||
return ok({
|
||||
success: true,
|
||||
results: await ctx.clawHubService.listInstalled(),
|
||||
});
|
||||
} catch (error) {
|
||||
return fail(500, error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
}
|
||||
|
||||
if (pathname === '/api/clawhub/open-readme' && method === 'POST') {
|
||||
try {
|
||||
const body = parseJsonBody<{ slug?: string; skillKey?: string; baseDir?: string }>(request.body);
|
||||
await ctx.clawHubService.openSkillReadme(
|
||||
body.skillKey || body.slug || '',
|
||||
body.slug,
|
||||
body.baseDir,
|
||||
);
|
||||
return ok({ success: true });
|
||||
} catch (error) {
|
||||
return fail(500, error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
}
|
||||
|
||||
if (pathname === '/api/clawhub/open-path' && method === 'POST') {
|
||||
try {
|
||||
const body = parseJsonBody<{ slug?: string; skillKey?: string; baseDir?: string }>(request.body);
|
||||
await ctx.clawHubService.openSkillPath(
|
||||
body.skillKey || body.slug || '',
|
||||
body.slug,
|
||||
body.baseDir,
|
||||
);
|
||||
return ok({ success: true });
|
||||
} catch (error) {
|
||||
return fail(500, error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
}
|
||||
|
||||
if (pathname === '/api/clawhub/skills-dir' && method === 'GET') {
|
||||
try {
|
||||
return ok({
|
||||
success: true,
|
||||
path: `${getOpenClawConfigDir()}/skills`,
|
||||
});
|
||||
} catch (error) {
|
||||
return fail(500, error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
}
|
||||
|
||||
if (pathname === '/api/clawhub/open-skills-dir' && method === 'POST') {
|
||||
try {
|
||||
const skillsDir = `${getOpenClawConfigDir()}/skills`;
|
||||
const openResult = await shell.openPath(skillsDir);
|
||||
if (openResult) {
|
||||
return fail(500, openResult);
|
||||
}
|
||||
return ok({ success: true });
|
||||
} catch (error) {
|
||||
return fail(500, error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user