misc: improve timeout handling and warnings in skills (#211)

This commit is contained in:
Felix
2026-02-28 10:16:36 +08:00
committed by GitHub
Unverified
parent 538d285c71
commit d4f77a442c
6 changed files with 79 additions and 13 deletions

View File

@@ -181,7 +181,7 @@ export class ClawHubService {
}).filter((s): s is ClawHubSkillResult => s !== null);
} catch (error) {
console.error('ClawHub search error:', error);
return [];
throw error;
}
}
@@ -217,7 +217,7 @@ export class ClawHubService {
}).filter((s): s is ClawHubSkillResult => s !== null);
} catch (error) {
console.error('ClawHub explore error:', error);
return [];
throw error;
}
}

View File

@@ -57,11 +57,18 @@
"failedOpenFolder": "Failed to open skills folder",
"failedInstall": "Failed to install",
"failedUninstall": "Failed to uninstall",
"failedFolderNotFound": "Skills folder does not exist yet. Install a skill first."
"failedFolderNotFound": "Skills folder does not exist yet. Install a skill first.",
"searchTimeoutError": "Search timed out, check network. You can also search on ClawHub.ai, download the ZIP, and extract it to \"{{path}}\"",
"installTimeoutError": "Installation timed out, check network. You can also download the ZIP from ClawHub.ai and extract it to \"{{path}}\"",
"searchRateLimitError": "Search rate limit exceeded. You can also search on ClawHub.ai, download the ZIP, and extract it to \"{{path}}\"",
"installRateLimitError": "Installation rate limit exceeded. You can also download the ZIP from ClawHub.ai and extract it to \"{{path}}\"",
"fetchTimeoutError": "Fetching skills timed out, please check your network connection.",
"fetchRateLimitError": "Fetching skills rate limit exceeded, please try again later."
},
"marketplace": {
"title": "Marketplace",
"securityNote": "Click skill card to view its documentation and security information on ClawHub before installation.",
"manualInstallHint": "Network issues? You can always download skill ZIP archives from ClawHub.ai and extract them manually into \"{{path}}\".",
"searching": "Searching ClawHub...",
"noResults": "No skills found matching your search.",
"emptyPrompt": "Search for new skills to expand your capabilities.",

View File

@@ -57,11 +57,18 @@
"failedOpenFolder": "スキルフォルダを開けませんでした",
"failedInstall": "インストールに失敗しました",
"failedUninstall": "アンインストールに失敗しました",
"failedFolderNotFound": "スキルフォルダがまだ存在しません。先にスキルをインストールしてください。"
"failedFolderNotFound": "スキルフォルダがまだ存在しません。先にスキルをインストールしてください。",
"searchTimeoutError": "検索がタイムアウトしました。ClawHub.aiで検索してZIPをダウンロードし、\"{{path}}\" に展開することも可能です",
"installTimeoutError": "インストールがタイムアウトしました。ClawHub.aiでZIPをダウンロードし、\"{{path}}\" に展開することも可能です",
"searchRateLimitError": "検索リクエストの制限を超過しました。ClawHub.aiで検索してZIPをダウンロードし、\"{{path}}\" に展開することも可能です",
"installRateLimitError": "インストールリクエストの制限を超過しました。ClawHub.aiからZIPをダウンロードし、\"{{path}}\" に展開することも可能です",
"fetchTimeoutError": "スキルリストの取得がタイムアウトしました。ネットワークを確認してください。",
"fetchRateLimitError": "スキルリスト取得のリクエスト制限を超過しました。後でお試しください。"
},
"marketplace": {
"title": "マーケットプレイス",
"securityNote": "インストール前にスキルカードをクリックして、ClawHubでドキュメントとセキュリティ情報を確認してください。",
"manualInstallHint": "ネットワークに問題がありますかいつでもClawHub.aiからスキルのZIPをダウンロードし、手動で \"{{path}}\" に展開してインストールできます。",
"searching": "ClawHubを検索中...",
"noResults": "検索に一致するスキルが見つかりません。",
"emptyPrompt": "新しいスキルを検索して機能を拡張しましょう。",

View File

@@ -57,11 +57,18 @@
"failedOpenFolder": "无法打开技能文件夹",
"failedInstall": "安装失败",
"failedUninstall": "卸载失败",
"failedFolderNotFound": "技能文件夹尚不存在,请先安装一个技能。"
"failedFolderNotFound": "技能文件夹尚不存在,请先安装一个技能。",
"searchTimeoutError": "搜索超时,请检查网络。您也可访问 ClawHub.ai 搜索并下载压缩包,解压到 \"{{path}}\"",
"installTimeoutError": "安装超时,请检查网络。您也可在 ClawHub.ai 下载该技能压缩包,解压到 \"{{path}}\"",
"searchRateLimitError": "搜索请求过于频繁。您也可访问 ClawHub.ai 搜索并下载压缩包,解压到 \"{{path}}\"",
"installRateLimitError": "安装请求过于频繁。您也可在 ClawHub.ai 下载该技能压缩包,解压到 \"{{path}}\"",
"fetchTimeoutError": "获取技能列表超时,请检查网络。",
"fetchRateLimitError": "获取技能列表请求过于频繁,请稍后再试。"
},
"marketplace": {
"title": "市场",
"securityNote": "安装前请点击技能卡片,在 ClawHub 上查看其文档和安全信息。",
"manualInstallHint": "遇到网络问题?您可以随时从 ClawHub.ai 下载技能压缩包,并将其解压至 \"{{path}}\" 目录来完成手动安装。",
"searching": "正在搜索 ClawHub...",
"noResults": "未找到匹配的技能。",
"emptyPrompt": "搜索新技能以扩展您的能力。",

View File

@@ -637,6 +637,14 @@ export function Skills() {
}
}, [t]);
const [skillsDirPath, setSkillsDirPath] = useState('~/.openclaw/skills');
useEffect(() => {
window.electron.ipcRenderer.invoke('openclaw:getSkillsDir')
.then((dir) => setSkillsDirPath(dir as string))
.catch(console.error);
}, []);
// Handle marketplace search
const handleMarketplaceSearch = useCallback((e: React.FormEvent) => {
e.preventDefault();
@@ -659,9 +667,14 @@ export function Skills() {
await enableSkill(slug);
toast.success(t('toast.installed'));
} catch (err) {
toast.error(t('toast.failedInstall') + ': ' + String(err));
const errorMessage = err instanceof Error ? err.message : String(err);
if (['installTimeoutError', 'installRateLimitError'].includes(errorMessage)) {
toast.error(t(`toast.${errorMessage}`, { path: skillsDirPath }), { duration: 10000 });
} else {
toast.error(t('toast.failedInstall') + ': ' + errorMessage);
}
}
}, [installSkill, enableSkill, t]);
}, [installSkill, enableSkill, t, skillsDirPath]);
// Initial marketplace load (Discovery)
useEffect(() => {
@@ -798,8 +811,12 @@ export function Skills() {
{error && (
<Card className="border-destructive">
<CardContent className="py-4 text-destructive flex items-center gap-2">
<AlertCircle className="h-5 w-5" />
{error}
<AlertCircle className="h-5 w-5 shrink-0" />
<span>
{['fetchTimeoutError', 'fetchRateLimitError', 'timeoutError', 'rateLimitError'].includes(error)
? t(`toast.${error}`, { path: skillsDirPath })
: error}
</span>
</CardContent>
</Card>
)}
@@ -905,6 +922,12 @@ export function Skills() {
</div>
</CardContent>
</Card>
<Card className="border-info/30 bg-info/5">
<CardContent className="py-3 text-sm flex items-start gap-2 text-muted-foreground">
<Download className="h-4 w-4 mt-0.5 shrink-0" />
<span>{t('marketplace.manualInstallHint', { path: skillsDirPath })}</span>
</CardContent>
</Card>
<div className="flex gap-4">
<form onSubmit={handleMarketplaceSearch} className="flex-1 flex gap-2">
<div className="relative flex-1">
@@ -970,9 +993,13 @@ export function Skills() {
{searchError && (
<Card className="border-destructive/50 bg-destructive/5">
<CardContent className="py-3 text-sm text-destructive flex items-center gap-2">
<AlertCircle className="h-4 w-4" />
<span>{t('marketplace.searchError')}</span>
<CardContent className="py-3 text-sm text-destructive flex items-start gap-2">
<AlertCircle className="h-4 w-4 mt-0.5 shrink-0" />
<span>
{['searchTimeoutError', 'searchRateLimitError', 'timeoutError', 'rateLimitError'].includes(searchError.replace('Error: ', ''))
? t(`toast.${searchError.replace('Error: ', '')}`, { path: skillsDirPath })
: t('marketplace.searchError')}
</span>
</CardContent>
</Card>
)}

View File

@@ -142,7 +142,13 @@ export const useSkillsStore = create<SkillsState>((set, get) => ({
set({ skills: combinedSkills, loading: false });
} catch (error) {
console.error('Failed to fetch skills:', error);
set({ loading: false });
let errorMsg = error instanceof Error ? error.message : String(error);
if (errorMsg.includes('Timeout')) {
errorMsg = 'timeoutError';
} else if (errorMsg.toLowerCase().includes('rate limit')) {
errorMsg = 'rateLimitError';
}
set({ loading: false, error: errorMsg });
}
},
@@ -153,6 +159,12 @@ export const useSkillsStore = create<SkillsState>((set, get) => ({
if (result.success) {
set({ searchResults: result.results || [] });
} else {
if (result.error?.includes('Timeout')) {
throw new Error('searchTimeoutError');
}
if (result.error?.toLowerCase().includes('rate limit')) {
throw new Error('searchRateLimitError');
}
throw new Error(result.error || 'Search failed');
}
} catch (error) {
@@ -167,6 +179,12 @@ export const useSkillsStore = create<SkillsState>((set, get) => ({
try {
const result = await window.electron.ipcRenderer.invoke('clawhub:install', { slug, version }) as { success: boolean; error?: string };
if (!result.success) {
if (result.error?.includes('Timeout')) {
throw new Error('installTimeoutError');
}
if (result.error?.toLowerCase().includes('rate limit')) {
throw new Error('installRateLimitError');
}
throw new Error(result.error || 'Install failed');
}
// Refresh skills after install