diff --git a/electron/gateway/clawhub.ts b/electron/gateway/clawhub.ts index cbb7912eb..044d1a2c6 100644 --- a/electron/gateway/clawhub.ts +++ b/electron/gateway/clawhub.ts @@ -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; } } diff --git a/src/i18n/locales/en/skills.json b/src/i18n/locales/en/skills.json index 85f91af6b..8d39bc872 100644 --- a/src/i18n/locales/en/skills.json +++ b/src/i18n/locales/en/skills.json @@ -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.", diff --git a/src/i18n/locales/ja/skills.json b/src/i18n/locales/ja/skills.json index 9181273ac..8a6eff8a1 100644 --- a/src/i18n/locales/ja/skills.json +++ b/src/i18n/locales/ja/skills.json @@ -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": "新しいスキルを検索して機能を拡張しましょう。", diff --git a/src/i18n/locales/zh/skills.json b/src/i18n/locales/zh/skills.json index 9e9b3693c..abebc2f9c 100644 --- a/src/i18n/locales/zh/skills.json +++ b/src/i18n/locales/zh/skills.json @@ -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": "搜索新技能以扩展您的能力。", diff --git a/src/pages/Skills/index.tsx b/src/pages/Skills/index.tsx index 46f719553..0a79c0ea6 100644 --- a/src/pages/Skills/index.tsx +++ b/src/pages/Skills/index.tsx @@ -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 && ( - - {error} + + + {['fetchTimeoutError', 'fetchRateLimitError', 'timeoutError', 'rateLimitError'].includes(error) + ? t(`toast.${error}`, { path: skillsDirPath }) + : error} + )} @@ -905,6 +922,12 @@ export function Skills() { + + + + {t('marketplace.manualInstallHint', { path: skillsDirPath })} + +
@@ -970,9 +993,13 @@ export function Skills() { {searchError && ( - - - {t('marketplace.searchError')} + + + + {['searchTimeoutError', 'searchRateLimitError', 'timeoutError', 'rateLimitError'].includes(searchError.replace('Error: ', '')) + ? t(`toast.${searchError.replace('Error: ', '')}`, { path: skillsDirPath }) + : t('marketplace.searchError')} + )} diff --git a/src/stores/skills.ts b/src/stores/skills.ts index 9576728c9..2ae75f275 100644 --- a/src/stores/skills.ts +++ b/src/stores/skills.ts @@ -142,7 +142,13 @@ export const useSkillsStore = create((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((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((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