misc: improve timeout handling and warnings in skills (#211)
This commit is contained in:
@@ -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.",
|
||||
|
||||
@@ -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": "新しいスキルを検索して機能を拡張しましょう。",
|
||||
|
||||
@@ -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": "搜索新技能以扩展您的能力。",
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user